Linux 2.6下SPI裝置模型
--------基於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 結構表示匯流排,它的定義在<linux/device.h>中,如下
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 < 0)
goto err1;
status = class_register(&spi_master_class);
if (status < 0)
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 < 0)
return status;
status = class_register(&spidev_class);
if (status < 0) {
unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
return status;
}
status = spi_register_driver(&spidev_spi);
if (status < 0) {
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;
}
備忘:
1. 如果要設定模式,速率等,則可仿照以下語句:
speed =10*1000*1000; //10MHz
if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
perror("SPI max_speed_hz");
return;
}
2. 預設spi_io_transfer時,每個位元組之間有延時。在atmel_spi_setup.c檔案裡去掉該延時語句:
/* TODO: DLYBS and DLYBCT */
//csr |= SPI_BF(DLYBS, 10);
//csr |= SPI_BF(DLYBCT, 10);
這樣就可以達到無間隙快速傳輸批量資料。
3. 標準read(),write()兩個函數僅適用於半雙工傳輸,。在傳輸之間不啟用片選。而SPI_IOC_MESSAGE(N)則是全雙工系統傳輸,並且片選始終啟用。
4. SPI_IOC_MESSAGE傳輸長度有限制,預設是一頁的長度,但是可以更改。
spi_ioc_transfer結構的spi長度 是位元組長度,16位傳輸的時候要注意。