SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)_0
? 由于工作的原因,對SPI的理解最為深刻,也和SPI最有感情了,之前工作都是基于OSEK操作系統上進行實現,也在US/OS3上實現過SPI驅動的實現和測試,但是都是基于基本的寄存器操作,沒有一個系統軟件架構的思想,感覺linux SPI驅動很強大,水很深,廢話少說,SPI總線上有兩類設備:一類是主機端,通常作為SOC系統的一個子模塊出現,比如很多嵌入式MPU中都常常包含SPI模塊。一類是從機被控端,例如一些SPI接口的Flash、傳感器等等。主機端是SPI總線的控制者,通過使用SPI協議主動發起SPI總線上的會話。而受控端則被動接受SPI主控端的指令,并作出響應的響應,本文讀者前提是必須熟練掌握linux Platform總線驅動模型 和基本字符設備驅動的實現。
SPI總線由MISO(串行數據輸入)、MOSI(串行數據輸出)、SCK(串行移位時鐘)、CS(使能信號)4個信號線組成。SPI常用四種數據傳輸模式,主要差別在于:輸出串行同步時鐘極性(CPOL)和相位(CPHA)可以進行配置。如果CPOL= 0,串行同步時鐘的空閑狀態為低電平;如果CPOL= 1,串行同步時鐘的空閑狀態為高電平。如果CPHA= 0,在串行同步時鐘的前沿(上升或下降)數據被采樣;如果CPHA = 1,在串行同步時鐘的后沿(上升或下降)數據被采樣。同I2C子系統類似,SPI子系統分為3個部分,分別是SPI核心層、主控制器驅動和協議驅動,通俗一點就是SPI核心層主要完成1.定義并注冊SPI總線spi_bus_type和控制器類spi_master_class;2.提供spi_driver,spi_device和spi_master的分配,創建,注冊和注銷;3.實現SPI通信方法的上層代碼。主控制器驅動對應I2C的適配器驅動,SPI用spi_master來描述相應的控制器,通常用spi_bitbang來控制實際的數據傳輸,功能非常類似與i2c_algorithm。協議驅動類比I2C設備驅動,可以理解成客戶端驅動,下面詳細分析和實現基于linux3.14.78內核版本 SPI在S3C2440/6410上驅動移植.
Step1,實現SPI控制器的設備接口(相對應有兩種方式,一種實現方式是S3c2440純粹通過配置來實現,另一種實現方式針對m25p10,獨立編寫單獨的spi驅動模塊)
首先針對S3C2440,SPI控制器的設備接口在drivers/spi/spidev.c中實現,下圖為相應的軟件流程圖,spidev.c中的spidev_init()作為模塊初始化函數,在系統啟動或者模塊加載是被調用,主要完成以下三種操作:
1、調用register_chrdev()為SPI控制器注冊主設備號為153,次設備號范圍為0~255,文件操作集合為spidev_fops的字符設備;
2、調用class_create()注冊一個名為“spidev”的設備類;
3、調用spi_register_driver()向系統添加SPI控制器的設備驅動spidev_spi;
spi_register_driver()將驅動spidev_spi添加到SPI核心層注冊的spi_bus_type總線上,注意,該總線屬于spi總線,Spi總線對應的總線類型為spi_bus_type,在內核的drivers/spi/spi.c中定義,對應的匹配規則是(高版本中的匹配規則會稍有變化,引入了id_table,可以匹配多個spi設備名稱)詳見以下代碼。由于spi總線的匹配方式是檢查spi_device.modalias與spi_driver.driver.name是否相同,而spidev_spi的driver.name是“spidev”,so 只有spi_bus_type總線上的modalias為“spidev”的設備,才可以與spidev_spi驅動匹配。由于S3C2440擁有兩個SPI控制器,對應的平臺信息是s3c_device_spi0和s3c_device_spi1,可以將其添加到機器配置文件的My2440_devices數組中,驅動中probe方法中用到的總線編號、片選總數等信息來源于平臺數據,這些數據需要用戶添加,添加方式有兩種,平臺設備的平臺數據類型是S3C2410_SPI_info,在板級初始化文件中可以為兩個SPI平臺設備分別定義和添加這些平臺數據,此外為了支持SPI控制器設備接口功能,還需要在機器配置文件為SPI控制器設備添加并注冊spi_board_info對象,詳見下面代碼。
設備與驅動匹配后,通過調用spidev_spi的probe方法來綁定工作見代碼。SPI控制器設備操作集合是spidev_fops,主要包括spidev_write,spidev_read,spidev_ioctl,spidev_open和spidev_release。下面的代碼注釋中詳盡分析了spidev_read()函數的實現過程,spidev_write()的分析和實現方法與read類似,spidev_open()會遍歷device_list鏈表,找出其中設備號與打開設備文件的inode.i_rdev相等的spidev_data對象,并將該對象記錄在文件的私有數據filp->private_data中,以供read和write函數獲取,此外要實現全雙工的傳輸需要借助控制器設備的ioctl方法,對應實現函數是spidev_ioctl(),全雙工傳輸也是通過spidev_sync()完成的,與半雙工傳輸唯一不同的是在消息的傳輸段中,tx_buffer和rx_buffer同時被設置,它還提供獲取和修改時鐘模式、子寬、最大時鐘頻率屬性的命令。
1 static struct s3c2410_spi_info s3c2410_spi0_platdata = { 2 .pin_cs = S3C2410_GPG2, 3 .num_cs = 2, 4 .bus_num = 0, 5 .gpio_setup = s3c24xx_spi_gpiocfg_bus0_gpe11_12_13, 6 }; 7 static struct spi_board_info s3c2410_spi0_board[] = 8 { 9 [0] = { 10 .modalias = "spidev", 11 .bus_num = 0, 12 .chip_select = 0, 13 .max_speed_hz = 500 * 1000, 14 }, 15 [1] = { 16 .modalias = "at25", 17 .platform_data = &at25_eeprom_data, 18 .bus_num = 0, 19 .chip_select = 1, 20 .max_speed_hz = 500 * 1000, 21 } 22 }; 23 static struct s3c2410_spi_info s3c2410_spi1_platdata = { 24 .pin_cs = S3C2410_GPG3, 25 .num_cs = 1, 26 .bus_num = 1, 27 .gpio_setup = s3c24xx_spi_gpiocfg_bus1_gpg5_6_7, 28 }; 29 static struct spi_board_info s3c2410_spi1_board[] = 30 { 31 [0] = { 32 .modalias = "spidev", 33 .bus_num = 1, 34 .chip_select = 0, 35 .max_speed_hz = 500 * 1000, 36 } 37 }; 38 static void __init My2440_machine_init(void) 39 { 40 s3c24xx_fb_set_platdata(&My2440_fb_info); 41 s3c_i2c0_set_platdata(NULL); 42 i2c_register_board_info(0, i2c_devices, ARRAY_SIZE(i2c_devices)); 43 s3c_device_spi0.dev.platform_data= &s3c2410_spi0_platdata; 44 spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board)); 45 s3c_device_spi1.dev.platform_data= &s3c2410_spi1_platdata; 46 spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board)); 47 s3c_device_nand.dev.platform_data = &My2440_nand_info; 48 s3c_device_sdi.dev.platform_data = &My2440_mmc_cfg; 49 platform_add_devices(My2440_devices, ARRAY_SIZE(My2440_devices)); 50 } ? 1 struct bus_type spi_bus_type = { 2 .name = "spi", 3 .dev_attrs = spi_dev_attrs, 4 .match = spi_match_device, 5 .uevent = spi_uevent, 6 .suspend = spi_suspend, 7 .resume = spi_resume, 8 }; 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; } static int spidev_probe(struct spi_device *spi) {struct spidev_data *spidev;int status;unsigned long minor;/* Allocate driver data, 分配并初始化一個spidev_data對象,用來描述SPI控制器設備驅動操作的控制器設備,包含它的設備號,傳輸緩存和內嵌spi_device*/spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);if (!spidev)return -ENOMEM;/* Initialize the driver data ,將與spidev_spi驅動匹配的spi_device賦值給spidev_data.spi,通過find_first_zero_bit()從位圖minors中分配到次設備號,與主設備號SPIDEV_MAJOR構成設備號并賦值給spidev_data.devt,然后調用device_create()在/dev下創建名稱格式為“spidev%d%d”的字符設備文件,第一個%d表示控制器編號,
即spi_device.master.bus_num,第二個對應片選號,即spi_device.chip_select*/spidev->spi = spi;spin_lock_init(&spidev->spi_lock);mutex_init(&spidev->buf_lock);INIT_LIST_HEAD(&spidev->device_entry);/* If we can allocate a minor number, hook up this device.* Reusing minors is fine so long as udev or mdev is working.*/mutex_lock(&device_list_lock);minor = find_first_zero_bit(minors, N_SPI_MINORS);if (minor < N_SPI_MINORS) {struct device *dev;spidev->devt = MKDEV(SPIDEV_MAJOR, minor);dev = device_create(spidev_class, &spi->dev, spidev->devt,spidev, "spidev%d.%d",spi->master->bus_num, spi->chip_select);status = PTR_ERR_OR_ZERO(dev);} else {dev_dbg(&spi->dev, "no minor number available!\n");status = -ENODEV;}if (status == 0) {set_bit(minor, minors);
/*將spidev_data對象添加到一個名為device_list的設備列表中,最后將spidev_data對象設置為對應spi_device的驅動數據,將其值賦值給dpi_device.dev.driver_data*/list_add(&spidev->device_entry, &device_list);}mutex_unlock(&device_list_lock);if (status == 0)spi_set_drvdata(spi, spidev);elsekfree(spidev);return status; } /*spidev_read(),spidev_write()實現的讀寫操作都是半雙工的傳輸,要實現全雙工傳輸需要調用spidev_ioctl()操作,在spidev_read()中,通過調用spidev_sync_read()
同步讀取指定長度數據到spidev_data_buffer中,然后調用copy_to_user()將讀取的數據復制到用戶空間緩沖*/ static ssize_t spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {struct spidev_data *spidev;ssize_t status = 0;/* chipselect only toggles at start or end of operation */if (count > bufsiz)return -EMSGSIZE;spidev = filp->private_data;mutex_lock(&spidev->buf_lock);status = spidev_sync_read(spidev, count);if (status > 0) {unsigned long missing;missing = copy_to_user(buf, spidev->buffer, status);if (missing == status)status = -EFAULT;elsestatus = status - missing;}mutex_unlock(&spidev->buf_lock);return status; } /*spidev_sync_read()中定義一個spi_transfer和spi_message對象,使用spidev_data.buffer初始化spi_transfer.rx_buf,
然后將spi_transfer對象添加到spi_message中,調用spidev_sync()傳輸信息;*/
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); } /*spidev_sync()實現同步傳輸的機制:首先為傳入的消息對象的complete和context成員賦值,context被設置為完成接口done,complete被設置成spidev_complete(),然后
調用spi_async()啟動異步傳輸,spi_async()返回后,通過調用wait_for_completion()阻塞調用進程,直到異步傳輸完成,spi_message的complete方法,即spidev_complete()
被調用來喚醒被阻塞的進程,異步傳輸接口函數spi_async()屬于SPI核心層,在include/linux/spi/spi.h中定義并實現,它只調用了spi_device所屬控制器的transfer方法*/
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;elsestatus = 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; }
針對FS_S5PC100上的M25P10芯片,
//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);elseval &= ~(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);} //spi_driver.c 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,根據傳入的spi_device參數,可以找到對應的spi_master。接下來就可以利用spi子系統為我們完成數據交互了 static int __devinit m25p_probe(struct spi_device *spi)Step2,實現SPI platform 總線匹配
? S3C2440和S5PC100的SPI控制器驅動的實現同樣采用了Platform驅動模型,針對S3C2440在drivers/spi/spi_s3c24xx.c中,模塊初始化函數s3c24xx_spi_init()完成了platform驅動的注冊,名稱是“s3c2410-spi”,與該驅動匹配的平臺設備在arch/arm/plat-samsung/devs.c中定義,并且與驅動同名,針對S5PC100,詳見下面代碼,匹配完成后調用相應的probe:
//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;} //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); //注銷時調用}; static int s3c24xx_spi_probe(struct platform_device *pdev) {struct s3c2410_spi_info *pdata;struct s3c24xx_spi *hw;struct spi_master *master;struct resource *res;int err = 0;master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));//分配SPI控制器結構及驅動私有數據 if (master == NULL) {dev_err(&pdev->dev, "No memory for spi_master\n");return -ENOMEM;}hw = spi_master_get_devdata(master);memset(hw, 0, sizeof(struct s3c24xx_spi));hw->master = master;hw->pdata = pdata = dev_get_platdata(&pdev->dev);hw->dev = &pdev->dev;if (pdata == NULL) {dev_err(&pdev->dev, "No platform data supplied\n");err = -ENOENT;goto err_no_pdata;}platform_set_drvdata(pdev, hw);//將SPI控制器私有數據作為平臺設備驅動數據,便于通過相應接口獲得init_completion(&hw->done);//初始化完成接口/* initialise fiq handler */s3c24xx_spi_initfiq(hw);/* setup the master state. *//* the spi->mode bits understood by this driver: */master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;master->num_chipselect = hw->pdata->num_cs;//設置控制器片選總數和總線編號master->bus_num = pdata->bus_num;/* setup the state for the bitbang driver 初始化bitbang驅動的相關成員*/hw->bitbang.master = hw->master;hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;hw->bitbang.chipselect = s3c24xx_spi_chipsel;hw->bitbang.txrx_bufs = s3c24xx_spi_txrx;hw->master->setup = s3c24xx_spi_setup;hw->master->cleanup = s3c24xx_spi_cleanup;dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);/* find and map our resources 這是平臺驅動中最常規的工作,找到并映射資源*/res = platform_get_resource(pdev, IORESOURCE_MEM, 0);hw->regs = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(hw->regs)) {err = PTR_ERR(hw->regs);goto err_no_pdata;}hw->irq = platform_get_irq(pdev, 0);if (hw->irq < 0) {dev_err(&pdev->dev, "No IRQ specified\n");err = -ENOENT;goto err_no_pdata;}err = devm_request_irq(&pdev->dev, hw->irq, s3c24xx_spi_irq, 0,pdev->name, hw);if (err) {dev_err(&pdev->dev, "Cannot claim IRQ\n");goto err_no_pdata;}hw->clk = devm_clk_get(&pdev->dev, "spi");//獲取SPI的時鐘資源if (IS_ERR(hw->clk)) {dev_err(&pdev->dev, "No clock for device\n");err = PTR_ERR(hw->clk);goto err_no_pdata;}/* setup any gpio we can 設置片選方法并配置片選引腳 */if (!pdata->set_cs) {if (pdata->pin_cs < 0) {dev_err(&pdev->dev, "No chipselect pin\n");err = -EINVAL;goto err_register;}err = devm_gpio_request(&pdev->dev, pdata->pin_cs,dev_name(&pdev->dev));if (err) {dev_err(&pdev->dev, "Failed to get gpio for cs\n");goto err_register;}hw->set_cs = s3c24xx_spi_gpiocs;gpio_direction_output(pdata->pin_cs, 1);} elsehw->set_cs = pdata->set_cs;s3c24xx_spi_initialsetup(hw);//使能SPI時鐘,初始化2440 SPI控制器寄存器及片選引腳等/* register our spi controller 注冊SPI控制器,進而完成對控制器對象的分配初始化和注冊,注冊控制器會掃描board_list鏈表,從中取出spi_board_info創建spi_device設備,因此在執行probe方法時,只要board_list鏈表
上有控制器對應的spi_board_info,就能創建出控制器的設備對象,并與控制器設備驅動匹配,進而創建出用于訪問該控制器的設備文件
*/err = spi_bitbang_start(&hw->bitbang);if (err) {dev_err(&pdev->dev, "Failed to register SPI master\n");goto err_register;}return 0;err_register:clk_disable(hw->clk);err_no_pdata:spi_master_put(hw->master);return err; }
?Step3,分析實現SPI總線通信方法
SPI控制器通常由spi_bitbang來完成實際數據的數據傳輸,spi_bitbang的定義如下:
struct spi_bitbang {spinlock_t lock; /*操作工作隊列時使用的自旋鎖*/u8 busy;u8 use_dma;u8 flags; /* extra spi->mode support */struct spi_master *master;/* setup_transfer() changes clock and/or wordsize to match settings* for this transfer; zeroes restore defaults from spi_device.為特定的傳輸設置時鐘、字寬等屬性的方法*/int (*setup_transfer)(struct spi_device *spi,struct spi_transfer *t);void (*chipselect)(struct spi_device *spi, int is_on); #define BITBANG_CS_ACTIVE 1 /* normally nCS, active low */ #define BITBANG_CS_INACTIVE 0/* txrx_bufs()為實際的傳輸方法 may handle dma mapping for transfers that don't* already have one (transfer.{tx,rx}_dma is zero), or use PIO*/int (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);/* txrx_word[SPI_MODE_*]()按字傳輸的方法 just looks like a shift register */u32 (*txrx_word[4])(struct spi_device *spi,unsigned nsecs,u32 word, u8 bits); };?
兩者的SPI總線通信控制方法不同,在上面spi_bitbang結構可以看出,消息傳輸啟動利用的是spi_bibang_txrx_bufs(),在2440 SPI驅動中是通過s3c24xx_spi_txrx()來實現,他將傳輸段中請求數據的緩沖區和長度賦值給s3c24xx_spi對象的相關成員,然后主動發送一個字節數據,啟動傳輸過程,之后使用進程阻塞在完成接口s3c24xx_spi.done上,其他數據的傳輸將在中斷處理函數中完成,中斷檢查到發送完成后,通知完成接口,喚醒之前阻塞在其上的進程(這里特別說明下6410SPI通信方法,與2440不同,采用DMA傳輸,相應的實現函數為s3c64xx_spi_transfer(),利用工作隊列進行延遲調度),m25p80SPI總線通信控制詳盡代碼如下,說明一點,spi_message的消息可以同步傳輸也可以異步傳輸,正常產品中基本上都是異步傳輸,傳輸結束時有spi_message.complete()方法完成,下面的例子以同步實現簡單實現:
?參考文獻?1.《深入Linux內核架構》 2.《Linux設備驅動開發詳解》?
總結
以上是生活随笔為你收集整理的SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)_0的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 老李分享:六度分隔理论 1
- 下一篇: 如何做开发部门的绩效考核