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地址interrupts和interrupt-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接口添加设备树支持,需要:
- 定义SPI版本的设备树匹配表
- 在SPI驱动结构体中关联该匹配表
- 在
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 技术特点
-
双接口支持:同时支持I2C和SPI接口
-
高精度时间:内置温度补偿晶体振荡器
-
丰富的功能集:
- 实时时钟功能
- 闹钟功能(支持一个闹钟)
- 温度传感器(0.25°C分辨率)
- 236字节非易失性SRAM
- 中断输出
-
电源管理:
- 支持系统睡眠/唤醒
- 电池备份功能
- 低功耗模式
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驱动开发提供了很好的参考。