aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndres Salomon <dilinger@queued.net>2010-09-24 22:13:42 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2010-09-29 21:01:40 -0400
commiteecb3e4e5d9d83ebe1bef222a707eb7618498b67 (patch)
tree64e1c95f9e7530195f3b7a80fe120932c0fe30b4
parent25417922694e60f04cd4dc8448ada9236f18c532 (diff)
staging: olpc_dcon: add OLPC display controller (DCON) support
This adds DCON support for the OLPC XO. The DCON is found in XO-1 and XO-1.5 hardware. The XO-1 has a CS5536 southbridge, while the XO-1.5 has a Via chipset; the GPIO magic that's necessary to communicate with the DCON chip is unfortunately different across both platforms. This driver supports both. This driver is in bad state atm, so I'm requesting its inclusion into staging so it can be cleaned up while staying in the kernel tree. Original driver by Dave Woodhouse, and modified extensively by Jordan Crouse, myself, Deepak Saxena, Paul Fox, Daniel Drake, and probably others that I've missed. Signed-off-by: Andres Salomon <dilinger@queued.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/staging/Kconfig2
-rw-r--r--drivers/staging/Makefile1
-rw-r--r--drivers/staging/olpc_dcon/Kconfig8
-rw-r--r--drivers/staging/olpc_dcon/Makefile1
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon.c900
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon.h75
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon_xo_1.c171
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c219
8 files changed, 1377 insertions, 0 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 35a3ed1e1a1..886af1626e6 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -69,6 +69,8 @@ source "drivers/staging/rt2870/Kconfig"
69 69
70source "drivers/staging/comedi/Kconfig" 70source "drivers/staging/comedi/Kconfig"
71 71
72source "drivers/staging/olpc_dcon/Kconfig"
73
72source "drivers/staging/asus_oled/Kconfig" 74source "drivers/staging/asus_oled/Kconfig"
73 75
74source "drivers/staging/panel/Kconfig" 76source "drivers/staging/panel/Kconfig"
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 6221ffd3407..e6f88b9af29 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_BRCM80211) += brcm80211/
18obj-$(CONFIG_RT2860) += rt2860/ 18obj-$(CONFIG_RT2860) += rt2860/
19obj-$(CONFIG_RT2870) += rt2870/ 19obj-$(CONFIG_RT2870) += rt2870/
20obj-$(CONFIG_COMEDI) += comedi/ 20obj-$(CONFIG_COMEDI) += comedi/
21obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon/
21obj-$(CONFIG_ASUS_OLED) += asus_oled/ 22obj-$(CONFIG_ASUS_OLED) += asus_oled/
22obj-$(CONFIG_PANEL) += panel/ 23obj-$(CONFIG_PANEL) += panel/
23obj-$(CONFIG_R8187SE) += rtl8187se/ 24obj-$(CONFIG_R8187SE) += rtl8187se/
diff --git a/drivers/staging/olpc_dcon/Kconfig b/drivers/staging/olpc_dcon/Kconfig
new file mode 100644
index 00000000000..cfdec44591b
--- /dev/null
+++ b/drivers/staging/olpc_dcon/Kconfig
@@ -0,0 +1,8 @@
1config FB_OLPC_DCON
2 tristate "One Laptop Per Child Display CONtroller support"
3 depends on OLPC
4 select I2C
5 ---help---
6 Add support for the OLPC XO DCON controller. This controller is
7 only available on OLPC platforms. Unless you have one of these
8 platforms, you will want to say 'N'.
diff --git a/drivers/staging/olpc_dcon/Makefile b/drivers/staging/olpc_dcon/Makefile
new file mode 100644
index 00000000000..cd8f2898947
--- /dev/null
+++ b/drivers/staging/olpc_dcon/Makefile
@@ -0,0 +1 @@
obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon.o
diff --git a/drivers/staging/olpc_dcon/olpc_dcon.c b/drivers/staging/olpc_dcon/olpc_dcon.c
new file mode 100644
index 00000000000..688cf0f6952
--- /dev/null
+++ b/drivers/staging/olpc_dcon/olpc_dcon.c
@@ -0,0 +1,900 @@
1/*
2 * Mainly by David Woodhouse, somewhat modified by Jordan Crouse
3 *
4 * Copyright © 2006-2007 Red Hat, Inc.
5 * Copyright © 2006-2007 Advanced Micro Devices, Inc.
6 * Copyright © 2009 VIA Technology, Inc.
7 * Copyright (c) 2010 Andres Salomon <dilinger@queued.net>
8 *
9 * This program is free software. You can redistribute it and/or
10 * modify it under the terms of version 2 of the GNU General Public
11 * License as published by the Free Software Foundation.
12 */
13
14
15#include <linux/kernel.h>
16#include <linux/fb.h>
17#include <linux/console.h>
18#include <linux/i2c.h>
19#include <linux/platform_device.h>
20#include <linux/i2c-id.h>
21#include <linux/pci.h>
22#include <linux/vt_kern.h>
23#include <linux/pci_ids.h>
24#include <linux/interrupt.h>
25#include <linux/delay.h>
26#include <linux/backlight.h>
27#include <linux/device.h>
28#include <linux/notifier.h>
29#include <asm/uaccess.h>
30#include <linux/ctype.h>
31#include <linux/reboot.h>
32#include <linux/gpio.h>
33#include <asm/tsc.h>
34#include <asm/olpc.h>
35
36#include "olpc_dcon.h"
37
38/* Module definitions */
39
40static int resumeline = 898;
41module_param(resumeline, int, 0444);
42
43static int noinit;
44module_param(noinit, int, 0444);
45
46/* Default off since it doesn't work on DCON ASIC in B-test OLPC board */
47static int useaa = 1;
48module_param(useaa, int, 0444);
49
50struct dcon_platform_data {
51 int (*init)(void);
52 void (*bus_stabilize_wiggle)(void);
53 void (*set_dconload)(int);
54 int (*read_status)(void);
55};
56
57static struct dcon_platform_data *pdata;
58
59/* I2C structures */
60
61static struct i2c_driver dcon_driver;
62static struct i2c_client *dcon_client;
63
64/* Platform devices */
65static struct platform_device *dcon_device;
66
67/* Backlight device */
68static struct backlight_device *dcon_bl_dev;
69
70static struct fb_info *fbinfo;
71
72/* set this to 1 while controlling fb blank state from this driver */
73static int ignore_fb_events = 0;
74
75/* Current source, initialized at probe time */
76static int dcon_source;
77
78/* Desired source */
79static int dcon_pending;
80
81/* Current output type */
82static int dcon_output = DCON_OUTPUT_COLOR;
83
84/* Current sleep status (not yet implemented) */
85static int dcon_sleep_val = DCON_ACTIVE;
86
87/* Shadow register for the DCON_REG_MODE register */
88static unsigned short dcon_disp_mode;
89
90/* Variables used during switches */
91static int dcon_switched;
92static struct timespec dcon_irq_time;
93static struct timespec dcon_load_time;
94
95static DECLARE_WAIT_QUEUE_HEAD(dcon_wait_queue);
96
97static unsigned short normal_i2c[] = { 0x0d, I2C_CLIENT_END };
98
99#define dcon_write(reg,val) i2c_smbus_write_word_data(dcon_client,reg,val)
100#define dcon_read(reg) i2c_smbus_read_word_data(dcon_client,reg)
101
102/* The current backlight value - this saves us some smbus traffic */
103static int bl_val = -1;
104
105/* ===== API functions - these are called by a variety of users ==== */
106
107static int dcon_hw_init(struct i2c_client *client, int is_init)
108{
109 uint16_t ver;
110 int rc = 0;
111
112 ver = i2c_smbus_read_word_data(client, DCON_REG_ID);
113 if ((ver >> 8) != 0xDC) {
114 printk(KERN_ERR "olpc-dcon: DCON ID not 0xDCxx: 0x%04x "
115 "instead.\n", ver);
116 rc = -ENXIO;
117 goto err;
118 }
119
120 if (is_init) {
121 printk(KERN_INFO "olpc-dcon: Discovered DCON version %x\n",
122 ver & 0xFF);
123 if ((rc = pdata->init()) != 0) {
124 printk(KERN_ERR "olpc-dcon: Unable to init.\n");
125 goto err;
126 }
127 }
128
129 if (ver < 0xdc02 && !noinit) {
130 /* Initialize the DCON registers */
131
132 /* Start with work-arounds for DCON ASIC */
133 i2c_smbus_write_word_data(client, 0x4b, 0x00cc);
134 i2c_smbus_write_word_data(client, 0x4b, 0x00cc);
135 i2c_smbus_write_word_data(client, 0x4b, 0x00cc);
136 i2c_smbus_write_word_data(client, 0x0b, 0x007a);
137 i2c_smbus_write_word_data(client, 0x36, 0x025c);
138 i2c_smbus_write_word_data(client, 0x37, 0x025e);
139
140 /* Initialise SDRAM */
141
142 i2c_smbus_write_word_data(client, 0x3b, 0x002b);
143 i2c_smbus_write_word_data(client, 0x41, 0x0101);
144 i2c_smbus_write_word_data(client, 0x42, 0x0101);
145 }
146 else if (!noinit) {
147 /* SDRAM setup/hold time */
148 i2c_smbus_write_word_data(client, 0x3a, 0xc040);
149 i2c_smbus_write_word_data(client, 0x41, 0x0000);
150 i2c_smbus_write_word_data(client, 0x41, 0x0101);
151 i2c_smbus_write_word_data(client, 0x42, 0x0101);
152 }
153
154 /* Colour swizzle, AA, no passthrough, backlight */
155 if (is_init) {
156 dcon_disp_mode = MODE_PASSTHRU | MODE_BL_ENABLE | MODE_CSWIZZLE;
157 if (useaa)
158 dcon_disp_mode |= MODE_COL_AA;
159 }
160 i2c_smbus_write_word_data(client, DCON_REG_MODE, dcon_disp_mode);
161
162
163 /* Set the scanline to interrupt on during resume */
164 i2c_smbus_write_word_data(client, DCON_REG_SCAN_INT, resumeline);
165
166err:
167 return rc;
168}
169
170/*
171 * The smbus doesn't always come back due to what is believed to be
172 * hardware (power rail) bugs. For older models where this is known to
173 * occur, our solution is to attempt to wait for the bus to stabilize;
174 * if it doesn't happen, cut power to the dcon, repower it, and wait
175 * for the bus to stabilize. Rinse, repeat until we have a working
176 * smbus. For newer models, we simply BUG(); we want to know if this
177 * still happens despite the power fixes that have been made!
178 */
179static int dcon_bus_stabilize(struct i2c_client *client, int is_powered_down)
180{
181 unsigned long timeout;
182 int x;
183
184power_up:
185 if (is_powered_down) {
186 x = 1;
187 if ((x = olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0))) {
188 printk(KERN_WARNING "olpc-dcon: unable to force dcon "
189 "to power up: %d!\n", x);
190 return x;
191 }
192 msleep(10); /* we'll be conservative */
193 }
194
195 pdata->bus_stabilize_wiggle();
196
197 for (x = -1, timeout = 50; timeout && x < 0; timeout--) {
198 msleep(1);
199 x = dcon_read(DCON_REG_ID);
200 }
201 if (x < 0) {
202 printk(KERN_ERR "olpc-dcon: unable to stabilize dcon's "
203 "smbus, reasserting power and praying.\n");
204 BUG_ON(olpc_board_at_least(olpc_board(BOARD_XO_1_C2)));
205 x = 0;
206 olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0);
207 msleep(100);
208 is_powered_down = 1;
209 goto power_up; /* argh, stupid hardware.. */
210 }
211
212 if (is_powered_down)
213 return dcon_hw_init(client, 0);
214 return 0;
215}
216
217
218/* Backlight notes - turning off the backlight enable bit in the DCON
219 * doesn't save us any power over just pushing the BL to zero, so we
220 * don't use that bit in this code.
221 */
222
223static int dcon_get_backlight(void)
224{
225 if (dcon_client == NULL)
226 return 0;
227
228 if (bl_val == -1)
229 bl_val = dcon_read(DCON_REG_BRIGHT) & 0x0F;
230
231 return bl_val;
232}
233
234
235static void dcon_set_backlight_hw(int level)
236{
237 bl_val = level & 0x0F;
238 dcon_write(DCON_REG_BRIGHT, bl_val);
239
240 /* Purposely turn off the backlight when we go to level 0 */
241 if (bl_val == 0) {
242 dcon_disp_mode &= ~MODE_BL_ENABLE;
243 dcon_write(DCON_REG_MODE, dcon_disp_mode);
244 } else if (!(dcon_disp_mode & MODE_BL_ENABLE)) {
245 dcon_disp_mode |= MODE_BL_ENABLE;
246 dcon_write(DCON_REG_MODE, dcon_disp_mode);
247 }
248}
249
250static void dcon_set_backlight(int level)
251{
252 if (dcon_client == NULL)
253 return;
254
255 if (bl_val == (level & 0x0F))
256 return;
257
258 dcon_set_backlight_hw(level);
259}
260
261/* Set the output type to either color or mono */
262
263static int dcon_set_output(int arg)
264{
265 if (dcon_output == arg)
266 return 0;
267
268 dcon_output = arg;
269
270 if (arg == DCON_OUTPUT_MONO) {
271 dcon_disp_mode &= ~(MODE_CSWIZZLE | MODE_COL_AA);
272 dcon_disp_mode |= MODE_MONO_LUMA;
273 }
274 else {
275 dcon_disp_mode &= ~(MODE_MONO_LUMA);
276 dcon_disp_mode |= MODE_CSWIZZLE;
277 if (useaa)
278 dcon_disp_mode |= MODE_COL_AA;
279 }
280
281 dcon_write(DCON_REG_MODE, dcon_disp_mode);
282 return 0;
283}
284
285/* For now, this will be really stupid - we need to address how
286 * DCONLOAD works in a sleep and account for it accordingly
287 */
288
289static void dcon_sleep(int state)
290{
291 int x;
292
293 /* Turn off the backlight and put the DCON to sleep */
294
295 if (state == dcon_sleep_val)
296 return;
297
298 if (!olpc_board_at_least(olpc_board(BOARD_XO_1_C2)))
299 return;
300
301 if (state == DCON_SLEEP) {
302 x = 0;
303 if ((x = olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0)))
304 printk(KERN_WARNING "olpc-dcon: unable to force dcon "
305 "to power down: %d!\n", x);
306 else
307 dcon_sleep_val = state;
308 }
309 else {
310 /* Only re-enable the backlight if the backlight value is set */
311 if (bl_val != 0)
312 dcon_disp_mode |= MODE_BL_ENABLE;
313
314 if ((x=dcon_bus_stabilize(dcon_client, 1)))
315 printk(KERN_WARNING "olpc-dcon: unable to reinit dcon"
316 " hardware: %d!\n", x);
317 else
318 dcon_sleep_val = state;
319
320 /* Restore backlight */
321 dcon_set_backlight_hw(bl_val);
322 }
323
324 /* We should turn off some stuff in the framebuffer - but what? */
325}
326
327/* the DCON seems to get confused if we change DCONLOAD too
328 * frequently -- i.e., approximately faster than frame time.
329 * normally we don't change it this fast, so in general we won't
330 * delay here.
331 */
332void dcon_load_holdoff(void)
333{
334 struct timespec delta_t, now;
335 while(1) {
336 getnstimeofday(&now);
337 delta_t = timespec_sub(now, dcon_load_time);
338 if (delta_t.tv_sec != 0 ||
339 delta_t.tv_nsec > NSEC_PER_MSEC * 20) {
340 break;
341 }
342 mdelay(4);
343 }
344}
345/* Set the source of the display (CPU or DCON) */
346
347static void dcon_source_switch(struct work_struct *work)
348{
349 DECLARE_WAITQUEUE(wait, current);
350 int source = dcon_pending;
351
352 if (dcon_source == source)
353 return;
354
355 dcon_load_holdoff();
356
357 dcon_switched = 0;
358
359 switch (source) {
360 case DCON_SOURCE_CPU:
361 printk("dcon_source_switch to CPU\n");
362 /* Enable the scanline interrupt bit */
363 if (dcon_write(DCON_REG_MODE, dcon_disp_mode | MODE_SCAN_INT))
364 printk(KERN_ERR "olpc-dcon: couldn't enable scanline interrupt!\n");
365 else {
366 /* Wait up to one second for the scanline interrupt */
367 wait_event_timeout(dcon_wait_queue, dcon_switched == 1, HZ);
368 }
369
370 if (!dcon_switched)
371 printk(KERN_ERR "olpc-dcon: Timeout entering CPU mode; expect a screen glitch.\n");
372
373 /* Turn off the scanline interrupt */
374 if (dcon_write(DCON_REG_MODE, dcon_disp_mode))
375 printk(KERN_ERR "olpc-dcon: couldn't disable scanline interrupt!\n");
376
377 /*
378 * Ideally we'd like to disable interrupts here so that the
379 * fb unblanking and DCON turn on happen at a known time value;
380 * however, we can't do that right now with fb_blank
381 * messing with semaphores.
382 *
383 * For now, we just hope..
384 */
385 acquire_console_sem();
386 ignore_fb_events = 1;
387 if (fb_blank(fbinfo, FB_BLANK_UNBLANK)) {
388 ignore_fb_events = 0;
389 release_console_sem();
390 printk(KERN_ERR "olpc-dcon: Failed to enter CPU mode\n");
391 dcon_pending = DCON_SOURCE_DCON;
392 return;
393 }
394 ignore_fb_events = 0;
395 release_console_sem();
396
397 /* And turn off the DCON */
398 pdata->set_dconload(1);
399 getnstimeofday(&dcon_load_time);
400
401 printk(KERN_INFO "olpc-dcon: The CPU has control\n");
402 break;
403 case DCON_SOURCE_DCON:
404 {
405 int t;
406 struct timespec delta_t;
407
408 printk("dcon_source_switch to DCON\n");
409
410 add_wait_queue(&dcon_wait_queue, &wait);
411 set_current_state(TASK_UNINTERRUPTIBLE);
412
413 /* Clear DCONLOAD - this implies that the DCON is in control */
414 pdata->set_dconload(0);
415 getnstimeofday(&dcon_load_time);
416
417 t = schedule_timeout(HZ/2);
418 remove_wait_queue(&dcon_wait_queue, &wait);
419 set_current_state(TASK_RUNNING);
420
421 if (!dcon_switched) {
422 printk(KERN_ERR "olpc-dcon: Timeout entering DCON mode; expect a screen glitch.\n");
423 } else {
424 /* sometimes the DCON doesn't follow its own rules,
425 * and doesn't wait for two vsync pulses before
426 * ack'ing the frame load with an IRQ. the result
427 * is that the display shows the *previously*
428 * loaded frame. we can detect this by looking at
429 * the time between asserting DCONLOAD and the IRQ --
430 * if it's less than 20msec, then the DCON couldn't
431 * have seen two VSYNC pulses. in that case we
432 * deassert and reassert, and hope for the best.
433 * see http://dev.laptop.org/ticket/9664
434 */
435 delta_t = timespec_sub(dcon_irq_time, dcon_load_time);
436 if (dcon_switched && delta_t.tv_sec == 0 &&
437 delta_t.tv_nsec < NSEC_PER_MSEC * 20) {
438 printk(KERN_ERR "olpc-dcon: missed loading, retrying\n");
439 pdata->set_dconload(1);
440 mdelay(41);
441 pdata->set_dconload(0);
442 getnstimeofday(&dcon_load_time);
443 mdelay(41);
444 }
445 }
446
447 acquire_console_sem();
448 ignore_fb_events = 1;
449 if (fb_blank(fbinfo, FB_BLANK_POWERDOWN))
450 printk(KERN_ERR "olpc-dcon: couldn't blank fb!\n");
451 ignore_fb_events = 0;
452 release_console_sem();
453
454 printk(KERN_INFO "olpc-dcon: The DCON has control\n");
455 break;
456 }
457 default:
458 BUG();
459 }
460
461 dcon_source = source;
462}
463
464static DECLARE_WORK(dcon_work, dcon_source_switch);
465
466static void dcon_set_source(int arg)
467{
468 if (dcon_pending == arg)
469 return;
470
471 dcon_pending = arg;
472
473 if ((dcon_source != arg) && !work_pending(&dcon_work))
474 schedule_work(&dcon_work);
475}
476
477static void dcon_set_source_sync(int arg)
478{
479 dcon_set_source(arg);
480 flush_scheduled_work();
481}
482
483static int dconbl_set(struct backlight_device *dev) {
484
485 int level = dev->props.brightness;
486
487 if (dev->props.power != FB_BLANK_UNBLANK)
488 level = 0;
489
490 dcon_set_backlight(level);
491 return 0;
492}
493
494static int dconbl_get(struct backlight_device *dev) {
495 return dcon_get_backlight();
496}
497
498static ssize_t dcon_mode_show(struct device *dev,
499 struct device_attribute *attr, char *buf)
500{
501 return sprintf(buf, "%4.4X\n", dcon_disp_mode);
502}
503
504static ssize_t dcon_sleep_show(struct device *dev,
505 struct device_attribute *attr, char *buf)
506{
507
508 return sprintf(buf, "%d\n", dcon_sleep_val);
509}
510
511static ssize_t dcon_freeze_show(struct device *dev,
512 struct device_attribute *attr, char *buf)
513{
514 return sprintf(buf, "%d\n", dcon_source == DCON_SOURCE_DCON ? 1 : 0);
515}
516
517static ssize_t dcon_output_show(struct device *dev,
518 struct device_attribute *attr, char *buf)
519{
520 return sprintf(buf, "%d\n", dcon_output);
521}
522
523static ssize_t dcon_resumeline_show(struct device *dev,
524 struct device_attribute *attr, char *buf)
525{
526 return sprintf(buf, "%d\n", resumeline);
527}
528
529static int _strtoul(const char *buf, int len, unsigned int *val)
530{
531
532 char *endp;
533 unsigned int output = simple_strtoul(buf, &endp, 0);
534 int size = endp - buf;
535
536 if (*endp && isspace(*endp))
537 size++;
538
539 if (size != len)
540 return -EINVAL;
541
542 *val = output;
543 return 0;
544}
545
546static ssize_t dcon_output_store(struct device *dev,
547 struct device_attribute *attr, const char *buf, size_t count)
548{
549 int output;
550 int rc = -EINVAL;
551
552 if (_strtoul(buf, count, &output))
553 return -EINVAL;
554
555 if (output == DCON_OUTPUT_COLOR || output == DCON_OUTPUT_MONO) {
556 dcon_set_output(output);
557 rc = count;
558 }
559
560 return rc;
561}
562
563static ssize_t dcon_freeze_store(struct device *dev,
564 struct device_attribute *attr, const char *buf, size_t count)
565{
566 int output;
567
568 if (_strtoul(buf, count, &output))
569 return -EINVAL;
570
571 printk("dcon_freeze_store: %d\n", output);
572
573 switch (output) {
574 case 0:
575 dcon_set_source(DCON_SOURCE_CPU);
576 break;
577 case 1:
578 dcon_set_source_sync(DCON_SOURCE_DCON);
579 break;
580 case 2: // normally unused
581 dcon_set_source(DCON_SOURCE_DCON);
582 break;
583 default:
584 return -EINVAL;
585 }
586
587 return count;
588}
589
590static ssize_t dcon_resumeline_store(struct device *dev,
591 struct device_attribute *attr, const char *buf, size_t count)
592{
593 int rl;
594 int rc = -EINVAL;
595
596 if (_strtoul(buf, count, &rl))
597 return rc;
598
599 resumeline = rl;
600 dcon_write(DCON_REG_SCAN_INT, resumeline);
601 rc = count;
602
603 return rc;
604}
605
606static ssize_t dcon_sleep_store(struct device *dev,
607 struct device_attribute *attr, const char *buf, size_t count)
608{
609 int output;
610
611 if (_strtoul(buf, count, &output))
612 return -EINVAL;
613
614 dcon_sleep(output ? DCON_SLEEP : DCON_ACTIVE);
615 return count;
616}
617
618static struct device_attribute dcon_device_files[] = {
619 __ATTR(mode, 0444, dcon_mode_show, NULL),
620 __ATTR(sleep, 0644, dcon_sleep_show, dcon_sleep_store),
621 __ATTR(freeze, 0644, dcon_freeze_show, dcon_freeze_store),
622 __ATTR(output, 0644, dcon_output_show, dcon_output_store),
623 __ATTR(resumeline, 0644, dcon_resumeline_show, dcon_resumeline_store),
624};
625
626static struct backlight_ops dcon_bl_ops = {
627 .get_brightness = dconbl_get,
628 .update_status = dconbl_set
629};
630
631
632static int dcon_reboot_notify(struct notifier_block *nb, unsigned long foo, void *bar)
633{
634 if (dcon_client == NULL)
635 return 0;
636
637 /* Turn off the DCON. Entirely. */
638 dcon_write(DCON_REG_MODE, 0x39);
639 dcon_write(DCON_REG_MODE, 0x32);
640 return 0;
641}
642
643static int dcon_conswitch_notify(struct notifier_block *nb,
644 unsigned long mode, void *dummy)
645{
646 if (mode == CONSOLE_EVENT_SWITCH_TEXT)
647 dcon_sleep(DCON_ACTIVE);
648
649 return 0;
650}
651
652static struct notifier_block dcon_nb = {
653 .notifier_call = dcon_reboot_notify,
654 .priority = -1,
655};
656
657static struct notifier_block dcon_console_nb = {
658 .notifier_call = dcon_conswitch_notify,
659 .priority = -1,
660};
661
662static int unfreeze_on_panic(struct notifier_block *nb, unsigned long e, void *p)
663{
664 pdata->set_dconload(1);
665 return NOTIFY_DONE;
666}
667
668static struct notifier_block dcon_panic_nb = {
669 .notifier_call = unfreeze_on_panic,
670};
671
672/* when framebuffer sleeps due to external source (e.g. user idle), power down
673 * the DCON. also power up when the framebuffer comes back to life. */
674static int fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data)
675{
676 struct fb_event *evdata = data;
677 int *blank = (int *) evdata->data;
678 if (event != FB_EVENT_BLANK || ignore_fb_events)
679 return 0;
680 dcon_sleep((*blank) ? DCON_SLEEP : DCON_ACTIVE);
681 return 0;
682}
683
684static struct notifier_block fb_nb = {
685 .notifier_call = fb_notifier_callback,
686};
687
688static int dcon_detect(struct i2c_client *client, struct i2c_board_info *info)
689{
690 strlcpy(info->type, "olpc_dcon", I2C_NAME_SIZE);
691
692 return 0;
693}
694
695static int dcon_probe(struct i2c_client *client, const struct i2c_device_id *id)
696{
697 int rc, i;
698
699 if (num_registered_fb >= 1)
700 fbinfo = registered_fb[0];
701
702 rc = dcon_hw_init(client, 1);
703 if (rc)
704 goto einit;
705
706 /* Add the DCON device */
707
708 dcon_device = platform_device_alloc("dcon", -1);
709
710 if (dcon_device == NULL) {
711 printk("dcon: Unable to create the DCON device\n");
712 rc = -ENOMEM;
713 goto eirq;
714 }
715 /* Place holder...*/
716 i2c_set_clientdata(client, dcon_device);
717
718 if ((rc = platform_device_add(dcon_device))) {
719 printk("dcon: Unable to add the DCON device\n");
720 goto edev;
721 }
722
723 for(i = 0; i < ARRAY_SIZE(dcon_device_files); i++)
724 device_create_file(&dcon_device->dev, &dcon_device_files[i]);
725
726 /* Add the backlight device for the DCON */
727
728 dcon_client = client;
729
730 dcon_bl_dev = backlight_device_register("dcon-bl", &dcon_device->dev,
731 NULL, &dcon_bl_ops, NULL);
732
733 if (IS_ERR(dcon_bl_dev)) {
734 printk("Could not register the backlight device for the DCON (%ld)\n", PTR_ERR(dcon_bl_dev));
735 dcon_bl_dev = NULL;
736 }
737 else {
738 dcon_bl_dev->props.max_brightness = 15;
739 dcon_bl_dev->props.power = FB_BLANK_UNBLANK;
740 dcon_bl_dev->props.brightness = dcon_get_backlight();
741
742 backlight_update_status(dcon_bl_dev);
743 }
744
745 register_reboot_notifier(&dcon_nb);
746 console_event_register(&dcon_console_nb);
747 atomic_notifier_chain_register(&panic_notifier_list, &dcon_panic_nb);
748 fb_register_client(&fb_nb);
749
750 return 0;
751
752 edev:
753 platform_device_unregister(dcon_device);
754 dcon_device = NULL;
755 i2c_set_clientdata(client, NULL);
756 eirq:
757 free_irq(DCON_IRQ, &dcon_driver);
758 einit:
759 return rc;
760}
761
762static int dcon_remove(struct i2c_client *client)
763{
764 dcon_client = NULL;
765
766 fb_unregister_client(&fb_nb);
767 unregister_reboot_notifier(&dcon_nb);
768 console_event_unregister(&dcon_console_nb);
769 atomic_notifier_chain_unregister(&panic_notifier_list, &dcon_panic_nb);
770
771 free_irq(DCON_IRQ, &dcon_driver);
772
773 if (dcon_bl_dev != NULL)
774 backlight_device_unregister(dcon_bl_dev);
775
776 if (dcon_device != NULL)
777 platform_device_unregister(dcon_device);
778 cancel_work_sync(&dcon_work);
779
780 i2c_set_clientdata(client, NULL);
781
782 return 0;
783}
784
785#ifdef CONFIG_PM
786static int dcon_suspend(struct i2c_client *client, pm_message_t state)
787{
788 if (dcon_sleep_val == DCON_ACTIVE) {
789 /* Set up the DCON to have the source */
790 dcon_set_source_sync(DCON_SOURCE_DCON);
791 }
792
793 return 0;
794}
795
796static int dcon_resume(struct i2c_client *client)
797{
798 if (dcon_sleep_val == DCON_ACTIVE) {
799 dcon_bus_stabilize(client, 0);
800 dcon_set_source(DCON_SOURCE_CPU);
801 }
802
803 return 0;
804}
805
806#endif
807
808
809static irqreturn_t dcon_interrupt(int irq, void *id)
810{
811 int status = pdata->read_status();
812
813 if (status == -1)
814 return IRQ_NONE;
815
816 switch (status & 3) {
817 case 3:
818 printk(KERN_DEBUG "olpc-dcon: DCONLOAD_MISSED interrupt\n");
819 break;
820
821 case 2: /* switch to DCON mode */
822 case 1: /* switch to CPU mode */
823 dcon_switched = 1;
824 getnstimeofday(&dcon_irq_time);
825 wake_up(&dcon_wait_queue);
826 break;
827
828 case 0:
829 /* workaround resume case: the DCON (on 1.5) doesn't
830 * ever assert status 0x01 when switching to CPU mode
831 * during resume. this is because DCONLOAD is de-asserted
832 * _immediately_ upon exiting S3, so the actual release
833 * of the DCON happened long before this point.
834 * see http://dev.laptop.org/ticket/9869
835 */
836 if (dcon_source != dcon_pending && !dcon_switched) {
837 dcon_switched = 1;
838 getnstimeofday(&dcon_irq_time);
839 wake_up(&dcon_wait_queue);
840 printk(KERN_DEBUG "olpc-dcon: switching w/ status 0/0\n");
841 } else {
842 printk(KERN_DEBUG "olpc-dcon: scanline interrupt w/CPU\n");
843 }
844 }
845
846 return IRQ_HANDLED;
847}
848
849static struct i2c_device_id dcon_idtable[] = {
850 { "olpc_dcon", 0 },
851 { }
852};
853
854MODULE_DEVICE_TABLE(i2c, dcon_idtable);
855
856static struct i2c_driver dcon_driver = {
857 .driver = {
858 .name = "olpc_dcon",
859 },
860 .class = I2C_CLASS_DDC | I2C_CLASS_HWMON,
861 .id_table = dcon_idtable,
862 .probe = dcon_probe,
863 .remove = __devexit_p(dcon_remove),
864 .detect = dcon_detect,
865 .address_list = normal_i2c,
866#ifdef CONFIG_PM
867 .suspend = dcon_suspend,
868 .resume = dcon_resume,
869#endif
870};
871
872#if defined(CONFIG_OLPC_XO_1)
873#include "olpc_dcon_xo_1.c"
874#elif defined(CONFIG_OLPC_XO_1_5)
875#include "olpc_dcon_xo_1_5.c"
876#else
877#error "Trying to build OLPC DCON driver but kernel not configured for XO"
878#endif
879
880static int __init olpc_dcon_init(void)
881{
882#ifdef CONFIG_OLPC_XO_1
883 pdata = &dcon_pdata_xo_1;
884#else
885 pdata = &dcon_pdata_xo_1_5;
886#endif
887
888 i2c_add_driver(&dcon_driver);
889 return 0;
890}
891
892static void __exit olpc_dcon_exit(void)
893{
894 i2c_del_driver(&dcon_driver);
895}
896
897module_init(olpc_dcon_init);
898module_exit(olpc_dcon_exit);
899
900MODULE_LICENSE("GPL");
diff --git a/drivers/staging/olpc_dcon/olpc_dcon.h b/drivers/staging/olpc_dcon/olpc_dcon.h
new file mode 100644
index 00000000000..6453ca4ba0e
--- /dev/null
+++ b/drivers/staging/olpc_dcon/olpc_dcon.h
@@ -0,0 +1,75 @@
1#ifndef OLPC_DCON_H_
2#define OLPC_DCON_H_
3
4/* DCON registers */
5
6#define DCON_REG_ID 0
7#define DCON_REG_MODE 1
8
9#define MODE_PASSTHRU (1<<0)
10#define MODE_SLEEP (1<<1)
11#define MODE_SLEEP_AUTO (1<<2)
12#define MODE_BL_ENABLE (1<<3)
13#define MODE_BLANK (1<<4)
14#define MODE_CSWIZZLE (1<<5)
15#define MODE_COL_AA (1<<6)
16#define MODE_MONO_LUMA (1<<7)
17#define MODE_SCAN_INT (1<<8)
18#define MODE_CLOCKDIV (1<<9)
19#define MODE_DEBUG (1<<14)
20#define MODE_SELFTEST (1<<15)
21
22#define DCON_REG_HRES 2
23#define DCON_REG_HTOTAL 3
24#define DCON_REG_HSYNC_WIDTH 4
25#define DCON_REG_VRES 5
26#define DCON_REG_VTOTAL 6
27#define DCON_REG_VSYNC_WIDTH 7
28#define DCON_REG_TIMEOUT 8
29#define DCON_REG_SCAN_INT 9
30#define DCON_REG_BRIGHT 10
31
32/* GPIO registers (CS5536) */
33
34#define MSR_LBAR_GPIO 0x5140000C
35
36#define GPIOx_OUT_VAL 0x00
37#define GPIOx_OUT_EN 0x04
38#define GPIOx_IN_EN 0x20
39#define GPIOx_INV_EN 0x24
40#define GPIOx_IN_FLTR_EN 0x28
41#define GPIOx_EVNTCNT_EN 0x2C
42#define GPIOx_READ_BACK 0x30
43#define GPIOx_EVNT_EN 0x38
44#define GPIOx_NEGEDGE_EN 0x44
45#define GPIOx_NEGEDGE_STS 0x4C
46#define GPIO_FLT7_AMNT 0xD8
47#define GPIO_MAP_X 0xE0
48#define GPIO_MAP_Y 0xE4
49#define GPIO_FE7_SEL 0xF7
50
51
52/* Status values */
53
54#define DCONSTAT_SCANINT 0
55#define DCONSTAT_SCANINT_DCON 1
56#define DCONSTAT_DISPLAYLOAD 2
57#define DCONSTAT_MISSED 3
58
59/* Source values */
60
61#define DCON_SOURCE_DCON 0
62#define DCON_SOURCE_CPU 1
63
64/* Output values */
65#define DCON_OUTPUT_COLOR 0
66#define DCON_OUTPUT_MONO 1
67
68/* Sleep values */
69#define DCON_ACTIVE 0
70#define DCON_SLEEP 1
71
72/* Interrupt */
73#define DCON_IRQ 6
74
75#endif
diff --git a/drivers/staging/olpc_dcon/olpc_dcon_xo_1.c b/drivers/staging/olpc_dcon/olpc_dcon_xo_1.c
new file mode 100644
index 00000000000..779fb7d7b30
--- /dev/null
+++ b/drivers/staging/olpc_dcon/olpc_dcon_xo_1.c
@@ -0,0 +1,171 @@
1/*
2 * Mainly by David Woodhouse, somewhat modified by Jordan Crouse
3 *
4 * Copyright © 2006-2007 Red Hat, Inc.
5 * Copyright © 2006-2007 Advanced Micro Devices, Inc.
6 * Copyright © 2009 VIA Technology, Inc.
7 * Copyright (c) 2010 Andres Salomon <dilinger@queued.net>
8 *
9 * This program is free software. You can redistribute it and/or
10 * modify it under the terms of version 2 of the GNU General Public
11 * License as published by the Free Software Foundation.
12 */
13
14#include <asm/olpc.h>
15
16#include "olpc_dcon.h"
17
18/* Base address of the GPIO registers */
19static unsigned long gpio_base;
20
21/*
22 * List of GPIOs that we care about:
23 * (in) GPIO12 -- DCONBLANK
24 * (in) GPIO[56] -- DCONSTAT[01]
25 * (out) GPIO11 -- DCONLOAD
26 */
27
28#define IN_GPIOS ((1<<5) | (1<<6) | (1<<7) | (1<<12))
29#define OUT_GPIOS (1<<11)
30
31static int dcon_init_xo_1(void)
32{
33 unsigned long lo, hi;
34 unsigned char lob;
35
36 rdmsr(MSR_LBAR_GPIO, lo, hi);
37
38 /* Check the mask and whether GPIO is enabled (sanity check) */
39 if (hi != 0x0000f001) {
40 printk(KERN_ERR "GPIO not enabled -- cannot use DCON\n");
41 return -ENODEV;
42 }
43
44 /* Mask off the IO base address */
45 gpio_base = lo & 0x0000ff00;
46
47 /* Turn off the event enable for GPIO7 just to be safe */
48 outl(1 << (16+7), gpio_base + GPIOx_EVNT_EN);
49
50 /* Set the directions for the GPIO pins */
51 outl(OUT_GPIOS | (IN_GPIOS << 16), gpio_base + GPIOx_OUT_EN);
52 outl(IN_GPIOS | (OUT_GPIOS << 16), gpio_base + GPIOx_IN_EN);
53
54 /* Set up the interrupt mappings */
55
56 /* Set the IRQ to pair 2 */
57 geode_gpio_event_irq(OLPC_GPIO_DCON_IRQ, 2);
58
59 /* Enable group 2 to trigger the DCON interrupt */
60 geode_gpio_set_irq(2, DCON_IRQ);
61
62 /* Select edge level for interrupt (in PIC) */
63 lob = inb(0x4d0);
64 lob &= ~(1 << DCON_IRQ);
65 outb(lob, 0x4d0);
66
67 /* Register the interupt handler */
68 if (request_irq(DCON_IRQ, &dcon_interrupt, 0, "DCON", &dcon_driver))
69 return -EIO;
70
71 /* Clear INV_EN for GPIO7 (DCONIRQ) */
72 outl((1<<(16+7)), gpio_base + GPIOx_INV_EN);
73
74 /* Enable filter for GPIO12 (DCONBLANK) */
75 outl(1<<(12), gpio_base + GPIOx_IN_FLTR_EN);
76
77 /* Disable filter for GPIO7 */
78 outl(1<<(16+7), gpio_base + GPIOx_IN_FLTR_EN);
79
80 /* Disable event counter for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
81
82 outl(1<<(16+7), gpio_base + GPIOx_EVNTCNT_EN);
83 outl(1<<(16+12), gpio_base + GPIOx_EVNTCNT_EN);
84
85 /* Add GPIO12 to the Filter Event Pair #7 */
86 outb(12, gpio_base + GPIO_FE7_SEL);
87
88 /* Turn off negative Edge Enable for GPIO12 */
89 outl(1<<(16+12), gpio_base + GPIOx_NEGEDGE_EN);
90
91 /* Enable negative Edge Enable for GPIO7 */
92 outl(1<<7, gpio_base + GPIOx_NEGEDGE_EN);
93
94 /* Zero the filter amount for Filter Event Pair #7 */
95 outw(0, gpio_base + GPIO_FLT7_AMNT);
96
97 /* Clear the negative edge status for GPIO7 and GPIO12 */
98 outl((1<<7) | (1<<12), gpio_base+0x4c);
99
100 /* FIXME: Clear the posiitive status as well, just to be sure */
101 outl((1<<7) | (1<<12), gpio_base+0x48);
102
103 /* Enable events for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
104 outl((1<<(7))|(1<<12), gpio_base + GPIOx_EVNT_EN);
105
106 /* Determine the current state by reading the GPIO bit */
107 /* Earlier stages of the boot process have established the state */
108 dcon_source = inl(gpio_base + GPIOx_OUT_VAL) & (1<<11)
109 ? DCON_SOURCE_CPU
110 : DCON_SOURCE_DCON;
111 dcon_pending = dcon_source;
112
113 return 0;
114}
115
116static void dcon_wiggle_xo_1(void)
117{
118 int x;
119
120 /*
121 * According to HiMax, when powering the DCON up we should hold
122 * SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON
123 * state machine to reset to a (sane) initial state. Mitch Bradley
124 * did some testing and discovered that holding for 16 SMB_CLK cycles
125 * worked a lot more reliably, so that's what we do here.
126 *
127 * According to the cs5536 spec, to set GPIO14 to SMB_CLK we must
128 * simultaneously set AUX1 IN/OUT to GPIO14; ditto for SMB_DATA and
129 * GPIO15.
130 */
131 geode_gpio_set(OLPC_GPIO_SMB_CLK|OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_VAL);
132 geode_gpio_set(OLPC_GPIO_SMB_CLK|OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_ENABLE);
133 geode_gpio_clear(OLPC_GPIO_SMB_CLK|OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1);
134 geode_gpio_clear(OLPC_GPIO_SMB_CLK|OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX2);
135 geode_gpio_clear(OLPC_GPIO_SMB_CLK|OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1);
136
137 for (x = 0; x < 16; x++) {
138 udelay(5);
139 geode_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL);
140 udelay(5);
141 geode_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL);
142 }
143 udelay(5);
144 geode_gpio_set(OLPC_GPIO_SMB_CLK|OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1);
145 geode_gpio_set(OLPC_GPIO_SMB_CLK|OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1);
146}
147
148static void dcon_set_dconload_1(int val)
149{
150 if (val)
151 outl(1<<11, gpio_base + GPIOx_OUT_VAL);
152 else
153 outl(1<<(11 + 16), gpio_base + GPIOx_OUT_VAL);
154}
155
156static int dcon_read_status_xo_1(void)
157{
158 int status = inl(gpio_base + GPIOx_READ_BACK) >> 5;
159
160 /* Clear the negative edge status for GPIO7 */
161 outl(1 << 7, gpio_base + GPIOx_NEGEDGE_STS);
162
163 return status;
164}
165
166static struct dcon_platform_data dcon_pdata_xo_1 = {
167 .init = dcon_init_xo_1,
168 .bus_stabilize_wiggle = dcon_wiggle_xo_1,
169 .set_dconload = dcon_set_dconload_1,
170 .read_status = dcon_read_status_xo_1,
171};
diff --git a/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c b/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c
new file mode 100644
index 00000000000..cca6a235ef9
--- /dev/null
+++ b/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c
@@ -0,0 +1,219 @@
1/*
2 * Copyright (c) 2009,2010 One Laptop per Child
3 *
4 * This program is free software. You can redistribute it and/or
5 * modify it under the terms of version 2 of the GNU General Public
6 * License as published by the Free Software Foundation.
7 */
8
9#include <linux/acpi.h>
10
11/* Hardware setup on the XO 1.5:
12 * DCONLOAD connects to
13 * VX855_GPO12 (not nCR_PWOFF) (rev A)
14 * VX855_GPIO1 (not SMBCK2) (rev B)
15 * DCONBLANK connects to VX855_GPIO8 (not SSPICLK) unused in driver
16 * DCONSTAT0 connects to VX855_GPI10 (not SSPISDI)
17 * DCONSTAT1 connects to VX855_GPI11 (not nSSPISS)
18 * DCONIRQ connects to VX855_GPIO12 (on B3. on B2, it goes to
19 * SMBALRT, which doesn't work.)
20 * DCONSMBDATA connects to VX855 graphics CRTSPD
21 * DCONSMBCLK connects to VX855 graphics CRTSPCLK
22 */
23
24#define TEST_B2 0 // define to test B3 paths on a modded B2 board
25
26#define VX855_GENL_PURPOSE_OUTPUT 0x44c // PMIO_Rx4c-4f
27#define VX855_GPI_STATUS_CHG 0x450 // PMIO_Rx50
28#define VX855_GPI_SCI_SMI 0x452 // PMIO_Rx52
29#define BIT_GPIO12 0x40
30
31#define PREFIX "OLPC DCON:"
32
33/*
34 there is no support here for DCONIRQ on 1.5 boards earlier than
35 B3. the issue is that the DCONIRQ signal on earlier boards is
36 routed to SMBALRT, which turns out to to be a level sensitive
37 interrupt. the DCONIRQ signal is far too short (11usec) to
38 be detected reliably in that case. including support for
39 DCONIRQ functions no better than none at all.
40*/
41
42static struct dcon_platform_data dcon_pdata_xo_1_5;
43
44static void dcon_clear_irq(void)
45{
46 if (TEST_B2 || olpc_board_at_least(olpc_board(BOARD_XO_1_5_B3))) {
47 // irq status will appear in PMIO_Rx50[6] (RW1C) on gpio12
48 outb(BIT_GPIO12, VX855_GPI_STATUS_CHG);
49 }
50}
51
52static int dcon_was_irq(void)
53{
54 u_int8_t tmp;
55
56 if (TEST_B2 || olpc_board_at_least(olpc_board(BOARD_XO_1_5_B3))) {
57 // irq status will appear in PMIO_Rx50[6] on gpio12
58 tmp = inb(VX855_GPI_STATUS_CHG);
59 return !!(tmp & BIT_GPIO12);
60 }
61
62 return 0;
63}
64
65static int dcon_init_xo_1_5(void)
66{
67 unsigned int irq;
68 u_int8_t tmp;
69 struct pci_dev *pdev;
70
71
72 pdev = pci_get_device(PCI_VENDOR_ID_VIA,
73 PCI_DEVICE_ID_VIA_VX855, NULL);
74 if (!pdev) {
75 printk(KERN_ERR "cannot find VX855 PCI ID\n");
76 return 1;
77 }
78
79 if (olpc_board_at_least(olpc_board(BOARD_XO_1_5_B1))) {
80 pci_read_config_byte(pdev, 0x95, &tmp);
81 pci_write_config_byte(pdev, 0x95, tmp|0x0c);
82 } else {
83 /* Set GPO12 to GPO mode, not nCR_PWOFF */
84 pci_read_config_byte(pdev, 0x9b, &tmp);
85 pci_write_config_byte(pdev, 0x9b, tmp|0x01);
86 }
87
88 /* Set GPIO8 to GPIO mode, not SSPICLK */
89 pci_read_config_byte(pdev, 0xe3, &tmp);
90 pci_write_config_byte(pdev, 0xe3, tmp | 0x04);
91
92 /* Set GPI10/GPI11 to GPI mode, not SSPISDI/SSPISS */
93 pci_read_config_byte(pdev, 0xe4, &tmp);
94 pci_write_config_byte(pdev, 0xe4, tmp|0x08);
95
96 if (TEST_B2 || olpc_board_at_least(olpc_board(BOARD_XO_1_5_B3))) {
97 // clear PMU_RxE1[6] to select SCI on GPIO12
98 // clear PMU_RxE0[6] to choose falling edge
99 pci_read_config_byte(pdev, 0xe1, &tmp);
100 pci_write_config_byte(pdev, 0xe1, tmp & ~BIT_GPIO12);
101 pci_read_config_byte(pdev, 0xe0, &tmp);
102 pci_write_config_byte(pdev, 0xe0, tmp & ~BIT_GPIO12);
103
104 dcon_clear_irq();
105
106 // set PMIO_Rx52[6] to enable SCI/SMI on gpio12
107 outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI);
108
109 }
110
111 /* Determine the current state of DCONLOAD, likely set by firmware */
112 if (olpc_board_at_least(olpc_board(BOARD_XO_1_5_B1))) {
113 // GPIO1
114 dcon_source = (inl(VX855_GENL_PURPOSE_OUTPUT) & 0x1000) ?
115 DCON_SOURCE_CPU : DCON_SOURCE_DCON;
116 } else {
117 // GPO12
118 dcon_source = (inl(VX855_GENL_PURPOSE_OUTPUT) & 0x04000000) ?
119 DCON_SOURCE_CPU : DCON_SOURCE_DCON;
120 }
121 dcon_pending = dcon_source;
122
123 pci_dev_put(pdev);
124
125 /* we're sharing the IRQ with ACPI */
126 irq = acpi_gbl_FADT.sci_interrupt;
127 if (request_irq(irq, &dcon_interrupt, IRQF_SHARED, "DCON", &dcon_driver)) {
128 printk(KERN_ERR PREFIX "DCON (IRQ%d) allocation failed\n", irq);
129 return 1;
130 }
131
132
133 return 0;
134}
135
136static void set_i2c_line(int sda, int scl)
137{
138 unsigned char tmp;
139 unsigned int port = 0x26;
140
141 /* FIXME: This directly accesses the CRT GPIO controller !!! */
142 outb(port, 0x3c4);
143 tmp = inb(0x3c5);
144
145 if (scl)
146 tmp |= 0x20;
147 else
148 tmp &= ~0x20;
149
150 if (sda)
151 tmp |= 0x10;
152 else
153 tmp &= ~0x10;
154
155 tmp |= 0x01;
156
157 outb(port, 0x3c4);
158 outb(tmp, 0x3c5);
159}
160
161
162static void dcon_wiggle_xo_1_5(void)
163{
164 int x;
165
166 /*
167 * According to HiMax, when powering the DCON up we should hold
168 * SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON
169 * state machine to reset to a (sane) initial state. Mitch Bradley
170 * did some testing and discovered that holding for 16 SMB_CLK cycles
171 * worked a lot more reliably, so that's what we do here.
172 */
173 set_i2c_line(1, 1);
174
175 for (x = 0; x < 16; x++) {
176 udelay(5);
177 set_i2c_line(1, 0);
178 udelay(5);
179 set_i2c_line(1, 1);
180 }
181 udelay(5);
182
183 if (TEST_B2 || olpc_board_at_least(olpc_board(BOARD_XO_1_5_B3))) {
184 // set PMIO_Rx52[6] to enable SCI/SMI on gpio12
185 outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI);
186 }
187}
188
189static void dcon_set_dconload_xo_1_5(int val)
190{
191 if (olpc_board_at_least(olpc_board(BOARD_XO_1_5_B1))) {
192 gpio_set_value(VX855_GPIO(1), val);
193 } else {
194 gpio_set_value(VX855_GPO(12), val);
195 }
196}
197
198static int dcon_read_status_xo_1_5(void)
199{
200 int status;
201
202 if (!dcon_was_irq())
203 return -1;
204
205 // i believe this is the same as "inb(0x44b) & 3"
206 status = gpio_get_value(VX855_GPI(10));
207 status |= gpio_get_value(VX855_GPI(11)) << 1;
208
209 dcon_clear_irq();
210
211 return status;
212}
213
214static struct dcon_platform_data dcon_pdata_xo_1_5 = {
215 .init = dcon_init_xo_1_5,
216 .bus_stabilize_wiggle = dcon_wiggle_xo_1_5,
217 .set_dconload = dcon_set_dconload_xo_1_5,
218 .read_status = dcon_read_status_xo_1_5,
219};