
清空记录
历史记录
取消
清空记录
历史记录

一、I2C 基本概念
1.1 什么是 I2C?
I2C(Inter-Integrated Circuit)是一种两线串行总线协议,由 Philips(现 NXP)在 1980 年代发明,广泛用于连接低速外设:
•SDA(Serial Data):数据线,双向传输,开漏输出
•SCL(Serial Clock):时钟线,由主设备产生,开漏输出
•外部需接上拉电阻(典型 4.7kΩ)将总线拉高
1.2 I2C 总线特点
特性 | 说明 |
线数 | 2 根(SDA + SCL),节省 PCB 空间 |
设备数量 | 最多 127 个(7 位地址)或 1023 个(10 位地址) |
速率 | 标准模式 100 kHz,快速模式 400 kHz,高速模式 3.4 MHz |
通信方式 | 主从架构,主设备(Master)发起通信并产生时钟 |
数据传输 | MSB 优先,每个字节后跟一个 ACK/NACK 位 |
1.3 I2C 与 SPI 的简要对比
特性 | I2C | SPI |
引脚数 | 2(SDA + SCL) | 3+N(SCK + MOSI + MISO + N×CS) |
速率 | ~100 kHz–3.4 MHz | ~MHz–几十 MHz |
寻址 | 软件寻址(7/10 位地址) | 硬件片选(每个设备一根 CS) |
多主支持 | 支持(冲突检测) | 不支持 |
功耗 | 较低(开漏+上拉) | 较高(推挽输出) |
适合场景 | 低复杂度、节省引脚的外设 | 高速、大批量数据传输 |
1.4 RK3576 I2C 资源
RK3576 提供 10 路 I2C 控制器(I2C0 ~ I2C9),每个控制器可通过 pinctrl 选择多个引脚复用(mux)选项:
i2c0 = &i2c0; // 通常用于 PMIC (RK806)
i2c1 = &i2c1; // 通用
i2c2 = &i2c2; // 本教程使用:RTC (hym8563)
i2c3 = &i2c3; // Audio Codec
...
i2c9 = &i2c9;二、I2C 通信协议
2.1 I2C 协议交互流程
I2C 总线上一次完整的数据传输遵循以下流程:
起始条件(S) → 7位从机地址 → R/W位 → ACK → 8位数据 → ACK → 停止条件(P)以下是主设备与从设备之间的完整协议交互序列图(含起始条件、地址发送、数据读写、应答和停止条件):
协议时序参数表:
阶段 | 时钟周期数 | 说明 |
S (起始条件) | 0 | SCL=High,SDA 下降沿 |
7位地址 | 7 个 SCL | MSB (A6) 先发,LSB (A0) 最后 |
R/W 位 | 1 个 SCL | 0 = 写操作,1 = 读操作 |
ACK | 1 个 SCL(第9个) | 从机拉低 SDA 应答 |
数据字节 | 8 个 SCL | 数据位 D7~D0,MSB 先发 |
P (停止条件) | 0 | SCL=High,SDA 上升沿 |
数据帧结构 — I2C 总线上一个完整数据帧的比特布局(共 19 个 SCL 时钟周期):
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ S │A6 │A5 │A4 │A3 │A2 │A1 │A0 │R/W│ACK│D7 │D6 │D5 │D4 │D3 │D2 │D1 │D0 │ P │
│ ↓ │◄──────── 从机地址7bit ────────►│ │◄──────── 数据8bit────────────►│ ↑ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
S ▲──── SCL 时钟采样 SDA ────▲ P
起始 每个时钟1位数据 停止
条件 条件2.2 起始条件与停止条件
这是 I2C 总线最核心的时序特征 — SDA 在 SCL 高电平时跳变,此信号在数据传输阶段被严格禁止。以下是两个关键边沿的序列图:
规则:在数据传输阶段(S 和 P 之间),SDA 只能在 SCL 低电平时变化,SCL 高电平时 SDA 必须保持稳定 — 这是 I2C 协议的根本规则。
2.3 数据有效性规则
以下是 SCL 与 SDA 配合关系的序列图:
2.4 应答机制(ACK/NACK)
每个字节传输完成后,接收方必须在第 9 个 SCL 时钟周期驱动 SDA 发送应答位。以下是应答机制的序列图:
应答 | SDA 电平 | 发送方 | 含义 |
ACK | Low | 接收方驱动 | 接收成功,继续发送下一字节 |
NACK | High | 上拉电阻拉高 | 接收失败或传输结束 |
2.5 I2C 设备地址
I2C 7 位地址由芯片厂商分配(高位)和硬件引脚电平(低位)共同决定,在总线上地址左移 1 位后附加 R/W 位发送:
7'h51 (HYM8563) → 左移1位 → 0xA2 (写) / 0xA3 (读)
bit: 6 5 4 3 2 1 0 R/W
1 0 1 0 0 0 1 0/1
└──────── 0xA2 (写) / 0xA3 (读)设备 | 7位地址 | 写地址(<<1) | 典型用途 |
HYM8563 RTC | 0x51 | 0xA2 | 本教程 RTC 芯片 |
PCF8563 RTC | 0x51 | 0xA2 | NXP 兼容 RTC |
EEPROM (AT24Cxx) | 0x50 | 0xA0 | 存储 |
ES8316 Audio Codec | 0x18 | 0x30 | 音频编解码 |
RK806 PMIC | 0x23 | 0x46 | 电源管理 |
三、HYM8563 RTC 硬件原理
3.1 HYM8563 芯片简介
HYM8563 是 Haoyu Microelectronics 生产的低功耗 RTC(实时时钟)芯片,与 NXP PCF8563 兼容,通过 I2C 接口与主控通信。
关键特性:
•I2C 总线接口(最大 400 kHz)
•计时精度依赖外部 32768 Hz 晶振
•内置秒、分、时、日、星期、月、年计数(BCD 格式)
•具有闹钟(Alarm)和定时器(Timer)功能
•可编程 CLKOUT 引脚输出时钟(32768 / 1024 / 32 / 1 Hz)
•低功耗:典型 0.25 μA(VDD=3.0V,定时器关闭)
•宽电压:1.0V ~ 5.5V
3.2 引脚定义与典型电路
应用电路:
3.3 寄存器映射表(核心)
HYM8563 内部寄存器共 16 个(0x00 ~ 0x0f),其中时间寄存器从 0x02 开始,全部采用 BCD 编码:
地址 | 名称 | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | 取值范围 |
0x00 | CTL1 | TEST | — | STOP | — | TESTC | — | — | — | — |
0x01 | CTL2 | — | — | TI/TP | AF | TF | AIE | TIE | — | — |
0x02 | SEC | VL | 秒(BCD 十位) |
|
|
| 秒(BCD 个位) |
| 00–59 |
|
0x03 | MIN | — | 分(BCD 十位) |
|
|
| 分(BCD 个位) |
| 00–59 |
|
0x04 | HOUR | — | — | 时(BCD 十位) |
|
| 时(BCD 个位) |
| 00–23 |
|
0x05 | DAY | — | — | 日(BCD 十位) |
|
| 日(BCD 个位) |
| 01–31 |
|
0x06 | WEEKDAY | — | — | — | — | — | 星期 |
| 00–06 |
|
0x07 | MONTH | C | — | — | 月(BCD 十位) |
|
| 月(BCD 个位) | 01–12 |
|
0x08 | YEAR | 年(BCD 千位+百位) |
|
| 年(BCD 十位) |
|
| 年(BCD 个位) | 00–99 |
|
0x09 | ALM_MIN | AE | — | 闹钟分(BCD) |
|
|
|
| 00–59 |
|
0x0A | ALM_HOUR | AE | — | — | 闹钟时(BCD) |
|
|
| 00–23 |
|
0x0B | ALM_DAY | AE | — | — | 闹钟日(BCD) |
|
|
| 01–31 |
|
0x0C | ALM_WEEK | AE | — | — | — | 闹钟星期 |
|
| 00–06 |
|
0x0D | CLKOUT | OE | — | — | — | — | FD1 | FD0 | — | — |
0x0E | TMR_CTL | TE | — | — | — | — | TD1 | TD0 | — | — |
0x0F | TMR_CNT | 定时器倒计数值 |
|
|
|
|
|
| 0–255 |
|
关键位说明:
位 | 所在寄存器 | 功能 |
STOP (Bit 5) | CTL1 (0x00) | 1 = RTC 停止计数,0 = 正常运行 |
VL (Bit 7) | SEC (0x02) | 0 = 时间有效,1 = 时间无效(掉电或晶振停振) |
C (Bit 7) | MONTH (0x07) | 世纪位— 但驱动实测不可靠,内核限制为 2000–2099 |
AE (Bit 7) | ALM_* (0x09~0x0C) | 1 = 该闹钟字段不参与匹配(即忽略此项) |
OE (Bit 7) | CLKOUT (0x0D) | 1 = 使能 CLKOUT 输出 |
FD[1:0] | CLKOUT (0x0D) | 00=32768Hz, 01=1024Hz, 10=32Hz, 11=1Hz |
TE (Bit 7) | TMR_CTL (0x0E) | 1 = 使能定时器 |
TD[1:0] | TMR_CTL (0x0E) | 定时器时钟源选择 |
3.4 BCD 编码说明
时间寄存器使用 BCD(Binary-Coded Decimal)编码,即每个十进制数字用 4 位二进制表示,驱动中使用bcd2bin() / bin2bcd() 完成转换:
BCD 编码示例:时间 12:34:56
秒(0x56) = 0101 0110 → 高4位(5) + 低4位(6) = 56 秒
分(0x34) = 0011 0100 → 高4位(3) + 低4位(4) = 34 分
时(0x12) = 0001 0010 → 高4位(1) + 低4位(2) = 12 时
驱动中的 BCD 转换函数(定义在 linux/bcd.h):
bcd2bin(0x56) = 5*10 + 6 = 56 // BCD → 整数
bin2bcd(34) = (3<<4) | 4 = 0x34 // 整数 → BCD四、I2C 设备树配置
4.1 使能 I2C 控制器并添加 RTC 设备
在设备树文件(如ido-evb7609-v1a.dtsi)中配置 I2C2 控制器及 HYM8563 子设备:
/* I2C2 - 挂载 HYM8563 RTC */
&i2c2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c2m0_xfer>;
hym8563: hym8563@51 {
compatible = "haoyu,hym8563"; // 驱动匹配的关键字符串
reg = <0x51>; // I2C 7 位从机地址 (7'h51)
#clock-cells = <0>; // 可作为时钟提供者
clock-frequency = <32768>; // CLKOUT 输出频率
clock-output-names = "hym8563"; // 时钟输出名称
interrupt-parent = <&gpio0>; // 中断控制器
interrupts = <RK_PC5 IRQ_TYPE_LEVEL_LOW>; // 中断引脚与触发方式
wakeup-source; // 支持系统唤醒
};
};各属性详解:
属性 | 作用 | 说明 |
compatible | 驱动匹配 | 内核据此查找of_device_id 表中匹配的驱动 |
reg | I2C 地址 | 7 位地址左对齐写入,0x51 即器件地址 |
#clock-cells | 时钟接口 | 0 表示直接输出固定频率时钟 |
clock-frequency | 输出频率 | 仅描述信息,实际由 CLKOUT 寄存器控制 |
interrupts | 中断配置 | 指定 INT 引脚对应的 GPIO 和触发类型 |
wakeup-source | 唤醒标记 | 系统 Suspend 时 RTC 闹钟可唤醒 SoC |
4.2 pinctrl 引脚复用配置
I2C2 的 SDA/SCL 引脚可以复用到不同的 GPIO 组,需要根据实际硬件原理图选择对应的 mux:
&pinctrl {
i2c2 {
/* I2C2 M0 组:SCL = GPIO1_B4, SDA = GPIO1_B3 */
i2c2m0_xfer: i2c2m0-xfer {
rockchip,pins =
<1 RK_PB4 9 &pcfg_pull_none>, /* i2c2_scl_m0 */
<1 RK_PB3 9 &pcfg_pull_none>; /* i2c2_sda_m0 */
};
/* I2C2 M1 组:备用引脚(如需要) */
i2c2m1_xfer: i2c2m1-xfer {
rockchip,pins =
<1 RK_PB4 10 &pcfg_pull_none>, /* i2c2_scl_m1 */
<1 RK_PB3 10 &pcfg_pull_none>; /* i2c2_sda_m1 */
};
};
};pinctrl 字段说明:
•<1 RK_PB4 9 &pcfg_pull_none> = <bank(1) pin(GPIO1_B4) function(9) pull-config(无上拉)>
•每个 SoC 的 function 编号不同,需参考 RK3576 TRM(Technical Reference Manual) 的 GPIO 复用表格
4.3 查看当前引脚复用状态
# 查看 GPIO1_B3 (I2C2_SDA) 当前功能
cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep i2c2
# 或者检查 pinctrl 设置
cat /sys/kernel/debug/gpio | grep i2c五、hym8563 驱动源码分析
5.1 驱动文件概览
源文件:kernel-6.1/drivers/rtc/rtc-hym8563.c(643 行)
编译配置:
// Kconfig
config RTC_DRV_HYM8563
tristate "Haoyu Microelectronics HYM8563"
depends on OF
help
Say Y to enable support for the HYM8563 I2C RTC chip.
// Makefile
obj-$(CONFIG_RTC_DRV_HYM8563) += rtc-hym8563.o5.2 数据结构与寄存器定义
struct hym8563 {
struct i2c_client *client; // I2C 客户端(关联设备和适配器)
struct rtc_device *rtc; // RTC 核心框架设备
#ifdef CONFIG_COMMON_CLK
struct clk_hw clkout_hw; // CLKOUT 时钟提供者
#endif
int alarm_or_timer_irq; // 0=闹钟模式, 1=定时器模式
int alarm_tm_sec; // 闹钟秒数(定时器模式用)
};寄存器宏定义(驱动中命名对应数据手册):
宏 | 值 | 对应寄存器 |
HYM8563_CTL1 | 0x00 | 控制寄存器 1 |
HYM8563_CTL2 | 0x01 | 控制寄存器 2 |
HYM8563_SEC | 0x02 | 秒寄存器(含 VL 位) |
HYM8563_MIN | 0x03 | 分钟寄存器 |
HYM8563_HOUR | 0x04 | 小时寄存器 |
HYM8563_DAY | 0x05 | 日寄存器 |
HYM8563_WEEKDAY | 0x06 | 星期寄存器 |
HYM8563_MONTH | 0x07 | 月寄存器(含 C 世纪位) |
HYM8563_YEAR | 0x08 | 年寄存器 |
HYM8563_ALM_MIN | 0x09 | 闹钟分钟 |
HYM8563_CLKOUT | 0x0D | 时钟输出控制 |
HYM8563_TMR_CTL | 0x0E | 定时器控制 |
HYM8563_TMR_CNT | 0x0F | 定时器计数值 |
5.3 核心函数分析:读取时间
static int hym8563_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
u8 buf[7];
int ret;
// ---- I2C 通信:一次读取 7 个时间寄存器 (SEC ~ YEAR) ----
// SMBus 协议:发送寄存器地址 0x02 → 连续读取 7 个字节
ret = i2c_smbus_read_i2c_block_data(client, HYM8563_SEC, 7, buf);
if (ret < 0)
return ret;
// ---- BCD 解码:将 BCD 格式转为内核 tm 结构体 ----
tm->tm_sec = bcd2bin(buf[0] & HYM8563_SEC_MASK); // buf[0]=SEC
tm->tm_min = bcd2bin(buf[1] & HYM8563_MIN_MASK); // buf[1]=MIN
tm->tm_hour = bcd2bin(buf[2] & HYM8563_HOUR_MASK); // buf[2]=HOUR
tm->tm_mday = bcd2bin(buf[3] & HYM8563_DAY_MASK); // buf[3]=DAY
tm->tm_wday = bcd2bin(buf[4] & HYM8563_WEEKDAY_MASK); // buf[4]=WEEKDAY, 0=周日
tm->tm_mon = bcd2bin(buf[5] & HYM8563_MONTH_MASK) - 1; // buf[5]=MONTH, -1=0基
tm->tm_year = bcd2bin(buf[6]) + 100; // buf[6]=YEAR, +100=2000基
return 0;
}I2C 读写复合时序(i2c_smbus_read_i2c_block_data 的实际总线波形):
数据结构转换关系:
5.4 核心函数分析:写入时间
static int hym8563_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
u8 buf[7];
int ret;
// ---- 年份范围检查 ----
// 硬件世纪位不可靠,驱动限制:2000–2099
if (tm->tm_year < 100 || tm->tm_year >= 200)
return -EINVAL;
// ---- BCD 编码:将 tm 结构体转为 BCD 格式 ----
buf[0] = bin2bcd(tm->tm_sec);
buf[1] = bin2bcd(tm->tm_min);
buf[2] = bin2bcd(tm->tm_hour);
buf[3] = bin2bcd(tm->tm_mday);
buf[4] = bin2bcd(tm->tm_wday);
buf[5] = bin2bcd(tm->tm_mon + 1); // +1: 0基→1基
buf[6] = bin2bcd(tm->tm_year - 100); // -100: 2000基→0基
// ---- 写入时序:STOP → 写时间 → 清除STOP ----
// ① 先设置 STOP 位,暂停 RTC 计数(防止写入过程中时间变化)
ret = i2c_smbus_write_byte_data(client, HYM8563_CTL1,
HYM8563_CTL1_STOP);
if (ret < 0)
return ret;
// ② 一次性写入 7 个时间寄存器
ret = i2c_smbus_write_i2c_block_data(client, HYM8563_SEC, 7, buf);
if (ret < 0)
return ret;
// ③ 清除 STOP 位,RTC 恢复计数(从新设定的时间开始)
ret = i2c_smbus_write_byte_data(client, HYM8563_CTL1, 0);
if (ret < 0)
return ret;
return 0;
}写入时序要点 — 为什么需要先暂停 RTC?
5.5 Probe 函数分析
static int hym8563_probe(struct i2c_client *client)
{
struct hym8563 *hym8563;
int ret;
struct rtc_time tm_read, tm = {
.tm_wday = 0,
.tm_year = 121, // 2021年
.tm_mon = 0, // 1月
.tm_mday = 1,
.tm_hour = 12,
.tm_min = 0,
.tm_sec = 0,
};
// ① 分配私有数据结构
hym8563 = devm_kzalloc(&client->dev, sizeof(*hym8563), GFP_KERNEL);
if (!hym8563)
return -ENOMEM;
// ② 分配 RTC 核心设备
hym8563->rtc = devm_rtc_allocate_device(&client->dev);
if (IS_ERR(hym8563->rtc))
return PTR_ERR(hym8563->rtc);
hym8563->client = client;
i2c_set_clientdata(client, hym8563);
// ③ 初始化硬件:清除STOP/中断/标志位
ret = hym8563_init_device(client);
if (ret) {
dev_err(&client->dev, "could not init device, %d\n", ret);
return ret;
}
// ④ 注册中断(仅当 DT 中配置了 interrupt 时才执行)
if (client->irq > 0) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, hym8563_irq,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
client->name, hym8563);
// ... 错误处理 ...
}
// ⑤ 检查 VL 位 — 判断 RTC 时间是否有效
ret = i2c_smbus_read_byte_data(client, HYM8563_SEC);
dev_info(&client->dev, "rtc information is %s\n",
(ret & HYM8563_SEC_VL) ? "invalid" : "valid");
// ⑥ 如果时间无效或超出范围,重置为默认时间 (2021-01-01 12:00:00)
hym8563_rtc_read_time(&client->dev, &tm_read);
if ((ret & HYM8563_SEC_VL) || (tm_read.tm_year < 70) ||
(tm_read.tm_year > 200) || (rtc_valid_tm(&tm_read) != 0))
hym8563_rtc_set_time(&client->dev, &tm);
// ⑦ 注册 RTC 设备到 Linux RTC 核心框架
hym8563->rtc->ops = &hym8563_rtc_ops;
clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, hym8563->rtc->features);
return devm_rtc_register_device(hym8563->rtc);
}Probe 完整流程图:
5.6 初始化设备分析
static int hym8563_init_device(struct i2c_client *client)
{
int ret;
ret = i2c_smbus_read_byte_data(client, HYM8563_CTL1);
// ... error check ...
// 清除 STOP 位(如果 RTC 之前被停止过)
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;
// 禁用闹钟中断 (AIE) 和定时器中断 (TIE)
ret &= ~HYM8563_CTL2_AIE;
ret &= ~HYM8563_CTL2_TIE;
// 清除任何待处理的闹钟/定时器标志
if (ret & HYM8563_CTL2_AF) ret &= ~HYM8563_CTL2_AF;
if (ret & HYM8563_CTL2_TF) ret &= ~HYM8563_CTL2_TF;
// 复位定时器(写回配置)
i2c_smbus_write_byte_data(client, HYM8563_TMR_CNT, 0);
i2c_smbus_write_byte_data(client, HYM8563_TMR_CTL, HYM8563_TMR_CFG);
// HYM8563_TMR_CFG = TE(使能) | 01(时钟源=64Hz)
// ↑每 1/64 秒减1
return i2c_smbus_write_byte_data(client, HYM8563_CTL2, ret);
}5.7 中断处理分析
static irqreturn_t hym8563_irq(int irq, void *dev_id)
{
struct hym8563 *hym8563 = (struct hym8563 *)dev_id;
struct i2c_client *client = hym8563->client;
int data, ret;
rtc_lock(hym8563->rtc);
// 读取 CTL2,获取中断状态
data = i2c_smbus_read_byte_data(client, HYM8563_CTL2);
// 清除闹钟标志 (AF) 和定时器标志 (TF)
data &= ~HYM8563_CTL2_AF;
data &= ~HYM8563_CTL2_TF;
// 复位定时器计数器
i2c_smbus_write_byte_data(client, HYM8563_TMR_CNT, 0);
// 写回清除后的 CTL2
ret = i2c_smbus_write_byte_data(client, HYM8563_CTL2, data);
out:
rtc_unlock(hym8563->rtc);
return IRQ_HANDLED;
}
// 中断注册参数:
// IRQF_TRIGGER_LOW — INT 引脚为低电平触发
// IRQF_ONESHOT — 线程化中断,中断线在handler运行期间保持屏蔽5.8 闹钟与定时器双模式
这是一个巧妙的设计:HYM8563 硬件同时支持闹钟(Alarm)和定时器(Timer),驱动根据唤醒间隔自动选择:
static int hym8563_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
{
// ...
interval = alarm - now; // 计算到闹钟的秒数
if (interval < HYM8563_TMR_MAXCNT) { // < 255 秒
// ---- 定时器模式(1秒精度) ----
// 使用硬件定时器做短间隔唤醒
// 精度高,但最长只能 255 秒
hym8563->alarm_or_timer_irq = 1;
i2c_smbus_write_byte_data(client, HYM8563_TMR_CNT, (u8)interval);
} else {
// ---- 闹钟模式(分钟精度) ----
// 使用硬件闹钟做长间隔唤醒
// 没有秒级精度(tm_sec 被强制设为 0)
// 但可以设置任意未来的时间
hym8563->alarm_or_timer_irq = 0;
alm_tm->tm_sec = 0; // 秒强制归零
}
// ...
}双模式选择流程图:
5.9 CLKOUT 时钟提供者
HYM8563 的 CLKOUT 引脚可以输出 32768/1024/32/1 Hz 的方波,驱动通过 Common Clock Framework 注册为时钟提供者:
#ifdef CONFIG_COMMON_CLK
static int clkout_rates[] = {
32768, // FD[1:0] = 00
1024, // FD[1:0] = 01
32, // FD[1:0] = 10
1, // FD[1:0] = 11
};
static int hym8563_clkout_control(struct clk_hw *hw, bool enable)
{
// 读 CLKOUT 寄存器 → 设置/清除 OE 位 → 写回
int ret = i2c_smbus_read_byte_data(client, HYM8563_CLKOUT);
if (enable)
ret |= HYM8563_CLKOUT_ENABLE;
else
ret &= ~HYM8563_CLKOUT_ENABLE;
return i2c_smbus_write_byte_data(client, HYM8563_CLKOUT, ret);
}
// 驱动标记为 CLK_IS_CRITICAL — 防止系统关闭 CLKOUT
// 某些 SoC 依赖此时钟源,关闭会导致系统问题
init.flags = CLK_IS_CRITICAL;
#endif5.10 I2C 驱动注册
// 设备树匹配表 — 与 dts 中的 compatible 关联
static const struct of_device_id hym8563_dt_idtable[] = {
{ .compatible = "haoyu,hym8563" },
{},
};
MODULE_DEVICE_TABLE(of, hym8563_dt_idtable);
// I2C 设备 ID 表(用于非设备树平台)
static const struct i2c_device_id hym8563_id[] = {
{ "hym8563", 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, hym8563_id);
// I2C 驱动结构体
static struct i2c_driver hym8563_driver = {
.driver = {
.name = "hym8563",
.of_match_table = hym8563_dt_idtable,
},
.probe = hym8563_probe,
.id_table = hym8563_id,
};
module_i2c_driver(hym8563_driver);
MODULE_DESCRIPTION("Haoyu HYM8563 RTC driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Haoyu Microelectronics");本文完整覆盖了I2C协议原理、HYM8563硬件设计、设备树配置及驱动源码核心逻辑,是嵌入式开发者学习I2C驱动开发的实用参考。基于Purple Pi OH2开发板的实战场景,所有代码和配置均可直接落地调试。
