ac.c revision cc8ef52707341e67a12067d6ead991d56ea017ca
1/* 2 * acpi_ac.c - ACPI AC Adapter Driver ($Revision: 27 $) 3 * 4 * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> 5 * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> 6 * 7 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or (at 12 * your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, but 15 * WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, write to the Free Software Foundation, Inc., 21 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 22 * 23 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 */ 25 26#include <linux/kernel.h> 27#include <linux/module.h> 28#include <linux/slab.h> 29#include <linux/init.h> 30#include <linux/types.h> 31#include <linux/dmi.h> 32#include <linux/delay.h> 33#ifdef CONFIG_ACPI_PROCFS_POWER 34#include <linux/proc_fs.h> 35#include <linux/seq_file.h> 36#endif 37#include <linux/platform_device.h> 38#include <linux/power_supply.h> 39#include <acpi/acpi_bus.h> 40#include <acpi/acpi_drivers.h> 41 42#define PREFIX "ACPI: " 43 44#define ACPI_AC_CLASS "ac_adapter" 45#define ACPI_AC_DEVICE_NAME "AC Adapter" 46#define ACPI_AC_FILE_STATE "state" 47#define ACPI_AC_NOTIFY_STATUS 0x80 48#define ACPI_AC_STATUS_OFFLINE 0x00 49#define ACPI_AC_STATUS_ONLINE 0x01 50#define ACPI_AC_STATUS_UNKNOWN 0xFF 51 52#define _COMPONENT ACPI_AC_COMPONENT 53ACPI_MODULE_NAME("ac"); 54 55MODULE_AUTHOR("Paul Diefenbaugh"); 56MODULE_DESCRIPTION("ACPI AC Adapter Driver"); 57MODULE_LICENSE("GPL"); 58 59#ifdef CONFIG_ACPI_PROCFS_POWER 60extern struct proc_dir_entry *acpi_lock_ac_dir(void); 61extern void *acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir); 62static int acpi_ac_open_fs(struct inode *inode, struct file *file); 63#endif 64 65static int ac_sleep_before_get_state_ms; 66 67struct acpi_ac { 68 struct power_supply charger; 69 struct acpi_device *adev; 70 struct platform_device *pdev; 71 unsigned long long state; 72}; 73 74#define to_acpi_ac(x) container_of(x, struct acpi_ac, charger) 75 76#ifdef CONFIG_ACPI_PROCFS_POWER 77static const struct file_operations acpi_ac_fops = { 78 .owner = THIS_MODULE, 79 .open = acpi_ac_open_fs, 80 .read = seq_read, 81 .llseek = seq_lseek, 82 .release = single_release, 83}; 84#endif 85 86/* -------------------------------------------------------------------------- 87 AC Adapter Management 88 -------------------------------------------------------------------------- */ 89 90static int acpi_ac_get_state(struct acpi_ac *ac) 91{ 92 acpi_status status; 93 94 status = acpi_evaluate_integer(ac->adev->handle, "_PSR", NULL, 95 &ac->state); 96 if (ACPI_FAILURE(status)) { 97 ACPI_EXCEPTION((AE_INFO, status, 98 "Error reading AC Adapter state")); 99 ac->state = ACPI_AC_STATUS_UNKNOWN; 100 return -ENODEV; 101 } 102 103 return 0; 104} 105 106/* -------------------------------------------------------------------------- 107 sysfs I/F 108 -------------------------------------------------------------------------- */ 109static int get_ac_property(struct power_supply *psy, 110 enum power_supply_property psp, 111 union power_supply_propval *val) 112{ 113 struct acpi_ac *ac = to_acpi_ac(psy); 114 115 if (!ac) 116 return -ENODEV; 117 118 if (acpi_ac_get_state(ac)) 119 return -ENODEV; 120 121 switch (psp) { 122 case POWER_SUPPLY_PROP_ONLINE: 123 val->intval = ac->state; 124 break; 125 default: 126 return -EINVAL; 127 } 128 return 0; 129} 130 131static enum power_supply_property ac_props[] = { 132 POWER_SUPPLY_PROP_ONLINE, 133}; 134 135#ifdef CONFIG_ACPI_PROCFS_POWER 136/* -------------------------------------------------------------------------- 137 FS Interface (/proc) 138 -------------------------------------------------------------------------- */ 139 140static struct proc_dir_entry *acpi_ac_dir; 141 142static int acpi_ac_seq_show(struct seq_file *seq, void *offset) 143{ 144 struct acpi_ac *ac = seq->private; 145 146 147 if (!ac) 148 return 0; 149 150 if (acpi_ac_get_state(ac)) { 151 seq_puts(seq, "ERROR: Unable to read AC Adapter state\n"); 152 return 0; 153 } 154 155 seq_puts(seq, "state: "); 156 switch (ac->state) { 157 case ACPI_AC_STATUS_OFFLINE: 158 seq_puts(seq, "off-line\n"); 159 break; 160 case ACPI_AC_STATUS_ONLINE: 161 seq_puts(seq, "on-line\n"); 162 break; 163 default: 164 seq_puts(seq, "unknown\n"); 165 break; 166 } 167 168 return 0; 169} 170 171static int acpi_ac_open_fs(struct inode *inode, struct file *file) 172{ 173 return single_open(file, acpi_ac_seq_show, PDE_DATA(inode)); 174} 175 176static int acpi_ac_add_fs(struct acpi_ac *ac) 177{ 178 struct proc_dir_entry *entry = NULL; 179 180 printk(KERN_WARNING PREFIX "Deprecated procfs I/F for AC is loaded," 181 " please retry with CONFIG_ACPI_PROCFS_POWER cleared\n"); 182 if (!acpi_device_dir(ac->adev)) { 183 acpi_device_dir(ac->adev) = 184 proc_mkdir(acpi_device_bid(ac->adev), acpi_ac_dir); 185 if (!acpi_device_dir(ac->adev)) 186 return -ENODEV; 187 } 188 189 /* 'state' [R] */ 190 entry = proc_create_data(ACPI_AC_FILE_STATE, 191 S_IRUGO, acpi_device_dir(ac->adev), 192 &acpi_ac_fops, ac); 193 if (!entry) 194 return -ENODEV; 195 return 0; 196} 197 198static int acpi_ac_remove_fs(struct acpi_ac *ac) 199{ 200 201 if (acpi_device_dir(ac->adev)) { 202 remove_proc_entry(ACPI_AC_FILE_STATE, 203 acpi_device_dir(ac->adev)); 204 remove_proc_entry(acpi_device_bid(ac->adev), acpi_ac_dir); 205 acpi_device_dir(ac->adev) = NULL; 206 } 207 208 return 0; 209} 210#endif 211 212/* -------------------------------------------------------------------------- 213 Driver Model 214 -------------------------------------------------------------------------- */ 215 216static void acpi_ac_notify_handler(acpi_handle handle, u32 event, void *data) 217{ 218 struct acpi_ac *ac = data; 219 220 if (!ac) 221 return; 222 223 switch (event) { 224 default: 225 ACPI_DEBUG_PRINT((ACPI_DB_INFO, 226 "Unsupported event [0x%x]\n", event)); 227 case ACPI_AC_NOTIFY_STATUS: 228 case ACPI_NOTIFY_BUS_CHECK: 229 case ACPI_NOTIFY_DEVICE_CHECK: 230 /* 231 * A buggy BIOS may notify AC first and then sleep for 232 * a specific time before doing actual operations in the 233 * EC event handler (_Qxx). This will cause the AC state 234 * reported by the ACPI event to be incorrect, so wait for a 235 * specific time for the EC event handler to make progress. 236 */ 237 if (ac_sleep_before_get_state_ms > 0) 238 msleep(ac_sleep_before_get_state_ms); 239 240 acpi_ac_get_state(ac); 241 acpi_bus_generate_netlink_event(ac->adev->pnp.device_class, 242 dev_name(&ac->pdev->dev), 243 event, (u32) ac->state); 244 acpi_notifier_call_chain(ac->adev, event, (u32) ac->state); 245 kobject_uevent(&ac->charger.dev->kobj, KOBJ_CHANGE); 246 } 247 248 return; 249} 250 251static int thinkpad_e530_quirk(const struct dmi_system_id *d) 252{ 253 ac_sleep_before_get_state_ms = 1000; 254 return 0; 255} 256 257static struct dmi_system_id ac_dmi_table[] = { 258 { 259 .callback = thinkpad_e530_quirk, 260 .ident = "thinkpad e530", 261 .matches = { 262 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 263 DMI_MATCH(DMI_PRODUCT_NAME, "32597CG"), 264 }, 265 }, 266 {}, 267}; 268 269static int acpi_ac_probe(struct platform_device *pdev) 270{ 271 int result = 0; 272 struct acpi_ac *ac = NULL; 273 struct acpi_device *adev; 274 275 if (!pdev) 276 return -EINVAL; 277 278 result = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); 279 if (result) 280 return -ENODEV; 281 282 ac = kzalloc(sizeof(struct acpi_ac), GFP_KERNEL); 283 if (!ac) 284 return -ENOMEM; 285 286 strcpy(acpi_device_name(adev), ACPI_AC_DEVICE_NAME); 287 strcpy(acpi_device_class(adev), ACPI_AC_CLASS); 288 ac->adev = adev; 289 ac->pdev = pdev; 290 platform_set_drvdata(pdev, ac); 291 292 result = acpi_ac_get_state(ac); 293 if (result) 294 goto end; 295 296#ifdef CONFIG_ACPI_PROCFS_POWER 297 result = acpi_ac_add_fs(ac); 298 if (result) 299 goto end; 300#endif 301 ac->charger.name = acpi_device_bid(adev); 302 ac->charger.type = POWER_SUPPLY_TYPE_MAINS; 303 ac->charger.properties = ac_props; 304 ac->charger.num_properties = ARRAY_SIZE(ac_props); 305 ac->charger.get_property = get_ac_property; 306 result = power_supply_register(&pdev->dev, &ac->charger); 307 if (result) 308 goto end; 309 310 result = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), 311 ACPI_DEVICE_NOTIFY, acpi_ac_notify_handler, ac); 312 if (result) { 313 power_supply_unregister(&ac->charger); 314 goto end; 315 } 316 printk(KERN_INFO PREFIX "%s [%s] (%s)\n", 317 acpi_device_name(adev), acpi_device_bid(adev), 318 ac->state ? "on-line" : "off-line"); 319 320 end: 321 if (result) { 322#ifdef CONFIG_ACPI_PROCFS_POWER 323 acpi_ac_remove_fs(ac); 324#endif 325 kfree(ac); 326 } 327 328 dmi_check_system(ac_dmi_table); 329 return result; 330} 331 332#ifdef CONFIG_PM_SLEEP 333static int acpi_ac_resume(struct device *dev) 334{ 335 struct acpi_ac *ac; 336 unsigned old_state; 337 338 if (!dev) 339 return -EINVAL; 340 341 ac = platform_get_drvdata(to_platform_device(dev)); 342 if (!ac) 343 return -EINVAL; 344 345 old_state = ac->state; 346 if (acpi_ac_get_state(ac)) 347 return 0; 348 if (old_state != ac->state) 349 kobject_uevent(&ac->charger.dev->kobj, KOBJ_CHANGE); 350 return 0; 351} 352#endif 353static SIMPLE_DEV_PM_OPS(acpi_ac_pm_ops, NULL, acpi_ac_resume); 354 355static int acpi_ac_remove(struct platform_device *pdev) 356{ 357 struct acpi_ac *ac; 358 359 if (!pdev) 360 return -EINVAL; 361 362 acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), 363 ACPI_DEVICE_NOTIFY, acpi_ac_notify_handler); 364 365 ac = platform_get_drvdata(pdev); 366 if (ac->charger.dev) 367 power_supply_unregister(&ac->charger); 368 369#ifdef CONFIG_ACPI_PROCFS_POWER 370 acpi_ac_remove_fs(ac); 371#endif 372 373 kfree(ac); 374 375 return 0; 376} 377 378static const struct acpi_device_id acpi_ac_match[] = { 379 { "ACPI0003", 0 }, 380 { } 381}; 382MODULE_DEVICE_TABLE(acpi, acpi_ac_match); 383 384static struct platform_driver acpi_ac_driver = { 385 .probe = acpi_ac_probe, 386 .remove = acpi_ac_remove, 387 .driver = { 388 .name = "acpi-ac", 389 .owner = THIS_MODULE, 390 .pm = &acpi_ac_pm_ops, 391 .acpi_match_table = ACPI_PTR(acpi_ac_match), 392 }, 393}; 394 395static int __init acpi_ac_init(void) 396{ 397 int result; 398 399 if (acpi_disabled) 400 return -ENODEV; 401 402#ifdef CONFIG_ACPI_PROCFS_POWER 403 acpi_ac_dir = acpi_lock_ac_dir(); 404 if (!acpi_ac_dir) 405 return -ENODEV; 406#endif 407 408 result = platform_driver_register(&acpi_ac_driver); 409 if (result < 0) { 410#ifdef CONFIG_ACPI_PROCFS_POWER 411 acpi_unlock_ac_dir(acpi_ac_dir); 412#endif 413 return -ENODEV; 414 } 415 416 return 0; 417} 418 419static void __exit acpi_ac_exit(void) 420{ 421 platform_driver_unregister(&acpi_ac_driver); 422#ifdef CONFIG_ACPI_PROCFS_POWER 423 acpi_unlock_ac_dir(acpi_ac_dir); 424#endif 425} 426module_init(acpi_ac_init); 427module_exit(acpi_ac_exit); 428