Linux驱动之I2C设备驱动

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

内核:4.20
芯片:HYM8563 RTC

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

一、加载和卸载函数

static const struct i2c_device_id hym8563_id[] = {
  { "hym8563", 0 },
  {},
};
MODULE_DEVICE_TABLE(i2c, hym8563_id);

static const struct of_device_id hym8563_dt_idtable[] = {
  { .compatible = "haoyu,hym8563" },
  {},
};
MODULE_DEVICE_TABLE(of, hym8563_dt_idtable);

static struct i2c_driver hym8563_driver = {
  .driver    = {
    .name  = "rtc-hym8563",
    .pm  = &hym8563_pm_ops,
    .of_match_table  = hym8563_dt_idtable, //dt匹配表
  },
  .probe    = hym8563_probe,
  .id_table  = hym8563_id,// id表
};
// 封住了module_init()和module_exit()
// 里面会调用i2c_register_driver(hym8563_driver)
// 和i2c_del_driver(hym8563_driver)
module_i2c_driver(hym8563_driver);

二、probe()函数

static int hym8563_probe(struct i2c_client *client,
       const struct i2c_device_id *id)
{
  struct hym8563 *hym8563;
  int ret;
    //申请内存空间
  hym8563 = devm_kzalloc(&client->dev, sizeof(*hym8563), GFP_KERNEL);
  if (!hym8563)
    return -ENOMEM;
    //保存数据
  hym8563->client = client;
  i2c_set_clientdata(client, hym8563);
    //HYM8563初始化
  ret = hym8563_init_device(client);

    //申请中断
  if (client->irq > 0) {
    ret = devm_request_threaded_irq(&client->dev, client->irq,
            NULL, hym8563_irq,
            IRQF_TRIGGER_LOW | IRQF_ONESHOT,
            client->name, hym8563);
  }

  //检查一下模块是否正常运行
  ret = i2c_smbus_read_byte_data(client, HYM8563_SEC);
  if (ret < 0)
    return ret;
    //VL位用来标识模块是否正常工作
  hym8563->valid = !(ret & HYM8563_SEC_VL);
  dev_dbg(&client->dev, "rtc information is %s\n",
    hym8563->valid ? "valid" : "invalid");
    //注册RTC设备
  hym8563->rtc = devm_rtc_device_register(&client->dev, client->name,
            &hym8563_rtc_ops, THIS_MODULE);

  /* the hym8563 alarm only supports a minute accuracy */
  hym8563->rtc->uie_unsupported = 1;

#ifdef CONFIG_COMMON_CLK
    //HYM8563可以作为时钟源
  hym8563_clkout_register_clk(hym8563);
#endif

  return 0;
}

上面删掉了一些判断和Log信息。

三、HYM8563初始化

//查看datasheet对8563进行初始化,对寄存器进行设置
static int hym8563_init_device(struct i2c_client *client)
{
  int ret;

  /* Clear stop flag if present */
    //向寄存器中写入值
  ret = i2c_smbus_write_byte_data(client, HYM8563_CTL1, 0);
  if (ret < 0)
    return ret;
    //读取寄存器的值
  ret = i2c_smbus_read_byte_data(client, HYM8563_CTL2);
  if (ret < 0)
    return ret;

  /* Disable alarm and timer interrupts */
  ret &= ~HYM8563_CTL2_AIE;
  ret &= ~HYM8563_CTL2_TIE;

  /* Clear any pending alarm and timer flags */
  if (ret & HYM8563_CTL2_AF)
    ret &= ~HYM8563_CTL2_AF;

  if (ret & HYM8563_CTL2_TF)
    ret &= ~HYM8563_CTL2_TF;

  ret &= ~HYM8563_CTL2_TI_TP;
    //将修改后的值写入寄存器
  return i2c_smbus_write_byte_data(client, HYM8563_CTL2, ret);
}

四、HYM8563操作函数

static int hym8563_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
  struct i2c_client *client = to_i2c_client(dev);
  struct hym8563 *hym8563 = i2c_get_clientdata(client);
  u8 buf[7];
  int ret;

  if (!hym8563->valid) {
    dev_warn(&client->dev, "no valid clock/calendar values available\n");
    return -EPERM;
  }
    //读取寄存器值, 连续读取7个寄存器
  ret = i2c_smbus_read_i2c_block_data(client, HYM8563_SEC, 7, buf);
    //bcd数转成2进制
  tm->tm_sec = bcd2bin(buf[0] & HYM8563_SEC_MASK);
  tm->tm_min = bcd2bin(buf[1] & HYM8563_MIN_MASK);
  tm->tm_hour = bcd2bin(buf[2] & HYM8563_HOUR_MASK);
  tm->tm_mday = bcd2bin(buf[3] & HYM8563_DAY_MASK);
  tm->tm_wday = bcd2bin(buf[4] & HYM8563_WEEKDAY_MASK); /* 0 = Sun */
  tm->tm_mon = bcd2bin(buf[5] & HYM8563_MONTH_MASK) - 1; /* 0 = Jan */
  tm->tm_year = bcd2bin(buf[6]) + 100;

  return 0;
}

static const struct rtc_class_ops hym8563_rtc_ops = {
  .read_time    = hym8563_rtc_read_time,
  .set_time    = hym8563_rtc_set_time,
  .alarm_irq_enable  = hym8563_rtc_alarm_irq_enable,
  .read_alarm    = hym8563_rtc_read_alarm,
  .set_alarm    = hym8563_rtc_set_alarm,
};

其他的读写函数都是去通过I2C去读取寄存器的值。
20200501210135822.png

上面的调用关系图显示了设备与控制器之间的关系。

相关文章
|
22小时前
|
Linux 芯片 Ubuntu
Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯
Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯
|
22小时前
|
Ubuntu Linux
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-2
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-2
|
22小时前
|
Linux 芯片
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-1
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-1
|
22小时前
|
Linux C语言 Ubuntu
Linux驱动入门——编写第一个驱动
Linux驱动入门——编写第一个驱动
Linux驱动入门——编写第一个驱动
|
4天前
|
网络协议 Shell Linux
LabVIEW 在NI Linux实时设备上访问Shell
LabVIEW 在NI Linux实时设备上访问Shell
|
7天前
|
Linux
linux驱动层输出dev_dbg打印信息
linux驱动层输出dev_dbg打印信息
11 0
|
16天前
|
存储 监控 Linux
【专栏】在 Linux 中,了解已安装驱动器是系统管理的关键
【4月更文挑战第28天】在 Linux 中,了解已安装驱动器是系统管理的关键。本文介绍了三种方法:1) 使用 `lsblk` 命令显示设备名、大小和类型;2) `fdisk -l` 命令提供详细分区信息;3) `gnome-disks` 等系统管理工具展示驱动器信息。此外,还讨论了驱动器类型识别、挂载点概念及其应用。通过这些方法,用户能有效地监控和管理 Linux 系统中的驱动器。
|
23天前
|
存储 Linux
如何查看Linux设备的硬盘信息?
【4月更文挑战第12天】在Linux系统中,查看硬盘信息的常用命令。
29 4
|
存储 网络协议 Linux
Linux I2C设备驱动基本规范
不同于单片机驱动开发,即使是简单的I2C设备驱动程序,如果要在Linux上实现同种功能的驱动程序,事情也会变的复杂起来。对于初学者而言,主要的困难就是不知道如何使用Linux现有的驱动框架,去完成驱动程序的开发。I2C设备驱动,相对来说比较简单,但由于Linux大部分设备驱动框架十分的类似,所以,通过对于I2C驱动框架的学习,可以作为继续深入Linux其他设备驱动框架的基础。
454 0