欢迎光临
我们一直在努力

03-I2C设备驱动开发(DS1307)

03-I2C设备驱动开发(DS1307)

一 目的

  • 从0开始编写一个DS1307​芯片的驱动,并逐步完善,通过实践熟悉i2c_client驱动开发流程
  • 基于基础设备-总线-驱动模型,实现驱动的与设备树的匹配、芯片的初始化等操作
  • 生成设备节点/dev/rtc_ds1307​,并完善file_operations,编写应用层测试APP进行交互
  • 引入​RTC​​子系统机制中,使其称为一个标准的i2c​接口的rtc​设备驱动,可通过hwclock获取和写入时钟
  • 将DS1307​的56个非易失性SRAM(NV SRAM)注册为rtc_nvmem设备
  • 使用Regmap函数重构寄存器访问操作
  • 二 内容

    2.1 V1.0:基于设备驱动模型,基础i2c字符设备驱动

    • 源码

      #include "linux/uaccess.h"
      #include <linux/bcd.h>
      #include <linux/i2c.h>
      #include <linux/init.h>
      #include <linux/mod_devicetable.h>
      #include <linux/module.h>
      #include <linux/property.h>
      #include <linux/rtc/ds1307.h>
      #include <linux/rtc.h>
      #include <linux/slab.h>
      #include <linux/string.h>
      #include <linux/hwmon.h>
      #include <linux/hwmon-sysfs.h>
      #include <linux/clk-provider.h>
      #include <linux/regmap.h>
      #include <linux/watchdog.h>
      #include <linux/device.h>
      #include <linux/fs.h>

      #define DRIVER_NAME "rtc_ds1307"
      #define CLASS_NAME "ds1307_class"
      #define DRIVER_VERSION "V1.0"

      /* Time Register */
      #define DS1307_TIME_REGS_COUNT 7
      #define DS1307_REG_SECS 0x00
      # define DS1307_BIT_CH 0x80
      #define DS1307_REG_MIN 0x01
      #define DS1307_REG_HOUR 0x02
      # define DS1307_BIT_12HR 0x40
      # define DS1307_BIT_PM 0x20
      #define DS1307_REG_WDAY 0x03
      #define DS1307_REG_MDAY 0x04
      #define DS1307_REG_MONTH 0x05
      #define DS1307_REG_YEAR 0x06

      /* Control Register */
      #define DS1307_REG_CONTROL 0x07
      # define DS1307_BIT_OUT 0x80
      # define DS1307_BIT_SQWE 0x10
      # define DS1307_BIT_RS1 0x02
      # define DS1307_BIT_RS0 0x01

      struct ds1307_time {
      u8 secs;
      u8 min;
      u8 hour;
      u8 wday;
      u8 mday;
      u8 month;
      u8 year;
      };

      struct ds1307_dev {
      struct cdev cdev;
      dev_t dev_num;
      struct class *dev_class;
      struct device *device;
      struct i2c_client *client;
      };

      static int ds1307_read_reg(struct i2c_client *client, u8 reg, u8 *val)
      {
      int ret;
      ret = i2c_smbus_read_byte_data(client, reg);
      if (ret < 0) {
      pr_err("Failed to read register 0x%02x\\n", reg);
      return ret;
      }
      *val = ret;
      return 0;
      }

      static int ds1307_write_reg(struct i2c_client *client, u8 reg, u8 val)
      {
      int ret;
      ret = i2c_smbus_write_byte_data(client, reg, val);
      if (ret < 0) {
      pr_err("Failed to write register 0x%02x\\n", reg);
      return ret;
      }
      return 0;
      }

      static int ds1307_read_regs(struct i2c_client *client, u8 reg, u8 *buf, u8 count)
      {
      int ret;
      ret = i2c_smbus_read_i2c_block_data(client, reg, count, buf);
      if (ret < 0) {
      pr_err("Failed to read registers starting from 0x%02x\\n", reg);
      }
      return ret;
      }

      static int ds1307_write_regs(struct i2c_client *client, u8 reg, u8 *buf, u8 count)
      {
      int ret;
      ret = i2c_smbus_write_i2c_block_data(client, reg, count, buf);
      if (ret < 0) {
      pr_err("Failed to write registers starting from 0x%02x\\n", reg);
      }
      return ret;
      }

      static int ds1307_check_init(struct i2c_client *client)
      {
      int ret;
      struct ds1307_dev *dev = i2c_get_clientdata(client);
      u8 seconds;

      /* 1. check */
      ret = i2c_smbus_read_byte_data(client, DS1307_REG_SECS);
      if(ret < 0) {
      dev_err(&client->dev, "Failed to read from DS1307 device\\n");
      return ret;
      }

      seconds = (u8)ret;

      /* 2. init */
      if(seconds & DS1307_BIT_CH) {
      u8 default_time[] = {0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x25};
      dev_warn(&client->dev, "Clock was halted. Initializing clock…\\n");

      ret = ds1307_write_regs(client, DS1307_REG_SECS, default_time, DS1307_TIME_REGS_COUNT);
      if (ret < 0) {
      dev_err(&client->dev, "Failed to write default time\\n");
      return ret;
      }
      dev_info(&client->dev, "Clock started with default time.\\n");
      }
      else {
      dev_info(&client->dev, "ds1307 without init\\n");
      }

      return 0;
      }

      static int ds1307_open(struct inode *inode, struct file *file)
      {
      struct ds1307_dev *dev = container_of(inode->i_cdev, struct ds1307_dev, cdev);
      file->private_data = dev;
      return 0;
      }

      static int ds1307_release(struct inode *inode, struct file *file)
      {
      return 0;
      }

      static ssize_t ds1307_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
      {
      struct ds1307_dev *dev = file->private_data;
      u8 time_buf[DS1307_TIME_REGS_COUNT];
      int ret;

      if(*f_pos > 0) {
      return 0;
      }

      ret = ds1307_read_regs(dev->client, DS1307_REG_SECS, time_buf, DS1307_TIME_REGS_COUNT);
      if (ret < 0) {
      pr_err("Failed to read time registers from DS1307\\n");
      return ret;
      }

      if (count > DS1307_TIME_REGS_COUNT) {
      count = DS1307_TIME_REGS_COUNT;
      }

      if(copy_to_user(buf, time_buf, count)) {
      pr_err("Failed to copy data to user space\\n");
      return EFAULT;
      }

      *f_pos += count;

      return count;
      }

      static ssize_t ds1307_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
      {
      struct ds1307_dev *dev = file->private_data;
      u8 time_buf[DS1307_TIME_REGS_COUNT];
      int ret;

      if(count > DS1307_TIME_REGS_COUNT) {
      count = DS1307_TIME_REGS_COUNT;
      }

      ret = copy_from_user(time_buf, buf, count);
      if(ret < 0) {
      pr_err("Failed to copy data from user space\\n");
      return EFAULT;
      }
      time_buf[0] &= ~DS1307_BIT_CH;

      ret = ds1307_write_regs(dev->client, DS1307_REG_SECS, time_buf, DS1307_TIME_REGS_COUNT);
      if(ret < 0) {
      pr_err("Failed to write time registers from DS1307\\n");
      return ret;
      }

      return count;
      }

      static const struct file_operations ds1307_fops = {
      .owner = THIS_MODULE,
      .open = ds1307_open,
      .read = ds1307_read,
      .write = ds1307_write,
      .release = ds1307_release,
      };

      static int ds1307_drv_probe(struct i2c_client *client, const struct i2c_device_id *id) {
      struct ds1307_dev *dev;
      int ret;

      /* 1.内存分配 */
      dev = kzalloc(sizeof(struct ds1307_dev), GFP_KERNEL);
      if(!dev) {
      return ENOMEM;
      }
      dev->client = client;
      i2c_set_clientdata(client, dev);

      /* 1.1 检查RTC是否工作 */
      ret = ds1307_check_init(client);
      if (ret < 0) {
      goto err_free_mem;
      }

      /* 2.字符设备号分配 */
      ret = alloc_chrdev_region(&dev->dev_num, 0, 1, DRIVER_NAME);
      if(ret < 0) {
      pr_err("Failed to allocate char device region\\n");
      goto err_free_mem;
      }

      /* 3.初始化cdev */
      cdev_init(&dev->cdev, &ds1307_fops);
      dev->cdev.owner = THIS_MODULE;
      ret = cdev_add(&dev->cdev, dev->dev_num, 1);
      if (ret < 0) {
      pr_err("Failed to add cdev\\n");
      goto err_unregister_chrdev;
      }

      /* 4.设备类创建 */
      dev->dev_class = class_create(THIS_MODULE, CLASS_NAME);
      if (IS_ERR(dev->dev_class)) {
      pr_err("Failed to create device class\\n");
      ret = PTR_ERR(dev->dev_class);
      goto err_del_cdev;
      }

      /* 5.创建设备文件“/dev/rtc_ds1307” */
      dev->device = device_create(dev->dev_class, NULL, dev->dev_num, NULL, DRIVER_NAME);
      if (IS_ERR(dev->device)) {
      pr_err("Failed to create device file\\n");
      ret = PTR_ERR(dev->device);
      goto err_destroy_class;
      }

      pr_info("DS1307 driver probed successfully\\n");
      return 0;

      /* 错误处理:按申请顺序的逆序释放资源 */
      err_destroy_class:
      class_destroy(dev->dev_class);
      err_del_cdev:
      cdev_del(&dev->cdev);
      err_unregister_chrdev:
      unregister_chrdev_region(dev->dev_num, 1);
      err_free_mem:
      kfree(dev);
      return ret;
      }

      static void ds1307_drv_remove(struct i2c_client *client) {
      struct ds1307_dev *dev = i2c_get_clientdata(client);
      pr_info("ds1307_remove function entered\\n");

      device_destroy(dev->dev_class, dev->dev_num);
      class_destroy(dev->dev_class);
      cdev_del(&dev->cdev);
      unregister_chrdev_region(dev->dev_num, 1);
      kfree(dev);
      }

      static const struct i2c_device_id ds1307_id[] = {
      {"rtc-ds1307", 0},
      {},
      };
      MODULE_DEVICE_TABLE(i2c, ds1307_id);

      static const struct of_device_id ds1307_of_match[] = {
      { .compatible = "fog,rtc-ds1307" },
      { /* sentinel */ }
      };
      MODULE_DEVICE_TABLE(of, ds1307_of_match);

      static struct i2c_driver ds1307_driver = {
      .driver = {
      .name = "ds1307",
      .of_match_table = of_match_ptr(ds1307_of_match),
      },
      .probe = ds1307_drv_probe,
      .remove = ds1307_drv_remove,
      .id_table = ds1307_id,
      };
      module_i2c_driver(ds1307_driver);

      MODULE_LICENSE("GPL");

    • 测试

    • 驱动匹配 && 节点信息

    • 设备识别

      image

    • 驱动加载

      image

    • 节点信息

      image

    • 测试程序

      • 主要实现

      • 通过i2c读取寄存器原始数据
      • 打印原始数据与转化后的数据
      • 通过i2c写入时间
      • 同步系统时间并写入设备
      • 源码

        #include <stdio.h>
        #include <stdlib.h>
        #include <fcntl.h>
        #include <unistd.h>
        #include <string.h>
        #include <stdint.h>
        #include <time.h>

        #define DEVICE_PATH "/dev/rtc_ds1307"
        #define DS1307_TIME_REGS_COUNT 7

        static uint8_t bcd2bin(uint8_t val)
        {
        return (val & 0x0f) + (val >> 4) * 10;
        }

        static uint8_t bin2bcd(uint8_t val)
        {
        return ((val / 10) << 4) + (val % 10);
        }

        static const char *wday_name[] = {
        "", "Sun.", "Mon.", "Tue.", "Wed.", "Fri.", "Sat."
        };

        static void print_raw_regs(uint8_t *regs)
        {
        printf("\\n Raw data: \\n");
        printf(" Sec: 0x%02X\\n", regs[0]);
        printf(" Min: 0x%02X\\n", regs[1]);
        printf(" Hour: 0x%02X\\n", regs[2]);
        printf(" Week: 0x%02X\\n", regs[3]);
        printf(" Day: 0x%02X\\n", regs[4]);
        printf(" Mon: 0x%02X\\n", regs[5]);
        printf(" Year: 0x%02X\\n", regs[6]);
        }

        static void print_time(uint8_t *regs)
        {
        uint8_t sec = bcd2bin(regs[0] & 0x7F);
        uint8_t min = bcd2bin(regs[1]);
        uint8_t hour = bcd2bin(regs[2] & 0x3F);
        uint8_t wday = regs[3];
        uint8_t mday = bcd2bin(regs[4]);
        uint8_t mon = bcd2bin(regs[5]);
        uint8_t year = bcd2bin(regs[6]);

        printf("\\n Time:\\n");
        printf(" 20%02d:%02d:%02d:(%s)\\n", year, mon, mday,
        (wday >= 1 && wday <= 7) ? wday_name[wday] : "Unknowd");
        printf(" %02d:%02d:%02d\\n", hour, min, sec);
        }

        static int read_time(void)
        {
        int fd;
        uint8_t time_regs[DS1307_TIME_REGS_COUNT];
        ssize_t ret;

        fd = open(DEVICE_PATH, O_RDONLY);
        if(fd < 0) {
        perror("Cant open device");
        return 1;
        }

        ret = read(fd, time_regs, DS1307_TIME_REGS_COUNT);
        if(ret < 0) {
        perror("read error");
        close(fd);
        return 1;
        }

        printf("read %d Byte\\n", ret);
        print_raw_regs(time_regs);
        print_time(time_regs);
        }

        static int write_time(int year, int mon, int mday, int wday,
        int hour, int min, int sec)
        {
        int fd;
        uint8_t time_regs[DS1307_TIME_REGS_COUNT];
        ssize_t ret;

        time_regs[0] = bin2bcd(sec);
        time_regs[1] = bin2bcd(min);
        time_regs[2] = bin2bcd(hour);
        time_regs[3] = wday;
        time_regs[4] = bin2bcd(mday);
        time_regs[5] = bin2bcd(mon);
        time_regs[6] = bin2bcd(year);

        printf("\\n Write Time: \\n");
        printf(" 20%02d:%02d:%02d:(%s)\\n", year, mon, mday,
        (wday >= 1 && wday <= 7) ? wday_name[wday] : "Unknowd");
        printf(" %02d:%02d:%02d\\n", hour, min, sec);

        fd = open(DEVICE_PATH, O_WRONLY);
        if(fd < 0) {
        perror("Cant open device");
        return 1;
        }

        ret = write(fd, time_regs, DS1307_TIME_REGS_COUNT);
        if(ret < 0) {
        perror("write error");
        close(fd);
        return 1;
        }

        printf("Write Byte:%d\\n", ret);
        close(fd);
        return 0;
        };

        int sync_time(void)
        {
        int fd;
        struct tm sync_time;
        time_t tNow = 1;
        uint8_t time_regs[DS1307_TIME_REGS_COUNT];
        ssize_t ret;

        fd = open(DEVICE_PATH, O_WRONLY);
        if(fd < 0) {
        perror("Cant open device");
        close(fd);
        }

        time(&tNow);
        if(tNow < 0) {
        perror("time");
        close(fd);
        return 1;
        }
        printf("time:%s\\n", ctime(&tNow));

        memset(&sync_time, 0, sizeof(sync_time));
        localtime_r(&tNow, &sync_time);

        time_regs[0] = bin2bcd((uint8_t)sync_time.tm_sec);
        time_regs[1] = bin2bcd((uint8_t)sync_time.tm_min);
        time_regs[2] = bin2bcd((uint8_t)sync_time.tm_hour);
        time_regs[3] = bin2bcd((uint8_t)sync_time.tm_wday + 1);
        time_regs[4] = bin2bcd((uint8_t)sync_time.tm_mday);
        time_regs[5] = bin2bcd((uint8_t)sync_time.tm_mon + 1);
        time_regs[6] = bin2bcd((uint8_t)sync_time.tm_year 100);

        ret = write(fd, time_regs, DS1307_TIME_REGS_COUNT);
        if(ret < 0) {
        perror("sync error");
        close(fd);
        return 1;
        }
        close(fd);
        return 0;
        }

        int main(int argc, char *argv[])
        {
        if(strcmp(argv[1], "read") == 0) {
        return read_time();
        }
        else if(strcmp(argv[1], "write") == 0) {
        int year = atoi(argv[2]);
        int mon = atoi(argv[3]);
        int mday = atoi(argv[4]);
        int wday = atoi(argv[5]);
        int hour = atoi(argv[6]);
        int min = atoi(argv[7]);
        int sec = atoi(argv[8]);

        return write_time(year, mon, mday, wday, hour, min, sec);
        }
        else if(strcmp(argv[1], "sync") == 0) {
        return sync_time();
        }
        else {
        return 1;
        }
        return 0;
        }

    • 测试现象

    • 读功能

      image

    • 写功能

      image

    • 同步功能

      image

    2.2 V1.1:引入rtc子系统,实现基础功能

    • 源码

      • 主要修改

      • ​ds1307_dev​结构体中新增成员struct rtc_device *rtc_device

        image

      • 实现rtc_class_ops,用于读写操作

        image

        image

        image

      • ​probe函数中注册RTC设备

        image

        image

      • 补丁

        commit 2f560c6869afc63565f83970f0b629f7bafcd87b (HEAD -> master)
        Author: zjp <zjp@tronlong.com>
        Date: Tue Jan 13 15:52:33 2026 +0800

        V1.1Implement basic RTC device functionality

        diff git a/ds1307.c b/ds1307.c
        index de9c917..1ae5997 100644
        a/ds1307.c
        +++ b/ds1307.c
        @@ 1,3 +1,5 @@
        +#include "asm-generic/errno-base.h"
        +#include "linux/export.h"
        #include "linux/uaccess.h"
        #include <linux/bcd.h>
        #include <linux/i2c.h>
        @@ 57,6 +59,7 @@ struct ds1307_dev {
        struct class *dev_class;
        struct device *device;
        struct i2c_client *client;
        + struct rtc_device *rtc_device;
        };

        static int ds1307_read_reg(struct i2c_client *client, u8 reg, u8 *val)
        @@ 204,6 +207,66 @@ static ssize_t ds1307_write(struct file *file, const char __user *buf, size_t co
        return count;
        }

        +static int ds1307_rtc_read_time(struct device *dev, struct rtc_time *t)
        +{
        + struct ds1307_dev *ds1307_dev = dev_get_drvdata(dev);
        + u8 time_buf[DS1307_TIME_REGS_COUNT];
        + int ret;
        +
        + ret = ds1307_read_regs(ds1307_dev->client, DS1307_REG_SECS, time_buf, DS1307_TIME_REGS_COUNT);
        + if(ret < 0) {
        + pr_err("Failed to read time registers from DS1307\\n");
        + return ret;
        + }
        + if(time_buf[0] & DS1307_BIT_CH)
        + return EINVAL;
        +
        + t->tm_sec = bcd2bin(time_buf[0] & 0x7f);
        + t->tm_min = bcd2bin(time_buf[1] & 0x7f);
        + t->tm_hour = bcd2bin(time_buf[2] & 0x3f);
        + t->tm_wday = bcd2bin(time_buf[3] & 0x07) 1 ;
        + t->tm_mday = bcd2bin(time_buf[4] & 0x3f);
        + t->tm_mon = bcd2bin(time_buf[5] & 0x1f) 1;
        + t->tm_year = bcd2bin(time_buf[6] ) + 100;
        +
        + dev_dbg(dev, "%s secs=%d, mins=%d, "
        + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\\n",
        + "read", t->tm_sec, t->tm_min,
        + t->tm_hour, t->tm_mday,
        + t->tm_mon, t->tm_year, t->tm_wday);
        +
        + return 0;
        +}
        +
        +static int ds1307_rtc_set_time(struct device *dev, struct rtc_time *t)
        +{
        + struct ds1307_dev *ds1307_dev = dev_get_drvdata(dev);
        + u8 time_buf[DS1307_TIME_REGS_COUNT];
        + int ret;
        +
        + dev_dbg(dev, "%s secs=%d, mins=%d, "
        + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\\n",
        + "write", t->tm_sec, t->tm_min,
        + t->tm_hour, t->tm_mday,
        + t->tm_mon, t->tm_year, t->tm_wday);
        +
        +
        + time_buf[0] = bin2bcd(t->tm_sec);
        + time_buf[1] = bin2bcd(t->tm_min);
        + time_buf[2] = bin2bcd(t->tm_hour);
        + time_buf[3] = bin2bcd(t->tm_wday + 1);
        + time_buf[4] = bin2bcd(t->tm_mday);
        + time_buf[5] = bin2bcd(t->tm_mon + 1);
        + time_buf[6] = bin2bcd(t->tm_year 100);
        +
        + ret = ds1307_write_regs(ds1307_dev->client, DS1307_REG_SECS, time_buf, DS1307_TIME_REGS_COUNT);
        + return 0;
        +}
        +
        +static const struct rtc_class_ops ds1307_rtc_ops = {
        + .read_time = ds1307_rtc_read_time,
        + .set_time = ds1307_rtc_set_time,
        +};
        +
        static const struct file_operations ds1307_fops = {
        .owner = THIS_MODULE,
        .open = ds1307_open,
        @@ 221,6 +284,7 @@ static int ds1307_drv_probe(struct i2c_client *client, const struct i2c_device_i
        if(!dev) {
        return ENOMEM;
        }
        + dev_set_drvdata(&client->dev, dev);
        dev->client = client;
        i2c_set_clientdata(client, dev);

        @@ 262,6 +326,14 @@ static int ds1307_drv_probe(struct i2c_client *client, const struct i2c_device_i
        goto err_destroy_class;
        }

        + /* 6.ע<B2><E1>RTC<C9>豸 */
        + dev->rtc_device = devm_rtc_device_register(&client->dev, DRIVER_NAME, &ds1307_rtc_ops, THIS_MODULE);
        + if (IS_ERR(dev->rtc_device)) {
        + dev_err(&client->dev, "Failed to register RTC device\\n");
        + ret = PTR_ERR(dev->rtc_device);
        + goto err_destroy_class; // <CC><F8>ת<B5><BD><CF><D6><D3>д<ED><CE><F3><B4><A6><C0><ED><B5><E3>
        +}
        +

    • 测试

    • 驱动加载 && 节点信息

      image

      image

    • ​hwclock 读写时间

    • 查看时钟

      hwclock u

      image

    • 设置系统时间 && 同步至 RTC时钟

      date s "2026-01-13 17:22:00"
      date
      hwclock systohc u
      hwclock u

      image

    2.3 V1.2:注册 NV SRAM设备

    • 源码

      • 主要修改

      • 头文件引入 && 配置相关宏

        image

      • 实现rtc_nvme设备的读写函数

        image

      • 配置nvmem_config​并注册rtc_nvmem

        image

      • 修改ds1307_read_regs​与ds1307_write_regs​实现,使用i2c_transfer

        若不修改,存在问题3.2

        image

        image

    • 测试

    • 节点信息 && 读取内容

      image

      image

    • 指令写入测试

      echo ne "01234567890123456789012345678901234567890123456789012345" > /sys/bus/nvmem/devices/ds1307_nvram0/nvmem

      image

      若不加-ne​,将打印报错信息-sh: echo: write error: File too large实际为字符串的截至符

    • 程序读写测试

      • 主要实现

      • 向rtc-nvme节点写入数据并校验
      • 从rtc-nvme读出数据
      • 对rtc-nvme​所有NV SRAM进行写入后读出,进行校验
      • 源码

        #include <stdio.h>
        #include <stdlib.h>
        #include <fcntl.h>
        #include <unistd.h>
        #include <string.h>
        #include <stdint.h>
        #include <time.h>

        #define DEVICE_PATH "/dev/rtc_ds1307"
        #define DS1307_TIME_REGS_COUNT 7
        #define NVMEM_PATH "/sys/bus/nvmem/devices/ds1307_nvram0/nvmem"
        #define NVMEM_SIZE 56
        #define NVMEM_TEST_SIZE 16

        static uint8_t bcd2bin(uint8_t val)
        {
        return (val & 0x0f) + (val >> 4) * 10;
        }

        static uint8_t bin2bcd(uint8_t val)
        {
        return ((val / 10) << 4) + (val % 10);
        }

        static const char *wday_name[] = {
        "", "Sun.", "Mon.", "Tue.", "Wed.", "Fri.", "Sat."
        };

        static void print_raw_regs(uint8_t *regs)
        {
        printf("\\n Raw data: \\n");
        printf(" Sec: 0x%02X\\n", regs[0]);
        printf(" Min: 0x%02X\\n", regs[1]);
        printf(" Hour: 0x%02X\\n", regs[2]);
        printf(" Week: 0x%02X\\n", regs[3]);
        printf(" Day: 0x%02X\\n", regs[4]);
        printf(" Mon: 0x%02X\\n", regs[5]);
        printf(" Year: 0x%02X\\n", regs[6]);
        }

        static void print_time(uint8_t *regs)
        {
        uint8_t sec = bcd2bin(regs[0] & 0x7F);
        uint8_t min = bcd2bin(regs[1]);
        uint8_t hour = bcd2bin(regs[2] & 0x3F);
        uint8_t wday = regs[3];
        uint8_t mday = bcd2bin(regs[4]);
        uint8_t mon = bcd2bin(regs[5]);
        uint8_t year = bcd2bin(regs[6]);

        printf("\\n Time:\\n");
        printf(" 20%02d:%02d:%02d:(%s)\\n", year, mon, mday,
        (wday >= 1 && wday <= 7) ? wday_name[wday] : "Unknowd");
        printf(" %02d:%02d:%02d\\n", hour, min, sec);
        }

        static int read_time(void)
        {
        int fd;
        uint8_t time_regs[DS1307_TIME_REGS_COUNT];
        ssize_t ret;

        fd = open(DEVICE_PATH, O_RDONLY);
        if(fd < 0) {
        perror("Cant open device");
        return 1;
        }

        ret = read(fd, time_regs, DS1307_TIME_REGS_COUNT);
        if(ret < 0) {
        perror("read error");
        close(fd);
        return 1;
        }

        printf("read %d Byte\\n", ret);
        print_raw_regs(time_regs);
        print_time(time_regs);
        }

        static int write_time(int year, int mon, int mday, int wday,
        int hour, int min, int sec)
        {
        int fd;
        uint8_t time_regs[DS1307_TIME_REGS_COUNT];
        ssize_t ret;

        time_regs[0] = bin2bcd(sec);
        time_regs[1] = bin2bcd(min);
        time_regs[2] = bin2bcd(hour);
        time_regs[3] = wday;
        time_regs[4] = bin2bcd(mday);
        time_regs[5] = bin2bcd(mon);
        time_regs[6] = bin2bcd(year);

        printf("\\n Write Time: \\n");
        printf(" 20%02d:%02d:%02d:(%s)\\n", year, mon, mday,
        (wday >= 1 && wday <= 7) ? wday_name[wday] : "Unknowd");
        printf(" %02d:%02d:%02d\\n", hour, min, sec);

        fd = open(DEVICE_PATH, O_WRONLY);
        if(fd < 0) {
        perror("Cant open device");
        return 1;
        }

        ret = write(fd, time_regs, DS1307_TIME_REGS_COUNT);
        if(ret < 0) {
        perror("write error");
        close(fd);
        return 1;
        }

        printf("Write Byte:%d\\n", ret);
        close(fd);
        return 0;
        };

        int sync_time(void)
        {
        int fd;
        struct tm sync_time;
        time_t tNow = 1;
        uint8_t time_regs[DS1307_TIME_REGS_COUNT];
        ssize_t ret;

        fd = open(DEVICE_PATH, O_WRONLY);
        if(fd < 0) {
        perror("Cant open device");
        close(fd);
        }

        time(&tNow);
        if(tNow < 0) {
        perror("time");
        close(fd);
        return 1;
        }
        printf("time:%s\\n", ctime(&tNow));

        memset(&sync_time, 0, sizeof(sync_time));
        localtime_r(&tNow, &sync_time);

        time_regs[0] = bin2bcd((uint8_t)sync_time.tm_sec);
        time_regs[1] = bin2bcd((uint8_t)sync_time.tm_min);
        time_regs[2] = bin2bcd((uint8_t)sync_time.tm_hour);
        time_regs[3] = bin2bcd((uint8_t)sync_time.tm_wday + 1);
        time_regs[4] = bin2bcd((uint8_t)sync_time.tm_mday);
        time_regs[5] = bin2bcd((uint8_t)sync_time.tm_mon + 1);
        time_regs[6] = bin2bcd((uint8_t)sync_time.tm_year 100);

        ret = write(fd, time_regs, DS1307_TIME_REGS_COUNT);
        if(ret < 0) {
        perror("sync error");
        close(fd);
        return 1;
        }
        close(fd);
        return 0;
        }

        static int nvmem_read_test(void)
        {
        int fd;
        uint8_t buffer[NVMEM_SIZE];
        ssize_t ret;
        int i;

        printf("\\n=== NVMEM Read Test ===\\n");

        /* Open NVMEM file */
        fd = open(NVMEM_PATH, O_RDONLY);
        if (fd < 0) {
        perror("Failed to open NVMEM device");
        return 1;
        }

        /* Set file offset to 0 */
        lseek(fd, 0, SEEK_SET);

        /* Read all NVMEM data */
        ret = read(fd, buffer, NVMEM_SIZE);
        if (ret < 0) {
        perror("Failed to read NVMEM data");
        close(fd);
        return 1;
        }

        printf("Read %zd bytes from NVMEM (0x00-0x%02X)\\n", ret, ret1);

        /* Print the data in hex format */
        printf("NVMEM Data (hex):\\n");
        for (i = 0; i < ret; i++) {
        if (i % 16 == 0) {
        printf(" 0x%02X: ", i);
        }
        printf("%02X ", buffer[i]);
        if (i % 16 == 15 || i == ret 1) {
        printf("\\n");
        }
        }

        /* Print the data in ASCII format */
        printf("NVMEM Data (ASCII):\\n");
        for (i = 0; i < ret; i++) {
        if (i % 16 == 0) {
        printf(" 0x%02X: ", i);
        }
        if (buffer[i] >= 32 && buffer[i] <= 126) {
        printf("%c ", buffer[i]);
        } else {
        printf(". ");
        }
        if (i % 16 == 15 || i == ret 1) {
        printf("\\n");
        }
        }

        close(fd);
        return 0;
        }

        static int nvmem_write_test(void)
        {
        int fd;
        uint8_t write_data[NVMEM_SIZE];
        uint8_t read_data[NVMEM_SIZE];
        ssize_t ret;
        int i;
        int test_offset = 0;
        const char *test_str = "DS1307 NVMEM Test 2026";

        printf("\\n=== NVMEM Write Test ===\\n");

        /* Open NVMEM file for writing */
        fd = open(NVMEM_PATH, O_WRONLY);
        if (fd < 0) {
        perror("Failed to open NVMEM device for writing");
        return 1;
        }

        /* Set file offset to 0 */
        lseek(fd, 0, SEEK_SET);

        /* Prepare test data */
        memset(write_data, 0x55, NVMEM_SIZE); /* Fill with 0x55 first */

        /* Write test string to specific offset */
        strncpy((char *)&write_data[test_offset], test_str, sizeof(write_data) test_offset 1);
        write_data[sizeof(write_data) 1] = '\\0'; /* Ensure null termination */

        printf("Writing test data to offset 0x%02X:\\n", test_offset);
        printf(" Test string: %s\\n", test_str);
        printf(" Data pattern: 0x55 for remaining bytes\\n");

        /* Write data to NVMEM */
        ret = write(fd, write_data, NVMEM_SIZE);
        if (ret < 0) {
        perror("Failed to write NVMEM data");
        close(fd);
        return 1;
        }

        printf("Wrote %zd bytes to NVMEM (0x00-0x%02X)\\n", ret, ret1);
        close(fd);

        /* Verify the write operation by reading back */
        fd = open(NVMEM_PATH, O_RDONLY);
        if (fd < 0) {
        perror("Failed to open NVMEM device for verification");
        return 1;
        }

        /* Set file offset to 0 */
        lseek(fd, 0, SEEK_SET);

        ret = read(fd, read_data, NVMEM_SIZE);
        if (ret < 0) {
        perror("Failed to read back NVMEM data");
        close(fd);
        return 1;
        }

        /* Compare written data with read data */
        if (memcmp(write_data, read_data, NVMEM_SIZE) == 0) {
        printf("✓ Write verification PASSED\\n");
        } else {
        printf("✗ Write verification FAILED\\n");

        /* Show mismatch details */
        int match_count = 0;
        int mismatch_count = 0;
        for (i = 0; i < NVMEM_SIZE; i++) {
        if (write_data[i] == read_data[i]) {
        match_count++;
        } else {
        mismatch_count++;
        /* Only show first 10 mismatches to avoid too much output */
        if (mismatch_count <= 10) {
        printf(" Mismatch at offset 0x%02X: expected 0x%02X, got 0x%02X\\n",
        i, write_data[i], read_data[i]);
        }
        }
        }
        if (mismatch_count > 10) {
        printf(" … and %d more mismatches\\n", mismatch_count 10);
        }
        printf(" Total: %d matches, %d mismatches\\n", match_count, mismatch_count);
        }

        close(fd);
        return 0;
        }

        static int nvmem_verify_test(void)
        {
        int fd;
        uint8_t test_data[56];
        uint8_t read_back[56];
        ssize_t ret;
        int i;

        printf("\\n=== NVMEM Verification Test ===\\n");

        for (i = 0; i < 56; i++) {
        test_data[i] = i % 256;
        }

        /* Write test pattern */
        fd = open(NVMEM_PATH, O_WRONLY);
        if (fd < 0) {
        perror("Failed to open NVMEM device");
        return 1;
        }

        lseek(fd, 0, SEEK_SET); /* Seek to beginning */
        ret = write(fd, test_data, sizeof(test_data));
        if (ret < 0) {
        perror("Failed to write verification data");
        close(fd);
        return 1;
        }

        close(fd);

        /* Read back and verify */
        fd = open(NVMEM_PATH, O_RDONLY);
        if (fd < 0) {
        perror("Failed to open NVMEM device for verification");
        return 1;
        }

        lseek(fd, 0, SEEK_SET); /* Seek to beginning */
        ret = read(fd, read_back, sizeof(read_back));
        if (ret < 0) {
        perror("Failed to read verification data");
        close(fd);
        return 1;
        }

        printf("Verification Data:\\n");
        printf(" Expected: ");
        for (i = 0; i < sizeof(test_data); i++) {
        if(i%8 == 0 && i!=0)
        printf("\\n");
        printf("%02X ", test_data[i]);
        }
        printf("\\n");

        printf(" Actual: ");
        for (i = 0; i < sizeof(read_back); i++) {
        if(i%8 == 0 && i!=0)
        printf("\\n");
        printf("%02X ", read_back[i]);
        }
        printf("\\n");

        /* Compare */
        if (memcmp(test_data, read_back, sizeof(test_data)) == 0) {
        printf("✓ Data verification PASSED\\n");
        } else {
        printf("✗ Data verification FAILED\\n");
        }

        close(fd);
        return 0;
        }

        void print_help(void)
        {
        printf("DS1307 Test Application\\n");
        printf("Usage: %s <command> [arguments]\\n\\n", "test");
        printf("Commands:\\n");
        printf(" read – Read RTC time\\n");
        printf(" write <y> <m> <d> <wd> <h> <mi> <s> – Write RTC time\\n");
        printf(" sync – Sync system time to RTC\\n");
        printf(" nvmem-read – Read and display NVMEM data\\n");
        printf(" nvmem-write – Write test data to NVMEM\\n");
        printf(" nvmem-verify – Verify NVMEM read/write functionality\\n");
        printf(" help – Show this help message\\n\\n");
        }

        int main(int argc, char *argv[])
        {
        if (argc < 2) {
        print_help();
        return 1;
        }

        if(strcmp(argv[1], "read") == 0) {
        return read_time();
        }
        else if(strcmp(argv[1], "write") == 0) {
        if (argc != 9) {
        printf("Error: write command requires 7 arguments (year month day weekday hour minute second)\\n");
        return 1;
        }
        int year = atoi(argv[2]);
        int mon = atoi(argv[3]);
        int mday = atoi(argv[4]);
        int wday = atoi(argv[5]);
        int hour = atoi(argv[6]);
        int min = atoi(argv[7]);
        int sec = atoi(argv[8]);

        return write_time(year, mon, mday, wday, hour, min, sec);
        }
        else if(strcmp(argv[1], "sync") == 0) {
        return sync_time();
        }
        else if(strcmp(argv[1], "nvmem-read") == 0) {
        return nvmem_read_test();
        }
        else if(strcmp(argv[1], "nvmem-write") == 0) {
        return nvmem_write_test();
        }
        else if(strcmp(argv[1], "nvmem-verify") == 0) {
        return nvmem_verify_test();
        }
        else if(strcmp(argv[1], "help") == 0) {
        print_help();
        return 0;
        }
        else {
        printf("Error: Unknown command '%s'\\n", argv[1]);
        print_help();
        return 1;
        }
        return 0;
        }

      • 测试现象

      • 写入

        image

      • 读取

        image

      • 完全校验

        image

    2.4 V2.0:使用Regmap实现寄存器访问

    • ​​Regmap​​简介

      ​Regmap是Linux内核提供的一个寄存器映射框架,用于抽象不同类型设备的寄存器访问方式。它的主要作用包括:

    • 统一API :为不同总线(I2C、SPI、MMIO等)提供一致的寄存器访问接口
    • 减少重复代码 :避免在每个驱动中重复实现相似的寄存器访问逻辑
    • 自动缓存 :支持寄存器值的缓存,减少实际硬件访问次数
    • 同步管理 :自动处理寄存器访问的同步和锁定
    • 字节序转换 :自动处理不同设备的字节序差异
    • 调试支持 :提供内置的调试功能,方便开发和故障排查
    • 源码

      • 主要修改

      • 定义Regmap结构

        image

      • ​ds1307_dev​结构体新增成员regmap

      • ​probe​函数中初始化regmap​(在check_init之前)

        image

      • 替换寄存器访问函数

        image

      • 删除或屏蔽原寄存器访问函数

        image

    • 测试

    • 读写字符设备节点/dev/rtc_ds1307测试

      image

    • 通过hwclock​测试rtc设备

      image

    • 测试NV SRAM

      image

    • ​Regmap调试节点

      image

      生成调试节点,但默认为只读

    三 问题 && 经验

    3.1 设备驱动匹配

  • 当驱动中配置compatible​为fog,ds1307时,设备树同步修改

  • 此时原先驱动rtc-ds1307.c仍编译进内核

  • 进入系统后,发现设备与驱动仍能匹配上

    image

  • 根本原因

  • ​rtc-ds1307.c​中,id表中有ds1307

    image

  • 设备与驱动的匹配次序为of_mach​—>ACPI​—>id_table

  • ​id_table匹配时,使用的匹配项为设备的名称

  • 在设备树中的节点转化为device_node​节点过程中,会取compatible​中逗号后的字符作为设备的name​,即“ds1307”

  • 此时,设备与驱动完成匹配

  • 3.2 SMBus函数限制

  • 在V1.0​和V1.1​版本中,ds1307_read_regs​函数与ds1307_write_regs​函数的使用i2c_smbus_read_i2c_block_data​和i2c_smbus_write_i2c_block_data进行通信

  • ​​SMBus​​函数有严格的32字节限制,导致如果一次性发送大于32字节的数据,无法正确发送

    image

  • 测试现象

  • 在V1.1​基础,引入NVMEM​子系统(仍使用SMBus函数实现数据通信),并对节点进行测试

  • 测试一次发大于32字节数据,实际只有前32位生效

    hexdump C /sys/bus/nvmem/devices/ds1307_nvram0/nvmem
    echo "01234567890123456789012345678901234567890123456789" > /sys/bus/nvmem/devices/ds1307_nvram0/nvmem

    image

  • 实际后续的24字节可正常使用,通过加入偏移值写入

    echo ne "012345678901234567890123" | dd of=/sys/bus/nvmem/devices/ds1307_nvram0/nvmem bs=1 seek=32 conv=notrunc

    image

  • ​V1.2修改实现后,测试现象如下

  • 单次写入超出32字节数据

    正常写入

    image

  • 赞(0)
    未经允许不得转载:171主机测评 » 03-I2C设备驱动开发(DS1307)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址