SPI 协议 linux设备驱动和控制器驱动

 

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);    //传输完成会返回到这里,并拿到收发完成后的数据

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值