SPI(Serial Peripheral Interface) 是一個同步的四線制串行線,,用于連接微控制器和傳感器、存儲器及外圍設(shè)備。三條信號線持有時鐘信號(SCLK,,經(jīng)常在10MHz左右)和并行數(shù)據(jù)線帶有“主出,從進(MOSI)”或是“主進,,從出(MISO)”信號,。數(shù)據(jù)交換的時候有四種時鐘模式,模式0和模式3是最經(jīng)常使用的,。每個時鐘周期將會傳遞數(shù)據(jù)進和出,。如果沒有數(shù)據(jù)傳遞的話,時鐘將不會循環(huán),。
SPI驅(qū)動分為兩類:
控制器驅(qū)動:它們通常內(nèi)嵌于片上系統(tǒng)處理器,,通常既支持主設(shè)備,又支持從設(shè)備,。這些驅(qū)動涉及硬件寄存器,,可能使用DMA?;蛩鼈兪褂?/span>GPIO引腳成為PIO bitbangers,。這部分通常會由特定的開發(fā)板提供商提供,不用自己寫,。
協(xié)議驅(qū)動:它們通過控制器驅(qū)動,,以SPI連接的方式在主從設(shè)備之間傳遞信息。這部分涉及具體的SPI從設(shè)備,,通常需要自己編寫,。
那么特定的目標(biāo)板如何讓Linux 操控SPI設(shè)備,?下面以AT91SAM9260系列CAN設(shè)備驅(qū)動為例,Linux內(nèi)核版本為2.6.19,。本文不涉及控制器驅(qū)動分析,。
board_info提供足夠的信息使得系統(tǒng)正常工作而不需要芯片驅(qū)動加載
- 在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驅(qū)動。
下面來看MCP2515 CAN驅(qū)動的結(jié)構(gòu),,協(xié)議驅(qū)動有點類似平臺設(shè)備驅(qū)動,,本文只列出框架,不涉及具體實現(xiàn)代碼,,在/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
- };
- 驅(qū)動將自動試圖綁定驅(qū)動到任何board_info給定別名為" mcp2515"的SPI設(shè)備,。
- 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(),驅(qū)動使用"struct spi_message"向SPI設(shè)備要求I/O,。當(dāng)remove()返回時,,驅(qū)動保證將不會提交任何這種信息。 一個spi_message是協(xié)議操作序列,,以一個原子序列執(zhí)行,。SPI驅(qū)動控制包括: (1)當(dāng)雙向讀寫開始,是根據(jù)spi_transfer要求序列是怎樣安排的,。 (2)隨意設(shè)定傳遞后的短延時,,使用spi_transfer.delay_usecs設(shè)定。 (3)在一次傳遞和任何延時之后,,無論片選是否活躍,,使用spi_transfer.cs_change標(biāo)志, 暗示下條信息是否進入這個同樣的設(shè)備,,使用原子組中最后一次傳輸上的spi_transfer.cs_change標(biāo)志位,,可能節(jié)省芯片選擇取消操作的成本。
|