Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig CAN_M_CAN
tristate "Bosch M_CAN support"
select CAN_RX_OFFLOAD
help
Say Y here if you want support for Bosch M_CAN controller framework.
This is common support for devices that embed the Bosch M_CAN IP.
if CAN_M_CAN
config CAN_M_CAN_PCI
tristate "Generic PCI Bus based M_CAN driver"
depends on PCI
help
Say Y here if you want to support Bosch M_CAN controller connected
to the pci bus.
config CAN_M_CAN_PLATFORM
tristate "Bosch M_CAN support for io-mapped devices"
depends on HAS_IOMEM
help
Say Y here if you want support for IO Mapped Bosch M_CAN controller.
This support is for devices that have the Bosch M_CAN controller
IP embedded into the device and the IP is IO Mapped to the processor.
config CAN_M_CAN_TCAN4X5X
depends on SPI
select REGMAP_SPI
tristate "TCAN4X5X M_CAN device"
help
Say Y here if you want support for Texas Instruments TCAN4x5x
M_CAN controller. This device is a peripheral device that uses the
SPI bus for communication.
endif
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the Bosch M_CAN controller driver.
#
obj-$(CONFIG_CAN_M_CAN) += m_can.o
obj-$(CONFIG_CAN_M_CAN_PCI) += m_can_pci.o
obj-$(CONFIG_CAN_M_CAN_PLATFORM) += m_can_platform.o
obj-$(CONFIG_CAN_M_CAN_TCAN4X5X) += tcan4x5x.o
tcan4x5x-objs :=
tcan4x5x-objs += tcan4x5x-core.o
tcan4x5x-objs += tcan4x5x-regmap.o
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* CAN bus driver for Bosch M_CAN controller
* Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
*/
#ifndef _CAN_M_CAN_H_
#define _CAN_M_CAN_H_
#include <linux/can/core.h>
#include <linux/can/dev.h>
#include <linux/can/rx-offload.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/freezer.h>
#include <linux/hrtimer.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
/* m_can lec values */
enum m_can_lec_type {
LEC_NO_ERROR = 0,
LEC_STUFF_ERROR,
LEC_FORM_ERROR,
LEC_ACK_ERROR,
LEC_BIT1_ERROR,
LEC_BIT0_ERROR,
LEC_CRC_ERROR,
LEC_NO_CHANGE,
};
enum m_can_mram_cfg {
MRAM_SIDF = 0,
MRAM_XIDF,
MRAM_RXF0,
MRAM_RXF1,
MRAM_RXB,
MRAM_TXE,
MRAM_TXB,
MRAM_CFG_NUM,
};
/* address offset and element number for each FIFO/Buffer in the Message RAM */
struct mram_cfg {
u16 off;
u8 num;
};
struct m_can_classdev;
struct m_can_ops {
/* Device specific call backs */
int (*clear_interrupts)(struct m_can_classdev *cdev);
u32 (*read_reg)(struct m_can_classdev *cdev, int reg);
int (*write_reg)(struct m_can_classdev *cdev, int reg, int val);
int (*read_fifo)(struct m_can_classdev *cdev, int addr_offset, void *val, size_t val_count);
int (*write_fifo)(struct m_can_classdev *cdev, int addr_offset,
const void *val, size_t val_count);
int (*init)(struct m_can_classdev *cdev);
int (*deinit)(struct m_can_classdev *cdev);
};
struct m_can_tx_op {
struct m_can_classdev *cdev;
struct work_struct work;
struct sk_buff *skb;
bool submit;
};
struct m_can_classdev {
struct can_priv can;
struct can_rx_offload offload;
struct napi_struct napi;
struct net_device *net;
struct device *dev;
struct clk *hclk;
struct clk *cclk;
struct reset_control *rst;
struct workqueue_struct *tx_wq;
struct phy *transceiver;
ktime_t irq_timer_wait;
const struct m_can_ops *ops;
int version;
u32 irqstatus;
int pm_clock_support;
int pm_wake_source;
int is_peripheral;
bool irq_edge_triggered;
// Cached M_CAN_IE register content
u32 active_interrupts;
u32 rx_max_coalesced_frames_irq;
u32 rx_coalesce_usecs_irq;
u32 tx_max_coalesced_frames;
u32 tx_max_coalesced_frames_irq;
u32 tx_coalesce_usecs_irq;
// Store this internally to avoid fetch delays on peripheral chips
u32 tx_fifo_putidx;
/* Protects shared state between start_xmit and m_can_isr */
spinlock_t tx_handling_spinlock;
int tx_fifo_in_flight;
struct m_can_tx_op *tx_ops;
int tx_fifo_size;
int next_tx_op;
int nr_txs_without_submit;
/* bitfield of fifo elements that will be submitted together */
u32 tx_peripheral_submit;
struct mram_cfg mcfg[MRAM_CFG_NUM];
struct hrtimer hrtimer;
struct pinctrl *pinctrl;
struct pinctrl_state *pinctrl_state_wakeup;
};
struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, int sizeof_priv);
void m_can_class_free_dev(struct net_device *net);
int m_can_class_register(struct m_can_classdev *cdev);
void m_can_class_unregister(struct m_can_classdev *cdev);
int m_can_class_get_clocks(struct m_can_classdev *cdev);
int m_can_check_mram_cfg(struct m_can_classdev *cdev, u32 mram_max_size);
int m_can_class_suspend(struct device *dev);
int m_can_class_resume(struct device *dev);
#endif /* _CAN_M_H_ */
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Specific M_CAN Glue
*
* Copyright (C) 2018-2020 Intel Corporation
* Author: Felipe Balbi (Intel)
* Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
* Author: Raymond Tan <raymond.tan@intel.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include "m_can.h"
#define M_CAN_PCI_MMIO_BAR 0
#define M_CAN_CLOCK_FREQ_EHL 200000000
#define CTL_CSR_INT_CTL_OFFSET 0x508
struct m_can_pci_priv {
struct m_can_classdev cdev;
void __iomem *base;
};
static inline struct m_can_pci_priv *cdev_to_priv(struct m_can_classdev *cdev)
{
return container_of(cdev, struct m_can_pci_priv, cdev);
}
static u32 iomap_read_reg(struct m_can_classdev *cdev, int reg)
{
struct m_can_pci_priv *priv = cdev_to_priv(cdev);
return readl(priv->base + reg);
}
static int iomap_read_fifo(struct m_can_classdev *cdev, int offset, void *val, size_t val_count)
{
struct m_can_pci_priv *priv = cdev_to_priv(cdev);
void __iomem *src = priv->base + offset;
while (val_count--) {
*(unsigned int *)val = ioread32(src);
val += 4;
src += 4;
}
return 0;
}
static int iomap_write_reg(struct m_can_classdev *cdev, int reg, int val)
{
struct m_can_pci_priv *priv = cdev_to_priv(cdev);
writel(val, priv->base + reg);
return 0;
}
static int iomap_write_fifo(struct m_can_classdev *cdev, int offset,
const void *val, size_t val_count)
{
struct m_can_pci_priv *priv = cdev_to_priv(cdev);
void __iomem *dst = priv->base + offset;
while (val_count--) {
iowrite32(*(unsigned int *)val, dst);
val += 4;
dst += 4;
}
return 0;
}
static const struct m_can_ops m_can_pci_ops = {
.read_reg = iomap_read_reg,
.write_reg = iomap_write_reg,
.write_fifo = iomap_write_fifo,
.read_fifo = iomap_read_fifo,
};
static int m_can_pci_probe(struct pci_dev *pci, const struct pci_device_id *id)
{
struct device *dev = &pci->dev;
struct m_can_classdev *mcan_class;
struct m_can_pci_priv *priv;
void __iomem *base;
int ret;
ret = pcim_enable_device(pci);
if (ret)
return ret;
pci_set_master(pci);
ret = pcim_iomap_regions(pci, BIT(M_CAN_PCI_MMIO_BAR), pci_name(pci));
if (ret)
return ret;
base = pcim_iomap_table(pci)[M_CAN_PCI_MMIO_BAR];
if (!base) {
dev_err(dev, "failed to map BARs\n");
return -ENOMEM;
}
mcan_class = m_can_class_allocate_dev(&pci->dev,
sizeof(struct m_can_pci_priv));
if (IS_ERR(mcan_class))
return PTR_ERR(mcan_class);
priv = cdev_to_priv(mcan_class);
priv->base = base;
ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_ALL_TYPES);
if (ret < 0)
goto err_free_dev;
mcan_class->dev = &pci->dev;
mcan_class->net->irq = pci_irq_vector(pci, 0);
mcan_class->pm_clock_support = 1;
mcan_class->pm_wake_source = 0;
mcan_class->can.clock.freq = id->driver_data;
mcan_class->irq_edge_triggered = true;
mcan_class->ops = &m_can_pci_ops;
pci_set_drvdata(pci, mcan_class);
ret = m_can_class_register(mcan_class);
if (ret)
goto err_free_irq;
/* Enable interrupt control at CAN wrapper IP */
writel(0x1, base + CTL_CSR_INT_CTL_OFFSET);
pm_runtime_set_autosuspend_delay(dev, 1000);
pm_runtime_use_autosuspend(dev);
pm_runtime_put_noidle(dev);
pm_runtime_allow(dev);
return 0;
err_free_irq:
pci_free_irq_vectors(pci);
err_free_dev:
m_can_class_free_dev(mcan_class->net);
return ret;
}
static void m_can_pci_remove(struct pci_dev *pci)
{
struct m_can_classdev *mcan_class = pci_get_drvdata(pci);
struct m_can_pci_priv *priv = cdev_to_priv(mcan_class);
pm_runtime_forbid(&pci->dev);
pm_runtime_get_noresume(&pci->dev);
/* Disable interrupt control at CAN wrapper IP */
writel(0x0, priv->base + CTL_CSR_INT_CTL_OFFSET);
m_can_class_unregister(mcan_class);
m_can_class_free_dev(mcan_class->net);
pci_free_irq_vectors(pci);
}
static __maybe_unused int m_can_pci_suspend(struct device *dev)
{
return m_can_class_suspend(dev);
}
static __maybe_unused int m_can_pci_resume(struct device *dev)
{
return m_can_class_resume(dev);
}
static SIMPLE_DEV_PM_OPS(m_can_pci_pm_ops,
m_can_pci_suspend, m_can_pci_resume);
static const struct pci_device_id m_can_pci_id_table[] = {
{ PCI_VDEVICE(INTEL, 0x4bc1), M_CAN_CLOCK_FREQ_EHL, },
{ PCI_VDEVICE(INTEL, 0x4bc2), M_CAN_CLOCK_FREQ_EHL, },
{ } /* Terminating Entry */
};
MODULE_DEVICE_TABLE(pci, m_can_pci_id_table);
static struct pci_driver m_can_pci_driver = {
.name = "m_can_pci",
.probe = m_can_pci_probe,
.remove = m_can_pci_remove,
.id_table = m_can_pci_id_table,
.driver = {
.pm = &m_can_pci_pm_ops,
},
};
module_pci_driver(m_can_pci_driver);
MODULE_AUTHOR("Felipe Balbi (Intel)");
MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CAN bus driver for Bosch M_CAN controller on PCI bus");
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0
// IOMapped CAN bus driver for Bosch M_CAN controller
// Copyright (C) 2014 Freescale Semiconductor, Inc.
// Dong Aisheng <aisheng.dong@nxp.com>
//
// Copyright (C) 2018-19 Texas Instruments Incorporated - http://www.ti.com/
#include <linux/hrtimer.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include "m_can.h"
struct m_can_plat_priv {
struct m_can_classdev cdev;
void __iomem *base;
void __iomem *mram_base;
};
static inline struct m_can_plat_priv *cdev_to_priv(struct m_can_classdev *cdev)
{
return container_of(cdev, struct m_can_plat_priv, cdev);
}
static u32 iomap_read_reg(struct m_can_classdev *cdev, int reg)
{
struct m_can_plat_priv *priv = cdev_to_priv(cdev);
return readl(priv->base + reg);
}
static int iomap_read_fifo(struct m_can_classdev *cdev, int offset, void *val, size_t val_count)
{
struct m_can_plat_priv *priv = cdev_to_priv(cdev);
void __iomem *src = priv->mram_base + offset;
while (val_count--) {
*(unsigned int *)val = ioread32(src);
val += 4;
src += 4;
}
return 0;
}
static int iomap_write_reg(struct m_can_classdev *cdev, int reg, int val)
{
struct m_can_plat_priv *priv = cdev_to_priv(cdev);
writel(val, priv->base + reg);
return 0;
}
static int iomap_write_fifo(struct m_can_classdev *cdev, int offset,
const void *val, size_t val_count)
{
struct m_can_plat_priv *priv = cdev_to_priv(cdev);
void __iomem *dst = priv->mram_base + offset;
while (val_count--) {
iowrite32(*(unsigned int *)val, dst);
val += 4;
dst += 4;
}
return 0;
}
static const struct m_can_ops m_can_plat_ops = {
.read_reg = iomap_read_reg,
.write_reg = iomap_write_reg,
.write_fifo = iomap_write_fifo,
.read_fifo = iomap_read_fifo,
};
static int m_can_plat_probe(struct platform_device *pdev)
{
struct m_can_classdev *mcan_class;
struct m_can_plat_priv *priv;
struct resource *res;
void __iomem *addr;
void __iomem *mram_addr;
struct phy *transceiver;
int irq = 0, ret = 0;
mcan_class = m_can_class_allocate_dev(&pdev->dev,
sizeof(struct m_can_plat_priv));
if (IS_ERR(mcan_class))
return PTR_ERR(mcan_class);
priv = cdev_to_priv(mcan_class);
ret = m_can_class_get_clocks(mcan_class);
if (ret)
goto probe_fail;
addr = devm_platform_ioremap_resource_byname(pdev, "m_can");
if (IS_ERR(addr)) {
ret = PTR_ERR(addr);
goto probe_fail;
}
if (device_property_present(mcan_class->dev, "interrupts") ||
device_property_present(mcan_class->dev, "interrupt-names")) {
irq = platform_get_irq_byname(pdev, "int0");
if (irq < 0) {
ret = irq;
goto probe_fail;
}
}
/* message ram could be shared */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "message_ram");
if (!res) {
ret = -ENODEV;
goto probe_fail;
}
mram_addr = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!mram_addr) {
ret = -ENOMEM;
goto probe_fail;
}
transceiver = devm_phy_optional_get(&pdev->dev, NULL);
if (IS_ERR(transceiver)) {
ret = PTR_ERR(transceiver);
dev_err_probe(&pdev->dev, ret, "failed to get phy\n");
goto probe_fail;
}
if (transceiver)
mcan_class->can.bitrate_max = transceiver->attrs.max_link_rate;
priv->base = addr;
priv->mram_base = mram_addr;
mcan_class->net->irq = irq;
mcan_class->pm_clock_support = 1;
mcan_class->pm_wake_source = 0;
mcan_class->can.clock.freq = clk_get_rate(mcan_class->cclk);
mcan_class->dev = &pdev->dev;
mcan_class->transceiver = transceiver;
mcan_class->ops = &m_can_plat_ops;
mcan_class->is_peripheral = false;
platform_set_drvdata(pdev, mcan_class);
pm_runtime_enable(mcan_class->dev);
ret = m_can_class_register(mcan_class);
if (ret)
goto out_runtime_disable;
return ret;
out_runtime_disable:
pm_runtime_disable(mcan_class->dev);
probe_fail:
m_can_class_free_dev(mcan_class->net);
return ret;
}
static __maybe_unused int m_can_suspend(struct device *dev)
{
return m_can_class_suspend(dev);
}
static __maybe_unused int m_can_resume(struct device *dev)
{
return m_can_class_resume(dev);
}
static void m_can_plat_remove(struct platform_device *pdev)
{
struct m_can_plat_priv *priv = platform_get_drvdata(pdev);
struct m_can_classdev *mcan_class = &priv->cdev;
m_can_class_unregister(mcan_class);
pm_runtime_disable(mcan_class->dev);
m_can_class_free_dev(mcan_class->net);
}
static int __maybe_unused m_can_runtime_suspend(struct device *dev)
{
struct m_can_plat_priv *priv = dev_get_drvdata(dev);
struct m_can_classdev *mcan_class = &priv->cdev;
clk_disable_unprepare(mcan_class->cclk);
clk_disable_unprepare(mcan_class->hclk);
return 0;
}
static int __maybe_unused m_can_runtime_resume(struct device *dev)
{
struct m_can_plat_priv *priv = dev_get_drvdata(dev);
struct m_can_classdev *mcan_class = &priv->cdev;
int err;
err = clk_prepare_enable(mcan_class->hclk);
if (err)
return err;
err = clk_prepare_enable(mcan_class->cclk);
if (err)
clk_disable_unprepare(mcan_class->hclk);
return err;
}
static const struct dev_pm_ops m_can_pmops = {
SET_RUNTIME_PM_OPS(m_can_runtime_suspend,
m_can_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(m_can_suspend, m_can_resume)
};
static const struct of_device_id m_can_of_table[] = {
{ .compatible = "bosch,m_can", .data = NULL },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, m_can_of_table);
static struct platform_driver m_can_plat_driver = {
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = m_can_of_table,
.pm = &m_can_pmops,
},
.probe = m_can_plat_probe,
.remove = m_can_plat_remove,
};
module_platform_driver(m_can_plat_driver);
MODULE_AUTHOR("Dong Aisheng <aisheng.dong@nxp.com>");
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("M_CAN driver for IO Mapped Bosch controllers");
@@ -0,0 +1,610 @@
// SPDX-License-Identifier: GPL-2.0
// SPI to CAN driver for the Texas Instruments TCAN4x5x
// Copyright (C) 2018-19 Texas Instruments Incorporated - http://www.ti.com/
#include "tcan4x5x.h"
#define TCAN4X5X_EXT_CLK_DEF 40000000
#define TCAN4X5X_DEV_ID1 0x00
#define TCAN4X5X_DEV_ID1_TCAN 0x4e414354 /* ASCII TCAN */
#define TCAN4X5X_DEV_ID2 0x04
#define TCAN4X5X_REV 0x08
#define TCAN4X5X_STATUS 0x0C
#define TCAN4X5X_ERROR_STATUS_MASK 0x10
#define TCAN4X5X_CONTROL 0x14
#define TCAN4X5X_CONFIG 0x800
#define TCAN4X5X_TS_PRESCALE 0x804
#define TCAN4X5X_TEST_REG 0x808
#define TCAN4X5X_INT_FLAGS 0x820
#define TCAN4X5X_MCAN_INT_REG 0x824
#define TCAN4X5X_INT_EN 0x830
/* Interrupt bits */
#define TCAN4X5X_CANBUSTERMOPEN_INT_EN BIT(30)
#define TCAN4X5X_CANHCANL_INT_EN BIT(29)
#define TCAN4X5X_CANHBAT_INT_EN BIT(28)
#define TCAN4X5X_CANLGND_INT_EN BIT(27)
#define TCAN4X5X_CANBUSOPEN_INT_EN BIT(26)
#define TCAN4X5X_CANBUSGND_INT_EN BIT(25)
#define TCAN4X5X_CANBUSBAT_INT_EN BIT(24)
#define TCAN4X5X_UVSUP_INT_EN BIT(22)
#define TCAN4X5X_UVIO_INT_EN BIT(21)
#define TCAN4X5X_TSD_INT_EN BIT(19)
#define TCAN4X5X_ECCERR_INT_EN BIT(16)
#define TCAN4X5X_CANINT_INT_EN BIT(15)
#define TCAN4X5X_LWU_INT_EN BIT(14)
#define TCAN4X5X_CANSLNT_INT_EN BIT(10)
#define TCAN4X5X_CANDOM_INT_EN BIT(8)
#define TCAN4X5X_CANBUS_ERR_INT_EN BIT(5)
#define TCAN4X5X_BUS_FAULT BIT(4)
#define TCAN4X5X_MCAN_INT BIT(1)
#define TCAN4X5X_ENABLE_TCAN_INT \
(TCAN4X5X_MCAN_INT | TCAN4X5X_BUS_FAULT | \
TCAN4X5X_CANBUS_ERR_INT_EN | TCAN4X5X_CANINT_INT_EN)
/* MCAN Interrupt bits */
#define TCAN4X5X_MCAN_IR_ARA BIT(29)
#define TCAN4X5X_MCAN_IR_PED BIT(28)
#define TCAN4X5X_MCAN_IR_PEA BIT(27)
#define TCAN4X5X_MCAN_IR_WD BIT(26)
#define TCAN4X5X_MCAN_IR_BO BIT(25)
#define TCAN4X5X_MCAN_IR_EW BIT(24)
#define TCAN4X5X_MCAN_IR_EP BIT(23)
#define TCAN4X5X_MCAN_IR_ELO BIT(22)
#define TCAN4X5X_MCAN_IR_BEU BIT(21)
#define TCAN4X5X_MCAN_IR_BEC BIT(20)
#define TCAN4X5X_MCAN_IR_DRX BIT(19)
#define TCAN4X5X_MCAN_IR_TOO BIT(18)
#define TCAN4X5X_MCAN_IR_MRAF BIT(17)
#define TCAN4X5X_MCAN_IR_TSW BIT(16)
#define TCAN4X5X_MCAN_IR_TEFL BIT(15)
#define TCAN4X5X_MCAN_IR_TEFF BIT(14)
#define TCAN4X5X_MCAN_IR_TEFW BIT(13)
#define TCAN4X5X_MCAN_IR_TEFN BIT(12)
#define TCAN4X5X_MCAN_IR_TFE BIT(11)
#define TCAN4X5X_MCAN_IR_TCF BIT(10)
#define TCAN4X5X_MCAN_IR_TC BIT(9)
#define TCAN4X5X_MCAN_IR_HPM BIT(8)
#define TCAN4X5X_MCAN_IR_RF1L BIT(7)
#define TCAN4X5X_MCAN_IR_RF1F BIT(6)
#define TCAN4X5X_MCAN_IR_RF1W BIT(5)
#define TCAN4X5X_MCAN_IR_RF1N BIT(4)
#define TCAN4X5X_MCAN_IR_RF0L BIT(3)
#define TCAN4X5X_MCAN_IR_RF0F BIT(2)
#define TCAN4X5X_MCAN_IR_RF0W BIT(1)
#define TCAN4X5X_MCAN_IR_RF0N BIT(0)
#define TCAN4X5X_ENABLE_MCAN_INT \
(TCAN4X5X_MCAN_IR_TC | TCAN4X5X_MCAN_IR_RF0N | \
TCAN4X5X_MCAN_IR_RF1N | TCAN4X5X_MCAN_IR_RF0F | \
TCAN4X5X_MCAN_IR_RF1F)
#define TCAN4X5X_MRAM_START 0x8000
#define TCAN4X5X_MRAM_SIZE 0x800
#define TCAN4X5X_MCAN_OFFSET 0x1000
#define TCAN4X5X_CLEAR_ALL_INT 0xffffffff
#define TCAN4X5X_SET_ALL_INT 0xffffffff
#define TCAN4X5X_MODE_SEL_MASK (BIT(7) | BIT(6))
#define TCAN4X5X_MODE_SLEEP 0x00
#define TCAN4X5X_MODE_STANDBY BIT(6)
#define TCAN4X5X_MODE_NORMAL BIT(7)
#define TCAN4X5X_NWKRQ_VOLTAGE_VIO BIT(19)
#define TCAN4X5X_DISABLE_WAKE_MSK (BIT(31) | BIT(30))
#define TCAN4X5X_DISABLE_INH_MSK BIT(9)
#define TCAN4X5X_SW_RESET BIT(2)
#define TCAN4X5X_MCAN_CONFIGURED BIT(5)
#define TCAN4X5X_WATCHDOG_EN BIT(3)
#define TCAN4X5X_WD_60_MS_TIMER 0
#define TCAN4X5X_WD_600_MS_TIMER BIT(28)
#define TCAN4X5X_WD_3_S_TIMER BIT(29)
#define TCAN4X5X_WD_6_S_TIMER (BIT(28) | BIT(29))
struct tcan4x5x_version_info {
const char *name;
u32 id2_register;
bool has_wake_pin;
bool has_state_pin;
};
enum {
TCAN4552 = 0,
TCAN4553,
TCAN4X5X,
};
static const struct tcan4x5x_version_info tcan4x5x_versions[] = {
[TCAN4552] = {
.name = "4552",
.id2_register = 0x32353534,
},
[TCAN4553] = {
.name = "4553",
.id2_register = 0x33353534,
},
/* generic version with no id2_register at the end */
[TCAN4X5X] = {
.name = "generic",
.has_wake_pin = true,
.has_state_pin = true,
},
};
static inline struct tcan4x5x_priv *cdev_to_priv(struct m_can_classdev *cdev)
{
return container_of(cdev, struct tcan4x5x_priv, cdev);
}
static void tcan4x5x_check_wake(struct tcan4x5x_priv *priv)
{
int wake_state = 0;
if (priv->device_state_gpio)
wake_state = gpiod_get_value(priv->device_state_gpio);
if (priv->device_wake_gpio && wake_state) {
gpiod_set_value(priv->device_wake_gpio, 0);
usleep_range(5, 50);
gpiod_set_value(priv->device_wake_gpio, 1);
}
}
static int tcan4x5x_reset(struct tcan4x5x_priv *priv)
{
int ret = 0;
if (priv->reset_gpio) {
gpiod_set_value(priv->reset_gpio, 1);
/* tpulse_width minimum 30us */
usleep_range(30, 100);
gpiod_set_value(priv->reset_gpio, 0);
} else {
ret = regmap_write(priv->regmap, TCAN4X5X_CONFIG,
TCAN4X5X_SW_RESET);
if (ret)
return ret;
}
usleep_range(700, 1000);
return ret;
}
static u32 tcan4x5x_read_reg(struct m_can_classdev *cdev, int reg)
{
struct tcan4x5x_priv *priv = cdev_to_priv(cdev);
u32 val;
regmap_read(priv->regmap, TCAN4X5X_MCAN_OFFSET + reg, &val);
return val;
}
static int tcan4x5x_read_fifo(struct m_can_classdev *cdev, int addr_offset,
void *val, size_t val_count)
{
struct tcan4x5x_priv *priv = cdev_to_priv(cdev);
return regmap_bulk_read(priv->regmap, TCAN4X5X_MRAM_START + addr_offset, val, val_count);
}
static int tcan4x5x_write_reg(struct m_can_classdev *cdev, int reg, int val)
{
struct tcan4x5x_priv *priv = cdev_to_priv(cdev);
return regmap_write(priv->regmap, TCAN4X5X_MCAN_OFFSET + reg, val);
}
static int tcan4x5x_write_fifo(struct m_can_classdev *cdev,
int addr_offset, const void *val, size_t val_count)
{
struct tcan4x5x_priv *priv = cdev_to_priv(cdev);
return regmap_bulk_write(priv->regmap, TCAN4X5X_MRAM_START + addr_offset, val, val_count);
}
static int tcan4x5x_power_enable(struct regulator *reg, int enable)
{
if (IS_ERR_OR_NULL(reg))
return 0;
if (enable)
return regulator_enable(reg);
else
return regulator_disable(reg);
}
static int tcan4x5x_write_tcan_reg(struct m_can_classdev *cdev,
int reg, int val)
{
struct tcan4x5x_priv *priv = cdev_to_priv(cdev);
return regmap_write(priv->regmap, reg, val);
}
static int tcan4x5x_clear_interrupts(struct m_can_classdev *cdev)
{
int ret;
ret = tcan4x5x_write_tcan_reg(cdev, TCAN4X5X_STATUS,
TCAN4X5X_CLEAR_ALL_INT);
if (ret)
return ret;
return tcan4x5x_write_tcan_reg(cdev, TCAN4X5X_INT_FLAGS,
TCAN4X5X_CLEAR_ALL_INT);
}
static int tcan4x5x_init(struct m_can_classdev *cdev)
{
struct tcan4x5x_priv *tcan4x5x = cdev_to_priv(cdev);
int ret;
tcan4x5x_check_wake(tcan4x5x);
ret = tcan4x5x_clear_interrupts(cdev);
if (ret)
return ret;
ret = tcan4x5x_write_tcan_reg(cdev, TCAN4X5X_INT_EN,
TCAN4X5X_ENABLE_TCAN_INT);
if (ret)
return ret;
ret = tcan4x5x_write_tcan_reg(cdev, TCAN4X5X_ERROR_STATUS_MASK,
TCAN4X5X_CLEAR_ALL_INT);
if (ret)
return ret;
ret = regmap_update_bits(tcan4x5x->regmap, TCAN4X5X_CONFIG,
TCAN4X5X_MODE_SEL_MASK, TCAN4X5X_MODE_NORMAL);
if (ret)
return ret;
if (tcan4x5x->nwkrq_voltage_vio) {
ret = regmap_set_bits(tcan4x5x->regmap, TCAN4X5X_CONFIG,
TCAN4X5X_NWKRQ_VOLTAGE_VIO);
if (ret)
return ret;
}
return ret;
}
static int tcan4x5x_deinit(struct m_can_classdev *cdev)
{
struct tcan4x5x_priv *tcan4x5x = cdev_to_priv(cdev);
return regmap_update_bits(tcan4x5x->regmap, TCAN4X5X_CONFIG,
TCAN4X5X_MODE_SEL_MASK, TCAN4X5X_MODE_STANDBY);
};
static int tcan4x5x_disable_wake(struct m_can_classdev *cdev)
{
struct tcan4x5x_priv *tcan4x5x = cdev_to_priv(cdev);
return regmap_update_bits(tcan4x5x->regmap, TCAN4X5X_CONFIG,
TCAN4X5X_DISABLE_WAKE_MSK, 0x00);
}
static int tcan4x5x_disable_state(struct m_can_classdev *cdev)
{
struct tcan4x5x_priv *tcan4x5x = cdev_to_priv(cdev);
return regmap_update_bits(tcan4x5x->regmap, TCAN4X5X_CONFIG,
TCAN4X5X_DISABLE_INH_MSK, 0x01);
}
static const struct tcan4x5x_version_info
*tcan4x5x_find_version(struct tcan4x5x_priv *priv)
{
u32 val;
int ret;
ret = regmap_read(priv->regmap, TCAN4X5X_DEV_ID1, &val);
if (ret)
return ERR_PTR(ret);
if (val != TCAN4X5X_DEV_ID1_TCAN) {
dev_err(&priv->spi->dev, "Not a tcan device %x\n", val);
return ERR_PTR(-ENODEV);
}
ret = regmap_read(priv->regmap, TCAN4X5X_DEV_ID2, &val);
if (ret)
return ERR_PTR(ret);
for (int i = 0; i != ARRAY_SIZE(tcan4x5x_versions); ++i) {
const struct tcan4x5x_version_info *vinfo = &tcan4x5x_versions[i];
if (!vinfo->id2_register || val == vinfo->id2_register) {
dev_info(&priv->spi->dev, "Detected TCAN device version %s\n",
vinfo->name);
return vinfo;
}
}
return &tcan4x5x_versions[TCAN4X5X];
}
static void tcan4x5x_get_dt_data(struct m_can_classdev *cdev)
{
struct tcan4x5x_priv *tcan4x5x = cdev_to_priv(cdev);
tcan4x5x->nwkrq_voltage_vio =
of_property_read_bool(cdev->dev->of_node, "ti,nwkrq-voltage-vio");
}
static int tcan4x5x_get_gpios(struct m_can_classdev *cdev)
{
struct tcan4x5x_priv *tcan4x5x = cdev_to_priv(cdev);
int ret;
tcan4x5x->device_wake_gpio = devm_gpiod_get_optional(cdev->dev,
"device-wake",
GPIOD_OUT_HIGH);
if (IS_ERR(tcan4x5x->device_wake_gpio)) {
if (PTR_ERR(tcan4x5x->device_wake_gpio) == -EPROBE_DEFER)
return -EPROBE_DEFER;
tcan4x5x->device_wake_gpio = NULL;
}
tcan4x5x->reset_gpio = devm_gpiod_get_optional(cdev->dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(tcan4x5x->reset_gpio))
tcan4x5x->reset_gpio = NULL;
ret = tcan4x5x_reset(tcan4x5x);
if (ret)
return ret;
tcan4x5x->device_state_gpio = devm_gpiod_get_optional(cdev->dev,
"device-state",
GPIOD_IN);
if (IS_ERR(tcan4x5x->device_state_gpio))
tcan4x5x->device_state_gpio = NULL;
return 0;
}
static int tcan4x5x_check_gpios(struct m_can_classdev *cdev,
const struct tcan4x5x_version_info *version_info)
{
struct tcan4x5x_priv *tcan4x5x = cdev_to_priv(cdev);
int ret;
if (version_info->has_wake_pin && !tcan4x5x->device_wake_gpio) {
ret = tcan4x5x_disable_wake(cdev);
if (ret)
return ret;
}
if (version_info->has_state_pin && !tcan4x5x->device_state_gpio) {
ret = tcan4x5x_disable_state(cdev);
if (ret)
return ret;
}
return 0;
}
static const struct m_can_ops tcan4x5x_ops = {
.init = tcan4x5x_init,
.deinit = tcan4x5x_deinit,
.read_reg = tcan4x5x_read_reg,
.write_reg = tcan4x5x_write_reg,
.write_fifo = tcan4x5x_write_fifo,
.read_fifo = tcan4x5x_read_fifo,
.clear_interrupts = tcan4x5x_clear_interrupts,
};
static int tcan4x5x_can_probe(struct spi_device *spi)
{
const struct tcan4x5x_version_info *version_info;
struct tcan4x5x_priv *priv;
struct m_can_classdev *mcan_class;
int freq, ret;
mcan_class = m_can_class_allocate_dev(&spi->dev,
sizeof(struct tcan4x5x_priv));
if (IS_ERR(mcan_class))
return PTR_ERR(mcan_class);
ret = m_can_check_mram_cfg(mcan_class, TCAN4X5X_MRAM_SIZE);
if (ret)
goto out_m_can_class_free_dev;
priv = cdev_to_priv(mcan_class);
priv->power = devm_regulator_get_optional(&spi->dev, "vsup");
if (IS_ERR(priv->power)) {
if (PTR_ERR(priv->power) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto out_m_can_class_free_dev;
}
priv->power = NULL;
}
mcan_class->cclk = devm_clk_get(mcan_class->dev, "cclk");
if (IS_ERR(mcan_class->cclk)) {
dev_err(&spi->dev, "no CAN clock source defined\n");
freq = TCAN4X5X_EXT_CLK_DEF;
} else {
freq = clk_get_rate(mcan_class->cclk);
}
/* Sanity check */
if (freq < 20000000 || freq > TCAN4X5X_EXT_CLK_DEF) {
dev_err(&spi->dev, "Clock frequency is out of supported range %d\n",
freq);
ret = -ERANGE;
goto out_m_can_class_free_dev;
}
priv->spi = spi;
mcan_class->pm_clock_support = 0;
mcan_class->pm_wake_source = device_property_read_bool(&spi->dev, "wakeup-source");
mcan_class->can.clock.freq = freq;
mcan_class->dev = &spi->dev;
mcan_class->ops = &tcan4x5x_ops;
mcan_class->is_peripheral = true;
mcan_class->net->irq = spi->irq;
spi_set_drvdata(spi, priv);
/* Configure the SPI bus */
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret) {
dev_err(&spi->dev, "SPI setup failed %pe\n", ERR_PTR(ret));
goto out_m_can_class_free_dev;
}
ret = tcan4x5x_regmap_init(priv);
if (ret) {
dev_err(&spi->dev, "regmap init failed %pe\n", ERR_PTR(ret));
goto out_m_can_class_free_dev;
}
ret = tcan4x5x_power_enable(priv->power, 1);
if (ret) {
dev_err(&spi->dev, "Enabling regulator failed %pe\n",
ERR_PTR(ret));
goto out_m_can_class_free_dev;
}
ret = tcan4x5x_get_gpios(mcan_class);
if (ret) {
dev_err(&spi->dev, "Getting gpios failed %pe\n", ERR_PTR(ret));
goto out_power;
}
version_info = tcan4x5x_find_version(priv);
if (IS_ERR(version_info)) {
ret = PTR_ERR(version_info);
goto out_power;
}
ret = tcan4x5x_check_gpios(mcan_class, version_info);
if (ret) {
dev_err(&spi->dev, "Checking gpios failed %pe\n", ERR_PTR(ret));
goto out_power;
}
tcan4x5x_get_dt_data(mcan_class);
tcan4x5x_check_wake(priv);
ret = tcan4x5x_write_tcan_reg(mcan_class, TCAN4X5X_INT_EN, 0);
if (ret) {
dev_err(&spi->dev, "Disabling interrupts failed %pe\n", ERR_PTR(ret));
goto out_power;
}
ret = tcan4x5x_clear_interrupts(mcan_class);
if (ret) {
dev_err(&spi->dev, "Clearing interrupts failed %pe\n", ERR_PTR(ret));
goto out_power;
}
if (mcan_class->pm_wake_source)
device_init_wakeup(&spi->dev, true);
ret = m_can_class_register(mcan_class);
if (ret) {
dev_err(&spi->dev, "Failed registering m_can device %pe\n",
ERR_PTR(ret));
goto out_power;
}
netdev_info(mcan_class->net, "TCAN4X5X successfully initialized.\n");
return 0;
out_power:
tcan4x5x_power_enable(priv->power, 0);
out_m_can_class_free_dev:
m_can_class_free_dev(mcan_class->net);
return ret;
}
static void tcan4x5x_can_remove(struct spi_device *spi)
{
struct tcan4x5x_priv *priv = spi_get_drvdata(spi);
m_can_class_unregister(&priv->cdev);
tcan4x5x_power_enable(priv->power, 0);
m_can_class_free_dev(priv->cdev.net);
}
static int __maybe_unused tcan4x5x_suspend(struct device *dev)
{
struct m_can_classdev *cdev = dev_get_drvdata(dev);
struct spi_device *spi = to_spi_device(dev);
if (cdev->pm_wake_source)
enable_irq_wake(spi->irq);
return m_can_class_suspend(dev);
}
static int __maybe_unused tcan4x5x_resume(struct device *dev)
{
struct m_can_classdev *cdev = dev_get_drvdata(dev);
struct spi_device *spi = to_spi_device(dev);
int ret = m_can_class_resume(dev);
if (cdev->pm_wake_source)
disable_irq_wake(spi->irq);
return ret;
}
static const struct of_device_id tcan4x5x_of_match[] = {
{
.compatible = "ti,tcan4x5x",
}, {
/* sentinel */
},
};
MODULE_DEVICE_TABLE(of, tcan4x5x_of_match);
static const struct spi_device_id tcan4x5x_id_table[] = {
{
.name = "tcan4x5x",
}, {
/* sentinel */
},
};
MODULE_DEVICE_TABLE(spi, tcan4x5x_id_table);
static const struct dev_pm_ops tcan4x5x_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(tcan4x5x_suspend, tcan4x5x_resume)
};
static struct spi_driver tcan4x5x_can_driver = {
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = tcan4x5x_of_match,
.pm = &tcan4x5x_pm_ops,
},
.id_table = tcan4x5x_id_table,
.probe = tcan4x5x_can_probe,
.remove = tcan4x5x_can_remove,
};
module_spi_driver(tcan4x5x_can_driver);
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
MODULE_DESCRIPTION("Texas Instruments TCAN4x5x CAN driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0
//
// tcan4x5x - Texas Instruments TCAN4x5x Family CAN controller driver
//
// Copyright (c) 2020 Pengutronix,
// Marc Kleine-Budde <kernel@pengutronix.de>
// Copyright (c) 2018-2019 Texas Instruments Incorporated
// http://www.ti.com/
#include "tcan4x5x.h"
#define TCAN4X5X_SPI_INSTRUCTION_WRITE (0x61 << 24)
#define TCAN4X5X_SPI_INSTRUCTION_READ (0x41 << 24)
#define TCAN4X5X_MAX_REGISTER 0x87fc
static int tcan4x5x_regmap_gather_write(void *context,
const void *reg, size_t reg_len,
const void *val, size_t val_len)
{
struct spi_device *spi = context;
struct tcan4x5x_priv *priv = spi_get_drvdata(spi);
struct tcan4x5x_map_buf *buf_tx = &priv->map_buf_tx;
struct spi_transfer xfer[] = {
{
.tx_buf = buf_tx,
.len = sizeof(buf_tx->cmd) + val_len,
},
};
memcpy(&buf_tx->cmd, reg, sizeof(buf_tx->cmd.cmd) +
sizeof(buf_tx->cmd.addr));
tcan4x5x_spi_cmd_set_len(&buf_tx->cmd, val_len);
memcpy(buf_tx->data, val, val_len);
return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
}
static int tcan4x5x_regmap_write(void *context, const void *data, size_t count)
{
return tcan4x5x_regmap_gather_write(context, data, sizeof(__be32),
data + sizeof(__be32),
count - sizeof(__be32));
}
static int tcan4x5x_regmap_read(void *context,
const void *reg_buf, size_t reg_len,
void *val_buf, size_t val_len)
{
struct spi_device *spi = context;
struct tcan4x5x_priv *priv = spi_get_drvdata(spi);
struct tcan4x5x_map_buf *buf_rx = &priv->map_buf_rx;
struct tcan4x5x_map_buf *buf_tx = &priv->map_buf_tx;
struct spi_transfer xfer[2] = {
{
.tx_buf = buf_tx,
}
};
struct spi_message msg;
int err;
spi_message_init(&msg);
spi_message_add_tail(&xfer[0], &msg);
memcpy(&buf_tx->cmd, reg_buf, sizeof(buf_tx->cmd.cmd) +
sizeof(buf_tx->cmd.addr));
tcan4x5x_spi_cmd_set_len(&buf_tx->cmd, val_len);
if (spi->controller->flags & SPI_CONTROLLER_HALF_DUPLEX) {
xfer[0].len = sizeof(buf_tx->cmd);
xfer[1].rx_buf = val_buf;
xfer[1].len = val_len;
spi_message_add_tail(&xfer[1], &msg);
} else {
xfer[0].rx_buf = buf_rx;
xfer[0].len = sizeof(buf_tx->cmd) + val_len;
if (TCAN4X5X_SANITIZE_SPI)
memset(buf_tx->data, 0x0, val_len);
}
err = spi_sync(spi, &msg);
if (err)
return err;
if (!(spi->controller->flags & SPI_CONTROLLER_HALF_DUPLEX))
memcpy(val_buf, buf_rx->data, val_len);
return 0;
}
static const struct regmap_range tcan4x5x_reg_table_wr_range[] = {
/* Device ID and SPI Registers */
regmap_reg_range(0x000c, 0x0010),
/* Device configuration registers and Interrupt Flags*/
regmap_reg_range(0x0800, 0x080c),
regmap_reg_range(0x0820, 0x0820),
regmap_reg_range(0x0830, 0x0830),
/* M_CAN */
regmap_reg_range(0x100c, 0x102c),
regmap_reg_range(0x1048, 0x1048),
regmap_reg_range(0x1050, 0x105c),
regmap_reg_range(0x1080, 0x1088),
regmap_reg_range(0x1090, 0x1090),
regmap_reg_range(0x1098, 0x10a0),
regmap_reg_range(0x10a8, 0x10b0),
regmap_reg_range(0x10b8, 0x10c0),
regmap_reg_range(0x10c8, 0x10c8),
regmap_reg_range(0x10d0, 0x10d4),
regmap_reg_range(0x10e0, 0x10e4),
regmap_reg_range(0x10f0, 0x10f0),
regmap_reg_range(0x10f8, 0x10f8),
/* MRAM */
regmap_reg_range(0x8000, 0x87fc),
};
static const struct regmap_range tcan4x5x_reg_table_rd_range[] = {
regmap_reg_range(0x0000, 0x0010), /* Device ID and SPI Registers */
regmap_reg_range(0x0800, 0x0830), /* Device configuration registers and Interrupt Flags*/
regmap_reg_range(0x1000, 0x10fc), /* M_CAN */
regmap_reg_range(0x8000, 0x87fc), /* MRAM */
};
static const struct regmap_access_table tcan4x5x_reg_table_wr = {
.yes_ranges = tcan4x5x_reg_table_wr_range,
.n_yes_ranges = ARRAY_SIZE(tcan4x5x_reg_table_wr_range),
};
static const struct regmap_access_table tcan4x5x_reg_table_rd = {
.yes_ranges = tcan4x5x_reg_table_rd_range,
.n_yes_ranges = ARRAY_SIZE(tcan4x5x_reg_table_rd_range),
};
static const struct regmap_config tcan4x5x_regmap = {
.reg_bits = 24,
.reg_stride = 4,
.pad_bits = 8,
.val_bits = 32,
.wr_table = &tcan4x5x_reg_table_wr,
.rd_table = &tcan4x5x_reg_table_rd,
.max_register = TCAN4X5X_MAX_REGISTER,
.cache_type = REGCACHE_NONE,
.read_flag_mask = (__force unsigned long)
cpu_to_be32(TCAN4X5X_SPI_INSTRUCTION_READ),
.write_flag_mask = (__force unsigned long)
cpu_to_be32(TCAN4X5X_SPI_INSTRUCTION_WRITE),
};
static const struct regmap_bus tcan4x5x_bus = {
.write = tcan4x5x_regmap_write,
.gather_write = tcan4x5x_regmap_gather_write,
.read = tcan4x5x_regmap_read,
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
.val_format_endian_default = REGMAP_ENDIAN_BIG,
.max_raw_read = 256,
.max_raw_write = 256,
};
int tcan4x5x_regmap_init(struct tcan4x5x_priv *priv)
{
priv->regmap = devm_regmap_init(&priv->spi->dev, &tcan4x5x_bus,
priv->spi, &tcan4x5x_regmap);
return PTR_ERR_OR_ZERO(priv->regmap);
}
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0
*
* tcan4x5x - Texas Instruments TCAN4x5x Family CAN controller driver
*
* Copyright (c) 2020 Pengutronix,
* Marc Kleine-Budde <kernel@pengutronix.de>
*/
#ifndef _TCAN4X5X_H
#define _TCAN4X5X_H
#include <linux/gpio/consumer.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include "m_can.h"
#define TCAN4X5X_SANITIZE_SPI 1
struct __packed tcan4x5x_buf_cmd {
u8 cmd;
__be16 addr;
u8 len;
};
struct tcan4x5x_map_buf {
struct tcan4x5x_buf_cmd cmd;
u8 data[256 * sizeof(u32)];
} ____cacheline_aligned;
struct tcan4x5x_priv {
struct m_can_classdev cdev;
struct regmap *regmap;
struct spi_device *spi;
struct gpio_desc *reset_gpio;
struct gpio_desc *device_wake_gpio;
struct gpio_desc *device_state_gpio;
struct regulator *power;
struct tcan4x5x_map_buf map_buf_rx;
struct tcan4x5x_map_buf map_buf_tx;
bool nwkrq_voltage_vio;
};
static inline void
tcan4x5x_spi_cmd_set_len(struct tcan4x5x_buf_cmd *cmd, u8 len)
{
/* number of u32 */
cmd->len = len >> 2;
}
int tcan4x5x_regmap_init(struct tcan4x5x_priv *priv);
#endif