diff options
Diffstat (limited to 'drivers/macintosh/via-pmu.c')
-rw-r--r-- | drivers/macintosh/via-pmu.c | 3147 |
1 files changed, 3147 insertions, 0 deletions
diff --git a/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c new file mode 100644 index 00000000000..cea1e758eb4 --- /dev/null +++ b/drivers/macintosh/via-pmu.c @@ -0,0 +1,3147 @@ +/* + * Device driver for the via-pmu on Apple Powermacs. + * + * The VIA (versatile interface adapter) interfaces to the PMU, + * a 6805 microprocessor core whose primary function is to control + * battery charging and system power on the PowerBook 3400 and 2400. + * The PMU also controls the ADB (Apple Desktop Bus) which connects + * to the keyboard and mouse, as well as the non-volatile RAM + * and the RTC (real time clock) chip. + * + * Copyright (C) 1998 Paul Mackerras and Fabio Riccardi. + * Copyright (C) 2001-2002 Benjamin Herrenschmidt + * + * THIS DRIVER IS BECOMING A TOTAL MESS ! + * - Cleanup atomically disabling reply to PMU events after + * a sleep or a freq. switch + * - Move sleep code out of here to pmac_pm, merge into new + * common PM infrastructure + * - Move backlight code out as well + * - Save/Restore PCI space properly + * + */ +#include <stdarg.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/miscdevice.h> +#include <linux/blkdev.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/adb.h> +#include <linux/pmu.h> +#include <linux/cuda.h> +#include <linux/smp_lock.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/pm.h> +#include <linux/proc_fs.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/suspend.h> +#include <linux/syscalls.h> +#include <linux/cpu.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <asm/irq.h> +#include <asm/pmac_feature.h> +#include <asm/uaccess.h> +#include <asm/mmu_context.h> +#include <asm/cputable.h> +#include <asm/time.h> +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/backlight.h> +#endif + +/* Some compile options */ +#undef SUSPEND_USES_PMU +#define DEBUG_SLEEP +#undef HACKED_PCI_SAVE + +/* Misc minor number allocated for /dev/pmu */ +#define PMU_MINOR 154 + +/* How many iterations between battery polls */ +#define BATTERY_POLLING_COUNT 2 + +static volatile unsigned char __iomem *via; + +/* VIA registers - spaced 0x200 bytes apart */ +#define RS 0x200 /* skip between registers */ +#define B 0 /* B-side data */ +#define A RS /* A-side data */ +#define DIRB (2*RS) /* B-side direction (1=output) */ +#define DIRA (3*RS) /* A-side direction (1=output) */ +#define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */ +#define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */ +#define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */ +#define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */ +#define T2CL (8*RS) /* Timer 2 ctr/latch (low 8 bits) */ +#define T2CH (9*RS) /* Timer 2 counter (high 8 bits) */ +#define SR (10*RS) /* Shift register */ +#define ACR (11*RS) /* Auxiliary control register */ +#define PCR (12*RS) /* Peripheral control register */ +#define IFR (13*RS) /* Interrupt flag register */ +#define IER (14*RS) /* Interrupt enable register */ +#define ANH (15*RS) /* A-side data, no handshake */ + +/* Bits in B data register: both active low */ +#define TACK 0x08 /* Transfer acknowledge (input) */ +#define TREQ 0x10 /* Transfer request (output) */ + +/* Bits in ACR */ +#define SR_CTRL 0x1c /* Shift register control bits */ +#define SR_EXT 0x0c /* Shift on external clock */ +#define SR_OUT 0x10 /* Shift out if 1 */ + +/* Bits in IFR and IER */ +#define IER_SET 0x80 /* set bits in IER */ +#define IER_CLR 0 /* clear bits in IER */ +#define SR_INT 0x04 /* Shift register full/empty */ +#define CB2_INT 0x08 +#define CB1_INT 0x10 /* transition on CB1 input */ + +static volatile enum pmu_state { + idle, + sending, + intack, + reading, + reading_intr, + locked, +} pmu_state; + +static volatile enum int_data_state { + int_data_empty, + int_data_fill, + int_data_ready, + int_data_flush +} int_data_state[2] = { int_data_empty, int_data_empty }; + +static struct adb_request *current_req; +static struct adb_request *last_req; +static struct adb_request *req_awaiting_reply; +static unsigned char interrupt_data[2][32]; +static int interrupt_data_len[2]; +static int int_data_last; +static unsigned char *reply_ptr; +static int data_index; +static int data_len; +static volatile int adb_int_pending; +static volatile int disable_poll; +static struct adb_request bright_req_1, bright_req_2; +static struct device_node *vias; +static int pmu_kind = PMU_UNKNOWN; +static int pmu_fully_inited = 0; +static int pmu_has_adb; +static unsigned char __iomem *gpio_reg = NULL; +static int gpio_irq = -1; +static int gpio_irq_enabled = -1; +static volatile int pmu_suspended = 0; +static spinlock_t pmu_lock; +static u8 pmu_intr_mask; +static int pmu_version; +static int drop_interrupts; +#ifdef CONFIG_PMAC_PBOOK +static int option_lid_wakeup = 1; +static int sleep_in_progress; +#endif /* CONFIG_PMAC_PBOOK */ +static unsigned long async_req_locks; +static unsigned int pmu_irq_stats[11]; + +static struct proc_dir_entry *proc_pmu_root; +static struct proc_dir_entry *proc_pmu_info; +static struct proc_dir_entry *proc_pmu_irqstats; +static struct proc_dir_entry *proc_pmu_options; +static int option_server_mode; + +#ifdef CONFIG_PMAC_PBOOK +int pmu_battery_count; +int pmu_cur_battery; +unsigned int pmu_power_flags; +struct pmu_battery_info pmu_batteries[PMU_MAX_BATTERIES]; +static int query_batt_timer = BATTERY_POLLING_COUNT; +static struct adb_request batt_req; +static struct proc_dir_entry *proc_pmu_batt[PMU_MAX_BATTERIES]; +#endif /* CONFIG_PMAC_PBOOK */ + +#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) +extern int disable_kernel_backlight; +#endif /* defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) */ + +int __fake_sleep; +int asleep; +struct notifier_block *sleep_notifier_list; + +#ifdef CONFIG_ADB +static int adb_dev_map = 0; +static int pmu_adb_flags; + +static int pmu_probe(void); +static int pmu_init(void); +static int pmu_send_request(struct adb_request *req, int sync); +static int pmu_adb_autopoll(int devs); +static int pmu_adb_reset_bus(void); +#endif /* CONFIG_ADB */ + +static int init_pmu(void); +static int pmu_queue_request(struct adb_request *req); +static void pmu_start(void); +static irqreturn_t via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs); +static irqreturn_t gpio1_interrupt(int irq, void *arg, struct pt_regs *regs); +static int proc_get_info(char *page, char **start, off_t off, + int count, int *eof, void *data); +static int proc_get_irqstats(char *page, char **start, off_t off, + int count, int *eof, void *data); +#ifdef CONFIG_PMAC_BACKLIGHT +static int pmu_set_backlight_level(int level, void* data); +static int pmu_set_backlight_enable(int on, int level, void* data); +#endif /* CONFIG_PMAC_BACKLIGHT */ +#ifdef CONFIG_PMAC_PBOOK +static void pmu_pass_intr(unsigned char *data, int len); +static int proc_get_batt(char *page, char **start, off_t off, + int count, int *eof, void *data); +#endif /* CONFIG_PMAC_PBOOK */ +static int proc_read_options(char *page, char **start, off_t off, + int count, int *eof, void *data); +static int proc_write_options(struct file *file, const char __user *buffer, + unsigned long count, void *data); + +#ifdef CONFIG_ADB +struct adb_driver via_pmu_driver = { + "PMU", + pmu_probe, + pmu_init, + pmu_send_request, + pmu_adb_autopoll, + pmu_poll_adb, + pmu_adb_reset_bus +}; +#endif /* CONFIG_ADB */ + +extern void low_sleep_handler(void); +extern void enable_kernel_altivec(void); +extern void enable_kernel_fp(void); + +#ifdef DEBUG_SLEEP +int pmu_polled_request(struct adb_request *req); +int pmu_wink(struct adb_request *req); +#endif + +/* + * This table indicates for each PMU opcode: + * - the number of data bytes to be sent with the command, or -1 + * if a length byte should be sent, + * - the number of response bytes which the PMU will return, or + * -1 if it will send a length byte. + */ +static const s8 pmu_data_len[256][2] __openfirmwaredata = { +/* 0 1 2 3 4 5 6 7 */ +/*00*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*08*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*10*/ { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*18*/ { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0, 0}, +/*20*/ {-1, 0},{ 0, 0},{ 2, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*28*/ { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0,-1}, +/*30*/ { 4, 0},{20, 0},{-1, 0},{ 3, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*38*/ { 0, 4},{ 0,20},{ 2,-1},{ 2, 1},{ 3,-1},{-1,-1},{-1,-1},{ 4, 0}, +/*40*/ { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*48*/ { 0, 1},{ 0, 1},{-1,-1},{ 1, 0},{ 1, 0},{-1,-1},{-1,-1},{-1,-1}, +/*50*/ { 1, 0},{ 0, 0},{ 2, 0},{ 2, 0},{-1, 0},{ 1, 0},{ 3, 0},{ 1, 0}, +/*58*/ { 0, 1},{ 1, 0},{ 0, 2},{ 0, 2},{ 0,-1},{-1,-1},{-1,-1},{-1,-1}, +/*60*/ { 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*68*/ { 0, 3},{ 0, 3},{ 0, 2},{ 0, 8},{ 0,-1},{ 0,-1},{-1,-1},{-1,-1}, +/*70*/ { 1, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*78*/ { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{ 5, 1},{ 4, 1},{ 4, 1}, +/*80*/ { 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*88*/ { 0, 5},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*90*/ { 1, 0},{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*98*/ { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*a0*/ { 2, 0},{ 2, 0},{ 2, 0},{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0}, +/*a8*/ { 1, 1},{ 1, 0},{ 3, 0},{ 2, 0},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*b0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*b8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*c0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*c8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*d0*/ { 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*d8*/ { 1, 1},{ 1, 1},{-1,-1},{-1,-1},{ 0, 1},{ 0,-1},{-1,-1},{-1,-1}, +/*e0*/ {-1, 0},{ 4, 0},{ 0, 1},{-1, 0},{-1, 0},{ 4, 0},{-1, 0},{-1, 0}, +/*e8*/ { 3,-1},{-1,-1},{ 0, 1},{-1,-1},{ 0,-1},{-1,-1},{-1,-1},{ 0, 0}, +/*f0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*f8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +}; + +static char *pbook_type[] = { + "Unknown PowerBook", + "PowerBook 2400/3400/3500(G3)", + "PowerBook G3 Series", + "1999 PowerBook G3", + "Core99" +}; + +#ifdef CONFIG_PMAC_BACKLIGHT +static struct backlight_controller pmu_backlight_controller = { + pmu_set_backlight_enable, + pmu_set_backlight_level +}; +#endif /* CONFIG_PMAC_BACKLIGHT */ + +int __openfirmware +find_via_pmu(void) +{ + if (via != 0) + return 1; + vias = find_devices("via-pmu"); + if (vias == 0) + return 0; + if (vias->next != 0) + printk(KERN_WARNING "Warning: only using 1st via-pmu\n"); + + if (vias->n_addrs < 1 || vias->n_intrs < 1) { + printk(KERN_ERR "via-pmu: %d addresses, %d interrupts!\n", + vias->n_addrs, vias->n_intrs); + if (vias->n_addrs < 1 || vias->n_intrs < 1) + return 0; + } + + spin_lock_init(&pmu_lock); + + pmu_has_adb = 1; + + pmu_intr_mask = PMU_INT_PCEJECT | + PMU_INT_SNDBRT | + PMU_INT_ADB | + PMU_INT_TICK; + + if (vias->parent->name && ((strcmp(vias->parent->name, "ohare") == 0) + || device_is_compatible(vias->parent, "ohare"))) + pmu_kind = PMU_OHARE_BASED; + else if (device_is_compatible(vias->parent, "paddington")) + pmu_kind = PMU_PADDINGTON_BASED; + else if (device_is_compatible(vias->parent, "heathrow")) + pmu_kind = PMU_HEATHROW_BASED; + else if (device_is_compatible(vias->parent, "Keylargo") + || device_is_compatible(vias->parent, "K2-Keylargo")) { + struct device_node *gpio, *gpiop; + + pmu_kind = PMU_KEYLARGO_BASED; + pmu_has_adb = (find_type_devices("adb") != NULL); + pmu_intr_mask = PMU_INT_PCEJECT | + PMU_INT_SNDBRT | + PMU_INT_ADB | + PMU_INT_TICK | + PMU_INT_ENVIRONMENT; + + gpiop = find_devices("gpio"); + if (gpiop && gpiop->n_addrs) { + gpio_reg = ioremap(gpiop->addrs->address, 0x10); + gpio = find_devices("extint-gpio1"); + if (gpio == NULL) + gpio = find_devices("pmu-interrupt"); + if (gpio && gpio->parent == gpiop && gpio->n_intrs) + gpio_irq = gpio->intrs[0].line; + } + } else + pmu_kind = PMU_UNKNOWN; + + via = ioremap(vias->addrs->address, 0x2000); + + out_8(&via[IER], IER_CLR | 0x7f); /* disable all intrs */ + out_8(&via[IFR], 0x7f); /* clear IFR */ + + pmu_state = idle; + + if (!init_pmu()) { + via = NULL; + return 0; + } + + printk(KERN_INFO "PMU driver %d initialized for %s, firmware: %02x\n", + PMU_DRIVER_VERSION, pbook_type[pmu_kind], pmu_version); + + sys_ctrler = SYS_CTRLER_PMU; + + return 1; +} + +#ifdef CONFIG_ADB +static int __openfirmware +pmu_probe(void) +{ + return vias == NULL? -ENODEV: 0; +} + +static int __init +pmu_init(void) +{ + if (vias == NULL) + return -ENODEV; + return 0; +} +#endif /* CONFIG_ADB */ + +/* + * We can't wait until pmu_init gets called, that happens too late. + * It happens after IDE and SCSI initialization, which can take a few + * seconds, and by that time the PMU could have given up on us and + * turned us off. + * Thus this is called with arch_initcall rather than device_initcall. + */ +static int __init via_pmu_start(void) +{ + if (vias == NULL) + return -ENODEV; + + bright_req_1.complete = 1; + bright_req_2.complete = 1; +#ifdef CONFIG_PMAC_PBOOK + batt_req.complete = 1; +#endif + + if (request_irq(vias->intrs[0].line, via_pmu_interrupt, 0, "VIA-PMU", + (void *)0)) { + printk(KERN_ERR "VIA-PMU: can't get irq %d\n", + vias->intrs[0].line); + return -EAGAIN; + } + + if (pmu_kind == PMU_KEYLARGO_BASED && gpio_irq != -1) { + if (request_irq(gpio_irq, gpio1_interrupt, 0, "GPIO1 ADB", (void *)0)) + printk(KERN_ERR "pmu: can't get irq %d (GPIO1)\n", gpio_irq); + gpio_irq_enabled = 1; + } + + /* Enable interrupts */ + out_8(&via[IER], IER_SET | SR_INT | CB1_INT); + + pmu_fully_inited = 1; + + /* Make sure PMU settle down before continuing. This is _very_ important + * since the IDE probe may shut interrupts down for quite a bit of time. If + * a PMU communication is pending while this happens, the PMU may timeout + * Not that on Core99 machines, the PMU keeps sending us environement + * messages, we should find a way to either fix IDE or make it call + * pmu_suspend() before masking interrupts. This can also happens while + * scolling with some fbdevs. + */ + do { + pmu_poll(); + } while (pmu_state != idle); + + return 0; +} + +arch_initcall(via_pmu_start); + +/* + * This has to be done after pci_init, which is a subsys_initcall. + */ +static int __init via_pmu_dev_init(void) +{ + if (vias == NULL) + return -ENODEV; + +#ifndef CONFIG_PPC64 + request_OF_resource(vias, 0, NULL); +#endif +#ifdef CONFIG_PMAC_BACKLIGHT + /* Enable backlight */ + register_backlight_controller(&pmu_backlight_controller, NULL, "pmu"); +#endif /* CONFIG_PMAC_BACKLIGHT */ + +#ifdef CONFIG_PMAC_PBOOK + if (machine_is_compatible("AAPL,3400/2400") || + machine_is_compatible("AAPL,3500")) { + int mb = pmac_call_feature(PMAC_FTR_GET_MB_INFO, + NULL, PMAC_MB_INFO_MODEL, 0); + pmu_battery_count = 1; + if (mb == PMAC_TYPE_COMET) + pmu_batteries[0].flags |= PMU_BATT_TYPE_COMET; + else + pmu_batteries[0].flags |= PMU_BATT_TYPE_HOOPER; + } else if (machine_is_compatible("AAPL,PowerBook1998") || + machine_is_compatible("PowerBook1,1")) { + pmu_battery_count = 2; + pmu_batteries[0].flags |= PMU_BATT_TYPE_SMART; + pmu_batteries[1].flags |= PMU_BATT_TYPE_SMART; + } else { + struct device_node* prim = find_devices("power-mgt"); + u32 *prim_info = NULL; + if (prim) + prim_info = (u32 *)get_property(prim, "prim-info", NULL); + if (prim_info) { + /* Other stuffs here yet unknown */ + pmu_battery_count = (prim_info[6] >> 16) & 0xff; + pmu_batteries[0].flags |= PMU_BATT_TYPE_SMART; + if (pmu_battery_count > 1) + pmu_batteries[1].flags |= PMU_BATT_TYPE_SMART; + } + } +#endif /* CONFIG_PMAC_PBOOK */ + /* Create /proc/pmu */ + proc_pmu_root = proc_mkdir("pmu", NULL); + if (proc_pmu_root) { +#ifdef CONFIG_PMAC_PBOOK + int i; + + for (i=0; i<pmu_battery_count; i++) { + char title[16]; + sprintf(title, "battery_%d", i); + proc_pmu_batt[i] = create_proc_read_entry(title, 0, proc_pmu_root, + proc_get_batt, (void *)i); + } +#endif /* CONFIG_PMAC_PBOOK */ + + proc_pmu_info = create_proc_read_entry("info", 0, proc_pmu_root, + proc_get_info, NULL); + proc_pmu_irqstats = create_proc_read_entry("interrupts", 0, proc_pmu_root, + proc_get_irqstats, NULL); + proc_pmu_options = create_proc_entry("options", 0600, proc_pmu_root); + if (proc_pmu_options) { + proc_pmu_options->nlink = 1; + proc_pmu_options->read_proc = proc_read_options; + proc_pmu_options->write_proc = proc_write_options; + } + } + return 0; +} + +device_initcall(via_pmu_dev_init); + +static int __openfirmware +init_pmu(void) +{ + int timeout; + struct adb_request req; + + out_8(&via[B], via[B] | TREQ); /* negate TREQ */ + out_8(&via[DIRB], (via[DIRB] | TREQ) & ~TACK); /* TACK in, TREQ out */ + + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, pmu_intr_mask); + timeout = 100000; + while (!req.complete) { + if (--timeout < 0) { + printk(KERN_ERR "init_pmu: no response from PMU\n"); + return 0; + } + udelay(10); + pmu_poll(); + } + + /* ack all pending interrupts */ + timeout = 100000; + interrupt_data[0][0] = 1; + while (interrupt_data[0][0] || pmu_state != idle) { + if (--timeout < 0) { + printk(KERN_ERR "init_pmu: timed out acking intrs\n"); + return 0; + } + if (pmu_state == idle) + adb_int_pending = 1; + via_pmu_interrupt(0, NULL, NULL); + udelay(10); + } + + /* Tell PMU we are ready. */ + if (pmu_kind == PMU_KEYLARGO_BASED) { + pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2); + while (!req.complete) + pmu_poll(); + } + + /* Read PMU version */ + pmu_request(&req, NULL, 1, PMU_GET_VERSION); + pmu_wait_complete(&req); + if (req.reply_len > 0) + pmu_version = req.reply[0]; + + /* Read server mode setting */ + if (pmu_kind == PMU_KEYLARGO_BASED) { + pmu_request(&req, NULL, 2, PMU_POWER_EVENTS, + PMU_PWR_GET_POWERUP_EVENTS); + pmu_wait_complete(&req); + if (req.reply_len == 2) { + if (req.reply[1] & PMU_PWR_WAKEUP_AC_INSERT) + option_server_mode = 1; + printk(KERN_INFO "via-pmu: Server Mode is %s\n", + option_server_mode ? "enabled" : "disabled"); + } + } + return 1; +} + +int +pmu_get_model(void) +{ + return pmu_kind; +} + +#ifndef CONFIG_PPC64 +static inline void wakeup_decrementer(void) +{ + set_dec(tb_ticks_per_jiffy); + /* No currently-supported powerbook has a 601, + * so use get_tbl, not native + */ + last_jiffy_stamp(0) = tb_last_stamp = get_tbl(); +} +#endif + +static void pmu_set_server_mode(int server_mode) +{ + struct adb_request req; + + if (pmu_kind != PMU_KEYLARGO_BASED) + return; + + option_server_mode = server_mode; + pmu_request(&req, NULL, 2, PMU_POWER_EVENTS, PMU_PWR_GET_POWERUP_EVENTS); + pmu_wait_complete(&req); + if (req.reply_len < 2) + return; + if (server_mode) + pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, + PMU_PWR_SET_POWERUP_EVENTS, + req.reply[0], PMU_PWR_WAKEUP_AC_INSERT); + else + pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, + PMU_PWR_CLR_POWERUP_EVENTS, + req.reply[0], PMU_PWR_WAKEUP_AC_INSERT); + pmu_wait_complete(&req); +} + +#ifdef CONFIG_PMAC_PBOOK + +/* This new version of the code for 2400/3400/3500 powerbooks + * is inspired from the implementation in gkrellm-pmu + */ +static void __pmac +done_battery_state_ohare(struct adb_request* req) +{ + /* format: + * [0] : flags + * 0x01 : AC indicator + * 0x02 : charging + * 0x04 : battery exist + * 0x08 : + * 0x10 : + * 0x20 : full charged + * 0x40 : pcharge reset + * 0x80 : battery exist + * + * [1][2] : battery voltage + * [3] : CPU temperature + * [4] : battery temperature + * [5] : current + * [6][7] : pcharge + * --tkoba + */ + unsigned int bat_flags = PMU_BATT_TYPE_HOOPER; + long pcharge, charge, vb, vmax, lmax; + long vmax_charging, vmax_charged; + long amperage, voltage, time, max; + int mb = pmac_call_feature(PMAC_FTR_GET_MB_INFO, + NULL, PMAC_MB_INFO_MODEL, 0); + + if (req->reply[0] & 0x01) + pmu_power_flags |= PMU_PWR_AC_PRESENT; + else + pmu_power_flags &= ~PMU_PWR_AC_PRESENT; + + if (mb == PMAC_TYPE_COMET) { + vmax_charged = 189; + vmax_charging = 213; + lmax = 6500; + } else { + vmax_charged = 330; + vmax_charging = 330; + lmax = 6500; + } + vmax = vmax_charged; + + /* If battery installed */ + if (req->reply[0] & 0x04) { + bat_flags |= PMU_BATT_PRESENT; + if (req->reply[0] & 0x02) + bat_flags |= PMU_BATT_CHARGING; + vb = (req->reply[1] << 8) | req->reply[2]; + voltage = (vb * 265 + 72665) / 10; + amperage = req->reply[5]; + if ((req->reply[0] & 0x01) == 0) { + if (amperage > 200) + vb += ((amperage - 200) * 15)/100; + } else if (req->reply[0] & 0x02) { + vb = (vb * 97) / 100; + vmax = vmax_charging; + } + charge = (100 * vb) / vmax; + if (req->reply[0] & 0x40) { + pcharge = (req->reply[6] << 8) + req->reply[7]; + if (pcharge > lmax) + pcharge = lmax; + pcharge *= 100; + pcharge = 100 - pcharge / lmax; + if (pcharge < charge) + charge = pcharge; + } + if (amperage > 0) + time = (charge * 16440) / amperage; + else + time = 0; + max = 100; + amperage = -amperage; + } else + charge = max = amperage = voltage = time = 0; + + pmu_batteries[pmu_cur_battery].flags = bat_flags; + pmu_batteries[pmu_cur_battery].charge = charge; + pmu_batteries[pmu_cur_battery].max_charge = max; + pmu_batteries[pmu_cur_battery].amperage = amperage; + pmu_batteries[pmu_cur_battery].voltage = voltage; + pmu_batteries[pmu_cur_battery].time_remaining = time; + + clear_bit(0, &async_req_locks); +} + +static void __pmac +done_battery_state_smart(struct adb_request* req) +{ + /* format: + * [0] : format of this structure (known: 3,4,5) + * [1] : flags + * + * format 3 & 4: + * + * [2] : charge + * [3] : max charge + * [4] : current + * [5] : voltage + * + * format 5: + * + * [2][3] : charge + * [4][5] : max charge + * [6][7] : current + * [8][9] : voltage + */ + + unsigned int bat_flags = PMU_BATT_TYPE_SMART; + int amperage; + unsigned int capa, max, voltage; + + if (req->reply[1] & 0x01) + pmu_power_flags |= PMU_PWR_AC_PRESENT; + else + pmu_power_flags &= ~PMU_PWR_AC_PRESENT; + + + capa = max = amperage = voltage = 0; + + if (req->reply[1] & 0x04) { + bat_flags |= PMU_BATT_PRESENT; + switch(req->reply[0]) { + case 3: + case 4: capa = req->reply[2]; + max = req->reply[3]; + amperage = *((signed char *)&req->reply[4]); + voltage = req->reply[5]; + break; + case 5: capa = (req->reply[2] << 8) | req->reply[3]; + max = (req->reply[4] << 8) | req->reply[5]; + amperage = *((signed short *)&req->reply[6]); + voltage = (req->reply[8] << 8) | req->reply[9]; + break; + default: + printk(KERN_WARNING "pmu.c : unrecognized battery info, len: %d, %02x %02x %02x %02x\n", + req->reply_len, req->reply[0], req->reply[1], req->reply[2], req->reply[3]); + break; + } + } + + if ((req->reply[1] & 0x01) && (amperage > 0)) + bat_flags |= PMU_BATT_CHARGING; + + pmu_batteries[pmu_cur_battery].flags = bat_flags; + pmu_batteries[pmu_cur_battery].charge = capa; + pmu_batteries[pmu_cur_battery].max_charge = max; + pmu_batteries[pmu_cur_battery].amperage = amperage; + pmu_batteries[pmu_cur_battery].voltage = voltage; + if (amperage) { + if ((req->reply[1] & 0x01) && (amperage > 0)) + pmu_batteries[pmu_cur_battery].time_remaining + = ((max-capa) * 3600) / amperage; + else + pmu_batteries[pmu_cur_battery].time_remaining + = (capa * 3600) / (-amperage); + } else + pmu_batteries[pmu_cur_battery].time_remaining = 0; + + pmu_cur_battery = (pmu_cur_battery + 1) % pmu_battery_count; + + clear_bit(0, &async_req_locks); +} + +static void __pmac +query_battery_state(void) +{ + if (test_and_set_bit(0, &async_req_locks)) + return; + if (pmu_kind == PMU_OHARE_BASED) + pmu_request(&batt_req, done_battery_state_ohare, + 1, PMU_BATTERY_STATE); + else + pmu_request(&batt_req, done_battery_state_smart, + 2, PMU_SMART_BATTERY_STATE, pmu_cur_battery+1); +} + +#endif /* CONFIG_PMAC_PBOOK */ + +static int __pmac +proc_get_info(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char* p = page; + + p += sprintf(p, "PMU driver version : %d\n", PMU_DRIVER_VERSION); + p += sprintf(p, "PMU firmware version : %02x\n", pmu_version); +#ifdef CONFIG_PMAC_PBOOK + p += sprintf(p, "AC Power : %d\n", + ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0)); + p += sprintf(p, "Battery count : %d\n", pmu_battery_count); +#endif /* CONFIG_PMAC_PBOOK */ + + return p - page; +} + +static int __pmac +proc_get_irqstats(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int i; + char* p = page; + static const char *irq_names[] = { + "Total CB1 triggered events", + "Total GPIO1 triggered events", + "PC-Card eject button", + "Sound/Brightness button", + "ADB message", + "Battery state change", + "Environment interrupt", + "Tick timer", + "Ghost interrupt (zero len)", + "Empty interrupt (empty mask)", + "Max irqs in a row" + }; + + for (i=0; i<11; i++) { + p += sprintf(p, " %2u: %10u (%s)\n", + i, pmu_irq_stats[i], irq_names[i]); + } + return p - page; +} + +#ifdef CONFIG_PMAC_PBOOK +static int __pmac +proc_get_batt(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int batnum = (int)data; + char *p = page; + + p += sprintf(p, "\n"); + p += sprintf(p, "flags : %08x\n", + pmu_batteries[batnum].flags); + p += sprintf(p, "charge : %d\n", + pmu_batteries[batnum].charge); + p += sprintf(p, "max_charge : %d\n", + pmu_batteries[batnum].max_charge); + p += sprintf(p, "current : %d\n", + pmu_batteries[batnum].amperage); + p += sprintf(p, "voltage : %d\n", + pmu_batteries[batnum].voltage); + p += sprintf(p, "time rem. : %d\n", + pmu_batteries[batnum].time_remaining); + + return p - page; +} +#endif /* CONFIG_PMAC_PBOOK */ + +static int __pmac +proc_read_options(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *p = page; + +#ifdef CONFIG_PMAC_PBOOK + if (pmu_kind == PMU_KEYLARGO_BASED && + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0) + p += sprintf(p, "lid_wakeup=%d\n", option_lid_wakeup); +#endif /* CONFIG_PMAC_PBOOK */ + if (pmu_kind == PMU_KEYLARGO_BASED) + p += sprintf(p, "server_mode=%d\n", option_server_mode); + + return p - page; +} + +static int __pmac +proc_write_options(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char tmp[33]; + char *label, *val; + unsigned long fcount = count; + + if (!count) + return -EINVAL; + if (count > 32) + count = 32; + if (copy_from_user(tmp, buffer, count)) + return -EFAULT; + tmp[count] = 0; + + label = tmp; + while(*label == ' ') + label++; + val = label; + while(*val && (*val != '=')) { + if (*val == ' ') + *val = 0; + val++; + } + if ((*val) == 0) + return -EINVAL; + *(val++) = 0; + while(*val == ' ') + val++; +#ifdef CONFIG_PMAC_PBOOK + if (pmu_kind == PMU_KEYLARGO_BASED && + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0) + if (!strcmp(label, "lid_wakeup")) + option_lid_wakeup = ((*val) == '1'); +#endif /* CONFIG_PMAC_PBOOK */ + if (pmu_kind == PMU_KEYLARGO_BASED && !strcmp(label, "server_mode")) { + int new_value; + new_value = ((*val) == '1'); + if (new_value != option_server_mode) + pmu_set_server_mode(new_value); + } + return fcount; +} + +#ifdef CONFIG_ADB +/* Send an ADB command */ +static int __pmac +pmu_send_request(struct adb_request *req, int sync) +{ + int i, ret; + + if ((vias == NULL) || (!pmu_fully_inited)) { + req->complete = 1; + return -ENXIO; + } + + ret = -EINVAL; + + switch (req->data[0]) { + case PMU_PACKET: + for (i = 0; i < req->nbytes - 1; ++i) + req->data[i] = req->data[i+1]; + --req->nbytes; + if (pmu_data_len[req->data[0]][1] != 0) { + req->reply[0] = ADB_RET_OK; + req->reply_len = 1; + } else + req->reply_len = 0; + ret = pmu_queue_request(req); + break; + case CUDA_PACKET: + switch (req->data[1]) { + case CUDA_GET_TIME: + if (req->nbytes != 2) + break; + req->data[0] = PMU_READ_RTC; + req->nbytes = 1; + req->reply_len = 3; + req->reply[0] = CUDA_PACKET; + req->reply[1] = 0; + req->reply[2] = CUDA_GET_TIME; + ret = pmu_queue_request(req); + break; + case CUDA_SET_TIME: + if (req->nbytes != 6) + break; + req->data[0] = PMU_SET_RTC; + req->nbytes = 5; + for (i = 1; i <= 4; ++i) + req->data[i] = req->data[i+1]; + req->reply_len = 3; + req->reply[0] = CUDA_PACKET; + req->reply[1] = 0; + req->reply[2] = CUDA_SET_TIME; + ret = pmu_queue_request(req); + break; + } + break; + case ADB_PACKET: + if (!pmu_has_adb) + return -ENXIO; + for (i = req->nbytes - 1; i > 1; --i) + req->data[i+2] = req->data[i]; + req->data[3] = req->nbytes - 2; + req->data[2] = pmu_adb_flags; + /*req->data[1] = req->data[1];*/ + req->data[0] = PMU_ADB_CMD; + req->nbytes += 2; + req->reply_expected = 1; + req->reply_len = 0; + ret = pmu_queue_request(req); + break; + } + if (ret) { + req->complete = 1; + return ret; + } + + if (sync) + while (!req->complete) + pmu_poll(); + + return 0; +} + +/* Enable/disable autopolling */ +static int __pmac +pmu_adb_autopoll(int devs) +{ + struct adb_request req; + + if ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb) + return -ENXIO; + + if (devs) { + adb_dev_map = devs; + pmu_request(&req, NULL, 5, PMU_ADB_CMD, 0, 0x86, + adb_dev_map >> 8, adb_dev_map); + pmu_adb_flags = 2; + } else { + pmu_request(&req, NULL, 1, PMU_ADB_POLL_OFF); + pmu_adb_flags = 0; + } + while (!req.complete) + pmu_poll(); + return 0; +} + +/* Reset the ADB bus */ +static int __pmac +pmu_adb_reset_bus(void) +{ + struct adb_request req; + int save_autopoll = adb_dev_map; + + if ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb) + return -ENXIO; + + /* anyone got a better idea?? */ + pmu_adb_autopoll(0); + + req.nbytes = 5; + req.done = NULL; + req.data[0] = PMU_ADB_CMD; + req.data[1] = 0; + req.data[2] = ADB_BUSRESET; + req.data[3] = 0; + req.data[4] = 0; + req.reply_len = 0; + req.reply_expected = 1; + if (pmu_queue_request(&req) != 0) { + printk(KERN_ERR "pmu_adb_reset_bus: pmu_queue_request failed\n"); + return -EIO; + } + pmu_wait_complete(&req); + + if (save_autopoll != 0) + pmu_adb_autopoll(save_autopoll); + + return 0; +} +#endif /* CONFIG_ADB */ + +/* Construct and send a pmu request */ +int __openfirmware +pmu_request(struct adb_request *req, void (*done)(struct adb_request *), + int nbytes, ...) +{ + va_list list; + int i; + + if (vias == NULL) + return -ENXIO; + + if (nbytes < 0 || nbytes > 32) { + printk(KERN_ERR "pmu_request: bad nbytes (%d)\n", nbytes); + req->complete = 1; + return -EINVAL; + } + req->nbytes = nbytes; + req->done = done; + va_start(list, nbytes); + for (i = 0; i < nbytes; ++i) + req->data[i] = va_arg(list, int); + va_end(list); + req->reply_len = 0; + req->reply_expected = 0; + return pmu_queue_request(req); +} + +int __pmac +pmu_queue_request(struct adb_request *req) +{ + unsigned long flags; + int nsend; + + if (via == NULL) { + req->complete = 1; + return -ENXIO; + } + if (req->nbytes <= 0) { + req->complete = 1; + return 0; + } + nsend = pmu_data_len[req->data[0]][0]; + if (nsend >= 0 && req->nbytes != nsend + 1) { + req->complete = 1; + return -EINVAL; + } + + req->next = NULL; + req->sent = 0; + req->complete = 0; + + spin_lock_irqsave(&pmu_lock, flags); + if (current_req != 0) { + last_req->next = req; + last_req = req; + } else { + current_req = req; + last_req = req; + if (pmu_state == idle) + pmu_start(); + } + spin_unlock_irqrestore(&pmu_lock, flags); + + return 0; +} + +static inline void +wait_for_ack(void) +{ + /* Sightly increased the delay, I had one occurrence of the message + * reported + */ + int timeout = 4000; + while ((in_8(&via[B]) & TACK) == 0) { + if (--timeout < 0) { + printk(KERN_ERR "PMU not responding (!ack)\n"); + return; + } + udelay(10); + } +} + +/* New PMU seems to be very sensitive to those timings, so we make sure + * PCI is flushed immediately */ +static inline void +send_byte(int x) +{ + volatile unsigned char __iomem *v = via; + + out_8(&v[ACR], in_8(&v[ACR]) | SR_OUT | SR_EXT); + out_8(&v[SR], x); + out_8(&v[B], in_8(&v[B]) & ~TREQ); /* assert TREQ */ + (void)in_8(&v[B]); +} + +static inline void |