CAN 通信-底层

本文主要以rockchip的rk3568平台基础,介绍can 控制器、硬件电路和底层驱动。

rk3568 CAN 控制器

概览

CAN(控制器区域网络)总线是一种稳健的车载总线标准,它允许微控制器和设备在没有主机计算机的应用中相互通信。它是一个基于消息的协议,最初是为了在汽车中多路复用电线节省铜资源而设计的,但也用于许多其他场合。

CAN控制器具有以下特性:

  • 支持CAN 2.0B协议
  • 支持32位APB总线
  • 支持经典和FD模式的标准帧和扩展帧的发送和接收
  • 支持发送和接收数据帧、远程帧、过载帧、错误帧和帧间隔
  • 支持发送和接收错误计数
  • 支持接收滤波
  • 支持位错误、位填充错误、格式错误、ACK错误和CRC错误
  • 支持7种中断类型,所有中断都可以屏蔽
  • 支持查询CAN控制器状态
  • 支持捕获仲裁损失的仲裁位位置
  • 支持错误码检查
  • 支持自测试模式
  • 支持单次采样和三次采样可配置
  • 支持同步跳转宽度(SJW)可配置
  • 支持系统预分频系数(BRP)可配置
  • 支持位定时可配置
  • 支持环回自测试模式
  • 支持静默模式用于调试
  • 支持接收自发模式(rxstx_mode)
  • 支持TX和RX数据顺序选择
  • 支持覆盖接收数据模式
  • 支持自动重传
  • 支持总线关闭后自动总线上电
  • 支持空闲接收模式
  • 支持发送器延时补偿和采样点位置可配置
  • 支持睡眠模式
  • 支持时间戳
  • 支持发送事件FIFO
  • 支持2个发送缓冲区
  • 支持接收缓冲区/FIFO模式
  • 支持协议异常事件
  • 支持DMA

功能框图

一下所列特性,并一定全部支持,部分需要授权,具体可以和RK技术支持或者业务联系

这个版本的CAN控制器包括符合旧软件应用程序的以前版本的CAN逻辑,还包括新的CANFD逻辑,它支持传输和接收CAN和CANFD帧

在实际测试中rk3568并不支持CANFD

ACCEPTANCE FILTER接收滤波器

接收滤波器使用接受滤波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全部通过滤波则接收帧执行后续操作

BIT TIMING 位定时

Bit Timing Logic****位定时逻辑

CAN FD协议定义了两种位速率,第一种用于仲裁阶段,位时间更长;第二种用于数据阶段,位时间相同或更短。

第一种位速率的定义与CAN协议规范中的标称位速率和标称位时间相同。第二种位速率的数据位速率及数据位时间需要单独的配置寄存器组进行定义。

位定时逻辑通过定时寄存器中的缓冲位控制采样点位置,以确保数据采样的精确性。位定时逻辑接收识别的时钟频率分频信号,设置总线定时参数,建立同步参数,并调整总线传输速率。同时监控总线并在设定的定时向总线发送待发送消息。

根据CAN协议的规定:CAN总线在无消息发送时始终处于高电平,并监视总线上的连续衰落位。此时,总线为空闲状态,仲裁优先级低,随时准备接收数据。当总线检测到从衰落位向主导位的转变时,证明帧起始位开始传输,总线在位起始同步段进行硬同步。然后,在接收消息的过程中,一旦检测到接近采样点的转变边沿,并且边沿与同步段的相位错误小于同步宽度(SJW取值超过SJW时执行一次重新同步),控制器执行一次重新同步,一个数据传输过程中可以执行多次重新同步。

CAN的位时序构成

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。

  • tsync_seg、tprop_seg、tphase_seg1 和 tphase_seg2 是基本时间单位t_sclk的整数倍
  • t_sclk 是系统时钟clk的特定倍数,该倍数由总线定时寄存器中的分频系数BRP决定
  • 通过BRP操作获得计数器,该计数器对系统时钟clk进行计数
  • 当计数达到限值时,生成周期为t_sclk的时钟
  • 在系统设计中,考虑到每个时间段的周期性发生,位定时采用了三状态的状态机
  • 三状态分别对应同步段(sync_seg)、第1相位缓冲段(phase_seg1=phase_seg1+prop_seg)和第2相位缓冲段(phase_seg2)
  • 其中,PHASE_SEG1范围:116;PHASE_SEG2范围:18;BRP范围:1~64
  • 定义一个计数器来计数基本时间单元t_sclk

  • 当计数值达到总线定时寄存器中定义的TSEG1、TSEG2以及设计中定义的同步段长度时,系统将生成对应的转移条件:go_seg1、go_seg2、go_sync

  • 通过判断这些转移条件,控制状态机在上述三种状态之间循环

  • 三种状态分别对应:

    • 同步段(Sync_Seg)
    • 第1相段(Phase_Seg1)
    • 第2相段(Phase_Seg2)
  • 当计数器计数t_sclk的数值达到对应阈值时触发转移条件

  • 控制状态机根据转移条件在三种状态间切换

  • 从而形成了完整的CAN位定时管理机制

位时序转换
  • CAN FD帧的第一部分,直到BRS位,都是以标称位速率(NOMINAL BIT RATE)进行传输的。
  • 如果BRS位为隐性位,则从BRS位起到CRC分隔符结束,或者直到CAN FD控制器检测到错误条件从而开始发送错误帧的这段时间,使用数据位速率(DATA BIT RATE)。
  • CAN FD错误帧、ACK字段、帧结束、过载帧,以及CAN格式的所有帧,都是以标称位速率传输的。
  • 也就是说,CAN FD帧在BRS位之前使用标称位速率,BRS隐性时从BRS位到CRC分隔符使用数据位速率,错误帧等使用标称位速率。
  • 这种在一个帧中切换两种位速率的机制,是CAN FD相比普通CAN的一个重要改进,可以大大提高数据段的传输效率。

采样点(Sampling Point)和发送点(Sending Point)

采样点(Sampling Point):

  • 根据协议,采样点sample_point应位于phase_seg1和phase_seg2之间。设计中置于phase_seg2同步位置。
  • 采样脉冲宽度定义为一个系统时钟周期。考虑采样精确性,可以采用取三点采样取均值方法。每个采样点间隔一个tsclk。

发送点(Sending Point):

  • 根据协议,发送点tx_point的位置应该位于每个位时间的开始处,设计中与go_sync信号同步。
  • 此外,如果处在同步或重新同步中,tx_point将立即有效。
Bit Synchronization 位同步

CAN通信协议中明确定义这两种同步机制,使CAN网络能可靠工作,是CAN总线可靠性的重要保障。

  • 硬同步(Hard Synchronization): 当检测到总线上从隐性位到主导位的跳变时,满足同步条件的一个或多个节点会在特定时间将其同步段对齐到总线上传输的数据。硬同步发生在从1跳变到0的边沿,以控制边沿到采样点的距离。
    • 硬同步:所有节点必须同步到首先开始发送消息的节点的起始帧的导致边沿。每帧数据开始时,节点之间执行同步操作。
    • 硬同步实现:判断总线状态是否满足协议规定的帧起始条件,且节点不在待发送数据状态。此时生成硬同步标志信号hard_sync,脉冲宽度为一个系统时钟周期。
    • 在go_seg1信号的控制条件中加入hard_sync项,当hard_sync为'1'时go_seg1立即有效。
    • 因此,一旦满足硬同步条件,系统进入phase_seg1段,从而实现同步。
  • 重新同步(Resynchronization): 在接收消息的过程中,如果检测到接近采样点的跳变边沿,并且边沿与同步段的相位误差小于同步跳动宽度SJW,则执行重新同步,将采样点重新对齐到边沿。
    • 重新同步:除了每帧数据开始硬同步外,CAN通信协议还规定了每帧数据传输过程中的重新同步。当节点之间时序协调不理想时,会执行重新同步,使节点协作状态良好。
    • 对接收端来说,总线变化应发生在同步段内。一旦接收端状态违反此规定,将执行重新同步。
    • 需要重新同步的两种情况:
      1. 总线值'1'到'0'的跳变发生在同步段和采样点之间,即延迟跳变;
      2. 总线值'1'到'0'的跳变发生在采样点和同步段之间,即提前跳变。
    • 重新同步的目的是修正因不同节点之间时序偏差导致的采样点偏移,保证接收的可靠性。
    • 重新同步方法
      1. 重新同步条件:接收端在接收数据模式下(非帧间间隔或总线空闲),当检测到总线值'1'至'0'的跳变时生成重新同步标志信号resync。
      2. 根据resync信号位置判断是延迟跳变还是提前跳变。
      3. 通过计数器统计延迟时间或提前时间长度。
      4. 在该位对phase_seg1添加延迟时间或从phase_seg2减去提前时间,获得该位新的相段缓冲时间,从而实现重新同步。
  • 两种同步方法的目的是对齐采样点,确保接收端能可靠地对总线数据进行采样。
发送器延迟补偿(Transmitter Delay Compensation)机制
  • 在CAN FD传输的数据相位,只有一个节点在发送,其他都是接收状态。因此,传播延迟不会限制最大数据速率。
  • CAN FD控制器模块通过TXCAN引脚发送数据,并通过RXCAN引脚从本地CAN收发器接收发送的数据。收到的数据会延迟收发器的环路延迟时间。如果该延迟大于1 + DTSEG1,会检测到位错误。
  • 为了实现比收发器环路延迟还短的数据相位时间,实现了发送器延迟补偿机制。不再在DTSEG1后采样,而是计算一个二次采样点(SSP)在CAN FD消息的数据相位进行采样。
  • 发送器延迟补偿通过引入二次采样点,允许数据相位时间小于收发器延迟,提高了CAN FD的最大数据速率。

STREAM PROCESS 数据流处理

Data Buffering 数据缓存
  • 数据缓存包含发送缓冲区和接收缓冲区两个部分。
  • 发送缓冲区存储待发送CAN总线的数据,接收缓冲区存储从CAN总线接收的数据。
  • 缓冲区大小为13字节,包含帧信息、标识符和数据。
  • CPU配置传输信息后置位发送位启用发送。数据并行化后存入发送缓冲区等待发送。
  • 接收数据存入接收缓冲区,接收完毕中断后CPU读取数据并清空缓冲区。
  • CANFD设计中增加独立发送缓冲区、接收FIFO等新功能。
  • 发送缓冲区大小为18字,接收19字,包含时间戳信息。
  • 接收FIFO容量32位x128,可容纳6帧信息。
  • 两个发送请求位对应两个发送缓冲区。设置请求位后缓冲区发送数据。
  • 支持配置接收FIFO。FIFO使能时按格式存入FIFO,禁用时直接写入接收寄存器。
数据接收

根据总线协议,以扩展帧格式的数据帧为例,接收数据状态机如下:

发送数据

CAN FD的发送实现流程,包括访问控制、并串转换、发送同步等内容

  • 只有当请求位被设置时,CAN FD才访问TX块中的缓冲区消息空间。主机必须遵守访问规则以避免内存冲突。
  • 并行数据根据特定定时以串行方式传输到总线上,发送点为tx_point。发送计数器在有效数据部分自加,一帧发送完成或错误状态时清零。
  • 实现步骤:
    1. 根据当前帧类型,将所有发送数据组合成完全并行的数据链
    2. 使用tx_pointer作为数据链指针,获得要发送的串行数据信号tx_bit
    3. 根据当前状态确定发送的数据类型,确定下一个发送时间的要发送数据tx_next
    4. 通过发送点tx_point使数据发送信号tx与tx_next同步,获得真正的发送信号tx
  • 总结来说,CAN FD通过请求位访问缓冲区,并行数据串行化发送,在发送点同步完成最终发送。
位填充 Bit Stuffing
  • 位填充是为防止误码爆发而设置的功能。当相同电平持续5个比特时,插入一个反转的数据比特。
  • 在CAN FD格式的帧中,CRC序列采用了变化的位填充方法。在CRC序列的第一个比特之前强制插入一个固定的填充比特,即使前一字段的最后几个比特不满足CAN填充条件也如此。此外,CRC序列的每个4比特之后也插入一个固定填充比特,值为与其前一比特的反转值。
  • 发送端在发送数据帧和远程帧的SOF与CRC段之间的数据执行位填充操作。
  • 接收端在接收数据帧和远程帧的SOF与CRC段之间的数据执行位去填充操作,并检查去填充后的比特。
传输事件FIFO(Transmit Event FIFO

传输事件FIFO(TEF)允许应用程序跟踪消息传输的顺序和时间。TEF的工作原理类似于一个接收FIFO,它是一个32位的×16 FIFO。它不存储接收到的消息,而是存储传输的消息。仅在设置了TEF.anble时才保存消息。传输消息的序列号(SEQ)被复制到TEF对象中。不存储有效载荷数据。如果设置了测试项,则传输的消息将带有时间戳。在TXE FIFO中传输消息格式存储如下:

时间戳TIMESTAMP

CAN FD控制器模块包含一个时间基准计数器(TBC)。TBC是一个32位的自由运行计数器,按照CANCLK的倍数递增,并在达到最大值后重新归零。TBC可以通过写任何值到TBC来清零。

  • TIMESTAMP_CTRL用于配置TBC的分频器。
  • 设置TIMESTAMP_CTRL.TBCEN启用TBC。
  • 清除TBCEN会禁用、停止和重置TBC。
  • 在写入TBC之前,必须禁用TBC通过清除TBCEN。
  • 应用程序可以随时读取TBC。与任何多字节计数器一样,应用程序必须考虑计数器在读取不同字节之间递增和可能发生溢出。TBC的溢出会生成一个中断。
  • 在接收和发送帧的帧起始位的SAMPLE POINTS处捕获时间戳。

错误

CAN总线中存在五种类型的错误,多种错误可能同时发生。

  • 位错误(BIT ERROR):在总线上发送位的单元也会监视总线。位错误必须在监视到的位值与发送的位值不同时被检测到,除外情况是在"仲裁场"中的填充位流或"确认槽"期间发送"隐性"位。在这种情况下,监视到"支配"位时不会发生位错误。一个发送被动错误标志并检测到"支配"位的发送器不会将其解释为位错误。
  • 填充位错误(BIT STUFF ERROR):必须在应当通过位填充方法编码的消息字段中第6个连续的相等位级的位时间检测到填充位错误。
  • 格式错误(FORM ERROR):必须在固定格式位字段中包含一个或多个非法位时检测到格式错误(接收器在帧结束的最后一位期间检测到支配位,或在任何节点在错误分隔符或过载分隔符的最后一位期间检测到支配位除外)。当CAN FD格式CRC序列中固定填充位的值等于其前导位时,也应将其检测为格式错误。
  • 确认错误(ACK ERROR):每当发射器在确认槽期间没有监视到"支配"位时,必须由发射器检测到确认错误。
  • CRC错误(CRC ERROR):CRC序列由发射器进行CRC计算的结果组成。接收器以与发射器相同的方式计算CRC。如果计算的结果与CRC序列中接收到的结果不相同,必须检测到CRC错误。

使用要点

控制器初始化

控制器必须在通电或硬件重置后配置寄存器。在控制器的操作期间,可以发送软件重位请求并重新配置(重新初始化),如下所示。初始化完成后,控制器进入工作模式,将待发送的帧发送到缓冲区,然后设置命令寄存器的"发送请求"标志,开始发送。

EVB CAN接口硬件设计

驱动

设备树

    can1: can@fe580000 {
		compatible = "rockchip,canfd-1.0";
		reg = <0x0 0xfe580000 0x0 0x1000>;
		interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
		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 接口。

  1. can1: can@fe580000:这是一个名为 "can1" 的设备节点,具有兼容地址 "fe580000"。它表示设备树中的 CAN1 控制器。

    • compatible:指定与此设备节点匹配的兼容字符串,以将其与兼容的驱动程序关联起来。在这种情况下,它是 "rockchip,canfd-1.0"。

    • reg:定义了该设备的寄存器空间的地址和大小。它位于地址 0xfe580000,大小为 0x1000。

    • interrupts:指定与此设备相关的中断。它连接到 GIC(通用中断控制器),使用 SPI(共享外设中断)编号 2,并配置为电平触发、高电平有效的中断。

    • clocksclock-names:定义此设备使用的时钟源。CAN1 控制器使用两个时钟源:"baudclk" 和 "apb_pclk",它们都来自 "cru" 时钟控制器。

    • resetsreset-names:定义此设备使用的复位线。"can" 和 "can-apb" 复位线连接到 "cru" 复位控制器。

    • tx-fifo-depthrx-fifo-depth:这些值定义了发送和接收 FIFO 的深度。发送 FIFO 深度设置为 1,接收 FIFO 深度设置为 6。

    • status:此设备的状态最初设置为 "disabled"(禁用)。

  2. 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"。

  3. &can1:此部分引用了上面定义的 "can1" 设备节点。它指定了用于该 CAN1 接口的附加属性:

    • assigned-clocks:它将 "CLK_CAN1" 时钟从 "cru" 时钟控制器分配给此设备节点。

    • assigned-clock-rates:为此设备分配的时钟速率设置为 150,000,000 Hz。

    • pinctrl-namespinctrl-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);
  1. 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)的指针。移除函数用于卸载和清理设备。

  2. 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;
}
  1. irq = platform_get_irq(pdev, 0);:获取 Rockchip CANFD 控制器的中断号。中断用于通知 CPU 发生了重要事件。如果无法获取中断号,将打印错误消息并返回错误码。

  2. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);:获取 Rockchip CANFD 控制器的资源信息,这里是内存资源。这个资源包括了设备的物理地址范围。

  3. addr = devm_ioremap_resource(&pdev->dev, res);:使用 devm_ioremap_resource 函数将设备的物理地址映射到内核的虚拟地址空间。如果映射失败,将返回错误。

  4. ndev = alloc_candev(sizeof(struct rockchip_canfd), 1);:为 CAN 设备分配内存。这里使用了 alloc_candev 函数,分配了一个大小为 sizeof(struct rockchip_canfd) 的内存块来保存设备信息。如果分配失败,将返回错误。

  5. rcan = netdev_priv(ndev);:获取设备的私有数据结构 rockchip_canfd 的指针,以便后续使用。

  6. err = devm_request_irq(&pdev->dev, irq, rockchip_canfd_interrupt, 0, ndev->name, ndev);:注册中断处理函数 (rockchip_canfd_interrupt) 来处理中断事件。如果注册失败,将打印错误消息并返回错误。

  7. rcan->reset = devm_reset_control_array_get(&pdev->dev, false, false);:获取 CAN 控制器的复位线。如果获取失败,将打印错误消息并返回错误。

  8. rcan->num_clks = devm_clk_bulk_get_all(&pdev->dev, &rcan->clks);:获取与 CAN 控制器相关的时钟信息。如果获取失败,将返回错误。

  9. rcan->mode = (unsigned long)of_device_get_match_data(&pdev->dev);:从设备树中获取设备的匹配数据,以确定控制器的工作模式。

  10. 初始化 rcan 结构的各个字段,包括时钟频率、设备、CAN 控制状态等,根据设备的工作模式设置不同的控制器参数。

  11. 通过检查设备树属性 "rockchip,tx-invalid-info" 来决定是否将发送错误状态传递给接收。

  12. 设置网络设备操作函数和其他网络设备相关参数,如中断号、标志等。

  13. 初始化延迟工作队列,用于处理发送错误。

  14. 启用设备的运行时电源管理,并获取运行时电源锁。

  15. 注册 CAN 设备,将设备信息添加到内核的 CAN 子系统中。

  16. 初始化 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

相关推荐
嵌入式科普1 天前
嵌入式科普(24)从SPI和CAN通信重新理解“全双工”
c语言·stm32·can·spi·全双工·ra6m5
Logintern091 个月前
使用canmatrix模块解析DBC,生成的.exe文件执行报错
can·dbc·cantools·canmatrix
来可电子-CAN2 个月前
CANIOT网关CAN透传功能再工程机械行业的应用
can·工程机械·特种车辆
佣兵之王@大青山2 个月前
RS485/CAN的隔离电路分析
can·esd·rs485·防护·浪涌·gdt·空气放电管
Trump. yang2 个月前
AutoSar CP 通信服务核心—Com模块详解
嵌入式硬件·can·autosar·通信原理
mmprime3 个月前
杭州研砺LCWLAN的实际应用
can·canfd
来可电子-CAN3 个月前
USBCANFD卡再汽车电子行业中得应用
汽车·can·工程机械·特种车辆·煤矿
Zevalin爱灰灰4 个月前
CANoe/CANalyzer基础教程 第五章(CAPL语言)
汽车·can·canoe
LabVIEW开发5 个月前
LabVIEW与CANopen实现自动化生产线的设备控制与数据采集
can·labview·labview开发
蚂蚁小兵5 个月前
CANFD报文 位时间 理解
can·canfd·位时间·波特率