Commit | Line | Data |
---|---|---|
237f8aaf | 1 | /* uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3 |
1da177e4 LT |
2 | * |
3 | * Copyright 1999 Derrick J Brashear (shadow@dementia.org) | |
237f8aaf | 4 | * Copyright 2008 David S. Miller (davem@davemloft.net) |
1da177e4 LT |
5 | */ |
6 | ||
7 | #include <linux/module.h> | |
1da177e4 LT |
8 | #include <linux/errno.h> |
9 | #include <linux/delay.h> | |
10 | #include <linux/interrupt.h> | |
11 | #include <linux/slab.h> | |
f138e481 | 12 | #include <linux/smp_lock.h> |
1da177e4 LT |
13 | #include <linux/ioport.h> |
14 | #include <linux/init.h> | |
15 | #include <linux/miscdevice.h> | |
16 | #include <linux/mm.h> | |
237f8aaf DM |
17 | #include <linux/of.h> |
18 | #include <linux/of_device.h> | |
1da177e4 LT |
19 | |
20 | #include <asm/openprom.h> | |
21 | #include <asm/oplib.h> | |
22 | #include <asm/system.h> | |
23 | #include <asm/irq.h> | |
24 | #include <asm/io.h> | |
25 | #include <asm/pgtable.h> | |
1da177e4 LT |
26 | |
27 | #define UCTRL_MINOR 174 | |
28 | ||
29 | #define DEBUG 1 | |
30 | #ifdef DEBUG | |
31 | #define dprintk(x) printk x | |
32 | #else | |
33 | #define dprintk(x) | |
34 | #endif | |
35 | ||
36 | struct uctrl_regs { | |
237f8aaf DM |
37 | u32 uctrl_intr; |
38 | u32 uctrl_data; | |
39 | u32 uctrl_stat; | |
40 | u32 uctrl_xxx[5]; | |
1da177e4 LT |
41 | }; |
42 | ||
43 | struct ts102_regs { | |
237f8aaf DM |
44 | u32 card_a_intr; |
45 | u32 card_a_stat; | |
46 | u32 card_a_ctrl; | |
47 | u32 card_a_xxx; | |
48 | u32 card_b_intr; | |
49 | u32 card_b_stat; | |
50 | u32 card_b_ctrl; | |
51 | u32 card_b_xxx; | |
52 | u32 uctrl_intr; | |
53 | u32 uctrl_data; | |
54 | u32 uctrl_stat; | |
55 | u32 uctrl_xxx; | |
56 | u32 ts102_xxx[4]; | |
1da177e4 LT |
57 | }; |
58 | ||
59 | /* Bits for uctrl_intr register */ | |
60 | #define UCTRL_INTR_TXE_REQ 0x01 /* transmit FIFO empty int req */ | |
61 | #define UCTRL_INTR_TXNF_REQ 0x02 /* transmit FIFO not full int req */ | |
62 | #define UCTRL_INTR_RXNE_REQ 0x04 /* receive FIFO not empty int req */ | |
63 | #define UCTRL_INTR_RXO_REQ 0x08 /* receive FIFO overflow int req */ | |
64 | #define UCTRL_INTR_TXE_MSK 0x10 /* transmit FIFO empty mask */ | |
65 | #define UCTRL_INTR_TXNF_MSK 0x20 /* transmit FIFO not full mask */ | |
66 | #define UCTRL_INTR_RXNE_MSK 0x40 /* receive FIFO not empty mask */ | |
67 | #define UCTRL_INTR_RXO_MSK 0x80 /* receive FIFO overflow mask */ | |
68 | ||
69 | /* Bits for uctrl_stat register */ | |
70 | #define UCTRL_STAT_TXE_STA 0x01 /* transmit FIFO empty status */ | |
71 | #define UCTRL_STAT_TXNF_STA 0x02 /* transmit FIFO not full status */ | |
72 | #define UCTRL_STAT_RXNE_STA 0x04 /* receive FIFO not empty status */ | |
73 | #define UCTRL_STAT_RXO_STA 0x08 /* receive FIFO overflow status */ | |
74 | ||
75 | static const char *uctrl_extstatus[16] = { | |
76 | "main power available", | |
77 | "internal battery attached", | |
78 | "external battery attached", | |
79 | "external VGA attached", | |
80 | "external keyboard attached", | |
81 | "external mouse attached", | |
82 | "lid down", | |
83 | "internal battery currently charging", | |
84 | "external battery currently charging", | |
85 | "internal battery currently discharging", | |
86 | "external battery currently discharging", | |
87 | }; | |
88 | ||
89 | /* Everything required for one transaction with the uctrl */ | |
90 | struct uctrl_txn { | |
91 | u8 opcode; | |
92 | u8 inbits; | |
93 | u8 outbits; | |
94 | u8 *inbuf; | |
95 | u8 *outbuf; | |
96 | }; | |
97 | ||
98 | struct uctrl_status { | |
99 | u8 current_temp; /* 0x07 */ | |
100 | u8 reset_status; /* 0x0b */ | |
101 | u16 event_status; /* 0x0c */ | |
102 | u16 error_status; /* 0x10 */ | |
103 | u16 external_status; /* 0x11, 0x1b */ | |
104 | u8 internal_charge; /* 0x18 */ | |
105 | u8 external_charge; /* 0x19 */ | |
106 | u16 control_lcd; /* 0x20 */ | |
107 | u8 control_bitport; /* 0x21 */ | |
108 | u8 speaker_volume; /* 0x23 */ | |
109 | u8 control_tft_brightness; /* 0x24 */ | |
110 | u8 control_kbd_repeat_delay; /* 0x28 */ | |
111 | u8 control_kbd_repeat_period; /* 0x29 */ | |
112 | u8 control_screen_contrast; /* 0x2F */ | |
113 | }; | |
114 | ||
115 | enum uctrl_opcode { | |
116 | READ_SERIAL_NUMBER=0x1, | |
117 | READ_ETHERNET_ADDRESS=0x2, | |
118 | READ_HARDWARE_VERSION=0x3, | |
119 | READ_MICROCONTROLLER_VERSION=0x4, | |
120 | READ_MAX_TEMPERATURE=0x5, | |
121 | READ_MIN_TEMPERATURE=0x6, | |
122 | READ_CURRENT_TEMPERATURE=0x7, | |
123 | READ_SYSTEM_VARIANT=0x8, | |
124 | READ_POWERON_CYCLES=0x9, | |
125 | READ_POWERON_SECONDS=0xA, | |
126 | READ_RESET_STATUS=0xB, | |
127 | READ_EVENT_STATUS=0xC, | |
128 | READ_REAL_TIME_CLOCK=0xD, | |
129 | READ_EXTERNAL_VGA_PORT=0xE, | |
130 | READ_MICROCONTROLLER_ROM_CHECKSUM=0xF, | |
131 | READ_ERROR_STATUS=0x10, | |
132 | READ_EXTERNAL_STATUS=0x11, | |
133 | READ_USER_CONFIGURATION_AREA=0x12, | |
134 | READ_MICROCONTROLLER_VOLTAGE=0x13, | |
135 | READ_INTERNAL_BATTERY_VOLTAGE=0x14, | |
136 | READ_DCIN_VOLTAGE=0x15, | |
137 | READ_HORIZONTAL_POINTER_VOLTAGE=0x16, | |
138 | READ_VERTICAL_POINTER_VOLTAGE=0x17, | |
139 | READ_INTERNAL_BATTERY_CHARGE_LEVEL=0x18, | |
140 | READ_EXTERNAL_BATTERY_CHARGE_LEVEL=0x19, | |
141 | READ_REAL_TIME_CLOCK_ALARM=0x1A, | |
142 | READ_EVENT_STATUS_NO_RESET=0x1B, | |
143 | READ_INTERNAL_KEYBOARD_LAYOUT=0x1C, | |
144 | READ_EXTERNAL_KEYBOARD_LAYOUT=0x1D, | |
145 | READ_EEPROM_STATUS=0x1E, | |
146 | CONTROL_LCD=0x20, | |
147 | CONTROL_BITPORT=0x21, | |
148 | SPEAKER_VOLUME=0x23, | |
149 | CONTROL_TFT_BRIGHTNESS=0x24, | |
150 | CONTROL_WATCHDOG=0x25, | |
151 | CONTROL_FACTORY_EEPROM_AREA=0x26, | |
152 | CONTROL_KBD_TIME_UNTIL_REPEAT=0x28, | |
153 | CONTROL_KBD_TIME_BETWEEN_REPEATS=0x29, | |
154 | CONTROL_TIMEZONE=0x2A, | |
155 | CONTROL_MARK_SPACE_RATIO=0x2B, | |
156 | CONTROL_DIAGNOSTIC_MODE=0x2E, | |
157 | CONTROL_SCREEN_CONTRAST=0x2F, | |
158 | RING_BELL=0x30, | |
159 | SET_DIAGNOSTIC_STATUS=0x32, | |
160 | CLEAR_KEY_COMBINATION_TABLE=0x33, | |
161 | PERFORM_SOFTWARE_RESET=0x34, | |
162 | SET_REAL_TIME_CLOCK=0x35, | |
163 | RECALIBRATE_POINTING_STICK=0x36, | |
164 | SET_BELL_FREQUENCY=0x37, | |
165 | SET_INTERNAL_BATTERY_CHARGE_RATE=0x39, | |
166 | SET_EXTERNAL_BATTERY_CHARGE_RATE=0x3A, | |
167 | SET_REAL_TIME_CLOCK_ALARM=0x3B, | |
168 | READ_EEPROM=0x40, | |
169 | WRITE_EEPROM=0x41, | |
170 | WRITE_TO_STATUS_DISPLAY=0x42, | |
171 | DEFINE_SPECIAL_CHARACTER=0x43, | |
172 | DEFINE_KEY_COMBINATION_ENTRY=0x50, | |
173 | DEFINE_STRING_TABLE_ENTRY=0x51, | |
174 | DEFINE_STATUS_SCREEN_DISPLAY=0x52, | |
175 | PERFORM_EMU_COMMANDS=0x64, | |
176 | READ_EMU_REGISTER=0x65, | |
177 | WRITE_EMU_REGISTER=0x66, | |
178 | READ_EMU_RAM=0x67, | |
179 | WRITE_EMU_RAM=0x68, | |
180 | READ_BQ_REGISTER=0x69, | |
181 | WRITE_BQ_REGISTER=0x6A, | |
182 | SET_USER_PASSWORD=0x70, | |
183 | VERIFY_USER_PASSWORD=0x71, | |
184 | GET_SYSTEM_PASSWORD_KEY=0x72, | |
185 | VERIFY_SYSTEM_PASSWORD=0x73, | |
186 | POWER_OFF=0x82, | |
187 | POWER_RESTART=0x83, | |
188 | }; | |
189 | ||
237f8aaf DM |
190 | static struct uctrl_driver { |
191 | struct uctrl_regs __iomem *regs; | |
1da177e4 LT |
192 | int irq; |
193 | int pending; | |
194 | struct uctrl_status status; | |
237f8aaf | 195 | } *global_driver; |
1da177e4 | 196 | |
237f8aaf DM |
197 | static void uctrl_get_event_status(struct uctrl_driver *); |
198 | static void uctrl_get_external_status(struct uctrl_driver *); | |
1da177e4 LT |
199 | |
200 | static int | |
201 | uctrl_ioctl(struct inode *inode, struct file *file, | |
202 | unsigned int cmd, unsigned long arg) | |
203 | { | |
204 | switch (cmd) { | |
205 | default: | |
206 | return -EINVAL; | |
207 | } | |
208 | return 0; | |
209 | } | |
210 | ||
211 | static int | |
212 | uctrl_open(struct inode *inode, struct file *file) | |
213 | { | |
f138e481 | 214 | lock_kernel(); |
237f8aaf DM |
215 | uctrl_get_event_status(global_driver); |
216 | uctrl_get_external_status(global_driver); | |
f138e481 | 217 | unlock_kernel(); |
1da177e4 LT |
218 | return 0; |
219 | } | |
220 | ||
7d12e780 | 221 | static irqreturn_t uctrl_interrupt(int irq, void *dev_id) |
1da177e4 | 222 | { |
1da177e4 LT |
223 | return IRQ_HANDLED; |
224 | } | |
225 | ||
00977a59 | 226 | static const struct file_operations uctrl_fops = { |
1da177e4 LT |
227 | .owner = THIS_MODULE, |
228 | .llseek = no_llseek, | |
229 | .ioctl = uctrl_ioctl, | |
230 | .open = uctrl_open, | |
231 | }; | |
232 | ||
233 | static struct miscdevice uctrl_dev = { | |
234 | UCTRL_MINOR, | |
235 | "uctrl", | |
236 | &uctrl_fops | |
237 | }; | |
238 | ||
239 | /* Wait for space to write, then write to it */ | |
240 | #define WRITEUCTLDATA(value) \ | |
241 | { \ | |
242 | unsigned int i; \ | |
243 | for (i = 0; i < 10000; i++) { \ | |
237f8aaf | 244 | if (UCTRL_STAT_TXNF_STA & sbus_readl(&driver->regs->uctrl_stat)) \ |
1da177e4 LT |
245 | break; \ |
246 | } \ | |
247 | dprintk(("write data 0x%02x\n", value)); \ | |
237f8aaf | 248 | sbus_writel(value, &driver->regs->uctrl_data); \ |
1da177e4 LT |
249 | } |
250 | ||
251 | /* Wait for something to read, read it, then clear the bit */ | |
252 | #define READUCTLDATA(value) \ | |
253 | { \ | |
254 | unsigned int i; \ | |
255 | value = 0; \ | |
256 | for (i = 0; i < 10000; i++) { \ | |
237f8aaf | 257 | if ((UCTRL_STAT_RXNE_STA & sbus_readl(&driver->regs->uctrl_stat)) == 0) \ |
1da177e4 LT |
258 | break; \ |
259 | udelay(1); \ | |
260 | } \ | |
237f8aaf | 261 | value = sbus_readl(&driver->regs->uctrl_data); \ |
1da177e4 | 262 | dprintk(("read data 0x%02x\n", value)); \ |
237f8aaf | 263 | sbus_writel(UCTRL_STAT_RXNE_STA, &driver->regs->uctrl_stat); \ |
1da177e4 LT |
264 | } |
265 | ||
237f8aaf | 266 | static void uctrl_do_txn(struct uctrl_driver *driver, struct uctrl_txn *txn) |
1da177e4 | 267 | { |
1da177e4 LT |
268 | int stat, incnt, outcnt, bytecnt, intr; |
269 | u32 byte; | |
270 | ||
237f8aaf DM |
271 | stat = sbus_readl(&driver->regs->uctrl_stat); |
272 | intr = sbus_readl(&driver->regs->uctrl_intr); | |
273 | sbus_writel(stat, &driver->regs->uctrl_stat); | |
1da177e4 LT |
274 | |
275 | dprintk(("interrupt stat 0x%x int 0x%x\n", stat, intr)); | |
276 | ||
277 | incnt = txn->inbits; | |
278 | outcnt = txn->outbits; | |
279 | byte = (txn->opcode << 8); | |
280 | WRITEUCTLDATA(byte); | |
281 | ||
282 | bytecnt = 0; | |
283 | while (incnt > 0) { | |
284 | byte = (txn->inbuf[bytecnt] << 8); | |
285 | WRITEUCTLDATA(byte); | |
286 | incnt--; | |
287 | bytecnt++; | |
288 | } | |
289 | ||
290 | /* Get the ack */ | |
291 | READUCTLDATA(byte); | |
292 | dprintk(("ack was %x\n", (byte >> 8))); | |
293 | ||
294 | bytecnt = 0; | |
295 | while (outcnt > 0) { | |
296 | READUCTLDATA(byte); | |
297 | txn->outbuf[bytecnt] = (byte >> 8); | |
298 | dprintk(("set byte to %02x\n", byte)); | |
299 | outcnt--; | |
300 | bytecnt++; | |
301 | } | |
302 | } | |
303 | ||
237f8aaf | 304 | static void uctrl_get_event_status(struct uctrl_driver *driver) |
1da177e4 | 305 | { |
1da177e4 LT |
306 | struct uctrl_txn txn; |
307 | u8 outbits[2]; | |
308 | ||
309 | txn.opcode = READ_EVENT_STATUS; | |
310 | txn.inbits = 0; | |
311 | txn.outbits = 2; | |
fec607ff | 312 | txn.inbuf = NULL; |
1da177e4 LT |
313 | txn.outbuf = outbits; |
314 | ||
237f8aaf | 315 | uctrl_do_txn(driver, &txn); |
1da177e4 LT |
316 | |
317 | dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff))); | |
318 | driver->status.event_status = | |
319 | ((outbits[0] & 0xff) << 8) | (outbits[1] & 0xff); | |
320 | dprintk(("ev is %x\n", driver->status.event_status)); | |
321 | } | |
322 | ||
237f8aaf | 323 | static void uctrl_get_external_status(struct uctrl_driver *driver) |
1da177e4 | 324 | { |
1da177e4 LT |
325 | struct uctrl_txn txn; |
326 | u8 outbits[2]; | |
327 | int i, v; | |
328 | ||
329 | txn.opcode = READ_EXTERNAL_STATUS; | |
330 | txn.inbits = 0; | |
331 | txn.outbits = 2; | |
fec607ff | 332 | txn.inbuf = NULL; |
1da177e4 LT |
333 | txn.outbuf = outbits; |
334 | ||
237f8aaf | 335 | uctrl_do_txn(driver, &txn); |
1da177e4 LT |
336 | |
337 | dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff))); | |
338 | driver->status.external_status = | |
339 | ((outbits[0] * 256) + (outbits[1])); | |
340 | dprintk(("ex is %x\n", driver->status.external_status)); | |
341 | v = driver->status.external_status; | |
342 | for (i = 0; v != 0; i++, v >>= 1) { | |
343 | if (v & 1) { | |
344 | dprintk(("%s%s", " ", uctrl_extstatus[i])); | |
345 | } | |
346 | } | |
347 | dprintk(("\n")); | |
348 | ||
349 | } | |
350 | ||
237f8aaf DM |
351 | static int __devinit uctrl_probe(struct of_device *op, |
352 | const struct of_device_id *match) | |
1da177e4 | 353 | { |
237f8aaf DM |
354 | struct uctrl_driver *p; |
355 | int err = -ENOMEM; | |
1da177e4 | 356 | |
237f8aaf DM |
357 | p = kzalloc(sizeof(*p), GFP_KERNEL); |
358 | if (!p) { | |
359 | printk(KERN_ERR "uctrl: Unable to allocate device struct.\n"); | |
360 | goto out; | |
361 | } | |
1da177e4 | 362 | |
237f8aaf DM |
363 | p->regs = of_ioremap(&op->resource[0], 0, |
364 | resource_size(&op->resource[0]), | |
365 | "uctrl"); | |
366 | if (!p->regs) { | |
367 | printk(KERN_ERR "uctrl: Unable to map registers.\n"); | |
368 | goto out_free; | |
369 | } | |
1da177e4 | 370 | |
237f8aaf DM |
371 | p->irq = op->irqs[0]; |
372 | err = request_irq(p->irq, uctrl_interrupt, 0, "uctrl", p); | |
373 | if (err) { | |
374 | printk(KERN_ERR "uctrl: Unable to register irq.\n"); | |
375 | goto out_iounmap; | |
376 | } | |
1da177e4 | 377 | |
237f8aaf DM |
378 | err = misc_register(&uctrl_dev); |
379 | if (err) { | |
380 | printk(KERN_ERR "uctrl: Unable to register misc device.\n"); | |
381 | goto out_free_irq; | |
382 | } | |
1da177e4 | 383 | |
237f8aaf DM |
384 | sbus_writel(UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK, &p->regs->uctrl_intr); |
385 | printk(KERN_INFO "%s: uctrl regs[0x%p] (irq %d)\n", | |
386 | op->node->full_name, p->regs, p->irq); | |
387 | uctrl_get_event_status(p); | |
388 | uctrl_get_external_status(p); | |
1da177e4 | 389 | |
237f8aaf DM |
390 | dev_set_drvdata(&op->dev, p); |
391 | global_driver = p; | |
1da177e4 | 392 | |
237f8aaf DM |
393 | out: |
394 | return err; | |
1da177e4 | 395 | |
237f8aaf DM |
396 | out_free_irq: |
397 | free_irq(p->irq, p); | |
1da177e4 | 398 | |
237f8aaf DM |
399 | out_iounmap: |
400 | of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0])); | |
1da177e4 | 401 | |
237f8aaf DM |
402 | out_free: |
403 | kfree(p); | |
404 | goto out; | |
405 | } | |
1da177e4 | 406 | |
237f8aaf DM |
407 | static int __devexit uctrl_remove(struct of_device *op) |
408 | { | |
409 | struct uctrl_driver *p = dev_get_drvdata(&op->dev); | |
410 | ||
411 | if (p) { | |
412 | misc_deregister(&uctrl_dev); | |
413 | free_irq(p->irq, p); | |
414 | of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0])); | |
415 | kfree(p); | |
416 | } | |
417 | return 0; | |
1da177e4 LT |
418 | } |
419 | ||
fd098316 | 420 | static const struct of_device_id uctrl_match[] = { |
237f8aaf DM |
421 | { |
422 | .name = "uctrl", | |
423 | }, | |
424 | {}, | |
425 | }; | |
426 | MODULE_DEVICE_TABLE(of, uctrl_match); | |
427 | ||
428 | static struct of_platform_driver uctrl_driver = { | |
429 | .name = "uctrl", | |
430 | .match_table = uctrl_match, | |
431 | .probe = uctrl_probe, | |
432 | .remove = __devexit_p(uctrl_remove), | |
433 | }; | |
434 | ||
435 | ||
436 | static int __init uctrl_init(void) | |
1da177e4 | 437 | { |
237f8aaf DM |
438 | return of_register_driver(&uctrl_driver, &of_bus_type); |
439 | } | |
1da177e4 | 440 | |
237f8aaf DM |
441 | static void __exit uctrl_exit(void) |
442 | { | |
443 | of_unregister_driver(&uctrl_driver); | |
1da177e4 LT |
444 | } |
445 | ||
237f8aaf DM |
446 | module_init(uctrl_init); |
447 | module_exit(uctrl_exit); | |
1da177e4 | 448 | MODULE_LICENSE("GPL"); |