Skip to content

Commit 5af858a

Browse files
g0mb4alexandrebelloni
authored andcommitted
rtc: Add driver for SD2405AL
Add support for the DFRobot SD2405AL I2C RTC Module. Datasheet: https://image.dfrobot.com/image/data/TOY0021/SD2405AL%20datasheet%20(Angelo%20v0.1).pdf Product: https://www.dfrobot.com/product-1600.html To instantiate (assuming device is connected to I2C-1) as root: echo sd2405al 0x32 > /sys/bus/i2c/devices/i2c-1/new_device as user: echo 'sd2405al 0x32' | sudo tee /sys/class/i2c-adapter/i2c-1/new_device The driver is tested with: + hwclock + tools/testing/selftests/rtc/setdate + tools/testing/selftests/rtc/rtctest Reviewed-by: Csókás Bence <csokas.bence@prolan.hu> Signed-off-by: Tóth János <gomba007@gmail.com> Link: https://lore.kernel.org/r/20240830-rtc-sd2405al-v7-1-2f7102621b1d@gmail.com Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
1 parent da1531e commit 5af858a

File tree

4 files changed

+244
-0
lines changed

4 files changed

+244
-0
lines changed

MAINTAINERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6474,6 +6474,12 @@ F: include/net/devlink.h
64746474
F: include/uapi/linux/devlink.h
64756475
F: net/devlink/
64766476

6477+
DFROBOT SD2405AL RTC DRIVER
6478+
M: Tóth János <gomba007@gmail.com>
6479+
L: linux-rtc@vger.kernel.org
6480+
S: Maintained
6481+
F: drivers/rtc/rtc-sd2405al.c
6482+
64776483
DH ELECTRONICS IMX6 DHCOM/DHCOR BOARD SUPPORT
64786484
M: Christoph Niedermaier <cniedermaier@dh-electronics.com>
64796485
L: kernel@dh-electronics.com

drivers/rtc/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,16 @@ config RTC_DRV_S5M
743743
This driver can also be built as a module. If so, the module
744744
will be called rtc-s5m.
745745

746+
config RTC_DRV_SD2405AL
747+
tristate "DFRobot SD2405AL"
748+
select REGMAP_I2C
749+
help
750+
If you say yes here you will get support for the
751+
DFRobot SD2405AL I2C RTC Module.
752+
753+
This driver can also be built as a module. If so, the module
754+
will be called rtc-sd2405al.
755+
746756
config RTC_DRV_SD3078
747757
tristate "ZXW Shenzhen whwave SD3078"
748758
select REGMAP_I2C

drivers/rtc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
162162
obj-$(CONFIG_RTC_DRV_S5M) += rtc-s5m.o
163163
obj-$(CONFIG_RTC_DRV_SA1100) += rtc-sa1100.o
164164
obj-$(CONFIG_RTC_DRV_SC27XX) += rtc-sc27xx.o
165+
obj-$(CONFIG_RTC_DRV_SD2405AL) += rtc-sd2405al.o
165166
obj-$(CONFIG_RTC_DRV_SD3078) += rtc-sd3078.o
166167
obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o
167168
obj-$(CONFIG_RTC_DRV_SNVS) += rtc-snvs.o

drivers/rtc/rtc-sd2405al.c

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* RTC driver for the SD2405AL Real-Time Clock
4+
*
5+
* Datasheet:
6+
* https://image.dfrobot.com/image/data/TOY0021/SD2405AL%20datasheet%20(Angelo%20v0.1).pdf
7+
*
8+
* Copyright (C) 2024 Tóth János <gomba007@gmail.com>
9+
*/
10+
11+
#include <linux/bcd.h>
12+
#include <linux/i2c.h>
13+
#include <linux/regmap.h>
14+
#include <linux/rtc.h>
15+
16+
/* Real time clock registers */
17+
#define SD2405AL_REG_T_SEC 0x00
18+
#define SD2405AL_REG_T_MIN 0x01
19+
#define SD2405AL_REG_T_HOUR 0x02
20+
# define SD2405AL_BIT_12H_PM BIT(5)
21+
# define SD2405AL_BIT_24H BIT(7)
22+
#define SD2405AL_REG_T_WEEK 0x03
23+
#define SD2405AL_REG_T_DAY 0x04
24+
#define SD2405AL_REG_T_MON 0x05
25+
#define SD2405AL_REG_T_YEAR 0x06
26+
27+
#define SD2405AL_NUM_T_REGS (SD2405AL_REG_T_YEAR - SD2405AL_REG_T_SEC + 1)
28+
29+
/* Control registers */
30+
#define SD2405AL_REG_CTR1 0x0F
31+
# define SD2405AL_BIT_WRTC2 BIT(2)
32+
# define SD2405AL_BIT_WRTC3 BIT(7)
33+
#define SD2405AL_REG_CTR2 0x10
34+
# define SD2405AL_BIT_WRTC1 BIT(7)
35+
#define SD2405AL_REG_CTR3 0x11
36+
#define SD2405AL_REG_TTF 0x12
37+
#define SD2405AL_REG_CNTDWN 0x13
38+
39+
/* General RAM */
40+
#define SD2405AL_REG_M_START 0x14
41+
#define SD2405AL_REG_M_END 0x1F
42+
43+
struct sd2405al {
44+
struct device *dev;
45+
struct rtc_device *rtc;
46+
struct regmap *regmap;
47+
};
48+
49+
static int sd2405al_enable_reg_write(struct sd2405al *sd2405al)
50+
{
51+
int ret;
52+
53+
/* order of writes is important */
54+
ret = regmap_update_bits(sd2405al->regmap, SD2405AL_REG_CTR2,
55+
SD2405AL_BIT_WRTC1, SD2405AL_BIT_WRTC1);
56+
if (ret < 0)
57+
return ret;
58+
59+
ret = regmap_update_bits(sd2405al->regmap, SD2405AL_REG_CTR1,
60+
SD2405AL_BIT_WRTC2 | SD2405AL_BIT_WRTC3,
61+
SD2405AL_BIT_WRTC2 | SD2405AL_BIT_WRTC3);
62+
if (ret < 0)
63+
return ret;
64+
65+
return 0;
66+
}
67+
68+
static int sd2405al_disable_reg_write(struct sd2405al *sd2405al)
69+
{
70+
int ret;
71+
72+
/* order of writes is important */
73+
ret = regmap_update_bits(sd2405al->regmap, SD2405AL_REG_CTR1,
74+
SD2405AL_BIT_WRTC2 | SD2405AL_BIT_WRTC3, 0x00);
75+
if (ret < 0)
76+
return ret;
77+
78+
ret = regmap_update_bits(sd2405al->regmap, SD2405AL_REG_CTR2,
79+
SD2405AL_BIT_WRTC1, 0x00);
80+
if (ret < 0)
81+
return ret;
82+
83+
return 0;
84+
}
85+
86+
static int sd2405al_read_time(struct device *dev, struct rtc_time *time)
87+
{
88+
u8 data[SD2405AL_NUM_T_REGS] = { 0 };
89+
struct sd2405al *sd2405al = dev_get_drvdata(dev);
90+
int ret;
91+
92+
ret = regmap_bulk_read(sd2405al->regmap, SD2405AL_REG_T_SEC, data,
93+
SD2405AL_NUM_T_REGS);
94+
if (ret < 0)
95+
return ret;
96+
97+
time->tm_sec = bcd2bin(data[SD2405AL_REG_T_SEC] & 0x7F);
98+
time->tm_min = bcd2bin(data[SD2405AL_REG_T_MIN] & 0x7F);
99+
100+
if (data[SD2405AL_REG_T_HOUR] & SD2405AL_BIT_24H)
101+
time->tm_hour = bcd2bin(data[SD2405AL_REG_T_HOUR] & 0x3F);
102+
else
103+
if (data[SD2405AL_REG_T_HOUR] & SD2405AL_BIT_12H_PM)
104+
time->tm_hour = bcd2bin(data[SD2405AL_REG_T_HOUR]
105+
& 0x1F) + 12;
106+
else /* 12 hour mode, AM */
107+
time->tm_hour = bcd2bin(data[SD2405AL_REG_T_HOUR]
108+
& 0x1F);
109+
110+
time->tm_wday = bcd2bin(data[SD2405AL_REG_T_WEEK] & 0x07);
111+
time->tm_mday = bcd2bin(data[SD2405AL_REG_T_DAY] & 0x3F);
112+
time->tm_mon = bcd2bin(data[SD2405AL_REG_T_MON] & 0x1F) - 1;
113+
time->tm_year = bcd2bin(data[SD2405AL_REG_T_YEAR]) + 100;
114+
115+
dev_dbg(sd2405al->dev, "read time: %ptR (%d)\n", time, time->tm_wday);
116+
117+
return 0;
118+
}
119+
120+
static int sd2405al_set_time(struct device *dev, struct rtc_time *time)
121+
{
122+
u8 data[SD2405AL_NUM_T_REGS];
123+
struct sd2405al *sd2405al = dev_get_drvdata(dev);
124+
int ret;
125+
126+
data[SD2405AL_REG_T_SEC] = bin2bcd(time->tm_sec);
127+
data[SD2405AL_REG_T_MIN] = bin2bcd(time->tm_min);
128+
data[SD2405AL_REG_T_HOUR] = bin2bcd(time->tm_hour) | SD2405AL_BIT_24H;
129+
data[SD2405AL_REG_T_DAY] = bin2bcd(time->tm_mday);
130+
data[SD2405AL_REG_T_WEEK] = bin2bcd(time->tm_wday);
131+
data[SD2405AL_REG_T_MON] = bin2bcd(time->tm_mon) + 1;
132+
data[SD2405AL_REG_T_YEAR] = bin2bcd(time->tm_year - 100);
133+
134+
ret = sd2405al_enable_reg_write(sd2405al);
135+
if (ret < 0)
136+
return ret;
137+
138+
ret = regmap_bulk_write(sd2405al->regmap, SD2405AL_REG_T_SEC, data,
139+
SD2405AL_NUM_T_REGS);
140+
if (ret < 0)
141+
return ret;
142+
143+
ret = regmap_write(sd2405al->regmap, SD2405AL_REG_TTF, 0x00);
144+
if (ret < 0)
145+
return ret;
146+
147+
ret = sd2405al_disable_reg_write(sd2405al);
148+
if (ret < 0)
149+
return ret;
150+
151+
dev_dbg(sd2405al->dev, "set time: %ptR (%d)\n", time, time->tm_wday);
152+
153+
return 0;
154+
}
155+
156+
static const struct rtc_class_ops sd2405al_rtc_ops = {
157+
.read_time = sd2405al_read_time,
158+
.set_time = sd2405al_set_time,
159+
};
160+
161+
static const struct regmap_config sd2405al_regmap_conf = {
162+
.reg_bits = 8,
163+
.val_bits = 8,
164+
.max_register = SD2405AL_REG_M_END,
165+
};
166+
167+
static int sd2405al_probe(struct i2c_client *client)
168+
{
169+
struct sd2405al *sd2405al;
170+
int ret;
171+
172+
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
173+
return -ENODEV;
174+
175+
sd2405al = devm_kzalloc(&client->dev, sizeof(*sd2405al), GFP_KERNEL);
176+
if (!sd2405al)
177+
return -ENOMEM;
178+
179+
sd2405al->dev = &client->dev;
180+
181+
sd2405al->regmap = devm_regmap_init_i2c(client, &sd2405al_regmap_conf);
182+
if (IS_ERR(sd2405al->regmap))
183+
return PTR_ERR(sd2405al->regmap);
184+
185+
sd2405al->rtc = devm_rtc_allocate_device(&client->dev);
186+
if (IS_ERR(sd2405al->rtc))
187+
return PTR_ERR(sd2405al->rtc);
188+
189+
sd2405al->rtc->ops = &sd2405al_rtc_ops;
190+
sd2405al->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
191+
sd2405al->rtc->range_max = RTC_TIMESTAMP_END_2099;
192+
193+
dev_set_drvdata(&client->dev, sd2405al);
194+
195+
ret = devm_rtc_register_device(sd2405al->rtc);
196+
if (ret < 0)
197+
return ret;
198+
199+
return 0;
200+
}
201+
202+
static const struct i2c_device_id sd2405al_id[] = {
203+
{ "sd2405al" },
204+
{ /* sentinel */ }
205+
};
206+
MODULE_DEVICE_TABLE(i2c, sd2405al_id);
207+
208+
static const __maybe_unused struct of_device_id sd2405al_of_match[] = {
209+
{ .compatible = "dfrobot,sd2405al" },
210+
{ /* sentinel */ }
211+
};
212+
MODULE_DEVICE_TABLE(of, sd2405al_of_match);
213+
214+
static struct i2c_driver sd2405al_driver = {
215+
.driver = {
216+
.name = "sd2405al",
217+
.of_match_table = of_match_ptr(sd2405al_of_match),
218+
},
219+
.probe = sd2405al_probe,
220+
.id_table = sd2405al_id,
221+
};
222+
223+
module_i2c_driver(sd2405al_driver);
224+
225+
MODULE_AUTHOR("Tóth János <gomba007@gmail.com>");
226+
MODULE_LICENSE("GPL");
227+
MODULE_DESCRIPTION("SD2405AL RTC driver");

0 commit comments

Comments
 (0)