Linux内核时钟芯片DS3232驱动源码分析

Linux内核时钟芯片DS3232驱动源码分析

1. 获取DS3232 RTC驱动源码

1.1 源码位置

DS3232是一款高精度实时时钟芯片,支持I2C和SPI接口,其驱动在Linux内核中的位置:

复制代码
drivers/rtc/rtc-ds3232.c

1.2 获取方法

方法1:从内核源码树获取

bash 复制代码
# 克隆Linux内核源码
git clone https://github.com/torvalds/linux.git
cd linux

# 查看DS3232驱动
cat drivers/rtc/rtc-ds3232.c

方法2:在线查看

https://elixir.bootlin.com/linux/latest/source/drivers/rtc/rtc-ds3232.c

2. 驱动源码详细分析

2.1 模块头文件和宏定义

c 复制代码
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * RTC client/driver for the Maxim/Dallas DS3232/DS3234 Real-Time Clock
 *
 * Copyright (C) 2009-2011 Freescale Semiconductor.
 * Author: Jack Lan <jack.lan@freescale.com>
 * Copyright (C) 2008 MIMOMax Wireless Ltd.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#include <linux/hwmon.h>

2.2 寄存器定义

c 复制代码
#define DS3232_REG_SECONDS      0x00
#define DS3232_REG_MINUTES      0x01
#define DS3232_REG_HOURS        0x02
#define DS3232_REG_AMPM         0x02
#define DS3232_REG_DAY          0x03
#define DS3232_REG_DATE         0x04
#define DS3232_REG_MONTH        0x05
#define DS3232_REG_CENTURY      0x05
#define DS3232_REG_YEAR         0x06
#define DS3232_REG_ALARM1       0x07       /* Alarm 1 BASE */
#define DS3232_REG_ALARM2       0x0B       /* Alarm 2 BASE */
#define DS3232_REG_CR           0x0E       /* Control register */
#       define DS3232_REG_CR_nEOSC   0x80
#       define DS3232_REG_CR_INTCN   0x04
#       define DS3232_REG_CR_A2IE    0x02
#       define DS3232_REG_CR_A1IE    0x01

#define DS3232_REG_SR           0x0F       /* control/status register */
#       define DS3232_REG_SR_OSF     0x80
#       define DS3232_REG_SR_BSY     0x04
#       define DS3232_REG_SR_A2F     0x02
#       define DS3232_REG_SR_A1F     0x01

#define DS3232_REG_TEMPERATURE	0x11
#define DS3232_REG_SRAM_START   0x14
#define DS3232_REG_SRAM_END     0xFF

#define DS3232_REG_SRAM_SIZE    236

2.3 设备私有数据结构

c 复制代码
struct ds3232 {
    struct device *dev;
    struct regmap *regmap;
    int irq;
    struct rtc_device *rtc;

    bool suspended;
};

2.4 寄存器映射操作

驱动使用regmap API进行寄存器访问,而不是直接的SPI/I2C操作,这提供了更好的抽象和兼容性。

2.5 RTC状态检查函数

c 复制代码
static int ds3232_check_rtc_status(struct device *dev)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    int ret = 0;
    int control, stat;

    ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat);
    if (ret)
        return ret;

    if (stat & DS3232_REG_SR_OSF)
        dev_warn(dev,
                "oscillator discontinuity flagged, "
                "time unreliable\n");

    stat &= ~(DS3232_REG_SR_OSF | DS3232_REG_SR_A1F | DS3232_REG_SR_A2F);

    ret = regmap_write(ds3232->regmap, DS3232_REG_SR, stat);
    if (ret)
        return ret;

    /* If the alarm is pending, clear it before requesting
     * the interrupt, so an interrupt event isn't reported
     * before everything is initialized.
     */

    ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
    if (ret)
        return ret;

    control &= ~(DS3232_REG_CR_A1IE | DS3232_REG_CR_A2IE);
    control |= DS3232_REG_CR_INTCN;

    return regmap_write(ds3232->regmap, DS3232_REG_CR, control);
}

2.6 时间读取函数

c 复制代码
static int ds3232_read_time(struct device *dev, struct rtc_time *time)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    int ret;
    u8 buf[7];
    unsigned int year, month, day, hour, minute, second;
    unsigned int week, twelve_hr, am_pm;
    unsigned int century, add_century = 0;

    ret = regmap_bulk_read(ds3232->regmap, DS3232_REG_SECONDS, buf, 7);
    if (ret)
        return ret;

    second = buf[0];
    minute = buf[1];
    hour = buf[2];
    week = buf[3];
    day = buf[4];
    month = buf[5];
    year = buf[6];

    /* Extract additional information for AM/PM and century */

    twelve_hr = hour & 0x40;
    am_pm = hour & 0x20;
    century = month & 0x80;

    /* Write to rtc_time structure */

    time->tm_sec = bcd2bin(second);
    time->tm_min = bcd2bin(minute);
    if (twelve_hr) {
        /* Convert to 24 hr */
        if (am_pm)
            time->tm_hour = bcd2bin(hour & 0x1F) + 12;
        else
            time->tm_hour = bcd2bin(hour & 0x1F);
    } else {
        time->tm_hour = bcd2bin(hour);
    }

    /* Day of the week in linux range is 0~6 while 1~7 in RTC chip */
    time->tm_wday = bcd2bin(week) - 1;
    time->tm_mday = bcd2bin(day);
    /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */
    time->tm_mon = bcd2bin(month & 0x7F) - 1;
    if (century)
        add_century = 100;

    time->tm_year = bcd2bin(year) + add_century;

    return 0;
}

2.7 时间设置函数

c 复制代码
static int ds3232_set_time(struct device *dev, struct rtc_time *time)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    u8 buf[7];

    /* Extract time from rtc_time and load into ds3232*/

    buf[0] = bin2bcd(time->tm_sec);
    buf[1] = bin2bcd(time->tm_min);
    buf[2] = bin2bcd(time->tm_hour);
    /* Day of the week in linux range is 0~6 while 1~7 in RTC chip */
    buf[3] = bin2bcd(time->tm_wday + 1);
    buf[4] = bin2bcd(time->tm_mday); /* Date */
    /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */
    buf[5] = bin2bcd(time->tm_mon + 1);
    if (time->tm_year >= 100) {
        buf[5] |= 0x80;
        buf[6] = bin2bcd(time->tm_year - 100);
    } else {
        buf[6] = bin2bcd(time->tm_year);
    }

    return regmap_bulk_write(ds3232->regmap, DS3232_REG_SECONDS, buf, 7);
}

2.8 闹钟功能

2.8.1 闹钟读取函数
c 复制代码
/*
 * DS3232 has two alarm, we only use alarm1
 * According to linux specification, only support one-shot alarm
 * no periodic alarm mode
 */
static int ds3232_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    int control, stat;
    int ret;
    u8 buf[4];

    ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat);
    if (ret)
        goto out;
    ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
    if (ret)
        goto out;
    ret = regmap_bulk_read(ds3232->regmap, DS3232_REG_ALARM1, buf, 4);
    if (ret)
        goto out;

    alarm->time.tm_sec = bcd2bin(buf[0] & 0x7F);
    alarm->time.tm_min = bcd2bin(buf[1] & 0x7F);
    alarm->time.tm_hour = bcd2bin(buf[2] & 0x7F);
    alarm->time.tm_mday = bcd2bin(buf[3] & 0x7F);

    alarm->enabled = !!(control & DS3232_REG_CR_A1IE);
    alarm->pending = !!(stat & DS3232_REG_SR_A1F);

    ret = 0;
out:
    return ret;
}
2.8.2 闹钟设置函数
c 复制代码
/*
 * linux rtc-module does not support wday alarm
 * and only 24h time mode supported indeed
 */
static int ds3232_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    int control, stat;
    int ret;
    u8 buf[4];

    if (ds3232->irq <= 0)
        return -EINVAL;

    buf[0] = bin2bcd(alarm->time.tm_sec);
    buf[1] = bin2bcd(alarm->time.tm_min);
    buf[2] = bin2bcd(alarm->time.tm_hour);
    buf[3] = bin2bcd(alarm->time.tm_mday);

    /* clear alarm interrupt enable bit */
    ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
    if (ret)
        goto out;
    control &= ~(DS3232_REG_CR_A1IE | DS3232_REG_CR_A2IE);
    ret = regmap_write(ds3232->regmap, DS3232_REG_CR, control);
    if (ret)
        goto out;

    /* clear any pending alarm flag */
    ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat);
    if (ret)
        goto out;
    stat &= ~(DS3232_REG_SR_A1F | DS3232_REG_SR_A2F);
    ret = regmap_write(ds3232->regmap, DS3232_REG_SR, stat);
    if (ret)
        goto out;

    ret = regmap_bulk_write(ds3232->regmap, DS3232_REG_ALARM1, buf, 4);
    if (ret)
        goto out;

    if (alarm->enabled) {
        control |= DS3232_REG_CR_A1IE;
        ret = regmap_write(ds3232->regmap, DS3232_REG_CR, control);
    }
out:
    return ret;
}
2.8.3 闹钟中断更新函数
c 复制代码
static int ds3232_update_alarm(struct device *dev, unsigned int enabled)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    int control;
    int ret;

    ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
    if (ret)
        return ret;

    if (enabled)
        /* enable alarm1 interrupt */
        control |= DS3232_REG_CR_A1IE;
    else
        /* disable alarm1 interrupt */
        control &= ~(DS3232_REG_CR_A1IE);
    ret = regmap_write(ds3232->regmap, DS3232_REG_CR, control);

    return ret;
}

2.9 温度传感器功能

c 复制代码
/*
 * Temperature sensor support for ds3232/ds3234 devices.
 * A user-initiated temperature conversion is not started by this function,
 * so the temperature is updated once every 64 seconds.
 */
static int ds3232_hwmon_read_temp(struct device *dev, long int *mC)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    u8 temp_buf[2];
    s16 temp;
    int ret;

    ret = regmap_bulk_read(ds3232->regmap, DS3232_REG_TEMPERATURE, temp_buf,
                       sizeof(temp_buf));
    if (ret < 0)
        return ret;

    /*
     * Temperature is represented as a 10-bit code with a resolution of
     * 0.25 degree celsius and encoded in two's complement format.
     */
    temp = (temp_buf[0] << 8) | temp_buf[1];
    temp >>= 6;
    *mC = temp * 250;

    return 0;
}

2.10 HWMON接口

c 复制代码
static umode_t ds3232_hwmon_is_visible(const void *data,
                                   enum hwmon_sensor_types type,
                                   u32 attr, int channel)
{
    if (type != hwmon_temp)
        return 0;

    switch (attr) {
    case hwmon_temp_input:
        return 0444;
    default:
        return 0;
    }
}

static int ds3232_hwmon_read(struct device *dev,
                         enum hwmon_sensor_types type,
                         u32 attr, int channel, long *temp)
{
    int err;

    switch (attr) {
    case hwmon_temp_input:
        err = ds3232_hwmon_read_temp(dev, temp);
        break;
    default:
        err = -EOPNOTSUPP;
        break;
    }

    return err;
}

static const struct hwmon_channel_info * const ds3232_hwmon_info[] = {
    HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
    HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
    NULL
};

static const struct hwmon_ops ds3232_hwmon_hwmon_ops = {
    .is_visible = ds3232_hwmon_is_visible,
    .read = ds3232_hwmon_read,
};

static const struct hwmon_chip_info ds3232_hwmon_chip_info = {
    .ops = &ds3232_hwmon_hwmon_ops,
    .info = ds3232_hwmon_info,
};

static void ds3232_hwmon_register(struct device *dev, const char *name)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    struct device *hwmon_dev;

    if (!IS_ENABLED(CONFIG_RTC_DRV_DS3232_HWMON))
        return;

    hwmon_dev = devm_hwmon_device_register_with_info(dev, name, ds3232,
                                                &ds3232_hwmon_chip_info,
                                                NULL);
    if (IS_ERR(hwmon_dev)) {
        dev_err(dev, "unable to register hwmon device %ld\n",
            PTR_ERR(hwmon_dev));
    }
}

2.11 RTC操作集

c 复制代码
static const struct rtc_class_ops ds3232_rtc_ops = {
    .read_time = ds3232_read_time,
    .set_time = ds3232_set_time,
    .read_alarm = ds3232_read_alarm,
    .set_alarm = ds3232_set_alarm,
    .alarm_irq_enable = ds3232_alarm_irq_enable,
};

2.12 中断处理函数

c 复制代码
static irqreturn_t ds3232_irq(int irq, void *dev_id)
{
    struct device *dev = dev_id;
    struct ds3232 *ds3232 = dev_get_drvdata(dev);
    int ret;
    int stat, control;

    rtc_lock(ds3232->rtc);

    ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat);
    if (ret)
        goto unlock;

    if (stat & DS3232_REG_SR_A1F) {
        ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
        if (ret) {
            dev_warn(ds3232->dev,
                 "Read Control Register error %d\n", ret);
        } else {
            /* disable alarm1 interrupt */
            control &= ~(DS3232_REG_CR_A1IE);
            ret = regmap_write(ds3232->regmap, DS3232_REG_CR,
                       control);
            if (ret) {
                dev_warn(ds3232->dev,
                     "Write Control Register error %d\n",
                     ret);
                goto unlock;
            }

            /* clear the alarm pend flag */
            stat &= ~DS3232_REG_SR_A1F;
            ret = regmap_write(ds3232->regmap, DS3232_REG_SR, stat);
            if (ret) {
                dev_warn(ds3232->dev,
                     "Write Status Register error %d\n",
                     ret);
                goto unlock;
            }

            rtc_update_irq(ds3232->rtc, 1, RTC_AF | RTC_IRQF);
        }
    }

unlock:
    rtc_unlock(ds3232->rtc);

    return IRQ_HANDLED;
}

2.13 非易失性内存(NVMEM)支持

c 复制代码
static int ds3232_nvmem_read(void *priv, unsigned int offset, void *val,
                         size_t bytes)
{
    struct regmap *ds3232_regmap = (struct regmap *)priv;

    return regmap_bulk_read(ds3232_regmap, DS3232_REG_SRAM_START + offset,
                val, bytes);
}

static int ds3232_nvmem_write(void *priv, unsigned int offset, void *val,
                      size_t bytes)
{
    struct regmap *ds3232_regmap = (struct regmap *)priv;

    return regmap_bulk_write(ds3232_regmap, DS3232_REG_SRAM_START + offset,
                 val, bytes);
}

2.14 探测函数

c 复制代码
static int ds3232_probe(struct device *dev, struct regmap *regmap, int irq,
            const char *name)
{
    struct ds3232 *ds3232;
    int ret;
    struct nvmem_config nvmem_cfg = {
        .name = "ds3232_sram",
        .stride = 1,
        .size = DS3232_REG_SRAM_SIZE,
        .word_size = 1,
        .reg_read = ds3232_nvmem_read,
        .reg_write = ds3232_nvmem_write,
        .priv = regmap,
        .type = NVMEM_TYPE_BATTERY_BACKED
    };

    ds3232 = devm_kzalloc(dev, sizeof(*ds3232), GFP_KERNEL);
    if (!ds3232)
        return -ENOMEM;

    ds3232->regmap = regmap;
    ds3232->irq = irq;
    ds3232->dev = dev;
    dev_set_drvdata(dev, ds3232);

    ret = ds3232_check_rtc_status(dev);
    if (ret)
        return ret;

    if (ds3232->irq > 0)
        device_init_wakeup(dev, true);

    ds3232_hwmon_register(dev, name);

    ds3232->rtc = devm_rtc_device_register(dev, name, &ds3232_rtc_ops,
                        THIS_MODULE);
    if (IS_ERR(ds3232->rtc))
        return PTR_ERR(ds3232->rtc);

    ret = devm_rtc_nvmem_register(ds3232->rtc, &nvmem_cfg);
    if(ret)
        return ret;

    if (ds3232->irq > 0) {
        ret = devm_request_threaded_irq(dev, ds3232->irq, NULL,
                            ds3232_irq,
                            IRQF_SHARED | IRQF_ONESHOT,
                            name, dev);
        if (ret) {
            device_set_wakeup_capable(dev, 0);
            ds3232->irq = 0;
            dev_err(dev, "unable to request IRQ\n");
        }
    }

    return 0;
}

2.14.1 设备树关联分析

DS3232驱动通过设备树机制实现了与硬件的灵活绑定。以下是驱动中与设备树相关的关键代码分析:

设备树匹配表定义
c 复制代码
static const __maybe_unused struct of_device_id ds3232_of_match[] = {
    { .compatible = "dallas,ds3232" },
    { }
};
MODULE_DEVICE_TABLE(of, ds3232_of_match);

这是设备树匹配表,定义了驱动支持的设备类型。.compatible字段指定了设备树中设备节点的compatible属性值,用于匹配硬件设备。MODULE_DEVICE_TABLE(of, ds3232_of_match)宏将该匹配表导出到内核,以便设备树核心能够找到并匹配相应的设备。

I2C驱动结构体与设备树的关联
c 复制代码
static struct i2c_driver ds3232_driver = {
    .driver = {
        .name = "rtc-ds3232",
        .of_match_table = of_match_ptr(ds3232_of_match),
        .pm = &ds3232_pm_ops,
    },
    .probe = ds3232_i2c_probe,
    .id_table = ds3232_id,
};

在I2C驱动结构体中,.driver.of_match_table字段关联了前面定义的设备树匹配表,使驱动能够通过设备树机制进行匹配。

I2C探测函数与设备树参数传递
c 复制代码
static int ds3232_i2c_probe(struct i2c_client *client)
{
    struct regmap *regmap;
    static const struct regmap_config config = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = DS3232_REG_SRAM_END,
    };

    regmap = devm_regmap_init_i2c(client, &config);
    if (IS_ERR(regmap)) {
        dev_err(&client->dev, "%s: regmap allocation failed: %ld\n",
            __func__, PTR_ERR(regmap));
        return PTR_ERR(regmap);
    }

    return ds3232_probe(&client->dev, regmap, client->irq, client->name);
}

当设备树匹配成功后,ds3232_i2c_probe函数被调用。该函数通过i2c_client结构体获取设备信息,包括:

  • 设备树中定义的中断号(client->irq
  • 设备名称(client->name

这些参数被传递给通用的ds3232_probe函数,完成设备初始化。

设备树示例

DS3232设备在设备树中的典型定义如下:

dts 复制代码
rtc@68 {
    compatible = "dallas,ds3232";
    reg = <0x68>; /* I2C地址 */
    interrupts = <50 IRQ_TYPE_EDGE_FALLING>;
    interrupt-parent = <&gpio>;
};
  • compatible属性与驱动中的匹配表对应
  • reg属性指定设备的I2C地址
  • interruptsinterrupt-parent属性定义了中断源
SPI接口的设备树支持情况

需要注意的是,当前驱动版本中的SPI接口部分(DS3234)没有实现设备树支持:

c 复制代码
static struct spi_driver ds3234_driver = {
    .driver = {
        .name = "ds3234",
        /* 缺少 .of_match_table 字段 */
    },
    .probe = ds3234_probe,
};

与I2C接口不同,SPI驱动结构体中没有关联设备树匹配表。这意味着DS3234(SPI版本)只能通过传统的设备ID匹配方式工作,而不能通过设备树进行动态匹配和配置。

如果要为SPI接口添加设备树支持,需要:

  1. 定义SPI版本的设备树匹配表
  2. 在SPI驱动结构体中关联该匹配表
  3. ds3234_probe函数中处理来自设备树的参数

2.15 I2C接口支持

c 复制代码
#if IS_ENABLED(CONFIG_I2C)

#ifdef CONFIG_PM_SLEEP
static int ds3232_suspend(struct device *dev)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);

    if (device_may_wakeup(dev)) {
        if (enable_irq_wake(ds3232->irq))
            dev_warn_once(dev, "Cannot set wakeup source\n");
    }

    return 0;
}

static int ds3232_resume(struct device *dev)
{
    struct ds3232 *ds3232 = dev_get_drvdata(dev);

    if (device_may_wakeup(dev))
        disable_irq_wake(ds3232->irq);

    return 0;
}
#endif

static const struct dev_pm_ops ds3232_pm_ops = {
    SET_SYSTEM_SLEEP_PM_OPS(ds3232_suspend, ds3232_resume)
};

static int ds3232_i2c_probe(struct i2c_client *client)
{
    struct regmap *regmap;
    static const struct regmap_config config = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = DS3232_REG_SRAM_END,
    };

    regmap = devm_regmap_init_i2c(client, &config);
    if (IS_ERR(regmap)) {
        dev_err(&client->dev, "%s: regmap allocation failed: %ld\n",
            __func__, PTR_ERR(regmap));
        return PTR_ERR(regmap);
    }

    return ds3232_probe(&client->dev, regmap, client->irq, client->name);
}

static const struct i2c_device_id ds3232_id[] = {
    { "ds3232" },
    { }
};
MODULE_DEVICE_TABLE(i2c, ds3232_id);

static const  __maybe_unused struct of_device_id ds3232_of_match[] = {
    { .compatible = "dallas,ds3232" },
    { }
};
MODULE_DEVICE_TABLE(of, ds3232_of_match);

static struct i2c_driver ds3232_driver = {
    .driver = {
        .name = "rtc-ds3232",
        .of_match_table = of_match_ptr(ds3232_of_match),
        .pm	= &ds3232_pm_ops,
    },
    .probe = ds3232_i2c_probe,
    .id_table = ds3232_id,
};

static int ds3232_register_driver(void)
{
    return i2c_add_driver(&ds3232_driver);
}

static void ds3232_unregister_driver(void)
{
    i2c_del_driver(&ds3232_driver);
}

#else

static int ds3232_register_driver(void)
{
    return 0;
}

static void ds3232_unregister_driver(void)
{
}

#endif

2.16 SPI接口支持

c 复制代码
#if IS_ENABLED(CONFIG_SPI_MASTER)

static int ds3234_probe(struct spi_device *spi)
{
    int res;
    unsigned int tmp;
    static const struct regmap_config config = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = DS3232_REG_SRAM_END,
        .write_flag_mask = 0x80,
    };
    struct regmap *regmap;

    regmap = devm_regmap_init_spi(spi, &config);
    if (IS_ERR(regmap)) {
        dev_err(&spi->dev, "%s: regmap allocation failed: %ld\n",
            __func__, PTR_ERR(regmap));
        return PTR_ERR(regmap);
    }

    spi->mode = SPI_MODE_3;
    spi->bits_per_word = 8;
    spi_setup(spi);

    res = regmap_read(regmap, DS3232_REG_SECONDS, &tmp);
    if (res)
        return res;

    /* Control settings
     *
     * CONTROL_REG
     * BIT 7	6	5	4	3	2	1	0
     *     EOSC	BBSQW	CONV	RS2	RS1	INTCN	A2IE	A1IE
     *
     *     0	0	0	1	1	1	0	0
     *
     * CONTROL_STAT_REG
     * BIT 7	6	5	4	3	2	1	0
     *     OSF	BB32kHz	CRATE1	CRATE0	EN32kHz	BSY	A2F	A1F
     *
     *     1	0	0	0	1	0	0	0
     */
    res = regmap_read(regmap, DS3232_REG_CR, &tmp);
    if (res)
        return res;
    res = regmap_write(regmap, DS3232_REG_CR, tmp & 0x1c);
    if (res)
        return res;

    res = regmap_read(regmap, DS3232_REG_SR, &tmp);
    if (res)
        return res;
    res = regmap_write(regmap, DS3232_REG_SR, tmp & 0x88);
    if (res)
        return res;

    /* Print our settings */
    res = regmap_read(regmap, DS3232_REG_CR, &tmp);
    if (res)
        return res;
    dev_info(&spi->dev, "Control Reg: 0x%02x\n", tmp);

    res = regmap_read(regmap, DS3232_REG_SR, &tmp);
    if (res)
        return res;
    dev_info(&spi->dev, "Ctrl/Stat Reg: 0x%02x\n", tmp);

    return ds3232_probe(&spi->dev, regmap, spi->irq, "ds3234");
}

static struct spi_driver ds3234_driver = {
    .driver = {
        .name	 = "ds3234",
    },
    .probe	 = ds3234_probe,
};

static int ds3234_register_driver(void)
{
    return spi_register_driver(&ds3234_driver);
}

static void ds3234_unregister_driver(void)
{
    spi_unregister_driver(&ds3234_driver);
}

#else

static int ds3234_register_driver(void)
{
    return 0;
}

static void ds3234_unregister_driver(void)
{
}

#endif

2.17 模块初始化与退出

c 复制代码
static int __init ds323x_init(void)
{
    int ret;

    ret = ds3232_register_driver();
    if (ret) {
        pr_err("Failed to register ds3232 driver: %d\n", ret);
        return ret;
    }

    ret = ds3234_register_driver();
    if (ret) {
        pr_err("Failed to register ds3234 driver: %d\n", ret);
        ds3232_unregister_driver();
    }

    return ret;
}
module_init(ds323x_init)

static void __exit ds323x_exit(void)
{
    ds3234_unregister_driver();
    ds3232_unregister_driver();
}
module_exit(ds323x_exit)

MODULE_AUTHOR("Srikanth Srinivasan <srikanth.srinivasan@freescale.com>");
MODULE_AUTHOR("Dennis Aberilla <denzzzhome@yahoo.com>");
MODULE_DESCRIPTION("Maxim/Dallas DS3232/DS3234 RTC Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:ds3234");

3. 设备树绑定示例

3.1 I2C接口设备树示例

dts 复制代码
/* DS3232 I2C设备树节点示例 */
i2c0 {
    status = "okay";
    
    rtc@68 {
        compatible = "dallas,ds3232";
        reg = <0x68>;                     /* I2C地址 */
        interrupt-parent = <&gpio>;
        interrupts = <17 IRQ_TYPE_EDGE_FALLING>; /* 中断引脚 */
    };
};

3.2 SPI接口设备树示例

dts 复制代码
/* DS3234 SPI设备树节点示例 */
spi0 {
    status = "okay";
    
    rtc@0 {
        compatible = "dallas,ds3234";
        reg = <0>;                     /* CS0 */
        spi-max-frequency = <5000000>; /* 最大SPI频率5MHz */
        spi-cpol;\                      /* 时钟极性1 */
        spi-cpha;\                      /* 时钟相位1 */
        interrupt-parent = <&gpio>;
        interrupts = <17 IRQ_TYPE_EDGE_FALLING>; /* 中断引脚 */
    };
};

4. 驱动使用示例

4.1 加载驱动模块

bash 复制代码
# 加载I2C核心模块
modprobe i2c-dev

# 加载DS3232驱动
modprobe rtc-ds3232

# 查看注册的RTC设备
ls /sys/class/rtc/
cat /proc/driver/rtc

4.2 用户空间访问

bash 复制代码
# 设置系统时间到RTC
hwclock -w

# 从RTC读取时间到系统
hwclock -s

# 查看RTC时间
hwclock -r

# 通过sysfs查看信息
cat /sys/class/rtc/rtc0/date
cat /sys/class/rtc/rtc0/time
cat /sys/class/rtc/rtc0/since_epoch

# 查看温度(如果支持)
cat /sys/class/hwmon/hwmon*/temp1_input

5. 驱动关键设计分析

5.1 架构设计

  • 使用regmap API进行寄存器访问,提供统一的I2C/SPI接口抽象
  • 支持RTC核心功能:时间读写、闹钟设置、中断处理
  • 集成温度传感器功能,通过hwmon接口暴露
  • 支持非易失性SRAM访问
  • 完整的电源管理支持

5.2 技术特点

  1. 双接口支持:同时支持I2C和SPI接口

  2. 高精度时间:内置温度补偿晶体振荡器

  3. 丰富的功能集

    • 实时时钟功能
    • 闹钟功能(支持一个闹钟)
    • 温度传感器(0.25°C分辨率)
    • 236字节非易失性SRAM
    • 中断输出
  4. 电源管理

    • 支持系统睡眠/唤醒
    • 电池备份功能
    • 低功耗模式

6. 与其他RTC驱动的比较

特性 DS3232 DS1307 PCF8563
接口 I2C/SPI I2C I2C
温度补偿
精度 ±2ppm ±2ppm ±10ppm
SRAM 236字节 56字节 2字节
温度传感器
闹钟 2个 1个 1个
电源电压 2.3V-5.5V 2.0V-5.5V 1.0V-5.5V

7. 总结

DS3232驱动是一个功能完整、设计优良的RTC驱动实现,具有以下特点:

  • 完整的功能实现:支持时间读写、闹钟设置、温度读取等所有DS3232芯片功能
  • 优秀的架构设计:使用regmap抽象寄存器访问,支持双接口,模块化结构清晰
  • 良好的兼容性:支持设备树,与Linux RTC子系统无缝集成
  • 丰富的扩展功能:支持hwmon温度传感器接口和nvmem SRAM访问
  • 完善的电源管理:支持系统睡眠/唤醒和中断唤醒功能

这个驱动展示了Linux内核驱动开发的最佳实践,特别是在设备抽象、资源管理和接口设计方面,为其他RTC驱动开发提供了很好的参考。

相关推荐
deriva2 小时前
windows系统安装linux并docker部署.netcore项目
linux·docker·.netcore
可爱又迷人的反派角色“yang”2 小时前
docker(五)
linux·运维·网络·docker·容器·云计算
小馬佩德罗2 小时前
如何将x264 x265的动态库编译入Linux系统中的FFmpeg源码 - FFmpeg编译
linux·ffmpeg
Tipriest_2 小时前
linux /etc/profile.d 目录介绍
linux·运维·服务器
NotStrandedYet2 小时前
CentOS停更后的新选择:图文详解安装6.x内核openEuler+GNOME图形桌面
linux·运维·信创·国产化·openeuler·国产操作系统
山上三树2 小时前
codedump
linux·服务器
boligongzhu2 小时前
ubuntu22.04 安装Docker Engine和Compose
linux·ubuntu·docker·容器
liuyunshengsir2 小时前
huggingface-cli download 断点续传
linux·hugging face·魔塔社区
小码吃趴菜2 小时前
信号与管道
linux