Linux Audio

Check our new training course

Linux kernel drivers training

May 6-19, 2025
Register
Loading...
Note: File does not exist in v4.6.
  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
 43struct sd2405al {
 44	struct device		*dev;
 45	struct rtc_device	*rtc;
 46	struct regmap		*regmap;
 47};
 48
 49static 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
 68static 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
 86static 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
120static 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
156static const struct rtc_class_ops sd2405al_rtc_ops = {
157	.read_time = sd2405al_read_time,
158	.set_time = sd2405al_set_time,
159};
160
161static const struct regmap_config sd2405al_regmap_conf = {
162	.reg_bits = 8,
163	.val_bits = 8,
164	.max_register = SD2405AL_REG_M_END,
165};
166
167static 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
202static const struct i2c_device_id sd2405al_id[] = {
203	{ "sd2405al" },
204	{ /* sentinel */ }
205};
206MODULE_DEVICE_TABLE(i2c, sd2405al_id);
207
208static const __maybe_unused struct of_device_id sd2405al_of_match[] = {
209	{ .compatible = "dfrobot,sd2405al" },
210	{ /* sentinel */ }
211};
212MODULE_DEVICE_TABLE(of, sd2405al_of_match);
213
214static 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
223module_i2c_driver(sd2405al_driver);
224
225MODULE_AUTHOR("Tóth János <gomba007@gmail.com>");
226MODULE_LICENSE("GPL");
227MODULE_DESCRIPTION("SD2405AL RTC driver");