diff options
Diffstat (limited to 'drivers/staging/olpc_dcon/olpc_dcon.c')
-rw-r--r-- | drivers/staging/olpc_dcon/olpc_dcon.c | 900 |
1 files changed, 900 insertions, 0 deletions
diff --git a/drivers/staging/olpc_dcon/olpc_dcon.c b/drivers/staging/olpc_dcon/olpc_dcon.c new file mode 100644 index 000000000000..688cf0f69524 --- /dev/null +++ b/drivers/staging/olpc_dcon/olpc_dcon.c @@ -0,0 +1,900 @@ +/* + * Mainly by David Woodhouse, somewhat modified by Jordan Crouse + * + * Copyright © 2006-2007 Red Hat, Inc. + * Copyright © 2006-2007 Advanced Micro Devices, Inc. + * Copyright © 2009 VIA Technology, Inc. + * Copyright (c) 2010 Andres Salomon <dilinger@queued.net> + * + * This program is free software. You can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + */ + + +#include <linux/kernel.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/i2c-id.h> +#include <linux/pci.h> +#include <linux/vt_kern.h> +#include <linux/pci_ids.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/backlight.h> +#include <linux/device.h> +#include <linux/notifier.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/reboot.h> +#include <linux/gpio.h> +#include <asm/tsc.h> +#include <asm/olpc.h> + +#include "olpc_dcon.h" + +/* Module definitions */ + +static int resumeline = 898; +module_param(resumeline, int, 0444); + +static int noinit; +module_param(noinit, int, 0444); + +/* Default off since it doesn't work on DCON ASIC in B-test OLPC board */ +static int useaa = 1; +module_param(useaa, int, 0444); + +struct dcon_platform_data { + int (*init)(void); + void (*bus_stabilize_wiggle)(void); + void (*set_dconload)(int); + int (*read_status)(void); +}; + +static struct dcon_platform_data *pdata; + +/* I2C structures */ + +static struct i2c_driver dcon_driver; +static struct i2c_client *dcon_client; + +/* Platform devices */ +static struct platform_device *dcon_device; + +/* Backlight device */ +static struct backlight_device *dcon_bl_dev; + +static struct fb_info *fbinfo; + +/* set this to 1 while controlling fb blank state from this driver */ +static int ignore_fb_events = 0; + +/* Current source, initialized at probe time */ +static int dcon_source; + +/* Desired source */ +static int dcon_pending; + +/* Current output type */ +static int dcon_output = DCON_OUTPUT_COLOR; + +/* Current sleep status (not yet implemented) */ +static int dcon_sleep_val = DCON_ACTIVE; + +/* Shadow register for the DCON_REG_MODE register */ +static unsigned short dcon_disp_mode; + +/* Variables used during switches */ +static int dcon_switched; +static struct timespec dcon_irq_time; +static struct timespec dcon_load_time; + +static DECLARE_WAIT_QUEUE_HEAD(dcon_wait_queue); + +static unsigned short normal_i2c[] = { 0x0d, I2C_CLIENT_END }; + +#define dcon_write(reg,val) i2c_smbus_write_word_data(dcon_client,reg,val) +#define dcon_read(reg) i2c_smbus_read_word_data(dcon_client,reg) + +/* The current backlight value - this saves us some smbus traffic */ +static int bl_val = -1; + +/* ===== API functions - these are called by a variety of users ==== */ + +static int dcon_hw_init(struct i2c_client *client, int is_init) +{ + uint16_t ver; + int rc = 0; + + ver = i2c_smbus_read_word_data(client, DCON_REG_ID); + if ((ver >> 8) != 0xDC) { + printk(KERN_ERR "olpc-dcon: DCON ID not 0xDCxx: 0x%04x " + "instead.\n", ver); + rc = -ENXIO; + goto err; + } + + if (is_init) { + printk(KERN_INFO "olpc-dcon: Discovered DCON version %x\n", + ver & 0xFF); + if ((rc = pdata->init()) != 0) { + printk(KERN_ERR "olpc-dcon: Unable to init.\n"); + goto err; + } + } + + if (ver < 0xdc02 && !noinit) { + /* Initialize the DCON registers */ + + /* Start with work-arounds for DCON ASIC */ + i2c_smbus_write_word_data(client, 0x4b, 0x00cc); + i2c_smbus_write_word_data(client, 0x4b, 0x00cc); + i2c_smbus_write_word_data(client, 0x4b, 0x00cc); + i2c_smbus_write_word_data(client, 0x0b, 0x007a); + i2c_smbus_write_word_data(client, 0x36, 0x025c); + i2c_smbus_write_word_data(client, 0x37, 0x025e); + + /* Initialise SDRAM */ + + i2c_smbus_write_word_data(client, 0x3b, 0x002b); + i2c_smbus_write_word_data(client, 0x41, 0x0101); + i2c_smbus_write_word_data(client, 0x42, 0x0101); + } + else if (!noinit) { + /* SDRAM setup/hold time */ + i2c_smbus_write_word_data(client, 0x3a, 0xc040); + i2c_smbus_write_word_data(client, 0x41, 0x0000); + i2c_smbus_write_word_data(client, 0x41, 0x0101); + i2c_smbus_write_word_data(client, 0x42, 0x0101); + } + + /* Colour swizzle, AA, no passthrough, backlight */ + if (is_init) { + dcon_disp_mode = MODE_PASSTHRU | MODE_BL_ENABLE | MODE_CSWIZZLE; + if (useaa) + dcon_disp_mode |= MODE_COL_AA; + } + i2c_smbus_write_word_data(client, DCON_REG_MODE, dcon_disp_mode); + + + /* Set the scanline to interrupt on during resume */ + i2c_smbus_write_word_data(client, DCON_REG_SCAN_INT, resumeline); + +err: + return rc; +} + +/* + * The smbus doesn't always come back due to what is believed to be + * hardware (power rail) bugs. For older models where this is known to + * occur, our solution is to attempt to wait for the bus to stabilize; + * if it doesn't happen, cut power to the dcon, repower it, and wait + * for the bus to stabilize. Rinse, repeat until we have a working + * smbus. For newer models, we simply BUG(); we want to know if this + * still happens despite the power fixes that have been made! + */ +static int dcon_bus_stabilize(struct i2c_client *client, int is_powered_down) +{ + unsigned long timeout; + int x; + +power_up: + if (is_powered_down) { + x = 1; + if ((x = olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0))) { + printk(KERN_WARNING "olpc-dcon: unable to force dcon " + "to power up: %d!\n", x); + return x; + } + msleep(10); /* we'll be conservative */ + } + + pdata->bus_stabilize_wiggle(); + + for (x = -1, timeout = 50; timeout && x < 0; timeout--) { + msleep(1); + x = dcon_read(DCON_REG_ID); + } + if (x < 0) { + printk(KERN_ERR "olpc-dcon: unable to stabilize dcon's " + "smbus, reasserting power and praying.\n"); + BUG_ON(olpc_board_at_least(olpc_board(BOARD_XO_1_C2))); + x = 0; + olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0); + msleep(100); + is_powered_down = 1; + goto power_up; /* argh, stupid hardware.. */ + } + + if (is_powered_down) + return dcon_hw_init(client, 0); + return 0; +} + + +/* Backlight notes - turning off the backlight enable bit in the DCON + * doesn't save us any power over just pushing the BL to zero, so we + * don't use that bit in this code. + */ + +static int dcon_get_backlight(void) +{ + if (dcon_client == NULL) + return 0; + + if (bl_val == -1) + bl_val = dcon_read(DCON_REG_BRIGHT) & 0x0F; + + return bl_val; +} + + +static void dcon_set_backlight_hw(int level) +{ + bl_val = level & 0x0F; + dcon_write(DCON_REG_BRIGHT, bl_val); + + /* Purposely turn off the backlight when we go to level 0 */ + if (bl_val == 0) { + dcon_disp_mode &= ~MODE_BL_ENABLE; + dcon_write(DCON_REG_MODE, dcon_disp_mode); + } else if (!(dcon_disp_mode & MODE_BL_ENABLE)) { + dcon_disp_mode |= MODE_BL_ENABLE; + dcon_write(DCON_REG_MODE, dcon_disp_mode); + } +} + +static void dcon_set_backlight(int level) +{ + if (dcon_client == NULL) + return; + + if (bl_val == (level & 0x0F)) + return; + + dcon_set_backlight_hw(level); +} + +/* Set the output type to either color or mono */ + +static int dcon_set_output(int arg) +{ + if (dcon_output == arg) + return 0; + + dcon_output = arg; + + if (arg == DCON_OUTPUT_MONO) { + dcon_disp_mode &= ~(MODE_CSWIZZLE | MODE_COL_AA); + dcon_disp_mode |= MODE_MONO_LUMA; + } + else { + dcon_disp_mode &= ~(MODE_MONO_LUMA); + dcon_disp_mode |= MODE_CSWIZZLE; + if (useaa) + dcon_disp_mode |= MODE_COL_AA; + } + + dcon_write(DCON_REG_MODE, dcon_disp_mode); + return 0; +} + +/* For now, this will be really stupid - we need to address how + * DCONLOAD works in a sleep and account for it accordingly + */ + +static void dcon_sleep(int state) +{ + int x; + + /* Turn off the backlight and put the DCON to sleep */ + + if (state == dcon_sleep_val) + return; + + if (!olpc_board_at_least(olpc_board(BOARD_XO_1_C2))) + return; + + if (state == DCON_SLEEP) { + x = 0; + if ((x = olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0))) + printk(KERN_WARNING "olpc-dcon: unable to force dcon " + "to power down: %d!\n", x); + else + dcon_sleep_val = state; + } + else { + /* Only re-enable the backlight if the backlight value is set */ + if (bl_val != 0) + dcon_disp_mode |= MODE_BL_ENABLE; + + if ((x=dcon_bus_stabilize(dcon_client, 1))) + printk(KERN_WARNING "olpc-dcon: unable to reinit dcon" + " hardware: %d!\n", x); + else + dcon_sleep_val = state; + + /* Restore backlight */ + dcon_set_backlight_hw(bl_val); + } + + /* We should turn off some stuff in the framebuffer - but what? */ +} + +/* the DCON seems to get confused if we change DCONLOAD too + * frequently -- i.e., approximately faster than frame time. + * normally we don't change it this fast, so in general we won't + * delay here. + */ +void dcon_load_holdoff(void) +{ + struct timespec delta_t, now; + while(1) { + getnstimeofday(&now); + delta_t = timespec_sub(now, dcon_load_time); + if (delta_t.tv_sec != 0 || + delta_t.tv_nsec > NSEC_PER_MSEC * 20) { + break; + } + mdelay(4); + } +} +/* Set the source of the display (CPU or DCON) */ + +static void dcon_source_switch(struct work_struct *work) +{ + DECLARE_WAITQUEUE(wait, current); + int source = dcon_pending; + + if (dcon_source == source) + return; + + dcon_load_holdoff(); + + dcon_switched = 0; + + switch (source) { + case DCON_SOURCE_CPU: + printk("dcon_source_switch to CPU\n"); + /* Enable the scanline interrupt bit */ + if (dcon_write(DCON_REG_MODE, dcon_disp_mode | MODE_SCAN_INT)) + printk(KERN_ERR "olpc-dcon: couldn't enable scanline interrupt!\n"); + else { + /* Wait up to one second for the scanline interrupt */ + wait_event_timeout(dcon_wait_queue, dcon_switched == 1, HZ); + } + + if (!dcon_switched) + printk(KERN_ERR "olpc-dcon: Timeout entering CPU mode; expect a screen glitch.\n"); + + /* Turn off the scanline interrupt */ + if (dcon_write(DCON_REG_MODE, dcon_disp_mode)) + printk(KERN_ERR "olpc-dcon: couldn't disable scanline interrupt!\n"); + + /* + * Ideally we'd like to disable interrupts here so that the + * fb unblanking and DCON turn on happen at a known time value; + * however, we can't do that right now with fb_blank + * messing with semaphores. + * + * For now, we just hope.. + */ + acquire_console_sem(); + ignore_fb_events = 1; + if (fb_blank(fbinfo, FB_BLANK_UNBLANK)) { + ignore_fb_events = 0; + release_console_sem(); + printk(KERN_ERR "olpc-dcon: Failed to enter CPU mode\n"); + dcon_pending = DCON_SOURCE_DCON; + return; + } + ignore_fb_events = 0; + release_console_sem(); + + /* And turn off the DCON */ + pdata->set_dconload(1); + getnstimeofday(&dcon_load_time); + + printk(KERN_INFO "olpc-dcon: The CPU has control\n"); + break; + case DCON_SOURCE_DCON: + { + int t; + struct timespec delta_t; + + printk("dcon_source_switch to DCON\n"); + + add_wait_queue(&dcon_wait_queue, &wait); + set_current_state(TASK_UNINTERRUPTIBLE); + + /* Clear DCONLOAD - this implies that the DCON is in control */ + pdata->set_dconload(0); + getnstimeofday(&dcon_load_time); + + t = schedule_timeout(HZ/2); + remove_wait_queue(&dcon_wait_queue, &wait); + set_current_state(TASK_RUNNING); + + if (!dcon_switched) { + printk(KERN_ERR "olpc-dcon: Timeout entering DCON mode; expect a screen glitch.\n"); + } else { + /* sometimes the DCON doesn't follow its own rules, + * and doesn't wait for two vsync pulses before + * ack'ing the frame load with an IRQ. the result + * is that the display shows the *previously* + * loaded frame. we can detect this by looking at + * the time between asserting DCONLOAD and the IRQ -- + * if it's less than 20msec, then the DCON couldn't + * have seen two VSYNC pulses. in that case we + * deassert and reassert, and hope for the best. + * see http://dev.laptop.org/ticket/9664 + */ + delta_t = timespec_sub(dcon_irq_time, dcon_load_time); + if (dcon_switched && delta_t.tv_sec == 0 && + delta_t.tv_nsec < NSEC_PER_MSEC * 20) { + printk(KERN_ERR "olpc-dcon: missed loading, retrying\n"); + pdata->set_dconload(1); + mdelay(41); + pdata->set_dconload(0); + getnstimeofday(&dcon_load_time); + mdelay(41); + } + } + + acquire_console_sem(); + ignore_fb_events = 1; + if (fb_blank(fbinfo, FB_BLANK_POWERDOWN)) + printk(KERN_ERR "olpc-dcon: couldn't blank fb!\n"); + ignore_fb_events = 0; + release_console_sem(); + + printk(KERN_INFO "olpc-dcon: The DCON has control\n"); + break; + } + default: + BUG(); + } + + dcon_source = source; +} + +static DECLARE_WORK(dcon_work, dcon_source_switch); + +static void dcon_set_source(int arg) +{ + if (dcon_pending == arg) + return; + + dcon_pending = arg; + + if ((dcon_source != arg) && !work_pending(&dcon_work)) + schedule_work(&dcon_work); +} + +static void dcon_set_source_sync(int arg) +{ + dcon_set_source(arg); + flush_scheduled_work(); +} + +static int dconbl_set(struct backlight_device *dev) { + + int level = dev->props.brightness; + + if (dev->props.power != FB_BLANK_UNBLANK) + level = 0; + + dcon_set_backlight(level); + return 0; +} + +static int dconbl_get(struct backlight_device *dev) { + return dcon_get_backlight(); +} + +static ssize_t dcon_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%4.4X\n", dcon_disp_mode); +} + +static ssize_t dcon_sleep_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + return sprintf(buf, "%d\n", dcon_sleep_val); +} + +static ssize_t dcon_freeze_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", dcon_source == DCON_SOURCE_DCON ? 1 : 0); +} + +static ssize_t dcon_output_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", dcon_output); +} + +static ssize_t dcon_resumeline_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", resumeline); +} + +static int _strtoul(const char *buf, int len, unsigned int *val) +{ + + char *endp; + unsigned int output = simple_strtoul(buf, &endp, 0); + int size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + + if (size != len) + return -EINVAL; + + *val = output; + return 0; +} + +static ssize_t dcon_output_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int output; + int rc = -EINVAL; + + if (_strtoul(buf, count, &output)) + return -EINVAL; + + if (output == DCON_OUTPUT_COLOR || output == DCON_OUTPUT_MONO) { + dcon_set_output(output); + rc = count; + } + + return rc; +} + +static ssize_t dcon_freeze_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int output; + + if (_strtoul(buf, count, &output)) + return -EINVAL; + + printk("dcon_freeze_store: %d\n", output); + + switch (output) { + case 0: + dcon_set_source(DCON_SOURCE_CPU); + break; + case 1: + dcon_set_source_sync(DCON_SOURCE_DCON); + break; + case 2: // normally unused + dcon_set_source(DCON_SOURCE_DCON); + break; + default: + return -EINVAL; + } + + return count; +} + +static ssize_t dcon_resumeline_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int rl; + int rc = -EINVAL; + + if (_strtoul(buf, count, &rl)) + return rc; + + resumeline = rl; + dcon_write(DCON_REG_SCAN_INT, resumeline); + rc = count; + + return rc; +} + +static ssize_t dcon_sleep_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int output; + + if (_strtoul(buf, count, &output)) + return -EINVAL; + + dcon_sleep(output ? DCON_SLEEP : DCON_ACTIVE); + return count; +} + +static struct device_attribute dcon_device_files[] = { + __ATTR(mode, 0444, dcon_mode_show, NULL), + __ATTR(sleep, 0644, dcon_sleep_show, dcon_sleep_store), + __ATTR(freeze, 0644, dcon_freeze_show, dcon_freeze_store), + __ATTR(output, 0644, dcon_output_show, dcon_output_store), + __ATTR(resumeline, 0644, dcon_resumeline_show, dcon_resumeline_store), +}; + +static struct backlight_ops dcon_bl_ops = { + .get_brightness = dconbl_get, + .update_status = dconbl_set +}; + + +static int dcon_reboot_notify(struct notifier_block *nb, unsigned long foo, void *bar) +{ + if (dcon_client == NULL) + return 0; + + /* Turn off the DCON. Entirely. */ + dcon_write(DCON_REG_MODE, 0x39); + dcon_write(DCON_REG_MODE, 0x32); + return 0; +} + +static int dcon_conswitch_notify(struct notifier_block *nb, + unsigned long mode, void *dummy) +{ + if (mode == CONSOLE_EVENT_SWITCH_TEXT) + dcon_sleep(DCON_ACTIVE); + + return 0; +} + +static struct notifier_block dcon_nb = { + .notifier_call = dcon_reboot_notify, + .priority = -1, +}; + +static struct notifier_block dcon_console_nb = { + .notifier_call = dcon_conswitch_notify, + .priority = -1, +}; + +static int unfreeze_on_panic(struct notifier_block *nb, unsigned long e, void *p) +{ + pdata->set_dconload(1); + return NOTIFY_DONE; +} + +static struct notifier_block dcon_panic_nb = { + .notifier_call = unfreeze_on_panic, +}; + +/* when framebuffer sleeps due to external source (e.g. user idle), power down + * the DCON. also power up when the framebuffer comes back to life. */ +static int fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank = (int *) evdata->data; + if (event != FB_EVENT_BLANK || ignore_fb_events) + return 0; + dcon_sleep((*blank) ? DCON_SLEEP : DCON_ACTIVE); + return 0; +} + +static struct notifier_block fb_nb = { + .notifier_call = fb_notifier_callback, +}; + +static int dcon_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + strlcpy(info->type, "olpc_dcon", I2C_NAME_SIZE); + + return 0; +} + +static int dcon_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int rc, i; + + if (num_registered_fb >= 1) + fbinfo = registered_fb[0]; + + rc = dcon_hw_init(client, 1); + if (rc) + goto einit; + + /* Add the DCON device */ + + dcon_device = platform_device_alloc("dcon", -1); + + if (dcon_device == NULL) { + printk("dcon: Unable to create the DCON device\n"); + rc = -ENOMEM; + goto eirq; + } + /* Place holder...*/ + i2c_set_clientdata(client, dcon_device); + + if ((rc = platform_device_add(dcon_device))) { + printk("dcon: Unable to add the DCON device\n"); + goto edev; + } + + for(i = 0; i < ARRAY_SIZE(dcon_device_files); i++) + device_create_file(&dcon_device->dev, &dcon_device_files[i]); + + /* Add the backlight device for the DCON */ + + dcon_client = client; + + dcon_bl_dev = backlight_device_register("dcon-bl", &dcon_device->dev, + NULL, &dcon_bl_ops, NULL); + + if (IS_ERR(dcon_bl_dev)) { + printk("Could not register the backlight device for the DCON (%ld)\n", PTR_ERR(dcon_bl_dev)); + dcon_bl_dev = NULL; + } + else { + dcon_bl_dev->props.max_brightness = 15; + dcon_bl_dev->props.power = FB_BLANK_UNBLANK; + dcon_bl_dev->props.brightness = dcon_get_backlight(); + + backlight_update_status(dcon_bl_dev); + } + + register_reboot_notifier(&dcon_nb); + console_event_register(&dcon_console_nb); + atomic_notifier_chain_register(&panic_notifier_list, &dcon_panic_nb); + fb_register_client(&fb_nb); + + return 0; + + edev: + platform_device_unregister(dcon_device); + dcon_device = NULL; + i2c_set_clientdata(client, NULL); + eirq: + free_irq(DCON_IRQ, &dcon_driver); + einit: + return rc; +} + +static int dcon_remove(struct i2c_client *client) +{ + dcon_client = NULL; + + fb_unregister_client(&fb_nb); + unregister_reboot_notifier(&dcon_nb); + console_event_unregister(&dcon_console_nb); + atomic_notifier_chain_unregister(&panic_notifier_list, &dcon_panic_nb); + + free_irq(DCON_IRQ, &dcon_driver); + + if (dcon_bl_dev != NULL) + backlight_device_unregister(dcon_bl_dev); + + if (dcon_device != NULL) + platform_device_unregister(dcon_device); + cancel_work_sync(&dcon_work); + + i2c_set_clientdata(client, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int dcon_suspend(struct i2c_client *client, pm_message_t state) +{ + if (dcon_sleep_val == DCON_ACTIVE) { + /* Set up the DCON to have the source */ + dcon_set_source_sync(DCON_SOURCE_DCON); + } + + return 0; +} + +static int dcon_resume(struct i2c_client *client) +{ + if (dcon_sleep_val == DCON_ACTIVE) { + dcon_bus_stabilize(client, 0); + dcon_set_source(DCON_SOURCE_CPU); + } + + return 0; +} + +#endif + + +static irqreturn_t dcon_interrupt(int irq, void *id) +{ + int status = pdata->read_status(); + + if (status == -1) + return IRQ_NONE; + + switch (status & 3) { + case 3: + printk(KERN_DEBUG "olpc-dcon: DCONLOAD_MISSED interrupt\n"); + break; + + case 2: /* switch to DCON mode */ + case 1: /* switch to CPU mode */ + dcon_switched = 1; + getnstimeofday(&dcon_irq_time); + wake_up(&dcon_wait_queue); + break; + + case 0: + /* workaround resume case: the DCON (on 1.5) doesn't + * ever assert status 0x01 when switching to CPU mode + * during resume. this is because DCONLOAD is de-asserted + * _immediately_ upon exiting S3, so the actual release + * of the DCON happened long before this point. + * see http://dev.laptop.org/ticket/9869 + */ + if (dcon_source != dcon_pending && !dcon_switched) { + dcon_switched = 1; + getnstimeofday(&dcon_irq_time); + wake_up(&dcon_wait_queue); + printk(KERN_DEBUG "olpc-dcon: switching w/ status 0/0\n"); + } else { + printk(KERN_DEBUG "olpc-dcon: scanline interrupt w/CPU\n"); + } + } + + return IRQ_HANDLED; +} + +static struct i2c_device_id dcon_idtable[] = { + { "olpc_dcon", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, dcon_idtable); + +static struct i2c_driver dcon_driver = { + .driver = { + .name = "olpc_dcon", + }, + .class = I2C_CLASS_DDC | I2C_CLASS_HWMON, + .id_table = dcon_idtable, + .probe = dcon_probe, + .remove = __devexit_p(dcon_remove), + .detect = dcon_detect, + .address_list = normal_i2c, +#ifdef CONFIG_PM + .suspend = dcon_suspend, + .resume = dcon_resume, +#endif +}; + +#if defined(CONFIG_OLPC_XO_1) +#include "olpc_dcon_xo_1.c" +#elif defined(CONFIG_OLPC_XO_1_5) +#include "olpc_dcon_xo_1_5.c" +#else +#error "Trying to build OLPC DCON driver but kernel not configured for XO" +#endif + +static int __init olpc_dcon_init(void) +{ +#ifdef CONFIG_OLPC_XO_1 + pdata = &dcon_pdata_xo_1; +#else + pdata = &dcon_pdata_xo_1_5; +#endif + + i2c_add_driver(&dcon_driver); + return 0; +} + +static void __exit olpc_dcon_exit(void) +{ + i2c_del_driver(&dcon_driver); +} + +module_init(olpc_dcon_init); +module_exit(olpc_dcon_exit); + +MODULE_LICENSE("GPL"); |