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;
- }
- }