- 論壇徽章:
- 0
|
Linux 2.6下SPI設備模型
--------基于AT91RM9200分析
Atmel公司的ARM AT系列,其SPI驅動在kernel 2.6.23里已經(jīng)包含。如果你打了at91-patch補丁的話,則在內(nèi)核配置時要小心。在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,也就無法將設備和驅動聯(lián)系在一起。與現(xiàn)有atmel spi驅動發(fā)生沖突。
各選項對應的編譯情況如下:
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 函數(shù)會被調用。如果指定的驅動程序能夠處理指定的設備,該函數(shù)返回非零值。
對于spi總線,我們必須調用bus_register(&spi_bus_type)進行注冊。調用如果成功,SPI總線子系統(tǒng)將被添加到系統(tǒng)中,在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結構中包含了設備模型核心用來模擬系統(tǒng)的信息。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驅動程序注冊函數(shù)如下:
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添加到系統(tǒng)中。一旦初始化完成,就可以在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,表明設備已經(jīng)掛載在總線上了,同時與驅動聯(lián)系起來。
使用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時,每個字節(jié)之間有延時。在atmel_spi_setup.c文件里去掉該延時語句:
/* TODO: DLYBS and DLYBCT */
//csr |= SPI_BF(DLYBS, 10);
//csr |= SPI_BF(DLYBCT, 10);
這樣就可以達到無間隙快速傳輸批量數(shù)據(jù)。
標準read(),write()兩個函數(shù)僅適用于半雙工傳輸,。在傳輸之間不激活片選。而SPI_IOC_MESSAGE(N)則是全雙工傳輸,并且片選始終激活。
SPI_IOC_MESSAGE傳輸長度有限制,默認是一頁的長度,但是可以更改。
spi_ioc_transfer結構的spi長度 是字節(jié)長度,16位傳輸?shù)臅r候要注意。
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u3/110644/showart_2167176.html |
|