Linux 2.6下SPI設備模型

轉載自:http://hi.baidu.com/yao165671242/blog/item/0c8c403484fce39ba61e12af.html

 

 

SPI,是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍設備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在 EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只佔用四根線,節約了芯片的管腳,同時爲PCB的佈局上節省空間,提供方便,正是出於這種簡單易用的特性,現在越來越多的芯片集成了這種通信協議,比如AT91RM9200. SPI總線系統是一種同步串行外設接口,它可以使MCU與各種外圍設備以串行方式進行通信以交換信息。外圍設置FLASHRAM、網絡控制器、LCD顯示驅動器、A/D轉換器和MCU等。SPI總線系統可直接與各個廠家生產的多種標準外圍器件直接接口,該接口一般使用4條線:串行時鐘線(SCK)、主機輸入/從機輸出數據線MISO、主機輸出/從機輸入數據線MOST和低電平有效的從機選擇線CS(有的 SPI接口芯片帶有中斷信號線INT或INT、有的SPI接口芯片沒有主機輸出/從機輸入數據線MOSI).

SPI總線協議
SPI(Serial Peripheral Interface),顧名思義,就是串行外圍設備接口,是Motorola首先在其MC68HCXX系列處理器上定義的。SPI總線是一種高速,全雙工,同步的通信總線,只佔用四根線就可完成基本的數據通信,既節約了芯片的管腳,也給PCB的佈局佈線提供了方便。正是出於這種簡單易用的特點,越來越多的芯片集成了這種通信協議。
SPI的通信原理很簡單,以主從方式工作,一個主設備可以控制一個或多個從設備。主從設備之間的通信至少需要4根線,包括MISO(主輸入從輸出數據傳輸線),MOSI(主輸出從輸入數據傳輸線),SCK(時鐘),CS(片選)。
1)      MISO -主設備數據輸入端/從設備數據輸出端
2)      MOSI -主設備數據輸出端/從設備數據輸入端
3)      SCK -時鐘信號,由主設備提供給從設備
4)      CS   -從設備使能信號,由主設備控制
當主設備與多個從設備連接時,主設備通過尋址選通當前要訪問的從設備。被選中的從設備的使能信號CS被置爲有效狀態,其他從設備的使能信號被置爲無效狀態,從而實現一點對多點的通信。
因爲SPI是串行通信協議,也就是說數據是一位一位的傳輸,這就需要SCK時鐘信號提供時鐘脈衝,數據傳輸線基於此脈衝完成數據的傳輸。輸出數據在時鐘的上升沿(或下降沿)改變,在緊接着的下降沿(或上升沿)被讀取,完成一位數據的輸出,輸入同理。這樣,要完成8位數據的傳輸則至少需要SCK的8個時鐘週期。
要注意的是,SCK信號線只由主設備控制,從設備不能控制此信號線。因此,在一個基於SPI的設備中,至少有一個主設備。當沒有時鐘跳變時,從設備不採集或傳送數據,這樣,主設備就可以在數據傳輸期間通過對SCK時鐘信號的控制一位一位的傳輸數據甚至暫停數據的傳輸。
SPI協議舉例
SPI通信的過程實際上就是主從設備各自的移位寄存器之間的數據交換。如圖所示。
    假設主設備的8位移位寄存器中放的是待發送的數據01010101,上升沿發送,下降沿接收,高位先發送,(從設備的8位移位寄存器中放的數是不確定的,假設爲10111010)。那麼第一個上升沿到來時,主設備將移位寄存器中的最高位數據發送出去,主設備數據輸出端MOSI=0;同時從設備也將其移位寄存器的最高位數據發送出去,從設備數據輸出端MISO=1,這時主設備移位寄存器中的數據變爲1010101x。下降沿到來時,MOSI上的數據被鎖存到從設備的寄存器中,這時從設備的移位寄存器=01110100,同時MISO上的數據被鎖存到主設備的寄存器中。也就是說,當主設備向從設備發送一位數據的同時,從設備也會從移位寄存器中回送一位數據。這樣在8個脈衝以後,兩個寄存器的內容互換一次,從而完成了一段SPI傳輸。


SPI總線工作方式
因爲SPI的數據輸入和輸出線獨立,所以允許同時完成數據的輸入和輸出。不同的SPI設備實現方式不同,如圖所示。
由圖可知,SPI總線共有四種工作方式,主要是數據改變和採集的時間不同,在時鐘上升沿和下降沿採集有不同定義。這裏涉及到兩個參數,串行同步時鐘極性(CPOL)和時鐘相位(CPHA)。如果CPOL=0,串行同步時鐘(SCK)的空閒狀態爲低電平;如果CPOL=1,串行同步時鐘的空閒狀態爲高電平。時鐘相位(CPHA)能夠配置用於選擇兩種不同的傳輸協議之一進行數據傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降沿)數據被採樣;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降沿)數據被採樣。時鐘極性和相位配置正確後,數據才能夠準確的發送和接收。例如從設備在時鐘的上升沿接收數據,那麼主設備的SPI時鐘極性應該配置爲下降沿有效。
SPI Flash存儲器概述
SPI Flash(串行Flash)是通過SPI總線接口進行連續數據傳輸的小尺寸,低功耗的flash 存儲器。串行flash (SPI Flash)比並行flash(PPI Flash)佔用更少的線從系統中傳送數據。對於引腳數目少的串行flash來講,其優勢是減少了系統板的空間,功耗和成本。
串行Flash的主要特徵有:
1)      小尺寸
2)      操作電壓:單個2.7-3.6V 或者 3.0-3.6V 讀和寫操作
3)      4線SPI串行接口結構
4)      連續讀操作一個字節的特徵
5)      低功耗
6)      靈活的擦除能力(Sector/Block/Chip-Erase能力)
7)      快速擦除( Sector-Erase或者Block-Erase: 18ms(典型))
8)      字節編程: 14us(典型)
9)      通過WP#引腳的硬件寫保護


SPI的典型應用中,通信的雙方一個是主(Master),一個是從(slave)。區別是由主設備提供通信時鐘信號SCK給從設備,此外主設備還需要提供一個引腳來驅動Slave的片選信號CS。 主從設備的SO和SI是交叉連接的, 主的SO是數據輸出口要接在從設備的SI上,反之依然。根據這樣的設計, 能做SPI的設備往往是單片機, ARM芯片或者更強一些的CPU什麼,而flash, 網絡芯片或者聲音A/D 和D/A轉換芯片就扮演從設備的角色。主設備提供片選信號來選中從設備和連續時鐘信號來驅動雙方設備的讀寫過程。
由於從設備往往都是廠家設計好的, 主要的用戶工作是如何在主設備上把從設備驅動起來。 這是把我搞的灰頭土臉的地方。以我要驅動Flash爲例, 根據看的文檔,我直覺上知道應該把片選信號先拉低(選中flash),然後在SO上發出控制指令,最後等數據到來。結果我的程序是這樣寫的:
1、初始化SPI控制器,包括波特率設置
2、驅動CS爲低電平選中flash
3、發送控制命令
4、接受數據
結果我收到的數據只有一個字節,內容爲0. 忙了一個早上還是這個結果,搞的我極其鬱悶,嚴重懷疑自己的RP,然後開始懷疑單片機是不是壞的,flash是不是壞的。一圈下來繼續懷疑RP。 最後實在鬱悶,就扛來示波器測波形。 因爲這個玩也不熟悉,因此不敢輕易動,弄壞了把自己賣了才賠的起了(10G的哦)。結果發現430單片機的片選CS信號正常,在數據發送的時候SO口的確有波形輸出,說明輸出是對的。 但是。。。。爲什麼SCK沒有連續時鐘信號輸出 ???? 我立刻理直氣壯認爲 單片機燒了,告訴師兄, 結果師兄暴汗.... :SPI主設備如果不連續輸出數據,就不維護時鐘了。頓時覺得自己長的好白阿。 正確的做法應該在主設備上送完指令後不停的送無用數據讓spi控制器繼續輸出時鐘並且讀取發回來的數據。由於SPI控制器是同步讀取數據的,因此我在發送的同時也讀取數據,因此我送出去一個字節的數據,所以讀回來一個數據,當然這個數據是無用的。
知道問題了,午飯後我把程序改成中斷模式的, 所有的數據發送和接受全部採用中斷。 發送寄存器一空 430就會發一個發送就緒中斷,我在中斷程序中把數組中的命令字發出去,等發完之後就一直髮0x00,維護時鐘,直到發送出去的字節數等於期望收到的數據量。另外一方面當數據收到後430就觸發接收中斷得到數據。 中斷程序把數據讀出來扔到接收數組裏面。 等發送完後要檢查是不是移位寄存器爲空,防止還有數據沒出去, 等空了就拉高片選信號斷開flash. 完成這些後,檢查接收數組,可以看到如果發送命令爲n字節,則前面n字節的數據都是廢的,所以要從n+1的位置來找收到的數據。 圓滿大結局。(其實後面還碰到了波特率不對,結果丟數據的問題,但是很快解決了加上現在寫的手痠,就略過拉)

基於AT91RM9200分析
       Atmel公司的ARM AT系列,其SPI驅動在kernel 2.6.23裏已經包含。如果你打了at91-patch補丁的話,則在內核配置時要小心。在Device Drivers---- > Character devices ---- >取消選中SPI Driver(legacy) for at91rm9200 processor 。同時Device Drivers---- >SPI Support ---- > 選中SPI Support ,Atmel SPI Controler,同時選中 User mode SPI device driver support 。
SPI Driver(legacy) for at91rm9200 processor是保留選項,爲了兼容以前版本。如果同時選中SPI Driver(legacy) for at91rm9200 processor,則在/sys裏無法註冊類spidev,也就無法將設備和驅動聯繫在一起。與現有atmel spi驅動發生衝突。
各選項對應的編譯情況如下:
      

SPI support ---- Config_SPI 開啓SPI功能
      

Debug support for SPI drivers ---- config SPI_DEBUG   開啓SPI debug調試
       ----SPI Master Controller Drivers ---- depends on SPI_MASTER 生成spi.o
       Atmel SPI Controller ---- config SPI_ATMEL 生成atmel_spi.o
       Bitbanging SPI master ---- config SPI_BITBANG 生成spi_bitbang.o
       AT91RM9200 Bitbang SPI Master ---- CONFIG_SPI_AT91 spi_at91_bitbang.o
       ---- SPI Protocol Masters ---- depends on SPI_MASTER
      SPI EEPROMs from most vendors ---- config SPI_AT25 生成at25.o
       User mode SPI device driver support ---- config SPI_SPIDEV 生成spidev.o
總線
註冊SPI總線
#spi.c
       struct bus_type spi_bus_type = {
       .name             = "spi",   // spi總線名稱
       .dev_attrs       = spi_dev_attrs,
       .match           = spi_match_device,
       .uevent           = spi_uevent,
       .suspend = spi_suspend,
       .resume          = spi_resume,
};
spi總線將在sysfs/bus下顯示。
其bus_type 結構表示總線,它的定義在中,如下
struct bus_type {
       const char             * name;
       struct module         * owner;
       struct kset             subsys;
       struct kset             drivers;
       struct kset             devices;
       struct klist             klist_devices;
       struct klist             klist_drivers;
       struct blocking_notifier_head bus_notifier;
       struct bus_attribute * bus_attrs;
       struct device_attribute    * dev_attrs;
       struct driver_attribute    * drv_attrs;
       struct bus_attribute drivers_autoprobe_attr;
       struct bus_attribute drivers_probe_attr;
       int           (*match)(struct device * dev, struct device_driver * drv);
       int           (*uevent)(struct device *dev, char **envp,
                              int num_envp, char *buffer, int buffer_size);
       int           (*probe)(struct device * dev);
       int           (*remove)(struct device * dev);
       void        (*shutdown)(struct device * dev);
       int (*suspend)(struct device * dev, pm_message_t state);
       int (*suspend_late)(struct device * dev, pm_message_t state);
       int (*resume_early)(struct device * dev);
       int (*resume)(struct device * dev);
       unsigned int drivers_autoprobe:1;
};
其中,當一個總線上的新設備或者新驅動被添加時,*match 函數會被調用。如果指定的驅動程序能夠處理指定的設備,該函數返回非零值。
對於spi總線,我們必須調用bus_register(&spi_bus_type)進行註冊。調用如果成功,SPI總線子系統將被添加到系統中,在sysfs的/sys/bus目錄下可以看到。然後,我們就可以向這個總線添加設備了。代碼見下:
static int __init spi_init(void)
{
       int    status;
       buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
       if (!buf) {
              status = -ENOMEM;
              goto err0;
       }
       status = bus_register(&spi_bus_type);
       if (status
              goto err1;
       status = class_register(&spi_master_class);
       if (status
              goto err2;
       return 0;
err2:
       bus_unregister(&spi_bus_type);
err1:
       kfree(buf);
       buf = NULL;
err0:
       return status;
}
設備
spi設備的結構如下:
#spi.h
struct spi_device {
       struct device          dev;
       struct spi_master    *master;
       u32                max_speed_hz;
       u8                  chip_select;
       u8                  mode;
#define    SPI_CPHA     0x01                     /* clock phase */
#define    SPI_CPOL     0x02                     /* clock polarity */
#define    SPI_MODE_0 (0|0)                     /* (original MicroWire) */
#define    SPI_MODE_1 (0|SPI_CPHA)
#define    SPI_MODE_2 (SPI_CPOL|0)
#define    SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define    SPI_CS_HIGH       0x04                     /* chipselect active high? */
#define    SPI_LSB_FIRST    0x08                     /* per-word bits-on-wire */
#define    SPI_3WIRE    0x10                     /* SI/SO signals shared */
#define    SPI_LOOP     0x20                     /* loopback mode */
       u8                  bits_per_word;
       int                  irq;
       void               *controller_state;
       void               *controller_data;
       const char             *modalias;
       /*
        * likely need more hooks for more protocol options affecting how
        * the controller talks to each chip, like:
        * - memory packing (12 bit samples into low bits, others zeroed)
        * - priority
        * - drop chipselect after each word
        * - chipselect delays
        * - ...
        */
};
device結構中包含了設備模型核心用來模擬系統的信息。spidev還有設備的其他信息,因此spi設備結構包含在spidev_data結構裏。
struct spidev_data {
       struct device          dev;
       struct spi_device    *spi;
       struct list_head       device_entry;
       struct mutex          buf_lock;
       unsigned         users;
       u8                  *buffer;
};
註冊spi設備,
#spidev.c
static int spidev_probe(struct spi_device *spi)
{
       …
       …
status = device_register(&spidev->dev);


}
完成這個調用之後,我們就可以在sysfs中看到它了。
SPI設備驅動程序
spi驅動程序結構如下:
struct spi_driver {
       int                  (*probe)(struct spi_device *spi);
       int                  (*remove)(struct spi_device *spi);
       void               (*shutdown)(struct spi_device *spi);
       int                  (*suspend)(struct spi_device *spi, pm_message_t mesg);
       int                  (*resume)(struct spi_device *spi);
       struct device_driver      driver;
};
spi驅動程序註冊函數如下:
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);
}
spidev的驅動名如下:
static struct spi_driver spidev_spi = {
       .driver = {
              .name =          "spidev",
              .owner = THIS_MODULE,
       },
       .probe = spidev_probe,
       .remove =       __devexit_p(spidev_remove),
};
一個spi_register_driver調用將spidev添加到系統中。一旦初始化完成,就可以在sysfs中看到驅動程序信息。

spidev類結構如下:
static struct class spidev_class = {
       .name             = "spidev",
       .owner           = THIS_MODULE,
       .dev_release    = spidev_classdev_release,
};
AT91RM9200 SPIDEV初始化
AT91RM9200的spi驅動,對於EK板,原先的SPI是用於dataflash的。其代碼如下:
static struct spi_board_info ek_spi_devices[] = {
       {     /* DataFlash chip */
              .modalias = "mtd_dataflash",
              .chip_select    = 0,
              .max_speed_hz      = 15 * 1000 * 1000,
       },
我們需要將.modalias改成我們自己的spi設備名
在spi設備初始化代碼中,class_register(&spidev_class)註冊類,spi_register_driver(&spidev_spi)註冊spidev驅動。
#drivers/spi/spidev.c
static int __init spidev_init(void)
{
       int status;
       /* Claim our 256 reserved device numbers. Then register a class
        * that will key udev/mdev to add/remove /dev nodes. Last, register
        * the driver which manages those device numbers.
        */
       BUILD_BUG_ON(N_SPI_MINORS > 256);
       status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
       if (status
              return status;
       status = class_register(&spidev_class);
       if (status
              unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
              return status;
       }
       status = spi_register_driver(&spidev_spi);
       if (status
              class_unregister(&spidev_class);
              unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
       }
       return status;
}
掛載/sys
mount –t sysfs sysfs /sys
可以看到有/sys/class/spidev/spidev0.0,表明設備已經掛載在總線上了,同時與驅動聯繫起來。
使用mdev –s,可以在/dev下看到spidev0.0這個設備了。
自此,spi設備驅動就可以工作了。
測試程序:
#include stdio.h>
#include unistd.h>
#include stdlib.h>
#include fcntl.h>
#include string.h>
#include sys/ioctl.h>
#include sys/types.h>
#include sys/stat.h>
#include linux/types.h>
#include linux/spi/spidev.h>
static int verbose;
static void do_read(int fd, int len)
{
       unsigned char buf[32], *bp;
       int status;
       /* read at least 2 bytes, no more than 32 */
       if (len 2)
              len = 2;
       else if (len > sizeof(buf))
              len = sizeof(buf);
       memset(buf, 0, sizeof buf);
       status = read(fd, buf, len);
       if (status 0) {
              perror("read");
              return;
       }
       if (status != len) {
              fprintf(stderr, "short read/n");
              return;
       }
       printf("read(%2d, %2d): %02x %02x,", len, status,
              buf[0], buf[1]);
       status -= 2;
       bp = buf + 2;
       while (status-- > 0)
              printf(" %02x", *bp++);
       printf("/n");
}
static void do_msg(int fd, int len)
{
       struct spi_ioc_transfer xfer[2];
       unsigned char buf[32], *bp;
       int status;
       memset(xfer, 0, sizeof xfer);
       memset(buf, 0, sizeof buf);
       if (len > sizeof buf)
              len = sizeof buf;
       buf[0] = 0xaa;
       xfer[0].tx_buf = (__u64) buf;
       xfer[0].len = 1;
       xfer[1].rx_buf = (__u64) buf;
       xfer[1].len = len;
       status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
       if (status 0) {
              perror("SPI_IOC_MESSAGE");
              return;
       }
       printf("response(%2d, %2d): ", len, status);
       for (bp = buf; len; len--)
              printf(" %02x", *bp++);
       printf("/n");
}
static void dumpstat(const char *name, int fd)
{
       __u8 mode, lsb, bits;
       __u32 speed;
       if (ioctl(fd, SPI_IOC_RD_MODE, &mode) 0) {
              perror("SPI rd_mode");
              return;
       }
       if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb) 0) {
              perror("SPI rd_lsb_fist");
              return;
       }
       if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) 0) {
              perror("SPI bits_per_word");
              return;
       }
       if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) 0) {
              perror("SPI max_speed_hz");
              return;
       }
       printf("%s: spi mode %d, %d bits %sper word, %d Hz max/n",
              name, mode, bits, lsb ? "(lsb first) " : "", speed);
}
int main(int argc, char **argv)
{
       int c;
       int readcount = 0;
       int msglen = 0;
       int fd;
       const char *name;
       while ((c = getopt(argc, argv, "hm:r:v")) != EOF) {
              switch (c) {
              case 'm':
                     msglen = atoi(optarg);
                     if (msglen 0)
                            goto usage;
                     continue;
              case 'r':
                     readcount = atoi(optarg);
                     if (readcount 0)
                            goto usage;
                     continue;
              case 'v':
                     verbose++;
                     continue;
              case 'h':
              case '?':
usage:
                     fprintf(stderr,
                            "usage: %s [-h] [-m N] [-r N] /dev/spidevB.D/n",
                            argv[0]);
                     return 1;
              }
       }
       if ((optind + 1) != argc)
              goto usage;
       name = argv[optind];
       fd = open(name, O_RDWR);
       if (fd 0) {
              perror("open");
              return 1;
       }
       dumpstat(name, fd);
       if (msglen)
              do_msg(fd, msglen);
       if (readcount)
              do_read(fd, readcount);
       close(fd);
       return 0;
}
備註:
如果要設置模式,速率等,則可仿照以下語句:
speed      =10*1000*1000; //10MHz
if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed)
              perror("SPI max_speed_hz");
              return;
       }
默認spi_io_transfer時,每個字節之間有延時。在atmel_spi_setup.c文件裏去掉該延時語句:
              /* TODO: DLYBS and DLYBCT */
       //csr |= SPI_BF(DLYBS, 10);
       //csr |= SPI_BF(DLYBCT, 10);
這樣就可以達到無間隙快速傳輸批量數據。
標準read(),write()兩個函數僅適用於半雙工傳輸,。在傳輸之間不激活片選。而SPI_IOC_MESSAGE(N)則是全雙工傳輸,並且片選始終激活。
SPI_IOC_MESSAGE傳輸長度有限制,默認是一頁的長度,但是可以更改。
spi_ioc_transfer結構的spi長度 是字節長度,16位傳輸的時候要注意

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章