文档章节

spi slave及master接口驱动及传输时序

白白猪
 白白猪
发布于 2015/06/24 10:13
字数 2465
阅读 107
收藏 0

spi slave驱动
spi slave驱动在kernel中可以主要参考spidev.c,这是一个字符驱动,可以匹配kernel中的多个名称为“spidev”的spi设备,
分析这个文件,主要有以下几个重点:
1. 如何编写多设备公用驱动
2. 如何封装读写请求到spi框架层
3. spi message请求如何分发到master

自spi_board_info或者spi master注册后,两者就已经完成了匹配的工作,spi slave驱动不关心任何匹配的细节,它只需要完成
与spi slave的匹配,就可以通过slave进而找到master。这里是通过spi_register_driver(&spidev_spi_driver);注册进
kernel,而后spi框架进行name match,再调用probe,完成关于设备的一些成员初始化操作。
下面针对上面的三个问题,进行分析这个驱动,

spi设备全局链及保护信号量:
static LIST_HEAD(device_list);
static DEFINE_MUTEX(device_list_lock);

相对与设备的驱动数据:
struct spidev_data {
    dev_t            devt;            //设备号
    spinlock_t        spi_lock;        //spi 结构体的pin锁
    struct spi_device    *spi;
    struct list_head    device_entry;//挂接到device_list
    
    struct mutex        buf_lock;    //保护数据的lock
    unsigned        users;            //使用者
    u8            *buffer;            //实际数据区,由open时进行动态分配,release时释放
};
spi中任何会由多个使用者访问的区域,都需要使用锁保护,如这里的users,个人觉得需要使用原子变量而不应该简单的使用整形。
在probe的时候,首先分配spidev_data,并初始化其spi/device_entry/buf_lock/spi_lock,查找一个可用的bit用作次
设备号,创建设备spidev busnum.cs,挂到全局链中,并将私有数据spidev_data放到dev->p->driver_data中。
open时,从inode中获取dev_t,然后对比整个链,找到目标数据spidev_data,放到file->private_data中,并分配缓存
读写时,直接从file中获取对应的spidev_data数据,然后通过spi device来传递spi请求。
以上主要是数据如何传递的问题。

SPI读写请求的封装很简单,如下:
static inline ssize_t spidev_sync_write(struct spidev_data *spidev, size_t len)
{
    struct spi_transfer    t = {
            .tx_buf        = spidev->buffer,
            .len        = len,
        };
    struct spi_message    m;

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    return spidev_sync(spidev, &m);
}

static inline ssize_t spidev_sync_read(struct spidev_data *spidev, size_t len)
{
    struct spi_transfer    t = {
            .rx_buf        = spidev->buffer,
            .len        = len,
        };
    struct spi_message    m;

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    return spidev_sync(spidev, &m);
}

封装的同步函数:
static ssize_t spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
    DECLARE_COMPLETION_ONSTACK(done);
    int status;

    message->complete = spidev_complete;
    message->context = &done;

    spin_lock_irq(&spidev->spi_lock);
    if (spidev->spi == NULL)
        status = -ESHUTDOWN;
    else
        status = spi_async(spidev->spi, message);
    spin_unlock_irq(&spidev->spi_lock);

    if (status == 0) {
        wait_for_completion(&done);
        status = message->status;
        if (status == 0)
            status = message->actual_length;
    }
    return status;
}
只需要调用spi_async就可以完成数据读取/写入的操作。
这个函数在内部真正做了什么?如何分发/回调?我们走一遍代码:
首先master内部有两个锁:
spinlock_t        bus_lock_spinlock;    【用于异步】   spi_async
struct mutex        bus_lock_mutex;    【用于同步】     spi_sync
对于不同的场景,需要对master进行不同类型的加锁,
异步:
spin_lock_irqsave(&master->bus_lock_spinlock, flags);
ret = __spi_async(spi, message);
{
    message->spi = spi;
    message->status = -EINPROGRESS;
    return master->transfer(spi, message);
}
spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);

同步:
    message->complete = spi_complete;
    message->context = &done;

    if (!bus_locked)
        mutex_lock(&master->bus_lock_mutex);

    status = spi_async_locked(spi, message);

    if (!bus_locked)
        mutex_unlock(&master->bus_lock_mutex);

    if (status == 0) {
        wait_for_completion(&done);
        status = message->status;
    }
这里即在kernel内部完成了同步的工作,不需要像spidev那样需要自己等待完成量,使用的是bus_lock_mutex
内部与异步的调用方式一致:
spin_lock_irqsave(&master->bus_lock_spinlock, flags);
ret = __spi_async(spi, message);
spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);
从这里可以看出,同步与异步没有本质差别,只是多了一个完成量的操作而已。

最终调用的函数为:master->transfer(spi, message);
这个函数将在spi master中分析。

在spi slave侧需要熟悉传输的参数的每个域的功能,才能很好的完成工作
struct spi_transfer {
    const void    *tx_buf;                    //非dma 发送地址
    void        *rx_buf;                    //非dma 读取地址
    unsigned    len;                        //tx/rx bufffer size

    dma_addr_t    tx_dma;                        //若spi_message.is_dma_mapped置位,为transfer的dma address
    dma_addr_t    rx_dma;                        //若spi_message.is_dma_mapped置位,为read的dma address

    unsigned    cs_change:1;                //传输完成后,修改cs信号
    u8        bits_per_word;                    //长度,优先覆盖spi_board_info的设置(32)
    u16        delay_usecs;                    //传输后继续传输或者cs结束传输的中间时隙
    u32        speed_hz;                        //本次传输的速度,可以优先覆盖spi_board_info里的设置

    struct list_head transfer_list;            //挂接到spi_message上的连接体
};

struct spi_message {
    struct list_head    transfers;            //transfer链

    struct spi_device    *spi;                //对应的spi设备

    unsigned        is_dma_mapped:1;        //是否启动dma功能

    void            (*complete)(void *context);    //完成后回调
    void            *context;                    //回调参数
    unsigned        actual_length;                //传输的真正长度
    int            status;                            //0,成功

    struct list_head    queue;                    //driver使用
    void            *state;
};
每个域的使用方法,这里直接看起来并不明确,必须结合master的驱动。
---------------------------------------------------------------------------------------------------------
spi master驱动
---------------------------------------------------------------------------------------------------------
SPI 设备资源:
static struct resource s3c_spi0_resource[] = {
    [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI, SZ_32),
    [1] = DEFINE_RES_IRQ(IRQ_SPI0),
};

struct platform_device s3c_device_spi0 = {
    .name        = "s3c2410-spi",
    .id        = 0,
    .num_resources    = ARRAY_SIZE(s3c_spi0_resource),
    .resource    = s3c_spi0_resource,
    .dev        = {
        .dma_mask        = &samsung_device_dma_mask,
        .coherent_dma_mask    = DMA_BIT_MASK(32),
    }
};

static struct resource s3c_spi1_resource[] = {
    [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI1, SZ_32),
    [1] = DEFINE_RES_IRQ(IRQ_SPI1),
};

struct platform_device s3c_device_spi1 = {
    .name        = "s3c2410-spi",
    .id        = 1,
    .num_resources    = ARRAY_SIZE(s3c_spi1_resource),
    .resource    = s3c_spi1_resource,
    .dev        = {
        .dma_mask        = &samsung_device_dma_mask,
        .coherent_dma_mask    = DMA_BIT_MASK(32),
    }
};

在此之间,走过了一些弯路学了verilog/modelsim,在之前一直不明白的事情在逐渐的尝试中获得了新的认识,
硬件的ip core的工作是由clock来驱动的,而不是软件意义上的过程,在同步时钟的上升/下降沿中进行数据处理,移位等
在SPI 的ip core设计中,主要有三个模块:
1. clock generate 
2. data shift
3. register control
首先通过AP过来的系统时钟及设备能够支持的最大时钟频率,计算出对应的最接近的分频系数,而模块1就是根据这个分频系数
来通过系统的源clock产生对应的目标clock。【因为对于SPI IP不需要独立的精确的晶振】
生成了与slave同步的clock之后,输出到模块2, 模块2负责具体的发送数据功能。具体的采数发数时序见第三节。
而register control则负责所有的可配置接口,如: 分频系数,支持位宽,FIFO深度,支持的片选数,以及相应的MSB/LSB
设置选项。
在SPI的协议中,最大的误区在于master与slave之间的私有协议:
SPI master的本生设计中并不支持具体的传输协议,而是简单的提供了一个传输数据的通路,而协议则是由实现的slave端,以及
slave端驱动来决定的。下面来总结这个数据发送与接收的具体过程:

读指定地址: read(addr, &value, len);
1. 配置相关读取操作的寄存器
2. slave驱动 封装协议CMD【描述base + addr + len + flags】
3. 拉下slave对应在master上的cs【低电平有效】
4. 发送指定位宽cmd到slave端【由master来驱动,而slave只需要发送数据到对应fifo并启动发送即可】
5. slave端接收到指定的cmd,在MISO线上回应对应的数据
6. slave驱动从master 的fifo中等待数据,当master 读取到对应的线上数据,并放于FIFO中
7. 读取到数据,拉高CS

/*make sure len is word units*/
int gps_spi_read_bytes_test3( u32 len,u32 addr,u32 *data, bool sys, u32 base){
    u32 read_cmd[2];
    local_spi_init();
    read_cmd[0] = SPI_READ_CMD(len,addr >> 2 + base);
    spi_assert_function(0);
    local_spi_write((u8*)read_cmd, 4);
    local_spi_read((u8 *)data, 4);
    spi_assert_function(1);
    return 0;
}


写指定地址: write(addr, &value, len);
int gps_spi_write_bytes_test2( u32 len,u32 addr,u32 data){
    u32 write_cmd[2],one_read;
    local_spi_init();
    write_cmd[0] = SPI_WRITE_SYS_CMD(len*1,addr >> 2);
    write_cmd[1] = data;
    spi_assert_function(0);
    local_spi_write((u8 *)write_cmd, 8);
    spi_assert_function(1);
    return 0;
}

一些读写的实现细节:
int local_spi_read(u8 *buf, u32 size){
    while (cnt < size){
        /*wait for data*/
        while (reg->sts2 & (0x1 << 5)){
            if (time_out++ > READ_TIME_OUT){
                goto read_exit;
            }
        }
        *p = (u32)reg->txd;
        p++;
        cnt += 4;
    }
    return 0;
}

int local_spi_write(u8 *buf, u32 size){
    while (cnt < size){
        /*if tx fifo is not full*/
        while (reg->sts2 & (0x1 << 6));
        writel(*p, SPRD_SPI1_BASE);
        p++;
        /*bitlen is 4*/
        cnt += 4;
    }
}
以上为master 驱动需要实现传输的一些流程
---------------------------------------------------------------------------------------------------------
spi传输时序问题
---------------------------------------------------------------------------------------------------------
主要关注两个参数:
CPOL: Clock初始电平(0:低电平,1:高电平)
CPHA: 采样位置(0:第一个跳变边沿,1:第二个跳变边沿)

0/0:上升沿采样,下降沿发送
0/1:下降沿采样,上升沿发送
1/0:下降沿采样,上升沿发送
1/1:上升沿采样,下降沿发送

这里需要注意,在双工状态下,不可同沿采样及发送,必须错开。
采样的基本原理如下:
master与slave保持时钟同步,master每输出一个时钟,master输出一个数据(MOSI线),slave便响应一个数据(MISO线),在示波器上
可以看到三条线的数据交互过程。
setup time 与 hold time【假设上升沿采样】
建立时间:是指在时钟信号上升沿到来以前,数据稳定不变的时间,如果建立时间不够,数据将不能在这个时钟上升沿被打入触发器;
保持时间:是指在时钟信号上升沿到来以后,数据稳定不变的时间,如果保持时间不够,数据同样不能被打入触发器。
首先当第一个数据发送时master的寄存器首先锁住这个值,当第一个上升沿到来时,master获取数据,slave收到并Hold住固定时间,
同理如slave的数据接收.

相关问题
---------------------------------------------------------------------------------------------------------
一个由时钟倍频引起的SPI同步思考:
  assign cnt_zero = cnt == {`SPI_DIVIDER_LEN{1'b0}};
  assign cnt_one  = cnt == {{`SPI_DIVIDER_LEN-1{1'b0}}, 1'b1};
  
  // Counter counts half period
  always @(posedge clk_in or posedge rst)
  begin
    if(rst)
      cnt <= #Tp {`SPI_DIVIDER_LEN{1'b1}};
    else
      begin
        if(!enable || cnt_zero)
          cnt <= #Tp divider;
        else
          cnt <= #Tp cnt - {{`SPI_DIVIDER_LEN-1{1'b0}}, 1'b1};
      end
  end
  
  // clk_out is asserted every other half period
  always @(posedge clk_in or posedge rst)
  begin
    if(rst)
      clk_out <= #Tp 1'b0;
    else
      clk_out <= #Tp (enable && cnt_zero && (!last_clk || clk_out)) ? ~clk_out : clk_out;
  end

当divider为3时,计算的过程为:
   |     |    |   |    |    |    |    |    |    |    |    |
  -> 2 -> 1 -> 0 -> 3                    -> 2 -> 1 -> 0 -> 3
                      -> 2 -> 1 -> 0 -> 3
                   
三线模式SPI:
用一个data线,来代替MOSI/MISO两根线,使用分时复用

本文转载自:http://www.cnblogs.com/wenhuisun/archive/2013/05/14/3078786.html

白白猪
粉丝 1
博文 25
码字总数 8880
作品 0
深圳
私信 提问
Linux驱动修炼之道-SPI驱动框架源码分析(上)

SPI驱动架构,以前用过,不过没这个详细,跟各位一起分享: 来自:http://blog.csdn.net/woshixingaaa/article/details/6574215 SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,...

长平狐
2012/06/12
1K
0
SPI总线协议介绍

SPI总线协议介绍 一、技术性能 SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,...

长平狐
2013/06/03
115
0
AliOS Things 硬件抽象层(HAL)对接系列3 — I2C driver porting

HAL层(Hardware abstraction layer) 的目的是为了屏蔽底层不同芯片平台的差异,从而使驱动层上面的软件不会随芯片平台而改变。AliOS Things定义了全面的HAL抽象层,这个系列主要介绍AliOS...

泉墨170385
2018/08/10
0
0
01-嵌入式入门-如何看原理图

最近由于找到的工作是偏于嵌入式方向,因此又重新开始学习已经丢弃两年的知识。新手学习知识感觉有一个通病:喜欢收集各种各样的视频、资料,网盘里收藏一大堆,但是却从没有打开看过,到头来...

梦想成大牛
2018/06/28
0
0
Android Things:外设I/O接口-SPI

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 https://blog.csdn.net/p106786860/article/details/71076450 一、接口简介 串行外围接口(S...

1024工场
2017/05/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

JMM内存模型(一)&volatile关键字的可见性

在说这个之前,我想先说一下计算机的内存模型: CPU在执行的时候,肯定要有数据,而数据在内存中放着呢,这里的内存就是计算机的物理内存,刚开始还好,但是随着技术的发展,CPU处理的速度越...

走向人生巅峰的大路
15分钟前
29
0
你对AJAX认知有多少(2)?

接着昨日内容,我们几天继续探讨ajax的相关知识点 提到ajax下面几个问题又是必须要了解的啦~~~ 8、在浏览器端如何得到服务器端响应的XML数据。 通过XMLHttpRequest对象的responseXMl属性 9、 ...

理性思考
24分钟前
4
0
正则表达式基础(一)

1.转义 转义的作用: 当某个字符在表达式中具有特殊含义,例如字符串引号中出现了引号,为了可以使用这些字符本身,而不是使用其在表达式中的特殊含义,则需要通过转义符“\”来构建该字符转...

清自以敬
27分钟前
4
0
idea中@Data标签getset不起作用

背景:换电脑以后在idea中有@data注解都不生效 解决办法:idea装个插件 https://blog.csdn.net/seapeak007/article/details/72911529...

栾小糖
33分钟前
4
0
Apache Kudu 不能删除不存在的数据

使用Apache Kudu客户端,对KafkaConnect Sink 进行扩展。 使用的Apache Kudu 的Java 客户端。突然有天发现作业无法提交,一直报错。 后来才发现这是Kudu自身的一种校验机制。为了忽略这种校验...

吐槽的达达仔
43分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部