1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ST M48T86 / Dallas DS12887 RTC driver |
4 | * Copyright (c) 2006 Tower Technologies |
5 | * |
6 | * Author: Alessandro Zummo <a.zummo@towertech.it> |
7 | * |
8 | * This drivers only supports the clock running in BCD and 24H mode. |
9 | * If it will be ever adapted to binary and 12H mode, care must be taken |
10 | * to not introduce bugs. |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/mod_devicetable.h> |
15 | #include <linux/rtc.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/bcd.h> |
18 | #include <linux/io.h> |
19 | |
20 | #define M48T86_SEC 0x00 |
21 | #define M48T86_SECALRM 0x01 |
22 | #define M48T86_MIN 0x02 |
23 | #define M48T86_MINALRM 0x03 |
24 | #define M48T86_HOUR 0x04 |
25 | #define M48T86_HOURALRM 0x05 |
26 | #define M48T86_DOW 0x06 /* 1 = sunday */ |
27 | #define M48T86_DOM 0x07 |
28 | #define M48T86_MONTH 0x08 /* 1 - 12 */ |
29 | #define M48T86_YEAR 0x09 /* 0 - 99 */ |
30 | #define M48T86_A 0x0a |
31 | #define M48T86_B 0x0b |
32 | #define M48T86_B_SET BIT(7) |
33 | #define M48T86_B_DM BIT(2) |
34 | #define M48T86_B_H24 BIT(1) |
35 | #define M48T86_C 0x0c |
36 | #define M48T86_D 0x0d |
37 | #define M48T86_D_VRT BIT(7) |
38 | #define M48T86_NVRAM(x) (0x0e + (x)) |
39 | #define M48T86_NVRAM_LEN 114 |
40 | |
41 | struct m48t86_rtc_info { |
42 | void __iomem *index_reg; |
43 | void __iomem *data_reg; |
44 | struct rtc_device *rtc; |
45 | }; |
46 | |
47 | static unsigned char m48t86_readb(struct device *dev, unsigned long addr) |
48 | { |
49 | struct m48t86_rtc_info *info = dev_get_drvdata(dev); |
50 | unsigned char value; |
51 | |
52 | writeb(val: addr, addr: info->index_reg); |
53 | value = readb(addr: info->data_reg); |
54 | |
55 | return value; |
56 | } |
57 | |
58 | static void m48t86_writeb(struct device *dev, |
59 | unsigned char value, unsigned long addr) |
60 | { |
61 | struct m48t86_rtc_info *info = dev_get_drvdata(dev); |
62 | |
63 | writeb(val: addr, addr: info->index_reg); |
64 | writeb(val: value, addr: info->data_reg); |
65 | } |
66 | |
67 | static int m48t86_rtc_read_time(struct device *dev, struct rtc_time *tm) |
68 | { |
69 | unsigned char reg; |
70 | |
71 | reg = m48t86_readb(dev, M48T86_B); |
72 | |
73 | if (reg & M48T86_B_DM) { |
74 | /* data (binary) mode */ |
75 | tm->tm_sec = m48t86_readb(dev, M48T86_SEC); |
76 | tm->tm_min = m48t86_readb(dev, M48T86_MIN); |
77 | tm->tm_hour = m48t86_readb(dev, M48T86_HOUR) & 0x3f; |
78 | tm->tm_mday = m48t86_readb(dev, M48T86_DOM); |
79 | /* tm_mon is 0-11 */ |
80 | tm->tm_mon = m48t86_readb(dev, M48T86_MONTH) - 1; |
81 | tm->tm_year = m48t86_readb(dev, M48T86_YEAR) + 100; |
82 | tm->tm_wday = m48t86_readb(dev, M48T86_DOW); |
83 | } else { |
84 | /* bcd mode */ |
85 | tm->tm_sec = bcd2bin(m48t86_readb(dev, M48T86_SEC)); |
86 | tm->tm_min = bcd2bin(m48t86_readb(dev, M48T86_MIN)); |
87 | tm->tm_hour = bcd2bin(m48t86_readb(dev, M48T86_HOUR) & |
88 | 0x3f); |
89 | tm->tm_mday = bcd2bin(m48t86_readb(dev, M48T86_DOM)); |
90 | /* tm_mon is 0-11 */ |
91 | tm->tm_mon = bcd2bin(m48t86_readb(dev, M48T86_MONTH)) - 1; |
92 | tm->tm_year = bcd2bin(m48t86_readb(dev, M48T86_YEAR)) + 100; |
93 | tm->tm_wday = bcd2bin(m48t86_readb(dev, M48T86_DOW)); |
94 | } |
95 | |
96 | /* correct the hour if the clock is in 12h mode */ |
97 | if (!(reg & M48T86_B_H24)) |
98 | if (m48t86_readb(dev, M48T86_HOUR) & 0x80) |
99 | tm->tm_hour += 12; |
100 | |
101 | return 0; |
102 | } |
103 | |
104 | static int m48t86_rtc_set_time(struct device *dev, struct rtc_time *tm) |
105 | { |
106 | unsigned char reg; |
107 | |
108 | reg = m48t86_readb(dev, M48T86_B); |
109 | |
110 | /* update flag and 24h mode */ |
111 | reg |= M48T86_B_SET | M48T86_B_H24; |
112 | m48t86_writeb(dev, value: reg, M48T86_B); |
113 | |
114 | if (reg & M48T86_B_DM) { |
115 | /* data (binary) mode */ |
116 | m48t86_writeb(dev, value: tm->tm_sec, M48T86_SEC); |
117 | m48t86_writeb(dev, value: tm->tm_min, M48T86_MIN); |
118 | m48t86_writeb(dev, value: tm->tm_hour, M48T86_HOUR); |
119 | m48t86_writeb(dev, value: tm->tm_mday, M48T86_DOM); |
120 | m48t86_writeb(dev, value: tm->tm_mon + 1, M48T86_MONTH); |
121 | m48t86_writeb(dev, value: tm->tm_year % 100, M48T86_YEAR); |
122 | m48t86_writeb(dev, value: tm->tm_wday, M48T86_DOW); |
123 | } else { |
124 | /* bcd mode */ |
125 | m48t86_writeb(dev, bin2bcd(tm->tm_sec), M48T86_SEC); |
126 | m48t86_writeb(dev, bin2bcd(tm->tm_min), M48T86_MIN); |
127 | m48t86_writeb(dev, bin2bcd(tm->tm_hour), M48T86_HOUR); |
128 | m48t86_writeb(dev, bin2bcd(tm->tm_mday), M48T86_DOM); |
129 | m48t86_writeb(dev, bin2bcd(tm->tm_mon + 1), M48T86_MONTH); |
130 | m48t86_writeb(dev, bin2bcd(tm->tm_year % 100), M48T86_YEAR); |
131 | m48t86_writeb(dev, bin2bcd(tm->tm_wday), M48T86_DOW); |
132 | } |
133 | |
134 | /* update ended */ |
135 | reg &= ~M48T86_B_SET; |
136 | m48t86_writeb(dev, value: reg, M48T86_B); |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | static int m48t86_rtc_proc(struct device *dev, struct seq_file *seq) |
142 | { |
143 | unsigned char reg; |
144 | |
145 | reg = m48t86_readb(dev, M48T86_B); |
146 | |
147 | seq_printf(m: seq, fmt: "mode\t\t: %s\n" , |
148 | (reg & M48T86_B_DM) ? "binary" : "bcd" ); |
149 | |
150 | reg = m48t86_readb(dev, M48T86_D); |
151 | |
152 | seq_printf(m: seq, fmt: "battery\t\t: %s\n" , |
153 | (reg & M48T86_D_VRT) ? "ok" : "exhausted" ); |
154 | |
155 | return 0; |
156 | } |
157 | |
158 | static const struct rtc_class_ops m48t86_rtc_ops = { |
159 | .read_time = m48t86_rtc_read_time, |
160 | .set_time = m48t86_rtc_set_time, |
161 | .proc = m48t86_rtc_proc, |
162 | }; |
163 | |
164 | static int m48t86_nvram_read(void *priv, unsigned int off, void *buf, |
165 | size_t count) |
166 | { |
167 | struct device *dev = priv; |
168 | unsigned int i; |
169 | |
170 | for (i = 0; i < count; i++) |
171 | ((u8 *)buf)[i] = m48t86_readb(dev, M48T86_NVRAM(off + i)); |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static int m48t86_nvram_write(void *priv, unsigned int off, void *buf, |
177 | size_t count) |
178 | { |
179 | struct device *dev = priv; |
180 | unsigned int i; |
181 | |
182 | for (i = 0; i < count; i++) |
183 | m48t86_writeb(dev, value: ((u8 *)buf)[i], M48T86_NVRAM(off + i)); |
184 | |
185 | return 0; |
186 | } |
187 | |
188 | /* |
189 | * The RTC is an optional feature at purchase time on some Technologic Systems |
190 | * boards. Verify that it actually exists by checking if the last two bytes |
191 | * of the NVRAM can be changed. |
192 | * |
193 | * This is based on the method used in their rtc7800.c example. |
194 | */ |
195 | static bool m48t86_verify_chip(struct platform_device *pdev) |
196 | { |
197 | unsigned int offset0 = M48T86_NVRAM(M48T86_NVRAM_LEN - 2); |
198 | unsigned int offset1 = M48T86_NVRAM(M48T86_NVRAM_LEN - 1); |
199 | unsigned char tmp0, tmp1; |
200 | |
201 | tmp0 = m48t86_readb(dev: &pdev->dev, addr: offset0); |
202 | tmp1 = m48t86_readb(dev: &pdev->dev, addr: offset1); |
203 | |
204 | m48t86_writeb(dev: &pdev->dev, value: 0x00, addr: offset0); |
205 | m48t86_writeb(dev: &pdev->dev, value: 0x55, addr: offset1); |
206 | if (m48t86_readb(dev: &pdev->dev, addr: offset1) == 0x55) { |
207 | m48t86_writeb(dev: &pdev->dev, value: 0xaa, addr: offset1); |
208 | if (m48t86_readb(dev: &pdev->dev, addr: offset1) == 0xaa && |
209 | m48t86_readb(dev: &pdev->dev, addr: offset0) == 0x00) { |
210 | m48t86_writeb(dev: &pdev->dev, value: tmp0, addr: offset0); |
211 | m48t86_writeb(dev: &pdev->dev, value: tmp1, addr: offset1); |
212 | |
213 | return true; |
214 | } |
215 | } |
216 | return false; |
217 | } |
218 | |
219 | static int m48t86_rtc_probe(struct platform_device *pdev) |
220 | { |
221 | struct m48t86_rtc_info *info; |
222 | unsigned char reg; |
223 | int err; |
224 | struct nvmem_config m48t86_nvmem_cfg = { |
225 | .name = "m48t86_nvram" , |
226 | .word_size = 1, |
227 | .stride = 1, |
228 | .size = M48T86_NVRAM_LEN, |
229 | .reg_read = m48t86_nvram_read, |
230 | .reg_write = m48t86_nvram_write, |
231 | .priv = &pdev->dev, |
232 | }; |
233 | |
234 | info = devm_kzalloc(dev: &pdev->dev, size: sizeof(*info), GFP_KERNEL); |
235 | if (!info) |
236 | return -ENOMEM; |
237 | |
238 | info->index_reg = devm_platform_ioremap_resource(pdev, index: 0); |
239 | if (IS_ERR(ptr: info->index_reg)) |
240 | return PTR_ERR(ptr: info->index_reg); |
241 | |
242 | info->data_reg = devm_platform_ioremap_resource(pdev, index: 1); |
243 | if (IS_ERR(ptr: info->data_reg)) |
244 | return PTR_ERR(ptr: info->data_reg); |
245 | |
246 | dev_set_drvdata(dev: &pdev->dev, data: info); |
247 | |
248 | if (!m48t86_verify_chip(pdev)) { |
249 | dev_info(&pdev->dev, "RTC not present\n" ); |
250 | return -ENODEV; |
251 | } |
252 | |
253 | info->rtc = devm_rtc_allocate_device(dev: &pdev->dev); |
254 | if (IS_ERR(ptr: info->rtc)) |
255 | return PTR_ERR(ptr: info->rtc); |
256 | |
257 | info->rtc->ops = &m48t86_rtc_ops; |
258 | |
259 | err = devm_rtc_register_device(info->rtc); |
260 | if (err) |
261 | return err; |
262 | |
263 | devm_rtc_nvmem_register(rtc: info->rtc, nvmem_config: &m48t86_nvmem_cfg); |
264 | |
265 | /* read battery status */ |
266 | reg = m48t86_readb(dev: &pdev->dev, M48T86_D); |
267 | dev_info(&pdev->dev, "battery %s\n" , |
268 | (reg & M48T86_D_VRT) ? "ok" : "exhausted" ); |
269 | |
270 | return 0; |
271 | } |
272 | |
273 | static const struct of_device_id m48t86_rtc_of_ids[] = { |
274 | { .compatible = "st,m48t86" }, |
275 | { /* sentinel */ } |
276 | }; |
277 | MODULE_DEVICE_TABLE(of, m48t86_rtc_of_ids); |
278 | |
279 | static struct platform_driver m48t86_rtc_platform_driver = { |
280 | .driver = { |
281 | .name = "rtc-m48t86" , |
282 | .of_match_table = m48t86_rtc_of_ids, |
283 | }, |
284 | .probe = m48t86_rtc_probe, |
285 | }; |
286 | |
287 | module_platform_driver(m48t86_rtc_platform_driver); |
288 | |
289 | MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>" ); |
290 | MODULE_DESCRIPTION("M48T86 RTC driver" ); |
291 | MODULE_LICENSE("GPL" ); |
292 | MODULE_ALIAS("platform:rtc-m48t86" ); |
293 | |