Linux驱动分析之SPI控制器

简介: 之前对SPI驱动的整体架构做了介绍,现在来分析具体的驱动程序。之前说过,SPI驱动分为设备驱动和控制器驱动。先来分析控制器驱动。我们以RockChip的控制器来作为分析。

 前言

   之前对SPI驱动的整体架构做了介绍,现在来分析具体的驱动程序。之前说过,SPI驱动分为设备驱动和控制器驱动。先来分析控制器驱动。我们以RockChip的控制器来作为分析。


SPI控制器分析

下面的代码分析主要都在注释中,会按照驱动中函数的执行顺序分析。

(1) 装载和卸载函数

//dts匹配表staticconststructof_device_idrockchip_spi_dt_match[] = {
  { .compatible="rockchip,rv1108-spi", },
  { .compatible="rockchip,rk3036-spi", },
  { .compatible="rockchip,rk3066-spi", },
  { .compatible="rockchip,rk3188-spi", },
  { .compatible="rockchip,rk3228-spi", },
  { .compatible="rockchip,rk3288-spi", },
  { .compatible="rockchip,rk3368-spi", },
  { .compatible="rockchip,rk3399-spi", },
  { },
};
MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match);
staticstructplatform_driverrockchip_spi_driver= {
  .driver= {
    .name=DRIVER_NAME,
    .pm=&rockchip_spi_pm,
    .of_match_table=of_match_ptr(rockchip_spi_dt_match),
  },
  .probe=rockchip_spi_probe,
  .remove=rockchip_spi_remove,
};
//宏封装了platform_driver_register和platform_driver_unregistermodule_platform_driver(rockchip_spi_driver);

image.gif

module_platform_driver宏定义在 include/linux/platform_device.h, 具体看一下源码:

#define module_platform_driver(__platform_driver) \
  module_driver(__platform_driver, platform_driver_register, \
      platform_driver_unregister)
#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

所以其实和我们看到的platform_driver的注册和卸载时一样的,只是进行了封装。


(2) probe()函数

staticintrockchip_spi_probe(structplatform_device*pdev)
{
intret;
structrockchip_spi*rs;
structspi_master*master;
structresource*mem;
u32rsd_nsecs;
//分配一个spi_mastermaster=spi_alloc_master(&pdev->dev, sizeof(structrockchip_spi));
//保存为driver_data, 方便其他地方获取使用platform_set_drvdata(pdev, master);
//获取设备数据,就是driver_datars=spi_master_get_devdata(master);
//获取IO资源mem=platform_get_resource(pdev, IORESOURCE_MEM, 0);
//申请IO资源并进行重映射rs->regs=devm_ioremap_resource(&pdev->dev, mem);
//获取APB时钟(APB时钟)rs->apb_pclk=devm_clk_get(&pdev->dev, "apb_pclk");
//获取spi时钟(APB提供)rs->spiclk=devm_clk_get(&pdev->dev, "spiclk");
//使能APB时钟ret=clk_prepare_enable(rs->apb_pclk);
//使能spi时钟ret=clk_prepare_enable(rs->spiclk);
//关闭spi控制器(设置SSIENR寄存器的值),查看芯片手册spi_enable_chip(rs, 0);
rs->type=SSI_MOTO_SPI; //摩托罗拉SPI协议rs->master=master; //spi_masterrs->dev=&pdev->dev; //devicers->max_freq=clk_get_rate(rs->spiclk); //最大时钟频率//接收采样延迟时间if (!of_property_read_u32(pdev->dev.of_node, "rx-sample-delay-ns",
&rsd_nsecs))
rs->rsd_nsecs=rsd_nsecs;
//FIFO大小rs->fifo_len=get_fifo_len(rs);
spin_lock_init(&rs->lock);
//电源管理pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
master->auto_runtime_pm=true; //自动电源管理master->bus_num=pdev->id; //哪个spi, 比如是SPI1就bus_num=1, SPI2就bus_num=2master->mode_bits=SPI_CPOL|SPI_CPHA|SPI_LOOP; //所支持的模式master->num_chipselect=ROCKCHIP_SPI_MAX_CS_NUM; //片选最大值+1,spi设备的片选值要小于它master->dev.of_node=pdev->dev.of_node;
master->bits_per_word_mask=SPI_BPW_MASK(16) |SPI_BPW_MASK(8);//支持8或16bit//回调函数master->set_cs=rockchip_spi_set_cs; //硬件片选,使用控制器的片选(没使用可以不实现)master->prepare_message=rockchip_spi_prepare_message;//设置spi控制器(传输前的准备)master->unprepare_message=rockchip_spi_unprepare_message; //释放prepare的资源master->transfer_one=rockchip_spi_transfer_one;//传输一个简单的spi_transfermaster->max_transfer_size=rockchip_spi_max_transfer_size;
master->handle_err=rockchip_spi_handle_err;
master->flags=SPI_MASTER_GPIO_SS;
//使用DMArs->dma_tx.ch=dma_request_chan(rs->dev, "tx");
rs->dma_rx.ch=dma_request_chan(rs->dev, "rx");
if (rs->dma_tx.ch&&rs->dma_rx.ch) {
//FIFO的地址rs->dma_tx.addr= (dma_addr_t)(mem->start+ROCKCHIP_SPI_TXDR);
rs->dma_rx.addr= (dma_addr_t)(mem->start+ROCKCHIP_SPI_RXDR);
master->can_dma=rockchip_spi_can_dma;
master->dma_tx=rs->dma_tx.ch;
master->dma_rx=rs->dma_rx.ch;
  }
//注册spi_masterret=devm_spi_register_master(&pdev->dev, master);
return0;
//错误处理//.....returnret;
}

image.gif

上面将一些错误判断及Log信息去掉了,只留下关键的部分。


(3) 传输函数 -- rockchip_spi_transfer_one

staticintrockchip_spi_transfer_one(
structspi_master*master,
structspi_device*spi,
structspi_transfer*xfer)
{
//获取rockchip_spistructrockchip_spi*rs=spi_master_get_devdata(master);
//判断spi当前状态WARN_ON(readl_relaxed(rs->regs+ROCKCHIP_SPI_SSIENR) &&    (readl_relaxed(rs->regs+ROCKCHIP_SPI_SR) &SR_BUSY));
if (!xfer->tx_buf&&!xfer->rx_buf) {
dev_err(rs->dev, "No buffer for transfer\n");
return-EINVAL;
  }
if (xfer->len>ROCKCHIP_SPI_MAX_TRANLEN) {
dev_err(rs->dev, "Transfer is too long (%d)\n", xfer->len);
return-EINVAL;
  }
rs->speed=xfer->speed_hz; //传输速率rs->bpw=xfer->bits_per_word; //8bit或16bitrs->n_bytes=rs->bpw>>3;
//传输的数据rs->tx=xfer->tx_buf;
rs->tx_end=rs->tx+xfer->len;
rs->rx=xfer->rx_buf;
rs->rx_end=rs->rx+xfer->len;
rs->len=xfer->len;
rs->tx_sg=xfer->tx_sg;
rs->rx_sg=xfer->rx_sg;
if (rs->tx&&rs->rx)
rs->tmode=CR0_XFM_TR; //发送并接收elseif (rs->tx)
rs->tmode=CR0_XFM_TO; //只发送elseif (rs->rx)
rs->tmode=CR0_XFM_RO; //只接收/* we need prepare dma before spi was enabled *///是否使用DMAif (master->can_dma&&master->can_dma(master, spi, xfer))
rs->use_dma=true;
elsers->use_dma=false;
//配置spi,对寄存器进行配置rockchip_spi_config(rs);
if (rs->use_dma)
returnrockchip_spi_prepare_dma(rs);
//数据传输returnrockchip_spi_pio_transfer(rs);
}

image.gif

    • rockchip_spi_pio_transfer
    staticintrockchip_spi_pio_transfer(structrockchip_spi*rs)
    {
    intremain=0;
    //使能SPIspi_enable_chip(rs, 1);
    do {
    if (rs->tx) {
    remain=rs->tx_end-rs->tx;
    rockchip_spi_pio_writer(rs); //发送    }
    if (rs->rx) {
    remain=rs->rx_end-rs->rx;
    rockchip_spi_pio_reader(rs); //读取    }
    cpu_relax();
      } while (remain);
    /* If tx, wait until the FIFO data completely. */if (rs->tx)
    wait_for_idle(rs);
    //关闭SPIspi_enable_chip(rs, 0);
    return0;
    }

    image.gif

      • rockchip_spi_pio_writer
      //发送staticvoidrockchip_spi_pio_writer(structrockchip_spi*rs)
      {
      u32max=tx_max(rs);
      u32txw=0;
      while (max--) {
      if (rs->n_bytes==1)
      txw=*(u8*)(rs->tx);
      elsetxw=*(u16*)(rs->tx);
      //写到数据发送寄存器writel_relaxed(txw, rs->regs+ROCKCHIP_SPI_TXDR);
      rs->tx+=rs->n_bytes;
        }
      }
      //读取staticvoidrockchip_spi_pio_reader(structrockchip_spi*rs)
      {
      u32max=rx_max(rs);
      u32rxw;
      while (max--) {
      //读取数据接收寄存器中的数据rxw=readl_relaxed(rs->regs+ROCKCHIP_SPI_RXDR);
      if (rs->n_bytes==1)
      *(u8*)(rs->rx) = (u8)rxw;
      else*(u16*)(rs->rx) = (u16)rxw;
      rs->rx+=rs->n_bytes;
        }
      }

      image.gif

      总结

         每次看到这么多代码分析,很多人肯定都不怎么想看,但是多看几份,你就会发现都是套路。每个Linux版本的结构体可能都会变,但是基本的东西都是不变的。大家可以和之前的SPI驱动架构分析那篇文章一起看。

      相关文章
      |
      16小时前
      |
      缓存 网络协议 算法
      【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
      在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
      |
      1天前
      |
      监控 Unix Linux
      Linux中AWK命令的高级应用与案例分析
      Linux中AWK命令的高级应用与案例分析
      |
      3天前
      |
      监控 Unix Linux
      Linux中AWK命令的高级应用与案例分析
      Linux中AWK命令的高级应用与案例分析
      |
      7天前
      |
      监控 Linux 应用服务中间件
      探索Linux中的`ps`命令:进程监控与分析的利器
      探索Linux中的`ps`命令:进程监控与分析的利器
      |
      7天前
      |
      数据管理 Linux 数据处理
      探索Linux中的printf命令:数据处理与分析的利器
      `printf`是Linux命令行中的文本格式化工具,类似C语言函数,用于数据处理和分析。它接受格式字符串和参数,格式化输出,支持字符串、整数、浮点数等类型。常用格式说明符有%s、%d、%f等。例如,`printf "Hello, %s!\n" "World"`会输出"Hello, World!"。配合字段宽度和对齐,可用于制作表格。在脚本中,printf常与循环和其它命令结合,实现复杂文本操作。
      |
      8天前
      |
      算法 Linux 编译器
      技术笔记:LINUX2.6.32下的进程分析
      技术笔记:LINUX2.6.32下的进程分析
      |
      8天前
      |
      运维 监控 网络协议
      Linux 下的性能监控与分析技巧
      在Linux环境中,命令行工具助力服务器管理和故障排查。通过示例展示如何监控网络、TCP连接、CPU及内存使用。例如,用`netstat`结合`awk`查TOP 20高频率IP访问80端口,识别DDoS迹象;`netstat -nat`统计TCP状态;`ps -aux`排序列出CPU和内存消耗大的进程;`find`加`tar`查找并压缩`.conf`文件。掌握这些命令提升运维效率。
      13 1
      |
      1天前
      |
      Linux 调度
      Linux |使用“at”命令在指定时间运行任务
      Linux |使用“at”命令在指定时间运行任务
      6 0