spi slave及master接口驅動及傳輸時序


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兩根線,使用分時復用



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM