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> | |
a3108ca2 | 12 | #include <linux/mutex.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> | |
1da177e4 LT |
22 | #include <asm/irq.h> |
23 | #include <asm/io.h> | |
24 | #include <asm/pgtable.h> | |
1da177e4 LT |
25 | |
26 | #define UCTRL_MINOR 174 | |
27 | ||
28 | #define DEBUG 1 | |
29 | #ifdef DEBUG | |
30 | #define dprintk(x) printk x | |
31 | #else | |
32 | #define dprintk(x) | |
33 | #endif | |
34 | ||
35 | struct uctrl_regs { | |
237f8aaf DM |
36 | u32 uctrl_intr; |
37 | u32 uctrl_data; | |
38 | u32 uctrl_stat; | |
39 | u32 uctrl_xxx[5]; | |
1da177e4 LT |
40 | }; |
41 | ||
42 | struct ts102_regs { | |
237f8aaf DM |
43 | u32 card_a_intr; |
44 | u32 card_a_stat; | |
45 | u32 card_a_ctrl; | |
46 | u32 card_a_xxx; | |
47 | u32 card_b_intr; | |
48 | u32 card_b_stat; | |
49 | u32 card_b_ctrl; | |
50 | u32 card_b_xxx; | |
51 | u32 uctrl_intr; | |
52 | u32 uctrl_data; | |
53 | u32 uctrl_stat; | |
54 | u32 uctrl_xxx; | |
55 | u32 ts102_xxx[4]; | |
1da177e4 LT |
56 | }; |
57 | ||
58 | /* Bits for uctrl_intr register */ | |
59 | #define UCTRL_INTR_TXE_REQ 0x01 /* transmit FIFO empty int req */ | |
60 | #define UCTRL_INTR_TXNF_REQ 0x02 /* transmit FIFO not full int req */ | |
61 | #define UCTRL_INTR_RXNE_REQ 0x04 /* receive FIFO not empty int req */ | |
62 | #define UCTRL_INTR_RXO_REQ 0x08 /* receive FIFO overflow int req */ | |
63 | #define UCTRL_INTR_TXE_MSK 0x10 /* transmit FIFO empty mask */ | |
64 | #define UCTRL_INTR_TXNF_MSK 0x20 /* transmit FIFO not full mask */ | |
65 | #define UCTRL_INTR_RXNE_MSK 0x40 /* receive FIFO not empty mask */ | |
66 | #define UCTRL_INTR_RXO_MSK 0x80 /* receive FIFO overflow mask */ | |
67 | ||
68 | /* Bits for uctrl_stat register */ | |
69 | #define UCTRL_STAT_TXE_STA 0x01 /* transmit FIFO empty status */ | |
70 | #define UCTRL_STAT_TXNF_STA 0x02 /* transmit FIFO not full status */ | |
71 | #define UCTRL_STAT_RXNE_STA 0x04 /* receive FIFO not empty status */ | |
72 | #define UCTRL_STAT_RXO_STA 0x08 /* receive FIFO overflow status */ | |
73 | ||
a3108ca2 | 74 | static DEFINE_MUTEX(uctrl_mutex); |
1da177e4 LT |
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 | 199 | |
6c0f8bc7 SG |
200 | static long |
201 | uctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |
1da177e4 LT |
202 | { |
203 | switch (cmd) { | |
204 | default: | |
205 | return -EINVAL; | |
206 | } | |
207 | return 0; | |
208 | } | |
209 | ||
210 | static int | |
211 | uctrl_open(struct inode *inode, struct file *file) | |
212 | { | |
a3108ca2 | 213 | mutex_lock(&uctrl_mutex); |
237f8aaf DM |
214 | uctrl_get_event_status(global_driver); |
215 | uctrl_get_external_status(global_driver); | |
a3108ca2 | 216 | mutex_unlock(&uctrl_mutex); |
1da177e4 LT |
217 | return 0; |
218 | } | |
219 | ||
7d12e780 | 220 | static irqreturn_t uctrl_interrupt(int irq, void *dev_id) |
1da177e4 | 221 | { |
1da177e4 LT |
222 | return IRQ_HANDLED; |
223 | } | |
224 | ||
00977a59 | 225 | static const struct file_operations uctrl_fops = { |
1da177e4 LT |
226 | .owner = THIS_MODULE, |
227 | .llseek = no_llseek, | |
6c0f8bc7 | 228 | .unlocked_ioctl = uctrl_ioctl, |
1da177e4 LT |
229 | .open = uctrl_open, |
230 | }; | |
231 | ||
232 | static struct miscdevice uctrl_dev = { | |
233 | UCTRL_MINOR, | |
234 | "uctrl", | |
235 | &uctrl_fops | |
236 | }; | |
237 | ||
238 | /* Wait for space to write, then write to it */ | |
239 | #define WRITEUCTLDATA(value) \ | |
240 | { \ | |
241 | unsigned int i; \ | |
242 | for (i = 0; i < 10000; i++) { \ | |
237f8aaf | 243 | if (UCTRL_STAT_TXNF_STA & sbus_readl(&driver->regs->uctrl_stat)) \ |
1da177e4 LT |
244 | break; \ |
245 | } \ | |
246 | dprintk(("write data 0x%02x\n", value)); \ | |
237f8aaf | 247 | sbus_writel(value, &driver->regs->uctrl_data); \ |
1da177e4 LT |
248 | } |
249 | ||
250 | /* Wait for something to read, read it, then clear the bit */ | |
251 | #define READUCTLDATA(value) \ | |
252 | { \ | |
253 | unsigned int i; \ | |
254 | value = 0; \ | |
255 | for (i = 0; i < 10000; i++) { \ | |
237f8aaf | 256 | if ((UCTRL_STAT_RXNE_STA & sbus_readl(&driver->regs->uctrl_stat)) == 0) \ |
1da177e4 LT |
257 | break; \ |
258 | udelay(1); \ | |
259 | } \ | |
237f8aaf | 260 | value = sbus_readl(&driver->regs->uctrl_data); \ |
1da177e4 | 261 | dprintk(("read data 0x%02x\n", value)); \ |
237f8aaf | 262 | sbus_writel(UCTRL_STAT_RXNE_STA, &driver->regs->uctrl_stat); \ |
1da177e4 LT |
263 | } |
264 | ||
237f8aaf | 265 | static void uctrl_do_txn(struct uctrl_driver *driver, struct uctrl_txn *txn) |
1da177e4 | 266 | { |
1da177e4 LT |
267 | int stat, incnt, outcnt, bytecnt, intr; |
268 | u32 byte; | |
269 | ||
237f8aaf DM |
270 | stat = sbus_readl(&driver->regs->uctrl_stat); |
271 | intr = sbus_readl(&driver->regs->uctrl_intr); | |
272 | sbus_writel(stat, &driver->regs->uctrl_stat); | |
1da177e4 LT |
273 | |
274 | dprintk(("interrupt stat 0x%x int 0x%x\n", stat, intr)); | |
275 | ||
276 | incnt = txn->inbits; | |
277 | outcnt = txn->outbits; | |
278 | byte = (txn->opcode << 8); | |
279 | WRITEUCTLDATA(byte); | |
280 | ||
281 | bytecnt = 0; | |
282 | while (incnt > 0) { | |
283 | byte = (txn->inbuf[bytecnt] << 8); | |
284 | WRITEUCTLDATA(byte); | |
285 | incnt--; | |
286 | bytecnt++; | |
287 | } | |
288 | ||
289 | /* Get the ack */ | |
290 | READUCTLDATA(byte); | |
291 | dprintk(("ack was %x\n", (byte >> 8))); | |
292 | ||
293 | bytecnt = 0; | |
294 | while (outcnt > 0) { | |
295 | READUCTLDATA(byte); | |
296 | txn->outbuf[bytecnt] = (byte >> 8); | |
297 | dprintk(("set byte to %02x\n", byte)); | |
298 | outcnt--; | |
299 | bytecnt++; | |
300 | } | |
301 | } | |
302 | ||
237f8aaf | 303 | static void uctrl_get_event_status(struct uctrl_driver *driver) |
1da177e4 | 304 | { |
1da177e4 LT |
305 | struct uctrl_txn txn; |
306 | u8 outbits[2]; | |
307 | ||
308 | txn.opcode = READ_EVENT_STATUS; | |
309 | txn.inbits = 0; | |
310 | txn.outbits = 2; | |
fec607ff | 311 | txn.inbuf = NULL; |
1da177e4 LT |
312 | txn.outbuf = outbits; |
313 | ||
237f8aaf | 314 | uctrl_do_txn(driver, &txn); |
1da177e4 LT |
315 | |
316 | dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff))); | |
317 | driver->status.event_status = | |
318 | ((outbits[0] & 0xff) << 8) | (outbits[1] & 0xff); | |
319 | dprintk(("ev is %x\n", driver->status.event_status)); | |
320 | } | |
321 | ||
237f8aaf | 322 | static void uctrl_get_external_status(struct uctrl_driver *driver) |
1da177e4 | 323 | { |
1da177e4 LT |
324 | struct uctrl_txn txn; |
325 | u8 outbits[2]; | |
326 | int i, v; | |
327 | ||
328 | txn.opcode = READ_EXTERNAL_STATUS; | |
329 | txn.inbits = 0; | |
330 | txn.outbits = 2; | |
fec607ff | 331 | txn.inbuf = NULL; |
1da177e4 LT |
332 | txn.outbuf = outbits; |
333 | ||
237f8aaf | 334 | uctrl_do_txn(driver, &txn); |
1da177e4 LT |
335 | |
336 | dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff))); | |
337 | driver->status.external_status = | |
338 | ((outbits[0] * 256) + (outbits[1])); | |
339 | dprintk(("ex is %x\n", driver->status.external_status)); | |
340 | v = driver->status.external_status; | |
341 | for (i = 0; v != 0; i++, v >>= 1) { | |
342 | if (v & 1) { | |
343 | dprintk(("%s%s", " ", uctrl_extstatus[i])); | |
344 | } | |
345 | } | |
346 | dprintk(("\n")); | |
347 | ||
348 | } | |
349 | ||
082a2004 | 350 | static int uctrl_probe(struct platform_device *op) |
1da177e4 | 351 | { |
237f8aaf DM |
352 | struct uctrl_driver *p; |
353 | int err = -ENOMEM; | |
1da177e4 | 354 | |
237f8aaf DM |
355 | p = kzalloc(sizeof(*p), GFP_KERNEL); |
356 | if (!p) { | |
357 | printk(KERN_ERR "uctrl: Unable to allocate device struct.\n"); | |
358 | goto out; | |
359 | } | |
1da177e4 | 360 | |
237f8aaf DM |
361 | p->regs = of_ioremap(&op->resource[0], 0, |
362 | resource_size(&op->resource[0]), | |
363 | "uctrl"); | |
364 | if (!p->regs) { | |
365 | printk(KERN_ERR "uctrl: Unable to map registers.\n"); | |
366 | goto out_free; | |
367 | } | |
1da177e4 | 368 | |
1636f8ac | 369 | p->irq = op->archdata.irqs[0]; |
237f8aaf DM |
370 | err = request_irq(p->irq, uctrl_interrupt, 0, "uctrl", p); |
371 | if (err) { | |
372 | printk(KERN_ERR "uctrl: Unable to register irq.\n"); | |
373 | goto out_iounmap; | |
374 | } | |
1da177e4 | 375 | |
237f8aaf DM |
376 | err = misc_register(&uctrl_dev); |
377 | if (err) { | |
378 | printk(KERN_ERR "uctrl: Unable to register misc device.\n"); | |
379 | goto out_free_irq; | |
380 | } | |
1da177e4 | 381 | |
237f8aaf DM |
382 | sbus_writel(UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK, &p->regs->uctrl_intr); |
383 | printk(KERN_INFO "%s: uctrl regs[0x%p] (irq %d)\n", | |
61c7a080 | 384 | op->dev.of_node->full_name, p->regs, p->irq); |
237f8aaf DM |
385 | uctrl_get_event_status(p); |
386 | uctrl_get_external_status(p); | |
1da177e4 | 387 | |
237f8aaf DM |
388 | dev_set_drvdata(&op->dev, p); |
389 | global_driver = p; | |
1da177e4 | 390 | |
237f8aaf DM |
391 | out: |
392 | return err; | |
1da177e4 | 393 | |
237f8aaf DM |
394 | out_free_irq: |
395 | free_irq(p->irq, p); | |
1da177e4 | 396 | |
237f8aaf DM |
397 | out_iounmap: |
398 | of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0])); | |
1da177e4 | 399 | |
237f8aaf DM |
400 | out_free: |
401 | kfree(p); | |
402 | goto out; | |
403 | } | |
1da177e4 | 404 | |
082a2004 | 405 | static int uctrl_remove(struct platform_device *op) |
237f8aaf DM |
406 | { |
407 | struct uctrl_driver *p = dev_get_drvdata(&op->dev); | |
408 | ||
409 | if (p) { | |
410 | misc_deregister(&uctrl_dev); | |
411 | free_irq(p->irq, p); | |
412 | of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0])); | |
413 | kfree(p); | |
414 | } | |
415 | return 0; | |
1da177e4 LT |
416 | } |
417 | ||
fd098316 | 418 | static const struct of_device_id uctrl_match[] = { |
237f8aaf DM |
419 | { |
420 | .name = "uctrl", | |
421 | }, | |
422 | {}, | |
423 | }; | |
424 | MODULE_DEVICE_TABLE(of, uctrl_match); | |
425 | ||
4ebb24f7 | 426 | static struct platform_driver uctrl_driver = { |
4018294b GL |
427 | .driver = { |
428 | .name = "uctrl", | |
429 | .owner = THIS_MODULE, | |
430 | .of_match_table = uctrl_match, | |
431 | }, | |
237f8aaf | 432 | .probe = uctrl_probe, |
082a2004 | 433 | .remove = uctrl_remove, |
237f8aaf DM |
434 | }; |
435 | ||
436 | ||
dbf2b92d | 437 | module_platform_driver(uctrl_driver); |
1da177e4 | 438 | |
1da177e4 | 439 | MODULE_LICENSE("GPL"); |