【SPI】【二】SPI控制器驱动代码详解
本文用tda4作为平台,介绍整个spi control的驱动流程。
一、设备树
main_spi0: spi@2100000 {compatible = "ti,am654-mcspi","ti,omap4-mcspi";reg = <0x00 0x02100000 0x00 0x400>;interrupts = <0 184 4>;#address-cells = <1>;#size-cells = <0>;power-domains = <&k3_pds 339 1>;clocks = <&k3_clks 339 1>;status = "disabled";};&main_spi0{status = "okay";pinctrl-names = "default";pinctrl-0 = <&myspi0_pins_default>;dmas = <&main_udmap 0xc600>, <&main_udmap 0x4600>;dma-names = "tx0", "rx0";spidev@0{compatible = "rohm,dh2228fv";reg = <0>;spi-max-frequency = <24000000>;spi-cpol = <0>;spi-cpha = <0>;};
};
其中需要注意pinctrl信息是否是我们需要的。
二、控制器驱动
kernel/drivers/spi$ vi spi-omap2-mcspi.c
2.1 平台注册
static struct platform_driver omap2_mcspi_driver = {.driver = {.name = "omap2_mcspi",.pm = &omap2_mcspi_pm_ops,.of_match_table = omap_mcspi_of_match,},.probe = omap2_mcspi_probe,.remove = omap2_mcspi_remove,
};module_platform_driver(omap2_mcspi_driver);
2.2 probe函数
- 获取设备树资源
- 向上层提供设备的操作函数接口(数据传输接口:transfer_one实现)
- 想内核添加设备(顺便把挂载控制器上面的spidev进行注册)
static int omap2_mcspi_probe(struct platform_device *pdev)
{struct spi_master *master;const struct omap2_mcspi_platform_config *pdata;struct omap2_mcspi *mcspi;struct resource *r;int status = 0, i;u32 regs_offset = 0;struct device_node *node = pdev->dev.of_node;const struct of_device_id *match;master = spi_alloc_master(&pdev->dev, sizeof(*mcspi));/* the spi->mode bits understood by this driver: */master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32);master->setup = omap2_mcspi_setup;master->auto_runtime_pm = true;master->prepare_message = omap2_mcspi_prepare_message;master->can_dma = omap2_mcspi_can_dma;master->transfer_one = omap2_mcspi_transfer_one;master->set_cs = omap2_mcspi_set_cs;master->cleanup = omap2_mcspi_cleanup;master->slave_abort = omap2_mcspi_slave_abort;master->dev.of_node = node;master->use_gpio_descriptors = true;platform_set_drvdata(pdev, master);mcspi = spi_master_get_devdata(master);mcspi->master = master;//初始化地址regs_offset = pdata->regs_offset;if (pdata->max_xfer_len) {mcspi->max_xfer_len = pdata->max_xfer_len;master->max_transfer_size = omap2_mcspi_max_xfer_size;}r = platform_get_resource(pdev, IORESOURCE_MEM, 0);mcspi->base = devm_ioremap_resource(&pdev->dev, r);if (IS_ERR(mcspi->base)) {status = PTR_ERR(mcspi->base);goto free_master;}//获取地址mcspi->phys = r->start + regs_offset;mcspi->base += regs_offset;mcspi->dev = &pdev->dev;INIT_LIST_HEAD(&mcspi->ctx.cs);//分配DMA通道数组mcspi->dma_channels = devm_kcalloc(&pdev->dev, master->num_chipselect,sizeof(struct omap2_mcspi_dma),GFP_KERNEL);if (mcspi->dma_channels == NULL) {status = -ENOMEM;goto free_master;}//DMA 初始化for (i = 0; i < master->num_chipselect; i++) {sprintf(mcspi->dma_channels[i].dma_rx_ch_name, "rx%d", i);sprintf(mcspi->dma_channels[i].dma_tx_ch_name, "tx%d", i);status = omap2_mcspi_request_dma(mcspi,&mcspi->dma_channels[i]);if (status == -EPROBE_DEFER)goto free_master;}status = platform_get_irq(pdev, 0);if (status < 0) {dev_err_probe(&pdev->dev, status, "no irq resource found\n");goto free_master;}init_completion(&mcspi->txrxdone);//初始化中断status = devm_request_irq(&pdev->dev, status,omap2_mcspi_irq_handler, 0, pdev->name,mcspi);if (status) {dev_err(&pdev->dev, "Cannot request IRQ");goto free_master;}// 初始化时钟 mcspi->ref_clk = devm_clk_get_optional_enabled(&pdev->dev, NULL);if (mcspi->ref_clk)mcspi->ref_clk_hz = clk_get_rate(mcspi->ref_clk);elsemcspi->ref_clk_hz = OMAP2_MCSPI_MAX_FREQ;master->max_speed_hz = mcspi->ref_clk_hz;master->min_speed_hz = mcspi->ref_clk_hz >> 15;pm_runtime_use_autosuspend(&pdev->dev);pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT);pm_runtime_enable(&pdev->dev);status = omap2_mcspi_controller_setup(mcspi);if (status < 0)goto disable_pm;status = devm_spi_register_controller(&pdev->dev, master);if (status < 0)goto disable_pm;return status;disable_pm:pm_runtime_dont_use_autosuspend(&pdev->dev);pm_runtime_put_sync(&pdev->dev);pm_runtime_disable(&pdev->dev);
free_master:omap2_mcspi_release_dma(master);spi_master_put(master);return status;
}
三、控制器注册
3.1 spi_alloc_master
static inline struct spi_controller *spi_alloc_master(struct device *host,unsigned int size)
{return __spi_alloc_controller(host, size, false);
}struct spi_controller *__spi_alloc_controller(struct device *dev,unsigned int size, bool slave)
{struct spi_controller *ctlr;size_t ctlr_size = ALIGN(sizeof(*ctlr), dma_get_cache_alignment());if (!dev)return NULL;ctlr = kzalloc(size + ctlr_size, GFP_KERNEL);if (!ctlr)return NULL;device_initialize(&ctlr->dev);INIT_LIST_HEAD(&ctlr->queue);spin_lock_init(&ctlr->queue_lock);spin_lock_init(&ctlr->bus_lock_spinlock);mutex_init(&ctlr->bus_lock_mutex);mutex_init(&ctlr->io_mutex);mutex_init(&ctlr->add_lock);ctlr->bus_num = -1;ctlr->num_chipselect = 1;ctlr->slave = slave;if (IS_ENABLED(CONFIG_SPI_SLAVE) && slave)ctlr->dev.class = &spi_slave_class;elsectlr->dev.class = &spi_master_class;ctlr->dev.parent = dev;pm_suspend_ignore_children(&ctlr->dev, true);spi_controller_set_devdata(ctlr, (void *)ctlr + ctlr_size);return ctlr;
}
SPI控制器和I2C适配器一样,一般都是基于platform模型的,这里传入的参数host便是xxx_spi_probe(struct platform_device *pdev) 中的 &pdev->dev 部分。
这个接口不仅仅是分配一个spi_controller的结构,它在 kzalloc的时候,其实是分配了一个 spi_controller + size 的结构,然后首地址为一个 spi_controller,并对其做了基本的初始化操作,最后调用:
spi_controller_set_devdata(ctlr, (void *)ctlr + ctlr_size);
传入的参数是(void *)ctlr + ctlr_size
也就是跳过了第一个ctr的地址,将分配余下的size 大小的内存空间通过dev_set_drvdata的方式挂接到了 device->driver_data 下;
3.2 omap2_mcspi_controller_setup
{// 获取SPI控制器的主设备结构体和寄存器上下文struct spi_master *master = mcspi->master; // SPI主控制器实例struct omap2_mcspi_regs *ctx = &mcspi->ctx; // MCSPI寄存器保存上下文int ret = 0; // 操作返回值// 唤醒设备并增加电源引用计数ret = pm_runtime_resume_and_get(mcspi->dev);if (ret < 0)return ret; // 如果唤醒失败,直接返回错误码// 配置MCSPI的唤醒使能寄存器mcspi_write_reg(master, OMAP2_MCSPI_WAKEUPENABLE,OMAP2_MCSPI_WAKEUPENABLE_WKEN); // 使能SPI唤醒功能// 更新寄存器上下文中的唤醒使能状态ctx->wakeupenable = OMAP2_MCSPI_WAKEUPENABLE_WKEN;// 设置SPI控制器的工作模式(时钟极性/相位等)omap2_mcspi_set_mode(master);// 标记设备最后一次活动时间点(用于自动挂起计时)pm_runtime_mark_last_busy(mcspi->dev);// 释放电源引用计数,启动自动挂起计时器pm_runtime_put_autosuspend(mcspi->dev);// 返回操作成功状态return 0;
}
3.3 devm_spi_register_controller
// 使用设备资源管理机制注册SPI控制器
// 参数:
// dev - 关联的父设备结构指针(负责资源管理)
// ctlr - 要注册的SPI控制器结构指针
// 返回值:成功返回0,失败返回错误码
int devm_spi_register_controller(struct device *dev,struct spi_controller *ctlr)
{struct spi_controller **ptr; // 指向控制器指针的指针(用于资源管理)int ret; // 操作状态返回值// 分配设备资源:注册自动清理函数和存储空间ptr = devres_alloc(devm_spi_unregister, // 自动注销回调函数sizeof(*ptr), // 分配一个指针大小的空间GFP_KERNEL); // 内核内存分配标志if (!ptr)return -ENOMEM; // 内存分配失败返回"内存不足"错误// 核心SPI控制器注册函数ret = spi_register_controller(ctlr);if (!ret) { // 注册成功*ptr = ctlr; // 存储控制器指针到资源区域devres_add(dev, ptr); // 将资源添加到设备管理列表} else { // 注册失败devres_free(ptr); // 释放之前分配的资源}return ret; // 返回注册操作状态
}
// 导出符号:仅限GPL协议模块使用
EXPORT_SYMBOL_GPL(devm_spi_register_controller);/*** spi_register_controller - 注册SPI主控制器或从控制器* @ctlr: 已初始化的控制器,通常来自spi_alloc_master()或spi_alloc_slave()* Context: 可睡眠上下文** SPI控制器通过非SPI总线(如平台总线)连接到其驱动程序。* 在驱动程序probe()的最后阶段调用此函数以连接到SPI总线框架。** SPI控制器使用板级特定(通常是SoC特定)的总线编号,* 而SPI设备的板级特定寻址将总线编号与片选号结合。* 由于SPI不直接支持动态设备识别,板级需要配置表来指定芯片地址。** 必须在可睡眠上下文中调用。成功返回0,失败返回负错误码(控制器引用计数会减少)。* 成功返回后,调用者有责任调用spi_unregister_controller()。** 返回值:成功返回0,失败返回负错误码。*/
int spi_register_controller(struct spi_controller *ctlr)
{// 获取父设备指针(通常是platform_device)struct device *dev = ctlr->dev.parent;struct boardinfo *bi; // 存储板级SPI设备信息的结构int status; // 操作状态int id, first_dynamic; // 总线ID和动态ID管理// 验证父设备是否存在if (!dev)return -ENODEV;/** 检查控制器操作函数是否完整实现* 确保所有必要的钩子函数都已实现*/status = spi_controller_check_ops(ctlr);if (status)return status;// 总线ID分配逻辑(三种情况)if (ctlr->bus_num >= 0) {/* 情况1:固定总线号 - 直接使用指定ID */mutex_lock(&board_lock);id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,ctlr->bus_num + 1, GFP_KERNEL);mutex_unlock(&board_lock);// 分配失败处理if (WARN(id < 0, "couldn't get idr"))return id == -ENOSPC ? -EBUSY : id;ctlr->bus_num = id;} else if (ctlr->dev.of_node) {/* 情况2:设备树节点 - 尝试获取别名ID */id = of_alias_get_id(ctlr->dev.of_node, "spi");if (id >= 0) {ctlr->bus_num = id;mutex_lock(&board_lock);id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,ctlr->bus_num + 1, GFP_KERNEL);mutex_unlock(&board_lock);if (WARN(id < 0, "couldn't get idr"))return id == -ENOSPC ? -EBUSY : id;}}if (ctlr->bus_num < 0) {/* 情况3:动态分配 - 获取下一个可用ID */first_dynamic = of_alias_get_highest_id("spi");if (first_dynamic < 0)first_dynamic = 0; // 无别名时从0开始elsefirst_dynamic++; // 从最高ID+1开始分配mutex_lock(&board_lock);id = idr_alloc(&spi_master_idr, ctlr, first_dynamic,0, GFP_KERNEL);mutex_unlock(&board_lock);if (WARN(id < 0, "couldn't get idr"))return id;ctlr->bus_num = id;}// 初始化控制器状态ctlr->bus_lock_flag = 0; // 清除总线锁标志init_completion(&ctlr->xfer_completion); // 传输完成量init_completion(&ctlr->cur_msg_completion); // 消息完成量// 设置最大DMA长度(默认为INT_MAX)if (!ctlr->max_dma_len)ctlr->max_dma_len = INT_MAX;/** 创建设备名称(格式:spiX,X为总线号)* 设备注册后用户空间可见*/dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);// 处理GPIO片选描述符(仅主模式)if (!spi_controller_is_slave(ctlr) && ctlr->use_gpio_descriptors) {status = spi_get_gpio_descs(ctlr);if (status)goto free_bus_id; // 错误处理/* 使用GPIO描述符的控制器必须支持SPI_CS_HIGH */ctlr->mode_bits |= SPI_CS_HIGH;}/* 验证片选数量有效性(至少需要1个片选) */if (!ctlr->num_chipselect) {status = -EINVAL;goto free_bus_id;}// 初始化最后选择的片选(-1表示无片选激活)ctlr->last_cs = -1;/* 核心设备注册 - 向设备模型添加控制器 * 会在/sys/devices/platform/-spi.x/spi_master下创建spi%d目录*/status = device_add(&ctlr->dev);if (status < 0)goto free_bus_id;// 调试信息:打印注册成功dev_dbg(dev, "registered %s %s\n",spi_controller_is_slave(ctlr) ? "slave" : "master",dev_name(&ctlr->dev));/* 队列初始化处理 */if (ctlr->transfer) {/* 传统非队列模式(已弃用) */dev_info(dev, "controller is unqueued, this is deprecated\n");} else if (ctlr->transfer_one || ctlr->transfer_one_message) {/* 现代队列模式初始化 */status = spi_controller_initialize_queue(ctlr);if (status) {device_del(&ctlr->dev); // 回滚设备注册goto free_bus_id;}}/* 分配性能统计数据结构 */ctlr->pcpu_statistics = spi_alloc_pcpu_stats(dev);if (!ctlr->pcpu_statistics) {dev_err(dev, "Error allocating per-cpu statistics\n");status = -ENOMEM;goto destroy_queue; // 错误处理}/* 全局注册和板级设备匹配 */mutex_lock(&board_lock);list_add_tail(&ctlr->list, &spi_controller_list); // 加入全局控制器列表/* 遍历所有板级信息,匹配此控制器 */list_for_each_entry(bi, &board_list, list)spi_match_controller_to_boardinfo(ctlr, &bi->board_info);mutex_unlock(&board_lock);/* 自动注册设备树和ACPI中的SPI设备 */of_register_spi_devices(ctlr); // 设备树设备注册acpi_register_spi_devices(ctlr); // ACPI设备注册return status; // 成功返回0/* 错误处理路径 */
destroy_queue:spi_destroy_queue(ctlr); // 销毁消息队列
free_bus_id:mutex_lock(&board_lock);idr_remove(&spi_master_idr, ctlr->bus_num); // 释放总线IDmutex_unlock(&board_lock);return status; // 返回错误码
}
这个函数中,其实是进行了一些必要的设备模型的操作和钩子函数的check,通过后,将SPI控制器设备模型添加系统,同时将SPI控制器挂接到全局的spi_controller_list链表上。具体如下:
- 65行 检查传入的spi_controller结构中,挂接的ops是不是全的,也就是说SPI核心层需要的一些必要的钩子函数是不是已经指定好了,这里需要确定SPI控制器的那个传输的函数transfer、transfer_one、transfer_one_message至少挂接一个才行,也就是说,芯片厂家必须实现至少一种控制实际 SoC的传输 SPI的方式,SPI核心层才能够正常运行;
- 71-143行 一些链表,自旋锁、互斥锁、dma最大传输长度的初始化;将设备模型添加到系统:
- 163行 如果芯片厂家没有实现transfer钩子函数的话,那么至少使用了挂接了transfer_one或者transfer_one_message,这意味着将会使用SPI核心层的新的传输机制,这里初始化queue机制;
- 180 将 spi_controller 挂接到全局的spi_controller_list双向链表;
- 184 遍历borad list链表,调用spi_match_controller_to_boardinfo注册SPI从设备;
- 188 通过设备树节点、mach文件盖住该控制器下的所有SPI从设备;
3.4 spi_controller_initialize_queue
在注册SPI控制器方法中调用spi_controller_initialize_queue
/*** spi_controller_initialize_queue - 初始化并启动SPI控制器的传输队列* @ctlr: 目标SPI控制器** 功能:* 1. 设置队列传输函数和消息传输回调* 2. 初始化内核工作队列* 3. 启动队列处理机制** 返回值:* 成功返回0,失败返回错误码*/
static int spi_controller_initialize_queue(struct spi_controller *ctlr)
{int ret;/* 核心操作:将控制器的传输函数替换为队列式传输 */ctlr->transfer = spi_queued_transfer;/* 确保存在消息传输处理函数(设置默认实现) */if (!ctlr->transfer_one_message)ctlr->transfer_one_message = spi_transfer_one_message;/* 初始化底层工作队列和消息队列 */ret = spi_init_queue(ctlr);if (ret) {dev_err(&ctlr->dev, "problem initializing queue\n");goto err_init_queue; // 跳转至错误处理}/* 标记控制器已启用队列模式 */ctlr->queued = true;/* 启动工作队列线程处理传输请求 */ret = spi_start_queue(ctlr);if (ret) {dev_err(&ctlr->dev, "problem starting queue\n");goto err_start_queue; // 跳转至二级错误处理}return 0; // 成功返回/* 错误处理路径 */
err_start_queue:spi_destroy_queue(ctlr); // 回滚:销毁已初始化的队列
err_init_queue:return ret; // 返回错误码
}
该函数主要主要:
- 先将 transfer 赋值成为 spi_queued_transfer 调用(因为只有当 transfer 没有挂指针的时候,才会走进这个函.数),如果没有指定 transfer_one_message,那么为他它上SPI 核心层的 spitransfer_one_message;这样spi controller->transfer 和 spi controller->spi transfer one message 已经赋值为SPI核心层层的函数钩子
- 接着调用spi_init_queue和spi_start_queue函数;
spi_init_queue
/*** spi_init_queue - 初始化SPI控制器的消息队列系统* @ctlr: 目标SPI控制器** 功能:* 1. 初始化队列状态标志* 2. 创建工作线程(worker)处理消息传输* 3. 初始化消息泵(pump)工作项* 4. 根据控制器配置设置线程优先级** 返回值:成功返回0,失败返回错误码*/
static int spi_init_queue(struct spi_controller *ctlr)
{/* 初始化队列状态标志 */ctlr->running = false; // 消息泵尚未运行ctlr->busy = false; // 当前无传输进行中ctlr->queue_empty = true; // 初始状态为空队列/* 创建专用工作线程(消息泵)* 参数1:CPU绑定核心(0=不绑定)* 参数2:线程命名格式(使用控制器设备名)*/ctlr->kworker = kthread_create_worker(0, dev_name(&ctlr->dev));if (IS_ERR(ctlr->kworker)) {dev_err(&ctlr->dev, "failed to create message pump kworker\n");return PTR_ERR(ctlr->kworker); // 返回线程创建错误码}/* 初始化工作项:将spi_pump_messages函数关联到pump_messages工作 */kthread_init_work(&ctlr->pump_messages, spi_pump_messages);/** 实时性配置检测:如果控制器需要低延迟传输* 则提升工作线程为实时(RT)优先级*/if (ctlr->rt) // ctlr->rt标志表示需要实时处理spi_set_thread_rt(ctlr); // 设置线程为SCHED_FIFO实时调度策略return 0; // 成功返回
}static void spi_set_thread_rt(struct spi_controller *ctlr)
{dev_info(&ctlr->dev,"will run message pump with realtime priority\n");sched_set_fifo(ctlr->kworker->task);
}
在spi_init_queue时,会初始化&ctlr->pump_messages为spi_pump_messages,其内部调用了
static void spi_pump_messages(struct kthread_work *work)
{struct spi_controller *ctlr =container_of(work, struct spi_controller, pump_messages);__spi_pump_messages(ctlr, true);
}
spi_start_queue
spi_start_queue函数调用比较简单,直接running状态为 true,并设置当前cur_msg 为NULL,并将pump_messages挂载工作线程kthread_worker 上,这个 work执行函就是上面设置的 spi_pump_messages。
/*** spi_start_queue - 启动SPI控制器的传输队列处理* @ctlr: 目标SPI控制器** 功能:* 1. 检查控制器状态确保可以安全启动* 2. 设置控制器运行状态* 3. 调度消息泵工作线程** 返回值:* 0 - 启动成功* -EBUSY - 控制器已在运行或忙状态*/
static int spi_start_queue(struct spi_controller *ctlr)
{unsigned long flags;/* 获取队列自旋锁并保存中断状态(进入临界区) */spin_lock_irqsave(&ctlr->queue_lock, flags);/* 安全检查:确保控制器未运行且不忙 */if (ctlr->running || ctlr->busy) {spin_unlock_irqrestore(&ctlr->queue_lock, flags);return -EBUSY; // 返回忙碌错误码}/* 更新控制器状态 */ctlr->running = true; // 标记消息泵开始运行ctlr->cur_msg = NULL; // 清除当前处理的消息指针spin_unlock_irqrestore(&ctlr->queue_lock, flags); // 解锁临界区/* 调度消息泵工作项到工作线程 */kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);return 0; // 成功返回
}
spi_pump_messages
/*** __spi_pump_messages - SPI消息队列处理核心函数* @ctlr: 待处理队列的控制器* @in_kthread: 指示是否在消息泵线程上下文中调用** 功能:* 1. 检查并处理SPI消息队列* 2. 调用驱动进行硬件初始化和消息传输* 3. 处理控制器空闲状态和资源清理** 注意:该函数既可在工作线程上下文调用,也可在spi_sync()同步上下文中调用*/
static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
{struct spi_message *msg;bool was_busy = false; // 记录进入函数前控制器的忙状态unsigned long flags;int ret;.../* * 队列处理核心:提取并处理消息 *//* 获取队列首条消息 */// spi message结构体,spi_controller->queue链表链接着一系列spi_message// 获取链表中的第一个spi_message结构体,每次添加时都是添加到链表未尾msg = list_first_entry(&ctlr->queue, struct spi_message, queue);ctlr->cur_msg = msg; // 设置为当前处理消息/* 从队列移除消息 */list_del_init(&msg->queue);/* 更新忙状态标志 */if (ctlr->busy) {was_busy = true; // 记录先前已是忙状态} else {ctlr->busy = true; // 标记控制器开始忙碌}spin_unlock_irqrestore(&ctlr->queue_lock, flags); // 解锁/* 核心传输处理:准备硬件并执行消息传输 */ret = __spi_pump_transfer_message(ctlr, msg, was_busy);/* 处理完成后,立即重新调度自身处理下一条消息 */kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);...
}
/*** __spi_pump_transfer_message - SPI消息传输执行函数* @ctlr: SPI控制器* @msg: 待传输的SPI消息* @was_busy: 传输前控制器是否已忙碌** 功能:* 1. 电源管理* 2. 硬件准备* 3. 消息预处理* 4. 执行传输* 5. 处理传输同步** 设计要点:* - 电源状态转换管理* - 大传输拆分机制* - 双标志异步传输协议* - 统一错误处理路径*/
static int __spi_pump_transfer_message(struct spi_controller *ctlr,struct spi_message *msg, bool was_busy)
{.../* 消息准备回调 */if (ctlr->prepare_message) {ret = ctlr->prepare_message(ctlr, msg);if (ret) {msg->status = ret;spi_finalize_current_message(ctlr);return ret;}msg->prepared = true;}/* true;}/* 3.3 DMA映射 */ret = spi_map_msg(ctlr, msg);if (ret) {msg->status = ret;spi_finalize_current_message(ctlr);return ret;}.../* 4.2 调用驱动传输函数 */ret = ctlr->transfer_one_message(ctlr, msg);if (ret) {dev_err(&ctlr->dev, "Transfer failed: %d\n", ret);return ret;}...
}
spi_transfer_one_message
static int spi_transfer_one_message(struct spi_controller *ctlr,struct spi_message *msg)
{struct spi_transfer *xfer;bool keep_cs = false; // 标记消息结束后是否保持片选(CS)有效int ret = 0;// 获取控制器的每CPU统计数据和设备的每CPU统计数据struct spi_statistics __percpu *statm = ctlr->pcpu_statistics;struct spi_statistics __percpu *stats = msg->spi->pcpu_statistics;// 获取消息中的第一个传输xfer = list_first_entry(&msg->transfers, struct spi_transfer, transfer_list);// 根据第一个传输的cs_off设置初始片选状态// !xfer->cs_off 表示需要激活片选,false 表示不强制设置spi_set_cs(msg->spi, !xfer->cs_off, false);// 更新消息计数统计SPI_STATISTICS_INCREMENT_FIELD(statm, messages);SPI_STATISTICS_INCREMENT_FIELD(stats, messages);// 遍历消息中的所有传输list_for_each_entry(xfer, &msg->transfers, transfer_list) {trace_spi_transfer_start(msg, xfer); // 传输开始跟踪点// 更新传输统计信息(字节数等)spi_statistics_add_transfer_stats(statm, xfer, ctlr);spi_statistics_add_transfer_stats(stats,istics_add_transfer_stats(stats, xfer, ctlr);// 处理时间戳(如果不支持PTP)if (!ctlr->ptp_sts_supported) {xfer->ptp_sts_word_pre = 0;ptp_read_system_prets(xfer->ptp_sts); // 记录传输前时间戳}// 仅当存在有效缓冲区且长度>0时执行数据传输if ((xfer->tx_buf || xfer->rx_buf) && xfer->len) {reinit_completion(&ctlr->xfer_completion); // 初始化完成量fallback_pio: // DMA失败时回退到PIO模式的标签//1 DMA传输前同步到设备spi_dma_sync_for_device(ctlr, xfer);//2 调用控制器驱动的实际DMA传输函数ret = ctlr->transfer_one(ctlr, msg->spi, xfer);if (ret < 0) { // 传输错误处理spi_dma_sync_for_cpu(ctlr, xfer);// 处理DMA启动失败的回退逻辑if (ctlr->cur_msg_mapped &&(xfer->error & SPI_TRANS_FAILfer->error & SPI_TRANS_FAIL_NO_START)) {__spi_unmap_msg(ctlr, msg); // 解除DMA映射ctlr->fallback = true; // 启用回退模式xfer->error &= ~SPI_TRANS_FAIL_NO_START; // 清除错误标志goto fallback_pio; // 重试传输(使用PIO)}// 更新错误统计SPI_STATISTICS_INCREMENT_FIELD(statm, errors);SPI_STATISTICS_INCREMENT_FIELD(stats, errors);dev_err(&msg->spi->dev, "SPI transfer failed: %d\n", ret);goto out; // 跳转到错误处理}// 处理异步传输(ret>0表示需要等待完成)if (ret > 0) {ret = spi_transfer_wait(ctlr, msg, xfer); // 等待传输完成if (ret < 0)msg->status = ret; // 设置消息状态}//3 DMA传输后同步到CPUspi_dma_sync_for_cpu(ctlr, xfer); // DMA完成同步} else {// 无缓冲区但有长度的错误检查if (xfer->len)dev_err(&msg->spi->dev,"Bufferless transfer has length %u\n",xfer->len);}// 记录传输后时间戳if (!ctlr->ptp_sts_supported) {ptp_read_system_postts(xfer->ptp_sts);xfer->ptp_sts_word_post = xfer->len;}trace_spi_transfer_stop(msg, xfer); // 传输结束跟踪点// 检查消息是否已标记失败if (msg->status != -EINPROGRESS)goto out;// 执行传输后延迟(delay_usecs)spi_transfer_delay_exec(xfer);// 处理片选(CS)状态变更if (xfer->cs_change) {if (list_is_last(&xfer->transfer_list, &msg->transfers)) {keep_cs = true; // 最后一个传输保持CS有效} else {// 非最后一个传输:停用CS并添加延迟if (!xfer->cs_off)spi_set_cs(msg->spi, false, false);_spi_transfer_cs_change_delay(msg, xfer);// 激活下一个传输的CSif (!list_next_entry(xfer, transfer_list)->cs_off)spi_set_cs(msg->spi, true, false);}} else if (!list_is_last(&xfer->transfer_list, &msg->transfers) &&xfer->cs_off != list_next_entry(xfer, transfer_list)->cs_off) {// 相邻传输CS状态不同时更新spi_set_cs(msg->spi, xfer->cs_off, false);}// 更新消息总传输长度msg->actual_length += xfer->len;}out:// 错误处理或非保持CS时停用片选if (ret != 0 || !keep_cs)spi_set_cs(msg->spi, false, false);// 更新最终消息状态if (msg->status == -EINPROGRESS)msg->status = ret;// 调用控制器的错误处理回调if (msg->status && ctlr->handle_err)ctlr->handle_err(ctlr, msg);// 完成消息处理(唤醒等待者/调用完成回调)spi_finalize_current_message(ctlr);return ret;
}
这里我们就要发起传输了:
- 15行 首先设置了片选 cs,具体调用到了芯片厂家底层对接的 set_cs
- 43行 从 spi _message 中的 transfer_list 逐个取出 transfer 调用底层对接的 transfer_one
- 92行 根据是否配置了需要的延时 delay_usecs,来确定是否在下一次传输之前进行必要的 udelay
- 95-115行 根据是否配置了 cs change,来判断是否需要变动片选信号 cs
- 114行 累加实际传输的数据 actual length
- 131行 调用 spi_finalize_current_message 告知完成传输
omap2_mcspi_transfer_one
static int omap2_mcspi_transfer_one(struct spi_master *master,struct spi_device *spi,struct spi_transfer *t)
{/* 注释说明:虽然控制器支持多通道并行传输,但我们一次只启用一个通道(当前处理的通道)。这对应于"单通道"主模式。因此我们需要使用 FORCE 位单独管理片选(CS)...注意:CS 启用 ≠ 通道启用。 */// 获取主控制器私有数据struct omap2_mcspi *mcspi;// 获取与当前 SPI 设备对应的 DMA 通道信息struct omap2_mcspi_dma *mcspi_dma;// 获取当前 SPI 设备的芯片选择状态struct omap2_mcspi_cs *cs;// 获取设备特定的配置struct omap2_mcspi_device_config *cd;int par_override = 0; // 参数覆盖标志(需要重新配置)int status = 0; // 函数返回状态u32 chconf; // 通道配置寄存器值// 从主控制器获取私有数据结构mcspi = spi_master_get_devdata(master);// 根据片选号获取对应的 DMA 通道信息mcspi_dma = mcspi->dma_channels + spi->chip_select;// 获取 SPI 设备的控制器状态(包含寄存器基址等信息)cs = spi->controller_state;// 获取设备特定的配置参数cd = spi->controller_data;/** 检查 SPI 模式是否发生变化(从设备驱动可能修改 spi->mode)* 如果与当前硬件设置 (cs->mode) 不同,设置 par_override 标志* 强制在第一个循环迭代中重新配置硬件*/if (spi->mode != cs->mode)par_override = 1;// 禁用 SPI 通道omap2_mcspi_set_enable(spi, 0);// 如果使用 GPIO 片选,根据 SPI_CS_HIGH 标志设置初始电平if (spi->cs_gpiod)omap2_mcspi_set_cs(spi, spi->mode & SPI_CS_HIGH);/* 检查是否需要重新配置传输参数:* 1. 模式已改变 (par_override)* 2. 传输速率改变* 3. 每字位数改变*/if (par_override ||(t->speed_hz != spi->max_speed_hz) ||(t->bits_per_word != spi->bits_per_word)) {par_override = 1;// 设置传输参数(时钟分频、字长等)status = omap2_mcspi_setup_transfer(spi, t);if (status < 0)goto out; // 出错则跳转清理// 如果参数设置后与默认值相同,清除覆盖标志if (t->speed_hz == spi->max_speed_hz &&t->bits_per_word == spi->bits_per_word)par_override = 0;}// 如果设备配置要求每个字都切换片选if (cd && cd->cs_per_word) {chconf = mcspi->ctx.modulctrl;chconf &= ~OMAP2_MCSPI_MODULCTRL_SINGLE; // 禁用单通道模式mcspi_write_reg(master, OMAP2_MCSPI_MODULCTRL, chconf);mcspi->ctx.modulctrl = mcspi_read_cs_reg(spi, OMAP2_MCSPI_MODULCTRL);}// 获取当前通道配置并清除传输模式和 Turbo 位chconf = mcspi_cached_chconf0(spi);chconf &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;chconf &= ~OMAP2_MCSPI_CHCONF_TURBO;// 根据缓冲区设置传输方向if (t->tx_buf == NULL) // 只接收模式chconf |= OMAP2_MCSPI_CHCONF_TRM_RX_ONLY;else if (t->rx_buf == NULL) // 只发送模式chconf |= OMAP2_MCSPI_CHCONF_TRM_TX_ONLY;// 检查并设置 Turbo 模式(加速接收)if (cd && cd->turbo_mode && t->tx_buf == NULL) {/* Turbo 模式用于多字传输 */if (t->len > ((cs->word_len + 7) >> 3)) // 计算字长对应的字节数chconf |= OMAP2_MCSPI_CHCONF_TURBO;}// 写入更新后的通道配置mcspi_write_chconf0(spi, chconf);// 处理实际数据传输if (t->len) {unsigned count; // 实际传输的字节数/* 检查是否使用 DMA:* 1. DMA 通道可用* 2. 当前消息已映射* 3. 控制器支持 DMA */if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&master->cur_msg_mapped &&master->can_dma(master, spi, t))omap2_mcspi_set_fifo(spi, t, 1); // 设置 FIFO 用于 DMA// 启用 SPI 通道omap2_mcspi_set_enable(spi, 1);// 对于 RX_ONLY 模式,写入虚拟数据启动时钟if (t->tx_buf == NULL)writel_relaxed(0, cs->base + OMAP2_MCSPI_TX0);// 执行数据传输(DMA 或 PIO)if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&master->cur_msg_mapped &&master->can_dma(master, spi, t))count = omap2_mcspi_txrx_dma(spi, t); // DMA 传输elsecount = omap2_mcspi_txrx_pio(spi, t); // PIO 轮询传输// 检查传输完整性if (count != t->len) {status = -EIO; // 传输长度不匹配goto out;}}// 传输完成后禁用通道omap2_mcspi_set_enable(spi, 0);// 如果启用了 FIFO,禁用它if (mcspi->fifo_depth > 0)omap2_mcspi_set_fifo(spi, t, 0);out: // 清理标签/* 恢复被覆盖的默认参数 */if (par_override) {par_override = 0;status = omap2_mcspi_setup_transfer(spi, NULL);}// 恢复单通道模式设置if (cd && cd->cs_per_word) {chconf = mcspi->ctx.modulctrl;chconf |= OMAP2_MCSPI_MODULCTRL_SINGLE;mcspi_write_reg(master, OMAP2_MCSPI_MODULCTRL, chconf);mcspi->ctx.modulctrl = mcspi_read_cs_reg(spi, OMAP2_MCSPI_MODULCTRL);}// 确保 SPI 通道被禁用omap2_mcspi_set_enable(spi, 0);// 释放片选线(如果使用 GPIO)if (spi->cs_gpiod)omap2_mcspi_set_cs(spi, !(spi->mode & SPI_CS_HIGH));// 确保 FIFO 被禁用if (mcspi->fifo_depth > 0 && t)omap2_mcspi_set_fifo(spi, t, 0);return status; // 返回操作状态
}
3.5 spi_match_controller_to_boardinfo
static void spi_match_controller_to_boardinfo(struct spi_controller *ctlr,struct spi_board_info *bi)
{struct spi_device *dev;// 检查当前控制器的总线号是否匹配板级信息的总线号if (ctlr->bus_num != bi->bus_num)return; // 如果不匹配则直接返回(此板级设备不属于此控制器)// 尝试为该板级信息创建SPI设备实例dev = spi_new_device(ctlr, bi);// 检查设备是否成功创建if (!dev)// 创建失败时打印错误信息(通常在内存不足或无效参数时发生)dev_err(ctlr->dev.parent, "can't create new device for %s\n",bi->modalias); // modalias通常是驱动名称(如"mcp2515")
}
四、数据收发
4.1 DMA初始化
由上面的分析可知
omap2_mcspi_probemaster = spi_alloc_master(&pdev->dev, sizeof(*mcspi)); // 1. 分配SPI控制器master->can_dma = omap2_mcspi_can_dma; // 2 DMA能力检查函数devm_kcalloc // 3. 分配DMA通道数组omap2_mcspi_request_dma(mcspi, &mcspi->dma_channels[i]);// 4. 为每个片选请求DMA通道dma_request_chan // 分配 dma_rx和 dma_tx通道
4.2 数据收发
数据发送使用omap2_mcspi_transfer_one
static int omap2_mcspi_transfer_one(struct spi_master *master,struct spi_device *spi,struct spi_transfer *t)
{// 1. 检查是否使用DMAif ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&master->cur_msg_mapped &&master->can_dma(master, spi, t)) {// 2. 设置FIFOomap2_mcspi_set_fifo(spi, t, 1);// 3. 执行DMA传输count = omap2_mcspi_txrx_dma(spi, t);} else {// 使用PIO传输count = omap2_mcspi_txrx_pio(spi, t);}
}static unsigned omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
{// 1. 配置DMA参数memset(&cfg, 0, sizeof(cfg));cfg.src_addr = cs->phys + OMAP2_MCSPI_RX0; // 源地址:RX寄存器cfg.dst_addr = cs->phys + OMAP2_MCSPI_TX0; // 目标地址:TX寄存器cfg.src_addr_width = width; // 数据宽度cfg.dst_addr_width = width;cfg.src_maxburst = 1;cfg.dst_maxburst = 1;// 2. 启动TX DMA(如果存在)if (tx)omap2_mcspi_tx_dma(spi, xfer, cfg);// 3. 启动RX DMA(如果存在)if (rx)count = omap2_mcspi_rx_dma(spi, xfer, cfg, es);// 4. 等待传输完成ret = mcspi_wait_for_completion(mcspi, &mcspi->txrxdone);
}
4.2.1 数据发送
static void omap2_mcspi_tx_dma(struct spi_device *spi,struct spi_transfer *xfer,struct dma_slave_config cfg)
{// 1. 配置DMA从设备dmaengine_slave_config(mcspi_dma->dma_tx, &cfg);// 2. 准备DMA描述符// mcspi_dma->dma_tx: TX DMA通道// xfer->tx_sg.sgl: scatterlist链表的第一个元素// xfer->tx_sg.nents: scatterlist中的条目数量// DMA_MEM_TO_DEV: 传输方向(从内存到设备)// DMA_CTRL_ACK: 控制标志,表示需要确认传输完成tx = dmaengine_prep_slave_sg(mcspi_dma->dma_tx, xfer->tx_sg.sgl,xfer->tx_sg.nents, DMA_MEM_TO_DEV, DMA_CTRL_ACK);// 3. 提交DMA事务if (tx) {dmaengine_submit(tx);}// 4. 启动DMA传输dma_async_issue_pending(mcspi_dma->dma_tx);// 5. 使能SPI控制器的DMA请求omap2_mcspi_set_dma_req(spi, 0, 1); // 0=TX, 1=enable
}
4.2.2 数据接收
static unsigned omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,struct dma_slave_config cfg,unsigned es)
{// 1. 计算传输长度减少量(硬件特性)if (mcspi->fifo_depth == 0)transfer_reduction = es;if ((l & OMAP2_MCSPI_CHCONF_TURBO) && mcspi->fifo_depth == 0)transfer_reduction += es;// 2. 分割scatterlist(如果需要)if (transfer_reduction) {sizes[0] = count - transfer_reduction;sizes[1] = transfer_reduction;nb_sizes = 2;} else {sizes[0] = count;nb_sizes = 1;}ret = sg_split(xfer->rx_sg.sgl, xfer->rx_sg.nents, 0, nb_sizes,sizes, sg_out, out_mapped_nents, GFP_KERNEL);// 3. 准备RX DMA描述符tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx, sg_out[0],out_mapped_nents[0], DMA_DEV_TO_MEM, DMA_CTRL_ACK);// 4. 提交并启动RX DMAif (tx) {dma_rx_cookie = dmaengine_submit(tx);}dma_async_issue_pending(mcspi_dma->dma_rx);omap2_mcspi_set_dma_req(spi, 1, 1); // 1=RX, 1=enable// 5. 等待传输完成ret = mcspi_wait_for_completion(mcspi, &mcspi->txrxdone);// 6. 检查DMA状态do {dma_status = dmaengine_tx_status(mcspi_dma->dma_rx, dma_rx_cookie,&mcspi_dma_rxstate);} while (dma_status != DMA_COMPLETE);// 7. 禁用DMA请求omap2_mcspi_set_dma_req(spi, 1, 0); // 1=RX, 0=disable// 8. 手动读取剩余数据(硬件特性要求)if (mcspi->fifo_depth == 0) {// 由于DMA传输长度减少,需要手动读取最后几个字节elements = element_count - 1;if (l & OMAP2_MCSPI_CHCONF_TURBO) {elements--;// 读取倒数第二个字if (!mcspi_wait_for_reg_bit(chstat_reg, OMAP2_MCSPI_CHSTAT_RXS)) {w = mcspi_read_cs_reg(spi, OMAP2_MCSPI_RX0);// 根据字长存储数据if (word_len <= 8)((u8 *)xfer->rx_buf)[elements++] = w;else if (word_len <= 16)((u16 *)xfer->rx_buf)[elements++] = w;else((u32 *)xfer->rx_buf)[elements++] = w;}}// 读取最后一个字if (!mcspi_wait_for_reg_bit(chstat_reg, OMAP2_MCSPI_CHSTAT_RXS)) {w = mcspi_read_cs_reg(spi, OMAP2_MCSPI_RX0);// 存储最后一个字w = mcspi_read_cs_reg(spi, OMAP2_MCSPI_RX0);if (word_len <= 8)((u8 *)xfer->rx_buf)[elements] = w;else if (word_len <= 16)((u16 *)xfer->rx_buf)[elements] = w;else /* word_len <= 32 */((u32 *)xfer->rx_buf)[elements] = w;}}
}
五、时钟问题
omap2_mcspi_setupomap2_mcspi_setup_transfer
// 设置 SPI 传输参数函数
// spi: SPI 设备结构体指针
// t: SPI 传输结构体指针(可为 NULL)
static int omap2_mcspi_setup_transfer(struct spi_device *spi,struct spi_transfer *t)
{// 获取 SPI 设备的控制器状态(芯片选择相关配置)struct omap2_mcspi_cs *cs = spi->controller_state;// 主控制器私有数据struct omap2_mcspi *mcspi;// 时钟相关变量:参考时钟频率、相关变量:参考时钟频率、配置寄存器值、分频系数、分频值、外部时钟分频、时钟粒度u32 ref_clk_hz, l = 0, clkd = 0, div, extclk = 0, clkg = 0;// 字长(每个数据字的位数)u8 word_len = spi->bits_per_word;// 传输速度(Hz)u32 speed_hz = spi->max_speed_hz;// 从 SPI 主控制器获取私有数据mcspi = spi_master_get_devdata(spi->master);// 如果传输参数 t 存在且指定了 bits_per_word,则覆盖默认值if (t != NULL && t->bits_per_word)word_len = t->bits_per_word;// 更新控制器状态中的字长cs->word_len = word_len;// 如果传输参数 t 存在且指定了速度,则覆盖默认速度if (t && t->speed_hz)speed_hz = t->speed_hz;// 获取参考时钟频率ref_clk_hz = mcspi->ref_clk_hz;// 确保请求速度不超过参考时钟频率speed_hz = min_t(u32, speed_hz, ref_clk_hz);// 计算时钟分频if (speed_hz < (ref_clk_hz / OMAP2_MCSPI_MAX_DIVIDER)) {// 低速模式:使用移位分频clkd = omap2_mcspi_calc_divisor(speed_hz, ref_clk_hz);speed_hz = ref_clk_hz >> clkd; // 计算实际速度clkg = 0; // 禁用时钟粒度} else {// 高速模式:使用直接分频div = (ref_clk_hz + speed_hz - 1) / speed_hz; // 向上取整speed_hz = ref_clk_hz / div; // 计算实际速度clkd = (div - 1) & 0xf; // 低 4 位分频值extclk = (div - 1) >> 4; // 高 4 位分频值clkg = OMAP2_MCSPI_CHCONF_CLKG; // 启用时钟粒度}// 获取当前通道配置寄存器的值l = mcspi_cached_chconf0(spi);/* 配置引脚方向 */if (mcspi->pin_dir == MCSPI_PINDIR_D0_IN_D1_OUT) {// 三线制模式 (D0 输入, D1 输出)l &= ~OMAP2_MCSPI_CHCONF_IS; // 单线双向模式l &= ~OMAP2_MCSPI_CHCONF_DPE1; // 禁用 D1 输出l |= OMAP2_MCSPI_CHCONF_DPE0; // 使能 D0 输出}能 D0 输出} else {// 标准四线制模式 (独立 MISO/MOSI)l |= OMAP2_MCSPI_CHCONF_IS; // 独立引脚模式l |= OMAP2_MCSPI_CHCONF_DPE1; // 使能 D1 (MOSI) 输出l &= ~OMAP2_MCSPI_CHCONF_DPE0; // 禁用 D0 (MISO) 输出}/* 设置字长 */l &= ~OMAP2_M l &= ~OMAP2_MCSPI_CHCONF_WL_MASK; // 清除字长位域l |= (word_len - 1) << 7; // 设置新字长(寄存器值 = 实际位数-1)/* 设置片选极性 */if (!(片选极性 */if (!(spi->mode & SPI_CS_HIGH))l |= OMAP2_MCSPI_CHCONF_EPOL; // 低电平有效(常规)elsel &= ~OMAP elsel &= ~OMAP2_MCSPI_CHCONF_EPOL; // 高电平有效/* 设置时钟分频 */l &= ~OMAP2_MCSPI_CHCONF_CLKD_MASK; // 清除分频位域l |= clkd << 2; // 设置新的分频值/* 设置时钟粒度 */l &= ~OMAP2_MCSPI_CHCONF_CLKG; // 清除时钟粒度位l |= clkg; // 设置时钟粒度配置if (clkg) {// 如果启用了时钟粒度,配置外部时钟分频cs->chctrl0 &= ~OMAP2_MCSPI_CHCTRL_EXTCLK_MASK; // 清除外部时钟位域cs->chctrl0 |= extclk << 8; // 设置外部时钟分频值// 更新通道控制寄存器0mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCTRL0,CSPI_CHCTRL0, cs->chctrl0);}/* 设置 SPI 模式 (CPOL/CPHA) */if (spi->mode & SPI_CPOL)l |= OMAP2_MCSPI_CHCONF_POL; // 时钟空闲高电平elsel &= ~OMAP2_MCSPI_CHCONF_POL; // 时钟空闲低电平if (spi->mode & SPI_CPHA)l |= OMAP2_MCSPI_CHCONF_PHA; // 数据在第二个时钟边沿采样
数据在第二个时钟边沿采样elsel &= ~OMAP2_MCSPI_CHCONF_PHA; // 数据在第一个时钟边沿采样// 将配置写入通道配置寄存器0mcspi_write_chconf0(spi, l);// 保存当前 SPI 模式cs->mode = spi->mode;// 输出调试信息:速度、采样边沿和时钟极性、采样边沿和时钟极性dev_dbg(&spi->dev, "setup: speed %d, sample %s edge, clk %s\n",speed_hz,(spi->mode & SPI_CPHA) ? "trailing" : "leading", // CPHA=1: 后沿采样(spi->mode & SPI_CPOL) ? "inverted" : "normal"); // CPOL=1: 反向时钟return 0; // 成功返回
}
其中
- ref_clk_hz 是传入的参考时钟,由设备树中 clocks = <&k3_clks 339 1>;决定
- speed_hz 是传入的时钟和设备树中 spi-max-frequency = <24000000>;的最小值
- 内部分频最小是2分频,div 为1时异常
在tda4中,我测试,当参考时钟是50MHZ时,可用的时钟有25MHZ、16.6MHZ、12.5HMZ