OMAP3630下的Linux SPI總線驅動分析(2)
4 OMAP3630 SPI 控制器驅動
在Linux內核中,SPI 控制器驅動位於drivers/spi目錄下,OMAP3630 的spi控制器驅動程序爲omap2_mcspi.c。SPI 控制器驅動的註冊採用Platform設備和驅動機制。
4.1 SPI 控制器的Platform device
Android2.1中Platform device的註冊代碼位於內核的arch/arm/mach-omap2/devices.c中。4.1.1 Platform device的定義
在文件arch/arm/mach-omap2/devices.c中,Platform device定義如下:
- <span style="font-size:18px;">static struct omap2_mcspi_platform_config omap2_mcspi1_config = {
- .num_cs = 4,
- };
- static struct resource omap2_mcspi1_resources[] = {
- {
- .start = OMAP2_MCSPI1_BASE,
- .end = OMAP2_MCSPI1_BASE + 0xff,
- .flags = IORESOURCE_MEM,
- },
- };
- static struct platform_device omap2_mcspi1 = {
- .name = "omap2_mcspi",
- .id = 1,
- .num_resources = ARRAY_SIZE(omap2_mcspi1_resources),
- .resource = omap2_mcspi1_resources,
- .dev = {
- .platform_data = &omap2_mcspi1_config,
- },
- };
- static struct omap2_mcspi_platform_config omap2_mcspi2_config = {
- .num_cs = 2,
- };
- static struct resource omap2_mcspi2_resources[] = {
- {
- .start = OMAP2_MCSPI2_BASE,
- .end = OMAP2_MCSPI2_BASE + 0xff,
- .flags = IORESOURCE_MEM,
- },
- };
- static struct platform_device omap2_mcspi2 = {
- .name = "omap2_mcspi",
- .id = 2,
- .num_resources = ARRAY_SIZE(omap2_mcspi2_resources),
- .resource = omap2_mcspi2_resources,
- .dev = {
- .platform_data = &omap2_mcspi2_config,
- },
- };
- #if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3)
- static struct omap2_mcspi_platform_config omap2_mcspi3_config = {
- .num_cs = 2,
- };
- static struct resource omap2_mcspi3_resources[] = {
- {
- .start = OMAP2_MCSPI3_BASE,
- .end = OMAP2_MCSPI3_BASE + 0xff,
- .flags = IORESOURCE_MEM,
- },
- };
- static struct platform_device omap2_mcspi3 = {
- .name = "omap2_mcspi",
- .id = 3,
- .num_resources = ARRAY_SIZE(omap2_mcspi3_resources),
- .resource = omap2_mcspi3_resources,
- .dev = {
- .platform_data = &omap2_mcspi3_config,
- },
- };
- #endif
- #ifdef CONFIG_ARCH_OMAP3
- static struct omap2_mcspi_platform_config omap2_mcspi4_config = {
- .num_cs = 1,
- };
- static struct resource omap2_mcspi4_resources[] = {
- {
- .start = OMAP2_MCSPI4_BASE,
- .end = OMAP2_MCSPI4_BASE + 0xff,
- .flags = IORESOURCE_MEM,
- },
- };
- static struct platform_device omap2_mcspi4 = {
- .name = "omap2_mcspi",
- .id = 4,
- .num_resources = ARRAY_SIZE(omap2_mcspi4_resources),
- .resource = omap2_mcspi4_resources,
- .dev = {
- .platform_data = &omap2_mcspi4_config,
- },
- };
- #endif</span>
4.1.2 Platform device的註冊
Platform device的註冊是由內核啓動時完成的。在文件arch/arm/mach-omap2/devices.c中,具體的代碼如下:- <span style="font-size:18px;">static int __init omap2_init_devices(void)
- {
- /* please keep these calls, and their implementations above,
- * in alphabetical order so they're easier to sort through.
- */
- omap_hsmmc_reset();
- omap_init_camera();
- omap_init_mbox();
- omap_init_mcspi();
- omap_init_sti();
- omap_init_sha1_md5();
- return 0;
- }
- arch_initcall(omap2_init_devices);</span><span style="font-family:Arial, Verdana, sans-serif;"><span style="white-space: normal;">
- </span></span>
內核啓動時會調用函數omap2_init_devices(),函數omap2_init_devices ()再調到spi的初始化函數omap_init_mcspi(),omap_init_mcspi()代碼如下:
- <span style="font-size:18px;">static void omap_init_mcspi(void)
- {
- platform_device_register(&omap2_mcspi1);
- platform_device_register(&omap2_mcspi2);
- #if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3)
- if (cpu_is_omap2430() || cpu_is_omap343x())
- platform_device_register(&omap2_mcspi3);
- #endif
- #ifdef CONFIG_ARCH_OMAP3
- if (cpu_is_omap343x())
- platform_device_register(&omap2_mcspi4);
- #endif
- }</span>
函數omap2_init_devices()通過調用platform_device_register()將Platform device註冊到Platform總線上,完成註冊後,寄存器的基地址等信息會在設備樹中描述了,此後只需利用platform_get_resource等標準接口自動獲取即可,實現了驅動和資源的分離。
4.2 SPI 控制器的Platform driver
Andrord 2.1中Platform driver的註冊代碼位於內核的drivers/spi/omap2_mcspi.c中,該驅動的註冊目的是初始化OMAP3630的SPI 控制器,提供SPI總線數據傳輸的具體實現,並且向spi core層註冊spi 控制器。4.2.1 Platform driver的定義
在文件drivers/spi/omap2_mcspi.c中,platform driver定義如下:- <span style="font-size:18px;">static struct platform_driver omap2_mcspi_driver = {
- .driver = {
- .name = "omap2_mcspi",
- .owner = THIS_MODULE,
- },
- .remove = __exit_p(omap2_mcspi_remove),
- };</span>
4.2.2 Platform driver的註冊
在文件drivers/spi/omap2_mcspi.c中,platform driver註冊如下:- <span style="font-size:18px;">static int __init omap2_mcspi_init(void)
- {
- omap2_mcspi_wq = create_singlethread_workqueue(
- omap2_mcspi_driver.driver.name);
- if (omap2_mcspi_wq == NULL)
- return -1;
- return platform_driver_probe(&omap2_mcspi_driver, omap2_mcspi_probe);
- }
- subsys_initcall(omap2_mcspi_init); </span>
Workqueue創建成功後纔會用函數platform_driver_probe()來註冊Platform driver,該函數是帶有probe函數的平臺驅動註冊函數,用於非hot-plug設備。
註冊時,會掃描platform bus上的所有設備,由於匹配因子name爲" omap2_mcspi ",因此與4.1.1節中註冊的Platform device匹配成功,於是函數omap2_mcspi_probe()將被調用,控制器device和driver將被綁定起來。
在文件drivers/spi/omap2_mcspi.c中會涉及到一個數據結構omap2_mcspi,這個結構定義了專門針對omap3630的SPI控制器結構,代碼如下:
- <span style="font-size:18px;">struct omap2_mcspi {
- struct work_struct work;
- /* lock protects queue and registers */
- spinlock_t lock;
- struct list_head msg_queue;
- struct spi_master *master;
- struct clk *ick;
- struct clk *fck;
- /* Virtual base address of the controller */
- void __iomem *base;
- unsigned long phys;
- /* SPI1 has 4 channels, while SPI2 has 2 */
- struct omap2_mcspi_dma *dma_channels;
- };</span>
master對應通用的spi控制器結構。
ick和fck分別對應接口時鐘和功能時鐘。
base對應SPI控制器寄存器的虛擬地址。
phys對應SPI控制器寄存器的物理地址。
dma_channels對應SPI控制器的DMA通道。
函數omap2_mcspi_probe()的執行流程如下圖:
- <span style="font-size:18px;">static int __init omap2_mcspi_probe(struct platform_device *pdev)
- {
- struct spi_master *master;
- struct omap2_mcspi *mcspi;
- ……
- switch (pdev->id) {
- case 1:
- rxdma_id = spi1_rxdma_id;
- txdma_id = spi1_txdma_id;
- num_chipselect = 4;
- break;
- case 2:
- ……
- #if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3)
- case 3:
- ……
- #endif
- #ifdef CONFIG_ARCH_OMAP3
- case 4:
- ……
- #endif
- default:
- return -EINVAL;
- }
- master = spi_alloc_master(&pdev->dev, sizeof *mcspi);
- ……
- if (pdev->id != -1)
- master->bus_num = pdev->id;
- master->setup = omap2_mcspi_setup;
- master->transfer = omap2_mcspi_transfer;
- master->cleanup = omap2_mcspi_cleanup;
- master->num_chipselect = num_chipselect;
- dev_set_drvdata(&pdev->dev, master);
- mcspi = spi_master_get_devdata(master);
- mcspi->master = master;
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (r == NULL) {
- status = -ENODEV;
- goto err1;
- }
- if (!request_mem_region(r->start, (r->end - r->start) + 1,
- pdev->dev.bus_id)) {
- status = -EBUSY;
- goto err1;
- }
- mcspi->phys = r->start;
- mcspi->base = ioremap(r->start, r->end - r->start + 1);
- ……
- INIT_WORK(&mcspi->work, omap2_mcspi_work);
- spin_lock_init(&mcspi->lock);
- INIT_LIST_HEAD(&mcspi->msg_queue);
- mcspi->ick = clk_get(&pdev->dev, "mcspi_ick");
- ……
- mcspi->fck = clk_get(&pdev->dev, "mcspi_fck");
- ……
- mcspi->dma_channels = kcalloc(master->num_chipselect,
- sizeof(struct omap2_mcspi_dma),
- GFP_KERNEL);
- ……
- for (i = 0; i < num_chipselect; i++) {
- mcspi->dma_channels[i].dma_rx_channel = -1;
- mcspi->dma_channels[i].dma_rx_sync_dev = rxdma_id[i];
- mcspi->dma_channels[i].dma_tx_channel = -1;
- mcspi->dma_channels[i].dma_tx_sync_dev = txdma_id[i];
- }
- ……
- status = spi_register_master(master);
- ……
- return status;
- ……
- }</span>
5 OMAP3630 SPI 設備驅動
在Linux內核中,SPI 設備可以是各種SPI接口的設備,設備可以有不同的用途,這裏以Ti的觸摸屏控制器芯片tsc2005爲例來介紹SPI設備驅動的註冊過程,對應的設備驅動程序爲tsc2005.c,位於目錄drivers/input/touchscreen下。5.1 Tsc2005設備註冊
Tsc2005設備的創建以及註冊分爲兩步。5.1.1 將tsc2005設備信息加入到設備鏈表
在板級初始化時將tsc2005的名稱,地址和相關的信息加入到鏈表board_list中,該鏈表定義在driver/spi/spi.c中,記錄了具體開發板上的SPI設備信息。- <span style="font-size:18px;">static LIST_HEAD(board_list);</span>
- <span style="font-size:18px;">static struct spi_board_info xxxx_spi_board_info[] __initdata = {
- ……
- {
- .modalias = "tsc2005",
- .platform_data = &tsc2005_config,
- .controller_data = &touchscreen_spi_cfg,
- .mode = SPI_MODE_1,
- .irq = OMAP_GPIO_IRQ(TS_GPIO),
- .max_speed_hz = 700000,
- .bus_num = 0,
- .chip_select = 2,
- },
- };</span>
- <span style="font-size:18px;">static void __init omap_xxxx_init(void)
- {
- omap_i2c_init();
- ……
- spi_register_board_info(xxxx_spi_board_info,
- ARRAY_SIZE(xxxx_spi_board_info));
- ……
- }</span>
- <span style="font-size:18px;">int __init spi_register_board_info(struct spi_board_info const *info, unsigned n)
- {
- struct boardinfo *bi;
- bi = kmalloc(sizeof(*bi) + n * sizeof *info, GFP_KERNEL);
- if (!bi)
- return -ENOMEM;
- bi->n_board_info = n;
- memcpy(bi->board_info, info, n * sizeof *info);
- mutex_lock(&board_lock);
- list_add_tail(&bi->list, &board_list);
- mutex_unlock(&board_lock);
- return 0;
- }</span>
5.1.2 Tsc2005 spi_device的創建並添加
Tsc2005 spi_device的創建並添加在SPI控制器驅動註冊過程中完成,SPI 控制器驅動的註冊可以參考4.2.2節,spi_register_master()函數在註冊SPI控制器驅動的過程會掃描5.1.1中提到的SPI設備鏈表board_list,如果該總線上有對應的SPI設備,則創建相應的 spi_device,並將其添加到SPI的設備鏈表中。流程圖如下所示:- <span style="font-size:18px;">int spi_register_master(struct spi_master *master)
- {
- static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
- struct device *dev = master->dev.parent;
- int status = -ENODEV;
- int dynamic = 0;
- ……
- status = device_add(&master->dev);
- ……
- scan_boardinfo(master);
- ……
- }</span>
- <span style="font-size:18px;">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);
- }
- }
- mutex_unlock(&board_lock);
- }</span>
- <span style="font-size:18px;">struct spi_device *spi_new_device(struct spi_master *master,
- struct spi_board_info *chip)
- {
- struct spi_device *proxy;
- int status;
- ……
- proxy = spi_alloc_device(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;
- status = spi_add_device(proxy);
- if (status < 0) {
- spi_dev_put(proxy);
- return NULL;
- }
- return proxy;
- }</span>
- <span style="font-size:18px;">struct spi_device *spi_alloc_device(struct spi_master *master)
- {
- struct spi_device *spi;
- struct device *dev = master->dev.parent;
- if (!spi_master_get(master))
- return NULL;
- 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);
- return spi;
- }</span>
從spi_alloc_device()函數返回後繼續初始化spi_device結構體的chip_select,mode,modalias, controller_data等變量,這裏的proxy->modalias被初始化爲chip->modalias,在5.1.1中,chip->modalias初始化爲“tsc2005”,proxy->modalias後面會用於spi device和spi driver匹配時使用,最後調用spi_add_device ()將該SPI設備proxy添加到SPI設備鏈表中。
- <span style="font-size:18px;">int spi_add_device(struct spi_device *spi)
- {
- static DEFINE_MUTEX(spi_add_lock);
- struct device *dev = spi->master->dev.parent;
- int status;
- ……
- if (bus_find_device_by_name(&spi_bus_type, NULL, dev_name(&spi->dev))
- != NULL) {
- dev_err(dev, "chipselect %d already in use\n",
- spi->chip_select);
- status = -EBUSY;
- goto done;
- }
- ……
- status = spi->master->setup(spi);
- ……
- /* Device may be bound to an active driver when this returns */
- status = device_add(&spi->dev);
- …….
- }</span>
5.2 Tsc2005設備驅動註冊
在driver/input/touchscreen/tsc2005.c中,定義了tsc2005的設備驅動,代碼如下:- <span style="font-size:18px;">static struct spi_driver tsc2005_driver = {
- .driver = {
- .name = "tsc2005",
- .owner = THIS_MODULE,
- },
- #ifdef CONFIG_PM
- .suspend = tsc2005_suspend,
- .resume = tsc2005_resume,
- #endif
- .probe = tsc2005_probe,
- .remove = __devexit_p(tsc2005_remove),
- };</span>
- <span style="font-size:18px;">static int __init tsc2005_init(void)
- {
- printk(KERN_INFO "TSC2005 driver initializing\n");
- return spi_register_driver(&tsc2005_driver);
- }
- module_init(tsc2005_init);</span>
- <span style="font-size:18px;">int spi_register_driver(struct spi_driver *sdrv)
- {
- sdrv->driver.bus = &spi_bus_type;
- if (sdrv->probe)
- sdrv->driver.probe = spi_drv_probe;
- if (sdrv->remove)
- sdrv->driver.remove = spi_drv_remove;
- if (sdrv->shutdown)
- sdrv->driver.shutdown = spi_drv_shutdown;
- return driver_register(&sdrv->driver);
- } </span>
- <span style="font-size:18px;">static int __devinit tsc2005_probe(struct spi_device *spi)
- {
- struct tsc2005 *tsc;
- struct tsc2005_platform_data *pdata = spi->dev.platform_data;
- int r;
- ……
- tsc = kzalloc(sizeof(*tsc), GFP_KERNEL);
- ……
- dev_set_drvdata(&spi->dev, tsc);
- tsc->spi = spi;
- spi->dev.power.power_state = PMSG_ON;
- spi->mode = SPI_MODE_0;
- spi->bits_per_word = 8;
- ……
- spi_setup(spi);
- r = tsc2005_ts_init(tsc, pdata);
- ……
- }</span>
在tsc2005_probe()函數中,完成一些具體tsc2005設備相關的初始化等操作,這邊就不再詳述。
6 用戶空間的支持
和I2C一樣,SPI也具有一個通用的設備的動程序,通過一個帶有操作集file_operations的標準字符設備驅動爲用戶空間提供了訪問接口。此驅動模型是針對SPI設備的,只有註冊board info時modalias是”spidev”的才能由此驅動訪問。訪問各個slave的基本框架是一致的,具體的差異將由從設備號來體現,代碼部分位於drivers/spi/spidev.c。
6.1 Spidev的註冊
在drivers/spi/spidev.c中,首先定義了SPI設備驅動spidev_spi:- <span style="font-size:18px;">static struct spi_driver spidev_spi = {
- .driver = {
- .name = "spidev",
- .owner = THIS_MODULE,
- },
- .probe = spidev_probe,
- .remove = __devexit_p(spidev_remove),
- };</span>
- <span style="font-size:18px;">static int __init spidev_init(void)
- {
- int status;
- ……
- status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
- if (status < 0)
- return status;
- spidev_class = class_create(THIS_MODULE, "spidev");
- ……
- status = spi_register_driver(&spidev_spi);
- ……
- }
- module_init(spidev_init);</span>
- <span style="font-size:18px;">static struct file_operations spidev_fops = {
- .owner = THIS_MODULE,
- /* REVISIT switch to aio primitives, so that userspace
- * gets more complete API coverage. It'll simplify things
- * too, except for the locking.
- */
- .write = spidev_write,
- .read = spidev_read,
- .unlocked_ioctl = spidev_ioctl,
- .open = spidev_open,
- .release = spidev_release,
- };</span>
然後調用函數spi_register_driver(&spidev_spi)將spidev_spi 驅動註冊到SPI 核心層中,spidev_spi驅動註冊過程中會掃描SPI總線上的所有SPI設備,一旦掃描到關鍵字modalias爲”spidev”的SPI設備時,則與該SPI驅動匹配,匹配函數spidev_probe()被調用,具體的註冊流程圖如下:
- <span style="font-size:18px;">static int spidev_probe(struct spi_device *spi)
- {
- struct spidev_data *spidev;
- int status;
- unsigned long minor;
- /* Allocate driver data */
- spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
- if (!spidev)
- return -ENOMEM;
- /* Initialize the driver data */
- spidev->spi = spi;
- ……
- 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 = IS_ERR(dev) ? PTR_ERR(dev) : 0;
- } else {
- dev_dbg(&spi->dev, "no minor number available!\n");
- status = -ENODEV;
- }
- if (status == 0) {
- set_bit(minor, minors);
- list_add(&spidev->device_entry, &device_list);
- }
- …….
- }</span>
- <span style="font-size:18px;">struct spidev_data {
- dev_t devt;
- spinlock_t spi_lock;
- struct spi_device *spi;
- struct list_head device_entry;
- /* buffer is NULL unless this device is open (users > 0) */
- struct mutex buf_lock;
- unsigned users;
- u8 *buffer;
- };</span>
分配完spidev_data結構體後對其進行初始化,然後創建spi設備,成功後將該spidev_data添加到device_list鏈表中,該鏈表維護這些modalias爲”spidev”的SPI設備。device_list鏈表定義在spidev.c中如下:
- <span style="font-size:18px;">static LIST_HEAD(device_list);</span>
6.2 Spidev的打開
Spidev的open函數如下:- <span style="font-size:18px;">static int spidev_open(struct inode *inode, struct file *filp)
- {
- struct spidev_data *spidev;
- int status = -ENXIO;
- lock_kernel();
- mutex_lock(&device_list_lock);
- list_for_each_entry(spidev, &device_list, device_entry) {
- if (spidev->devt == inode->i_rdev) {
- status = 0;
- break;
- }
- }
- if (status == 0) {
- if (!spidev->buffer) {
- spidev->buffer = kmalloc(bufsiz, GFP_KERNEL);
- if (!spidev->buffer) {
- dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
- status = -ENOMEM;
- }
- }
- if (status == 0) {
- spidev->users++;
- filp->private_data = spidev;
- nonseekable_open(inode, filp);
- }
- } else
- pr_debug("spidev: nothing for minor %d\n", iminor(inode));
- mutex_unlock(&device_list_lock);
- unlock_kernel();
- return status;
- }
- </span>
用open函數將spidev設備打開以後,就可以通過ioctl函數的各種命令來對modalias爲”spidev”的SPI設備進行讀寫等操作,也可以通過 read和write函數完成對SPI設備的讀寫。
對SPI設備的具體操作在這裏不再具體闡述,可以參看spidev.c源代碼。
7 SPI數據收發
第二章的時候的時候已經講到過,SPI的通信是通過消息隊列機制也就是workqueue,核心思想就是要構造一個spi_message。7.1 SPI數據收發數據結構
SPI數據收發用到的結構體主要有兩個,spi_message和spi_transfer,兩者都定義在include/linux/spi/spi.h中。spi_message定義如下:
- <span style="font-size:18px;">/**
- * struct spi_message - one multi-segment SPI transaction
- * @transfers: list of transfer segments in this transaction
- * @spi: SPI device to which the transaction is queued
- * @is_dma_mapped: if true, the caller provided both dma and cpu virtual
- * addresses for each transfer buffer
- * @complete: called to report transaction completions
- * @context: the argument to complete() when it's called
- * @actual_length: the total number of bytes that were transferred in all
- * successful segments
- * @status: zero for success, else negative errno
- * @queue: for use by whichever driver currently owns the message
- * @state: for use by whichever driver currently owns the message
- *
- * A @spi_message is used to execute an atomic sequence of data transfers,
- * each represented by a struct spi_transfer. The sequence is "atomic"
- * in the sense that no other spi_message may use that SPI bus until that
- * sequence completes. On some systems, many such sequences can execute as
- * as single programmed DMA transfer. On all systems, these messages are
- * queued, and might complete after transactions to other devices. Messages
- * sent to a given spi_device are alway executed in FIFO order.
- *
- * The code that submits an spi_message (and its spi_transfers)
- * to the lower layers is responsible for managing its memory.
- * Zero-initialize every field you don't set up explicitly, to
- * insulate against future API updates. After you submit a message
- * and its transfers, ignore them until its completion callback.
- */
- struct spi_message {
- struct list_head transfers;
- struct spi_device *spi;
- unsigned is_dma_mapped:1;
- /* REVISIT: we might want a flag affecting the behavior of the
- * last transfer ... allowing things like "read 16 bit length L"
- * immediately followed by "read L bytes". Basically imposing
- * a specific message scheduling algorithm.
- *
- * Some controller drivers (message-at-a-time queue processing)
- * could provide that as their default scheduling algorithm. But
- * others (with multi-message pipelines) could need a flag to
- * tell them about such special cases.
- */
- /* completion is reported through a callback */
- void (*complete)(void *context);
- void *context;
- unsigned actual_length;
- int status;
- /* for optional use by whatever driver currently owns the
- * spi_message ... between calls to spi_async and then later
- * complete(), that's the spi_master controller driver.
- */
- struct list_head queue;
- void *state;
- };</span><span style="font-size:18px;">
- </span>
還定義了需要發送的SPI設備,上下文信息context和傳輸完成後需要的回調函數complete等。
spi_transfer結構體的定義如下:
- <span style="font-size:18px;">/**
- * struct spi_transfer - a read/write buffer pair
- * @tx_buf: data to be written (dma-safe memory), or NULL
- * @rx_buf: data to be read (dma-safe memory), or NULL
- * @tx_dma: DMA address of tx_buf, if @spi_message.is_dma_mapped
- * @rx_dma: DMA address of rx_buf, if @spi_message.is_dma_mapped
- * @len: size of rx and tx buffers (in bytes)
- * @speed_hz: Select a speed other than the device default for this
- * transfer. If 0 the default (from @spi_device) is used.
- * @bits_per_word: select a bits_per_word other than the device default
- * for this transfer. If 0 the default (from @spi_device) is used.
- * @cs_change: affects chipselect after this transfer completes
- * @delay_usecs: microseconds to delay after this transfer before
- * (optionally) changing the chipselect status, then starting
- * the next transfer or completing this @spi_message.
- * @transfer_list: transfers are sequenced through @spi_message.transfers
- *
- * SPI transfers always write the same number of bytes as they read.
- * Protocol drivers should always provide @rx_buf and/or @tx_buf.
- * In some cases, they may also want to provide DMA addresses for
- * the data being transferred; that may reduce overhead, when the
- * underlying driver uses dma.
- *
- * If the transmit buffer is null, zeroes will be shifted out
- * while filling @rx_buf. If the receive buffer is null, the data
- * shifted in will be discarded. Only "len" bytes shift out (or in).
- * It's an error to try to shift out a partial word. (For example, by
- * shifting out three bytes with word size of sixteen or twenty bits;
- * the former uses two bytes per word, the latter uses four bytes.)
- *
- * In-memory data values are always in native CPU byte order, translated
- * from the wire byte order (big-endian except with SPI_LSB_FIRST). So
- * for example when bits_per_word is sixteen, buffers are 2N bytes long
- * (@len = 2N) and hold N sixteen bit words in CPU byte order.
- *
- * When the word size of the SPI transfer is not a power-of-two multiple
- * of eight bits, those in-memory words include extra bits. In-memory
- * words are always seen by protocol drivers as right-justified, so the
- * undefined (rx) or unused (tx) bits are always the most significant bits.
- *
- * All SPI transfers start with the relevant chipselect active. Normally
- * it stays selected until after the last transfer in a message. Drivers
- * can affect the chipselect signal using cs_change.
- *
- * (i) If the transfer isn't the last one in the message, this flag is
- * used to make the chipselect briefly go inactive in the middle of the
- * message. Toggling chipselect in this way may be needed to terminate
- * a chip command, letting a single spi_message perform all of group of
- * chip transactions together.
- *
- * (ii) When the transfer is the last one in the message, the chip may
- * stay selected until the next transfer. On multi-device SPI busses
- * with nothing blocking messages going to other devices, this is just
- * a performance hint; starting a message to another device deselects
- * this one. But in other cases, this can be used to ensure correctness.
- * Some devices need protocol transactions to be built from a series of
- * spi_message submissions, where the content of one message is determined
- * by the results of previous messages and where the whole transaction
- * ends when the chipselect goes intactive.
- *
- * The code that submits an spi_message (and its spi_transfers)
- * to the lower layers is responsible for managing its memory.
- * Zero-initialize every field you don't set up explicitly, to
- * insulate against future API updates. After you submit a message
- * and its transfers, ignore them until its completion callback.
- */
- struct spi_transfer {
- /* it's ok if tx_buf == rx_buf (right?)
- * for MicroWire, one buffer must be null
- * buffers must work with dma_*map_single() calls, unless
- * spi_message.is_dma_mapped reports a pre-existing mapping
- */
- const void *tx_buf;
- void *rx_buf;
- unsigned len;
- dma_addr_t tx_dma;
- dma_addr_t rx_dma;
- unsigned cs_change:1;
- u8 bits_per_word;
- u16 delay_usecs;
- u32 speed_hz;
- struct list_head transfer_list;
- };</span><span style="font-size:18px;">
- </span>
對於SPI總線協議來說,傳輸單位可以是4-32之間的任意bits,但對於SPI控制器來說,bits_per_word只能是8/16/32,即需要取整,待收發的數據在內存裏都是以主機字節序右對齊存儲,SPI控制器會自動根據傳輸單位及大小端進行轉換。
7.2 SPI數據收發的函數
SPI數據收發的函數主要有spi_async()和spi_sync()。函數spi_async()定義在include/linux/spi/spi.h中,
- <span style="font-size:18px;">/**
- * spi_async - asynchronous SPI transfer
- * @spi: device with which data will be exchanged
- * @message: describes the data transfers, including completion callback
- * Context: any (irqs may be blocked, etc)
- *
- * This call may be used in_irq and other contexts which can't sleep,
- * as well as from task contexts which can sleep.
- *
- * The completion callback is invoked in a context which can't sleep.
- * Before that invocation, the value of message->status is undefined.
- * When the callback is issued, message->status holds either zero (to
- * indicate complete success) or a negative error code. After that
- * callback returns, the driver which issued the transfer request may
- * deallocate the associated memory; it's no longer in use by any SPI
- * core or controller driver code.
- *
- * Note that although all messages to a spi_device are handled in
- * FIFO order, messages may go to different devices in other orders.
- * Some device might be higher priority, or have various "hard" access
- * time requirements, for example.
- *
- * On detection of any fault during the transfer, processing of
- * the entire message is aborted, and the device is deselected.
- * Until returning from the associated message completion callback,
- * no other spi_message queued to that device will be processed.
- * (This rule applies equally to all the synchronous transfer calls,
- * which are wrappers around this core asynchronous primitive.)
- */
- static inline int
- spi_async(struct spi_device *spi, struct spi_message *message)
- {
- message->spi = spi;
- return spi->master->transfer(spi, message);
- }</span><span style="font-family:Arial, Verdana, sans-serif;"><span style="white-space: normal;font-size:18px;">
- </span></span>
該函數是異步的SPI傳輸函數,可以用於在中端或者某些不能睡眠的上下文場合,當然也可以用於睡眠的場合。
對於發送給同一個SPI設備的message遵循FIFO的順序,發送給不同的SPI設備的message可以有不同的順序。
直到從回調函數返回之前,都不能處理該設備的其他spi_message。從源代碼可以看到,該函數最後調用了發送給相應設備的相應控制器的transfer函數。
spi_sync()函數定義在driver/spi/spi.c中,
- <span style="font-size:18px;">/**
- * spi_sync - blocking/synchronous SPI data transfers
- * @spi: device with which data will be exchanged
- * @message: describes the data transfers
- * Context: can sleep
- *
- * This call may only be used from a context that may sleep. The sleep
- * is non-interruptible, and has no timeout. Low-overhead controller
- * drivers may DMA directly into and out of the message buffers.
- *
- * Note that the SPI device's chip select is active during the message,
- * and then is normally disabled between messages. Drivers for some
- * frequently-used devices may want to minimize costs of selecting a chip,
- * by leaving it selected in anticipation that the next message will go
- * to the same chip. (That may increase power usage.)
- *
- * Also, the caller is guaranteeing that the memory associated with the
- * message will not be freed before this call returns.
- *
- * It returns zero on success, else a negative error code.
- */
- int spi_sync(struct spi_device *spi, struct spi_message *message)
- {
- DECLARE_COMPLETION_ONSTACK(done);
- int status;
- message->complete = spi_complete;
- message->context = &done;
- status = spi_async(spi, message);
- if (status == 0) {
- wait_for_completion(&done);
- status = message->status;
- }
- message->context = NULL;
- return status;
- }</span><span style="font-size:18px;">
- </span>
7.3 SPI數據傳輸example
下面是tsc2005進行SPI數據傳輸的例子,- <span style="font-size:18px;">static void tsc2005_write(struct tsc2005 *ts, u8 reg, u16 value)
- {
- u32 tx;
- struct spi_message msg;
- struct spi_transfer xfer = { 0 };
- tx = (TSC2005_REG | reg | TSC2005_REG_PND0 |
- TSC2005_REG_WRITE) << 16;
- tx |= value;
- xfer.tx_buf = &tx;
- xfer.rx_buf = NULL;
- xfer.len = 4;
- xfer.bits_per_word = 24;
- spi_message_init(&msg);
- spi_message_add_tail(&xfer, &msg);
- spi_sync(ts->spi, &msg);
- }</span><span style="font-size:18px;">
- </span>
該函數首先定義SPI 傳輸所需的spi_message和spi_transfer數據結構,初始化他們,然後將spi_transfer添加到spi_message的transfer_list鏈表中,最後調用spi_sync()將SPI數據發送出去
- TI的CPU芯片手冊有兩種:
- 一種是datasheet(DS:數據手冊),較小,只是大概介紹下芯片的結構;
- 另一種是Technical Reference Manual(TRM:技術參考手冊),較大,詳細介紹芯片的各部分功能原理和寄存器定義。
- 在開發過程中,這兩個手冊都需要參考,是互補的。
- mux.h
- mux.c
- mux33xx.h
- mux33xx.c
- board-am335xevm.c
-
- (還有一些用到了:arch/arm/plat-omap/include/plat/omap_hwmod.h)
-
/**
-
* struct mux_partition - 包含分區相關信息
-
* @name: 當前分區名
-
* @flags: 本分區的特定標誌
-
* @phys: 物理地址
-
* @size: 分區大小
-
* @base: ioremap 映射過的虛擬地址
-
* @muxmodes: 本分區mux節點鏈表頭
-
* @node: 分區鏈表頭
-
*/
-
struct omap_mux_partition {
-
const char *name;
-
u32 flags;
-
u32 phys;
-
u32 size;
-
void __iomem *base;
-
struct list_head muxmodes;
-
struct list_head node;
- };
-
/**
-
* struct omap_mux_entry - mux信息節點
-
* @mux: omap_mux結構體
-
* @node: 鏈表節點
-
*/
-
struct omap_mux_entry {
-
struct omap_mux mux;
-
struct list_head node;
- };
-
/**
-
* struct omap_mux - omap mux 寄存器偏移和值的數據
-
* @reg_offset: 從Control Module寄存器基地址算起的mux寄存器偏移
-
* @gpio: GPIO 編號
-
* @muxnames: 引腳可用的信號模式字符串指針數組
-
* @balls: 封裝中可用的引腳
-
*/
-
struct omap_mux {
-
u16 reg_offset;
-
u16 gpio;
-
#ifdef CONFIG_OMAP_MUX
-
char *muxnames[OMAP_MUX_NR_MODES];
-
#ifdef CONFIG_DEBUG_FS
-
char *balls[OMAP_MUX_NR_SIDES];
-
#endif
-
#endif
- };
-
#define _AM33XX_MUXENTRY(M0, g, m0, m1, m2, m3, m4, m5, m6, m7) \
-
{ \
-
.reg_offset = (AM33XX_CONTROL_PADCONF_##M0##_OFFSET), \
-
.gpio = (g), \
-
.muxnames = { m0, m1, m2, m3, m4, m5, m6, m7 }, \
- }
-
/**
-
* struct omap_board_mux - 初始化mux寄存器的數據
-
* @reg_offset: 從Control Module寄存器基地址算起的mux寄存器偏移
-
* @mux_value: 希望設置的mux寄存器值
-
*/
-
struct omap_board_mux {
-
u16 reg_offset;
-
u16 value;
- };
-
#ifdef CONFIG_OMAP_MUX
-
static struct omap_board_mux board_mux[] __initdata = {
-
AM33XX_MUX(I2C0_SDA, OMAP_MUX_MODE0 | AM33XX_SLEWCTRL_SLOW |
-
AM33XX_INPUT_EN | AM33XX_PIN_OUTPUT),
-
AM33XX_MUX(I2C0_SCL, OMAP_MUX_MODE0 | AM33XX_SLEWCTRL_SLOW |
-
AM33XX_INPUT_EN | AM33XX_PIN_OUTPUT),
-
{ .reg_offset = OMAP_MUX_TERMINATOR },
-
};
-
#else
-
#define board_mux NULL
- #endif
-
/* 如果引腳沒有定義爲輸入,拉動電阻將會被禁用
-
* 如果定義爲輸入,所提供的標誌位將確定拉動電阻的配置
-
*/
-
#define AM33XX_MUX(mode0, mux_value) \
-
{ \
-
.reg_offset = (AM33XX_CONTROL_PADCONF_##mode0##_OFFSET), \
-
.value = (((mux_value) & AM33XX_INPUT_EN) ? (mux_value)\
-
: ((mux_value) | AM33XX_PULL_DISA)), \
- }
-
/**
-
* omap_mux_init - MUX初始化的私有函數,請勿使用
-
* 由各板級特定的MUX初始化函數調用
-
*/
-
int omap_mux_init(const char *name, u32
flags,
-
u32 mux_pbase, u32 mux_size,
-
struct omap_mux *superset,
-
struct omap_mux *package_subset,
-
struct omap_board_mux *board_mux,
- struct omap_ball *package_balls);
-
/**
-
* am33xx_mux_init() - 用板級特定的設置來初始化MUX系統
-
* @board_mux: 板級特定的MUX配置表
-
*/
-
int __init am33xx_mux_init(struct omap_board_mux *board_subset)
-
{
-
return omap_mux_init("core", 0, AM33XX_CONTROL_PADCONF_MUX_PBASE,
-
AM33XX_CONTROL_PADCONF_MUX_SIZE, am33xx_muxmodes,
-
NULL, board_subset, NULL);
- }
-
/**
-
* omap_mux_init_signal - 根據信號名字符串初始化一個引腳的mux
-
* @muxname: mode0_name.signal_name的格式的Mux名稱
-
* @val: mux寄存器值
-
*/
-
int omap_mux_init_signal(const char *muxname, int val);
-
-
/**
-
* omap_mux_get() - 通過名字返回一個mux分區
-
* @name: mux分區名
-
*
-
*/
-
struct omap_mux_partition *omap_mux_get(const char *name);
-
-
/**
-
* omap_mux_read() - 讀取mux寄存器(通過分區結構體指針和寄存器偏移值)
-
* @partition: Mux分區
-
* @mux_offset: mux寄存器偏移
-
*
-
*/
-
u16 omap_mux_read(struct omap_mux_partition *p, u16 mux_offset);
-
-
/**
-
* omap_mux_write() - 寫mux寄存器(通過分區結構體指針和寄存器偏移值)
-
* @partition: Mux分區
-
* @val: 新的mux寄存器值
-
* @mux_offset: mux寄存器偏移
-
*
-
* 這個函數僅有在非GPIO信號的動態複用需要
-
*/
-
void omap_mux_write(struct omap_mux_partition *p, u16 val, u16
mux_offset);
-
-
/**
-
* omap_mux_write_array() - 寫mux寄存器陣列
-
* @partition: Mux分區
-
* @board_mux: mux寄存器陣列 (用MAP_MUX_TERMINATOR結尾)
-
*
-
* 這個函數僅有在非GPIO信號的動態複用需要
-
*/
-
void omap_mux_write_array(struct omap_mux_partition *p,
- struct omap_board_mux *board_mux);
-
/**
-
* omap_mux_init_gpio - 根據GPIO編號初始化一個信號引腳
-
* @gpio: GPIO編號
-
* @val: mux寄存器值
-
*/
-
int omap_mux_init_gpio(int gpio, int val);
-
-
/**
-
* omap_mux_get_gpio() - 根據GPIO編號獲取一個mux寄存器值
-
* @gpio: GPIO編號
-
*
-
*/
-
u16 omap_mux_get_gpio(int gpio);
-
-
/**
-
* omap_mux_set_gpio() - 根據GPIO編號設定一個mux寄存器值
-
* @val: 新的mux寄存器值
-
* @gpio: GPIO編號
-
*
-
*/
- void omap_mux_set_gpio(u16 val, int gpio);
-
/* 模塊引腳複用結構體 */
-
struct pinmux_config {
-
const char *string_name; /* 信號名格式化字符串,“模式0字符串.目標模式字符串“ */
-
int val; /* 其他mux寄存器可選配置值 */
-
};
-
-
/*
-
* @pin_mux - 單個模塊引腳複用結構體
-
* 其中定義了本模塊所有引腳複用細節.
-
*/
-
static void setup_pin_mux(struct pinmux_config *pin_mux)
-
{
-
int i;
-
-
for (i = 0; pin_mux->string_name != NULL; pin_mux++)
-
omap_mux_init_signal(pin_mux->string_name, pin_mux->val);
-
- }
-
static struct pinmux_config d_can_ia_pin_mux[] = {
-
{"uart0_rxd.d_can0_tx", OMAP_MUX_MODE2 | AM33XX_PULL_ENBL},
-
{"uart0_txd.d_can0_rx", OMAP_MUX_MODE2 | AM33XX_PIN_INPUT_PULLUP},
-
{NULL, 0},
-
};
-
-
......
-
static void d_can_init(int evm_id, int profile)
-
{
-
switch (evm_id) {
-
case IND_AUT_MTR_EVM:
-
if ((profile == PROFILE_0) || (profile == PROFILE_1)) {
-
setup_pin_mux(d_can_ia_pin_mux);
-
/* Instance Zero */
-
am33xx_d_can_init(0);
-
}
-
break;
-
case GEN_PURP_EVM:
-
if (profile == PROFILE_1) {
-
setup_pin_mux(d_can_gp_pin_mux);
-
/* Instance One */
-
am33xx_d_can_init(1);
-
}
-
break;
-
default:
-
break;
-
}
- }