Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
d6c7428f RF |
2 | /* |
3 | * An I2C driver for the Intersil ISL 12022 | |
4 | * | |
5 | * Author: Roman Fietze <roman.fietze@telemotive.de> | |
6 | * | |
7 | * Based on the Philips PCF8563 RTC | |
8 | * by Alessandro Zummo <a.zummo@towertech.it>. | |
d6c7428f RF |
9 | */ |
10 | ||
d6c7428f | 11 | #include <linux/bcd.h> |
17cc54b2 | 12 | #include <linux/err.h> |
8d816c1e | 13 | #include <linux/hwmon.h> |
303eac65 AS |
14 | #include <linux/i2c.h> |
15 | #include <linux/module.h> | |
16 | #include <linux/regmap.h> | |
17 | #include <linux/rtc.h> | |
18 | #include <linux/slab.h> | |
d6c7428f | 19 | |
93219a4f AS |
20 | #include <asm/byteorder.h> |
21 | ||
d6c7428f RF |
22 | /* ISL register offsets */ |
23 | #define ISL12022_REG_SC 0x00 | |
24 | #define ISL12022_REG_MN 0x01 | |
25 | #define ISL12022_REG_HR 0x02 | |
26 | #define ISL12022_REG_DT 0x03 | |
27 | #define ISL12022_REG_MO 0x04 | |
28 | #define ISL12022_REG_YR 0x05 | |
29 | #define ISL12022_REG_DW 0x06 | |
30 | ||
31 | #define ISL12022_REG_SR 0x07 | |
32 | #define ISL12022_REG_INT 0x08 | |
33 | ||
8d816c1e RV |
34 | #define ISL12022_REG_BETA 0x0d |
35 | #define ISL12022_REG_TEMP_L 0x28 | |
36 | ||
d6c7428f RF |
37 | /* ISL register bits */ |
38 | #define ISL12022_HR_MIL (1 << 7) /* military or 24 hour time */ | |
39 | ||
40 | #define ISL12022_SR_LBAT85 (1 << 2) | |
41 | #define ISL12022_SR_LBAT75 (1 << 1) | |
42 | ||
43 | #define ISL12022_INT_WRTC (1 << 6) | |
44 | ||
8d816c1e | 45 | #define ISL12022_BETA_TSE (1 << 7) |
d6c7428f | 46 | |
8d816c1e RV |
47 | static umode_t isl12022_hwmon_is_visible(const void *data, |
48 | enum hwmon_sensor_types type, | |
49 | u32 attr, int channel) | |
50 | { | |
51 | if (type == hwmon_temp && attr == hwmon_temp_input) | |
52 | return 0444; | |
53 | ||
54 | return 0; | |
55 | } | |
56 | ||
57 | /* | |
58 | * A user-initiated temperature conversion is not started by this function, | |
59 | * so the temperature is updated once every ~60 seconds. | |
60 | */ | |
61 | static int isl12022_hwmon_read_temp(struct device *dev, long *mC) | |
62 | { | |
f525b210 | 63 | struct regmap *regmap = dev_get_drvdata(dev); |
8d816c1e | 64 | int temp, ret; |
93219a4f | 65 | __le16 buf; |
8d816c1e | 66 | |
93219a4f | 67 | ret = regmap_bulk_read(regmap, ISL12022_REG_TEMP_L, &buf, sizeof(buf)); |
8d816c1e RV |
68 | if (ret) |
69 | return ret; | |
70 | /* | |
71 | * Temperature is represented as a 10-bit number, unit half-Kelvins. | |
72 | */ | |
93219a4f | 73 | temp = le16_to_cpu(buf); |
8d816c1e RV |
74 | temp *= 500; |
75 | temp -= 273000; | |
76 | ||
77 | *mC = temp; | |
78 | ||
79 | return 0; | |
80 | } | |
81 | ||
82 | static int isl12022_hwmon_read(struct device *dev, | |
83 | enum hwmon_sensor_types type, | |
84 | u32 attr, int channel, long *val) | |
85 | { | |
86 | if (type == hwmon_temp && attr == hwmon_temp_input) | |
87 | return isl12022_hwmon_read_temp(dev, val); | |
88 | ||
89 | return -EOPNOTSUPP; | |
90 | } | |
91 | ||
303b1e89 | 92 | static const struct hwmon_channel_info * const isl12022_hwmon_info[] = { |
8d816c1e RV |
93 | HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), |
94 | NULL | |
95 | }; | |
96 | ||
97 | static const struct hwmon_ops isl12022_hwmon_ops = { | |
98 | .is_visible = isl12022_hwmon_is_visible, | |
99 | .read = isl12022_hwmon_read, | |
100 | }; | |
101 | ||
102 | static const struct hwmon_chip_info isl12022_hwmon_chip_info = { | |
103 | .ops = &isl12022_hwmon_ops, | |
104 | .info = isl12022_hwmon_info, | |
105 | }; | |
106 | ||
107 | static void isl12022_hwmon_register(struct device *dev) | |
108 | { | |
f525b210 | 109 | struct regmap *regmap = dev_get_drvdata(dev); |
8d816c1e RV |
110 | struct device *hwmon; |
111 | int ret; | |
112 | ||
113 | if (!IS_REACHABLE(CONFIG_HWMON)) | |
114 | return; | |
115 | ||
f525b210 | 116 | ret = regmap_update_bits(regmap, ISL12022_REG_BETA, |
8d816c1e RV |
117 | ISL12022_BETA_TSE, ISL12022_BETA_TSE); |
118 | if (ret) { | |
119 | dev_warn(dev, "unable to enable temperature sensor\n"); | |
120 | return; | |
121 | } | |
122 | ||
f525b210 | 123 | hwmon = devm_hwmon_device_register_with_info(dev, "isl12022", regmap, |
8d816c1e RV |
124 | &isl12022_hwmon_chip_info, |
125 | NULL); | |
126 | if (IS_ERR(hwmon)) | |
127 | dev_warn(dev, "unable to register hwmon device: %pe\n", hwmon); | |
128 | } | |
129 | ||
d6c7428f RF |
130 | /* |
131 | * In the routines that deal directly with the isl12022 hardware, we use | |
132 | * rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch. | |
133 | */ | |
143d92be | 134 | static int isl12022_rtc_read_time(struct device *dev, struct rtc_time *tm) |
d6c7428f | 135 | { |
f525b210 | 136 | struct regmap *regmap = dev_get_drvdata(dev); |
d6c7428f RF |
137 | uint8_t buf[ISL12022_REG_INT + 1]; |
138 | int ret; | |
139 | ||
b1a1baa6 | 140 | ret = regmap_bulk_read(regmap, ISL12022_REG_SC, buf, sizeof(buf)); |
d6c7428f RF |
141 | if (ret) |
142 | return ret; | |
143 | ||
144 | if (buf[ISL12022_REG_SR] & (ISL12022_SR_LBAT85 | ISL12022_SR_LBAT75)) { | |
ca358871 | 145 | dev_warn(dev, |
9bb1e189 | 146 | "voltage dropped below %u%%, date and time is not reliable.\n", |
d6c7428f RF |
147 | buf[ISL12022_REG_SR] & ISL12022_SR_LBAT85 ? 85 : 75); |
148 | } | |
149 | ||
ca358871 | 150 | dev_dbg(dev, |
9bb1e189 | 151 | "raw data is sec=%02x, min=%02x, hr=%02x, mday=%02x, mon=%02x, year=%02x, wday=%02x, sr=%02x, int=%02x", |
d6c7428f RF |
152 | buf[ISL12022_REG_SC], |
153 | buf[ISL12022_REG_MN], | |
154 | buf[ISL12022_REG_HR], | |
155 | buf[ISL12022_REG_DT], | |
156 | buf[ISL12022_REG_MO], | |
157 | buf[ISL12022_REG_YR], | |
158 | buf[ISL12022_REG_DW], | |
159 | buf[ISL12022_REG_SR], | |
160 | buf[ISL12022_REG_INT]); | |
161 | ||
162 | tm->tm_sec = bcd2bin(buf[ISL12022_REG_SC] & 0x7F); | |
163 | tm->tm_min = bcd2bin(buf[ISL12022_REG_MN] & 0x7F); | |
164 | tm->tm_hour = bcd2bin(buf[ISL12022_REG_HR] & 0x3F); | |
165 | tm->tm_mday = bcd2bin(buf[ISL12022_REG_DT] & 0x3F); | |
166 | tm->tm_wday = buf[ISL12022_REG_DW] & 0x07; | |
167 | tm->tm_mon = bcd2bin(buf[ISL12022_REG_MO] & 0x1F) - 1; | |
168 | tm->tm_year = bcd2bin(buf[ISL12022_REG_YR]) + 100; | |
169 | ||
7093b8a4 | 170 | dev_dbg(dev, "%s: %ptR\n", __func__, tm); |
d6c7428f | 171 | |
22652ba7 | 172 | return 0; |
d6c7428f RF |
173 | } |
174 | ||
143d92be | 175 | static int isl12022_rtc_set_time(struct device *dev, struct rtc_time *tm) |
d6c7428f | 176 | { |
f525b210 | 177 | struct regmap *regmap = dev_get_drvdata(dev); |
d6c7428f RF |
178 | int ret; |
179 | uint8_t buf[ISL12022_REG_DW + 1]; | |
180 | ||
7093b8a4 | 181 | dev_dbg(dev, "%s: %ptR\n", __func__, tm); |
d6c7428f | 182 | |
b1a1baa6 RV |
183 | /* Ensure the write enable bit is set. */ |
184 | ret = regmap_update_bits(regmap, ISL12022_REG_INT, | |
185 | ISL12022_INT_WRTC, ISL12022_INT_WRTC); | |
186 | if (ret) | |
187 | return ret; | |
d6c7428f RF |
188 | |
189 | /* hours, minutes and seconds */ | |
190 | buf[ISL12022_REG_SC] = bin2bcd(tm->tm_sec); | |
191 | buf[ISL12022_REG_MN] = bin2bcd(tm->tm_min); | |
6d23b258 | 192 | buf[ISL12022_REG_HR] = bin2bcd(tm->tm_hour) | ISL12022_HR_MIL; |
d6c7428f RF |
193 | |
194 | buf[ISL12022_REG_DT] = bin2bcd(tm->tm_mday); | |
195 | ||
196 | /* month, 1 - 12 */ | |
197 | buf[ISL12022_REG_MO] = bin2bcd(tm->tm_mon + 1); | |
198 | ||
199 | /* year and century */ | |
200 | buf[ISL12022_REG_YR] = bin2bcd(tm->tm_year % 100); | |
201 | ||
202 | buf[ISL12022_REG_DW] = tm->tm_wday & 0x07; | |
203 | ||
f525b210 | 204 | return regmap_bulk_write(regmap, ISL12022_REG_SC, buf, sizeof(buf)); |
d6c7428f RF |
205 | } |
206 | ||
d6c7428f RF |
207 | static const struct rtc_class_ops isl12022_rtc_ops = { |
208 | .read_time = isl12022_rtc_read_time, | |
209 | .set_time = isl12022_rtc_set_time, | |
210 | }; | |
211 | ||
b1a1baa6 RV |
212 | static const struct regmap_config regmap_config = { |
213 | .reg_bits = 8, | |
214 | .val_bits = 8, | |
215 | .use_single_write = true, | |
216 | }; | |
217 | ||
3f4a3322 | 218 | static int isl12022_probe(struct i2c_client *client) |
d6c7428f | 219 | { |
f525b210 AS |
220 | struct rtc_device *rtc; |
221 | struct regmap *regmap; | |
d6c7428f | 222 | |
d6c7428f RF |
223 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) |
224 | return -ENODEV; | |
225 | ||
f525b210 AS |
226 | regmap = devm_regmap_init_i2c(client, ®map_config); |
227 | if (IS_ERR(regmap)) { | |
b1a1baa6 | 228 | dev_err(&client->dev, "regmap allocation failed\n"); |
f525b210 | 229 | return PTR_ERR(regmap); |
b1a1baa6 RV |
230 | } |
231 | ||
f525b210 AS |
232 | dev_set_drvdata(&client->dev, regmap); |
233 | ||
8d816c1e RV |
234 | isl12022_hwmon_register(&client->dev); |
235 | ||
f525b210 AS |
236 | rtc = devm_rtc_allocate_device(&client->dev); |
237 | if (IS_ERR(rtc)) | |
238 | return PTR_ERR(rtc); | |
a35a2ad2 | 239 | |
f525b210 AS |
240 | rtc->ops = &isl12022_rtc_ops; |
241 | rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; | |
242 | rtc->range_max = RTC_TIMESTAMP_END_2099; | |
a35a2ad2 | 243 | |
f525b210 | 244 | return devm_rtc_register_device(rtc); |
d6c7428f RF |
245 | } |
246 | ||
a28885bc | 247 | static const struct of_device_id isl12022_dt_match[] = { |
c5cd8273 AE |
248 | { .compatible = "isl,isl12022" }, /* for backward compat., don't use */ |
249 | { .compatible = "isil,isl12022" }, | |
db04d628 SL |
250 | { }, |
251 | }; | |
1c4fc295 | 252 | MODULE_DEVICE_TABLE(of, isl12022_dt_match); |
db04d628 | 253 | |
d6c7428f RF |
254 | static const struct i2c_device_id isl12022_id[] = { |
255 | { "isl12022", 0 }, | |
d6c7428f RF |
256 | { } |
257 | }; | |
258 | MODULE_DEVICE_TABLE(i2c, isl12022_id); | |
259 | ||
260 | static struct i2c_driver isl12022_driver = { | |
261 | .driver = { | |
262 | .name = "rtc-isl12022", | |
c8ae2735 | 263 | .of_match_table = isl12022_dt_match, |
d6c7428f | 264 | }, |
31b0cecb | 265 | .probe = isl12022_probe, |
d6c7428f RF |
266 | .id_table = isl12022_id, |
267 | }; | |
268 | ||
0abc9201 | 269 | module_i2c_driver(isl12022_driver); |
d6c7428f RF |
270 | |
271 | MODULE_AUTHOR("roman.fietze@telemotive.de"); | |
272 | MODULE_DESCRIPTION("ISL 12022 RTC driver"); | |
273 | MODULE_LICENSE("GPL"); |