一、概述
首先我們對SPI做個認識,SPI是串行外設接口(Serial Peripheral Interface)的縮寫。SPI,是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只佔用四根線。這4根線分別是它們是數據輸入SDI(MISO)、數據輸出SDO(MOSI)、時鐘SCLK、片選CS。
SPI常用四種數據傳輸模式,主要是由輸出串行同步時鐘極性(CPOL)和相位(CPHA)進行配置。如果CPOL= 0,串行同步時鐘的空閒狀態爲低電平;如果CPOL= 1,串行同步時鐘的空閒狀態爲高電平。如果CPHA= 0,在串行同步時鐘的前沿(上升或下降)數據被採樣;如果CPHA = 1,在串行同步時鐘的後沿(上升或下降)數據被採樣。下面是針對4中模式的時序圖:
注:我們需要設置的模式是根據spi芯片手冊上支持的模式來設置的。
二、linux下SPI驅動開發
在帶有linux系統的平臺中和純單片機中對於spi驅動的開發是有着本質的區別的:在linux系統中spi驅動是作爲一個子系統模塊的,更通俗的講就是一套通信協議(spi驅動也叫協議驅動),也就是說無論你選擇的平臺是什麼,只要是用的linux內核,那麼都使用的是該協議,內核提供給用戶的接口函數都是一致的,驅動的實現也是一致的,是用戶不需要更改的,而用戶需要關心的是: 一、你要打開的spi設備節點是哪個(spidevx.x),這個spi設備節點是在spi子系統中創建的。
二、你根據平臺來選擇配置多少個spi控制器以及添加多少個spi設備(這些信息都是在平臺依賴的板級信息裏面配置)。
每個平臺中從上層spi控制器到下面的spi設備都是有一定的層次結構的,可以先通過下面的圖概要的理解一下,後面還會從代碼的來詳細的分析一下其實現原理。
我們以上面的這個圖爲思路
1、 Platform bus
Platform bus對應的結構是platform_bus_type,這個內核開始就定義好的。對於itop4412這個平臺,platform bus的定義和註冊可以參看arch/arm/driver/base/platform.c文件
點擊(此處)摺疊或打開
-
struct bus_type platform_bus_type = {
-
.name = "platform", //定義platform總線
-
.dev_attrs= platform_dev_attrs,
-
.match= platform_match,
-
.uevent= platform_uevent,
-
.pm= &platform_dev_pm_ops,
- };
-
-
int __init platform_bus_init(void)
- {
- int error;
- early_platform_cleanup();
-
-
error = device_register(&platform_bus);
- if (error)
-
return error;
-
-
error = bus_register(&platform_bus_type); //註冊platform總線
-
if (error)
-
device_unregister(&platform_bus);
-
return error;
- }
-
2、Platform_device
SPI控制器對應platform_device的定義方式,同樣以itop4412中的SPI控制器爲例,參看arch/arm/mach-exynos/dev-spi.c文件
點擊(此處)摺疊或打開
-
struct platform_device exynos_device_spi0 = {
-
.name = "s3c64xx-spi",
//名稱,要和Platform_driver匹配
-
.id = 0, //第0個控制器,itop4412中有3個控制器
-
.num_resources = ARRAY_SIZE(exynos_spi0_resource), //佔用資源的種類
-
.resource = exynos_spi0_resource, //指向資源結構數組的指針
-
.dev = {
-
.dma_mask = &spi_dmamask, //dma尋址範圍
-
.coherent_dma_mask = DMA_BIT_MASK(32),
//可以通過關閉cache等措施保證一致性的dma尋址範圍
-
.platform_data = &exynos_spi0_pdata,
//特殊的平臺數據,參看後文
-
},
-
};
-
- static struct s3c64xx_spi_info exynos_spi0_pdata = {
- .cfg_gpio = s5pc1xx_spi_cfg_gpio, //用於控制器管腳的IO配置
-
.fifo_lvl_mask = 0x7f,
- .rx_lvl_offset = 15,
- .high_speed = 1,
-
.clk_from_cmu = true,
-
.tx_st_done = 25,
}; -
- static int exynos_spi_cfg_gpio(struct platform_device *pdev){
- int gpio;
-
-
switch (pdev->id) {
-
case 0:
-
s3c_gpio_cfgpin(EXYNOS4_GPB(0), S3C_GPIO_SFN(2));
-
s3c_gpio_cfgpin(EXYNOS4_GPB(2), S3C_GPIO_SFN(2));
-
s3c_gpio_cfgpin(EXYNOS4_GPB(3), S3C_GPIO_SFN(2));
-
s3c_gpio_setpull(EXYNOS4_GPB(0), S3C_GPIO_PULL_UP);
-
s3c_gpio_setpull(EXYNOS4_GPB(2), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPB(3), S3C_GPIO_PULL_UP);
-
- for (gpio = EXYNOS4_GPB(0); gpio < EXYNOS4_GPB(4); gpio++)
-
s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
- break;
- case 1:
- s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_SFN(2));
- s3c_gpio_cfgpin(EXYNOS4_GPB(6), S3C_GPIO_SFN(2));
- s3c_gpio_cfgpin(EXYNOS4_GPB(7), S3C_GPIO_SFN(2));
- s3c_gpio_setpull(EXYNOS4_GPB(4), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPB(6), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPB(7), S3C_GPIO_PULL_UP);
-
- for (gpio = EXYNOS4_GPB(4); gpio < EXYNOS4_GPB(8); gpio++)
- s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
- break;
- case 2:
- s3c_gpio_cfgpin(EXYNOS4_GPC1(1), S3C_GPIO_SFN(5));
- s3c_gpio_cfgpin(EXYNOS4_GPC1(3), S3C_GPIO_SFN(5));
- s3c_gpio_cfgpin(EXYNOS4_GPC1(4), S3C_GPIO_SFN(5));
- s3c_gpio_setpull(EXYNOS4_GPC1(1), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPC1(3), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPC1(4), S3C_GPIO_PULL_UP);
-
- for (gpio = EXYNOS4_GPC1(1); gpio < EXYNOS4_GPC1(5); gpio++)
- s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
- 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,
- };
點擊(此處)摺疊或打開
-
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總線上的一個設備,所以描述它的時候應該明確它自身的設備特性、傳輸要求、及掛接在哪個總線上。參看arch/arm/mach-exynos/mach-itop4412.c文件。
點擊(此處)摺疊或打開
-
static struct spi_board_info spi2_board_info[] __initdata = {
-
{
-
.modalias = "spidev", //sp設備的名字,這個名字將會和spidev.c中的驅動名字匹配。
- .platform_data = NULL;
- .max_speed_hz = 10*1000*1000, //最大的spi時鐘頻率
- .bus_num = 2, //設備連接在spi控制器0上
- .chip_select = 0, //片選線號
- .mode = SPI_MODE_0, //CPOL=0, CPHA=0 此處選擇具體數據傳輸模式
- .controller_data = &spi2_csi[0],
-
},
- };
-
- static struct s3c64xx_spi_csinfo spi2_csi[] = {
- [0] = {
- .line = EXYNOS4_GPC1(2),
-
.set_level = gpio_set_value,
-
.fb_delay = 0x2,
-
},
- };
-
-
spi_register_board_info(spi2_board_info, ARRAY_SIZE(spi2_board_info));
- //註冊spi_board_info。這個代碼會把spi_board_info註冊要鏈表board_list上。
事實上上文提到的spi_master的註冊會在spi_register_board_info之後,spi_master註冊的過程中會調用spi_match_master_to_boardinfo(有些平臺是scan_boardinfo函數)來對比傳入的總線號和板級配置的總線號是否匹配,然後創建並註冊spi_device。
點擊(此處)摺疊或打開
- static void spi_match_master_to_boardinfo(struct spi_master *master,struct spi_board_info *bi)
- {
- struct spi_device *dev;
-
- if (master->bus_num != bi->bus_num) //spi控制器的總線號和板級配置的總線是否匹配
- return;
-
- dev = spi_new_device(master, bi); //創建spi新設備
- if (!dev)
- dev_err(master->dev.parent, "can't create new device for %s\n",bi->modalias);
- }
6、spi_driver
本文先以linux內核中的/driver/spi/spidev.c驅動爲參考。
點擊(此處)摺疊或打開
-
static struct spi_driver spidev_spi_driver = { //spi_driver的構建
-
.driver = {
- .name = "spidev"
-
.owner = THIS_MODULE,
-
},
-
.probe = spidev_probe,
- .remove = __devexit_p(spidev_remove),
-
};
-
-
spi_register_driver(&spidev_spi_driver);//spi
driver的註冊
-
-
在有匹配的spi device時,會調用m25p_probe
-
-
static int __devinit spidev_probe(struct spi_device *spi)
-
{
-
……
- }
根據傳入的spi_device參數,可以找到對應的spi_master。接下來就可以利用spi子系統爲我們完成數據交互了。