1 | /* |
2 | * HP i8042 SDC + MSM-58321 BBRTC driver. |
3 | * |
4 | * Copyright (c) 2001 Brian S. Julin |
5 | * All rights reserved. |
6 | * |
7 | * Redistribution and use in source and binary forms, with or without |
8 | * modification, are permitted provided that the following conditions |
9 | * are met: |
10 | * 1. Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions, and the following disclaimer, |
12 | * without modification. |
13 | * 2. The name of the author may not be used to endorse or promote products |
14 | * derived from this software without specific prior written permission. |
15 | * |
16 | * Alternatively, this software may be distributed under the terms of the |
17 | * GNU General Public License ("GPL"). |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR |
23 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
28 | * |
29 | * References: |
30 | * System Device Controller Microprocessor Firmware Theory of Operation |
31 | * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2 |
32 | * efirtc.c by Stephane Eranian/Hewlett Packard |
33 | * |
34 | */ |
35 | |
36 | #include <linux/hp_sdc.h> |
37 | #include <linux/errno.h> |
38 | #include <linux/types.h> |
39 | #include <linux/init.h> |
40 | #include <linux/module.h> |
41 | #include <linux/time.h> |
42 | #include <linux/miscdevice.h> |
43 | #include <linux/proc_fs.h> |
44 | #include <linux/seq_file.h> |
45 | #include <linux/poll.h> |
46 | #include <linux/rtc.h> |
47 | #include <linux/mutex.h> |
48 | #include <linux/semaphore.h> |
49 | |
50 | MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>" ); |
51 | MODULE_DESCRIPTION("HP i8042 SDC + MSM-58321 RTC Driver" ); |
52 | MODULE_LICENSE("Dual BSD/GPL" ); |
53 | |
54 | #define RTC_VERSION "1.10d" |
55 | |
56 | static unsigned long epoch = 2000; |
57 | |
58 | static struct semaphore i8042tregs; |
59 | |
60 | static void hp_sdc_rtc_isr (int irq, void *dev_id, |
61 | uint8_t status, uint8_t data) |
62 | { |
63 | return; |
64 | } |
65 | |
66 | static int hp_sdc_rtc_do_read_bbrtc (struct rtc_time *rtctm) |
67 | { |
68 | struct semaphore tsem; |
69 | hp_sdc_transaction t; |
70 | uint8_t tseq[91]; |
71 | int i; |
72 | |
73 | i = 0; |
74 | while (i < 91) { |
75 | tseq[i++] = HP_SDC_ACT_DATAREG | |
76 | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN; |
77 | tseq[i++] = 0x01; /* write i8042[0x70] */ |
78 | tseq[i] = i / 7; /* BBRTC reg address */ |
79 | i++; |
80 | tseq[i++] = HP_SDC_CMD_DO_RTCR; /* Trigger command */ |
81 | tseq[i++] = 2; /* expect 1 stat/dat pair back. */ |
82 | i++; i++; /* buffer for stat/dat pair */ |
83 | } |
84 | tseq[84] |= HP_SDC_ACT_SEMAPHORE; |
85 | t.endidx = 91; |
86 | t.seq = tseq; |
87 | t.act.semaphore = &tsem; |
88 | sema_init(sem: &tsem, val: 0); |
89 | |
90 | if (hp_sdc_enqueue_transaction(this: &t)) return -1; |
91 | |
92 | /* Put ourselves to sleep for results. */ |
93 | if (WARN_ON(down_interruptible(&tsem))) |
94 | return -1; |
95 | |
96 | /* Check for nonpresence of BBRTC */ |
97 | if (!((tseq[83] | tseq[90] | tseq[69] | tseq[76] | |
98 | tseq[55] | tseq[62] | tseq[34] | tseq[41] | |
99 | tseq[20] | tseq[27] | tseq[6] | tseq[13]) & 0x0f)) |
100 | return -1; |
101 | |
102 | memset(rtctm, 0, sizeof(struct rtc_time)); |
103 | rtctm->tm_year = (tseq[83] & 0x0f) + (tseq[90] & 0x0f) * 10; |
104 | rtctm->tm_mon = (tseq[69] & 0x0f) + (tseq[76] & 0x0f) * 10; |
105 | rtctm->tm_mday = (tseq[55] & 0x0f) + (tseq[62] & 0x0f) * 10; |
106 | rtctm->tm_wday = (tseq[48] & 0x0f); |
107 | rtctm->tm_hour = (tseq[34] & 0x0f) + (tseq[41] & 0x0f) * 10; |
108 | rtctm->tm_min = (tseq[20] & 0x0f) + (tseq[27] & 0x0f) * 10; |
109 | rtctm->tm_sec = (tseq[6] & 0x0f) + (tseq[13] & 0x0f) * 10; |
110 | |
111 | return 0; |
112 | } |
113 | |
114 | static int hp_sdc_rtc_read_bbrtc (struct rtc_time *rtctm) |
115 | { |
116 | struct rtc_time tm, tm_last; |
117 | int i = 0; |
118 | |
119 | /* MSM-58321 has no read latch, so must read twice and compare. */ |
120 | |
121 | if (hp_sdc_rtc_do_read_bbrtc(rtctm: &tm_last)) return -1; |
122 | if (hp_sdc_rtc_do_read_bbrtc(rtctm: &tm)) return -1; |
123 | |
124 | while (memcmp(p: &tm, q: &tm_last, size: sizeof(struct rtc_time))) { |
125 | if (i++ > 4) return -1; |
126 | memcpy(&tm_last, &tm, sizeof(struct rtc_time)); |
127 | if (hp_sdc_rtc_do_read_bbrtc(rtctm: &tm)) return -1; |
128 | } |
129 | |
130 | memcpy(rtctm, &tm, sizeof(struct rtc_time)); |
131 | |
132 | return 0; |
133 | } |
134 | |
135 | |
136 | static int64_t hp_sdc_rtc_read_i8042timer (uint8_t loadcmd, int numreg) |
137 | { |
138 | hp_sdc_transaction t; |
139 | uint8_t tseq[26] = { |
140 | HP_SDC_ACT_PRECMD | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, |
141 | 0, |
142 | HP_SDC_CMD_READ_T1, 2, 0, 0, |
143 | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, |
144 | HP_SDC_CMD_READ_T2, 2, 0, 0, |
145 | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, |
146 | HP_SDC_CMD_READ_T3, 2, 0, 0, |
147 | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, |
148 | HP_SDC_CMD_READ_T4, 2, 0, 0, |
149 | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, |
150 | HP_SDC_CMD_READ_T5, 2, 0, 0 |
151 | }; |
152 | |
153 | t.endidx = numreg * 5; |
154 | |
155 | tseq[1] = loadcmd; |
156 | tseq[t.endidx - 4] |= HP_SDC_ACT_SEMAPHORE; /* numreg assumed > 1 */ |
157 | |
158 | t.seq = tseq; |
159 | t.act.semaphore = &i8042tregs; |
160 | |
161 | /* Sleep if output regs in use. */ |
162 | if (WARN_ON(down_interruptible(&i8042tregs))) |
163 | return -1; |
164 | |
165 | if (hp_sdc_enqueue_transaction(this: &t)) { |
166 | up(sem: &i8042tregs); |
167 | return -1; |
168 | } |
169 | |
170 | /* Sleep until results come back. */ |
171 | if (WARN_ON(down_interruptible(&i8042tregs))) |
172 | return -1; |
173 | |
174 | up(sem: &i8042tregs); |
175 | |
176 | return (tseq[5] | |
177 | ((uint64_t)(tseq[10]) << 8) | ((uint64_t)(tseq[15]) << 16) | |
178 | ((uint64_t)(tseq[20]) << 24) | ((uint64_t)(tseq[25]) << 32)); |
179 | } |
180 | |
181 | |
182 | /* Read the i8042 real-time clock */ |
183 | static inline int hp_sdc_rtc_read_rt(struct timespec64 *res) { |
184 | int64_t raw; |
185 | uint32_t tenms; |
186 | unsigned int days; |
187 | |
188 | raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_RT, numreg: 5); |
189 | if (raw < 0) return -1; |
190 | |
191 | tenms = (uint32_t)raw & 0xffffff; |
192 | days = (unsigned int)(raw >> 24) & 0xffff; |
193 | |
194 | res->tv_nsec = (long)(tenms % 100) * 10000 * 1000; |
195 | res->tv_sec = (tenms / 100) + (time64_t)days * 86400; |
196 | |
197 | return 0; |
198 | } |
199 | |
200 | |
201 | /* Read the i8042 fast handshake timer */ |
202 | static inline int hp_sdc_rtc_read_fhs(struct timespec64 *res) { |
203 | int64_t raw; |
204 | unsigned int tenms; |
205 | |
206 | raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_FHS, numreg: 2); |
207 | if (raw < 0) return -1; |
208 | |
209 | tenms = (unsigned int)raw & 0xffff; |
210 | |
211 | res->tv_nsec = (long)(tenms % 100) * 10000 * 1000; |
212 | res->tv_sec = (time64_t)(tenms / 100); |
213 | |
214 | return 0; |
215 | } |
216 | |
217 | |
218 | /* Read the i8042 match timer (a.k.a. alarm) */ |
219 | static inline int hp_sdc_rtc_read_mt(struct timespec64 *res) { |
220 | int64_t raw; |
221 | uint32_t tenms; |
222 | |
223 | raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_MT, numreg: 3); |
224 | if (raw < 0) return -1; |
225 | |
226 | tenms = (uint32_t)raw & 0xffffff; |
227 | |
228 | res->tv_nsec = (long)(tenms % 100) * 10000 * 1000; |
229 | res->tv_sec = (time64_t)(tenms / 100); |
230 | |
231 | return 0; |
232 | } |
233 | |
234 | |
235 | /* Read the i8042 delay timer */ |
236 | static inline int hp_sdc_rtc_read_dt(struct timespec64 *res) { |
237 | int64_t raw; |
238 | uint32_t tenms; |
239 | |
240 | raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_DT, numreg: 3); |
241 | if (raw < 0) return -1; |
242 | |
243 | tenms = (uint32_t)raw & 0xffffff; |
244 | |
245 | res->tv_nsec = (long)(tenms % 100) * 10000 * 1000; |
246 | res->tv_sec = (time64_t)(tenms / 100); |
247 | |
248 | return 0; |
249 | } |
250 | |
251 | |
252 | /* Read the i8042 cycle timer (a.k.a. periodic) */ |
253 | static inline int hp_sdc_rtc_read_ct(struct timespec64 *res) { |
254 | int64_t raw; |
255 | uint32_t tenms; |
256 | |
257 | raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_CT, numreg: 3); |
258 | if (raw < 0) return -1; |
259 | |
260 | tenms = (uint32_t)raw & 0xffffff; |
261 | |
262 | res->tv_nsec = (long)(tenms % 100) * 10000 * 1000; |
263 | res->tv_sec = (time64_t)(tenms / 100); |
264 | |
265 | return 0; |
266 | } |
267 | |
268 | static int __maybe_unused hp_sdc_rtc_proc_show(struct seq_file *m, void *v) |
269 | { |
270 | #define YN(bit) ("no") |
271 | #define NY(bit) ("yes") |
272 | struct rtc_time tm; |
273 | struct timespec64 tv; |
274 | |
275 | memset(&tm, 0, sizeof(struct rtc_time)); |
276 | |
277 | if (hp_sdc_rtc_read_bbrtc(rtctm: &tm)) { |
278 | seq_puts(m, s: "BBRTC\t\t: READ FAILED!\n" ); |
279 | } else { |
280 | seq_printf(m, |
281 | fmt: "rtc_time\t: %ptRt\n" |
282 | "rtc_date\t: %ptRd\n" |
283 | "rtc_epoch\t: %04lu\n" , |
284 | &tm, &tm, epoch); |
285 | } |
286 | |
287 | if (hp_sdc_rtc_read_rt(res: &tv)) { |
288 | seq_puts(m, s: "i8042 rtc\t: READ FAILED!\n" ); |
289 | } else { |
290 | seq_printf(m, fmt: "i8042 rtc\t: %lld.%02ld seconds\n" , |
291 | (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L); |
292 | } |
293 | |
294 | if (hp_sdc_rtc_read_fhs(res: &tv)) { |
295 | seq_puts(m, s: "handshake\t: READ FAILED!\n" ); |
296 | } else { |
297 | seq_printf(m, fmt: "handshake\t: %lld.%02ld seconds\n" , |
298 | (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L); |
299 | } |
300 | |
301 | if (hp_sdc_rtc_read_mt(res: &tv)) { |
302 | seq_puts(m, s: "alarm\t\t: READ FAILED!\n" ); |
303 | } else { |
304 | seq_printf(m, fmt: "alarm\t\t: %lld.%02ld seconds\n" , |
305 | (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L); |
306 | } |
307 | |
308 | if (hp_sdc_rtc_read_dt(res: &tv)) { |
309 | seq_puts(m, s: "delay\t\t: READ FAILED!\n" ); |
310 | } else { |
311 | seq_printf(m, fmt: "delay\t\t: %lld.%02ld seconds\n" , |
312 | (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L); |
313 | } |
314 | |
315 | if (hp_sdc_rtc_read_ct(res: &tv)) { |
316 | seq_puts(m, s: "periodic\t: READ FAILED!\n" ); |
317 | } else { |
318 | seq_printf(m, fmt: "periodic\t: %lld.%02ld seconds\n" , |
319 | (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L); |
320 | } |
321 | |
322 | seq_printf(m, |
323 | fmt: "DST_enable\t: %s\n" |
324 | "BCD\t\t: %s\n" |
325 | "24hr\t\t: %s\n" |
326 | "square_wave\t: %s\n" |
327 | "alarm_IRQ\t: %s\n" |
328 | "update_IRQ\t: %s\n" |
329 | "periodic_IRQ\t: %s\n" |
330 | "periodic_freq\t: %ld\n" |
331 | "batt_status\t: %s\n" , |
332 | YN(RTC_DST_EN), |
333 | NY(RTC_DM_BINARY), |
334 | YN(RTC_24H), |
335 | YN(RTC_SQWE), |
336 | YN(RTC_AIE), |
337 | YN(RTC_UIE), |
338 | YN(RTC_PIE), |
339 | 1UL, |
340 | 1 ? "okay" : "dead" ); |
341 | |
342 | return 0; |
343 | #undef YN |
344 | #undef NY |
345 | } |
346 | |
347 | static int __init hp_sdc_rtc_init(void) |
348 | { |
349 | int ret; |
350 | |
351 | #ifdef __mc68000__ |
352 | if (!MACH_IS_HP300) |
353 | return -ENODEV; |
354 | #endif |
355 | |
356 | sema_init(sem: &i8042tregs, val: 1); |
357 | |
358 | if ((ret = hp_sdc_request_timer_irq(callback: &hp_sdc_rtc_isr))) |
359 | return ret; |
360 | |
361 | proc_create_single("driver/rtc" , 0, NULL, hp_sdc_rtc_proc_show); |
362 | |
363 | printk(KERN_INFO "HP i8042 SDC + MSM-58321 RTC support loaded " |
364 | "(RTC v " RTC_VERSION ")\n" ); |
365 | |
366 | return 0; |
367 | } |
368 | |
369 | static void __exit hp_sdc_rtc_exit(void) |
370 | { |
371 | remove_proc_entry ("driver/rtc" , NULL); |
372 | hp_sdc_release_timer_irq(callback: hp_sdc_rtc_isr); |
373 | printk(KERN_INFO "HP i8042 SDC + MSM-58321 RTC support unloaded\n" ); |
374 | } |
375 | |
376 | module_init(hp_sdc_rtc_init); |
377 | module_exit(hp_sdc_rtc_exit); |
378 | |