diff options
Diffstat (limited to 'drivers/rtc/rtc-isl12057.c')
-rw-r--r-- | drivers/rtc/rtc-isl12057.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-isl12057.c b/drivers/rtc/rtc-isl12057.c new file mode 100644 index 000000000000..7854a656628f --- /dev/null +++ b/drivers/rtc/rtc-isl12057.c | |||
@@ -0,0 +1,310 @@ | |||
1 | /* | ||
2 | * rtc-isl12057 - Driver for Intersil ISL12057 I2C Real Time Clock | ||
3 | * | ||
4 | * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org> | ||
5 | * | ||
6 | * This work is largely based on Intersil ISL1208 driver developed by | ||
7 | * Hebert Valerio Riedel <hvr@gnu.org>. | ||
8 | * | ||
9 | * Detailed datasheet on which this development is based is available here: | ||
10 | * | ||
11 | * http://natisbad.org/NAS2/refs/ISL12057.pdf | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify | ||
14 | * it under the terms of the GNU General Public License as published by | ||
15 | * the Free Software Foundation; either version 2 of the License, or | ||
16 | * (at your option) any later version. | ||
17 | * | ||
18 | * This program is distributed in the hope that it will be useful, | ||
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
21 | * GNU General Public License for more details. | ||
22 | */ | ||
23 | |||
24 | #include <linux/module.h> | ||
25 | #include <linux/mutex.h> | ||
26 | #include <linux/rtc.h> | ||
27 | #include <linux/i2c.h> | ||
28 | #include <linux/bcd.h> | ||
29 | #include <linux/rtc.h> | ||
30 | #include <linux/of.h> | ||
31 | #include <linux/of_device.h> | ||
32 | #include <linux/regmap.h> | ||
33 | |||
34 | #define DRV_NAME "rtc-isl12057" | ||
35 | |||
36 | /* RTC section */ | ||
37 | #define ISL12057_REG_RTC_SC 0x00 /* Seconds */ | ||
38 | #define ISL12057_REG_RTC_MN 0x01 /* Minutes */ | ||
39 | #define ISL12057_REG_RTC_HR 0x02 /* Hours */ | ||
40 | #define ISL12057_REG_RTC_HR_PM BIT(5) /* AM/PM bit in 12h format */ | ||
41 | #define ISL12057_REG_RTC_HR_MIL BIT(6) /* 24h/12h format */ | ||
42 | #define ISL12057_REG_RTC_DW 0x03 /* Day of the Week */ | ||
43 | #define ISL12057_REG_RTC_DT 0x04 /* Date */ | ||
44 | #define ISL12057_REG_RTC_MO 0x05 /* Month */ | ||
45 | #define ISL12057_REG_RTC_YR 0x06 /* Year */ | ||
46 | #define ISL12057_RTC_SEC_LEN 7 | ||
47 | |||
48 | /* Alarm 1 section */ | ||
49 | #define ISL12057_REG_A1_SC 0x07 /* Alarm 1 Seconds */ | ||
50 | #define ISL12057_REG_A1_MN 0x08 /* Alarm 1 Minutes */ | ||
51 | #define ISL12057_REG_A1_HR 0x09 /* Alarm 1 Hours */ | ||
52 | #define ISL12057_REG_A1_HR_PM BIT(5) /* AM/PM bit in 12h format */ | ||
53 | #define ISL12057_REG_A1_HR_MIL BIT(6) /* 24h/12h format */ | ||
54 | #define ISL12057_REG_A1_DWDT 0x0A /* Alarm 1 Date / Day of the week */ | ||
55 | #define ISL12057_REG_A1_DWDT_B BIT(6) /* DW / DT selection bit */ | ||
56 | #define ISL12057_A1_SEC_LEN 4 | ||
57 | |||
58 | /* Alarm 2 section */ | ||
59 | #define ISL12057_REG_A2_MN 0x0B /* Alarm 2 Minutes */ | ||
60 | #define ISL12057_REG_A2_HR 0x0C /* Alarm 2 Hours */ | ||
61 | #define ISL12057_REG_A2_DWDT 0x0D /* Alarm 2 Date / Day of the week */ | ||
62 | #define ISL12057_A2_SEC_LEN 3 | ||
63 | |||
64 | /* Control/Status registers */ | ||
65 | #define ISL12057_REG_INT 0x0E | ||
66 | #define ISL12057_REG_INT_A1IE BIT(0) /* Alarm 1 interrupt enable bit */ | ||
67 | #define ISL12057_REG_INT_A2IE BIT(1) /* Alarm 2 interrupt enable bit */ | ||
68 | #define ISL12057_REG_INT_INTCN BIT(2) /* Interrupt control enable bit */ | ||
69 | #define ISL12057_REG_INT_RS1 BIT(3) /* Freq out control bit 1 */ | ||
70 | #define ISL12057_REG_INT_RS2 BIT(4) /* Freq out control bit 2 */ | ||
71 | #define ISL12057_REG_INT_EOSC BIT(7) /* Oscillator enable bit */ | ||
72 | |||
73 | #define ISL12057_REG_SR 0x0F | ||
74 | #define ISL12057_REG_SR_A1F BIT(0) /* Alarm 1 interrupt bit */ | ||
75 | #define ISL12057_REG_SR_A2F BIT(1) /* Alarm 2 interrupt bit */ | ||
76 | #define ISL12057_REG_SR_OSF BIT(7) /* Oscillator failure bit */ | ||
77 | |||
78 | /* Register memory map length */ | ||
79 | #define ISL12057_MEM_MAP_LEN 0x10 | ||
80 | |||
81 | struct isl12057_rtc_data { | ||
82 | struct regmap *regmap; | ||
83 | struct mutex lock; | ||
84 | }; | ||
85 | |||
86 | static void isl12057_rtc_regs_to_tm(struct rtc_time *tm, u8 *regs) | ||
87 | { | ||
88 | tm->tm_sec = bcd2bin(regs[ISL12057_REG_RTC_SC]); | ||
89 | tm->tm_min = bcd2bin(regs[ISL12057_REG_RTC_MN]); | ||
90 | |||
91 | if (regs[ISL12057_REG_RTC_HR] & ISL12057_REG_RTC_HR_MIL) { /* AM/PM */ | ||
92 | tm->tm_hour = bcd2bin(regs[ISL12057_REG_RTC_HR] & 0x0f); | ||
93 | if (regs[ISL12057_REG_RTC_HR] & ISL12057_REG_RTC_HR_PM) | ||
94 | tm->tm_hour += 12; | ||
95 | } else { /* 24 hour mode */ | ||
96 | tm->tm_hour = bcd2bin(regs[ISL12057_REG_RTC_HR] & 0x3f); | ||
97 | } | ||
98 | |||
99 | tm->tm_mday = bcd2bin(regs[ISL12057_REG_RTC_DT]); | ||
100 | tm->tm_wday = bcd2bin(regs[ISL12057_REG_RTC_DW]) - 1; /* starts at 1 */ | ||
101 | tm->tm_mon = bcd2bin(regs[ISL12057_REG_RTC_MO]) - 1; /* starts at 1 */ | ||
102 | tm->tm_year = bcd2bin(regs[ISL12057_REG_RTC_YR]) + 100; | ||
103 | } | ||
104 | |||
105 | static int isl12057_rtc_tm_to_regs(u8 *regs, struct rtc_time *tm) | ||
106 | { | ||
107 | /* | ||
108 | * The clock has an 8 bit wide bcd-coded register for the year. | ||
109 | * tm_year is an offset from 1900 and we are interested in the | ||
110 | * 2000-2099 range, so any value less than 100 is invalid. | ||
111 | */ | ||
112 | if (tm->tm_year < 100) | ||
113 | return -EINVAL; | ||
114 | |||
115 | regs[ISL12057_REG_RTC_SC] = bin2bcd(tm->tm_sec); | ||
116 | regs[ISL12057_REG_RTC_MN] = bin2bcd(tm->tm_min); | ||
117 | regs[ISL12057_REG_RTC_HR] = bin2bcd(tm->tm_hour); /* 24-hour format */ | ||
118 | regs[ISL12057_REG_RTC_DT] = bin2bcd(tm->tm_mday); | ||
119 | regs[ISL12057_REG_RTC_MO] = bin2bcd(tm->tm_mon + 1); | ||
120 | regs[ISL12057_REG_RTC_YR] = bin2bcd(tm->tm_year - 100); | ||
121 | regs[ISL12057_REG_RTC_DW] = bin2bcd(tm->tm_wday + 1); | ||
122 | |||
123 | return 0; | ||
124 | } | ||
125 | |||
126 | /* | ||
127 | * Try and match register bits w/ fixed null values to see whether we | ||
128 | * are dealing with an ISL12057. Note: this function is called early | ||
129 | * during init and hence does need mutex protection. | ||
130 | */ | ||
131 | static int isl12057_i2c_validate_chip(struct regmap *regmap) | ||
132 | { | ||
133 | u8 regs[ISL12057_MEM_MAP_LEN]; | ||
134 | static const u8 mask[ISL12057_MEM_MAP_LEN] = { 0x80, 0x80, 0x80, 0xf8, | ||
135 | 0xc0, 0x60, 0x00, 0x00, | ||
136 | 0x00, 0x00, 0x00, 0x00, | ||
137 | 0x00, 0x00, 0x60, 0x7c }; | ||
138 | int ret, i; | ||
139 | |||
140 | ret = regmap_bulk_read(regmap, 0, regs, ISL12057_MEM_MAP_LEN); | ||
141 | if (ret) | ||
142 | return ret; | ||
143 | |||
144 | for (i = 0; i < ISL12057_MEM_MAP_LEN; ++i) { | ||
145 | if (regs[i] & mask[i]) /* check if bits are cleared */ | ||
146 | return -ENODEV; | ||
147 | } | ||
148 | |||
149 | return 0; | ||
150 | } | ||
151 | |||
152 | static int isl12057_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
153 | { | ||
154 | struct isl12057_rtc_data *data = dev_get_drvdata(dev); | ||
155 | u8 regs[ISL12057_RTC_SEC_LEN]; | ||
156 | int ret; | ||
157 | |||
158 | mutex_lock(&data->lock); | ||
159 | ret = regmap_bulk_read(data->regmap, ISL12057_REG_RTC_SC, regs, | ||
160 | ISL12057_RTC_SEC_LEN); | ||
161 | mutex_unlock(&data->lock); | ||
162 | |||
163 | if (ret) { | ||
164 | dev_err(dev, "%s: RTC read failed\n", __func__); | ||
165 | return ret; | ||
166 | } | ||
167 | |||
168 | isl12057_rtc_regs_to_tm(tm, regs); | ||
169 | |||
170 | return rtc_valid_tm(tm); | ||
171 | } | ||
172 | |||
173 | static int isl12057_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
174 | { | ||
175 | struct isl12057_rtc_data *data = dev_get_drvdata(dev); | ||
176 | u8 regs[ISL12057_RTC_SEC_LEN]; | ||
177 | int ret; | ||
178 | |||
179 | ret = isl12057_rtc_tm_to_regs(regs, tm); | ||
180 | if (ret) | ||
181 | return ret; | ||
182 | |||
183 | mutex_lock(&data->lock); | ||
184 | ret = regmap_bulk_write(data->regmap, ISL12057_REG_RTC_SC, regs, | ||
185 | ISL12057_RTC_SEC_LEN); | ||
186 | mutex_unlock(&data->lock); | ||
187 | |||
188 | if (ret) | ||
189 | dev_err(dev, "%s: RTC write failed\n", __func__); | ||
190 | |||
191 | return ret; | ||
192 | } | ||
193 | |||
194 | /* | ||
195 | * Check current RTC status and enable/disable what needs to be. Return 0 if | ||
196 | * everything went ok and a negative value upon error. Note: this function | ||
197 | * is called early during init and hence does need mutex protection. | ||
198 | */ | ||
199 | static int isl12057_check_rtc_status(struct device *dev, struct regmap *regmap) | ||
200 | { | ||
201 | int ret; | ||
202 | |||
203 | /* Enable oscillator if not already running */ | ||
204 | ret = regmap_update_bits(regmap, ISL12057_REG_INT, | ||
205 | ISL12057_REG_INT_EOSC, 0); | ||
206 | if (ret < 0) { | ||
207 | dev_err(dev, "Unable to enable oscillator\n"); | ||
208 | return ret; | ||
209 | } | ||
210 | |||
211 | /* Clear oscillator failure bit if needed */ | ||
212 | ret = regmap_update_bits(regmap, ISL12057_REG_SR, | ||
213 | ISL12057_REG_SR_OSF, 0); | ||
214 | if (ret < 0) { | ||
215 | dev_err(dev, "Unable to clear oscillator failure bit\n"); | ||
216 | return ret; | ||
217 | } | ||
218 | |||
219 | /* Clear alarm bit if needed */ | ||
220 | ret = regmap_update_bits(regmap, ISL12057_REG_SR, | ||
221 | ISL12057_REG_SR_A1F, 0); | ||
222 | if (ret < 0) { | ||
223 | dev_err(dev, "Unable to clear alarm bit\n"); | ||
224 | return ret; | ||
225 | } | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | static const struct rtc_class_ops rtc_ops = { | ||
231 | .read_time = isl12057_rtc_read_time, | ||
232 | .set_time = isl12057_rtc_set_time, | ||
233 | }; | ||
234 | |||
235 | static struct regmap_config isl12057_rtc_regmap_config = { | ||
236 | .reg_bits = 8, | ||
237 | .val_bits = 8, | ||
238 | }; | ||
239 | |||
240 | static int isl12057_probe(struct i2c_client *client, | ||
241 | const struct i2c_device_id *id) | ||
242 | { | ||
243 | struct device *dev = &client->dev; | ||
244 | struct isl12057_rtc_data *data; | ||
245 | struct rtc_device *rtc; | ||
246 | struct regmap *regmap; | ||
247 | int ret; | ||
248 | |||
249 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | | ||
250 | I2C_FUNC_SMBUS_BYTE_DATA | | ||
251 | I2C_FUNC_SMBUS_I2C_BLOCK)) | ||
252 | return -ENODEV; | ||
253 | |||
254 | regmap = devm_regmap_init_i2c(client, &isl12057_rtc_regmap_config); | ||
255 | if (IS_ERR(regmap)) { | ||
256 | ret = PTR_ERR(regmap); | ||
257 | dev_err(dev, "regmap allocation failed: %d\n", ret); | ||
258 | return ret; | ||
259 | } | ||
260 | |||
261 | ret = isl12057_i2c_validate_chip(regmap); | ||
262 | if (ret) | ||
263 | return ret; | ||
264 | |||
265 | ret = isl12057_check_rtc_status(dev, regmap); | ||
266 | if (ret) | ||
267 | return ret; | ||
268 | |||
269 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | ||
270 | if (!data) | ||
271 | return -ENOMEM; | ||
272 | |||
273 | mutex_init(&data->lock); | ||
274 | data->regmap = regmap; | ||
275 | dev_set_drvdata(dev, data); | ||
276 | |||
277 | rtc = devm_rtc_device_register(dev, DRV_NAME, &rtc_ops, THIS_MODULE); | ||
278 | if (IS_ERR(rtc)) | ||
279 | return PTR_ERR(rtc); | ||
280 | |||
281 | return 0; | ||
282 | } | ||
283 | |||
284 | #ifdef CONFIG_OF | ||
285 | static struct of_device_id isl12057_dt_match[] = { | ||
286 | { .compatible = "isl,isl12057" }, | ||
287 | { }, | ||
288 | }; | ||
289 | #endif | ||
290 | |||
291 | static const struct i2c_device_id isl12057_id[] = { | ||
292 | { "isl12057", 0 }, | ||
293 | { } | ||
294 | }; | ||
295 | MODULE_DEVICE_TABLE(i2c, isl12057_id); | ||
296 | |||
297 | static struct i2c_driver isl12057_driver = { | ||
298 | .driver = { | ||
299 | .name = DRV_NAME, | ||
300 | .owner = THIS_MODULE, | ||
301 | .of_match_table = of_match_ptr(isl12057_dt_match), | ||
302 | }, | ||
303 | .probe = isl12057_probe, | ||
304 | .id_table = isl12057_id, | ||
305 | }; | ||
306 | module_i2c_driver(isl12057_driver); | ||
307 | |||
308 | MODULE_AUTHOR("Arnaud EBALARD <arno@natisbad.org>"); | ||
309 | MODULE_DESCRIPTION("Intersil ISL12057 RTC driver"); | ||
310 | MODULE_LICENSE("GPL"); | ||