Commit | Line | Data |
---|---|---|
4d61ff6b PDM |
1 | /* |
2 | * A driver for the I2C members of the Abracon AB x8xx RTC family, | |
3 | * and compatible: AB 1805 and AB 0805 | |
4 | * | |
5 | * Copyright 2014-2015 Macq S.A. | |
6 | * | |
7 | * Author: Philippe De Muyter <phdm@macqel.be> | |
8 | * Author: Alexandre Belloni <alexandre.belloni@free-electrons.com> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | * | |
14 | */ | |
15 | ||
16 | #include <linux/bcd.h> | |
17 | #include <linux/i2c.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/rtc.h> | |
20 | ||
21 | #define ABX8XX_REG_HTH 0x00 | |
22 | #define ABX8XX_REG_SC 0x01 | |
23 | #define ABX8XX_REG_MN 0x02 | |
24 | #define ABX8XX_REG_HR 0x03 | |
25 | #define ABX8XX_REG_DA 0x04 | |
26 | #define ABX8XX_REG_MO 0x05 | |
27 | #define ABX8XX_REG_YR 0x06 | |
28 | #define ABX8XX_REG_WD 0x07 | |
29 | ||
30 | #define ABX8XX_REG_CTRL1 0x10 | |
5f1b2f77 | 31 | #define ABX8XX_CTRL_WRITE BIT(0) |
4d61ff6b PDM |
32 | #define ABX8XX_CTRL_12_24 BIT(6) |
33 | ||
34 | #define ABX8XX_REG_CFG_KEY 0x1f | |
35 | #define ABX8XX_CFG_KEY_MISC 0x9d | |
36 | ||
37 | #define ABX8XX_REG_ID0 0x28 | |
38 | ||
39 | #define ABX8XX_REG_TRICKLE 0x20 | |
40 | #define ABX8XX_TRICKLE_CHARGE_ENABLE 0xa0 | |
41 | #define ABX8XX_TRICKLE_STANDARD_DIODE 0x8 | |
42 | #define ABX8XX_TRICKLE_SCHOTTKY_DIODE 0x4 | |
43 | ||
44 | static u8 trickle_resistors[] = {0, 3, 6, 11}; | |
45 | ||
46 | enum abx80x_chip {AB0801, AB0803, AB0804, AB0805, | |
47 | AB1801, AB1803, AB1804, AB1805, ABX80X}; | |
48 | ||
49 | struct abx80x_cap { | |
50 | u16 pn; | |
51 | bool has_tc; | |
52 | }; | |
53 | ||
54 | static struct abx80x_cap abx80x_caps[] = { | |
55 | [AB0801] = {.pn = 0x0801}, | |
56 | [AB0803] = {.pn = 0x0803}, | |
57 | [AB0804] = {.pn = 0x0804, .has_tc = true}, | |
58 | [AB0805] = {.pn = 0x0805, .has_tc = true}, | |
59 | [AB1801] = {.pn = 0x1801}, | |
60 | [AB1803] = {.pn = 0x1803}, | |
61 | [AB1804] = {.pn = 0x1804, .has_tc = true}, | |
62 | [AB1805] = {.pn = 0x1805, .has_tc = true}, | |
63 | [ABX80X] = {.pn = 0} | |
64 | }; | |
65 | ||
66 | static struct i2c_driver abx80x_driver; | |
67 | ||
68 | static int abx80x_enable_trickle_charger(struct i2c_client *client, | |
69 | u8 trickle_cfg) | |
70 | { | |
71 | int err; | |
72 | ||
73 | /* | |
74 | * Write the configuration key register to enable access to the Trickle | |
75 | * register | |
76 | */ | |
77 | err = i2c_smbus_write_byte_data(client, ABX8XX_REG_CFG_KEY, | |
78 | ABX8XX_CFG_KEY_MISC); | |
79 | if (err < 0) { | |
80 | dev_err(&client->dev, "Unable to write configuration key\n"); | |
81 | return -EIO; | |
82 | } | |
83 | ||
84 | err = i2c_smbus_write_byte_data(client, ABX8XX_REG_TRICKLE, | |
85 | ABX8XX_TRICKLE_CHARGE_ENABLE | | |
86 | trickle_cfg); | |
87 | if (err < 0) { | |
88 | dev_err(&client->dev, "Unable to write trickle register\n"); | |
89 | return -EIO; | |
90 | } | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
95 | static int abx80x_rtc_read_time(struct device *dev, struct rtc_time *tm) | |
96 | { | |
97 | struct i2c_client *client = to_i2c_client(dev); | |
98 | unsigned char buf[8]; | |
99 | int err; | |
100 | ||
101 | err = i2c_smbus_read_i2c_block_data(client, ABX8XX_REG_HTH, | |
102 | sizeof(buf), buf); | |
103 | if (err < 0) { | |
104 | dev_err(&client->dev, "Unable to read date\n"); | |
105 | return -EIO; | |
106 | } | |
107 | ||
108 | tm->tm_sec = bcd2bin(buf[ABX8XX_REG_SC] & 0x7F); | |
109 | tm->tm_min = bcd2bin(buf[ABX8XX_REG_MN] & 0x7F); | |
110 | tm->tm_hour = bcd2bin(buf[ABX8XX_REG_HR] & 0x3F); | |
111 | tm->tm_wday = buf[ABX8XX_REG_WD] & 0x7; | |
112 | tm->tm_mday = bcd2bin(buf[ABX8XX_REG_DA] & 0x3F); | |
113 | tm->tm_mon = bcd2bin(buf[ABX8XX_REG_MO] & 0x1F) - 1; | |
114 | tm->tm_year = bcd2bin(buf[ABX8XX_REG_YR]) + 100; | |
115 | ||
116 | err = rtc_valid_tm(tm); | |
117 | if (err < 0) | |
118 | dev_err(&client->dev, "retrieved date/time is not valid.\n"); | |
119 | ||
120 | return err; | |
121 | } | |
122 | ||
123 | static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) | |
124 | { | |
125 | struct i2c_client *client = to_i2c_client(dev); | |
126 | unsigned char buf[8]; | |
127 | int err; | |
128 | ||
129 | if (tm->tm_year < 100) | |
130 | return -EINVAL; | |
131 | ||
132 | buf[ABX8XX_REG_HTH] = 0; | |
133 | buf[ABX8XX_REG_SC] = bin2bcd(tm->tm_sec); | |
134 | buf[ABX8XX_REG_MN] = bin2bcd(tm->tm_min); | |
135 | buf[ABX8XX_REG_HR] = bin2bcd(tm->tm_hour); | |
136 | buf[ABX8XX_REG_DA] = bin2bcd(tm->tm_mday); | |
137 | buf[ABX8XX_REG_MO] = bin2bcd(tm->tm_mon + 1); | |
138 | buf[ABX8XX_REG_YR] = bin2bcd(tm->tm_year - 100); | |
139 | buf[ABX8XX_REG_WD] = tm->tm_wday; | |
140 | ||
141 | err = i2c_smbus_write_i2c_block_data(client, ABX8XX_REG_HTH, | |
142 | sizeof(buf), buf); | |
143 | if (err < 0) { | |
144 | dev_err(&client->dev, "Unable to write to date registers\n"); | |
145 | return -EIO; | |
146 | } | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
151 | static const struct rtc_class_ops abx80x_rtc_ops = { | |
152 | .read_time = abx80x_rtc_read_time, | |
153 | .set_time = abx80x_rtc_set_time, | |
154 | }; | |
155 | ||
156 | static int abx80x_dt_trickle_cfg(struct device_node *np) | |
157 | { | |
158 | const char *diode; | |
159 | int trickle_cfg = 0; | |
160 | int i, ret; | |
161 | u32 tmp; | |
162 | ||
163 | ret = of_property_read_string(np, "abracon,tc-diode", &diode); | |
164 | if (ret) | |
165 | return ret; | |
166 | ||
167 | if (!strcmp(diode, "standard")) | |
168 | trickle_cfg |= ABX8XX_TRICKLE_STANDARD_DIODE; | |
169 | else if (!strcmp(diode, "schottky")) | |
170 | trickle_cfg |= ABX8XX_TRICKLE_SCHOTTKY_DIODE; | |
171 | else | |
172 | return -EINVAL; | |
173 | ||
174 | ret = of_property_read_u32(np, "abracon,tc-resistor", &tmp); | |
175 | if (ret) | |
176 | return ret; | |
177 | ||
178 | for (i = 0; i < sizeof(trickle_resistors); i++) | |
179 | if (trickle_resistors[i] == tmp) | |
180 | break; | |
181 | ||
182 | if (i == sizeof(trickle_resistors)) | |
183 | return -EINVAL; | |
184 | ||
185 | return (trickle_cfg | i); | |
186 | } | |
187 | ||
188 | static int abx80x_probe(struct i2c_client *client, | |
189 | const struct i2c_device_id *id) | |
190 | { | |
191 | struct device_node *np = client->dev.of_node; | |
192 | struct rtc_device *rtc; | |
193 | int i, data, err, trickle_cfg = -EINVAL; | |
194 | char buf[7]; | |
195 | unsigned int part = id->driver_data; | |
196 | unsigned int partnumber; | |
197 | unsigned int majrev, minrev; | |
198 | unsigned int lot; | |
199 | unsigned int wafer; | |
200 | unsigned int uid; | |
201 | ||
202 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) | |
203 | return -ENODEV; | |
204 | ||
205 | err = i2c_smbus_read_i2c_block_data(client, ABX8XX_REG_ID0, | |
206 | sizeof(buf), buf); | |
207 | if (err < 0) { | |
208 | dev_err(&client->dev, "Unable to read partnumber\n"); | |
209 | return -EIO; | |
210 | } | |
211 | ||
212 | partnumber = (buf[0] << 8) | buf[1]; | |
213 | majrev = buf[2] >> 3; | |
214 | minrev = buf[2] & 0x7; | |
215 | lot = ((buf[4] & 0x80) << 2) | ((buf[6] & 0x80) << 1) | buf[3]; | |
216 | uid = ((buf[4] & 0x7f) << 8) | buf[5]; | |
217 | wafer = (buf[6] & 0x7c) >> 2; | |
218 | dev_info(&client->dev, "model %04x, revision %u.%u, lot %x, wafer %x, uid %x\n", | |
219 | partnumber, majrev, minrev, lot, wafer, uid); | |
220 | ||
221 | data = i2c_smbus_read_byte_data(client, ABX8XX_REG_CTRL1); | |
222 | if (data < 0) { | |
223 | dev_err(&client->dev, "Unable to read control register\n"); | |
224 | return -EIO; | |
225 | } | |
226 | ||
227 | err = i2c_smbus_write_byte_data(client, ABX8XX_REG_CTRL1, | |
228 | ((data & ~ABX8XX_CTRL_12_24) | | |
229 | ABX8XX_CTRL_WRITE)); | |
230 | if (err < 0) { | |
231 | dev_err(&client->dev, "Unable to write control register\n"); | |
232 | return -EIO; | |
233 | } | |
234 | ||
235 | /* part autodetection */ | |
236 | if (part == ABX80X) { | |
237 | for (i = 0; abx80x_caps[i].pn; i++) | |
238 | if (partnumber == abx80x_caps[i].pn) | |
239 | break; | |
240 | if (abx80x_caps[i].pn == 0) { | |
241 | dev_err(&client->dev, "Unknown part: %04x\n", | |
242 | partnumber); | |
243 | return -EINVAL; | |
244 | } | |
245 | part = i; | |
246 | } | |
247 | ||
248 | if (partnumber != abx80x_caps[part].pn) { | |
249 | dev_err(&client->dev, "partnumber mismatch %04x != %04x\n", | |
250 | partnumber, abx80x_caps[part].pn); | |
251 | return -EINVAL; | |
252 | } | |
253 | ||
254 | if (np && abx80x_caps[part].has_tc) | |
255 | trickle_cfg = abx80x_dt_trickle_cfg(np); | |
256 | ||
257 | if (trickle_cfg > 0) { | |
258 | dev_info(&client->dev, "Enabling trickle charger: %02x\n", | |
259 | trickle_cfg); | |
260 | abx80x_enable_trickle_charger(client, trickle_cfg); | |
261 | } | |
262 | ||
263 | rtc = devm_rtc_device_register(&client->dev, abx80x_driver.driver.name, | |
264 | &abx80x_rtc_ops, THIS_MODULE); | |
265 | ||
266 | if (IS_ERR(rtc)) | |
267 | return PTR_ERR(rtc); | |
268 | ||
269 | i2c_set_clientdata(client, rtc); | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | static int abx80x_remove(struct i2c_client *client) | |
275 | { | |
276 | return 0; | |
277 | } | |
278 | ||
279 | static const struct i2c_device_id abx80x_id[] = { | |
280 | { "abx80x", ABX80X }, | |
281 | { "ab0801", AB0801 }, | |
282 | { "ab0803", AB0803 }, | |
283 | { "ab0804", AB0804 }, | |
284 | { "ab0805", AB0805 }, | |
285 | { "ab1801", AB1801 }, | |
286 | { "ab1803", AB1803 }, | |
287 | { "ab1804", AB1804 }, | |
288 | { "ab1805", AB1805 }, | |
289 | { } | |
290 | }; | |
291 | MODULE_DEVICE_TABLE(i2c, abx80x_id); | |
292 | ||
293 | static struct i2c_driver abx80x_driver = { | |
294 | .driver = { | |
295 | .name = "rtc-abx80x", | |
296 | }, | |
297 | .probe = abx80x_probe, | |
298 | .remove = abx80x_remove, | |
299 | .id_table = abx80x_id, | |
300 | }; | |
301 | ||
302 | module_i2c_driver(abx80x_driver); | |
303 | ||
304 | MODULE_AUTHOR("Philippe De Muyter <phdm@macqel.be>"); | |
305 | MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@free-electrons.com>"); | |
306 | MODULE_DESCRIPTION("Abracon ABX80X RTC driver"); | |
307 | MODULE_LICENSE("GPL v2"); |