一、概述
基于子系統去開發驅動程序已經是linux內核中普遍的做法了。前面寫過基于I2C子系統的驅動開發。本文介紹另外一種常用總線SPI的開發方法。SPI子系統的開發和I2C有很多的相似性,大家可以對比學習。本主題分為兩個部分敘述,第一部分介紹基于SPI子系統開發的理論框架;第二部分以華清遠見教學平臺FS_S5PC100上的M25P10芯片為例(內核版本2.6.29),編寫一個SPI驅動程序
一、概述
基于子系統去開發驅動程序已經是linux內核中普遍的做法了。前面寫過基于I2C子系統的驅動開發。本文介紹另外一種常用總線SPI的開發方法。SPI子系統的開發和I2C有很多的相似性,大家可以對比學習。本主題分為兩個部分敘述,第一部分介紹基于SPI子系統開發的理論框架;第二部分以華清遠見教學平臺FS_S5PC100上的M25P10芯片為例(內核版本2.6.29),編寫一個SPI驅動程序實例。
二、SPI總線協議簡介
介紹驅動開發前,需要先熟悉下SPI通訊協議中的幾個關鍵的地方,后面在編寫驅動時,需要考慮相關因素。
SPI總線由MISO(串行數據輸入)、MOSI(串行數據輸出)、SCK(串行移位時鐘)、CS(使能信號)4個信號線組成。如FS_S5PC100上的M25P10芯片接線為:
上圖中M25P10的D腳為它的數據輸入腳,Q為數據輸出腳,C為時鐘腳。
SPI常用四種數據傳輸模式,主要差別在于:輸出串行同步時鐘極性(CPOL)和相位(CPHA)可以進行配置。如果CPOL= 0,串行同步時鐘的空閑狀態為低電平;如果CPOL= 1,串行同步時鐘的空閑狀態為高電平。如果CPHA= 0,在串行同步時鐘的前沿(上升或下降)數據被采樣;如果CPHA = 1,在串行同步時鐘的后沿(上升或下降)數據被采樣。
這四種模式中究竟選擇哪種模式取決于設備。如M25P10的手冊中明確它可以支持的兩種模式為:CPOL=0 CPHA=0? 和 CPOL=1 CPHA=1
三、linux下SPI驅動開發
首先明確SPI驅動層次,如下圖:???????
我們以上面的這個圖為思路
1、 Platform bus
Platform bus對應的結構是platform_bus_type,這個內核開始就定義好的。我們不需要定義。
2、Platform_device
SPI控制器對應platform_device的定義方式,同樣以S5PC100中的SPI控制器為例,參看arch/arm/plat-s5pc1xx/dev-spi.c文件
點擊(此處)折疊或打開
struct platform_device s3c_device_spi0?=?{
.name?=?"s3c64xx-spi",?//名稱,要和Platform_driver匹配
.id?=?0,?//第0個控制器,S5PC100中有3個控制器
.num_resources?=?ARRAY_SIZE(s5pc1xx_spi0_resource),?//占用資源的種類
.resource?=?s5pc1xx_spi0_resource,?//指向資源結構數組的指針
.dev?=?{
.dma_mask?=?&spi_dmamask,?//dma尋址范圍?
.coherent_dma_mask?=?DMA_BIT_MASK(32),?//可以通過關閉cache等措施保證一致性的dma尋址范圍
.platform_data?=?&s5pc1xx_spi0_pdata,?//特殊的平臺數據,參看后文
},
};
static struct s3c64xx_spi_cntrlr_info s5pc1xx_spi0_pdata?=?{
.cfg_gpio?=?s5pc1xx_spi_cfg_gpio,?//用于控制器管腳的IO配置
.fifo_lvl_mask?=?0x7f,
.rx_lvl_offset?=?13,
};
static?int?s5pc1xx_spi_cfg_gpio(struct platform_device?*pdev)
{
switch?(pdev->id)?{
case?0:
s3c_gpio_cfgpin(S5PC1XX_GPB(0),?S5PC1XX_GPB0_SPI_MISO0);
s3c_gpio_cfgpin(S5PC1XX_GPB(1),?S5PC1XX_GPB1_SPI_CLK0);
s3c_gpio_cfgpin(S5PC1XX_GPB(2),?S5PC1XX_GPB2_SPI_MOSI0);
s3c_gpio_setpull(S5PC1XX_GPB(0),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPB(1),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPB(2),?S3C_GPIO_PULL_UP);
break;
case?1:
s3c_gpio_cfgpin(S5PC1XX_GPB(4),?S5PC1XX_GPB4_SPI_MISO1);
s3c_gpio_cfgpin(S5PC1XX_GPB(5),?S5PC1XX_GPB5_SPI_CLK1);
s3c_gpio_cfgpin(S5PC1XX_GPB(6),?S5PC1XX_GPB6_SPI_MOSI1);
s3c_gpio_setpull(S5PC1XX_GPB(4),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPB(5),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPB(6),?S3C_GPIO_PULL_UP);
break;
case?2:
s3c_gpio_cfgpin(S5PC1XX_GPG3(0),?S5PC1XX_GPG3_0_SPI_CLK2);
s3c_gpio_cfgpin(S5PC1XX_GPG3(2),?S5PC1XX_GPG3_2_SPI_MISO2);
s3c_gpio_cfgpin(S5PC1XX_GPG3(3),?S5PC1XX_GPG3_3_SPI_MOSI2);
s3c_gpio_setpull(S5PC1XX_GPG3(0),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPG3(2),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPG3(3),?S3C_GPIO_PULL_UP);
break;
default:
dev_err(&pdev->dev,?"Invalid SPI Controller number!");
return?-EINVAL;
}
3、Platform_driver
再看platform_driver,參看drivers/spi/spi_s3c64xx.c文件
點擊(此處)折疊或打開
static struct platform_driver s3c64xx_spi_driver?=?{
.driver?=?{
.name?=?"s3c64xx-spi",?//名稱,和platform_device對應
.owner?=?THIS_MODULE,
},
.remove?=?s3c64xx_spi_remove,
.suspend?=?s3c64xx_spi_suspend,
.resume?=?s3c64xx_spi_resume,
};
platform_driver_probe(&s3c64xx_spi_driver,?s3c64xx_spi_probe);//注冊s3c64xx_spi_driver
和平臺中注冊的platform_device匹配后,調用s3c64xx_spi_probe。然后根據傳入的platform_device參數,構建一個用于描述SPI控制器的結構體spi_master,并注冊。spi_register_master(master)。后續注冊的spi_device需要選定自己的spi_master,并利用spi_master提供的傳輸功能傳輸spi數據。
和I2C類似,SPI也有一個描述控制器的對象叫spi_master。其主要成員是主機控制器的序號(系統中可能存在多個SPI主機控制器)、片選數量、SPI模式和時鐘設置用到的函數、數據傳輸用到的函數等。
點擊(此處)折疊或打開
struct spi_master?{
struct device dev;
s16 bus_num;?//表示是SPI主機控制器的編號。由平臺代碼決定
u16 num_chipselect;?//控制器支持的片選數量,即能支持多少個spi設備
int?(*setup)(struct spi_device?*spi);?//針對設備設置SPI的工作時鐘及數據傳輸模式等。在spi_add_device函數中調用。
int?(*transfer)(struct spi_device?*spi,
struct spi_message?*mesg);?//實現數據的雙向傳輸,可能會睡眠
void?(*cleanup)(struct spi_device?*spi);?//注銷時調用
};
4、Spi bus
Spi總線對應的總線類型為spi_bus_type,在內核的drivers/spi/spi.c中定義
點擊(此處)折疊或打開
struct bus_type spi_bus_type?=?{
.name?=?"spi",
.dev_attrs?=?spi_dev_attrs,
.match?=?spi_match_device,
.uevent?=?spi_uevent,
.suspend?=?spi_suspend,
.resume?=?spi_resume,
};
對應的匹配規則是(高版本中的匹配規則會稍有變化,引入了id_table,可以匹配多個spi設備名稱):
點擊(此處)折疊或打開
static?int?spi_match_device(struct device?*dev,?struct device_driver?*drv)
{
const?struct spi_device?*spi?=?to_spi_device(dev);
return strcmp(spi->modalias,?drv->name)?==?0;
}
5、spi_device
下面該講到spi_device的構建與注冊了。spi_device對應的含義是掛接在spi總線上的一個設備,所以描述它的時候應該明確它自身的設備特性、傳輸要求、及掛接在哪個總線上。
點擊(此處)折疊或打開
static struct spi_board_info s3c_spi_devs[]?__initdata?=?{
{
.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],
},
};
static struct s3c64xx_spi_csinfo smdk_spi0_csi[]?=?{
[0]?=?{
.set_level?=?smdk_m25p10_cs_set_level,
.fb_delay?=?0x3,
},
};
static void smdk_m25p10_cs_set_level(int?high)?//spi控制器會用這個方法設置cs
{
u32 val;
val?=?readl(S5PC1XX_GPBDAT);
if?(high)
val?|=?(1<<3);
else
val?&=?~(1<<3);
writel(val,?S5PC1XX_GPBDAT);
}
spi_register_board_info(s3c_spi_devs,?ARRAY_SIZE(s3c_spi_devs));//注冊spi_board_info。這個代碼會把spi_board_info注冊要鏈表board_list上。
事實上上文提到的spi_master的注冊會在spi_register_board_info之后,spi_master注冊的過程中會調用scan_boardinfo掃描board_list,找到掛接在它上面的spi設備,然后創建并注冊spi_device。
點擊(此處)折疊或打開
static void scan_boardinfo(struct spi_master?*master)
{
struct boardinfo?*bi;
mutex_lock(&board_lock);
list_for_each_entry(bi,?&board_list,?list)?{
struct spi_board_info?*chip?=?bi->board_info;
unsigned n;
for?(n?=?bi->n_board_info;?n?>?0;?n--,?chip++)?{
if?(chip->bus_num?!=?master->bus_num)
continue;
/*?NOTE:?this relies?on?spi_new_device?to
*?issue diagnostics when given bogus inputs
*/
(void)?spi_new_device(master,?chip);?//創建并注冊了spi_device
}
}
mutex_unlock(&board_lock);
}
6、spi_driver
本文先以linux內核中的/driver/mtd/devices/m25p80.c驅動為參考。
點擊(此處)折疊或打開
static struct spi_driver m25p80_driver?=?{?//spi_driver的構建
.driver?=?{
.name?=?"m25p80",
.bus?=?&spi_bus_type,
.owner?=?THIS_MODULE,
},
.probe?=?m25p_probe,
.remove?=?__devexit_p(m25p_remove),
*/
};
spi_register_driver(&m25p80_driver);//spi driver的注冊
在有匹配的spi device時,會調用m25p_probe
static?int?__devinit m25p_probe(struct spi_device?*spi)
{
……
}
根據傳入的spi_device參數,可以找到對應的spi_master。接下來就可以利用spi子系統為我們完成數據交互了??梢詤⒖磎25p80_read函數。要完成傳輸,先理解下面幾個結構的含義:(這兩個結構的定義及詳細注釋參見include/linux/spi/spi.h)
spi_message:描述一次完整的傳輸,即cs信號從高->底->高的傳輸
??????? spi_transfer:多個spi_transfer夠成一個spi_message
??????????????? 舉例說明:m25p80的讀過程如下圖
可以分解為兩個spi_ transfer一個是寫命令,另一個是讀數據。具體實現參見m25p80.c中的m25p80_read函數。下面內容摘取之此函數。
點擊(此處)折疊或打開
struct spi_transfer t[2];?//定義了兩個spi_transfer
struct spi_message m;?//定義了兩個spi_message
spi_message_init(&m);?//初始化其transfers鏈表
t[0].tx_buf?=?flash->command;
t[0].len?=?CMD_SIZE?+?FAST_READ_DUMMY_BYTE;?//定義第一個transfer的寫指針和長度
spi_message_add_tail(&t[0],?&m);?//添加到spi_message
t[1].rx_buf?=?buf;
t[1].len?=?len;?//定義第二個transfer的讀指針和長度
spi_message_add_tail(&t[1],?&m);?//添加到spi_message
flash->command[0]?=?OPCODE_READ;
flash->command[1]?=?from?>>?16;
flash->command[2]?=?from?>>?8;
flash->command[3]?=?from;?//初始化前面寫buf的內容
spi_sync(flash->spi,?&m);?//調用spi_master發送spi_message
//?spi_sync為同步方式發送,還可以用spi_async異步方式,那樣的話,需要設置回調完成函數。
另外你也可以選擇一些封裝好的更容易使用的函數,這些函數可以在include/linux/spi/spi.h文件中找到,如:
extern?int?spi_write_then_read(struct spi_device?*spi,
const?u8?*txbuf,?unsigned n_tx,
u8?*rxbuf,?unsigned n_rx);
這篇博文就到這了,下篇給出一個針對m25p10完整的驅動程序。?
Linux下spi驅動開發之m25p10驅動測試
目標:在華清遠見的FS_S5PC100平臺上編寫一個簡單的spi驅動模塊,在probe階段實現對m25p10的ID號探測、flash擦除、flash狀態讀取、flash寫入、flash讀取等操作。代碼已經經過測試,運行于2.6.35內核。理解下面代碼需要參照m25p10的芯片手冊。其實下面的代碼和處理器沒有太大關系,這也是spi子系統的分層特點。
點擊(此處)折疊或打開
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#define FLASH_PAGE_SIZE 256
/*?Flash Operating Commands?*/
#define CMD_READ_ID 0x9f
#define CMD_WRITE_ENABLE 0x06?
#define CMD_BULK_ERASE 0xc7
#define CMD_READ_BYTES 0x03
#define CMD_PAGE_PROGRAM 0x02
#define CMD_RDSR 0x05?
/*?Status Register bits.?*/
#define SR_WIP 1?/*?Write?in?progress?*/
#define SR_WEL 2?/*?Write enable latch?*/
/*?ID Numbers?*/
#define MANUFACTURER_ID 0x20
#define DEVICE_ID 0x1120
/*?Define max times?to?check status register before we give up.?*/
#define MAX_READY_WAIT_COUNT 100000
#define CMD_SZ 4
struct m25p10a?{
struct spi_device?*spi;
struct mutex lock;
char erase_opcode;
char cmd[?CMD_SZ?];
};
/*
*?Internal Helper functions?
*/
/*
*?Read the status register,?returning its value?in?the?location
*?Return the status register value.
*?Returns negative?if?error?occurred.
*/
static?int?read_sr(struct m25p10a?*flash)
{
ssize_t retval;
u8 code?=?CMD_RDSR;
u8 val;
retval?=?spi_write_then_read(flash->spi,?&code,?1,?&val,?1);
if?(retval?0)?{
dev_err(&flash->spi->dev,?"error %d reading SR\n",?(int)?retval);
return retval;
}
return val;
}
/*
*?Service routine?to?read status register?until?ready,?or?timeout occurs.
*?Returns non-zero?if?error.
*/
static?int?wait_till_ready(struct m25p10a?*flash)
{
int?count;
int?sr;
/*?one chip guarantees max 5 msec wait here after page writes,
*?but potentially three seconds?(!)?after page?erase.
*/
for?(count?=?0;?count?
if?((sr?=?read_sr(flash))?0)
break;
else?if?(!(sr?&?SR_WIP))
return 0;
/*?REVISIT sometimes sleeping would be best?*/
}
printk(?"in (%s): count = %d\n",?count?);
return 1;
}
/*
*?Set?write enable latch with Write Enable command.
*?Returns negative?if?error?occurred.
*/
static inline?int?write_enable(?struct m25p10a?*flash?)
{
flash->cmd[0]?=?CMD_WRITE_ENABLE;
return spi_write(?flash->spi,?flash->cmd,?1?);
}
/*
*?Erase?the whole flash memory
*
*?Returns 0?if?successful,?non-zero otherwise.
*/
static?int?erase_chip(?struct m25p10a?*flash?)
{
/*?Wait?until?finished previous write command.?*/
if?(wait_till_ready(flash))
return?-1;
/*?Send write enable,?then?erase?commands.?*/
write_enable(?flash?);
flash->cmd[0]?=?CMD_BULK_ERASE;
return spi_write(?flash->spi,?flash->cmd,?1?);
}
/*
*?Read an address range from the flash chip.?The address range
*?may be any size provided it?is?within the physical boundaries.
*/
static?int?m25p10a_read(?struct m25p10a?*flash,?loff_t from,?size_t?len,?char?*buf?)
{
int?r_count?=?0,?i;
flash->cmd[0]?=?CMD_READ_BYTES;
flash->cmd[1]?=?from?>>?16;
flash->cmd[2]?=?from?>>?8;
flash->cmd[3]?=?from;
#if?1
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?
printk(?"0x%02x\n",?buf[?i?]?);
}
mutex_unlock(?&flash->lock?);
#endif
return 0;
}
/*
*?Write an address range?to?the flash chip.?Data must be written?in
*?FLASH_PAGE_SIZE chunks.?The address range may be any size provided
*?it?is?within the physical boundaries.
*/
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;
#if?1
if?(wait_till_ready(flash))?{?//讀狀態,等待ready
mutex_unlock(?&flash->lock?);
return?-1;
}
#endif
write_enable(?flash?);?//寫使能?
spi_message_init(?&msg?);
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_message_add_tail(?&st[0],?&msg?);
st[?1?].tx_buf?=?buf;
st[?1?].len?=?len;
spi_message_add_tail(?&st[1],?&msg?);
mutex_lock(?&flash->lock?);
/*?get?offset address inside a page?*/
page_offset?=?to?%?FLASH_PAGE_SIZE;
/*?do?all the bytes fit onto one page??*/
if(?page_offset?+?len?<=?FLASH_PAGE_SIZE?)?{?//?yes
st[?1?].len?=?len;
printk("%d, cmd = %d\n",?st[?1?].len,?*(char?*)st[0].tx_buf);
//while(1)
{
spi_sync(?flash->spi,?&msg?);
}
w_count?=?msg.actual_length?-?CMD_SZ;
}
else?{?//?no
}
printk(?"in (%s): write %d bytes to flash in total\n",?__func__,?w_count?);
mutex_unlock(?&flash->lock?);
return 0;
}
static?int?check_id(?struct m25p10a?*flash?)
{
char buf[10]?=?{0};
flash->cmd[0]?=?CMD_READ_ID;
spi_write_then_read(?flash->spi,?flash->cmd,?1,?buf,?3?);
printk(?"Manufacture ID: 0x%x\n",?buf[0]?);
printk(?"Device ID: 0x%x\n",?buf[1]?|?buf[2]?<8?);
return buf[2]?<16?|?buf[1]?<8?|?buf[0];
}
static?int?m25p10a_probe(struct spi_device?*spi)
{
int?ret?=?0;
struct m25p10a?*flash;
char buf[?256?];
printk(?"%s was called\n",?__func__?);
flash?=?kzalloc(?sizeof(struct m25p10a),?GFP_KERNEL?);
if(?!flash?)?{
return?-ENOMEM;
}
flash->spi?=?spi;
mutex_init(?&flash->lock?);
/*?save flash as driver's?private?data?*/
spi_set_drvdata(?spi,?flash?);
check_id(?flash?);?//讀取ID
#if?1
ret?=?erase_chip(?flash?);?//擦除?
if(?ret?0?)?{
printk(?"erase the entirely chip failed\n"?);
}
printk(?"erase the whole chip done\n"?);
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個數?
#endif
return 0;
}
static?int?m25p10a_remove(struct spi_device?*spi)
{
return 0;
}
static struct spi_driver m25p10a_driver?=?{
.probe?=?m25p10a_probe,
.remove?=?m25p10a_remove,
.driver?=?{
.name?=?"m25p10a",
},
};
static?int?__init m25p10a_init(void)
{
return spi_register_driver(&m25p10a_driver);
}
static void __exit m25p10a_exit(void)
{
spi_unregister_driver(&m25p10a_driver);
}
module_init(m25p10a_init);
module_exit(m25p10a_exit);
MODULE_DESCRIPTION("m25p10a driver for FS_S5PC100");
MODULE_LICENSE("GPL");
例。
二、SPI總線協議簡介
介紹驅動開發前,需要先熟悉下SPI通訊協議中的幾個關鍵的地方,后面在編寫驅動時,需要考慮相關因素。
SPI總線由MISO(串行數據輸入)、MOSI(串行數據輸出)、SCK(串行移位時鐘)、CS(使能信號)4個信號線組成。如FS_S5PC100上的M25P10芯片接線為:
上圖中M25P10的D腳為它的數據輸入腳,Q為數據輸出腳,C為時鐘腳。
SPI常用四種數據傳輸模式,主要差別在于:輸出串行同步時鐘極性(CPOL)和相位(CPHA)可以進行配置。如果CPOL= 0,串行同步時鐘的空閑狀態為低電平;如果CPOL= 1,串行同步時鐘的空閑狀態為高電平。如果CPHA= 0,在串行同步時鐘的前沿(上升或下降)數據被采樣;如果CPHA = 1,在串行同步時鐘的后沿(上升或下降)數據被采樣。
這四種模式中究竟選擇哪種模式取決于設備。如M25P10的手冊中明確它可以支持的兩種模式為:CPOL=0 CPHA=0? 和 CPOL=1 CPHA=1
三、linux下SPI驅動開發
首先明確SPI驅動層次,如下圖:???????
我們以上面的這個圖為思路
1、 Platform bus
Platform bus對應的結構是platform_bus_type,這個內核開始就定義好的。我們不需要定義。
2、Platform_device
SPI控制器對應platform_device的定義方式,同樣以S5PC100中的SPI控制器為例,參看arch/arm/plat-s5pc1xx/dev-spi.c文件
點擊(此處)折疊或打開
struct platform_device s3c_device_spi0?=?{
.name?=?"s3c64xx-spi",?//名稱,要和Platform_driver匹配
.id?=?0,?//第0個控制器,S5PC100中有3個控制器
.num_resources?=?ARRAY_SIZE(s5pc1xx_spi0_resource),?//占用資源的種類
.resource?=?s5pc1xx_spi0_resource,?//指向資源結構數組的指針
.dev?=?{
.dma_mask?=?&spi_dmamask,?//dma尋址范圍?
.coherent_dma_mask?=?DMA_BIT_MASK(32),?//可以通過關閉cache等措施保證一致性的dma尋址范圍
.platform_data?=?&s5pc1xx_spi0_pdata,?//特殊的平臺數據,參看后文
},
};
static struct s3c64xx_spi_cntrlr_info s5pc1xx_spi0_pdata?=?{
.cfg_gpio?=?s5pc1xx_spi_cfg_gpio,?//用于控制器管腳的IO配置
.fifo_lvl_mask?=?0x7f,
.rx_lvl_offset?=?13,
};
static?int?s5pc1xx_spi_cfg_gpio(struct platform_device?*pdev)
{
switch?(pdev->id)?{
case?0:
s3c_gpio_cfgpin(S5PC1XX_GPB(0),?S5PC1XX_GPB0_SPI_MISO0);
s3c_gpio_cfgpin(S5PC1XX_GPB(1),?S5PC1XX_GPB1_SPI_CLK0);
s3c_gpio_cfgpin(S5PC1XX_GPB(2),?S5PC1XX_GPB2_SPI_MOSI0);
s3c_gpio_setpull(S5PC1XX_GPB(0),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPB(1),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPB(2),?S3C_GPIO_PULL_UP);
break;
case?1:
s3c_gpio_cfgpin(S5PC1XX_GPB(4),?S5PC1XX_GPB4_SPI_MISO1);
s3c_gpio_cfgpin(S5PC1XX_GPB(5),?S5PC1XX_GPB5_SPI_CLK1);
s3c_gpio_cfgpin(S5PC1XX_GPB(6),?S5PC1XX_GPB6_SPI_MOSI1);
s3c_gpio_setpull(S5PC1XX_GPB(4),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPB(5),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPB(6),?S3C_GPIO_PULL_UP);
break;
case?2:
s3c_gpio_cfgpin(S5PC1XX_GPG3(0),?S5PC1XX_GPG3_0_SPI_CLK2);
s3c_gpio_cfgpin(S5PC1XX_GPG3(2),?S5PC1XX_GPG3_2_SPI_MISO2);
s3c_gpio_cfgpin(S5PC1XX_GPG3(3),?S5PC1XX_GPG3_3_SPI_MOSI2);
s3c_gpio_setpull(S5PC1XX_GPG3(0),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPG3(2),?S3C_GPIO_PULL_UP);
s3c_gpio_setpull(S5PC1XX_GPG3(3),?S3C_GPIO_PULL_UP);
break;
default:
dev_err(&pdev->dev,?"Invalid SPI Controller number!");
return?-EINVAL;
}
3、Platform_driver
再看platform_driver,參看drivers/spi/spi_s3c64xx.c文件
點擊(此處)折疊或打開
static struct platform_driver s3c64xx_spi_driver?=?{
.driver?=?{
.name?=?"s3c64xx-spi",?//名稱,和platform_device對應
.owner?=?THIS_MODULE,
},
.remove?=?s3c64xx_spi_remove,
.suspend?=?s3c64xx_spi_suspend,
.resume?=?s3c64xx_spi_resume,
};
platform_driver_probe(&s3c64xx_spi_driver,?s3c64xx_spi_probe);//注冊s3c64xx_spi_driver
和平臺中注冊的platform_device匹配后,調用s3c64xx_spi_probe。然后根據傳入的platform_device參數,構建一個用于描述SPI控制器的結構體spi_master,并注冊。spi_register_master(master)。后續注冊的spi_device需要選定自己的spi_master,并利用spi_master提供的傳輸功能傳輸spi數據。
和I2C類似,SPI也有一個描述控制器的對象叫spi_master。其主要成員是主機控制器的序號(系統中可能存在多個SPI主機控制器)、片選數量、SPI模式和時鐘設置用到的函數、數據傳輸用到的函數等。
點擊(此處)折疊或打開
struct spi_master?{
struct device dev;
s16 bus_num;?//表示是SPI主機控制器的編號。由平臺代碼決定
u16 num_chipselect;?//控制器支持的片選數量,即能支持多少個spi設備
int?(*setup)(struct spi_device?*spi);?//針對設備設置SPI的工作時鐘及數據傳輸模式等。在spi_add_device函數中調用。
int?(*transfer)(struct spi_device?*spi,
struct spi_message?*mesg);?//實現數據的雙向傳輸,可能會睡眠
void?(*cleanup)(struct spi_device?*spi);?//注銷時調用
};
4、Spi bus
Spi總線對應的總線類型為spi_bus_type,在內核的drivers/spi/spi.c中定義
點擊(此處)折疊或打開
struct bus_type spi_bus_type?=?{
.name?=?"spi",
.dev_attrs?=?spi_dev_attrs,
.match?=?spi_match_device,
.uevent?=?spi_uevent,
.suspend?=?spi_suspend,
.resume?=?spi_resume,
};
對應的匹配規則是(高版本中的匹配規則會稍有變化,引入了id_table,可以匹配多個spi設備名稱):
點擊(此處)折疊或打開
static?int?spi_match_device(struct device?*dev,?struct device_driver?*drv)
{
const?struct spi_device?*spi?=?to_spi_device(dev);
return strcmp(spi->modalias,?drv->name)?==?0;
}
5、spi_device
下面該講到spi_device的構建與注冊了。spi_device對應的含義是掛接在spi總線上的一個設備,所以描述它的時候應該明確它自身的設備特性、傳輸要求、及掛接在哪個總線上。
點擊(此處)折疊或打開
static struct spi_board_info s3c_spi_devs[]?__initdata?=?{
{
.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],
},
};
static struct s3c64xx_spi_csinfo smdk_spi0_csi[]?=?{
[0]?=?{
.set_level?=?smdk_m25p10_cs_set_level,
.fb_delay?=?0x3,
},
};
static void smdk_m25p10_cs_set_level(int?high)?//spi控制器會用這個方法設置cs
{
u32 val;
val?=?readl(S5PC1XX_GPBDAT);
if?(high)
val?|=?(1<<3);
else
val?&=?~(1<<3);
writel(val,?S5PC1XX_GPBDAT);
}
spi_register_board_info(s3c_spi_devs,?ARRAY_SIZE(s3c_spi_devs));//注冊spi_board_info。這個代碼會把spi_board_info注冊要鏈表board_list上。
事實上上文提到的spi_master的注冊會在spi_register_board_info之后,spi_master注冊的過程中會調用scan_boardinfo掃描board_list,找到掛接在它上面的spi設備,然后創建并注冊spi_device。
點擊(此處)折疊或打開
static void scan_boardinfo(struct spi_master?*master)
{
struct boardinfo?*bi;
mutex_lock(&board_lock);
list_for_each_entry(bi,?&board_list,?list)?{
struct spi_board_info?*chip?=?bi->board_info;
unsigned n;
for?(n?=?bi->n_board_info;?n?>?0;?n--,?chip++)?{
if?(chip->bus_num?!=?master->bus_num)
continue;
/*?NOTE:?this relies?on?spi_new_device?to
*?issue diagnostics when given bogus inputs
*/
(void)?spi_new_device(master,?chip);?//創建并注冊了spi_device
}
}
mutex_unlock(&board_lock);
}
6、spi_driver
本文先以linux內核中的/driver/mtd/devices/m25p80.c驅動為參考。
點擊(此處)折疊或打開
static struct spi_driver m25p80_driver?=?{?//spi_driver的構建
.driver?=?{
.name?=?"m25p80",
.bus?=?&spi_bus_type,
.owner?=?THIS_MODULE,
},
.probe?=?m25p_probe,
.remove?=?__devexit_p(m25p_remove),
*/
};
spi_register_driver(&m25p80_driver);//spi driver的注冊
在有匹配的spi device時,會調用m25p_probe
static?int?__devinit m25p_probe(struct spi_device?*spi)
{
……
}
根據傳入的spi_device參數,可以找到對應的spi_master。接下來就可以利用spi子系統為我們完成數據交互了。可以參看m25p80_read函數。要完成傳輸,先理解下面幾個結構的含義:(這兩個結構的定義及詳細注釋參見include/linux/spi/spi.h)
spi_message:描述一次完整的傳輸,即cs信號從高->底->高的傳輸
??????? spi_transfer:多個spi_transfer夠成一個spi_message
??????????????? 舉例說明:m25p80的讀過程如下圖
可以分解為兩個spi_ transfer一個是寫命令,另一個是讀數據。具體實現參見m25p80.c中的m25p80_read函數。下面內容摘取之此函數。
點擊(此處)折疊或打開
struct spi_transfer t[2];?//定義了兩個spi_transfer
struct spi_message m;?//定義了兩個spi_message
spi_message_init(&m);?//初始化其transfers鏈表
t[0].tx_buf?=?flash->command;
t[0].len?=?CMD_SIZE?+?FAST_READ_DUMMY_BYTE;?//定義第一個transfer的寫指針和長度
spi_message_add_tail(&t[0],?&m);?//添加到spi_message
t[1].rx_buf?=?buf;
t[1].len?=?len;?//定義第二個transfer的讀指針和長度
spi_message_add_tail(&t[1],?&m);?//添加到spi_message
flash->command[0]?=?OPCODE_READ;
flash->command[1]?=?from?>>?16;
flash->command[2]?=?from?>>?8;
flash->command[3]?=?from;?//初始化前面寫buf的內容
spi_sync(flash->spi,?&m);?//調用spi_master發送spi_message
//?spi_sync為同步方式發送,還可以用spi_async異步方式,那樣的話,需要設置回調完成函數。
另外你也可以選擇一些封裝好的更容易使用的函數,這些函數可以在include/linux/spi/spi.h文件中找到,如:
extern?int?spi_write_then_read(struct spi_device?*spi,
const?u8?*txbuf,?unsigned n_tx,
u8?*rxbuf,?unsigned n_rx);
這篇博文就到這了,下篇給出一個針對m25p10完整的驅動程序。?
Linux下spi驅動開發之m25p10驅動測試
目標:在華清遠見的FS_S5PC100平臺上編寫一個簡單的spi驅動模塊,在probe階段實現對m25p10的ID號探測、flash擦除、flash狀態讀取、flash寫入、flash讀取等操作。代碼已經經過測試,運行于2.6.35內核。理解下面代碼需要參照m25p10的芯片手冊。其實下面的代碼和處理器沒有太大關系,這也是spi子系統的分層特點。
點擊(此處)折疊或打開
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#define FLASH_PAGE_SIZE 256
/*?Flash Operating Commands?*/
#define CMD_READ_ID 0x9f
#define CMD_WRITE_ENABLE 0x06?
#define CMD_BULK_ERASE 0xc7
#define CMD_READ_BYTES 0x03
#define CMD_PAGE_PROGRAM 0x02
#define CMD_RDSR 0x05?
/*?Status Register bits.?*/
#define SR_WIP 1?/*?Write?in?progress?*/
#define SR_WEL 2?/*?Write enable latch?*/
/*?ID Numbers?*/
#define MANUFACTURER_ID 0x20
#define DEVICE_ID 0x1120
/*?Define max times?to?check status register before we give up.?*/
#define MAX_READY_WAIT_COUNT 100000
#define CMD_SZ 4
struct m25p10a?{
struct spi_device?*spi;
struct mutex lock;
char erase_opcode;
char cmd[?CMD_SZ?];
};
/*
*?Internal Helper functions?
*/
/*
*?Read the status register,?returning its value?in?the?location
*?Return the status register value.
*?Returns negative?if?error?occurred.
*/
static?int?read_sr(struct m25p10a?*flash)
{
ssize_t retval;
u8 code?=?CMD_RDSR;
u8 val;
retval?=?spi_write_then_read(flash->spi,?&code,?1,?&val,?1);
if?(retval?0)?{
dev_err(&flash->spi->dev,?"error %d reading SR\n",?(int)?retval);
return retval;
}
return val;
}
/*
*?Service routine?to?read status register?until?ready,?or?timeout occurs.
*?Returns non-zero?if?error.
*/
static?int?wait_till_ready(struct m25p10a?*flash)
{
int?count;
int?sr;
/*?one chip guarantees max 5 msec wait here after page writes,
*?but potentially three seconds?(!)?after page?erase.
*/
for?(count?=?0;?count?
if?((sr?=?read_sr(flash))?0)
break;
else?if?(!(sr?&?SR_WIP))
return 0;
/*?REVISIT sometimes sleeping would be best?*/
}
printk(?"in (%s): count = %d\n",?count?);
return 1;
}
/*
*?Set?write enable latch with Write Enable command.
*?Returns negative?if?error?occurred.
*/
static inline?int?write_enable(?struct m25p10a?*flash?)
{
flash->cmd[0]?=?CMD_WRITE_ENABLE;
return spi_write(?flash->spi,?flash->cmd,?1?);
}
/*
*?Erase?the whole flash memory
*
*?Returns 0?if?successful,?non-zero otherwise.
*/
static?int?erase_chip(?struct m25p10a?*flash?)
{
/*?Wait?until?finished previous write command.?*/
if?(wait_till_ready(flash))
return?-1;
/*?Send write enable,?then?erase?commands.?*/
write_enable(?flash?);
flash->cmd[0]?=?CMD_BULK_ERASE;
return spi_write(?flash->spi,?flash->cmd,?1?);
}
/*
*?Read an address range from the flash chip.?The address range
*?may be any size provided it?is?within the physical boundaries.
*/
static?int?m25p10a_read(?struct m25p10a?*flash,?loff_t from,?size_t?len,?char?*buf?)
{
int?r_count?=?0,?i;
flash->cmd[0]?=?CMD_READ_BYTES;
flash->cmd[1]?=?from?>>?16;
flash->cmd[2]?=?from?>>?8;
flash->cmd[3]?=?from;
#if?1
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?
printk(?"0x%02x\n",?buf[?i?]?);
}
mutex_unlock(?&flash->lock?);
#endif
return 0;
}
/*
*?Write an address range?to?the flash chip.?Data must be written?in
*?FLASH_PAGE_SIZE chunks.?The address range may be any size provided
*?it?is?within the physical boundaries.
*/
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;
#if?1
if?(wait_till_ready(flash))?{?//讀狀態,等待ready
mutex_unlock(?&flash->lock?);
return?-1;
}
#endif
write_enable(?flash?);?//寫使能?
spi_message_init(?&msg?);
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_message_add_tail(?&st[0],?&msg?);
st[?1?].tx_buf?=?buf;
st[?1?].len?=?len;
spi_message_add_tail(?&st[1],?&msg?);
mutex_lock(?&flash->lock?);
/*?get?offset address inside a page?*/
page_offset?=?to?%?FLASH_PAGE_SIZE;
/*?do?all the bytes fit onto one page??*/
if(?page_offset?+?len?<=?FLASH_PAGE_SIZE?)?{?//?yes
st[?1?].len?=?len;
printk("%d, cmd = %d\n",?st[?1?].len,?*(char?*)st[0].tx_buf);
//while(1)
{
spi_sync(?flash->spi,?&msg?);
}
w_count?=?msg.actual_length?-?CMD_SZ;
}
else?{?//?no
}
printk(?"in (%s): write %d bytes to flash in total\n",?__func__,?w_count?);
mutex_unlock(?&flash->lock?);
return 0;
}
static?int?check_id(?struct m25p10a?*flash?)
{
char buf[10]?=?{0};
flash->cmd[0]?=?CMD_READ_ID;
spi_write_then_read(?flash->spi,?flash->cmd,?1,?buf,?3?);
printk(?"Manufacture ID: 0x%x\n",?buf[0]?);
printk(?"Device ID: 0x%x\n",?buf[1]?|?buf[2]?<8?);
return buf[2]?<16?|?buf[1]?<8?|?buf[0];
}
static?int?m25p10a_probe(struct spi_device?*spi)
{
int?ret?=?0;
struct m25p10a?*flash;
char buf[?256?];
printk(?"%s was called\n",?__func__?);
flash?=?kzalloc(?sizeof(struct m25p10a),?GFP_KERNEL?);
if(?!flash?)?{
return?-ENOMEM;
}
flash->spi?=?spi;
mutex_init(?&flash->lock?);
/*?save flash as driver's?private?data?*/
spi_set_drvdata(?spi,?flash?);
check_id(?flash?);?//讀取ID
#if?1
ret?=?erase_chip(?flash?);?//擦除?
if(?ret?0?)?{
printk(?"erase the entirely chip failed\n"?);
}
printk(?"erase the whole chip done\n"?);
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個數?
#endif
return 0;
}
static?int?m25p10a_remove(struct spi_device?*spi)
{
return 0;
}
static struct spi_driver m25p10a_driver?=?{
.probe?=?m25p10a_probe,
.remove?=?m25p10a_remove,
.driver?=?{
.name?=?"m25p10a",
},
};
static?int?__init m25p10a_init(void)
{
return spi_register_driver(&m25p10a_driver);
}
static void __exit m25p10a_exit(void)
{
spi_unregister_driver(&m25p10a_driver);
}
module_init(m25p10a_init);
module_exit(m25p10a_exit);
MODULE_DESCRIPTION("m25p10a driver for FS_S5PC100");
MODULE_LICENSE("GPL");
?
評論
查看更多