SPI(Serial Peripheral Interface) 是一个同步的四线制串行线,用于连接微控制器和传感器、存储器及外围设备。三条信号线持有时钟信号(SCLK,经常在10MHz左右)和并行数据线带有“主出,从进(MOSI)”或是“主进,从出(MISO)”信号。数据交换的时候有四种时钟模式,模式0和模式3是最经常使用的。每个时钟周期将会传递数据进和出。如果没有数据传递的话,时钟将不会循环。
SPI驱动分为两类:
控制器驱动:它们通常内嵌于片上系统处理器,通常既支持主设备,又支持从设备。这些驱动涉及硬件寄存器,可能使用DMA。或它们使用GPIO引脚成为PIO bitbangers。这部分通常会由特定的开发板提供商提供,不用自己写。
协议驱动:它们通过控制器驱动,以SPI连接的方式在主从设备之间传递信息。这部分涉及具体的SPI从设备,通常需要自己编写。
那么特定的目标板如何让Linux 操控SPI设备?下面以AT91SAM9260系列CAN设备驱动为例,Linux内核版本为2.6.19。本文不涉及控制器驱动分析。
board_info提供足够的信息使得系统正常工作而不需要芯片驱动加载
在arch/arm/mach-at91rm9200/board-at91sam9260.c中有如下代码: #include <linux/platform_device.h> #include <linux/spi/spi.h> ……. static struct spi_board_info ek_spi_devices[] = { /* spi can ,add by mrz */ #if defined(CONFIG_CAN_MCP2515) { .modalias = "mcp2515", .chip_select = 0, // .controller_data = AT91_PIN_PB3, .irq = AT91_PIN_PC6, //AT91SAM9260_ID_IRQ0, .platform_data = &mcp251x_data, .max_speed_hz = 10 * 1000 * 1000, .bus_num = 1, .mode = 0, } #endif };. ……… static void __init ek_board_init(void) { …… /* SPI */ at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices)); }
这样在Linux初始化时候就可以加载SPI CAN驱动。
下面来看MCP2515 CAN驱动的结构,协议驱动有点类似平台设备驱动,本文只列出框架,不涉及具体实现代码,在/driver/CAN/MCP2515.c中:
static struct spi_driver mcp251x_driver = { .driver = { .name = mcp2515, .bus = &spi_bus_type, .owner = THIS_MODULE, }, .probe = mcp251x_probe, .remove = __devexit_p(mcp251x_remove), #ifdef CONFIG_PM .suspend = mcp251x_suspend, .resume = mcp251x_resume, #endif }; 驱动将自动试图绑定驱动到任何board_info给定别名为" mcp2515"的SPI设备。 static int __devinit mcp251x_probe(struct spi_device *spi) { struct mcp251x *chip; int ret = 0; dev_dbg(&spi->dev, "%s: start/n", __FUNCTION__); chip = kmalloc(sizeof(struct mcp251x), GFP_KERNEL); if (!chip) { ret = -ENOMEM; goto error_alloc; } dev_set_drvdata(&spi->dev, chip); chip->txbin = chip->txbout = 0; chip->rxbin = chip->rxbout = 0; chip->count = 0; chip->spi = spi; init_MUTEX(&chip->lock); init_MUTEX(&chip->txblock); init_MUTEX(&chip->rxblock); init_waitqueue_head(&chip->wq); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)) INIT_WORK(&chip->irq_work, mcp251x_irq_handler); #else INIT_WORK(&chip->irq_work, mcp251x_irq_handler, spi); #endif chip->spi_transfer_buf = kmalloc(SPI_TRANSFER_BUF_LEN, GFP_KERNEL); if (!chip->spi_transfer_buf) { ret = -ENOMEM; goto error_buf; } ret = request_irq(spi->irq, mcp251x_irq, SA_SAMPLE_RANDOM, DRIVER_NAME, spi); if (ret < 0) { dev_err(&spi->dev, "request irq %d failed (ret = %d)/n", spi->irq, ret); goto error_irq; } cdev_init(&chip->cdev, &mcp251x_fops); chip->cdev.owner = THIS_MODULE; ret = cdev_add(&chip->cdev, MKDEV(MAJOR(can_devt), can_minor), 1); if (ret < 0) { dev_err(&spi->dev, "register char device failed (ret = %d)/n", ret); goto error_register; } chip->class_dev = class_device_create(can_class, NULL, MKDEV(MAJOR(can_devt), can_minor), &spi->dev, "can%d", can_minor); if (IS_ERR(chip->class_dev)) { dev_err(&spi->dev, "cannot create CAN class device/n"); ret = PTR_ERR(chip->class_dev); goto error_class_reg; } dev_info(&spi->dev, "device register at dev(%d:%d)/n", MAJOR(can_devt), can_minor); mcp251x_hw_init(spi); mcp251x_set_bit_rate(spi, 125000); /* A reasonable default */ mcp251x_hw_sleep(spi); can_minor++; return 0; error_class_reg: cdev_del(&chip->cdev); error_register: free_irq(spi->irq, spi); error_irq: kfree(chip->spi_transfer_buf); error_buf: kfree(chip); error_alloc: return ret; }
一旦进入probe(),驱动使用"struct spi_message"向SPI设备要求I/O。当remove()返回时,驱动保证将不会提交任何这种信息。
一个spi_message是协议操作序列,以一个原子序列执行。SPI驱动控制包括:
(1)当双向读写开始,是根据spi_transfer要求序列是怎样安排的。
(2)随意设定传递后的短延时,使用spi_transfer.delay_usecs设定。
(3)在一次传递和任何延时之后,无论片选是否活跃,使用spi_transfer.cs_change标志, 暗示下条信息是否进入这个同样的设备,使用原子组中最后一次传输上的spi_transfer.cs_change标志位,可能节省芯片选择取消操作的成本。