diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1999-12-04 03:58:56 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1999-12-04 03:58:56 +0000 |
commit | 1d67e90f19a7acfd9a05dc59678e7d0c5090bd0d (patch) | |
tree | 357efc7b93f8f5102110d20d293f41360ec212fc /drivers/misc/acpi.c | |
parent | aea27b2e18d69af87e673972246e66657b4fa274 (diff) |
Merge with Linux 2.3.21.
Diffstat (limited to 'drivers/misc/acpi.c')
-rw-r--r-- | drivers/misc/acpi.c | 219 |
1 files changed, 200 insertions, 19 deletions
diff --git a/drivers/misc/acpi.c b/drivers/misc/acpi.c index 27137fa40..99911093b 100644 --- a/drivers/misc/acpi.c +++ b/drivers/misc/acpi.c @@ -18,6 +18,11 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +/* + * See http://www.geocities.com/SiliconValley/Hardware/3165/ + * for the user-level ACPI stuff + */ + #include <linux/module.h> #include <linux/init.h> #include <linux/miscdevice.h> @@ -46,6 +51,10 @@ static struct acpi_facp *acpi_facp = NULL; static unsigned long acpi_facp_addr = 0; static unsigned long acpi_dsdt_addr = 0; + +static volatile u32 acpi_pm1_status = 0; +static volatile u32 acpi_gpe_status = 0; +static volatile u32 acpi_gpe_level = 0; static DECLARE_WAIT_QUEUE_HEAD(acpi_wait_event); /* @@ -73,6 +82,19 @@ static void acpi_write_pm1_status(struct acpi_facp *facp, u32 value) } /* + * Get the value of the fixed event enable register + */ +static u32 acpi_read_pm1_enable(struct acpi_facp *facp) +{ + int offset = facp->pm1_evt_len >> 1; + u32 value = inw(facp->pm1a_evt + offset); + if (facp->pm1b_evt) { + value |= inw(facp->pm1b_evt + offset); + } + return value; +} + +/* * Set the value of the fixed event enable register (enable events) */ static void acpi_write_pm1_enable(struct acpi_facp *facp, u32 value) @@ -128,6 +150,28 @@ static void acpi_write_gpe_status(struct acpi_facp *facp, u32 value) } /* + * Get the value of the general-purpose event enable register + */ +static u32 acpi_read_gpe_enable(struct acpi_facp *facp) +{ + u32 value = 0; + int i, size, offset; + + offset = facp->gpe0_len >> 1; + if (facp->gpe1) { + size = facp->gpe1_len >> 1; + for (i = size - 1; i >= 0; i--) { + value = (value << 8) | inb(facp->gpe1 + offset + i); + } + } + size = facp->gpe0_len >> 1; + for (i = size - 1; i >= 0; i--) { + value = (value << 8) | inb(facp->gpe0 + offset + i); + } + return value; +} + +/* * Set the value of the general-purpose event enable register (enable events) */ static void acpi_write_gpe_enable(struct acpi_facp *facp, u32 value) @@ -157,7 +201,8 @@ static struct acpi_table *__init acpi_map_table(u32 addr) if (addr) { // map table header to determine size table = (struct acpi_table *) - ioremap_nocache((unsigned long) addr, sizeof(struct acpi_table)); + ioremap_nocache((unsigned long) addr, + sizeof(struct acpi_table)); if (table) { unsigned long table_size = table->length; iounmap(table); @@ -201,10 +246,13 @@ static int __init acpi_map_tables(void) // 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--) { + 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); + printk(KERN_INFO "ACPI: \"%s\" found at 0x%p\n", + oem, (void *) i); break; } @@ -252,6 +300,7 @@ static int __init acpi_map_tables(void) */ static void acpi_unmap_tables(void) { + acpi_idle = NULL; acpi_dsdt_addr = 0; acpi_facp_addr = 0; acpi_unmap_table((struct acpi_table *) acpi_facp); @@ -263,17 +312,32 @@ static void acpi_unmap_tables(void) */ 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); - + u32 pm1_status, gpe_status, gpe_level, gpe_edge; + // detect and clear fixed events + pm1_status = (acpi_read_pm1_status(acpi_facp) + & acpi_read_pm1_enable(acpi_facp)); + acpi_write_pm1_status(acpi_facp, pm1_status); + + // detect and handle general-purpose events + gpe_status = (acpi_read_gpe_status(acpi_facp) + & acpi_read_gpe_enable(acpi_facp)); + gpe_level = gpe_status & acpi_gpe_level; + if (gpe_level) { + // disable level-triggered events + acpi_write_gpe_enable( + acpi_facp, + acpi_read_gpe_enable(acpi_facp) & ~gpe_level); + } + gpe_edge = gpe_status & ~gpe_level; + if (gpe_edge) { + // clear edge-triggered events + while (acpi_read_gpe_status(acpi_facp) & gpe_edge) + acpi_write_gpe_status(acpi_facp, gpe_edge); + } + // notify process reading /dev/acpi + acpi_pm1_status |= pm1_status; + acpi_gpe_status |= gpe_status; wake_up_interruptible(&acpi_wait_event); } @@ -311,17 +375,79 @@ static int acpi_ioctl(struct inode *inode, (void *) arg, sizeof(struct acpi_find_tables)); if (!status) { - struct acpi_find_tables *rqst = (struct acpi_find_tables *) arg; + 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_ENABLE_EVENT: + status = verify_area(VERIFY_READ, + (void *) arg, + sizeof(struct acpi_enable_event)); + if (!status) { + struct acpi_enable_event *rqst + = (struct acpi_enable_event *) arg; + u32 pm1_enable, gpe_enable, gpe_level; + u32 pm1_enabling, gpe_enabling; + + get_user(pm1_enable, &rqst->pm1_enable); + get_user(gpe_enable, &rqst->gpe_enable); + get_user(gpe_level, &rqst->gpe_level); + gpe_level &= gpe_enable; + + // clear previously disabled events before enabling + pm1_enabling = (pm1_enable + & ~acpi_read_pm1_enable(acpi_facp)); + acpi_write_pm1_status(acpi_facp, pm1_enabling); + gpe_enabling = (gpe_enable & + ~acpi_read_gpe_enable(acpi_facp)); + while (acpi_read_gpe_status(acpi_facp) & gpe_enabling) + acpi_write_gpe_status(acpi_facp, gpe_enabling); + + acpi_write_pm1_enable(acpi_facp, pm1_enable); + acpi_write_gpe_enable(acpi_facp, gpe_enable); + acpi_gpe_level = gpe_level; + + status = 0; + } + break; case ACPI_WAIT_EVENT: - interruptible_sleep_on(&acpi_wait_event); - if (signal_pending(current)) - return -ERESTARTSYS; - status = 0; + status = verify_area(VERIFY_WRITE, + (void *) arg, + sizeof(struct acpi_wait_event)); + if (!status) { + struct acpi_wait_event *rqst + = (struct acpi_wait_event *) arg; + u32 pm1_status = 0; + u32 gpe_status = 0; + + for (;;) { + unsigned long flags; + + // we need an atomic exchange here + save_flags(flags); + cli(); + pm1_status = acpi_pm1_status; + acpi_pm1_status = 0; + gpe_status = acpi_gpe_status; + acpi_gpe_status = 0; + restore_flags(flags); + + if (pm1_status || gpe_status) + break; + + // wait for an event to arrive + interruptible_sleep_on(&acpi_wait_event); + if (signal_pending(current)) + return -ERESTARTSYS; + } + + put_user(pm1_status, &rqst->pm1_status); + put_user(gpe_status, &rqst->gpe_status); + status = 0; + } break; } return status; @@ -355,6 +481,48 @@ static struct miscdevice acpi_device = NULL }; +/* Make it impossible to enter L2/L3 until after we've initialized */ +static unsigned long acpi_p_lvl2_lat = ~0UL; +static unsigned long acpi_p_lvl3_lat = ~0UL; + +/* Initialize to guaranteed harmless port read */ +static u16 acpi_p_lvl2 = 0x80; +static u16 acpi_p_lvl3 = 0x80; + +static void acpi_idle_handler(void) +{ + unsigned long time; + static int sleep_level = 1; + + time = inl(acpi_facp->pm_tmr); + switch (sleep_level) { + case 1: + __asm__ __volatile__("sti ; hlt": : :"memory"); + break; + case 2: + inb(acpi_p_lvl2); + break; + case 3: + /* Disable PCI arbitration while sleeping, to avoid DMA corruption? */ + if (acpi_facp->pm2_cnt) { + unsigned int port = acpi_facp->pm2_cnt; + outb(inb(port) | ACPI_ARB_DIS, port); + inb(acpi_p_lvl3); + outb(inb(port) & ~ACPI_ARB_DIS, port); + break; + } + inb(acpi_p_lvl3); + } + time = (inl(acpi_facp->pm_tmr) - time) & ACPI_TMR_MASK; + + if (time > acpi_p_lvl3_lat) + sleep_level = 3; + else if (time > acpi_p_lvl2_lat) + sleep_level = 2; + else + sleep_level = 1; +} + /* * Initialize and enable ACPI */ @@ -376,6 +544,17 @@ static int __init acpi_init(void) if (misc_register(&acpi_device)) { printk(KERN_ERR "ACPI: misc. register failed\n"); } + + /* + * Set up the ACPI idle function. Note that we can't really + * do this with multiple CPU's, we'd need a per-CPU ACPI + * device.. + */ +#ifdef __SMP__ + if (smp_num_cpus > 1) + return 0; +#endif + acpi_idle = acpi_idle_handler; return 0; } @@ -389,7 +568,8 @@ static void __exit acpi_exit(void) // 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_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)); @@ -405,6 +585,7 @@ static void __exit acpi_exit(void) module_init(acpi_init) module_exit(acpi_exit) + #else __initcall(acpi_init); |