I2C驱动框架分析(1):I2C重要概念与数据结构
I2C驱动框架分析(2):I2C框架源码分析
I2C驱动框架分析(3):DW_I2C驱动分析

第三章:DW_I2C驱动

其驱动文件在drivers/i2c/busses/i2c-designware-platdrv.c

3.1 I2C_platform驱动

dw_i2c的platform驱动定义为:

static struct platform_driver dw_i2c_driver = {.probe = dw_i2c_plat_probe,.remove = dw_i2c_plat_remove,.driver        = {.name   = "i2c_designware",.of_match_table = of_match_ptr(dw_i2c_of_match),.acpi_match_table = ACPI_PTR(dw_i2c_acpi_match),.pm = DW_I2C_DEV_PMOPS,},
};

调用platform_driver_register(&dw_i2c_driver)函数注册到platform总线上,platform_driver_register函数流程( 矢量图可放大查看):

以上流程主要工作是,将I2C_platform驱动进行注册,并进行device与driver匹配,最后执行probe函数。上述流程适用与所有的paltform驱动

3.2 DW_I2C probe函数

当device与driver匹配完成后,执行probe函数,DW_I2C probe函数为dw_i2c_plat_probe,函数主要流程为:

通过调用i2c_detect_slave_mode函数来判断控制器主从模式,其内容主要为:

//读取i2c设备树子节点reg属性
of_property_read_u32(child, "reg", &reg)//地址高bit30为1,即为从
reg & I2C_OWN_SLAVE_ADDRESS

【关于dw_i2c速率】

dw_i2c速率赋值有两种方法:

第一种是通过struct dw_i2c_platform_data *pdata = dev_get_platdata(&pdev->dev)来赋值

第二种是调用i2c_parse_fw_timings函数,从设备树解析clock-frequency属性来赋值

3.3 DW_I2C主从模式设置

从之前的内容得知,通过从i2c设备树子节点地址来判断i2c控制器的主从设置。

3.3.1DW_I2C主机模式设置

调用i2c_dw_configure_master函数完成,其函数内容为:

static void i2c_dw_configure_master(struct dw_i2c_dev *dev)
{struct i2c_timings *t = &dev->timings;dev->functionality = I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY;dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE |DW_IC_CON_RESTART_EN;dev->mode = DW_IC_MASTER;switch (t->bus_freq_hz) {case 100000:dev->master_cfg |= DW_IC_CON_SPEED_STD;break;case 3400000:dev->master_cfg |= DW_IC_CON_SPEED_HIGH;break;default:dev->master_cfg |= DW_IC_CON_SPEED_FAST;}
}

上述主要工作为:

  1. 设置functionality属性;
  2. 设置主机标志位;
  3. 设置传输速率。
3.3.2 DW_I2C从机模式设置

调用i2c_dw_configure_slave函数实现从机模式设置,函数为:

static void i2c_dw_configure_slave(struct dw_i2c_dev *dev)
{dev->functionality = I2C_FUNC_SLAVE | DW_IC_DEFAULT_FUNCTIONALITY;dev->slave_cfg = DW_IC_CON_RX_FIFO_FULL_HLD_CTRL |DW_IC_CON_RESTART_EN | DW_IC_CON_STOP_DET_IFADDRESSED;dev->mode = DW_IC_SLAVE;
}

上述主要工作为:

  1. 设置functionality属性;
  2. 设置从机机标志位;
  3. 设置从机模式。

3.4 DW_I2C主机probe函数

主机probe函数由i2c_dw_probe,完成主机模式功能配置,函数的流程如下图所示:

上述流程中主要工作为:

  1. 主机模式相关硬件初始化;
  2. 例化适配器成员变量,提供i2c_dw_algo接口;
  3. 提供中断服务函数;
  4. 向bus添加adapter的id,并注册adapter设备.

在上面的工作中,最重要的是第2点与第3点,下面将详细介绍。

3.4.1 传输变量i2c_dw_algo

i2c_dw_algostruct i2c_algorithm结构体类型变量,其内容为:

static const struct i2c_algorithm i2c_dw_algo = {.master_xfer = i2c_dw_xfer,.functionality = i2c_dw_func,
};

i2c_dw_xfer钩子函数的形参为:

  • adap: 当前传递的适配器;
  • msgs: 要传递的消息(数据)内容;
  • num: 消息的长度。

函数的主要内容如下:

static int i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{struct dw_i2c_dev *dev = i2c_get_adapdata(adap);int ret;...//例化dw_i2c_dev设备成员变量dev->msgs = msgs;dev->msgs_num = num;dev->cmd_err = 0;dev->msg_write_idx = 0;dev->msg_read_idx = 0;dev->msg_err = 0;dev->status = STATUS_IDLE;dev->abort_source = 0;dev->rx_outstanding = 0;ret = i2c_dw_acquire_lock(dev);if (ret)goto done_nolock;//判断当前设备是否busyret = i2c_dw_wait_bus_not_busy(dev);if (ret < 0)goto done;//开始传输消息i2c_dw_xfer_init(dev);/* Wait for tx to complete */if (!wait_for_completion_timeout(&dev->cmd_complete, adap->timeout)) {dev_err(dev->dev, "controller timed out\n");/* i2c_dw_init implicitly disables the adapter */i2c_recover_bus(&dev->adapter);i2c_dw_init_master(dev);ret = -ETIMEDOUT;goto done;}...
}

上述函数中主要工作为:

  1. 例化dw_i2c_dev设备成员变量,特别是dev->msgs;
  2. 判断当前设备是否busy;
  3. 设置传输时的参数,比如中断和从机地址。

其中第3点调用i2c_dw_xfer_init函数实现,其把内容为:

static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
{struct i2c_msg *msgs = dev->msgs;u32 ic_con, ic_tar = 0;//关闭i2c__i2c_dw_disable(dev);//读取i2c硬件配置信息,并设置从机地址ic_con = dw_readl(dev, DW_IC_CON);if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) {//10bit从机地址ic_con |= DW_IC_CON_10BITADDR_MASTER;ic_tar = DW_IC_TAR_10BITADDR_MASTER;} else {ic_con &= ~DW_IC_CON_10BITADDR_MASTER;}dw_writel(dev, ic_con, DW_IC_CON);dw_writel(dev, msgs[dev->msg_write_idx].addr | ic_tar, DW_IC_TAR);//关闭中断i2c_dw_disable_int(dev);//使能i2c__i2c_dw_enable(dev);/* Dummy read to avoid the register getting stuck on Bay Trail */dw_readl(dev, DW_IC_ENABLE_STATUS);//使能中断dw_readl(dev, DW_IC_CLR_INTR);dw_writel(dev, DW_IC_INTR_MASTER_MASK, DW_IC_INTR_MASK);
}

由于本驱动在中断中处理数据接受与发送,在最后的使能中断,其使能的中断包括如下:

DW_IC_INTR_TX_EMPTY
DW_IC_INTR_RX_FULL
DW_IC_INTR_TX_ABRT
DW_IC_INTR_STOP_DET

【注释】

在很多i2c的ip驱动中,master_xfer函数指针都用于实现数据传输,在dw_i2c驱动中,master_xfer用于设置传输时的参数,真正数据传输依靠中断服务函数实现。

3.4.2 中断服务函数i2c_dw_isr

在中断服务函数i2c_dw_isr中,实际调用函数i2c_dw_irq_handler_master来实现,其函数主要内容为:

static int i2c_dw_irq_handler_master(struct dw_i2c_dev *dev)
{u32 stat;//获取中断类型stat = i2c_dw_read_clear_intrbits(dev);if (stat & DW_IC_INTR_TX_ABRT) {dev->cmd_err |= DW_IC_ERR_TX_ABRT;dev->status = STATUS_IDLE;dw_writel(dev, 0, DW_IC_INTR_MASK);goto tx_aborted;}//读中断if (stat & DW_IC_INTR_RX_FULL)i2c_dw_read(dev);//发中断if (stat & DW_IC_INTR_TX_EMPTY)i2c_dw_xfer_msg(dev);...return 0;
}
  • 读中断处理,当接受FIFO满时,触发接受中断由i2c_dw_read函数实现读,其内容为:

    static void i2c_dw_read(struct dw_i2c_dev *dev)
    {struct i2c_msg *msgs = dev->msgs;int rx_valid;for (; dev->msg_read_idx < dev->msgs_num; dev->msg_read_idx++) {u32 len;u8 *buf;if (!(msgs[dev->msg_read_idx].flags & I2C_M_RD))continue;if (!(dev->status & STATUS_READ_IN_PROGRESS)) {len = msgs[dev->msg_read_idx].len;buf = msgs[dev->msg_read_idx].buf;} else {len = dev->rx_buf_len;buf = dev->rx_buf;}//读有效数据个数rx_valid = dw_readl(dev, DW_IC_RXFLR);for (; len > 0 && rx_valid > 0; len--, rx_valid--) {u32 flags = msgs[dev->msg_read_idx].flags;//读数据*buf = dw_readl(dev, DW_IC_DATA_CMD);/* Ensure length byte is a valid value */if (flags & I2C_M_RECV_LEN &&*buf <= I2C_SMBUS_BLOCK_MAX && *buf > 0) {len = i2c_dw_recv_len(dev, *buf);}buf++;dev->rx_outstanding--;}if (len > 0) {dev->status |= STATUS_READ_IN_PROGRESS;dev->rx_buf_len = len;dev->rx_buf = buf;return;} elsedev->status &= ~STATUS_READ_IN_PROGRESS;}
    }
    
  • 发中断处理。由函数i2c_dw_xfer_msg实现,其内容为:

    static void
    i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
    {struct i2c_msg *msgs = dev->msgs;u32 intr_mask;int tx_limit, rx_limit;u32 addr = msgs[dev->msg_write_idx].addr;u32 buf_len = dev->tx_buf_len;u8 *buf = dev->tx_buf;bool need_restart = false;intr_mask = DW_IC_INTR_MASTER_MASK;for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) {u32 flags = msgs[dev->msg_write_idx].flags;if (msgs[dev->msg_write_idx].addr != addr) {dev_err(dev->dev,"%s: invalid target address\n", __func__);dev->msg_err = -EINVAL;break;}if (!(dev->status & STATUS_WRITE_IN_PROGRESS)) {/* new i2c_msg */buf = msgs[dev->msg_write_idx].buf;buf_len = msgs[dev->msg_write_idx].len;if ((dev->master_cfg & DW_IC_CON_RESTART_EN) &&(dev->msg_write_idx > 0))need_restart = true;}tx_limit = dev->tx_fifo_depth - dw_readl(dev, DW_IC_TXFLR);rx_limit = dev->rx_fifo_depth - dw_readl(dev, DW_IC_RXFLR);while (buf_len > 0 && tx_limit > 0 && rx_limit > 0) {u32 cmd = 0;if (dev->msg_write_idx == dev->msgs_num - 1 &&buf_len == 1 && !(flags & I2C_M_RECV_LEN))cmd |= BIT(9);if (need_restart) {cmd |= BIT(10);need_restart = false;}if (msgs[dev->msg_write_idx].flags & I2C_M_RD) {/* Avoid rx buffer overrun */if (dev->rx_outstanding >= dev->rx_fifo_depth)break;dw_writel(dev, cmd | 0x100, DW_IC_DATA_CMD);rx_limit--;dev->rx_outstanding++;} else{//发送数据dw_writel(dev, cmd | *buf++, DW_IC_DATA_CMD);}tx_limit--; buf_len--;}dev->tx_buf = buf;dev->tx_buf_len = buf_len;if (buf_len > 0 || flags & I2C_M_RECV_LEN) {/* more bytes to be written */dev->status |= STATUS_WRITE_IN_PROGRESS;break;} elsedev->status &= ~STATUS_WRITE_IN_PROGRESS;}if (dev->msg_write_idx == dev->msgs_num)intr_mask &= ~DW_IC_INTR_TX_EMPTY;if (dev->msg_err)intr_mask = 0;//清除发送中断dw_writel(dev, intr_mask,  DW_IC_INTR_MASK);
    }
    

    【注释】:

    dw_i2c驱动使用中断的方式来传输数据,其发送中断的大致机制为:当TX_FIFO里的数据量低于FIFO的阈值,则会触发中断。

    在驱动中实现机制是:系统上电时,没有使能发送中断;当有数据发送时,调用到master_xfer函数指针时,会进行发送中断使能,此时TX_FIFO为空,发送中断触发,进入中断服务函数中进行数据发送处理。

3.5 DW_I2C从机probe函数

从机probe函数由i2c_dw_probe_slave实现,完成从机配置,其函数流程为:

3.5.1 传输变量i2c_dw_algo

在例化adapter成员变量时,i2c_dw_algo如下:

static const struct i2c_algorithm i2c_dw_algo = {.functionality = i2c_dw_func,.reg_slave = i2c_dw_reg_slave,.unreg_slave = i2c_dw_unreg_slave,
};

由于作为从机,i2c_dw_algo变量没有提供传输函数,但是在i2c_dw_reg_slave中,实现作为从机时其从机地址的设置。

【注释】

i2c_dw_reg_slave函数中,设置从机地址时,实际是被i2c_slave_register调用。

i2c_slave_register函数在添加i2c设备时,设备驱动会使用到,比如eeprom驱动。

因此在控制器作为从机设置从机地址时,设置方法如下所示:

i2c0: i2c@1cc30000 {#address-cells = <1>;#size-cells = <0>;compatible = "snps,designware-i2c";reg = <0x1cc30000 0x1000>;interrupt-parent = <&gic>;interrupts = <0 117 IRQ_TYPE_LEVEL_HIGH>;clock-names = "ic_clk", "pclk";clocks = <&sysclk1>, <&sysclk1>;clock-frequency = <100000>;status = "okay";//test for i2c slaveeeprom@64{             compatible = "slave-24c02";reg = <0x40000064>;  //控制器从机地址为0x64};};
3.5.2 中断服务函数i2c_dw_isr_slave

i2c_dw_isr_slave中断服务函数中,实际调用的是i2c_dw_irq_handler_slave,函数内容为:

static int i2c_dw_irq_handler_slave(struct dw_i2c_dev *dev)
{u32 raw_stat, stat, enabled;u8 val, slave_activity;stat = dw_readl(dev, DW_IC_INTR_STAT);enabled = dw_readl(dev, DW_IC_ENABLE);raw_stat = dw_readl(dev, DW_IC_RAW_INTR_STAT);slave_activity = ((dw_readl(dev, DW_IC_STATUS) &DW_IC_STATUS_SLAVE_ACTIVITY) >> 6);...if (stat & DW_IC_INTR_RD_REQ) { //接受到读请求if (slave_activity) {if (stat & DW_IC_INTR_RX_FULL) {val = dw_readl(dev, DW_IC_DATA_CMD);  //将FIFO数据读取出来if (!i2c_slave_event(dev->slave,I2C_SLAVE_WRITE_RECEIVED,&val)) {dev_vdbg(dev->dev, "Byte %X acked!",val);}dw_readl(dev, DW_IC_CLR_RD_REQ);stat = i2c_dw_read_clear_intrbits_slave(dev);} else {dw_readl(dev, DW_IC_CLR_RD_REQ);dw_readl(dev, DW_IC_CLR_RX_UNDER);stat = i2c_dw_read_clear_intrbits_slave(dev);}if (!i2c_slave_event(dev->slave,I2C_SLAVE_READ_REQUESTED,&val))dw_writel(dev, val, DW_IC_DATA_CMD);}}if (stat & DW_IC_INTR_RX_DONE) {if (!i2c_slave_event(dev->slave, I2C_SLAVE_READ_PROCESSED,&val))dw_readl(dev, DW_IC_CLR_RX_DONE);i2c_slave_event(dev->slave, I2C_SLAVE_STOP, &val);stat = i2c_dw_read_clear_intrbits_slave(dev);return 1;}if (stat & DW_IC_INTR_RX_FULL) {val = dw_readl(dev, DW_IC_DATA_CMD);if (!i2c_slave_event(dev->slave, I2C_SLAVE_WRITE_RECEIVED,&val))dev_vdbg(dev->dev, "Byte %X acked!", val);} else {i2c_slave_event(dev->slave, I2C_SLAVE_STOP, &val);stat = i2c_dw_read_clear_intrbits_slave(dev);}return 1;
}

在处理读写数据时,使用i2c_slave_event函数进行处理,下一小节将介绍。

3.5.3 I2C slave events

在上一小节中,多次调用i2c_slave_event函数,这是i2c控制器作为从机时的事件机制,函数原型为:

static inline int i2c_slave_event(struct i2c_client *client,enum i2c_slave_event event, u8 *val)

client 描述I2C slave设备。 event 是下面描述的特殊事件类型之一。 val 为要读/写的数据字节保存一个u8值,因此是双向的。即使val不用于事件,也必须始终提供指向val的指针,即不要在这里使用NULL。

event事件类型有:

  • I2C_SLAVE_WRITE_REQUESTEDval未使用, ret总是 0。

    另一个I2C master想要向我们写入数据。一旦检测到我们自己的地址和写位,就应该发送这个事件。数据还没有到达,所以没有需要处理或返回的内容。

  • I2C_SLAVE_READ_REQUESTEDval返回要发送的第一个字节, ret总是 0。

    另一个I2C master想从我们这里读取数据。一旦检测到我们自己的地址和读位,就应该发送此事件。返回后,总线驱动程序应该发送第一个字节。

  • I2C_SLAVE_WRITE_RECEIVEDval总线驱动发送接收的字节, ret0表示该字节被ACK,errno表示该字节被NACK。

    另一个I2C master发送了一个字节给我们,需要在val中设置。如果ret为零,总线驱动程序应该ack这个字节。如果ret是errno,则该字节应该被删除。

  • I2C_SLAVE_READ_PROCESSEDval返回要发送的下一个字节, ret总是 0。

    总线驱动请求将下一个字节以val 的形式发送给另一个I2C master。

  • I2C_SLAVE_STOPval未使用, ret总是 0。

    接收到停止条件。

【注释】

具体的事件行为由i2c设备实现。

I2C驱动框架分析(3):DW_I2C驱动分析相关推荐

  1. hisi3516dv300芯片基于hwmon驱动框架的温度获取驱动源码分析

    1.内核hwmon驱动框架 参考博客:<内核hwmon驱动框架详解以及海思芯片温度驱动分析>: 2.驱动实现的效果 /sys/devices/virtual/hwmon/hwmon0 # ...

  2. 驱动框架入门之LED-linux驱动开发第4部分-朱有鹏-专题视频课程

    驱动框架入门之LED-linux驱动开发第4部分-5199人已学习 课程介绍         本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入.通过led驱动框架和gpiolib的这两 ...

  3. 《Linux驱动:使用音频设备驱动框架-OSS构建音频设备驱动》

    文章目录 一,前言 二,框架 三,OSS实现 3.1 OSS初始化 3.2 向OSS注册音频设备 3.3 OSS管理音频设备 四,音频基本概念 4.1 采样频率 4.2 采样精度 4.3 左声道/右声 ...

  4. Linux驱动框架之v4l2视频驱动框架解析

    1.简介 v4l2是专门为linux设备设计的一套视频框架,其主体框架在linux内核,可以理解为是整个 linux 系统上面的视频源捕获驱动框架.其广泛应用在嵌入式设备以及移动端.个人电脑设备上面, ...

  5. 驱动框架1——什么是驱动框架?

    以下内容源于朱有鹏<物联网大讲堂>课程的学习整理,如有侵权,请告知删除. 1.驱动是谁写的? 驱动开发工程师(譬如厂商的驱动开发攻城狮们) 内核维护者 2.驱动编程协作要求 接口标准化 内 ...

  6. Linux PCI驱动框架分析:(Peripheral Component Interconnect,外部设备互联)

    <DPDK 20.05 | rte_pci_bus思维导图 | 第一版> <linux系统下:IO端口,内存,PCI总线 的 读写(I/O)操作> <Linux指令:ls ...

  7. Linux PCIe驱动框架分析(第二章)

    目录 项目背景 1. 概述 2. 数据结构 3. 流程分析 3.1 设备驱动模型 3.2 初始化 3.2.1 pci_bus_match 3.2.2 pci_device_probe 3.3 枚举 项 ...

  8. 深入分析Linux PCI驱动框架分析(二)

    说明: Kernel版本:4.14 ARM64处理器 使用工具:Source Insight 3.5, Visio 1. 概述 本文将分析Linux PCI子系统的框架,主要围绕Linux PCI子系 ...

  9. framebuffer驱动详解2——framebuffer驱动框架分析

    以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除. 一.framebuffer驱动框架总览 1.驱动框架部分 (1)drivers/video/fbmem.c(主要的文件) 创建graphic ...

  10. imx6 通过移植XRM117x(SPI转串口)对Linux中的SPI驱动框架进行分析

    最近分析了一下Linux 中的SPI驱动框架,将自己的理解总结一下,不足之处还请斧正! 1.SPI通信基础知识 SPI(Serial Peripheral Interface)是一种串行(一次发送1b ...

最新文章

  1. 一文读懂马斯克展示脑机接口:硬币大小芯片植入猪脑 实时读取猪脑信息
  2. python写web自动化_jenkins+selenium+python实现web自动化测试
  3. 安装Sql server 2008遇到无法安装.net 3.5的问题解决办法
  4. Educational Codeforces Round 54 (Rated for Div. 2): E. Vasya and a Tree(DFS+差分)
  5. 添加删除windows的系统服务
  6. android+4.3+usb存储模式,点点点:点出来安卓4.3的USB调试
  7. 【ML小结5】决策树(ID3、C4.5、CART)
  8. excel按模板导出
  9. 基于JavaWeb的小说阅读网站设计与实现 毕业论文+答辩PPT+项目源码及数据库文件
  10. saved_model_cli查看SavedModel
  11. httprunner踩坑之路
  12. Java、JSP大阳电动车销售系统的设计与实现
  13. Office-PPT如何使多张图片自动等距排列
  14. 上海高一计算机奥赛,上海物理奥赛金牌“大神”爱番剧和高达,已保送清华大学姚班...
  15. windows 录屏软件
  16. itext给已有pdf添加页眉页脚
  17. 一位博士在华为的22年 | 职业生涯奋斗之路
  18. Mac OS X Lion安装MacPorts(让你在Mac的Shell下更加游刃有余)
  19. 基于javaweb+mysql的二手交易平台二手商城二手物品(前台、后台)
  20. 计算机主要分为台式机和什么,计算机基础知识-台式机.ppt

热门文章

  1. 中秋--吃月饼,还不如就看看吧
  2. C语言之大王叫我来巡山呐
  3. David Silver《强化学习RL》第八讲 整合学习与规划
  4. java加密与解密工具_cat: CAT,全称Cryptographic Algorithm Tool,是一款小巧的Java加密与解密算法调用工具包...
  5. poj1734 无向图最小环
  6. CAD6:1.如何选择对象(批量选择、反选、筛选、栏选、)、2.删除工具的使用、3.如何显示图形
  7. 数字化转型:制造业企业,如何创新技术并借力发展?
  8. CET4汉译英part
  9. session-cookie-前后端分离-跨域
  10. [全程建模]全程建模方法被乱介绍的高校培训