patch-2.3.20 linux/drivers/macintosh/via-pmu.c
Next file: linux/drivers/misc/Config.in
Previous file: linux/drivers/macintosh/via-cuda.c
Back to the patch index
Back to the overall index
- Lines: 1069
- Date:
Thu Oct 7 10:17:09 1999
- Orig file:
v2.3.19/linux/drivers/macintosh/via-pmu.c
- Orig date:
Tue Aug 31 17:29:14 1999
diff -u --recursive --new-file v2.3.19/linux/drivers/macintosh/via-pmu.c linux/drivers/macintosh/via-pmu.c
@@ -21,10 +21,12 @@
#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 <asm/prom.h>
-#include <asm/adb.h>
-#include <asm/pmu.h>
-#include <asm/cuda.h>
+#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/system.h>
@@ -32,6 +34,7 @@
#include <asm/irq.h>
#include <asm/feature.h>
#include <asm/uaccess.h>
+#include <asm/mmu_context.h>
/* Misc minor number allocated for /dev/pmu */
#define PMU_MINOR 154
@@ -70,6 +73,7 @@
#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 enum pmu_state {
@@ -98,11 +102,13 @@
int asleep;
struct notifier_block *sleep_notifier_list;
+static int pmu_probe(void);
+static int pmu_init(void);
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 int pmu_adb_send_request(struct adb_request *req, int sync);
+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);
static void send_byte(int x);
@@ -112,15 +118,31 @@
static void pmu_handle_data(unsigned char *data, int len,
struct pt_regs *regs);
static void set_volume(int level);
+#ifdef CONFIG_PMAC_PBOOK
+static void pmu_pass_intr(unsigned char *data, int len);
+#endif
-static struct adb_controller pmu_controller = {
- ADB_VIAPMU,
- pmu_adb_send_request,
+struct adb_driver via_pmu_driver = {
+ "PMU",
+ pmu_probe,
+ pmu_init,
+ pmu_send_request,
+ /*pmu_queue_request,*/
pmu_adb_autopoll,
- pmu_adb_reset_bus,
- pmu_poll
+ pmu_poll,
+ pmu_adb_reset_bus
};
+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
@@ -164,13 +186,21 @@
/*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",
+};
-void __openfirmware
+int __openfirmware
find_via_pmu()
{
+ if (via != 0)
+ return 1;
vias = find_devices("via-pmu");
if (vias == 0)
- return;
+ return 0;
if (vias->next != 0)
printk(KERN_WARNING "Warning: only using 1st via-pmu\n");
@@ -179,7 +209,7 @@
#if 0
{ int i;
- printk("via_pmu_init: node = %p, addrs =", vias->node);
+ printk("find_via_pmu: node = %p, addrs =", vias->node);
for (i = 0; i < vias->n_addrs; ++i)
printk(" %x(%x)", vias->addrs[i].address, vias->addrs[i].size);
printk(", intrs =");
@@ -192,12 +222,14 @@
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;
+ return 0;
}
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
@@ -209,23 +241,28 @@
pmu_state = idle;
- if (!init_pmu())
+ if (!init_pmu()) {
via = NULL;
+ return 0;
+ }
- adb_controller = &pmu_controller;
+ printk(KERN_INFO "PMU driver initialized for %s\n",
+ pbook_type[pmu_kind]);
+ sys_ctrler = SYS_CTRLER_PMU;
+ return 1;
+}
- if (via)
- printk(KERN_INFO "PMU driver initialized for %s\n",
- (pmu_kind == PMU_OHARE_BASED) ? "PowerBook 2400/3400/3500(G3)" :
- ((pmu_kind == PMU_HEATHROW_BASED) ? "PowerBook G3 Series" :
- "Unknown PowerBook"));
+static int __openfirmware
+pmu_probe()
+{
+ return vias == NULL? -ENODEV: 0;
}
-void __openfirmware
-via_pmu_init(void)
+static int __openfirmware
+pmu_init(void)
{
if (vias == NULL)
- return;
+ return -ENXIO;
bright_req_1.complete = 1;
bright_req_2.complete = 1;
@@ -235,7 +272,7 @@
(void *)0)) {
printk(KERN_ERR "VIA-PMU: can't get irq %d\n",
vias->intrs[0].line);
- return;
+ return -ENXIO;
}
/* Enable interrupts */
@@ -245,6 +282,8 @@
/* Enable backlight */
pmu_enable_backlight(1);
+
+ return 0;
}
static int __openfirmware
@@ -292,20 +331,19 @@
/* Send an ADB command */
static int __openfirmware
-pmu_adb_send_request(struct adb_request *req, int sync)
+pmu_send_request(struct adb_request *req, int sync)
{
- int i, ret;
+ int i, ret;
- if ((vias == NULL) || (!pmu_fully_inited))
- {
- req->complete = 1;
- return -ENXIO;
- }
+ if ((vias == NULL) || (!pmu_fully_inited)) {
+ req->complete = 1;
+ return -ENXIO;
+ }
- ret = -EINVAL;
-
- switch (req->data[0]) {
- case PMU_PACKET:
+ 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;
@@ -316,7 +354,7 @@
req->reply_len = 0;
ret = pmu_queue_request(req);
break;
- case CUDA_PACKET:
+ case CUDA_PACKET:
switch (req->data[1]) {
case CUDA_GET_TIME:
if (req->nbytes != 2)
@@ -344,7 +382,7 @@
break;
}
break;
- case ADB_PACKET:
+ case ADB_PACKET:
for (i = req->nbytes - 1; i > 1; --i)
req->data[i+2] = req->data[i];
req->data[3] = req->nbytes - 2;
@@ -356,19 +394,17 @@
req->reply_len = 0;
ret = pmu_queue_request(req);
break;
- }
- if (ret)
- {
- req->complete = 1;
- return ret;
- }
-
- if (sync) {
- while (!req->complete)
- pmu_poll();
- }
+ }
+ if (ret) {
+ req->complete = 1;
+ return ret;
+ }
+
+ if (sync)
+ while (!req->complete)
+ pmu_poll();
- return 0;
+ return 0;
}
/* Enable/disable autopolling */
@@ -513,19 +549,25 @@
static void __openfirmware
send_byte(int x)
{
- out_8(&via[ACR], 0x1c);
- out_8(&via[SR], x);
- out_8(&via[B], via[B] & ~0x10); /* assert TREQ */
+ volatile unsigned char *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 */
}
static void __openfirmware
recv_byte()
{
- out_8(&via[ACR], 0x0c);
- in_8(&via[SR]); /* resets SR */
- out_8(&via[B], via[B] & ~0x10);
+ volatile unsigned char *v = via;
+
+ out_8(&v[ACR], (in_8(&v[ACR]) & ~SR_OUT) | SR_EXT);
+ in_8(&v[SR]); /* resets SR */
+ out_8(&v[B], in_8(&v[B]) & ~0x10);
}
+static int disable_poll;
+
static void __openfirmware
pmu_start()
{
@@ -545,7 +587,9 @@
data_len = pmu_data_len[req->data[0]][0];
/* set the shift register to shift out and send a byte */
+ ++disable_poll;
send_byte(req->data[0]);
+ --disable_poll;
out:
restore_flags(flags);
@@ -554,13 +598,15 @@
void __openfirmware
pmu_poll()
{
- int ie;
+ unsigned long flags;
- __save_flags(ie);
- __cli();
+ if (disable_poll)
+ return;
+ save_flags(flags);
+ cli();
if (via[IFR] & (SR_INT | CB1_INT))
via_pmu_interrupt(0, 0, 0);
- __restore_flags(ie);
+ restore_flags(flags);
}
static void __openfirmware
@@ -569,6 +615,7 @@
int intr;
int nloop = 0;
+ ++disable_poll;
while ((intr = in_8(&via[IFR])) != 0) {
if (++nloop > 1000) {
printk(KERN_DEBUG "PMU: stuck in intr loop, "
@@ -580,11 +627,9 @@
else if (intr & CB1_INT) {
adb_int_pending = 1;
out_8(&via[IFR], CB1_INT);
- } else
- {
- /* -- Disabled printk, will happen _really_ often on
- PowerBooks ((CB2 interrupts) --
- printk(KERN_DEBUG "PMU: spurrious interrupt intr=%x\n", intr); */
+ }
+ intr &= ~(SR_INT | CB1_INT);
+ if (intr != 0) {
out_8(&via[IFR], intr);
}
}
@@ -597,6 +642,7 @@
pmu_start();
}
}
+ --disable_poll;
}
static void __openfirmware
@@ -605,17 +651,17 @@
struct adb_request *req;
int bite, timeout;
+ if (via[B] & TREQ) {
+ printk(KERN_ERR "PMU: spurious SR intr (%x)\n", via[B]);
+ out_8(&via[IFR], SR_INT);
+ return;
+ }
if (via[B] & TACK)
- printk(KERN_DEBUG "PMU: sr_intr but ack still high! (%x)\n",
+ printk(KERN_ERR "PMU: sr_intr but ack still high! (%x)\n",
via[B]);
- /* if reading grab the byte, and reset the interrupt */
- if ((via[ACR] & SR_OUT) == 0)
- bite = in_8(&via[SR]);
- out_8(&via[IFR], SR_INT);
-
/* reset TREQ and wait for TACK to go high */
- out_8(&via[B], via[B] | TREQ);
+ out_8(&via[B], in_8(&via[B]) | TREQ);
timeout = 3200;
while ((in_8(&via[B]) & TACK) == 0) {
if (--timeout < 0) {
@@ -625,6 +671,11 @@
udelay(10);
}
+ /* if reading grab the byte, and reset the interrupt */
+ if (pmu_state == reading || pmu_state == reading_intr)
+ bite = in_8(&via[SR]);
+ out_8(&via[IFR], SR_INT);
+
switch (pmu_state) {
case sending:
req = current_req;
@@ -707,8 +758,6 @@
static void __openfirmware
pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs)
{
- static int show_pmu_ints = 1;
-
asleep = 0;
if (len < 1) {
adb_int_pending = 0;
@@ -730,21 +779,34 @@
}
pmu_done(req);
} else {
- adb_input(data+1, len-1, regs, 1);
+#ifdef CONFIG_XMON
+ if (len == 4 && data[1] == 0x2c) {
+ extern int xmon_wants_key, xmon_pmu_keycode;
+ if (xmon_wants_key) {
+ xmon_pmu_keycode = data[2];
+ return;
+ }
+ }
+#endif /* CONFIG_XMON */
+ /*
+ * XXX On the [23]400 the PMU gives us an up
+ * event for keycodes 0x74 or 0x75 when the PC
+ * card eject buttons are released, so we
+ * ignore those events.
+ */
+ if (!(pmu_kind == PMU_OHARE_BASED && len == 4
+ && data[1] == 0x2c && data[3] == 0xff
+ && (data[2] & ~1) == 0xf4))
+ adb_input(data+1, len-1, regs, 1);
}
+ } else if (data[0] == 0x08 && len == 3) {
+ /* sound/brightness buttons pressed */
+ pmu_set_brightness(data[1] >> 3);
+ set_volume(data[2]);
} else {
- if (data[0] == 0x08 && len == 3) {
- /* sound/brightness buttons pressed */
- pmu_set_brightness(data[1] >> 3);
- set_volume(data[2]);
- } else if (show_pmu_ints
- && !(data[0] == PMU_INT_TICK && len == 1)) {
- int i;
- printk(KERN_DEBUG "pmu intr");
- for (i = 0; i < len; ++i)
- printk(" %.2x", data[i]);
- printk("\n");
- }
+#ifdef CONFIG_PMAC_PBOOK
+ pmu_pass_intr(data, len);
+#endif
}
}
@@ -759,38 +821,46 @@
struct adb_request req;
if (vias == NULL)
- return ;
+ return;
- if (on) {
- /* first call: get current backlight value */
- if (backlight_level < 0) {
- switch(pmu_kind) {
- case PMU_OHARE_BASED:
+ /* first call: get current backlight value */
+ if (on && backlight_level < 0) {
+ switch (pmu_kind) {
+ case PMU_OHARE_BASED:
pmu_request(&req, NULL, 2, 0xd9, 0);
while (!req.complete)
pmu_poll();
backlight_level = req.reply[1] >> 3;
- printk(KERN_DEBUG "pmu: controls returned bright: %d\n", (int)req.reply[1]);
break;
- case PMU_HEATHROW_BASED:
+ case PMU_HEATHROW_BASED:
+ /* We cannot use nvram_read_byte here (not yet initialized) */
pmu_request(&req, NULL, 3, PMU_READ_NVRAM, 0x14, 0xe);
while (!req.complete)
pmu_poll();
- printk(KERN_DEBUG "pmu: nvram returned bright: %d\n", (int)req.reply[1]);
backlight_level = req.reply[1];
+ printk(KERN_DEBUG "pmu: nvram returned bright: %d\n", backlight_level);
break;
- default:
+ case PMU_PADDINGTON_BASED:
+ /* the G3 PB 1999 has a backlight node
+ and chrp-structured nvram */
+ /* XXX should read macos's "blkt" property in nvram
+ for this node. For now this ensures that the
+ backlight doesn't go off as soon as linux boots. */
+ backlight_level = 20;
+ break;
+ default:
backlight_enabled = 0;
return;
}
- }
- pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT,
- LEVEL_TO_BRIGHT(backlight_level));
- while (!req.complete)
- pmu_poll();
+ }
+ if (on) {
+ pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT,
+ LEVEL_TO_BRIGHT(backlight_level));
+ while (!req.complete)
+ pmu_poll();
}
pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
- PMU_POW_BACKLIGHT | (on ? PMU_POW_ON : PMU_POW_OFF));
+ PMU_POW_BACKLIGHT | (on ? PMU_POW_ON : PMU_POW_OFF));
while (!req.complete)
pmu_poll();
backlight_enabled = on;
@@ -847,7 +917,7 @@
{
struct adb_request req;
- __cli();
+ cli();
pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB |
PMU_INT_TICK );
@@ -866,7 +936,7 @@
{
struct adb_request req;
- __cli();
+ cli();
pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB |
PMU_INT_TICK );
@@ -881,13 +951,69 @@
;
}
+#ifdef CONFIG_PMAC_PBOOK
+
+static LIST_HEAD(sleep_notifiers);
+
int
-pmu_present(void)
+pmu_register_sleep_notifier(struct pmu_sleep_notifier *n)
{
- return (adb_controller && (adb_controller->kind == ADB_VIAPMU) && vias);
+ struct list_head *list;
+ struct pmu_sleep_notifier *current;
+
+ for (list = sleep_notifiers.next; list != &sleep_notifiers;
+ list = list->next) {
+ current = list_entry(list, struct pmu_sleep_notifier, list);
+ if (n->priority > current->priority)
+ break;
+ }
+ __list_add(&n->list, list->prev, list);
+ return 0;
}
-#ifdef CONFIG_PMAC_PBOOK
+int
+pmu_unregister_sleep_notifier(struct pmu_sleep_notifier* n)
+{
+ if (n->list.next == 0)
+ return -ENOENT;
+ list_del(&n->list);
+ n->list.next = 0;
+ return 0;
+}
+
+/* Sleep is broadcast last-to-first */
+static int
+broadcast_sleep(int when, int can_cancel)
+{
+ int ret = PBOOK_SLEEP_OK;
+ struct list_head *list;
+ struct pmu_sleep_notifier *current;
+
+ for (list = sleep_notifiers.prev; list != &sleep_notifiers;
+ list = list->prev) {
+ current = list_entry(list, struct pmu_sleep_notifier, list);
+ ret = current->notifier_call(current, when);
+ if (can_cancel && (ret != PBOOK_SLEEP_OK))
+ return ret;
+ }
+ return ret;
+}
+
+/* Wake is broadcast first-to-last */
+static int
+broadcast_wake(void)
+{
+ int ret = PBOOK_SLEEP_OK;
+ struct list_head *list;
+ struct pmu_sleep_notifier *current;
+
+ for (list = sleep_notifiers.next; list != &sleep_notifiers;
+ list = list->next) {
+ current = list_entry(list, struct pmu_sleep_notifier, list);
+ current->notifier_call(current, PBOOK_WAKE);
+ }
+ return ret;
+}
/*
* This struct is used to store config register values for
@@ -897,10 +1023,11 @@
u16 command;
u16 cache_lat;
u16 intr;
+ u32 rom_address;
} *pbook_pci_saves;
static int n_pbook_pci_saves;
-static inline void __openfirmware
+static void __openfirmware
pbook_pci_save(void)
{
int npci;
@@ -922,12 +1049,13 @@
pci_read_config_word(pd, PCI_COMMAND, &ps->command);
pci_read_config_word(pd, PCI_CACHE_LINE_SIZE, &ps->cache_lat);
pci_read_config_word(pd, PCI_INTERRUPT_LINE, &ps->intr);
+ pci_read_config_dword(pd, PCI_ROM_ADDRESS, &ps->rom_address);
++ps;
--npci;
}
}
-static inline void __openfirmware
+static void __openfirmware
pbook_pci_restore(void)
{
u16 cmd;
@@ -948,7 +1076,7 @@
PCI_BASE_ADDRESS_0 + j*4,
pd->resource[j].start);
pci_write_config_dword(pd, PCI_ROM_ADDRESS,
- pd->resource[PCI_ROM_RESOURCE].start);
+ ps->rom_address);
pci_write_config_word(pd, PCI_CACHE_LINE_SIZE,
ps->cache_lat);
pci_write_config_word(pd, PCI_INTERRUPT_LINE,
@@ -963,43 +1091,147 @@
/*
* Put the powerbook to sleep.
*/
-#define IRQ_ENABLE ((unsigned int *)0xf3000024)
-#define MEM_CTRL ((unsigned int *)0xf8000070)
+
+#define FEATURE_CTRL(base) ((unsigned int *)(base + 0x38))
+#define GRACKLE_PM (1<<7)
+#define GRACKLE_DOZE (1<<5)
+#define GRACKLE_NAP (1<<4)
+#define GRACKLE_SLEEP (1<<3)
+
+int __openfirmware powerbook_sleep_G3(void)
+{
+ int ret;
+ unsigned long save_l2cr;
+ unsigned long save_fcr;
+ unsigned long wait;
+ unsigned short pmcr1;
+ struct adb_request sleep_req;
+ struct device_node *macio;
+ unsigned long macio_base = 0;
-int __openfirmware powerbook_sleep(void)
+ macio = find_devices("mac-io");
+ if (macio != 0 && macio->n_addrs > 0)
+ macio_base = (unsigned long)
+ ioremap(macio->addrs[0].address, 0x40);
+
+ /* Sync the disks. */
+ /* XXX It would be nice to have some way to ensure that
+ * nobody is dirtying any new buffers while we wait. */
+ fsync_dev(0);
+
+ /* Notify device drivers */
+ ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, 1);
+ if (ret != PBOOK_SLEEP_OK) {
+ broadcast_sleep(PBOOK_SLEEP_REJECT, 0);
+ printk("pmu: sleep rejected\n");
+ return -EBUSY;
+ }
+ broadcast_sleep(PBOOK_SLEEP_NOW, 0);
+
+ /* Give the disks a little time to actually finish writing */
+ for (wait = jiffies + (HZ/4); time_before(jiffies, wait); )
+ mb();
+
+ /* Disable all interrupts except pmu */
+ sleep_save_intrs(vias->intrs[0].line);
+
+ /* Make sure the decrementer won't interrupt us */
+ asm volatile("mtdec %0" : : "r" (0x7fffffff));
+#if 0
+ /* Save the state of PCI config space for some slots */
+ pbook_pci_save();
+#endif
+ /* For 750, save backside cache setting and disable it */
+ save_l2cr = _get_L2CR(); /* (returns 0 if not 750) */
+ if (save_l2cr)
+ _set_L2CR(0);
+
+ if (macio_base != 0) {
+ save_fcr = in_le32(FEATURE_CTRL(macio_base));
+ /* Check if this is still valid on older powerbooks */
+ out_le32(FEATURE_CTRL(macio_base), save_fcr & ~(0x00000140UL));
+ }
+
+ if (current->thread.regs && (current->thread.regs->msr & MSR_FP) != 0)
+ giveup_fpu(current);
+
+ grackle_pcibios_read_config_word(0,0,0x70,&pmcr1);
+ /* Apparently, MacOS uses NAP mode for Grackle ??? */
+ pmcr1 &= ~(GRACKLE_DOZE|GRACKLE_SLEEP);
+ pmcr1 |= GRACKLE_PM|GRACKLE_NAP;
+ grackle_pcibios_write_config_word(0, 0, 0x70, pmcr1);
+
+ /* Ask the PMU to put us to sleep */
+ pmu_request(&sleep_req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
+ while (!sleep_req.complete)
+ mb();
+
+ cli();
+ while (pmu_state != idle)
+ pmu_poll();
+
+ /* Call low-level ASM sleep handler */
+ low_sleep_handler();
+
+ /* We're awake again, stop grackle PM */
+ grackle_pcibios_read_config_word(0, 0, 0x70, &pmcr1);
+ pmcr1 &= ~(GRACKLE_PM|GRACKLE_DOZE|GRACKLE_SLEEP|GRACKLE_NAP);
+ grackle_pcibios_write_config_word(0, 0, 0x70, pmcr1);
+
+ sti();
+#if 0
+ /* According to someone from Apple, this should not be needed,
+ at least not for all devices. Let's keep it for now until we
+ have something that works. */
+ pbook_pci_restore();
+#endif
+ set_context(current->mm->context);
+
+ /* Restore L2 cache */
+ if (save_l2cr)
+ _set_L2CR(save_l2cr | 0x200000); /* set invalidate bit */
+
+ /* reenable interrupts */
+ sleep_restore_intrs();
+
+ /* Notify drivers */
+ broadcast_wake();
+
+ return 0;
+}
+
+#define PB3400_MEM_CTRL ((unsigned int *)0xf8000070)
+
+int __openfirmware powerbook_sleep_3400(void)
{
int ret, i, x;
- static int save_backlight;
- static unsigned int save_irqen;
unsigned long msr;
unsigned int hid0;
unsigned long p, wait;
struct adb_request sleep_req;
- /* Notify device drivers */
- ret = notifier_call_chain(&sleep_notifier_list, PBOOK_SLEEP, NULL);
- if (ret & NOTIFY_STOP_MASK)
- return -EBUSY;
-
/* Sync the disks. */
/* XXX It would be nice to have some way to ensure that
* nobody is dirtying any new buffers while we wait. */
fsync_dev(0);
- /* Turn off the display backlight */
- save_backlight = backlight_enabled;
- if (save_backlight)
- pmu_enable_backlight(0);
+ /* Notify device drivers */
+ ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, 1);
+ if (ret != PBOOK_SLEEP_OK) {
+ broadcast_sleep(PBOOK_SLEEP_REJECT, 0);
+ printk("pmu: sleep rejected\n");
+ return -EBUSY;
+ }
+ broadcast_sleep(PBOOK_SLEEP_NOW, 0);
/* Give the disks a little time to actually finish writing */
for (wait = jiffies + (HZ/4); time_before(jiffies, wait); )
mb();
/* Disable all interrupts except pmu */
- save_irqen = in_le32(IRQ_ENABLE);
- for (i = 0; i < 32; ++i)
- if (i != vias->intrs[0].line && (save_irqen & (1 << i)))
- disable_irq(i);
+ sleep_save_intrs(vias->intrs[0].line);
+
+ /* Make sure the decrementer won't interrupt us */
asm volatile("mtdec %0" : : "r" (0x7fffffff));
/* Save the state of PCI config space for some slots */
@@ -1008,9 +1240,9 @@
/* Set the memory controller to keep the memory refreshed
while we're asleep */
for (i = 0x403f; i >= 0x4000; --i) {
- out_be32(MEM_CTRL, i);
+ out_be32(PB3400_MEM_CTRL, i);
do {
- x = (in_be32(MEM_CTRL) >> 16) & 0x3ff;
+ x = (in_be32(PB3400_MEM_CTRL) >> 16) & 0x3ff;
} while (x == 0);
if (x >= 0x100)
break;
@@ -1020,6 +1252,7 @@
pmu_request(&sleep_req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
while (!sleep_req.complete)
mb();
+
/* displacement-flush the L2 cache - necessary? */
for (p = KERNELBASE; p < KERNELBASE + 0x100000; p += 0x1000)
i = *(volatile int *)p;
@@ -1035,7 +1268,7 @@
udelay(10);
/* OK, we're awake again, start restoring things */
- out_be32(MEM_CTRL, 0x3f);
+ out_be32(PB3400_MEM_CTRL, 0x3f);
pbook_pci_restore();
/* wait for the PMU interrupt sequence to complete */
@@ -1043,21 +1276,10 @@
mb();
/* reenable interrupts */
- for (i = 0; i < 32; ++i)
- if (i != vias->intrs[0].line && (save_irqen & (1 << i)))
- enable_irq(i);
+ sleep_restore_intrs();
/* Notify drivers */
- notifier_call_chain(&sleep_notifier_list, PBOOK_WAKE, NULL);
-
- /* reenable ADB autopoll */
- pmu_adb_autopoll(adb_dev_map);
-
- /* Turn on the screen backlight, if it was on before */
- if (save_backlight)
- pmu_enable_backlight(1);
-
- /* Wait for the hard disk to spin up */
+ broadcast_wake();
return 0;
}
@@ -1065,15 +1287,112 @@
/*
* Support for /dev/pmu device
*/
+#define RB_SIZE 10
+struct pmu_private {
+ struct list_head list;
+ int rb_get;
+ int rb_put;
+ struct rb_entry {
+ unsigned short len;
+ unsigned char data[16];
+ } rb_buf[RB_SIZE];
+ wait_queue_head_t wait;
+ spinlock_t lock;
+};
+
+static LIST_HEAD(all_pmu_pvt);
+static spinlock_t all_pvt_lock = SPIN_LOCK_UNLOCKED;
+
+static void pmu_pass_intr(unsigned char *data, int len)
+{
+ struct pmu_private *pp;
+ struct list_head *list;
+ int i;
+ unsigned long flags;
+
+ if (len > sizeof(pp->rb_buf[0].data))
+ len = sizeof(pp->rb_buf[0].data);
+ spin_lock_irqsave(&all_pvt_lock, flags);
+ for (list = &all_pmu_pvt; (list = list->next) != &all_pmu_pvt; ) {
+ pp = list_entry(list, struct pmu_private, list);
+ i = pp->rb_put + 1;
+ if (i >= RB_SIZE)
+ i = 0;
+ if (i != pp->rb_get) {
+ struct rb_entry *rp = &pp->rb_buf[pp->rb_put];
+ rp->len = len;
+ memcpy(rp->data, data, len);
+ pp->rb_put = i;
+ wake_up_interruptible(&pp->wait);
+ }
+ }
+ spin_unlock_irqrestore(&all_pvt_lock, flags);
+}
+
static int __openfirmware pmu_open(struct inode *inode, struct file *file)
{
+ struct pmu_private *pp;
+ unsigned long flags;
+
+ pp = kmalloc(sizeof(struct pmu_private), GFP_KERNEL);
+ if (pp == 0)
+ return -ENOMEM;
+ pp->rb_get = pp->rb_put = 0;
+ spin_lock_init(&pp->lock);
+ init_waitqueue_head(&pp->wait);
+ spin_lock_irqsave(&all_pvt_lock, flags);
+ list_add(&pp->list, &all_pmu_pvt);
+ spin_unlock_irqrestore(&all_pvt_lock, flags);
+ file->private_data = pp;
return 0;
}
static ssize_t __openfirmware pmu_read(struct file *file, char *buf,
size_t count, loff_t *ppos)
{
- return 0;
+ struct pmu_private *pp = file->private_data;
+ DECLARE_WAITQUEUE(wait, current);
+ int ret;
+
+ if (count < 1 || pp == 0)
+ return -EINVAL;
+ ret = verify_area(VERIFY_WRITE, buf, count);
+ if (ret)
+ return ret;
+
+ add_wait_queue(&pp->wait, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+
+ for (;;) {
+ ret = -EAGAIN;
+ spin_lock(&pp->lock);
+ if (pp->rb_get != pp->rb_put) {
+ int i = pp->rb_get;
+ struct rb_entry *rp = &pp->rb_buf[i];
+ ret = rp->len;
+ if (ret > count)
+ ret = count;
+ if (ret > 0 && copy_to_user(buf, rp->data, ret))
+ ret = -EFAULT;
+ if (++i >= RB_SIZE)
+ i = 0;
+ pp->rb_get = i;
+ }
+ spin_unlock(&pp->lock);
+ if (ret >= 0)
+ break;
+
+ if (file->f_flags & O_NONBLOCK)
+ break;
+ ret = -ERESTARTSYS;
+ if (signal_pending(current))
+ break;
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&pp->wait, &wait);
+
+ return ret;
}
static ssize_t __openfirmware pmu_write(struct file *file, const char *buf,
@@ -1082,26 +1401,65 @@
return 0;
}
+static unsigned int pmu_fpoll(struct file *filp, poll_table *wait)
+{
+ struct pmu_private *pp = filp->private_data;
+ unsigned int mask = 0;
+
+ if (pp == 0)
+ return 0;
+ poll_wait(filp, &pp->wait, wait);
+ spin_lock(&pp->lock);
+ if (pp->rb_get != pp->rb_put)
+ mask |= POLLIN;
+ spin_unlock(&pp->lock);
+ return mask;
+}
+
+static int pmu_release(struct inode *inode, struct file *file)
+{
+ struct pmu_private *pp = file->private_data;
+ unsigned long flags;
+
+ if (pp != 0) {
+ file->private_data = 0;
+ spin_lock_irqsave(&all_pvt_lock, flags);
+ list_del(&pp->list);
+ spin_unlock_irqrestore(&all_pvt_lock, flags);
+ kfree(pp);
+ }
+ return 0;
+}
+
/* Note: removed __openfirmware here since it causes link errors */
-static int /*__openfirmware*/ pmu_ioctl(struct inode * inode, struct file *filp,
+static int pmu_ioctl(struct inode * inode, struct file *filp,
u_int cmd, u_long arg)
{
int error;
__u32 value;
switch (cmd) {
- case PMU_IOC_SLEEP:
- if (pmu_kind != PMU_OHARE_BASED)
- return -ENOSYS;
- return powerbook_sleep();
- case PMU_IOC_GET_BACKLIGHT:
+ case PMU_IOC_SLEEP:
+ switch (pmu_kind) {
+ case PMU_OHARE_BASED:
+ error = powerbook_sleep_3400();
+ break;
+ case PMU_HEATHROW_BASED:
+ case PMU_PADDINGTON_BASED:
+ error = powerbook_sleep_G3();
+ break;
+ default:
+ error = ENOSYS;
+ }
+ return error;
+ case PMU_IOC_GET_BACKLIGHT:
return put_user(backlight_level, (__u32 *)arg);
- case PMU_IOC_SET_BACKLIGHT:
+ case PMU_IOC_SET_BACKLIGHT:
error = get_user(value, (__u32 *)arg);
if (!error)
pmu_set_brightness(value);
return error;
- case PMU_IOC_GET_MODEL:
+ case PMU_IOC_GET_MODEL:
return put_user(pmu_kind, (__u32 *)arg);
}
return -EINVAL;
@@ -1112,12 +1470,12 @@
pmu_read,
pmu_write,
NULL, /* no readdir */
- NULL, /* no poll yet */
+ pmu_fpoll,
pmu_ioctl,
NULL, /* no mmap */
pmu_open,
NULL, /* flush */
- NULL /* no release */
+ pmu_release,
};
static struct miscdevice pmu_device = {
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)