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)去对具体芯片设备进行读写。


相关文章
|
3月前
|
存储 IDE Unix
Linux 内核源代码情景分析(四)(上)
Linux 内核源代码情景分析(四)
26 1
Linux 内核源代码情景分析(四)(上)
|
3月前
|
存储 Linux 块存储
Linux 内核源代码情景分析(三)(下)
Linux 内核源代码情景分析(三)
32 4
|
3月前
|
Linux C语言
深度探索Linux操作系统 —— 编译过程分析
深度探索Linux操作系统 —— 编译过程分析
26 2
|
3月前
|
存储 Unix Linux
Linux 内核源代码情景分析(四)(下)
Linux 内核源代码情景分析(四)
23 2
|
2月前
|
存储 传感器 Linux
STM32微控制器为何不适合运行Linux系统的分析
总的来说,虽然技术上可能存在某些特殊情况下将Linux移植到高端STM32微控制器上的可能性,但从资源、性能、成本和应用场景等多个方面考虑,STM32微控制器不适合运行Linux系统。对于需要运行Linux的应用,更适合选择ARM Cortex-A系列处理器的开发平台。
191 0
|
2月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
3月前
|
存储 算法 Unix
Linux 内核源代码情景分析(四)(中)
Linux 内核源代码情景分析(四)
42 0
|
存储 Unix Linux
浅入分析Linux
Linux 操作系统必须完成的两个主要目的 与硬件部分交互, 为包含在硬件平台上的所有底层可编程部件提供服务 为运行在计算机系统上的应用程序(即所谓的用户空间)提供执行环境 一些操作系统运行所有的用户程序都直接与硬件部分进行交互, 比如典型的MS-DOS。
997 0
|
8天前
|
运维 安全 Linux
Linux中传输文件文件夹的10个scp命令
【10月更文挑战第18天】本文详细介绍了10种利用scp命令在Linux系统中进行文件传输的方法,涵盖基础文件传输、使用密钥认证、复制整个目录、从远程主机复制文件、同时传输多个文件和目录、保持文件权限、跨多台远程主机传输、指定端口及显示传输进度等场景,旨在帮助用户在不同情况下高效安全地完成文件传输任务。
84 5
|
8天前
|
Linux
Linux系统之expr命令的基本使用
【10月更文挑战第18天】Linux系统之expr命令的基本使用
36 4