嵌入式開(kāi)發(fā)之linux spi驅動(dòng)分析 關(guān)于spi的學(xué)習,最好的方法當然是看Linux的源代碼,主要是driver/spi/spi.c(h),spidev.c(h)。spi dev的示例可以看看at25.c,spi總線(xiàn)的示例可以看omap_uwire或者spi_s3c24xx.c和spi_s3c24xx_gpio.c。在看這些代碼之前,需要對linux的設備模型有一定的了解。 另外,網(wǎng)上有兩篇教程不錯,《linux spi子系統驅動(dòng)分析》以及《linux spi子系統 驅動(dòng)分析 續》,如果嵌友有興趣可以百度一下。 下面是我們整理的關(guān)于SPI的一些經(jīng)驗心得。 SPI子系統 spi子系統中,spi設備用struct spi_dev描述,它的驅動(dòng)程序用struct spi_driver描述。spi總線(xiàn)設備用struct spi_master描述。另外,還有兩個(gè)重要的全局變量: 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 struct class spi_master_class = { .name = "spi_master", .owner = THIS_MODULE, .dev_release = spi_master_release, }; spi_bus_type對應sys中的spi bus總線(xiàn),Linux設備模型對這個(gè)結構體有詳細介紹。 所有spi_master對應的spi總線(xiàn)都屬于spi_master_class,也就是說(shuō)是一個(gè)虛擬設備,它的父設備可能是物理設備,比如platform_device等等,s3c2410就是這種情況。 SPI設備 SPI設備的驅動(dòng)程序通過(guò)spi_register_driver注冊進(jìn)SPI子系統,驅動(dòng)類(lèi)型為struct spi_driver。典型例子如at25.c。 static struct spi_driver at25_driver = { .driver = { .name = "at25", .owner = THIS_MODULE, }, .probe = at25_probe, .remove = __devexit_p(at25_remove), }; 因為spi總線(xiàn)不支持SPI設備的自動(dòng)檢測,所以一般在spi的probe函數中不會(huì )檢測設備是否存在,而是做一些spi設備的初始化工作。 spi驅動(dòng)中可以調用下列函數進(jìn)行spi的傳輸操作: static inline int spi_write(struct spi_device *spi, const u8 *buf, size_t len); static inline int spi_read(struct spi_device *spi, u8 *buf, size_t len); extern int spi_write_then_read(struct spi_device *spi, const u8 *txbuf, unsigned n_tx, u8 *rxbuf, unsigned n_rx); static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd); static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd); 由于spi設備不能被spi總線(xiàn)動(dòng)態(tài)掃描,所以spi子系統使用了另一種方法,就是通過(guò)spi_register_board_info函數將spi設備靜態(tài)得登記到系統中。 int __init spi_register_board_info(struct spi_board_info const *info, unsigned n); struct spi_board_info { char modalias[32]; // 設備名 const void *platform_data; // 私有數據,會(huì )被設置到spi_device.dev.platform_data void *controller_data; // 私有數據,會(huì )被設置到spi_device.controller_data int irq; // 中斷號 u32 max_speed_hz; // 最大速率 u16 bus_num; // 用于關(guān)聯(lián)spi_master u16 chip_select; // 與片選有關(guān) u8 mode; // spi_device.mode }; 在具體平臺的文件中,可以定義struct spi_board_info的結構體,然后通過(guò)spi_register_board_info函數保存這些結構體,最后在scan_boardinfo函數中根據這些保存的結構體創(chuàng )建spi設備(spi_new_device)。 spi_new_device用于登記spi設備,這里面又分兩步,首先是spi_alloc_device,然后是spi_add_device。 struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip) spi_dev* pdev = spi_alloc(master); proxy->chip_select = chip->chip_select; proxy->max_speed_hz = chip->max_speed_hz; proxy->mode = chip->mode; proxy->irq = chip->irq; strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias)); proxy->dev.platform_data = (void *) chip->platform_data; proxy->controller_data = chip->controller_data; proxy->controller_state = NULL; spi_add_device(proxy); struct spi_device *spi_alloc_device(struct spi_master *master) struct device * dev = master->dev.parent; struct spi_dev * spi = kzalloc(sizeof *spi, GFP_KERNEL); spi->master = master; spi->dev.parent = dev; spi->dev.bus = &spi_bus_type; spi->dev.release = spidev_release; device_initialize(&spi->dev); 這里spi_dev的父設備被指定為master的父設備,而master是spi總線(xiàn)設備,擁有class,是一個(gè)虛擬設備。也就是說(shuō),spi設備和與之對應的總線(xiàn)設備擁有同一個(gè)父設備,這個(gè)父設備一般來(lái)說(shuō)是一個(gè)物理設備。 int spi_add_device(struct spi_device *spi) snprintf(spi->dev.bus_id, sizeof spi->dev.bus_id, "%s.%u", spi->master->dev.bus_id, spi->chip_select); status = spi->master->setup(spi); status = device_add(&spi->dev); spi總線(xiàn) struct spi_master { struct device dev; s16 bus_num; // 總線(xiàn)號,如果板子上有多個(gè)spi總線(xiàn),靠這個(gè)域區分;另外,spi_dev中也有bus_num,spi_dev通過(guò)這個(gè)域找到它所屬的總線(xiàn)。 u16 num_chipselect; // 片選號,如果一個(gè)spi總線(xiàn)有多個(gè)設備, /* setup mode and clock, etc (spi driver may call many times) */ int (*setup)(struct spi_device *spi); int (*transfer)(struct spi_device *spi, struct spi_message *mesg); /* called on release() to free memory provided by spi_master */ void (*cleanup)(struct spi_device *spi); }; 登記spi總線(xiàn) struct spi_master *spi_alloc_master(struct device *dev, unsigned size); int spi_register_master(struct spi_master *master); scan_boardinfo(master); spi_register_master中會(huì )調用scan_boardinfo。scan_boardinfo中,會(huì )掃描前面保存的boardinfo,看新注冊的master中的bus_num是否與boardinfo中bus_num匹配,如果匹配,那就調用spi_new_device創(chuàng )建spi設備,并登記到spi子系統中。 setup函數 setup函數會(huì )做一些初始化工作。比如,根據spi設備的速率,設備paster的位傳輸定時(shí)器;設置spi傳輸類(lèi)型;等等。 spi_add_device函數中,會(huì )先調用setup函數,然后再調用device_add。這是因為device_add中會(huì )調用到driver的probe函數,而probe函數中可能會(huì )對spi設備做IO操作。所以spi子系統就先調用setup為可能的IO操作做好準備。 但是,在代碼中,setup函數似乎也就只在這一個(gè)地方被調用。具體傳輸過(guò)程中切換spi設備時(shí)也要做配置工作,但這里的配置工作就由具體傳輸的實(shí)現代碼決定了,可以看看spi_bitbang.c中的函數bitbang_work。 cleanup函數 cleanup函數會(huì )在spidev_release函數中被調用,spidev_release被登記為spi dev的release函數。 transfer函數 transfer函數用于spi的IO傳輸。但是,transfer函數一般不會(huì )執行真正的傳輸操作,而是把要傳輸的內容放到一個(gè)隊列里,然后調用一種類(lèi)似底半部的機制進(jìn)行真正的傳輸。這是因為,spi總線(xiàn)一般會(huì )連多個(gè)spi設備,而spi設備間的訪(fǎng)問(wèn)可能會(huì )并發(fā)。如果直接在transfer函數中實(shí)現傳輸,那么會(huì )產(chǎn)生競態(tài),spi設備互相間會(huì )干擾。 所以,真正的spi傳輸與具體的spi控制器的實(shí)現有關(guān),spi的框架代碼中沒(méi)有涉及。像spi設備的片選、根據具體設備進(jìn)行時(shí)鐘調整等等都在實(shí)現傳輸的代碼中被調用。 SPI的傳輸命令都是通過(guò)結構體spi_message定義。設備程序調用transfer函數將spi_message交給spi總線(xiàn)驅動(dòng),總線(xiàn)驅動(dòng)再將message傳到底半部排隊,實(shí)現串行化傳輸。 struct spi_message { struct list_head transfers; struct spi_device *spi; unsigned is_dma_mapped:1; void (*complete)(void *context); void *context; unsigned actual_length; int status; struct list_head queue; void *state; }; spi_message中,有一個(gè)transfers隊列,spi_transfer結構體通過(guò)這個(gè)隊列掛到spi_message中。一個(gè)spi_message代表一次傳輸會(huì )話(huà),spi_transfer代表一次單獨的IO操作。比如,有些spi設備需要先讀后寫(xiě),那么這個(gè)讀寫(xiě)過(guò)程就是一次spi會(huì )話(huà),里面包括兩個(gè)transfer,一個(gè)定義寫(xiě)操作的參數,另一個(gè)定義讀操作的參數。 spidev.c 如果不想為自己的SPI設備寫(xiě)驅動(dòng),那么可以用Linux自帶的spidev.c提供的驅動(dòng)程序。要使用spidev.c的驅動(dòng),只要在登記設備時(shí),把設備名設置成spidev就可以。spidev.c會(huì )在device目錄下自動(dòng)為每一個(gè)匹配的SPI設備創(chuàng )建設備節點(diǎn),節點(diǎn)名”spi%d”。之后,用戶(hù)程序可以通過(guò)字符型設備的通用接口控制SPI設備。 需要注意的是,spidev創(chuàng )建的設備在設備模型中屬于虛擬設備,它的class是spidev_class。它的父設備是在boardinfo中定義的spi設備。 想學(xué)習想進(jìn)步的你和我聯(lián)系預約就可以免費聽(tīng)課了。 以下課程可免費試聽(tīng)C語(yǔ)言、電子、PCB、STM32、Linux、FPGA、JAVA、安卓等。 宋工企鵝號:3524-6590-88 Tel/WX:173--1795--1908 ![]() |