歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> linux內核SPI總線驅動分析(一)

linux內核SPI總線驅動分析(一)

日期:2017/3/3 11:54:28   编辑:Linux內核

下面有兩個大的模塊:一個是SPI總線驅動的分析 (研究了具體實現的過程)

另一個是SPI總線驅動的編寫(不用研究具體的實現過程)SPI總線驅動分析

1 SPI概述 SPI是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍設備接口,是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在 EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只占用四根線,節約了芯片的管腳,同時為PCB的布局上節省空間,提供方便。

SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主設備和一個或多個從設備,需要4根線,事實上3根也可以。也是所有基於SPI的設備共有的,它們是SDI(數據輸入),SDO(數據輸出),SCLK(時鐘),CS(片選)。

MOSI(SDO):主器件數據輸出,從器件數據輸入。

MISO(SDI):主器件數據輸入,從器件數據輸出。

SCLK :時鐘信號,由主器件產生。

CS:從器件使能信號,由主器件控制。

其中CS是控制芯片是否被選中的,也就是說只有片選信號為預先規定的使能信號時(高電位或低電位),對此芯片的操作才有效,這就允許在同一總線上連接多個SPI設備成為可能。需要注意的是,在具體的應用中,當一條SPI總線上連接有多個設備時,SPI本身的CS有可能被其他的GPIO腳代替,即每個設備的CS腳被連接到處理器端不同的GPIO,通過操作不同的GPIO口來控制具體的需要操作的SPI設備,減少各個SPI設備間的干擾。

SPI是串行通訊協議,也就是說數據是一位一位從MSB或者LSB開始傳輸的,這就是SCK時鐘線存在的原因,由SCK提供時鐘脈沖,MISO、MOSI則基於此脈沖完成數據傳輸。 SPI支持4-32bits的串行數據傳輸,支持MSB和LSB,每次數據傳輸時當從設備的大小端發生變化時需要重新設置SPI Master的大小端。

2 Linux SPI驅動總體架構 在2.6的linux內核中,SPI的驅動架構可以分為如下三個層次:SPI 核心層、SPI控制器驅動層和SPI設備驅動層。

Linux 中SPI驅動代碼位於drivers/spi目錄。

2.1 SPI核心層 SPI核心層是Linux的SPI核心部分,提供了核心數據結構的定義、SPI控制器驅動和設備驅動的注冊、注銷管理等API。其為硬件平台無關層,向下屏蔽了物理總線控制器的差異,定義了統一的訪問策略和接口;其向上提供了統一的接口,以便SPI設備驅動通過總線控制器進行數據收發。

Linux中,SPI核心層的代碼位於driver/spi/ spi.c。由於該層是平台無關層,本文將不再敘述,有興趣可以查閱相關資料。

2.2 SPI控制器驅動層 SPI控制器驅動層,每種處理器平台都有自己的控制器驅動,屬於平台移植相關層。它的職責是為系統中每條SPI總線實現相應的讀寫方法。在物理上,每個SPI控制器可以連接若干個SPI從設備。

在系統開機時,SPI控制器驅動被首先裝載。一個控制器驅動用於支持一條特定的SPI總線的讀寫。一個控制器驅動可以用數據結構struct spi_master來描述。

在include/liunx/spi/spi.h文件中,在數據結構struct spi_master定義如下:

struct spi_master {

struct device dev;

s16 bus_num;

u16 num_chipselect;

int (*setup)(struct spi_device *spi);

int (*transfer)(struct spi_device *spi, struct spi_message *mesg);

void (*cleanup)(struct spi_device *spi);

};

bus_num為該控制器對應的SPI總線號。

num_chipselect 控制器支持的片選數量,即能支持多少個spi設備

setup函數是設置SPI總線的模式,時鐘等的初始化函數, 針對設備設置SPI的工作時鐘及數據傳輸模式等。在spi_add_device函數中調用。

transfer函數是實現SPI總線讀寫方法的函數。實現數據的雙向傳輸,可能會睡眠

cleanup注銷時候調用2.3 SPI設備驅動層 SPI設備驅動層為用戶接口層,其為用戶提供了通過SPI總線訪問具體設備的接口。

SPI設備驅動層可以用兩個模塊來描述,struct spi_driver和struct spi_device。

相關的數據結構如下:

struct spi_driver {

int (*probe)(struct spi_device *spi);

int (*remove)(struct spi_device *spi);

void (*shutdown)(struct spi_device *spi);

int (*suspend)(struct spi_device *spi, pm_message_t mesg);

int (*resume)(struct spi_device *spi);

struct device_driver driver;

};

Driver是為device服務的,spi_driver注冊時會掃描SPI bus上的設備,進行驅動和設備的綁定,probe函數用於驅動和設備匹配時被調用。從上面的結構體注釋中我們可以知道,SPI的通信是通過消息隊列機制,而不是像I2C那樣通過與從設備進行對話的方式。

struct spi_device {

struct device dev;

struct spi_master *master;

u32 max_speed_hz;

u8 chip_select;

u8 mode;

u8 bits_per_word;

int irq;

void *controller_state;

void *controller_data;

char modalias[32];

};

.modalias = "m25p10",

.mode =SPI_MODE_0, //CPOL=0, CPHA=0 此處選擇具體數據傳輸模式

.max_speed_hz = 10000000, //最大的spi時鐘頻率

/* Connected to SPI-0 as 1st Slave */

.bus_num = 0, //設備連接在spi控制器0上

.chip_select = 0, //片選線號,在S5PC100的控制器驅動中沒有使用它作為片選的依據,而是選擇了下文controller_data裡的方法。

.controller_data = &smdk_spi0_csi[0],

通常來說spi_device對應著SPI總線上某個特定的slave。並且spi_device封裝了一個spi_master結構體。spi_device結構體包含了私有的特定的slave設備特性,包括它最大的頻率,片選那個,輸入輸出模式等等

3 OMAP3630 SPI控制器 OMAP3630上SPI是一個主/從的同步串行總線,這邊有4個獨立的SPI模塊(SPI1,SPI2,SPI3,SPI4),各個模塊之間的區別在於SPI1支持多達4個SPI設備,SPI2和SPI3支持2個SPI設備,而SPI4只支持1個SPI設備。

SPI控制器具有以下特征:

 1.可編程的串行時鐘,包括頻率,相位,極性。

 2.支持4到32位數據傳輸

 3.支持4通道或者單通道的從模式

 4.支持主的多通道模式

 4.1全雙工/半雙工

 4.2只發送/只接收/收發都支持模式

 4.3靈活的I/O端口控制

 4.4每個通道都支持DMA讀寫

 5.支持多個中斷源的中斷時間

 6.支持wake-up的電源管理

 7.內置64字節的FIFO

4 spi_device以下一系列的操作是在platform板文件中完成!

spi_device的板信息用spi_board_info結構體來描述:

struct spi_board_info {

charmodalias[SPI_NAME_SIZE];

const void*platform_data;

void*controller_data;

intirq;

u32max_speed_hz;

u16bus_num;

u16chip_select;

u8mode;

};

這個結構體記錄了SPI外設使用的主機控制器序號、片選信號、數據比特率、SPI傳輸方式等

構建的操作是以下的兩個步驟:

1.static struct spi_board_info s3c_spi_devs[] __initdata = {

{.modalias = "m25p10a",

.mode = SPI_MODE_0,.max_speed_hz = 1000000,

.bus_num = 0,.chip_select = 0,

.controller_data = &smdk_spi0_csi[SMDK_MMCSPI_CS],},

};2.

而這個info在init函數調用的時候會初始化:

spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));

spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));//注冊spi_board_info。這個代碼會把spi_board_info注冊到鏈表board_list上。spi_device封裝了一個spi_master結構體,事實上spi_master的注冊會在spi_register_board_info之後,spi_master注冊的過程中會調用scan_boardinfo掃描board_list,找到掛接在它上面的spi設備,然後創建並注冊spi_device。至此spi_device就構建並注冊完成了!!!!!!!!!!!!!

5 spi_driver的構建與注冊driver有幾個重要的結構體:spi_driver、spi_transfer、spi_message

driver有幾個重要的函數 :spi_message_init、spi_message_add_tail、spi_sync//spi_driver的構建

static struct spi_driver m25p80_driver = { .driver = {

.name ="m25p80", .bus =&spi_bus_type,

.owner = THIS_MODULE, },

.probe = m25p_probe, .remove =__devexit_p(m25p_remove),

};//spidriver的注冊

spi_register_driver(&m25p80_driver);

在有匹配的spi_device時,會調用m25p_probeprobe裡完成了spi_transfer、spi_message的構建;

spi_message_init、spi_message_add_tail、spi_sync、spi_write_then_read函數的調用例如:

*/

static int m25p10a_read( struct m25p10a *flash, loff_t from,

size_t len, char *buf )

{

int r_count = 0, i;

struct spi_transfer st[2];

struct spi_message msg;

spi_message_init( &msg );

memset( st, 0, sizeof(st) );

flash->cmd[0] = CMD_READ_BYTES;

flash->cmd[1] = from >> 16;

flash->cmd[2] = from >> 8;

flash->cmd[3] = from;

st[ 0 ].tx_buf = flash->cmd;

st[ 0 ].len = CMD_SZ;

spi_message_add_tail( &st[0], &msg );

st[ 1 ].rx_buf = buf;

st[ 1 ].len = len;

spi_message_add_tail( &st[1], &msg );

mutex_lock( &flash->lock );

/* Wait until finished previous write command. */

if (wait_till_ready(flash)) {

mutex_unlock( &flash->lock );

return -1;

}

spi_sync( flash->spi, &msg );

r_count = msg.actual_length - CMD_SZ;

printk( "in (%s): read %d bytes\n", __func__, r_count );

for( i = 0; i < r_count; i++ ) {

printk( "0x%02x\n", buf[ i ] );

}

mutex_unlock( &flash->lock );

return 0;

}

static int m25p10a_write( struct m25p10a *flash, loff_t to,

size_t len, const char *buf )

{

int w_count = 0, i, page_offset;

struct spi_transfer st[2]; struct spi_message msg;

write_enable( flash ); //寫使能

[b] spi_message_init( &msg ); [/b]

memset( st, 0, sizeof(st) );

flash->cmd[0] = CMD_PAGE_PROGRAM;

flash->cmd[1] = to >> 16;

flash->cmd[2] = to >> 8;

flash->cmd[3] = to;

st[ 0 ].tx_buf = flash->cmd;

st[ 0 ].len = CMD_SZ;

//填充spi_transfer,將transfer放在隊列後面 spi_message_add_tail( &st[0], &msg );

st[ 1 ].tx_buf = buf;

st[ 1 ].len = len;

spi_message_add_tail( &st[1], &msg ); spi_sync( flash->spi, &msg ); 調用spi_master發送spi_message

return 0;

}

static int m25p10a_probe(struct spi_device *spi)

{

int ret = 0;

struct m25p10a *flash;

char buf[ 256 ];

flash = kzalloc( sizeof(struct m25p10a), GFP_KERNEL );

flash->spi = spi;

/* save flash as driver's private data */

spi_set_drvdata( spi, flash );

memset( buf, 0x7, 256 );

m25p10a_write( flash, 0, 20, buf); //0地址寫入20個7 memset( buf, 0, 256 );

m25p10a_read( flash, 0, 25, buf ); //0地址讀出25個數 return 0;

}

到目前為止,完成了SPI的驅動和應用

Copyright © Linux教程網 All Rights Reserved