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