diff options
Diffstat (limited to 'drivers/pnp/pnpacpi/core.c')
-rw-r--r-- | drivers/pnp/pnpacpi/core.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/drivers/pnp/pnpacpi/core.c b/drivers/pnp/pnpacpi/core.c new file mode 100644 index 00000000000..8655dd2e5b8 --- /dev/null +++ b/drivers/pnp/pnpacpi/core.c @@ -0,0 +1,269 @@ +/* + * pnpacpi -- PnP ACPI driver + * + * Copyright (c) 2004 Matthieu Castet <castet.matthieu@free.fr> + * Copyright (c) 2004 Li Shaohua <shaohua.li@intel.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/acpi.h> +#include <linux/pnp.h> +#include <acpi/acpi_bus.h> +#include "pnpacpi.h" + +static int num = 0; + +static char __initdata excluded_id_list[] = + "PNP0C0A," /* Battery */ + "PNP0C0C,PNP0C0E,PNP0C0D," /* Button */ + "PNP0C09," /* EC */ + "PNP0C0B," /* Fan */ + "PNP0A03," /* PCI root */ + "PNP0C0F," /* Link device */ + "PNP0000," /* PIC */ + "PNP0100," /* Timer */ + ; +static inline int is_exclusive_device(struct acpi_device *dev) +{ + return (!acpi_match_ids(dev, excluded_id_list)); +} + +void *pnpacpi_kmalloc(size_t size, int f) +{ + void *p = kmalloc(size, f); + if (p) + memset(p, 0, size); + return p; +} + +/* + * Compatible Device IDs + */ +#define TEST_HEX(c) \ + if (!(('0' <= (c) && (c) <= '9') || ('A' <= (c) && (c) <= 'F'))) \ + return 0 +#define TEST_ALPHA(c) \ + if (!('@' <= (c) || (c) <= 'Z')) \ + return 0 +static int __init ispnpidacpi(char *id) +{ + TEST_ALPHA(id[0]); + TEST_ALPHA(id[1]); + TEST_ALPHA(id[2]); + TEST_HEX(id[3]); + TEST_HEX(id[4]); + TEST_HEX(id[5]); + TEST_HEX(id[6]); + if (id[7] != '\0') + return 0; + return 1; +} + +static void __init pnpidacpi_to_pnpid(char *id, char *str) +{ + str[0] = id[0]; + str[1] = id[1]; + str[2] = id[2]; + str[3] = tolower(id[3]); + str[4] = tolower(id[4]); + str[5] = tolower(id[5]); + str[6] = tolower(id[6]); + str[7] = '\0'; +} + +static int pnpacpi_get_resources(struct pnp_dev * dev, struct pnp_resource_table * res) +{ + acpi_status status; + status = pnpacpi_parse_allocated_resource((acpi_handle)dev->data, + &dev->res); + return ACPI_FAILURE(status) ? -ENODEV : 0; +} + +static int pnpacpi_set_resources(struct pnp_dev * dev, struct pnp_resource_table * res) +{ + acpi_handle handle = dev->data; + struct acpi_buffer buffer; + int ret = 0; + acpi_status status; + + ret = pnpacpi_build_resource_template(handle, &buffer); + if (ret) + return ret; + ret = pnpacpi_encode_resources(res, &buffer); + if (ret) { + kfree(buffer.pointer); + return ret; + } + status = acpi_set_current_resources(handle, &buffer); + if (ACPI_FAILURE(status)) + ret = -EINVAL; + kfree(buffer.pointer); + return ret; +} + +static int pnpacpi_disable_resources(struct pnp_dev *dev) +{ + acpi_status status; + + /* acpi_unregister_gsi(pnp_irq(dev, 0)); */ + status = acpi_evaluate_object((acpi_handle)dev->data, + "_DIS", NULL, NULL); + return ACPI_FAILURE(status) ? -ENODEV : 0; +} + +struct pnp_protocol pnpacpi_protocol = { + .name = "Plug and Play ACPI", + .get = pnpacpi_get_resources, + .set = pnpacpi_set_resources, + .disable = pnpacpi_disable_resources, +}; + +static int __init pnpacpi_add_device(struct acpi_device *device) +{ + acpi_handle temp = NULL; + acpi_status status; + struct pnp_id *dev_id; + struct pnp_dev *dev; + + if (!ispnpidacpi(acpi_device_hid(device)) || + is_exclusive_device(device)) + return 0; + + pnp_dbg("ACPI device : hid %s", acpi_device_hid(device)); + dev = pnpacpi_kmalloc(sizeof(struct pnp_dev), GFP_KERNEL); + if (!dev) { + pnp_err("Out of memory"); + return -ENOMEM; + } + dev->data = device->handle; + /* .enabled means if the device can decode the resources */ + dev->active = device->status.enabled; + status = acpi_get_handle(device->handle, "_SRS", &temp); + if (ACPI_SUCCESS(status)) + dev->capabilities |= PNP_CONFIGURABLE; + dev->capabilities |= PNP_READ; + if (device->flags.dynamic_status) + dev->capabilities |= PNP_WRITE; + if (device->flags.removable) + dev->capabilities |= PNP_REMOVABLE; + status = acpi_get_handle(device->handle, "_DIS", &temp); + if (ACPI_SUCCESS(status)) + dev->capabilities |= PNP_DISABLE; + + dev->protocol = &pnpacpi_protocol; + + if (strlen(acpi_device_name(device))) + strncpy(dev->name, acpi_device_name(device), sizeof(dev->name)); + else + strncpy(dev->name, acpi_device_bid(device), sizeof(dev->name)); + + dev->number = num; + + /* set the initial values for the PnP device */ + dev_id = pnpacpi_kmalloc(sizeof(struct pnp_id), GFP_KERNEL); + if (!dev_id) + goto err; + pnpidacpi_to_pnpid(acpi_device_hid(device), dev_id->id); + pnp_add_id(dev_id, dev); + + if(dev->active) { + /* parse allocated resource */ + status = pnpacpi_parse_allocated_resource(device->handle, &dev->res); + if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) { + pnp_err("PnPACPI: METHOD_NAME__CRS failure for %s", dev_id->id); + goto err1; + } + } + + if(dev->capabilities & PNP_CONFIGURABLE) { + status = pnpacpi_parse_resource_option_data(device->handle, + dev); + if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) { + pnp_err("PnPACPI: METHOD_NAME__PRS failure for %s", dev_id->id); + goto err1; + } + } + + /* parse compatible ids */ + if (device->flags.compatible_ids) { + struct acpi_compatible_id_list *cid_list = device->pnp.cid_list; + int i; + + for (i = 0; i < cid_list->count; i++) { + if (!ispnpidacpi(cid_list->id[i].value)) + continue; + dev_id = pnpacpi_kmalloc(sizeof(struct pnp_id), + GFP_KERNEL); + if (!dev_id) + continue; + + pnpidacpi_to_pnpid(cid_list->id[i].value, dev_id->id); + pnp_add_id(dev_id, dev); + } + } + + /* clear out the damaged flags */ + if (!dev->active) + pnp_init_resource_table(&dev->res); + pnp_add_device(dev); + num ++; + + return AE_OK; +err1: + kfree(dev_id); +err: + kfree(dev); + return -EINVAL; +} + +static acpi_status __init pnpacpi_add_device_handler(acpi_handle handle, + u32 lvl, void *context, void **rv) +{ + struct acpi_device *device; + + if (!acpi_bus_get_device(handle, &device)) + pnpacpi_add_device(device); + else + return AE_CTRL_DEPTH; + return AE_OK; +} + +int pnpacpi_disabled __initdata; +int __init pnpacpi_init(void) +{ + if (acpi_disabled || pnpacpi_disabled) { + pnp_info("PnP ACPI: disabled"); + return 0; + } + pnp_info("PnP ACPI init"); + pnp_register_protocol(&pnpacpi_protocol); + acpi_get_devices(NULL, pnpacpi_add_device_handler, NULL, NULL); + pnp_info("PnP ACPI: found %d devices", num); + return 0; +} +subsys_initcall(pnpacpi_init); + +static int __init pnpacpi_setup(char *str) +{ + if (str == NULL) + return 1; + if (!strncmp(str, "off", 3)) + pnpacpi_disabled = 1; + return 1; +} +__setup("pnpacpi=", pnpacpi_setup); + +EXPORT_SYMBOL(pnpacpi_protocol); |