Linux驱动分析之SPI设备

临文乍了了,彻卷兀若无。这篇文章主要讲述Linux驱动分析之SPI设备相关的知识,希望能为你提供帮助。


前言前面我们对SPI控制器驱动进行了分析,接下来来分析SPI设备驱动。我们以DS1302驱动作为分析对象。DS1302是一款RTC芯片,估计很多人在学单片机时用到过。RTC芯片算是比较简单的,也方便分析理解。
SPI设备驱动分析内核:4.20
芯片:DS1302RTC
下面的代码分析主要都在注释中,会按照驱动中函数的执行顺序分析。我们不需要去关心RTC的具体内容,因为它主要是一些读写寄存器的过程。应主要关注SPI的通信。
(1) 装载和卸载函数

//dts匹配表
static const struct of_device_id ds1302_dt_ids[] = {
{ .compatible = "maxim,ds1302", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ds1302_dt_ids);


static struct spi_driver ds1302_driver = {
.driver.name= "rtc-ds1302",
.driver.of_match_table = of_match_ptr(ds1302_dt_ids),
.probe= ds1302_probe,
.remove= ds1302_remove,
};
//封装了spi_register_driver和spi_unregister_driver
module_spi_driver(ds1302_driver);

module_spi_driver宏定义在 include/linux/spi/spi.h, 具体看一下源码
#define module_spi_driver(__spi_driver) \\
module_driver(__spi_driver, spi_register_driver, \\
spi_unregister_driver)

#define module_driver(__driver, __register, __unregister, ...) \\
static int __init __driver##_init(void) \\
{ \\
return __register(& (__driver) , ##__VA_ARGS__); \\
} \\
module_init(__driver##_init); \\
static void __exit __driver##_exit(void) \\
{ \\
__unregister(& (__driver) , ##__VA_ARGS__); \\
} \\
module_exit(__driver##_exit);

所以只是对 spi_register_driver 和 spi_unregister_driver 做了封装。
(2) probe()函数
static int ds1302_probe(struct spi_device *spi)
{
struct rtc_device*rtc;
u8addr;
u8buf[4];
u8*bp;
intstatus;


//检查是不是8bit传输
if (spi-> bits_per_word & & (spi-> bits_per_word != 8)) {
dev_err(& spi-> dev, "bad word length\\n");
return -EINVAL;
} else if (spi-> max_speed_hz > 2000000) {//检查最大速率
dev_err(& spi-> dev, "speed is too high\\n");
return -EINVAL;
} else if (spi-> mode & SPI_CPHA) {
dev_err(& spi-> dev, "bad mode\\n");
return -EINVAL;
}
//使用spi读写一下寄存器,检查是否可以写(DS1302有个寄存器是设置写保护的)
addr = RTC_ADDR_CTRL < < 1 | RTC_CMD_READ;
status = spi_write_then_read(spi, & addr, sizeof(addr), buf, 1);
......


spi_set_drvdata(spi, spi);
//注册rtc
rtc = devm_rtc_device_register(& spi-> dev, "ds1302",
& ds1302_rtc_ops, THIS_MODULE);


return 0;
}

(3) RTC设置和读取函数
//读取时间
static int ds1302_rtc_get_time(struct device *dev, struct rtc_time *time)
{
struct spi_device*spi = dev_get_drvdata(dev);
u8addr = RTC_CLCK_BURST < < 1 | RTC_CMD_READ;
u8buf[RTC_CLCK_LEN - 1];
intstatus;


//spi读取时间
status = spi_write_then_read(spi, & addr, sizeof(addr),
buf, sizeof(buf));
if (status < 0)
return status;


/* Decode the registers */
time-> tm_sec = bcd2bin(buf[RTC_ADDR_SEC]);
time-> tm_min = bcd2bin(buf[RTC_ADDR_MIN]);
time-> tm_hour = bcd2bin(buf[RTC_ADDR_HOUR]);
time-> tm_wday = buf[RTC_ADDR_DAY] - 1;
time-> tm_mday = bcd2bin(buf[RTC_ADDR_DATE]);
time-> tm_mon = bcd2bin(buf[RTC_ADDR_MON]) - 1;
time-> tm_year = bcd2bin(buf[RTC_ADDR_YEAR]) + 100;


return 0;
}
//设置时间
static int ds1302_rtc_set_time(struct device *dev, struct rtc_time *time)
{
struct spi_device*spi = dev_get_drvdata(dev);
u8buf[1 + RTC_CLCK_LEN];
u8*bp;
intstatus;


/* Enable writing */
bp = buf;
*bp++ = RTC_ADDR_CTRL < < 1 | RTC_CMD_WRITE;
*bp++ = RTC_CMD_WRITE_ENABLE;
//关闭写保护
status = spi_write_then_read(spi, buf, 2,
NULL, 0);
if (status)
return status;


/* Write registers starting at the first time/date address. */
bp = buf;
*bp++ = RTC_CLCK_BURST < < 1 | RTC_CMD_WRITE;


*bp++ = bin2bcd(time-> tm_sec);
*bp++ = bin2bcd(time-> tm_min);
*bp++ = bin2bcd(time-> tm_hour);
*bp++ = bin2bcd(time-> tm_mday);
*bp++ = bin2bcd(time-> tm_mon + 1);
*bp++ = time-> tm_wday + 1;
*bp++ = bin2bcd(time-> tm_year % 100);
*bp++ = RTC_CMD_WRITE_DISABLE;


//只有写,没有读
return spi_write_then_read(spi, buf, sizeof(buf),
NULL, 0);
}


static const struct rtc_class_ops ds1302_rtc_ops = {
.read_time= ds1302_rtc_get_time,
.set_time= ds1302_rtc_set_time,
};

上面读取和设置都是调用spi_write_then_read来进行Spi通信,这个是Linux帮我们封装好的接口函数。看一下具体实现:
int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx)
{
static DEFINE_MUTEX(lock);


intstatus;
struct spi_messagemessage;
struct spi_transferx[2];
u8*local_buf;


if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(& lock)) {
local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx),
GFP_KERNEL | GFP_DMA);
if (!local_buf)
return -ENOMEM;
} else {
local_buf = buf;
}
//初始化spi_message
spi_message_init(& message);
//将要传的数据放到spi_transfer,然后追加到spi_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);
}


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


//进行SPI发送
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;
}

spi_sync最终会调用spi_master-> transfer();传递给spi_sync函数的参数中有spi_device, 而spi_device中又包含spi_master的指针。所以就能找到了对应的spi控制器进行数据发送。
总结大部分的SPI设备驱动框架都差不多,大家可以配合下面两篇文章一起看。这样更能理解。我们会发现,SPI设备驱动内容其实就是使用SPI控制器(spi_master)去对具体芯片设备进行读写。


Linux驱动分析之SPI设备

文章图片



【Linux驱动分析之SPI设备】


    推荐阅读