summaryrefslogtreecommitdiff
path: root/target/linux/olpc/files/arch/i386/kernel/olpc-pm.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/olpc/files/arch/i386/kernel/olpc-pm.c')
-rw-r--r--target/linux/olpc/files/arch/i386/kernel/olpc-pm.c785
1 files changed, 785 insertions, 0 deletions
diff --git a/target/linux/olpc/files/arch/i386/kernel/olpc-pm.c b/target/linux/olpc/files/arch/i386/kernel/olpc-pm.c
new file mode 100644
index 0000000000..93149f1c38
--- /dev/null
+++ b/target/linux/olpc/files/arch/i386/kernel/olpc-pm.c
@@ -0,0 +1,785 @@
+/* olpc-pm.c
+ * © 2006 Red Hat, Inc.
+ * Portions also copyright 2006 Advanced Micro Devices, Inc.
+ * GPLv2
+ */
+
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/suspend.h>
+#include <linux/bootmem.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/mc146818rtc.h>
+#include <asm/io.h>
+
+#include <asm/olpc.h>
+
+/* A few words about accessing the ACPI and PM registers. Long story short,
+ byte and word accesses of the ACPI and PM registers is broken. The only
+ way to do it really correctly is to use dword accesses, which we do
+ throughout this code. For more details, please consult Eratta 17 and 18
+ here:
+
+ http://www.amd.com/files/connectivitysolutions/geode/geode_gx/34472D_CS5536_B1_specupdate.pdf
+*/
+
+#define PM_IRQ 3
+
+#define CS5536_PM_PWRBTN (1 << 8)
+#define CS5536_PM_RTC (1 << 10)
+
+#define GPIO_WAKEUP_EC (1 << 31)
+#define GPIO_WAKEUP_LID (1 << 30)
+
+#define PM_MODE_NORMAL 0
+#define PM_MODE_TEST 1
+#define PM_MODE_MAX 2
+
+/* These, and the battery EC commands, should be in an olpc.h. */
+#define EC_WRITE_SCI_MASK 0x1b
+#define EC_READ_SCI_MASK 0x1c
+
+extern void do_olpc_suspend_lowlevel(void);
+
+static struct {
+ unsigned long address;
+ unsigned short segment;
+} ofw_bios_entry = { 0, __KERNEL_CS };
+
+static int olpc_pm_mode = PM_MODE_NORMAL;
+static unsigned long acpi_base;
+static unsigned long pms_base;
+static int sci_irq;
+static int olpc_lid_flag;
+
+static struct input_dev *pm_inputdev;
+static struct input_dev *lid_inputdev;
+static struct input_dev *ebook_inputdev;
+static struct pm_ops olpc_pm_ops;
+
+static int gpio_wake_events = 0;
+static int ebook_state = -1;
+static u16 olpc_wakeup_mask = 0;
+
+struct platform_device olpc_powerbutton_dev = {
+ .name = "powerbutton",
+ .id = -1,
+};
+
+struct platform_device olpc_lid_dev = {
+ .name = "lid",
+ .id = -1,
+};
+
+static void __init init_ebook_state(void)
+{
+ if (olpc_ec_cmd(0x2a, NULL, 0, (unsigned char *) &ebook_state, 1)) {
+ printk(KERN_WARNING "olpc-pm: failed to get EBOOK state!\n");
+ ebook_state = 0;
+ }
+ ebook_state &= 1;
+
+ /* the input layer needs to know what value to default to as well */
+ input_report_switch(ebook_inputdev, SW_TABLET_MODE, ebook_state);
+ input_sync(ebook_inputdev);
+}
+
+static void (*battery_callback)(unsigned long);
+static DEFINE_SPINLOCK(battery_callback_lock);
+
+/* propagate_events is non-NULL if run from workqueue,
+ NULL when called at init time to flush SCI queue */
+static void process_sci_queue(struct work_struct *propagate_events)
+{
+ unsigned char data = 0;
+ unsigned char battery_events = 0;
+ int ret;
+
+ do {
+ ret = olpc_ec_cmd(0x84, NULL, 0, &data, 1);
+ if (!ret) {
+ printk(KERN_DEBUG "olpc-pm: SCI 0x%x received\n",
+ data);
+
+ switch (data) {
+ case EC_SCI_SRC_EMPTY:
+ case EC_SCI_SRC_GAME:
+ case EC_SCI_SRC_WLAN:
+ /* we ignore these for now */
+ break;
+ case EC_SCI_SRC_BATERR:
+ printk(KERN_ERR "olpc-pm: Battery Management System detected an error! Remove turnip from battery slot.\n");
+ case EC_SCI_SRC_BATSOC:
+ case EC_SCI_SRC_BATTERY:
+ case EC_SCI_SRC_ACPWR:
+ battery_events |= data;
+ break;
+ case EC_SCI_SRC_EBOOK:
+ ebook_state = !ebook_state;
+ if (propagate_events) {
+ input_report_switch(ebook_inputdev,
+ SW_TABLET_MODE, ebook_state);
+ input_sync(ebook_inputdev);
+ }
+ break;
+ default:
+ printk(KERN_ERR "olpc-pm: Unknown SCI event 0x%x occurred!\n", data);
+ }
+ }
+ } while (data && !ret);
+
+ if (battery_events && battery_callback && propagate_events) {
+ void (*cbk)(unsigned long);
+
+ /* Older EC versions didn't distinguish between AC and battery
+ events */
+ if (olpc_platform_info.ecver < 0x45)
+ battery_events = EC_SCI_SRC_BATTERY | EC_SCI_SRC_ACPWR;
+
+ spin_lock(&battery_callback_lock);
+ cbk = battery_callback;
+ spin_unlock(&battery_callback_lock);
+
+ cbk(battery_events);
+ }
+
+ if (ret)
+ printk(KERN_WARNING "Failed to clear SCI queue!\n");
+}
+
+static DECLARE_WORK(sci_work, process_sci_queue);
+
+void olpc_register_battery_callback(void (*f)(unsigned long))
+{
+ spin_lock(&battery_callback_lock);
+ battery_callback = f;
+ spin_unlock(&battery_callback_lock);
+}
+EXPORT_SYMBOL_GPL(olpc_register_battery_callback);
+
+void olpc_deregister_battery_callback(void)
+{
+ spin_lock(&battery_callback_lock);
+ battery_callback = NULL;
+ spin_unlock(&battery_callback_lock);
+ cancel_work_sync(&sci_work);
+}
+EXPORT_SYMBOL_GPL(olpc_deregister_battery_callback);
+
+
+static int olpc_pm_interrupt(int irq, void *id)
+{
+ uint32_t sts, gpe = 0;
+
+ sts = inl(acpi_base + PM1_STS);
+ outl(sts | 0xFFFF, acpi_base + PM1_STS);
+
+ if (olpc_get_rev() >= OLPC_REV_B2) {
+ gpe = inl(acpi_base + PM_GPE0_STS);
+ outl(0xFFFFFFFF, acpi_base + PM_GPE0_STS);
+ }
+
+ if (sts & CS5536_PM_PWRBTN) {
+ input_report_key(pm_inputdev, KEY_POWER, 1);
+ input_sync(pm_inputdev);
+ printk(KERN_DEBUG "olpm-pm: PM_PWRBTN event received\n");
+ /* Do we need to delay this (and hence schedule_work)? */
+ input_report_key(pm_inputdev, KEY_POWER, 0);
+ input_sync(pm_inputdev);
+ }
+
+ if (gpe & GPIO_WAKEUP_EC) {
+ geode_gpio_clear(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS);
+ schedule_work(&sci_work);
+ }
+
+ if (gpe & GPIO_WAKEUP_LID) {
+ /* Disable events */
+ geode_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
+
+ /* Clear the edge */
+
+ if (olpc_lid_flag)
+ geode_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
+ else
+ geode_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
+
+ /* Clear the status too */
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);
+
+ /* The line is high when the LID is open, but SW_LID
+ * should be high when the LID is closed, so we pass the old
+ * value of olpc_lid_flag
+ */
+
+ input_report_switch(lid_inputdev, SW_LID, olpc_lid_flag);
+ input_sync(lid_inputdev);
+
+ /* Swap the status */
+ olpc_lid_flag = !olpc_lid_flag;
+
+ if (olpc_lid_flag)
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
+ else
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
+
+ /* re-enable the event */
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * For now, only support STR. We also don't support suspending on
+ * B1s, due to difficulties with the cafe FPGA.
+ */
+static int olpc_pm_state_valid(suspend_state_t pm_state)
+{
+ if (pm_state == PM_SUSPEND_MEM && olpc_rev_after(OLPC_REV_B1))
+ return 1;
+
+ return 0;
+}
+
+/* This is a catchall function for operations that just don't belong
+ * anywhere else. Later we will evaluate if these belong in the
+ * individual device drivers or the firmware.
+ * If you add something to this function, please explain yourself with
+ * a comment.
+ */
+
+extern void gxfb_flatpanel_control(int state);
+
+static u32 gpio_wakeup[2];
+static u64 irq_sources[4];
+static u64 mfgpt_irq_msr, mfgpt_nr_msr;
+
+void olpc_fixup_wakeup(void)
+{
+ u32 base = geode_gpio_base();
+ int i;
+
+ /* This clears any pending events from the status register -
+ * the firmware also does this, but its possible that it tries
+ * it too early before the key has a chance to debounce
+ */
+
+ outl((CS5536_PM_PWRBTN << 16) | 0xFFFF, acpi_base + PM1_STS);
+
+ /* Enable the flatpanel sequencing as early as possible, because
+ it takes ~64ms to resume. This probably belongs in the firmware */
+
+ //gxfb_flatpanel_control(1);
+
+ /* Restore the interrupt sources */
+ wrmsrl(MSR_PIC_YSEL_LOW, irq_sources[0]);
+ wrmsrl(MSR_PIC_ZSEL_LOW, irq_sources[1]);
+ wrmsrl(MSR_PIC_YSEL_HIGH, irq_sources[2]);
+ wrmsrl(MSR_PIC_ZSEL_HIGH, irq_sources[3]);
+
+ /* Restore the X and Y sources for GPIO */
+ outl(gpio_wakeup[0], base + GPIO_MAP_X);
+ outl(gpio_wakeup[1], base + GPIO_MAP_Y);
+
+ /* Resture the MFGPT MSRs */
+ wrmsrl(MFGPT_IRQ_MSR, mfgpt_irq_msr);
+ wrmsrl(MFGPT_NR_MSR, mfgpt_nr_msr);
+
+ for (i=0;i<2;i++) {
+ /* tell the wireless module to restart USB communication */
+ olpc_ec_cmd(0x24, NULL, 0, NULL, 0);
+ }
+}
+
+void olpc_fixup_sleep(void)
+{
+ u32 base = geode_gpio_base();
+ int i;
+
+ /* Save the X and Y sources for GPIO */
+ gpio_wakeup[0] = inl(base + GPIO_MAP_X);
+ gpio_wakeup[1] = inl(base + GPIO_MAP_Y);
+
+ /* Save the Y and Z unrestricted sources */
+
+ rdmsrl(MSR_PIC_YSEL_LOW, irq_sources[0]);
+ rdmsrl(MSR_PIC_ZSEL_LOW, irq_sources[1]);
+ rdmsrl(MSR_PIC_YSEL_HIGH, irq_sources[2]);
+ rdmsrl(MSR_PIC_ZSEL_HIGH, irq_sources[3]);
+
+ /* Turn off the MFGPT timers on the way down */
+
+ for(i = 0; i < 8; i++) {
+ u32 val = geode_mfgpt_read(i, MFGPT_REG_SETUP);
+
+ if (val & MFGPT_SETUP_SETUP) {
+ val &= ~MFGPT_SETUP_CNTEN;
+ geode_mfgpt_write(i, MFGPT_REG_SETUP, val);
+ }
+ }
+
+ /* Save the MFGPT MSRs */
+ rdmsrl(MFGPT_IRQ_MSR, mfgpt_irq_msr);
+ rdmsrl(MFGPT_NR_MSR, mfgpt_nr_msr);
+
+ if (device_may_wakeup(&olpc_powerbutton_dev.dev))
+ olpc_wakeup_mask |= CS5536_PM_PWRBTN;
+ else
+ olpc_wakeup_mask &= ~(CS5536_PM_PWRBTN);
+
+ if (device_may_wakeup(&olpc_lid_dev.dev)) {
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
+ gpio_wake_events |= GPIO_WAKEUP_LID;
+ } else {
+ geode_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
+ gpio_wake_events &= ~(GPIO_WAKEUP_LID);
+ }
+}
+
+static int olpc_pm_enter(suspend_state_t pm_state)
+{
+ /* Only STR is supported */
+ if (pm_state != PM_SUSPEND_MEM)
+ return -EINVAL;
+
+ olpc_fixup_sleep();
+
+ /* Set the GPIO wakeup bits */
+ outl(gpio_wake_events, acpi_base + PM_GPE0_EN);
+ outl(0xFFFFFFFF, acpi_base + PM_GPE0_STS);
+
+ /* Save CPU state */
+ do_olpc_suspend_lowlevel();
+
+ olpc_fixup_wakeup();
+
+ /* Restore the SCI wakeup events */
+ outl(gpio_wake_events, acpi_base + PM_GPE0_EN);
+
+ return 0;
+}
+
+int asmlinkage olpc_do_sleep(u8 sleep_state)
+{
+ void *pgd_addr = __va(read_cr3());
+ printk(KERN_ERR "olpc_do_sleep!\n"); /* this needs to remain here so
+ * that gcc doesn't optimize
+ * away our __va! */
+ /* FIXME: Set the SCI bits we want to wake up on here */
+
+ /* FIXME: Set any other SCI events that we might want here */
+
+ outl((olpc_wakeup_mask << 16) | 0xFFFF, acpi_base + PM1_STS);
+
+ /* If we are in test mode, then just return (simulate a successful
+ suspend/resume). Otherwise, if we are doing the real thing,
+ then go for the gusto */
+
+ if (olpc_pm_mode != PM_MODE_TEST) {
+ __asm__ __volatile__("movl %0,%%eax" : : "r" (pgd_addr));
+ __asm__("call *(%%edi); cld"
+ : : "D" (&ofw_bios_entry));
+ }
+
+ return 0;
+}
+
+/* This code will slowly disappear as we fixup the issues in the BIOS */
+
+static void __init olpc_fixup_bios(void)
+{
+ unsigned long hi, lo;
+
+ if (olpc_has_vsa()) {
+ /* The VSA aggressively sets up the ACPI and PM register for
+ * trapping - its not enough to force these values in the BIOS -
+ * they seem to be changed during PCI init as well.
+ */
+
+ /* Change the PM registers to decode to the DD */
+
+ rdmsr(0x510100e2, lo, hi);
+ hi |= 0x80000000;
+ wrmsr(0x510100e2, lo, hi);
+
+ /* Change the ACPI registers to decode to the DD */
+
+ rdmsr(0x510100e3, lo, hi);
+ hi |= 0x80000000;
+ wrmsr(0x510100e3, lo, hi);
+ }
+
+ /* GPIO24 controls WORK_AUX */
+
+ geode_gpio_set(OLPC_GPIO_WORKAUX, GPIO_OUTPUT_ENABLE);
+ geode_gpio_set(OLPC_GPIO_WORKAUX, GPIO_OUTPUT_AUX1);
+
+ if (olpc_get_rev() >= OLPC_REV_B2) {
+ /* GPIO10 is connected to the thermal alarm */
+ geode_gpio_set(OLPC_GPIO_THRM_ALRM, GPIO_INPUT_ENABLE);
+ geode_gpio_set(OLPC_GPIO_THRM_ALRM, GPIO_INPUT_AUX1);
+
+ /* Set up to get LID events */
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_INPUT_ENABLE);
+
+ /* Clear edge detection and event enable for now */
+ geode_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
+ geode_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
+ geode_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
+
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);
+
+ /* Set the LID to cause an PME event on group 6 */
+ geode_gpio_event_pme(OLPC_GPIO_LID, 6);
+
+ /* Set PME group 6 to fire the SCI interrupt */
+ geode_gpio_set_irq(6, sci_irq);
+ }
+
+ geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_INPUT_ENABLE);
+
+ /* Clear pending events */
+
+ geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS);
+ geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_POSITIVE_EDGE_STS);
+
+ //geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_EN);
+ geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_EVENTS_ENABLE);
+
+ /* Set the SCI to cause a PME event on group 7 */
+ geode_gpio_event_pme(OLPC_GPIO_ECSCI, 7);
+
+ /* And have group 6 also fire the SCI interrupt */
+ geode_gpio_set_irq(7, sci_irq);
+}
+
+/* This provides a control file for setting up testing of the
+ power management system. For now, there is just one setting:
+ "test" which means that we don't actually enter the power
+ off routine.
+*/
+
+static const char * const pm_states[] = {
+ [PM_MODE_NORMAL] = "normal",
+ [PM_MODE_TEST] = "test",
+};
+
+extern struct mutex pm_mutex;
+extern struct kset power_subsys;
+
+static ssize_t control_show(struct kset *s, char *buf)
+{
+ return sprintf(buf, "%s\n", pm_states[olpc_pm_mode]);
+}
+
+static ssize_t control_store(struct kset *s, const char *buf, size_t n)
+{
+ int i, len;
+ char *p;
+
+ p = memchr(buf, '\n', n);
+ len = p ? p - buf : n;
+
+ /* Grab the mutex */
+ mutex_lock(&pm_mutex);
+
+ for(i = 0; i < PM_MODE_MAX; i++) {
+ if (!strncmp(buf, pm_states[i], len)) {
+ olpc_pm_mode = i;
+ break;
+ }
+ }
+
+ mutex_unlock(&pm_mutex);
+
+ return (i == PM_MODE_MAX) ? -EINVAL : n;
+}
+
+static struct subsys_attribute control_attr = {
+ .attr = {
+ .name = "olpc-pm",
+ .mode = 0644,
+ },
+ .show = control_show,
+ .store = control_store,
+};
+
+static struct attribute * olpc_attributes[] = {
+ &control_attr.attr,
+ NULL
+};
+
+static struct attribute_group olpc_attrs = {
+ .attrs = olpc_attributes,
+};
+
+static int __init alloc_inputdevs(void)
+{
+ int ret = -ENOMEM;
+
+ pm_inputdev = input_allocate_device();
+ if (!pm_inputdev)
+ goto err;
+
+ pm_inputdev->name = "OLPC PM";
+ pm_inputdev->phys = "olpc_pm/input0";
+ set_bit(EV_KEY, pm_inputdev->evbit);
+ set_bit(KEY_POWER, pm_inputdev->keybit);
+
+ ret = input_register_device(pm_inputdev);
+ if (ret) {
+ printk(KERN_ERR "olpc-pm: failed to register PM input device: %d\n", ret);
+ goto err;
+ }
+
+ lid_inputdev = input_allocate_device();
+ if (!lid_inputdev)
+ goto err;
+
+ lid_inputdev->name = "OLPC lid switch";
+ lid_inputdev->phys = "olpc_pm/input1";
+ set_bit(EV_SW, lid_inputdev->evbit);
+ set_bit(SW_LID, lid_inputdev->swbit);
+
+ ret = input_register_device(lid_inputdev);
+ if (ret) {
+ printk(KERN_ERR "olpc-pm: failed to register lid input device: %d\n", ret);
+ goto err;
+ }
+
+ ebook_inputdev = input_allocate_device();
+ if (!ebook_inputdev)
+ goto err;
+
+ ebook_inputdev->name = "OLPC ebook switch";
+ ebook_inputdev->phys = "olpc_pm/input2";
+ set_bit(EV_SW, ebook_inputdev->evbit);
+ set_bit(SW_TABLET_MODE, ebook_inputdev->swbit);
+
+ ret = input_register_device(ebook_inputdev);
+ if (ret) {
+ printk(KERN_ERR "olpc-pm: failed to register ebook input device: %d\n", ret);
+ goto err;
+ }
+
+ return ret;
+err:
+ if (ebook_inputdev) {
+ input_unregister_device(ebook_inputdev);
+ ebook_inputdev = NULL;
+ }
+ if (lid_inputdev) {
+ input_unregister_device(lid_inputdev);
+ lid_inputdev = NULL;
+ }
+ if (pm_inputdev) {
+ input_unregister_device(pm_inputdev);
+ pm_inputdev = NULL;
+ }
+
+ return ret;
+}
+
+static int __init olpc_pm_init(void)
+{
+ uint32_t lo, hi;
+ int ret;
+ uint8_t ec_byte;
+
+ if (!machine_is_olpc())
+ return -ENODEV;
+
+ acpi_base = geode_acpi_base();
+ pms_base = geode_pms_base();
+
+ if (!acpi_base || !pms_base)
+ return -ENODEV;
+
+ ret = alloc_inputdevs();
+ if (ret)
+ return ret;
+
+ rdmsr(0x51400020, lo, hi);
+ sci_irq = (lo >> 20) & 15;
+
+ if (sci_irq) {
+ printk(KERN_INFO "SCI is mapped to IRQ %d\n", sci_irq);
+ } else {
+ /* Zero doesn't mean zero -- it means masked */
+ printk(KERN_INFO "SCI unmapped. Mapping to IRQ 3\n");
+ sci_irq = 3;
+ lo |= 0x00300000;
+ wrmsrl(0x51400020, lo);
+ }
+
+ olpc_fixup_bios();
+
+ lo = inl(pms_base + PM_FSD);
+
+ /* Lock, enable failsafe, 4 seconds */
+ outl(0xc001f400, pms_base + PM_FSD);
+
+ /* Here we set up the SCI events we're interested in during
+ * real-time. We have no sleep button, and the RTC doesn't make
+ * sense, so set up the power button
+ */
+
+ outl(inl(acpi_base) | ((CS5536_PM_PWRBTN) << 16), acpi_base);
+
+ if (olpc_get_rev() >= OLPC_REV_B2) {
+ gpio_wake_events |= GPIO_WAKEUP_LID;
+
+ /* Get the current value of the GPIO, and set up the edges */
+ olpc_lid_flag = geode_gpio_isset(OLPC_GPIO_LID, GPIO_READ_BACK);
+
+ /* Watch for the opposite edge */
+
+ if (olpc_lid_flag)
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
+ else
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
+
+ /* Enable the event */
+ geode_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
+ }
+
+ /* Set up the mask for wakeups the EC will generate SCIs on */
+
+ ret = olpc_ec_cmd(EC_READ_SCI_MASK, NULL, 0, &ec_byte, 1);
+ if (ret)
+ printk(KERN_ERR "Error getting the EC SCI mask: %d\n", ret);
+
+ /* Disable battery 1% charge wakeups */
+ ec_byte &= ~EC_SCI_SRC_BATSOC;
+
+ ret = olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0);
+ if (ret)
+ printk(KERN_ERR "Error setting the EC SCI mask: %d\n", ret);
+
+ /* Set up the EC SCI */
+
+ gpio_wake_events |= GPIO_WAKEUP_EC;
+
+ outl(gpio_wake_events, acpi_base + PM_GPE0_EN);
+ outl(0xFFFFFFFF, acpi_base + PM_GPE0_STS);
+
+ /* Select level triggered in PIC */
+
+ if (sci_irq < 8) {
+ lo = inb(0x4d0);
+ lo |= 1 << sci_irq;
+ outb(lo, 0x4d0);
+ } else {
+ lo = inb(0x4d1);
+ lo |= 1 << (sci_irq - 8);
+ outb(lo, 0x4d1);
+ }
+ /* Clear pending interrupt */
+ outl(inl(acpi_base) | 0xFFFF, acpi_base);
+ process_sci_queue(0); /* we just want to flush the queue here */
+ init_ebook_state();
+
+ /* Enable the interrupt */
+
+ ret = request_irq(sci_irq, &olpc_pm_interrupt, 0, "SCI", &acpi_base);
+
+ if (ret) {
+ printk(KERN_ERR "Error registering SCI: %d\n", ret);
+ return ret;
+ }
+
+ ofw_bios_entry.address = 0xF0000 + PAGE_OFFSET;
+ pm_set_ops(&olpc_pm_ops);
+
+ sysfs_create_group(&power_subsys.kobj, &olpc_attrs);
+
+ return 0;
+}
+
+
+#if defined (CONFIG_RTC_DRV_CMOS) || defined (CONFIG_RTC_DRV_CMOS_MODULE)
+struct resource rtc_platform_resource[2] = {
+ {
+ .flags = IORESOURCE_IO,
+ .start = RTC_PORT(0),
+ .end = RTC_PORT(0) + RTC_IO_EXTENT
+ },
+ {
+ .flags = IORESOURCE_IRQ,
+ .start = 8,
+ .end = 8,
+ },
+};
+
+
+static void rtc_wake_on(struct device *dev)
+{
+ olpc_wakeup_mask |= CS5536_PM_RTC;
+}
+
+static void rtc_wake_off(struct device *dev)
+{
+ olpc_wakeup_mask &= ~(CS5536_PM_RTC);
+}
+
+static struct cmos_rtc_board_info rtc_info = {
+ .rtc_day_alarm = 0,
+ .rtc_mon_alarm = 0,
+ .rtc_century = 0,
+ .wake_on = rtc_wake_on,
+ .wake_off = rtc_wake_off,
+};
+
+struct platform_device olpc_rtc_device = {
+ .name = "rtc_cmos",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(rtc_platform_resource),
+ .dev.platform_data = &rtc_info,
+ .resource = rtc_platform_resource,
+};
+
+static int __init olpc_platform_init(void)
+{
+ (void)platform_device_register(&olpc_rtc_device);
+ device_init_wakeup(&olpc_rtc_device.dev, 1);
+
+ (void)platform_device_register(&olpc_powerbutton_dev);
+ device_init_wakeup(&olpc_powerbutton_dev.dev, 1);
+
+ (void)platform_device_register(&olpc_lid_dev);
+ device_init_wakeup(&olpc_lid_dev.dev, 1);
+
+ return 0;
+}
+arch_initcall(olpc_platform_init);
+#endif /* CONFIG_RTC_DRV_CMOS */
+
+static void olpc_pm_exit(void)
+{
+ /* Clear any pending events, and disable them */
+ outl(0xFFFF, acpi_base+2);
+
+ free_irq(sci_irq, &acpi_base);
+ input_unregister_device(pm_inputdev);
+ input_unregister_device(lid_inputdev);
+ input_unregister_device(ebook_inputdev);
+}
+
+static struct pm_ops olpc_pm_ops = {
+ .valid = olpc_pm_state_valid,
+ .enter = olpc_pm_enter,
+};
+
+module_init(olpc_pm_init);
+module_exit(olpc_pm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("AMD Geode power management for OLPC CL1");