spi 协议
请仔细参考 https://www.cnblogs.com/deng-tao/p/6004280.html
spi 一般为了通讯至少需要4根线
cs(spi_enable)主机控制spi从设备的使能脚(主机控制,从设备根据这个脚状态 拉低情况,来判断接下来是要开始通讯了)
clk(spi_clock) 务必看下主从设备的芯片手册 ,如果速度过快 会导致信号异常,速度过慢一般没什么影响就是 通讯速度慢
mosi(spi_master输出_slave输入)先发高位数据
miso(spi_master输入_slave输出)先发高位数据
有4种模式 ,主要是 根据cs不用的时候的电平 和 数据和时钟采样关系决定的
关于mosi 和 miso 有一点说明,因为有时候只是读有时候是写,但是 这两根线数据不能断,所有部分不关心的数据可以忽略。具体每次尝试长度和数据内容 都是是(从)芯片而定的。
如下图第一个字节 可以是表示读写,第二个字节表示寄存器位置,假如说 00表示读 0x01表示寄存器 1,那么下图中mosi 前面发两个自己0x00 ,0x11,接下来要只看miso的数据了0x03 ,0x04。
linux spi 的驱动工作流程
linux控制器驱动一般在 dirver/spi 目录下面 spi_xxxx.c
首先spi_register_master , spi 主机控制器的注册,看完 spi master 做工作机制 ,以后写spi 设备就简单了
spi_register_master这个函数东西也不多,就是初始化一些后面用到的 变量,和判断下从 dts 获取的 信息是否正确。 如果dts参数设置异常 注册失败的。
接下来就是核心了,因为决定了整个spi 代码的流程
/* If we’re using a queued driver, start the queue */ //
if (master->transfer) // 对于控制器这里比较重要 。 transfer是负责真正spi master 和spi slave 进行 spi 数据 的工作。 如果说平台机制比较处理不是很复杂 ,那就用linux 自己 写好的队列框架 spi_master_initialize_queue 完成数据传输流程控制,但是如果,如果简单流程不满足要求 就 自己实现传输过程。
dev_info(dev, “master is unqueued, this is deprecated\n”);
else {
status = spi_master_initialize_queue(master);
if (status) {
device_del(&master->dev);
goto done;
}
}
关于 linux 的 spi_master_initialize_queue 大致原理是:队列+线程,当然这是driver 常用方式。努力维护一个排队,一个处理driver 提交的 spi 数据。
我们先接触几个关键字 spi_master spi_message spi_transfer
可以理解为 在上美食广场吃饭 ,spi master 就是各个美食店的点餐员 ,餐厅的美食店spi master 是固定的,但是身为消费者的你 叫做 spi slave(spi0 ,spi1,spi2…)。这里涉及到一个机制 就是排队点餐 ^_^!, 你来点餐,所以的 点餐小票 就是 spi_message , 点餐小票 里面 有你想要点的菜就是 spi_transfer。 master 主要做的事情就是 努力把消费者 spi slave 的订餐任务 spi_message ,里面的每项 spi_transfer(每道餐) 具体做出来(完成数据收发)。
什么控制spi 外设的driver 工程师需要想好 吃多少饭(处理什么数据) 就行了 可能是 菜 spi_transfer0,或者是菜 spi_transfer1,什么汤spi_transfer2。。。。这些spi_transfer xx,统一规整到 你 spi_message(只要点餐的时候,说出来就行)
然后调用函数 spi_message_add_tail 表示下好决心吃什么啦,接下来spi_sync 就是点菜 后排队等着老板把东西做好给你,master 会按照点餐的顺序,这都是master 驱动中做的事
好了我们回归 spi_register_master 上面给的 if (master->transfer)的含义,就是判断一下 master->transfer 是否存在,我们就 看某平台 spi 驱动吧,spi.c 文件。
我们在 好好看下 ,master->transfer 不是 spi->transfer,这个 master 是控制器,都是某平台 工程师自己写的代码,写平台主控制器的驱动,我们平时写的都是 slave spi driver。
文件在 kernel -driver -spi-平台-spi.c mt_spi_probe这个函数 master->transfer = mt_spi_transfer; 这一行存在。
我继续说下linux spi 框架吧
跟上
spi_master_initialize_queue(master);
master->transfer = spi_queued_transfer; 用我 linux 提供的通用spi 队列
master->transfer_one_message = spi_transfer_one_message 用我 linux 提供的通用spi 传输函数,或者也可以自定义这个函数
( or define by master )
/* Initialize and start queue */
spi_init_queue(master) 初始化队列
init_kthread_work(&master->pump_messages, spi_pump_messages); 开一个线程
spi_start_queue(master) 开始跑队列
queue_kthread_work(&master->kworker, &master->pump_messages); 开启刚申请的线程,下面的 pump_messages 就是线程中 一个工作,看似仅有一个work,一直循环也够了,
其他模块也是类似的机制几秒 跑一次
—>pump_messages—>spi_pump_messages
master->unprepare_transfer_hardware(master)
master->prepare_transfer_hardware(master);
master->prepare_message(master, master->cur_msg);
master->cur_msg_prepared = true;
spi_map_msg(master, master->cur_msg); 之前都是一些准备工作啊,下面 transfer_one_message开始 轮到你 message 打饭了
master->transfer_one_message(master, master->cur_msg); // or by driver
–>spi_transfer_one_message<–
spi_set_cs(msg->spi, true);
master->transfer_one(master, msg->spi, xfer); // core function for transfer,must implementation transfer_one,就是点菜,打饭,。。。。
wait_for_completion_timeout(&master->xfer_completion,msecs_to_jiffies(ms));
spi_finalize_current_message(master);
spi_unmap_msg(master, mesg);
master->unprepare_message(master, mesg);
queue_kthread_work(&master->kworker, &master->pump_messages); 传输结束,在回调pump_messages,保证所有传输完了
mesg->complete(mesg->context);
master->spi_sync() —>master->transfer –>spi_queued_transfer()
list_add_tail(&msg->queue, &master->queue);
queue_kthread_work(&master->kworker, &master->pump_messages); sync 就是spi_queued_transfer 添加队列和唤醒 队列
我们再看下 xxx 自己实现的 transfer
transfer函数 我们先看下这函数的linux 给的注释-》 此方法可能不会休眠;它的主要作用是只是将消息添加到队列中。
现在没有从队列中删除操作,或者任何其他请求管理对于给定的spi_device,消息队列是纯fifo 《—看到没 好好排队就是了
自己维护一个队列链表 ,mt_do_spi_setup 就是配置 模式极性,速度,字节……. mt_spi_next_message 就是处理 sync 的message ,mt_spi_next_xfer 处理transfer
list_add_tail(&msg->queue, &ms->queue);
xx_spi_next_message(ms);
xx_do_spi_setup(ms, chip_config);/* Write chip configuration to HW register */ struct chip_config
。。。
xx_spi_next_xfer(ms, msg); 下面是 每次传输的内容,主要设置寄存器包大小各种状态,数据源TX RX,模式啊DMA FIFO,然后启动传输 spi_start_transfer
spi_enable_dma(ms, mode);
spi_start_transfer(ms);
有个疑问,怎么没有上linux 那样写个异步线程啊,难道我每次排队都要等前面传输完吗?那上面pump_messages 回调自己,保证传输完整性
怎么 某平台 上面一次传输就完了,还不够我一个message呢,仅仅一个transfer 够干嘛?
好我们看下 master 中 mediated的 spi.c 中还有一个中断如果你对gic 了解些 就知道 这个中断干嘛的了,这个是 soc系统 spi 控制器的系统级别的中断,可不是 eint (扩展中断一般是gpio)
看一个都能找的到的dts吧
spi@1100c000 {
compatible = “xxx,xxx80-spi”;
cell-index = <0>;
reg = <0x1100c000 0x1000>;
interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_LOW>;
xxx,spi-padmacro = <1>;
};
上面的 interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_LOW>; 中的 64 就是内部中断号,和linux 的申请的中断号 不一样,linux 的中断号是 一种映射编码。
而 64 则是 硬件电路设计决定的 ,不用关心。 顺便提醒下 spi-padmacro 这个是 某平台 内部spi gpio映射配置寄存器。啥意思?
就是就80平台而言,gpio0-3 是spi0A,gpio4-7 spi0B 。都是spi0,那么spi0传输的时候想传输到 gpio0-3,另一个项目 用 gpio4-7 ,都是spi0 ,
那就配置 xxx,spi-padmacro = <1>; 改为 0 1 2 自己看datasheet,–!
下面继续说只传输了一次transfer就没了的情况,就是刚才提到的中断 ,那么这个中断 干吗用呢? 当传输有错误 ,或者正常传输,完成的时候都有中断。
所以 某平台 自己通过中断 产生是 在处理 每次的传输。并在最后 拉起 mt_spi_next_xfer 这个transfer 传输,把 刚才message 有剩余transfer 任务没有传输完的,继续传输下去。
并且判断 list_empty(&ms->queue),要是master里面 还有剩余message 就继续 mt_spi_next_message(ms),把剩余的若干driver 中sync提交 message 任务,跑完。
这就是 linux 通用驱动感觉有点类似 轮训 和 spi 控制器带中断驱动的一点区别的。(这里说的是控制器系统中断)
irqreturn_t mt_spi_interrupt
/* Clear interrupt status first by reading the register */
reg_val = spi_readl(ms, SPI_STATUS0_REG);
if (is_last_xfer(msg, xfer)) {
xx_spi_msg_done(ms, msg, 0);
spi_disable_dma(ms);
msg->complete(msg->context);
ms->cur_transfer = NULL;
/* continue if needed */
if (list_empty(&ms->queue)) {
spi_gpio_reset(ms);
disable_clk();
wake_unlock(&ms->wk_lock);
} else
xx_spi_next_message(ms);
} else {
ms->cur_transfer = ms->next_transfer;
xx_spi_next_xfer(ms, msg);
}
综上所述,就是 为了实现数据 输入输出,需要 设定好 spi控制器的时钟、 通讯模式、dma fifo 需要传输的数据源地址和spi 控制器中断寄存器。
linux 会开一些类似线程的方式, 最后后台等待传输完成唤醒当前线程继续处理通讯得到的数据,因为传输spi数据有大有小对于芯片而言 ,不能一直等。
spi设备驱动使用
spi设备使用比较简单,就是调用 transfer 函数 ,具体是用spi_sync函数做了封装。调用过一次算是完整一次传输(数据同步)。
struct spi_message msg;
struct spi_transfer *xfer = NULL;
spi_message_init(&msg);
xfer[0].tx_buf = tmp_buf; //要发送的数据
xfer[0].rx_buf = tmp_buf; //要接收的数据
xfer[0].len = data_len; //要收发的数据长度
xfer[0].speed_hz = 4 * 1000 * 1000; /*4M*/ //不同平台对速度的设定不一样,有的需要在此处设置
spi_message_add_tail(&xfer[0], &msg); //把要 收发数据这传输动作加到 spi控制器的队列上,传输也要排队,和linux任务一样,但是遵循先排队先处理 规则
spi_sync(accelgyro_obj_data->spi, &msg); //传输完成会返回到这里,并拿到收发完成后的数据