本文主要以rockchip的rk3568平台基础,介绍can 控制器、硬件电路和底层驱动。
CAN(控制器区域网络)总线是一种稳健的车载总线标准,它允许微控制器和设备在没有主机计算机的应用中相互通信。它是一个基于消息的协议,最初是为了在汽车中多路复用电线节省铜资源而设计的,但也用于许多其他场合。
CAN控制器具有以下特性:
一下所列特性,并一定全部支持,部分需要授权,具体可以和RK技术支持或者业务联系
这个版本的CAN控制器包括符合旧软件应用程序的以前版本的CAN逻辑,还包括新的CANFD逻辑,它支持传输和接收CAN和CANFD帧
在实际测试中rk3568并不支持CANFD
接收滤波器使用接受滤波ID寄存器和ID屏蔽寄存器进行滤波。接收滤波器使用多次滤波。它包括旧版接收滤波器和5个附加ID滤波器。每个ID滤波器对都有一个滤波屏蔽寄存器和一个滤波ID寄存器。每个滤波器对由AFR寄存器中的相应FILTER CTRL位控制。对于每个使用ID寄存器和ID屏蔽寄存器(由接收滤波寄存器控制)对ID的所有位进行采样,并与ID寄存器进行比较。这不是每次对位进行采样时就进行比较。并且不检查ID屏蔽寄存器中为1的位。一旦ID的所有位都通过了滤波,控制器就会考虑帧数据为期望的ID,从而执行下一操作。
- 使用接收滤波ID和屏蔽寄存器进行多次滤波
- 包含旧版滤波器和5个额外ID滤波器
- 每个滤波器有对应的屏蔽寄存器和ID寄存器
- AFR寄存器的FILTER CTRL位控制每个滤波器
- 对ID寄存器的位采样,与ID寄存器比较,跳过屏蔽寄存器的1位
- ID全部通过滤波则接收帧执行后续操作
CAN FD协议定义了两种位速率,第一种用于仲裁阶段,位时间更长;第二种用于数据阶段,位时间相同或更短。
第一种位速率的定义与CAN协议规范中的标称位速率和标称位时间相同。第二种位速率的数据位速率及数据位时间需要单独的配置寄存器组进行定义。
位定时逻辑通过定时寄存器中的缓冲位控制采样点位置,以确保数据采样的精确性。位定时逻辑接收识别的时钟频率分频信号,设置总线定时参数,建立同步参数,并调整总线传输速率。同时监控总线并在设定的定时向总线发送待发送消息。
根据CAN协议的规定:CAN总线在无消息发送时始终处于高电平,并监视总线上的连续衰落位。此时,总线为空闲状态,仲裁优先级低,随时准备接收数据。当总线检测到从衰落位向主导位的转变时,证明帧起始位开始传输,总线在位起始同步段进行硬同步。然后,在接收消息的过程中,一旦检测到接近采样点的转变边沿,并且边沿与同步段的相位错误小于同步宽度(SJW取值超过SJW时执行一次重新同步),控制器执行一次重新同步,一个数据传输过程中可以执行多次重新同步。
CAN总线的每个位(Bit)的周期 Tbit = 1 / Baudrate。根据CAN规范,每个位的时间内又可细分成4段:
同步段(Synchronization Segment,Tss)
传播段(Propagation Segment, Tps)
相位缓冲段1(Phase Buffer Segment 1, Tpbs1)
相位缓冲段2(Phase Buffer Segment 2, Tpbs2)
CAN控制器为了适应各种波特率,对上面四个段的时间长度,不是使用纳秒(ns)或微秒(us)来度量,而是使用节拍来度量,技术资料中将这个节拍称为时间量子(Time quantum, Tq)。
例如500k的波特率,每个Tbit是 2000ns,如果分为 10 个节拍,则每个 Tq 为 200ns。
定义一个计数器来计数基本时间单元t_sclk
当计数值达到总线定时寄存器中定义的TSEG1、TSEG2以及设计中定义的同步段长度时,系统将生成对应的转移条件:go_seg1、go_seg2、go_sync
通过判断这些转移条件,控制状态机在上述三种状态之间循环
三种状态分别对应:
当计数器计数t_sclk的数值达到对应阈值时触发转移条件
控制状态机根据转移条件在三种状态间切换
从而形成了完整的CAN位定时管理机制
采样点(Sampling Point):
发送点(Sending Point):
CAN通信协议中明确定义这两种同步机制,使CAN网络能可靠工作,是CAN总线可靠性的重要保障。
根据总线协议,以扩展帧格式的数据帧为例,接收数据状态机如下:
CAN FD的发送实现流程,包括访问控制、并串转换、发送同步等内容
传输事件FIFO(TEF)允许应用程序跟踪消息传输的顺序和时间。TEF的工作原理类似于一个接收FIFO,它是一个32位的×16 FIFO。它不存储接收到的消息,而是存储传输的消息。仅在设置了TEF.anble时才保存消息。传输消息的序列号(SEQ)被复制到TEF对象中。不存储有效载荷数据。如果设置了测试项,则传输的消息将带有时间戳。在TXE FIFO中传输消息格式存储如下:
CAN FD控制器模块包含一个时间基准计数器(TBC)。TBC是一个32位的自由运行计数器,按照CANCLK的倍数递增,并在达到最大值后重新归零。TBC可以通过写任何值到TBC来清零。
CAN总线中存在五种类型的错误,多种错误可能同时发生。
控制器必须在通电或硬件重置后配置寄存器。在控制器的操作期间,可以发送软件重位请求并重新配置(重新初始化),如下所示。初始化完成后,控制器进入工作模式,将待发送的帧发送到缓冲区,然后设置命令寄存器的“发送请求”标志,开始发送。
can1: can@fe580000 {
compatible = "rockchip,canfd-1.0";
reg = <0x0 0xfe580000 0x0 0x1000>;
interrupts = ;
clocks = <&cru CLK_CAN1>, <&cru PCLK_CAN1>;
clock-names = "baudclk", "apb_pclk";
resets = <&cru SRST_CAN1>, <&cru SRST_P_CAN1>;
reset-names = "can", "can-apb";
tx-fifo-depth = <1>;
rx-fifo-depth = <6>;
status = "disabled";
};
can1 {
/omit-if-no-ref/
can1m0_pins: can1m0-pins {
rockchip,pins =
/* can1_rxm0 */
<1 RK_PA0 3 &pcfg_pull_none>,
/* can1_txm0 */
<1 RK_PA1 3 &pcfg_pull_none>;
};
/omit-if-no-ref/
can1m1_pins: can1m1-pins {
rockchip,pins =
/* can1_rxm1 */
<4 RK_PC2 3 &pcfg_pull_none>,
/* can1_txm1 */
<4 RK_PC3 3 &pcfg_pull_none>;
};
};
&can1 {
assigned-clocks = <&cru CLK_CAN1>;
assigned-clock-rates = <150000000>;
pinctrl-names = "default";
pinctrl-0 = <&can1m1_pins>;
status = "okay";
};
此设备树片段提供了 Rockchip CAN1 接口的配置信息,包括其基址、中断设置、时钟源、引脚配置和状态。在设备初始化过程中,Linux 内核将使用此设备树片段来设置和管理 CAN1 接口。
can1: can@fe580000
:这是一个名为 "can1" 的设备节点,具有兼容地址 "fe580000"。它表示设备树中的 CAN1 控制器。
compatible
:指定与此设备节点匹配的兼容字符串,以将其与兼容的驱动程序关联起来。在这种情况下,它是 "rockchip,canfd-1.0"。
reg
:定义了该设备的寄存器空间的地址和大小。它位于地址 0xfe580000,大小为 0x1000。
interrupts
:指定与此设备相关的中断。它连接到 GIC(通用中断控制器),使用 SPI(共享外设中断)编号 2,并配置为电平触发、高电平有效的中断。
clocks
和 clock-names
:定义此设备使用的时钟源。CAN1 控制器使用两个时钟源:"baudclk" 和 "apb_pclk",它们都来自 "cru" 时钟控制器。
resets
和 reset-names
:定义此设备使用的复位线。"can" 和 "can-apb" 复位线连接到 "cru" 复位控制器。
tx-fifo-depth
和 rx-fifo-depth
:这些值定义了发送和接收 FIFO 的深度。发送 FIFO 深度设置为 1,接收 FIFO 深度设置为 6。
status
:此设备的状态最初设置为 "disabled"(禁用)。
can1
(在设备节点内部):此部分用于定义用于此 CAN1 接口的引脚配置。它指定了用于 CAN1_RXM0、CAN1_TXM0、CAN1_RXM1 和 CAN1_TXM1 的引脚。
can1m0_pins
:此子部分定义了用于 CAN1_RXM0 和 CAN1_TXM0 的引脚配置,它们分别与引脚 RK_PA0 和 RK_PA1 相关联,均将拉电阻配置设置为 "none"。
can1m1_pins
:此子部分定义了用于 CAN1_RXM1 和 CAN1_TXM1 的引脚配置,它们分别与引脚 RK_PC2 和 RK_PC3 相关联,均将拉电阻配置设置为 "none"。
&can1
:此部分引用了上面定义的 "can1" 设备节点。它指定了用于该 CAN1 接口的附加属性:
assigned-clocks
:它将 "CLK_CAN1" 时钟从 "cru" 时钟控制器分配给此设备节点。
assigned-clock-rates
:为此设备分配的时钟速率设置为 150,000,000 Hz。
pinctrl-names
和 pinctrl-0
:它指定了用于此设备的引脚控制设置。在这种情况下,使用 "default" 作为引脚控制设置,它引用了 "can1m1_pins" 配置,这是引脚的默认配置。
status
:CAN1 接口的状态设置为 "okay",表示它已启用并正在运行。
主要是得是Linux 5.10.110,代码路径为linux-5.10\drivers\net\can\rockchip\rockchip_canfd.c
static const struct of_device_id rockchip_canfd_of_match[] = {
{
.compatible = "rockchip,canfd-1.0",
.data = (void *)ROCKCHIP_CANFD_MODE
},
{
.compatible = "rockchip,can-2.0",
.data = (void *)ROCKCHIP_CAN_MODE
},
{
.compatible = "rockchip,rk3568-can-2.0",
.data = (void *)ROCKCHIP_RK3568_CAN_MODE
},
{},
};
static struct platform_driver rockchip_canfd_driver = {
.driver = {
.name = DRV_NAME,
.pm = &rockchip_canfd_dev_pm_ops,
.of_match_table = rockchip_canfd_of_match,
},
.probe = rockchip_canfd_probe,
.remove = rockchip_canfd_remove,
};
module_platform_driver(rockchip_canfd_driver);
static struct platform_driver rockchip_canfd_driver
:定义了名为 rockchip_canfd_driver
的静态平台驱动程序结构。这结构用于定义和管理特定设备的驱动程序。这里是结构的主要字段:
.driver
:这是一个包含与驱动程序有关信息的结构体。
.name
:指定了驱动程序的名称,通常是一个字符串常量。在这里,它使用了 DRV_NAME
,该名称可能在其他地方定义。
.pm
:指定了与电源管理相关的操作,这里是 rockchip_canfd_dev_pm_ops
。电源管理操作通常涉及设备的低功耗模式和唤醒等。
.of_match_table
:该字段用于指定设备树匹配表,以便将设备树中的设备与此驱动程序匹配。在这里,使用了 rockchip_canfd_of_match
,这可能是一个设备树匹配表的定义。
.probe
:这是指向探测函数(rockchip_canfd_probe
)的指针。探测函数用于初始化并注册设备。
.remove
:这是指向移除函数(rockchip_canfd_remove
)的指针。移除函数用于卸载和清理设备。
module_platform_driver(rockchip_canfd_driver)
:这是一个宏,用于将定义的平台驱动程序注册到内核。它将 rockchip_canfd_driver
传递给内核以进行初始化和管理。一旦内核加载了这个模块,它将自动执行 probe
函数来初始化设备。
设备指定的名字为rockchip,canfd-1.0,当设备树加载时可以匹配到这个驱动,进一步执行rockchip_canfd_probe,其主要任务是设置并注册设备,以便 Linux 内核能够与 Rockchip CANFD 控制器进行通信。
static int rockchip_canfd_probe(struct platform_device *pdev)
{
struct net_device *ndev;
struct rockchip_canfd *rcan;
struct resource *res;
void __iomem *addr;
int err, irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "could not get a valid irq\n");
return -ENODEV;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return -EBUSY;
ndev = alloc_candev(sizeof(struct rockchip_canfd), 1);
if (!ndev) {
dev_err(&pdev->dev, "could not allocate memory for CANFD device\n");
return -ENOMEM;
}
rcan = netdev_priv(ndev);
/* register interrupt handler */
err = devm_request_irq(&pdev->dev, irq, rockchip_canfd_interrupt,
0, ndev->name, ndev);
if (err) {
dev_err(&pdev->dev, "request_irq err: %d\n", err);
return err;
}
rcan->reset = devm_reset_control_array_get(&pdev->dev, false, false);
if (IS_ERR(rcan->reset)) {
if (PTR_ERR(rcan->reset) != -EPROBE_DEFER)
dev_err(&pdev->dev, "failed to get canfd reset lines\n");
return PTR_ERR(rcan->reset);
}
rcan->num_clks = devm_clk_bulk_get_all(&pdev->dev, &rcan->clks);
if (rcan->num_clks < 1)
return -ENODEV;
rcan->mode = (unsigned long)of_device_get_match_data(&pdev->dev);
rcan->base = addr;
rcan->can.clock.freq = clk_get_rate(rcan->clks[0].clk);
rcan->dev = &pdev->dev;
rcan->can.state = CAN_STATE_STOPPED;
switch (rcan->mode) {
case ROCKCHIP_CANFD_MODE:
rcan->can.bittiming_const = &rockchip_canfd_bittiming_const;
rcan->can.data_bittiming_const = &rockchip_canfd_data_bittiming_const;
rcan->can.do_set_mode = rockchip_canfd_set_mode;
rcan->can.do_get_berr_counter = rockchip_canfd_get_berr_counter;
rcan->can.do_set_bittiming = rockchip_canfd_set_bittiming;
rcan->can.do_set_data_bittiming = rockchip_canfd_set_bittiming;
rcan->can.ctrlmode = CAN_CTRLMODE_FD;
/* IFI CANFD can do both Bosch FD and ISO FD */
rcan->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
CAN_CTRLMODE_FD;
rcan->rx_fifo_shift = RX_FIFO_CNT0_SHIFT;
rcan->rx_fifo_mask = RX_FIFO_CNT0_MASK;
break;
case ROCKCHIP_CAN_MODE:
case ROCKCHIP_RK3568_CAN_MODE:
rcan->can.bittiming_const = &rockchip_canfd_bittiming_const;
rcan->can.do_set_mode = rockchip_canfd_set_mode;
rcan->can.do_get_berr_counter = rockchip_canfd_get_berr_counter;
rcan->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING |
CAN_CTRLMODE_LISTENONLY |
CAN_CTRLMODE_LOOPBACK |
CAN_CTRLMODE_3_SAMPLES;
rcan->rx_fifo_shift = RX_FIFO_CNT0_SHIFT;
rcan->rx_fifo_mask = RX_FIFO_CNT0_MASK;
break;
default:
return -EINVAL;
}
if (rcan->mode == ROCKCHIP_CAN_MODE) {
rcan->rx_fifo_shift = RX_FIFO_CNT1_SHIFT;
rcan->rx_fifo_mask = RX_FIFO_CNT1_MASK;
}
if (device_property_read_u32_array(&pdev->dev,
"rockchip,tx-invalid-info",
rcan->tx_invalid, 4))
rcan->txtorx = 1;
ndev->netdev_ops = &rockchip_canfd_netdev_ops;
ndev->irq = irq;
ndev->flags |= IFF_ECHO;
rcan->can.restart_ms = 1;
INIT_DELAYED_WORK(&rcan->tx_err_work, rockchip_canfd_tx_err_delay_work);
platform_set_drvdata(pdev, ndev);
SET_NETDEV_DEV(ndev, &pdev->dev);
pm_runtime_enable(&pdev->dev);
err = pm_runtime_get_sync(&pdev->dev);
if (err < 0) {
dev_err(&pdev->dev, "%s: pm_runtime_get failed(%d)\n",
__func__, err);
goto err_pmdisable;
}
err = register_candev(ndev);
if (err) {
dev_err(&pdev->dev, "registering %s failed (err=%d)\n",
DRV_NAME, err);
goto err_disableclks;
}
devm_can_led_init(ndev);
return 0;
err_disableclks:
pm_runtime_put(&pdev->dev);
err_pmdisable:
pm_runtime_disable(&pdev->dev);
free_candev(ndev);
return err;
}
irq = platform_get_irq(pdev, 0);
:获取 Rockchip CANFD 控制器的中断号。中断用于通知 CPU 发生了重要事件。如果无法获取中断号,将打印错误消息并返回错误码。
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
:获取 Rockchip CANFD 控制器的资源信息,这里是内存资源。这个资源包括了设备的物理地址范围。
addr = devm_ioremap_resource(&pdev->dev, res);
:使用 devm_ioremap_resource
函数将设备的物理地址映射到内核的虚拟地址空间。如果映射失败,将返回错误。
ndev = alloc_candev(sizeof(struct rockchip_canfd), 1);
:为 CAN 设备分配内存。这里使用了 alloc_candev
函数,分配了一个大小为 sizeof(struct rockchip_canfd)
的内存块来保存设备信息。如果分配失败,将返回错误。
rcan = netdev_priv(ndev);
:获取设备的私有数据结构 rockchip_canfd
的指针,以便后续使用。
err = devm_request_irq(&pdev->dev, irq, rockchip_canfd_interrupt, 0, ndev->name, ndev);
:注册中断处理函数 (rockchip_canfd_interrupt
) 来处理中断事件。如果注册失败,将打印错误消息并返回错误。
rcan->reset = devm_reset_control_array_get(&pdev->dev, false, false);
:获取 CAN 控制器的复位线。如果获取失败,将打印错误消息并返回错误。
rcan->num_clks = devm_clk_bulk_get_all(&pdev->dev, &rcan->clks);
:获取与 CAN 控制器相关的时钟信息。如果获取失败,将返回错误。
rcan->mode = (unsigned long)of_device_get_match_data(&pdev->dev);
:从设备树中获取设备的匹配数据,以确定控制器的工作模式。
初始化 rcan
结构的各个字段,包括时钟频率、设备、CAN 控制状态等,根据设备的工作模式设置不同的控制器参数。
通过检查设备树属性 "rockchip,tx-invalid-info" 来决定是否将发送错误状态传递给接收。
设置网络设备操作函数和其他网络设备相关参数,如中断号、标志等。
初始化延迟工作队列,用于处理发送错误。
启用设备的运行时电源管理,并获取运行时电源锁。
注册 CAN 设备,将设备信息添加到内核的 CAN 子系统中。
初始化 CAN 设备的 LED 控制。
需要关注的网络CAN设备被注册为网络设备
ndev = alloc_candev(sizeof(struct rockchip_canfd), 1);
if (!ndev) {
dev_err(&pdev->dev, "could not allocate memory for CANFD device\n");
return -ENOMEM;
}
。。。。。。。。。。。。。。。。。。。。。。。
err = register_candev(ndev);
if (err) {
dev_err(&pdev->dev, "registering %s failed (err=%d)\n",
DRV_NAME, err);
goto err_disableclks;
}
这个两个函数实际调用的dev.c 中的
/* Allocate and setup space for the CAN network device */
struct net_device *alloc_candev_mqs(int sizeof_priv, unsigned int echo_skb_max,
unsigned int txqs, unsigned int rxqs)
{
struct can_ml_priv *can_ml;
struct net_device *dev;
struct can_priv *priv;
int size;
/* We put the driver's priv, the CAN mid layer priv and the
* echo skb into the netdevice's priv. The memory layout for
* the netdev_priv is like this:
*
* +-------------------------+
* | driver's priv |
* +-------------------------+
* | struct can_ml_priv |
* +-------------------------+
* | array of struct sk_buff |
* +-------------------------+
*/
size = ALIGN(sizeof_priv, NETDEV_ALIGN) + sizeof(struct can_ml_priv);
if (echo_skb_max)
size = ALIGN(size, sizeof(struct sk_buff *)) +
echo_skb_max * sizeof(struct sk_buff *);
dev = alloc_netdev_mqs(size, "can%d", NET_NAME_UNKNOWN, can_setup,
txqs, rxqs);
if (!dev)
return NULL;
priv = netdev_priv(dev);
priv->dev = dev;
can_ml = (void *)priv + ALIGN(sizeof_priv, NETDEV_ALIGN);
can_set_ml_priv(dev, can_ml);
if (echo_skb_max) {
priv->echo_skb_max = echo_skb_max;
priv->echo_skb = (void *)priv +
(size - echo_skb_max * sizeof(struct sk_buff *));
}
priv->state = CAN_STATE_STOPPED;
INIT_DELAYED_WORK(&priv->restart_work, can_restart_work);
return dev;
}
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
static struct rtnl_link_ops can_link_ops __read_mostly = {
.kind = "can",
.netns_refund = true,
.maxtype = IFLA_CAN_MAX,
.policy = can_policy,
.setup = can_setup,
.validate = can_validate,
.newlink = can_newlink,
.changelink = can_changelink,
.dellink = can_dellink,
.get_size = can_get_size,
.fill_info = can_fill_info,
.get_xstats_size = can_get_xstats_size,
.fill_xstats = can_fill_xstats,
};
/* Register the CAN network device */
int register_candev(struct net_device *dev)
{
struct can_priv *priv = netdev_priv(dev);
/* Ensure termination_const, termination_const_cnt and
* do_set_termination consistency. All must be either set or
* unset.
*/
if ((!priv->termination_const != !priv->termination_const_cnt) ||
(!priv->termination_const != !priv->do_set_termination))
return -EINVAL;
if (!priv->bitrate_const != !priv->bitrate_const_cnt)
return -EINVAL;
if (!priv->data_bitrate_const != !priv->data_bitrate_const_cnt)
return -EINVAL;
dev->rtnl_link_ops = &can_link_ops;
netif_carrier_off(dev);
return register_netdev(dev);
}
之后CAN 设备可以作为网络设备操作了
https://blog.csdn.net/hans_yu/article/details/89400011