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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章