Linux驱动分析之SPI设备

简介: 前面我们对SPI控制器驱动进行了分析,接下来来分析SPI设备驱动。我们以DS1302驱动作为分析对象。DS1302是一款RTC芯片,估计很多人在学单片机时用到过。RTC芯片算是比较简单的,也方便分析理解。

 前言

前面我们对SPI控制器驱动进行了分析,接下来来分析SPI设备驱动。我们以DS1302驱动作为分析对象。DS1302是一款RTC芯片,估计很多人在学单片机时用到过。RTC芯片算是比较简单的,也方便分析理解。


SPI设备驱动分析

内核:4.20

芯片:DS1302  RTC

下面的代码分析主要都在注释中,会按照驱动中函数的执行顺序分析。我们不需要去关心RTC的具体内容,因为它主要是一些读写寄存器的过程。应主要关注SPI的通信。


(1) 装载和卸载函数

//dts匹配表staticconststructof_device_idds1302_dt_ids[] = {
  { .compatible="maxim,ds1302", },
  { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ds1302_dt_ids);
staticstructspi_driverds1302_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_drivermodule_spi_driver(ds1302_driver);

image.gif

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);

image.gif

所以只是对 spi_register_driverspi_unregister_driver 做了封装。


(2) probe()函数

staticintds1302_probe(structspi_device*spi)
{
structrtc_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;
  } elseif (spi->max_speed_hz>2000000) {//检查最大速率dev_err(&spi->dev, "speed is too high\n");
return-EINVAL;
  } elseif (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);
//注册rtcrtc=devm_rtc_device_register(&spi->dev, "ds1302",
&ds1302_rtc_ops, THIS_MODULE);
return0;
}

image.gif

(3) RTC设置和读取函数

//读取时间staticintds1302_rtc_get_time(structdevice*dev, structrtc_time*time)
{
structspi_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)
returnstatus;
/* 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;
return0;
}
//设置时间staticintds1302_rtc_set_time(structdevice*dev, structrtc_time*time)
{
structspi_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)
returnstatus;
/* 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;
//只有写,没有读returnspi_write_then_read(spi, buf, sizeof(buf),
NULL, 0);
}
staticconststructrtc_class_opsds1302_rtc_ops= {
  .read_time=ds1302_rtc_get_time,
  .set_time=ds1302_rtc_set_time,
};

image.gif

上面读取和设置都是调用spi_write_then_read来进行Spi通信,这个是Linux帮我们封装好的接口函数。看一下具体实现:

intspi_write_then_read(structspi_device*spi,
constvoid*txbuf, unsignedn_tx,
void*rxbuf, unsignedn_rx)
{
staticDEFINE_MUTEX(lock);
intstatus;
structspi_messagemessage;
structspi_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_messagespi_message_init(&message);
//将要传的数据放到spi_transfer,然后追加到spi_messagememset(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);
elsekfree(local_buf);
returnstatus;
}

image.gif

spi_sync最终会调用spi_master->transfer();传递给spi_sync函数的参数中有spi_device, 而spi_device中又包含spi_master的指针。所以就能找到了对应的spi控制器进行数据发送。


总结

大部分的SPI设备驱动框架都差不多,大家可以配合下面两篇文章一起看。这样更能理解。我们会发现,SPI设备驱动内容其实就是使用SPI控制器(spi_master)去对具体芯片设备进行读写。


相关文章
|
6天前
|
缓存 网络协议 Linux
PCIe 以太网芯片 RTL8125B 的 spec 和 Linux driver 分析备忘
本文详细介绍了 Realtek RTL8125B PCIe 以太网芯片的规格以及在 Linux 中的驱动安装和配置方法。通过深入分析驱动源码,可以更好地理解其工作原理和优化方法。在实际应用中,合理配置和优化驱动程序可以显著提升网络性能和稳定性。希望本文能帮助您更好地使用和管理 RTL8125B,以满足各种网络应用需求。
52 33
|
2月前
|
存储 运维 监控
Linux--深入理与解linux文件系统与日志文件分析
深入理解 Linux 文件系统和日志文件分析,对于系统管理员和运维工程师来说至关重要。文件系统管理涉及到文件的组织、存储和检索,而日志文件则记录了系统和应用的运行状态,是排查故障和维护系统的重要依据。通过掌握文件系统和日志文件的管理和分析技能,可以有效提升系统的稳定性和安全性。
57 7
|
2月前
|
监控 安全 Linux
启用Linux防火墙日志记录和分析功能
为iptables启用日志记录对于监控进出流量至关重要
|
3月前
|
缓存 算法 Linux
Linux内核中的调度策略优化分析####
本文深入探讨了Linux操作系统内核中调度策略的工作原理,分析了不同调度算法(如CFS、实时调度)在多核处理器环境下的性能表现,并提出了针对高并发场景下调度策略的优化建议。通过对比测试数据,展示了调度策略调整对于系统响应时间及吞吐量的影响,为系统管理员和开发者提供了性能调优的参考方向。 ####
|
6月前
|
Linux C语言
深度探索Linux操作系统 —— 编译过程分析
深度探索Linux操作系统 —— 编译过程分析
50 2
|
5月前
|
存储 传感器 Linux
STM32微控制器为何不适合运行Linux系统的分析
总的来说,虽然技术上可能存在某些特殊情况下将Linux移植到高端STM32微控制器上的可能性,但从资源、性能、成本和应用场景等多个方面考虑,STM32微控制器不适合运行Linux系统。对于需要运行Linux的应用,更适合选择ARM Cortex-A系列处理器的开发平台。
350 0
|
5月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】