加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
  • 推荐器件
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

技术 | I2C 子系统(九)

2022/06/14
2611
阅读需 29 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

3、I2C Register Flow

/kernel-5.10/drivers/i2c/i2c-core-base.c 是 I2C 的核心部分,I2C 核心提供了一些与具体硬件无关的 API 函数

1、i2c_adapter 注册/注销函数

int i2c_add_adapter(struct i2c_adapter *adapter)//自动分配 adapter ID
int i2c_add_numbered_adapter(struct i2c_adapter *adap)//指定 ID
void i2c_del_adapter(struct i2c_adapter * adap)

2、i2c_driver 注册/注销函数

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver(struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)

上述 API 一般需要在 init/exit 或者 probe/remove 函数中成对使用。

设备和驱动的匹配过程也是由 I2C 总线完成的,I2C 总线的数据结构为 i2c_bus_type,定义在 /kernel-5.10/drivers/i2c/i2c-core-base.c 文件,i2c_bus_type 内容如下:

struct bus_type i2c_bus_type = {
  .name = "i2c",
  .match = i2c_device_match,
  .probe = i2c_device_probe,
  .remove = i2c_device_remove,
  .shutdown = i2c_device_shutdown,
};

.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数,此函数内容如下:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
 struct i2c_client *client = i2c_verify_client(dev);
 struct i2c_driver *driver;

 if (!client)
 return 0;

 /* Attempt an OF style match */
 if (of_driver_match_device(dev, drv))
 return 1;

 /* Then ACPI style match */
 if (acpi_driver_match_device(dev, drv))
 return 1;

 driver = to_i2c_driver(drv);
 /* match on an id table if there is one */
 if (driver->id_table)
 return i2c_match_id(driver->id_table, client) != NULL;

 return 0;
}

of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C 设备和驱动匹配。

acpi_driver_match_device 函数用于 ACPI 形式的匹配。

i2c_match_id 函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C 设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。

控制器驱动

I2C 总线驱动重点是 I2C 适配器驱动,这里要用到两个重要的数据结构:i2c_adapter 和 i2c_algorithm。其中,Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter。

对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。i2c_algorithm 就是 I2C 适配器与 I2C 设备进行通信的方法。

I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter 或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter。

I2C 控制器驱动加载

设备树 mt6885.dts

驱动

驱动和设备树匹配以后,probe 函数开始执行,重要的地方博主都进行了注释,不重要的部分进行了删除。

static int mt_i2c_probe(struct platform_device *pdev)
{
 int ret = 0;
 struct mt_i2c *i2c; //控制器结构体
 unsigned int clk_src_in_hz;
 struct resource *res;
 const struct of_device_id *of_id;
  
  //申请内存
 i2c = devm_kzalloc(&pdev->dev, sizeof(struct mt_i2c), GFP_KERNEL);

  //获取设备树节点
 ret = mt_i2c_parse_dt(pdev->dev.of_node, i2c);

  //从设备树获取 I2C 控制器寄存器物理基地址
 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

  //进行内存映射,得到 Linux 内核使用的虚拟地址
 i2c->base = devm_ioremap_resource(&pdev->dev, res);

  .....
  
  //获取中断号
 i2c->irqnr = platform_get_irq(pdev, 0);

 init_waitqueue_head(&i2c->wait);

  //请求中断,中断服务函数为 mt_i2c_irq
 ret = devm_request_irq(&pdev->dev, i2c->irqnr, mt_i2c_irq,
  IRQF_NO_SUSPEND | IRQF_TRIGGER_NONE, I2C_DRV_NAME, i2c);

 of_id = of_match_node(mtk_i2c_of_match, pdev->dev.of_node);

  //填充 adapter 结构体各个参数
 i2c->dev_comp = of_id->data;
 i2c->i2c_pll_info = &i2c_pll_info;
 i2c->adap.dev.of_node = pdev->dev.of_node;
 i2c->dev = &i2c->adap.dev;
 i2c->adap.dev.parent = &pdev->dev;
 i2c->adap.owner = THIS_MODULE;
 i2c->adap.algo = &mt_i2c_algorithm;
 i2c->adap.algo_data = NULL;
 i2c->adap.timeout = 2 * HZ;
 i2c->adap.retries = 1;
 i2c->adap.nr = i2c->id;
 spin_lock_init(&i2c->cg_lock);

  ......

 mutex_init(&i2c->i2c_mutex);
 ret = i2c_set_speed(i2c, clk_src_in_hz);

 ret = mt_i2c_clock_enable(i2c);

 mt_i2c_init_hw(i2c);

 mt_i2c_clock_disable(i2c);
  
  // DMA 相关
 if (i2c->ch_offset_default)
  i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
   (PAGE_SIZE * 2), &i2c->dma_buf.paddr, GFP_KERNEL);
 else
  i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
   PAGE_SIZE, &i2c->dma_buf.paddr, GFP_KERNEL);

 if (i2c->dma_buf.vaddr == NULL) {
  dev_info(&pdev->dev, "dma_alloc_coherent fail\n");
  return -ENOMEM;
 }
 i2c_set_adapdata(&i2c->adap, i2c);
  
  //向 Linux 内核注册 i2c_adapter
 ret = i2c_add_numbered_adapter(&i2c->adap);

 platform_set_drvdata(pdev, i2c);

 return 0;
}

 

节点创建

i2c_add_adapter、i2c_add_numbered_adapter 是注册 i2c_adapter,这两个 API 最终是调用 i2c_register_adapter。

static int i2c_register_adapter(struct i2c_adapter *adap)
{
 ......

 dev_set_name(&adap->dev, "i2c-%d", adap->nr);
 adap->dev.bus = &i2c_bus_type;
 adap->dev.type = &i2c_adapter_type;
 res = device_register(&adap->dev);

 /* create pre-declared device nodes */
 of_i2c_register_devices(adap);
 i2c_acpi_install_space_handler(adap);
 i2c_acpi_register_devices(adap);

  .....
}

dev_set_name(&adap->dev, "i2c-%d", adap->nr) 会在 Linux 中自动创建 /sys/devices/platform/11f00000.i2c6/i2c-6 节点。

of_i2c_register_devices 会调用 i2c_new_client_device

struct i2c_client *i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
  ......

 client->dev.parent = &client->adapter->dev;
 client->dev.bus = &i2c_bus_type;
 client->dev.type = &i2c_client_type;
 client->dev.of_node = of_node_get(info->of_node);
 client->dev.fwnode = info->fwnode;

 device_enable_async_suspend(&client->dev);
 i2c_dev_set_name(adap, client, info);

  ......

 status = device_register(&client->dev);

  ......
}

i2c_dev_set_name 会在 Linux 系统中自动创建 /sys/devices/platform/11f00000.i2c6/i2c-6/6-0058 ,这里是挂在某个 i2c bus 上的,从机地址为 0x58 的设备节点。

到这里,大家应该能看懂下图,这些节点在 I2C 控制器驱动加载时,自动创建的。大家在 I2C 控制器的子节点中声明的 I2C 从机设备,也会在这一步创建节点。

 

设备驱动

I2C 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver。i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容。

一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client。

i2c_driver 中包含 probe 函数和 device_driver 结构体。如果使用设备树的话,需要设置 device_driver 的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。

当 I2C 设备和驱动匹配以后,probe 函数就会执行。

因此,对于 Linux 来讲,不区分 I2C 控制器和 I2C 从机设备,用的都是同一套东西,即【总线、设备、驱动】框架,都有 probe 函数。

设备树

注意,I2C 设备在设备树中必须挂到对应的总线下,如图是在 &i2c0 下。

驱动

当驱动和设备匹配时,probe 函数开始执行,重要的地方博主都写了注释

static int goodix_i2c_probe(struct i2c_client *client,
 const struct i2c_device_id *dev_id)
{
 struct goodix_ts_device *ts_device = NULL;
 struct goodix_ts_board_data *ts_bdata = NULL;
 int r = 0;

  //检查 I2C 控制器支持是否支持标准 I2C 协议
 r = i2c_check_functionality(client->adapter,I2C_FUNC_I2C);

 /* 板级信息分配内存 */
 ts_bdata = devm_kzalloc(&client->dev,sizeof(struct goodix_ts_board_data), GFP_KERNEL);
 ts_device = devm_kzalloc(&client->dev,sizeof(struct goodix_ts_device), GFP_KERNEL);

  //填充结构体元素
 /* use pinctrl in core.c */
 ts_bdata->pinctrl_dev = client->adapter->dev.parent;

 ts_device->name = "GT9886 TouchDevcie";
 ts_device->dev = &client->dev;
 ts_device->board_data = ts_bdata;
 ts_device->hw_ops = &hw_i2c_ops;
 touch_filter_bdata = ts_bdata;

 /* ts core device */
 goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL); 
  
  //填充结构体元素
 goodix_pdev->name = GOODIX_CORE_DRIVER_NAME;
 goodix_pdev->id = 0;
 goodix_pdev->num_resources = 0;
 goodix_pdev->dev.platform_data = ts_device;
 goodix_pdev->dev.release = goodix_pdev_release;

 r = platform_device_register(goodix_pdev);

 /* register platform driver*/
 r = goodix_ts_core_init();

  ......
}

I2C 从机设备驱动中的 probe 函数和器件强相关,各家撰写方式不一样。

在 I2C 设备驱动中,除了 init、exit、probe、remove 函数外,还要实现 read、write 函数才行。

I2C 写

I2C spec 规定如下

详细解析如下

对从机进行写操作时,主设备发出开始标志 (S) 和写地址 (从机地址加一个 R/W 位,1 为读,0 为写)。从机产生应答信号。然后主设备开始传送寄存器地址 (RA),接到应答后,开始传送寄存器数据,然后仍然要有应答信号,连续写入多字节时依次推。

举例(例子进行了两次封装)

static int ap3216c_open(struct inode *inode, struct file *filp)
{
 filp->private_data = &ap3216cdev;

 /* 初始化AP3216C */
 ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);  /* 复位AP3216C    */
 mdelay(50);              /* AP3216C复位最少10ms  */
 ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);  /* 开启ALS、PS+IR   */
 return 0;
}

static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
 u8 buf = 0;
 buf = data;
 ap3216c_write_regs(dev, reg, &buf, 1);
}

static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
 u8 b[256];
 struct i2c_msg msg;
 struct i2c_client *client = (struct i2c_client *)dev->private_data;
 
 b[0] = reg;     /* 寄存器首地址 */
 memcpy(&b[1],buf,len);  /* 将要写入的数据拷贝到数组b里面 */
  
 msg.addr = client->addr; /* ap3216c地址 */
 msg.flags = 0;    /* 标记为写数据 */
 msg.buf = b;    /* 要写入的数据缓冲区 */
 msg.len = len + 1;   /* 要写入的数据长度 */

 return i2c_transfer(client->adapter, &msg, 1);
}

 

I2C 读

 

I2C 先写后读

其实所谓的 I2C 读,都是先写后读。

I2C spec 规定如下

详细解析如下(详细看图)

对从机进行读操作时,主设备发出开始标志 (S) 和读地址 (从机地址加一个 R/W 位,1 为读,0 为写)。等待从机产生应答信号。然后发送寄存器地址,告诉从机读哪一个寄存器。紧接着,收到应答信号后,主设备再发一个开始信号,然后发送从设备读地址。从机产生应答信号并开始发送寄存器数据。通信以主设备产生的拒绝应答信号 (NACK) 和结束标志 (P) 结束。

举例(例子进行了两次封装)

void ap3216c_readdata(struct ap3216c_dev *dev)
{
 unsigned char i =0;
    unsigned char buf[6];
 
 /* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++) 
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); 
    }
}

static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
 u8 data = 0;

 ap3216c_read_regs(dev, reg, &data, 1);
 return data;
}

static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
 int ret;
 struct i2c_msg msg[2];
 struct i2c_client *client = (struct i2c_client *)dev->private_data;

 /* msg[0]为发送要读取的首地址 */
 msg[0].addr = client->addr;   /* ap3216c地址 */
 msg[0].flags = 0;     /* 标记为发送数据 */
 msg[0].buf = &reg;     /* 读取的首地址 */
 msg[0].len = 1;      /* reg长度*/

 /* msg[1]读取数据 */
 msg[1].addr = client->addr;   /* ap3216c地址 */
 msg[1].flags = I2C_M_RD;   /* 标记为读取数据*/
 msg[1].buf = val;     /* 读取数据缓冲区 */
 msg[1].len = len;     /* 要读取的数据长度*/

 ret = i2c_transfer(client->adapter, msg, 2);
 if(ret == 2) {
  ret = 0;
 } else {
  printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
  ret = -EREMOTEIO;
 }
 return ret;
}

推荐器件

更多器件
器件型号 数量 器件厂商 器件描述 数据手册 ECAD模型 风险等级 参考价格 更多信息
CY62167EV30LL-45ZXIT 1 Cypress Semiconductor Standard SRAM, 1MX16, 45ns, CMOS, PDSO48, 12 X 18.40 MM, 1 MM HEIGHT, MO-142, ROHS COMPLIANT, TSOP1-48
$18.79 查看
CB3LV-3I-20M000000 1 CTS Corporation HCMOS/TTL Output Clock Oscillator, 20MHz Nom,
$2.78 查看
510BBA125M000BAG 1 Silicon Laboratories Inc LVDS Output Clock Oscillator, 125MHz Nom, ROHS COMPLIANT PACKAGE-6

ECAD模型

下载ECAD模型
$4.22 查看

相关推荐

电子产业图谱

研究生在读,熟悉硬件、STM32单片机、嵌入式Linux。已收获小米、联发科、浙江大华、上能电气、英威腾、汇川技术、格力、富士康等大厂offer。在这里分享求职经验、嵌入式学习规划、考研、嵌入式Linux技术文章等。