📄 via-pmu.c
字号:
/* * 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. * * todo: - Check this driver for smp safety (new Core99 motherboards). * - Cleanup synchro between VIA interrupt and GPIO-based PMU * interrupt. * * */#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/malloc.h>#include <linux/poll.h>#include <linux/adb.h>#include <linux/pmu.h>#include <linux/cuda.h>#include <linux/smp_lock.h>#include <linux/spinlock.h>#include <asm/prom.h>#include <asm/machdep.h>#include <asm/io.h>#include <asm/pgtable.h>#include <asm/system.h>#include <asm/init.h>#include <asm/irq.h>#include <asm/hardirq.h>#include <asm/feature.h>#include <asm/uaccess.h>#include <asm/mmu_context.h>#ifdef CONFIG_PMAC_BACKLIGHT#include <asm/backlight.h>#endif/* Some compile options */#undef SUSPEND_USES_PMU/* Misc minor number allocated for /dev/pmu */#define PMU_MINOR 154static volatile unsigned char *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,} pmu_state;static struct adb_request *current_req;static struct adb_request *last_req;static struct adb_request *req_awaiting_reply;static unsigned char interrupt_data[256]; /* Made bigger: I've been told that might happen */static unsigned char *reply_ptr;static int data_index;static int data_len;static volatile int adb_int_pending;static int pmu_adb_flags;static int adb_dev_map = 0;static struct adb_request bright_req_1, bright_req_2, bright_req_3;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 *gpio_reg = NULL;static int gpio_irq = -1;static volatile int pmu_suspended = 0;static spinlock_t pmu_lock;int asleep;struct notifier_block *sleep_notifier_list;#ifdef CONFIG_ADBstatic 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 void via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs);static void send_byte(int x);static void recv_byte(void);static void pmu_sr_intr(struct pt_regs *regs);static void pmu_done(struct adb_request *req);static void pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs);static void set_volume(int level);static void gpio1_interrupt(int irq, void *arg, struct pt_regs *regs);#ifdef CONFIG_PMAC_BACKLIGHTstatic 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_PBOOKstatic void pmu_pass_intr(unsigned char *data, int len);#endif#ifdef CONFIG_ADBstruct adb_driver via_pmu_driver = { "PMU", pmu_probe, pmu_init, pmu_send_request, pmu_adb_autopoll, pmu_poll, pmu_adb_reset_bus};#endif /* CONFIG_ADB */extern void low_sleep_handler(void);extern void sleep_save_intrs(int);extern void sleep_restore_intrs(void);extern int grackle_pcibios_read_config_word(unsigned char bus, unsigned char dev_fn, unsigned char offset, unsigned short *val);extern int grackle_pcibios_write_config_word(unsigned char bus, unsigned char dev_fn, unsigned char offset, unsigned short val);/* * 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_BACKLIGHTstatic struct backlight_controller pmu_backlight_controller = { pmu_set_backlight_enable, pmu_set_backlight_level};#endif /* CONFIG_PMAC_BACKLIGHT */int __openfirmwarefind_via_pmu(){ 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; 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")) { struct device_node *gpio, *gpiop; pmu_kind = PMU_KEYLARGO_BASED; pmu_has_adb = (find_type_devices("adb") != NULL); gpiop = find_devices("gpio"); if (gpiop && gpiop->n_addrs) { gpio_reg = ioremap(gpiop->addrs->address, 0x10); gpio = find_devices("extint-gpio1"); if (gpio && gpio->parent == gpiop && gpio->n_intrs) gpio_irq = gpio->intrs[0].line; } } else pmu_kind = PMU_UNKNOWN; via = (volatile unsigned char *) 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 initialized for %s\n", pbook_type[pmu_kind]); sys_ctrler = SYS_CTRLER_PMU; return 1;}#ifdef CONFIG_ADBstatic int __openfirmwarepmu_probe(){ return vias == NULL? -ENODEV: 0;}static int __openfirmwarepmu_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. * This is called from arch/ppc/kernel/pmac_setup.c:pmac_init2(). */int via_pmu_start(void){ if (vias == NULL) return -ENODEV; bright_req_1.complete = 1; bright_req_2.complete = 1; bright_req_3.complete = 1; 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); } /* Enable interrupts */ out_8(&via[IER], IER_SET | SR_INT | CB1_INT); pmu_fully_inited = 1;#ifdef CONFIG_PMAC_BACKLIGHT /* Enable backlight */ register_backlight_controller(&pmu_backlight_controller, NULL, "pmu");#endif /* CONFIG_PMAC_BACKLIGHT */ /* 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;}static int __openfirmwareinit_pmu(){ 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, 0xfc); 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] = 1; while (interrupt_data[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, 0, 0); udelay(10); } /* Tell PMU we are ready. Which PMU support this ? */ if (pmu_kind == PMU_KEYLARGO_BASED) { pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2); while (!req.complete) pmu_poll(); } return 1;}intpmu_get_model(void){ return pmu_kind;}#ifdef CONFIG_ADB/* Send an ADB command */static int __openfirmwarepmu_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)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -