和I2CBUS类似,SPIBUS上的物理设备,也是分为SPIMASTER和SPISLAVE。
不同的是,SPIBUS上,只能有一个MASTER。
在linux中,对应定义了两种对象,spi_master 和 spi_device。
类似于I2CBUS,linux驱动中,分为master驱动, core驱动,device驱动。
来看看spi_device的结构体。
struct spi_device{
struct device dev;
char modalias[SPI_NAME_SIZE];
struct spi_master * master;
u8 chip_select;
u16 mode;
int irq;
int cs_gpio;
void* controller_state;
void* controller_data;
...
};
SPIDEV内嵌一个DEVICE,是对DEVICE的扩展。
SPIDEV关联到一个MASTER,这是对应的主控器对象。
SPIDEV包含两个通配句柄,用来做实体标记。
类似于I2C,通常不直接构造SPIDEV,而是由内核在启动时,根据DTB来构造。来看一个例子。
spi0:spi@12d20000{
#address-cells= <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi0_bus>;
flash0:w25q80bw@0{
#address-cells = <1>;
#size-cells = <1>;
compatible = "w25x80";
reg = <0>;
spi-max-frequency = <10000>;
controller-data {
cs-gpio = <&gpa2 5 1 0 3>;
samsung,spi-feedback-delay = <0>;
};
};
};
spi0是主控器,重点是pinctrl属性,它指定了主控器所使用的管脚。
flash0是这个主控器所管辖的SPIBUS上的设备,它定义了所需要的硬件资源。
其中,cs-gpio定义了该设备使用主控器所提供的哪一个CSGPIO。
再来看看SPIDRIVER的结构体。类似于platform_driver。
struct spi_driver{
struct device_driver driver;
int (*probe)(sruct spi_device*);
int (*remove)(sruct spi_device*);
const struct spi_device_id* id_tabel;
...
};
SPIDRV内嵌一个DEVDRV,是对DEVDRV的扩展。
SPIDRV关联到一个IDTABLE,用来匹配SPIDEV。
内核提供了SPIDRIVER相关的API。
int spi_register_driver(struct spi_driver* sdrv);
void spi_unregister_driver(struct spi_driver* sdrv);
void spi_set_drvdata(struct spi_device* spi, void* data);
void* spi_get_drvdata(struct spi_device* spi);
通过将指针填充到spi.dev.driver_data,形成回溯引用。例如
spi_set_drvdata(spidev, &spidriver);
那么,从spidev中就能索引到spidriver。
struct spi_driver* sdrv = spi.dev.driver_data;
再来看看SPI传输相关的结构体。SPIBUS是同时收发的,主控发送的同时,从机也在发送,所以,主控也会同时接收从机传来的数据。
struct spi_transfer{
const void *tx_buf;
void* rx_buf;
unsigned len;
};
一个spi_transfer,被称为一个传输事务,多个传输事务,构成一个spi_message。它们在msg中以链表的形式被管理。
我们看到,spi_transfer是不含链节的,所以它不能自成链表。链节包含在msg中。
struct spi_message{
struct list_head transfers;
struct spi_device* spi;
void (*complete)(void* context);
void* context;
...
};
其中包含了一个链节。所以可以构成链表。
它还包含了Callback和CallbackRef。
它关联到一个spi_device,表明这是对应的SPIDEV的消息。
内核提供了相关的API。
void spi_message_init(struct spi_message* m);
初始化一个msg对象。
void spi_message_add_tail(struct spi_transfer* t, struct spi_message* m);
将一个transfer对象标记到msg对象的LIST中。
void spi_transfer_del(struct spi_transfer* t);
从msg对象的LIST中取消对一个transfer对象的标记。
int spi_sync(struct spi_device* spi, struct spi_message* message);
发起一个spi_device上的传输事务链表,并同步等待所有的事务完成。
另外,内核也提供了一些简化的API。
int spi_write(struct spi_device* spi, const void* buf, size_t len);
将buf中的数据,发送到spi设备中。
int spi_read(struct spi_device* spi, void* buf, size_t len);
从spi设备中,读取数据,存放到buf中。
int spi_write_then_read(struct spi_device* spi, const void* txbuf, unsigned n_tx, void* rxbuf, unsigned n_rx);
先将txbuf中的数据发送到spi设备中,然后再从spi设备中,读取数据,存放到rxbuf中。
整个驱动分为几个部分:
1)Derived_DEV_CB定义,并实例化。
2)UADEV的DRIVER的实例化,并填充。
3)UADEV的相关驱动操作函数编写,分为机制性函数的编写和事务性函数的编写。
4)SPIDRIVER的实例化,并填充,注册到内核中。
5)IDTABLE的实例化,注册到内核中。
6)probe函数编写,在其中注册用户可访问设备(User Accessable Device),如CDEV,BDEV,NDEV等。及其对应的DRIVER。
7)remove函数编写,在其中逆操作。
可以看出,与常规的UADEV的编写相比,多了几个部分,就是与BUSDEV相关的DRIVER。
来看一个spi_driver的例子。
struct xxx_dev{
struct cdev cdev;
struct spi_device* spi;
atomic_t available;
};
static xxx_dev* xxx_devp;
static struct file_operations xxx_ops = {
.owner = THIS_MODULE;
.open = xxx_open;
.release = xxx_release,
.read = xxx_read,
.write = xxx_write,
.unlocked_ioctl = xxx_ioctl,
};
...
static ssize_t xxx_read(struct file* filp, char __user* buf, size_t count, loff_t* pos)
{
struct xxx_dev* xxxdevp = filp->private_data;
unsigned char rx_buf[256];
spi_read(xxxdevp->spi, rx_buf, count);
copy_to_user(buf, rx_buf, count);
...
}
static ssize_t xxx_write(struct file* filp, const char __user*buf, size_t count, loff_t* pos)
{
struct xxx_dev* xxxdevp = filp->private_data;
unsigned char tx_buf[256];
copy_from_user(tx_buf, buf, count);
spi_write(xxxdevp->spi, tx_buf, count);
}
static ssize_t xxx_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
{
struct xxx_dev* xxxdevp = filp->private_data;
unsigned char tx_buf[256];
unsigned char rx_buf[256];
struct spi_transfer t ={
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = _IOC_SIZE(cmd)
};
struct spi_message m;
int ret;
switch(cmd){
case XXX_CMD_XXX:
copy_from_user(tx_buf, (void __user*)arg, _IOC_SIZE(cmd));
spi_message_init(&m);
spi_message_add_tail(&t, &m);
ret = spi_sync(xxxdevp->spi, &m);
break;
default:
return -ENOTTY;
}
...
}
static struct spi_driver xxx_driver = {
.driver = {
.name = "xxxdev",
.owner = THIS_MODULE,
},
.probe = xxx_probe,
.remove = xxx_remove,
.id_table = xxx_id_table,
};
module_spi_driver(xxx_driver);
static const struct spi_device_id xxx_id_table[] = {
{
.name = "xxxdev",
},
{}
};
类似于I2C,这个例子里,我们衍生了一个CDEV,作为UADEV。在系统内,有存在SVDEV,用来向衍生的设备提供服务。