diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1999-10-09 00:00:47 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1999-10-09 00:00:47 +0000 |
commit | d6434e1042f3b0a6dfe1b1f615af369486f9b1fa (patch) | |
tree | e2be02f33984c48ec019c654051d27964e42c441 /drivers/misc/acpi.c | |
parent | 609d1e803baf519487233b765eb487f9ec227a18 (diff) |
Merge with 2.3.19.
Diffstat (limited to 'drivers/misc/acpi.c')
-rw-r--r-- | drivers/misc/acpi.c | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/drivers/misc/acpi.c b/drivers/misc/acpi.c new file mode 100644 index 000000000..27137fa40 --- /dev/null +++ b/drivers/misc/acpi.c @@ -0,0 +1,412 @@ +/* + * acpi.c - Linux ACPI driver + * + * Copyright (C) 1999 Andrew Henroid + * + * 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 of the License, 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/module.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <linux/acpi.h> + +/* + * Defines for 2.2.x + */ +#ifndef __exit +#define __exit +#endif +#ifndef module_init +#define module_init(x) int init_module(void) {return x();} +#endif +#ifndef module_exit +#define module_exit(x) void cleanup_module(void) {x();} +#endif +#ifndef DECLARE_WAIT_QUEUE_HEAD +#define DECLARE_WAIT_QUEUE_HEAD(x) struct wait_queue * x = NULL +#endif + +static struct acpi_facp *acpi_facp = NULL; +static unsigned long acpi_facp_addr = 0; +static unsigned long acpi_dsdt_addr = 0; +static DECLARE_WAIT_QUEUE_HEAD(acpi_wait_event); + +/* + * Get the value of the fixed event status register + */ +static u32 acpi_read_pm1_status(struct acpi_facp *facp) +{ + u32 value = inw(facp->pm1a_evt); + + if (facp->pm1b_evt) { + value |= inw(facp->pm1b_evt); + } + return value; +} + +/* + * Set the value of the fixed event status register (clear events) + */ +static void acpi_write_pm1_status(struct acpi_facp *facp, u32 value) +{ + outw(value, facp->pm1a_evt); + if (facp->pm1b_evt) { + outw(value, facp->pm1b_evt); + } +} + +/* + * Set the value of the fixed event enable register (enable events) + */ +static void acpi_write_pm1_enable(struct acpi_facp *facp, u32 value) +{ + int offset = facp->pm1_evt_len >> 1; + + outw(value, facp->pm1a_evt + offset); + if (facp->pm1b_evt) { + outw(value, facp->pm1b_evt + offset); + } +} + +/* + * Get the value of the general-purpose event status register + */ +static u32 acpi_read_gpe_status(struct acpi_facp *facp) +{ + u32 value = 0; + int i, size; + + if (facp->gpe1) { + size = facp->gpe1_len >> 1; + for (i = size - 1; i >= 0; i--) { + value = (value << 8) | inb(facp->gpe1 + i); + } + } + size = facp->gpe0_len >> 1; + for (i = size - 1; i >= 0; i--) { + value = (value << 8) | inb(facp->gpe0 + i); + } + return value; +} + +/* + * Set the value of the general-purpose event status register (clear events) + */ +static void acpi_write_gpe_status(struct acpi_facp *facp, u32 value) +{ + int i, size; + + size = facp->gpe0_len >> 1; + for (i = 0; i < size; i++) { + outb(value & 0xff, facp->gpe0 + i); + value >>= 8; + } + if (facp->gpe1) { + size = facp->gpe1_len >> 1; + for (i = 0; i < size; i++) { + outb(value & 0xff, facp->gpe1 + i); + value >>= 8; + } + } +} + +/* + * Set the value of the general-purpose event enable register (enable events) + */ +static void acpi_write_gpe_enable(struct acpi_facp *facp, u32 value) +{ + int i, offset; + + offset = facp->gpe0_len >> 1; + for (i = 0; i < offset; i++) { + outb(value & 0xff, facp->gpe0 + offset + i); + value >>= 8; + } + if (facp->gpe1) { + offset = facp->gpe1_len >> 1; + for (i = 0; i < offset; i++) { + outb(value & 0xff, facp->gpe1 + offset + i); + value >>= 8; + } + } +} + +/* + * Map an ACPI table into virtual memory + */ +static struct acpi_table *__init acpi_map_table(u32 addr) +{ + struct acpi_table *table = NULL; + if (addr) { + // map table header to determine size + table = (struct acpi_table *) + ioremap_nocache((unsigned long) addr, sizeof(struct acpi_table)); + if (table) { + unsigned long table_size = table->length; + iounmap(table); + // remap entire table + table = (struct acpi_table *) + ioremap_nocache((unsigned long) addr, table_size); + } + } + return table; +} + +/* + * Unmap an ACPI table from virtual memory + */ +static void acpi_unmap_table(struct acpi_table *table) +{ + if (table) { + iounmap(table); + } +} + +/* + * Locate and map ACPI tables (FACP, DSDT, ...) + */ +static int __init acpi_map_tables(void) +{ + struct acpi_rsdp *rsdp; + struct acpi_table *rsdt; + u32 *rsdt_entry; + int rsdt_entry_count; + u8 *i; + + // search BIOS memory for RSDP + for (i = ACPI_BIOS_ROM_BASE; i < ACPI_BIOS_ROM_END; i += 16) { + rsdp = (struct acpi_rsdp *) i; + if (readl(rsdp->signature) == ACPI_RSDP1_SIG + && readl(rsdp->signature + 1) == ACPI_RSDP2_SIG) { + char oem[7]; + int j; + + // strip trailing space and print OEM identifier + memcpy_fromio(oem, rsdp->oem, 6); + oem[6] = '\0'; + for (j = 5; j > 0 && (oem[j] == '\0' || oem[j] == ' '); j--) { + oem[j] = '\0'; + } + printk(KERN_INFO "ACPI: \"%s\" found at 0x%p\n", oem, (void *) i); + + break; + } + } + if (i >= ACPI_BIOS_ROM_END) { + printk(KERN_ERR "ACPI: no RSDP found\n"); + return -ENODEV; + } + // fetch RSDT from RSDP + rsdt = acpi_map_table(readl(&rsdp->rsdt)); + if (!rsdt || rsdt->signature != ACPI_RSDT_SIG) { + printk(KERN_ERR "ACPI: no RSDT found\n"); + acpi_unmap_table(rsdt); + return -ENODEV; + } + // search RSDT for FACP + acpi_facp = NULL; + rsdt_entry = (u32 *) (rsdt + 1); + rsdt_entry_count = (int) ((rsdt->length - sizeof(*rsdt)) >> 2); + while (rsdt_entry_count) { + struct acpi_table *dt = acpi_map_table(*rsdt_entry); + if (dt && dt->signature == ACPI_FACP_SIG) { + acpi_facp = (struct acpi_facp *) dt; + acpi_facp_addr = *rsdt_entry; + acpi_dsdt_addr = acpi_facp->dsdt; + break; + } else { + acpi_unmap_table(dt); + } + rsdt_entry++; + rsdt_entry_count--; + } + + acpi_unmap_table(rsdt); + + if (!acpi_facp) { + printk(KERN_ERR "ACPI: no FACP found\n"); + return -ENODEV; + } + return 0; +} + +/* + * Unmap ACPI tables (FACP, DSDT, ...) + */ +static void acpi_unmap_tables(void) +{ + acpi_dsdt_addr = 0; + acpi_facp_addr = 0; + acpi_unmap_table((struct acpi_table *) acpi_facp); + acpi_facp = NULL; +} + +/* + * Handle an ACPI SCI (fixed or general purpose event) + */ +static void acpi_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 status; + + // detect and disable any fixed events + status = acpi_read_pm1_status(acpi_facp); + acpi_write_pm1_enable(acpi_facp, ~status); + + // detect and disable any general-purpose events + status = acpi_read_gpe_status(acpi_facp); + acpi_write_gpe_enable(acpi_facp, ~status); + + // notify process reading /dev/acpi + wake_up_interruptible(&acpi_wait_event); +} + +/* + * Handle open of /dev/acpi + */ +static int acpi_open(struct inode *inode, struct file *file) +{ + MOD_INC_USE_COUNT; + return 0; +} + +/* + * Handle close of /dev/acpi + */ +static int acpi_release(struct inode *inode, struct file *file) +{ + MOD_DEC_USE_COUNT; + return 0; +} + +/* + * Handle command to /dev/acpi + */ +static int acpi_ioctl(struct inode *inode, + struct file *file, + unsigned cmd, + unsigned long arg) +{ + int status = -EINVAL; + + switch (cmd) { + case ACPI_FIND_TABLES: + status = verify_area(VERIFY_WRITE, + (void *) arg, + sizeof(struct acpi_find_tables)); + if (!status) { + struct acpi_find_tables *rqst = (struct acpi_find_tables *) arg; + put_user(acpi_facp_addr, &rqst->facp); + put_user(acpi_dsdt_addr, &rqst->dsdt); + status = 0; + } + break; + case ACPI_WAIT_EVENT: + interruptible_sleep_on(&acpi_wait_event); + if (signal_pending(current)) + return -ERESTARTSYS; + status = 0; + break; + } + return status; +} + +static struct file_operations acpi_fops = +{ + NULL, /* llseek */ + NULL, /* read */ + NULL, /* write */ + NULL, /* readdir */ + NULL, /* poll */ + acpi_ioctl, /* ioctl */ + NULL, /* mmap */ + acpi_open, /* open */ + NULL, /* flush */ + acpi_release, /* release */ + NULL, /* fsync */ + NULL, /* fasync */ + NULL, /* check_media_change */ + NULL, /* revalidate */ + NULL, /* lock */ +}; + +static struct miscdevice acpi_device = +{ + ACPI_MINOR_DEV, + "acpi", + &acpi_fops, + NULL, + NULL +}; + +/* + * Initialize and enable ACPI + */ +static int __init acpi_init(void) +{ + if (acpi_map_tables()) { + return -ENODEV; + } + if (request_irq(acpi_facp->sci_int, + acpi_irq, + SA_INTERRUPT | SA_SHIRQ, + "acpi", + NULL)) { + printk(KERN_ERR "ACPI: SCI (IRQ%d) allocation failed\n", + acpi_facp->sci_int); + acpi_unmap_tables(); + return -ENODEV; + } + if (misc_register(&acpi_device)) { + printk(KERN_ERR "ACPI: misc. register failed\n"); + } + return 0; +} + +/* + * Disable and deinitialize ACPI + */ +static void __exit acpi_exit(void) +{ + misc_deregister(&acpi_device); + + // disable and clear any pending events + acpi_write_gpe_enable(acpi_facp, 0); + while (acpi_read_gpe_status(acpi_facp)) { + acpi_write_gpe_status(acpi_facp, acpi_read_gpe_status(acpi_facp)); + } + acpi_write_pm1_enable(acpi_facp, 0); + acpi_write_pm1_status(acpi_facp, acpi_read_pm1_status(acpi_facp)); + + // disable SCI and free interrupt + outb(acpi_facp->acpi_disable, acpi_facp->smi_cmd); + free_irq(acpi_facp->sci_int, NULL); + + acpi_unmap_tables(); +} + +#ifdef MODULE + +module_init(acpi_init) +module_exit(acpi_exit) +#else + +__initcall(acpi_init); + +#endif |