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