Linux SPI 设备驱动

SPI设备驱动的整体结构

设备驱动的抽象

使用 spi_driver 结构体来表示一个SPI设备驱动。

SPI从设备的抽象

使用 spi_device 结构体来表示SPI总线上匹配到的从设备,通常它被包含在设备的私有结构体中;在设备驱动中操作SPI设备时,需要先获得此结构体的实例。

SPI数据传输的抽象

使用 spi_message 结构体来描述一次完整的SPI传输,它通常包含一个或多个 spi_transfer 结构体。

通过 spi_message_init() 来初始化一个spi_messgae;通过 spi_message_add_tail() 将 spi_transfer 添加到 spi_message 的队列中。

/* liunux/spi/spi.h */

static void spi_message_init(struct spi_message *m);
static void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);

驱动代码分析

init, exit

初始化 spi_driver 结构体

static struct spi_driver xxx_driver = {
	.driver = {
		.name	= "xxx",
		.owner	= THIS_MODULE,
		.pm	= &xxx_pm,
	},
	.probe		= xxx_probe,
	.remove		= __devexit_p(xxx_remove),
};

驱动模块的 init 和 exit 函数

/* linux/spi/spi.h */
int spi_register_driver(struct spi_driver *sdrv);
void spi_unregister_driver(struct spi_driver *sdrv);

static int __init xxx_init(void)
{
    return spi_register_driver(&xxx_driver);
}
static void __exit xxx_exit(void)
{
    spi_unregister_driver(&xxx_driver);
}
moudle_init(xxx_init);
moudle_exit(xxx_exit);

也可以直接使用 module_spi_driver() 宏来注册 spi 驱动

module_spi_driver(xxx_driver);

probe, remove

一个spi外设在设备树中提供其片选序号、数据比特率、SPI传输模式(CPOL、CPHA)等信息,如:

&mcspi1{
	pinctrl-names = "default";
	pinctrl-0 = <&mcspi1_pins>;
	
	
	
	ads7846@0 {
		pinctrl-names = "default";
		pinctrl-0  = <&ads7846_pins>;
		
		compatible = "ti, ads7846";
		vcc-supply = <&ads7846_reg>;

        reg = 0; /* CS0 */
        spi-max-frequency = <1500000>;

        interrupt-parent = <&gpio4>;
        interrupts = <180>;
        pendown-gpio = <&gpio4 180>;
        
        ...
	}
}

SPI 总线将设备与驱动匹配之后,spi_device 结构体被实例化,并且将设备对应的主机控制器填充到其 master 成员, 片选序号填充到 chip_select 成员等。

然后,这个 spi_device 实例被传入其匹配的驱动的 probe 函数,以通过解析设备树完成进一步的设置。

SPI 设备驱动的probe函数和remove函数的原型如下:

static int __devinit xxx_probe(struct spi_device *spi);
static int __devexit xxx_remove(struct spi_device *spi);
probe 函数的主要工作

(1) 根据spi_device结构体的信息,设置spi设备的传输模式、时钟频率等;初始化 spi_message

(2) 分配设备的私有数据结构体(包含spi_device),并将这个私有数据结构体放到 spi_device 的 driver_data 中

(3) 设置和初始化私有数据结构体的其它成员:通常包括向其它子系统注册的成员(即SPI外设本身所属的设备类型),如 input_dev, hwmon 等;以及用于控制并发和同步的结构体,如 mutex, wait_queue 等

(4) 申请相关的硬件资源,如中断、GPIO等

remove 函数的主要工作

(1) 私有数据结构体中的成员的注销及释放

(2) 硬件退出的相关操作,释放硬件资源

(3) 释放私有数据结构体

相关 API
/* drivers/spi/spi.c */

/* 设置SPI模式和时钟频率 */
int spi_setup(struct spi_device *spi);

/* drivers/base/dd.c */
/* dev_set_drvdata(&spi->dev, xxx_data); */
int dev_set_drvdata(struct device *dev, void *data);
/* struct xxx_data *xxx_data = dev_get_drvdata(&spi->dev); */
void *dev_get_drvdata(const struct device *dev);

SPI 数据传输

spi_message 的传输有两种方式:同步和异步,可以调用 spi_sync() 和 spi_async() API来实现

/* drivers/spi/spi.c */

int spi_sync(struct spi_device *spi, struct spi_message *message);
int spi_async(struct spi_device *spi, struct spi_message *message);

spi_message 通常在设备驱动的 porbe 函数中被初始化好,之后在中断服务函数或者其它需要进行SPI传输的函数中调用以上数据传输函数。

也可以使用SPI核心层提供的便捷API来完成数据传输,它们只是将 spi_transfer 和 spi_message 的初始化以及SPI数据传输封装在一起。如以下的 spi_wrie(), spi_read() 和 spi_write_then_read() 函数:

/* linux/spi/spi.h */

/* 写 - 同步传输 */
static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= buf,	//指定传出的数据的缓冲区
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(spi, &m);
}

/* 读 - 同步传输 */
static inline int
spi_read(struct spi_device *spi, void *buf, size_t len)
{
	struct spi_transfer	t = {
			.rx_buf		= buf,	//指定接收的数据的缓冲区
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(spi, &m);
}


/* drivers/spi/spi.c */

/* 先写后读 - 同步传输 */
int spi_write_then_read(struct spi_device *spi,
		const void *txbuf, unsigned n_tx,
		void *rxbuf, unsigned n_rx)
{
	static DEFINE_MUTEX(lock);

	int			status;
	struct spi_message	message;
	struct spi_transfer	x[2];
	u8			*local_buf;

	/* Use preallocated DMA-safe buffer.  We can't avoid copying here,
	 * (as a pure convenience thing), but we can keep heap costs
	 * out of the hot path ...
	 */
	if ((n_tx + n_rx) > SPI_BUFSIZ)
		return -EINVAL;

	spi_message_init(&message);
	memset(x, 0, sizeof x);
	if (n_tx) {
		x[0].len = n_tx;
		spi_message_add_tail(&x[0], &message);
	}
	if (n_rx) {
		x[1].len = n_rx;
		spi_message_add_tail(&x[1], &message);
	}

	/* ... unless someone else is using the pre-allocated buffer */
	if (!mutex_trylock(&lock)) {
		local_buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
		if (!local_buf)
			return -ENOMEM;
	} else
		local_buf = buf;

	memcpy(local_buf, txbuf, n_tx);
	x[0].tx_buf = local_buf;
	x[1].rx_buf = local_buf + n_tx;

	/* do the i/o */
	status = spi_sync(spi, &message);
	if (status == 0)
		memcpy(rxbuf, x[1].rx_buf, n_rx);

	if (x[0].tx_buf == buf)
		mutex_unlock(&lock);
	else
		kfree(local_buf);

	return status;
}
EXPORT_SYMBOL_GPL(spi_write_then_read);

参考源码

linux/spi/spi.h

drivers/spi/spi.c

drivers/input/touchscreen/ads7846.c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值