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