linux驱动(第十六课,SPIBUS)

和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,用来向衍生的设备提供服务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值