1a0f30f59SDaniel Drake /* 2a0f30f59SDaniel Drake * Support for OLPC XO-1.5 System Control Interrupts (SCI) 3a0f30f59SDaniel Drake * 4a0f30f59SDaniel Drake * Copyright (C) 2009-2010 One Laptop per Child 5a0f30f59SDaniel Drake * 6a0f30f59SDaniel Drake * This program is free software; you can redistribute it and/or modify 7a0f30f59SDaniel Drake * it under the terms of the GNU General Public License as published by 8a0f30f59SDaniel Drake * the Free Software Foundation; either version 2 of the License, or 9a0f30f59SDaniel Drake * (at your option) any later version. 10a0f30f59SDaniel Drake */ 11a0f30f59SDaniel Drake 12a0f30f59SDaniel Drake #include <linux/device.h> 13a0f30f59SDaniel Drake #include <linux/slab.h> 14a0f30f59SDaniel Drake #include <linux/workqueue.h> 15a0f30f59SDaniel Drake #include <linux/power_supply.h> 163bf9428fSAndres Salomon #include <linux/olpc-ec.h> 17a0f30f59SDaniel Drake 188b48463fSLv Zheng #include <linux/acpi.h> 19a0f30f59SDaniel Drake #include <asm/olpc.h> 20a0f30f59SDaniel Drake 21a0f30f59SDaniel Drake #define DRV_NAME "olpc-xo15-sci" 22a0f30f59SDaniel Drake #define PFX DRV_NAME ": " 23a0f30f59SDaniel Drake #define XO15_SCI_CLASS DRV_NAME 24a0f30f59SDaniel Drake #define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI" 25a0f30f59SDaniel Drake 26a0f30f59SDaniel Drake static unsigned long xo15_sci_gpe; 27d1f42e31SDaniel Drake static bool lid_wake_on_close; 28d1f42e31SDaniel Drake 29d1f42e31SDaniel Drake /* 30d1f42e31SDaniel Drake * The normal ACPI LID wakeup behavior is wake-on-open, but not 31d1f42e31SDaniel Drake * wake-on-close. This is implemented as standard by the XO-1.5 DSDT. 32d1f42e31SDaniel Drake * 33d1f42e31SDaniel Drake * We provide here a sysfs attribute that will additionally enable 34d1f42e31SDaniel Drake * wake-on-close behavior. This is useful (e.g.) when we oportunistically 35d1f42e31SDaniel Drake * suspend with the display running; if the lid is then closed, we want to 36d1f42e31SDaniel Drake * wake up to turn the display off. 37d1f42e31SDaniel Drake * 38d1f42e31SDaniel Drake * This is controlled through a custom method in the XO-1.5 DSDT. 39d1f42e31SDaniel Drake */ 40d1f42e31SDaniel Drake static int set_lid_wake_behavior(bool wake_on_close) 41d1f42e31SDaniel Drake { 42d1f42e31SDaniel Drake acpi_status status; 43d1f42e31SDaniel Drake 44b408a054SZhang Rui status = acpi_execute_simple_method(NULL, "\\_SB.PCI0.LID.LIDW", wake_on_close); 45d1f42e31SDaniel Drake if (ACPI_FAILURE(status)) { 46d1f42e31SDaniel Drake pr_warning(PFX "failed to set lid behavior\n"); 47d1f42e31SDaniel Drake return 1; 48d1f42e31SDaniel Drake } 49d1f42e31SDaniel Drake 50d1f42e31SDaniel Drake lid_wake_on_close = wake_on_close; 51d1f42e31SDaniel Drake 52d1f42e31SDaniel Drake return 0; 53d1f42e31SDaniel Drake } 54d1f42e31SDaniel Drake 55d1f42e31SDaniel Drake static ssize_t 56d1f42e31SDaniel Drake lid_wake_on_close_show(struct kobject *s, struct kobj_attribute *attr, char *buf) 57d1f42e31SDaniel Drake { 58d1f42e31SDaniel Drake return sprintf(buf, "%u\n", lid_wake_on_close); 59d1f42e31SDaniel Drake } 60d1f42e31SDaniel Drake 61d1f42e31SDaniel Drake static ssize_t lid_wake_on_close_store(struct kobject *s, 62d1f42e31SDaniel Drake struct kobj_attribute *attr, 63d1f42e31SDaniel Drake const char *buf, size_t n) 64d1f42e31SDaniel Drake { 65d1f42e31SDaniel Drake unsigned int val; 66d1f42e31SDaniel Drake 67d1f42e31SDaniel Drake if (sscanf(buf, "%u", &val) != 1) 68d1f42e31SDaniel Drake return -EINVAL; 69d1f42e31SDaniel Drake 70d1f42e31SDaniel Drake set_lid_wake_behavior(!!val); 71d1f42e31SDaniel Drake 72d1f42e31SDaniel Drake return n; 73d1f42e31SDaniel Drake } 74d1f42e31SDaniel Drake 75d1f42e31SDaniel Drake static struct kobj_attribute lid_wake_on_close_attr = 76d1f42e31SDaniel Drake __ATTR(lid_wake_on_close, 0644, 77d1f42e31SDaniel Drake lid_wake_on_close_show, 78d1f42e31SDaniel Drake lid_wake_on_close_store); 79a0f30f59SDaniel Drake 80a0f30f59SDaniel Drake static void battery_status_changed(void) 81a0f30f59SDaniel Drake { 82a0f30f59SDaniel Drake struct power_supply *psy = power_supply_get_by_name("olpc-battery"); 83a0f30f59SDaniel Drake 84a0f30f59SDaniel Drake if (psy) { 85a0f30f59SDaniel Drake power_supply_changed(psy); 8667273a1bSKrzysztof Kozlowski power_supply_put(psy); 87a0f30f59SDaniel Drake } 88a0f30f59SDaniel Drake } 89a0f30f59SDaniel Drake 90a0f30f59SDaniel Drake static void ac_status_changed(void) 91a0f30f59SDaniel Drake { 92a0f30f59SDaniel Drake struct power_supply *psy = power_supply_get_by_name("olpc-ac"); 93a0f30f59SDaniel Drake 94a0f30f59SDaniel Drake if (psy) { 95a0f30f59SDaniel Drake power_supply_changed(psy); 9667273a1bSKrzysztof Kozlowski power_supply_put(psy); 97a0f30f59SDaniel Drake } 98a0f30f59SDaniel Drake } 99a0f30f59SDaniel Drake 100a0f30f59SDaniel Drake static void process_sci_queue(void) 101a0f30f59SDaniel Drake { 102a0f30f59SDaniel Drake u16 data; 103a0f30f59SDaniel Drake int r; 104a0f30f59SDaniel Drake 105a0f30f59SDaniel Drake do { 106a0f30f59SDaniel Drake r = olpc_ec_sci_query(&data); 107a0f30f59SDaniel Drake if (r || !data) 108a0f30f59SDaniel Drake break; 109a0f30f59SDaniel Drake 110a0f30f59SDaniel Drake pr_debug(PFX "SCI 0x%x received\n", data); 111a0f30f59SDaniel Drake 112a0f30f59SDaniel Drake switch (data) { 113a0f30f59SDaniel Drake case EC_SCI_SRC_BATERR: 114a0f30f59SDaniel Drake case EC_SCI_SRC_BATSOC: 115a0f30f59SDaniel Drake case EC_SCI_SRC_BATTERY: 116a0f30f59SDaniel Drake case EC_SCI_SRC_BATCRIT: 117a0f30f59SDaniel Drake battery_status_changed(); 118a0f30f59SDaniel Drake break; 119a0f30f59SDaniel Drake case EC_SCI_SRC_ACPWR: 120a0f30f59SDaniel Drake ac_status_changed(); 121a0f30f59SDaniel Drake break; 122a0f30f59SDaniel Drake } 123a0f30f59SDaniel Drake } while (data); 124a0f30f59SDaniel Drake 125a0f30f59SDaniel Drake if (r) 126a0f30f59SDaniel Drake pr_err(PFX "Failed to clear SCI queue"); 127a0f30f59SDaniel Drake } 128a0f30f59SDaniel Drake 129a0f30f59SDaniel Drake static void process_sci_queue_work(struct work_struct *work) 130a0f30f59SDaniel Drake { 131a0f30f59SDaniel Drake process_sci_queue(); 132a0f30f59SDaniel Drake } 133a0f30f59SDaniel Drake 134a0f30f59SDaniel Drake static DECLARE_WORK(sci_work, process_sci_queue_work); 135a0f30f59SDaniel Drake 136a0f30f59SDaniel Drake static u32 xo15_sci_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context) 137a0f30f59SDaniel Drake { 138a0f30f59SDaniel Drake schedule_work(&sci_work); 139a0f30f59SDaniel Drake return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE; 140a0f30f59SDaniel Drake } 141a0f30f59SDaniel Drake 142a0f30f59SDaniel Drake static int xo15_sci_add(struct acpi_device *device) 143a0f30f59SDaniel Drake { 144a0f30f59SDaniel Drake unsigned long long tmp; 145a0f30f59SDaniel Drake acpi_status status; 146d1f42e31SDaniel Drake int r; 147a0f30f59SDaniel Drake 148a0f30f59SDaniel Drake if (!device) 149a0f30f59SDaniel Drake return -EINVAL; 150a0f30f59SDaniel Drake 151a0f30f59SDaniel Drake strcpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME); 152a0f30f59SDaniel Drake strcpy(acpi_device_class(device), XO15_SCI_CLASS); 153a0f30f59SDaniel Drake 154a0f30f59SDaniel Drake /* Get GPE bit assignment (EC events). */ 155a0f30f59SDaniel Drake status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp); 156a0f30f59SDaniel Drake if (ACPI_FAILURE(status)) 157a0f30f59SDaniel Drake return -EINVAL; 158a0f30f59SDaniel Drake 159a0f30f59SDaniel Drake xo15_sci_gpe = tmp; 160a0f30f59SDaniel Drake status = acpi_install_gpe_handler(NULL, xo15_sci_gpe, 161a0f30f59SDaniel Drake ACPI_GPE_EDGE_TRIGGERED, 162a0f30f59SDaniel Drake xo15_sci_gpe_handler, device); 163a0f30f59SDaniel Drake if (ACPI_FAILURE(status)) 164a0f30f59SDaniel Drake return -ENODEV; 165a0f30f59SDaniel Drake 166a0f30f59SDaniel Drake dev_info(&device->dev, "Initialized, GPE = 0x%lx\n", xo15_sci_gpe); 167a0f30f59SDaniel Drake 168d1f42e31SDaniel Drake r = sysfs_create_file(&device->dev.kobj, &lid_wake_on_close_attr.attr); 169d1f42e31SDaniel Drake if (r) 170d1f42e31SDaniel Drake goto err_sysfs; 171d1f42e31SDaniel Drake 172a0f30f59SDaniel Drake /* Flush queue, and enable all SCI events */ 173a0f30f59SDaniel Drake process_sci_queue(); 174a0f30f59SDaniel Drake olpc_ec_mask_write(EC_SCI_SRC_ALL); 175a0f30f59SDaniel Drake 176a0f30f59SDaniel Drake acpi_enable_gpe(NULL, xo15_sci_gpe); 177a0f30f59SDaniel Drake 178a0f30f59SDaniel Drake /* Enable wake-on-EC */ 179a0f30f59SDaniel Drake if (device->wakeup.flags.valid) 18007d5b38eSDaniel Drake device_init_wakeup(&device->dev, true); 181a0f30f59SDaniel Drake 182a0f30f59SDaniel Drake return 0; 183d1f42e31SDaniel Drake 184d1f42e31SDaniel Drake err_sysfs: 185d1f42e31SDaniel Drake acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler); 186d1f42e31SDaniel Drake cancel_work_sync(&sci_work); 187d1f42e31SDaniel Drake return r; 188a0f30f59SDaniel Drake } 189a0f30f59SDaniel Drake 19051fac838SRafael J. Wysocki static int xo15_sci_remove(struct acpi_device *device) 191a0f30f59SDaniel Drake { 192a0f30f59SDaniel Drake acpi_disable_gpe(NULL, xo15_sci_gpe); 193a0f30f59SDaniel Drake acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler); 194a0f30f59SDaniel Drake cancel_work_sync(&sci_work); 195d1f42e31SDaniel Drake sysfs_remove_file(&device->dev.kobj, &lid_wake_on_close_attr.attr); 196a0f30f59SDaniel Drake return 0; 197a0f30f59SDaniel Drake } 198a0f30f59SDaniel Drake 19918468843SRafael J. Wysocki static int xo15_sci_resume(struct device *dev) 200a0f30f59SDaniel Drake { 201a0f30f59SDaniel Drake /* Enable all EC events */ 202a0f30f59SDaniel Drake olpc_ec_mask_write(EC_SCI_SRC_ALL); 203a0f30f59SDaniel Drake 204a0f30f59SDaniel Drake /* Power/battery status might have changed */ 205a0f30f59SDaniel Drake battery_status_changed(); 206a0f30f59SDaniel Drake ac_status_changed(); 207a0f30f59SDaniel Drake 208a0f30f59SDaniel Drake return 0; 209a0f30f59SDaniel Drake } 210a0f30f59SDaniel Drake 21118468843SRafael J. Wysocki static SIMPLE_DEV_PM_OPS(xo15_sci_pm, NULL, xo15_sci_resume); 21218468843SRafael J. Wysocki 213a0f30f59SDaniel Drake static const struct acpi_device_id xo15_sci_device_ids[] = { 214a0f30f59SDaniel Drake {"XO15EC", 0}, 215a0f30f59SDaniel Drake {"", 0}, 216a0f30f59SDaniel Drake }; 217a0f30f59SDaniel Drake 218a0f30f59SDaniel Drake static struct acpi_driver xo15_sci_drv = { 219a0f30f59SDaniel Drake .name = DRV_NAME, 220a0f30f59SDaniel Drake .class = XO15_SCI_CLASS, 221a0f30f59SDaniel Drake .ids = xo15_sci_device_ids, 222a0f30f59SDaniel Drake .ops = { 223a0f30f59SDaniel Drake .add = xo15_sci_add, 224a0f30f59SDaniel Drake .remove = xo15_sci_remove, 225a0f30f59SDaniel Drake }, 22618468843SRafael J. Wysocki .drv.pm = &xo15_sci_pm, 227a0f30f59SDaniel Drake }; 228a0f30f59SDaniel Drake 229a0f30f59SDaniel Drake static int __init xo15_sci_init(void) 230a0f30f59SDaniel Drake { 231a0f30f59SDaniel Drake return acpi_bus_register_driver(&xo15_sci_drv); 232a0f30f59SDaniel Drake } 233a0f30f59SDaniel Drake device_initcall(xo15_sci_init); 234