[PATCH] [RESEND] Update ADB drivers in 2.5

Paul Mackerras (paulus@samba.org)
Sat, 9 Nov 2002 11:00:15 +1100


This patch updates the ADB driver and the three low-level ADB bus
adaptor drivers used on powermacs. The files affected are all in
drivers/macintosh; they are adb.c, macio-adb.c, via-cuda.c and
via-pmu.c.

The main changes in this patch are:

- Remove the use of global cli/sti and replace them with local cli/sti,
spinlocks and semaphores as appropriate.
- Use DECLARE_WORK/schedule_work instead of tq_struct/schedule_task.
- Improvements to the PMU interrupt handling and sleep/wakeup code.

Linus, please apply.

Thanks,
Paul.

diff -urN linux-2.5/drivers/macintosh/adb.c pmac-2.5/drivers/macintosh/adb.c
--- linux-2.5/drivers/macintosh/adb.c 2002-10-31 02:09:29.000000000 +1100
+++ pmac-2.5/drivers/macintosh/adb.c 2002-11-07 14:38:03.000000000 +1100
@@ -34,8 +34,10 @@
#include <linux/wait.h>
#include <linux/init.h>
#include <linux/delay.h>
+#include <linux/spinlock.h>
#include <linux/completion.h>
#include <asm/uaccess.h>
+#include <asm/semaphore.h>
#ifdef CONFIG_PPC
#include <asm/prom.h>
#include <asm/hydra.h>
@@ -75,8 +77,8 @@

struct adb_driver *adb_controller;
struct notifier_block *adb_client_list = NULL;
-static int adb_got_sleep = 0;
-static int adb_inited = 0;
+static int adb_got_sleep;
+static int adb_inited;
static pid_t adb_probe_task_pid;
static DECLARE_MUTEX(adb_probe_mutex);
static struct completion adb_probe_task_comp;
@@ -94,7 +96,7 @@
static int adb_scan_bus(void);
static int do_adb_reset_bus(void);
static void adbdev_init(void);
-
+static int try_handler_change(int, int);

static struct adb_handler {
void (*handler)(unsigned char *, int, struct pt_regs *, int);
@@ -102,6 +104,18 @@
int handler_id;
} adb_handler[16];

+/*
+ * The adb_handler_sem mutex protects all accesses to the original_address
+ * and handler_id fields of adb_handler[i] for all i, and changes to the
+ * handler field.
+ * Accesses to the handler field are protected by the adb_handler_lock
+ * rwlock. It is held across all calls to any handler, so that by the
+ * time adb_unregister returns, we know that the old handler isn't being
+ * called.
+ */
+static DECLARE_MUTEX(adb_handler_sem);
+static rwlock_t adb_handler_lock = RW_LOCK_UNLOCKED;
+
#if 0
static void printADBreply(struct adb_request *req)
{
@@ -254,25 +268,18 @@
SIGCHLD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
}

+static DECLARE_WORK(adb_reset_work, __adb_probe_task, NULL);
+
int
adb_reset_bus(void)
{
- static struct tq_struct tqs = {
- routine: __adb_probe_task,
- };
-
if (__adb_probe_sync) {
do_adb_reset_bus();
return 0;
}

down(&adb_probe_mutex);
-
- /* Create probe thread as a child of keventd */
- if (current_is_keventd())
- __adb_probe_task(NULL);
- else
- schedule_task(&tqs);
+ schedule_work(&adb_reset_work);
return 0;
}

@@ -372,7 +379,6 @@
do_adb_reset_bus(void)
{
int ret, nret, devs;
- unsigned long flags;

if (adb_controller == NULL)
return -ENXIO;
@@ -391,11 +397,11 @@
/* Let the trackpad settle down */
adb_wait_ms(500);
}
-
- save_flags(flags);
- cli();
+
+ down(&adb_handler_sem);
+ write_lock_irq(&adb_handler_lock);
memset(adb_handler, 0, sizeof(adb_handler));
- restore_flags(flags);
+ write_unlock_irq(&adb_handler_lock);

/* That one is still a bit synchronous, oh well... */
if (adb_controller->reset_bus)
@@ -413,6 +419,7 @@
if (adb_controller->autopoll)
adb_controller->autopoll(devs);
}
+ up(&adb_handler_sem);

nret = notifier_call_chain(&adb_client_list, ADB_MSG_POST_RESET, NULL);
if (nret & NOTIFY_STOP_MASK)
@@ -512,30 +519,41 @@
{
int i;

+ down(&adb_handler_sem);
ids->nids = 0;
for (i = 1; i < 16; i++) {
if ((adb_handler[i].original_address == default_id) &&
(!handler_id || (handler_id == adb_handler[i].handler_id) ||
- adb_try_handler_change(i, handler_id))) {
+ try_handler_change(i, handler_id))) {
if (adb_handler[i].handler != 0) {
printk(KERN_ERR
"Two handlers for ADB device %d\n",
default_id);
continue;
}
+ write_lock_irq(&adb_handler_lock);
adb_handler[i].handler = handler;
+ write_unlock_irq(&adb_handler_lock);
ids->id[ids->nids++] = i;
}
}
+ up(&adb_handler_sem);
return ids->nids;
}

int
adb_unregister(int index)
{
- if (!adb_handler[index].handler)
- return -ENODEV;
- adb_handler[index].handler = 0;
+ int ret = -ENODEV;
+
+ down(&adb_handler_sem);
+ write_lock_irq(&adb_handler_lock);
+ if (adb_handler[index].handler) {
+ ret = 0;
+ adb_handler[index].handler = 0;
+ }
+ write_unlock_irq(&adb_handler_lock);
+ up(&adb_handler_sem);
return 0;
}

@@ -544,6 +562,7 @@
{
int i, id;
static int dump_adb_input = 0;
+ void (*handler)(unsigned char *, int, struct pt_regs *, int);

/* We skip keystrokes and mouse moves when the sleep process
* has been started. We stop autopoll, but this is another security
@@ -558,14 +577,15 @@
printk(" %x", buf[i]);
printk(", id = %d\n", id);
}
- if (adb_handler[id].handler != 0) {
- (*adb_handler[id].handler)(buf, nb, regs, autopoll);
- }
+ read_lock(&adb_handler_lock);
+ handler = adb_handler[id].handler;
+ if (handler != 0)
+ (*handler)(buf, nb, regs, autopoll);
+ read_unlock(&adb_handler_lock);
}

-/* Try to change handler to new_id. Will return 1 if successful */
-int
-adb_try_handler_change(int address, int new_id)
+/* Try to change handler to new_id. Will return 1 if successful. */
+static int try_handler_change(int address, int new_id)
{
struct adb_request req;

@@ -585,11 +605,24 @@
}

int
+adb_try_handler_change(int address, int new_id)
+{
+ int ret;
+
+ down(&adb_handler_sem);
+ ret = try_handler_change(address, new_id);
+ up(&adb_handler_sem);
+ return ret;
+}
+
+int
adb_get_infos(int address, int *original_address, int *handler_id)
{
+ down(&adb_handler_sem);
*original_address = adb_handler[address].original_address;
*handler_id = adb_handler[address].handler_id;
-
+ up(&adb_handler_sem);
+
return (*original_address != 0);
}

diff -urN linux-2.5/drivers/macintosh/macio-adb.c pmac-2.5/drivers/macintosh/macio-adb.c
--- linux-2.5/drivers/macintosh/macio-adb.c 2002-02-06 04:40:16.000000000 +1100
+++ pmac-2.5/drivers/macintosh/macio-adb.c 2002-10-13 15:00:41.000000000 +1000
@@ -7,6 +7,7 @@
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
+#include <linux/spinlock.h>
#include <asm/prom.h>
#include <linux/adb.h>
#include <asm/io.h>
@@ -57,7 +58,7 @@

static volatile struct adb_regs *adb;
static struct adb_request *current_req, *last_req;
-static unsigned char adb_rbuf[16];
+static spinlock_t macio_lock = SPIN_LOCK_UNLOCKED;

static int macio_probe(void);
static int macio_init(void);
@@ -66,7 +67,6 @@
static int macio_adb_autopoll(int devs);
static void macio_adb_poll(void);
static int macio_adb_reset_bus(void);
-static void completed(void);

struct adb_driver macio_adb_driver = {
"MACIO",
@@ -107,19 +107,19 @@
adb = (volatile struct adb_regs *)
ioremap(adbs->addrs->address, sizeof(struct adb_regs));

- if (request_irq(adbs->intrs[0].line, macio_adb_interrupt,
- 0, "ADB", (void *)0)) {
- printk(KERN_ERR "ADB: can't get irq %d\n",
- adbs->intrs[0].line);
- return -EAGAIN;
- }
-
out_8(&adb->ctrl.r, 0);
out_8(&adb->intr.r, 0);
out_8(&adb->error.r, 0);
out_8(&adb->active_hi.r, 0xff); /* for now, set all devices active */
out_8(&adb->active_lo.r, 0xff);
out_8(&adb->autopoll.r, APE);
+
+ if (request_irq(adbs->intrs[0].line, macio_adb_interrupt,
+ 0, "ADB", (void *)0)) {
+ printk(KERN_ERR "ADB: can't get irq %d\n",
+ adbs->intrs[0].line);
+ return -EAGAIN;
+ }
out_8(&adb->intr_enb.r, DFB | TAG);

printk("adb: mac-io driver 1.0 for unified ADB\n");
@@ -129,16 +129,27 @@

static int macio_adb_autopoll(int devs)
{
+ unsigned long flags;
+
+ spin_lock_irqsave(&macio_lock, flags);
out_8(&adb->active_hi.r, devs >> 8);
out_8(&adb->active_lo.r, devs);
out_8(&adb->autopoll.r, devs? APE: 0);
+ spin_unlock_irqrestore(&macio_lock, flags);
return 0;
}

static int macio_adb_reset_bus(void)
{
+ unsigned long flags;
int timeout = 1000000;

+ /* Hrm... we may want to not lock interrupts for so
+ * long ... oh well, who uses that chip anyway ? :)
+ * That function will be seldomly used during boot
+ * on rare machines, so...
+ */
+ spin_lock_irqsave(&macio_lock, flags);
out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | ADB_RST);
while ((in_8(&adb->ctrl.r) & ADB_RST) != 0) {
if (--timeout == 0) {
@@ -146,13 +157,14 @@
return -1;
}
}
+ spin_unlock_irqrestore(&macio_lock, flags);
return 0;
}

/* Send an ADB command */
static int macio_send_request(struct adb_request *req, int sync)
{
- unsigned long mflags;
+ unsigned long flags;
int i;

if (req->data[0] != ADB_PACKET)
@@ -167,8 +179,7 @@
req->complete = 0;
req->reply_len = 0;

- save_flags(mflags);
- cli();
+ spin_lock_irqsave(&macio_lock, flags);
if (current_req != 0) {
last_req->next = req;
last_req = req;
@@ -176,7 +187,7 @@
current_req = last_req = req;
out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
}
- restore_flags(mflags);
+ spin_unlock_irqrestore(&macio_lock, flags);

if (sync) {
while (!req->complete)
@@ -190,7 +201,12 @@
{
int i, n, err;
struct adb_request *req;
-
+ unsigned char ibuf[16];
+ int ibuf_len = 0;
+ int complete = 0;
+ int autopoll = 0;
+
+ spin_lock(&macio_lock);
if (in_8(&adb->intr.r) & TAG) {
if ((req = current_req) != 0) {
/* put the current request in */
@@ -202,7 +218,10 @@
out_8(&adb->ctrl.r, DTB + CRE);
} else {
out_8(&adb->ctrl.r, DTB);
- completed();
+ current_req = req->next;
+ complete = 1;
+ if (current_req)
+ out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
}
}
out_8(&adb->intr.r, 0);
@@ -218,39 +237,42 @@
for (i = 0; i < req->reply_len; ++i)
req->reply[i] = in_8(&adb->data[i].r);
}
- completed();
+ current_req = req->next;
+ complete = 1;
+ if (current_req)
+ out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
} else if (err == 0) {
/* autopoll data */
n = in_8(&adb->dcount.r) & HMB;
for (i = 0; i < n; ++i)
- adb_rbuf[i] = in_8(&adb->data[i].r);
- adb_input(adb_rbuf, n, regs,
- in_8(&adb->dcount.r) & APD);
+ ibuf[i] = in_8(&adb->data[i].r);
+ ibuf_len = n;
+ autopoll = (in_8(&adb->dcount.r) & APD) != 0;
}
out_8(&adb->error.r, 0);
out_8(&adb->intr.r, 0);
}
-}
-
-static void completed(void)
-{
- struct adb_request *req = current_req;
-
- req->complete = 1;
- current_req = req->next;
- if (current_req)
- out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
- if (req->done)
- (*req->done)(req);
+ spin_unlock(&macio_lock);
+ if (complete && req) {
+ void (*done)(struct adb_request *) = req->done;
+ mb();
+ req->complete = 1;
+ /* Here, we assume that if the request has a done member, the
+ * struct request will survive to setting req->complete to 1
+ */
+ if (done)
+ (*done)(req);
+ }
+ if (ibuf_len)
+ adb_input(ibuf, ibuf_len, regs, autopoll);
}

static void macio_adb_poll(void)
{
unsigned long flags;

- save_flags(flags);
- cli();
+ local_irq_save(flags);
if (in_8(&adb->intr.r) != 0)
macio_adb_interrupt(0, 0, 0);
- restore_flags(flags);
+ local_irq_restore(flags);
}
diff -urN linux-2.5/drivers/macintosh/via-cuda.c pmac-2.5/drivers/macintosh/via-cuda.c
--- linux-2.5/drivers/macintosh/via-cuda.c 2002-07-24 17:12:04.000000000 +1000
+++ pmac-2.5/drivers/macintosh/via-cuda.c 2002-10-13 15:00:41.000000000 +1000
@@ -17,6 +17,7 @@
#include <linux/sched.h>
#include <linux/adb.h>
#include <linux/cuda.h>
+#include <linux/spinlock.h>
#ifdef CONFIG_PPC
#include <asm/prom.h>
#include <asm/machdep.h>
@@ -31,6 +32,7 @@
#include <linux/init.h>

static volatile unsigned char *via;
+static spinlock_t cuda_lock = SPIN_LOCK_UNLOCKED;

#ifdef CONFIG_MAC
#define CUDA_IRQ IRQ_MAC_ADB
@@ -386,8 +388,8 @@
req->sent = 0;
req->complete = 0;
req->reply_len = 0;
- save_flags(flags); cli();

+ spin_lock_irqsave(&cuda_lock, flags);
if (current_req != 0) {
last_req->next = req;
last_req = req;
@@ -397,15 +399,14 @@
if (cuda_state == idle)
cuda_start();
}
+ spin_unlock_irqrestore(&cuda_lock, flags);

- restore_flags(flags);
return 0;
}

static void
cuda_start()
{
- unsigned long flags;
struct adb_request *req;

/* assert cuda_state == idle */
@@ -413,41 +414,46 @@
req = current_req;
if (req == 0)
return;
- save_flags(flags); cli();
- if ((via[B] & TREQ) == 0) {
- restore_flags(flags);
+ if ((via[B] & TREQ) == 0)
return; /* a byte is coming in from the CUDA */
- }

/* set the shift register to shift out and send a byte */
via[ACR] |= SR_OUT; eieio();
via[SR] = req->data[0]; eieio();
via[B] &= ~TIP;
cuda_state = sent_first_byte;
- restore_flags(flags);
}

void
cuda_poll()
{
- unsigned long flags;
+ if (via[IFR] & SR_INT) {
+ unsigned long flags;

- save_flags(flags);
- cli();
- if (via[IFR] & SR_INT)
+ /* cuda_interrupt only takes a normal lock, we disable
+ * interrupts here to avoid re-entering and thus deadlocking.
+ * An option would be to disable only the IRQ source with
+ * disable_irq(), would that work on m68k ? --BenH
+ */
+ local_irq_save(flags);
cuda_interrupt(0, 0, 0);
- restore_flags(flags);
+ local_irq_restore(flags);
+ }
}

static void
cuda_interrupt(int irq, void *arg, struct pt_regs *regs)
{
int x, status;
- struct adb_request *req;
-
+ struct adb_request *req = NULL;
+ unsigned char ibuf[16];
+ int ibuf_len = 0;
+ int complete = 0;
+
if ((via[IFR] & SR_INT) == 0)
return;

+ spin_lock(&cuda_lock);
status = (~via[B] & (TIP|TREQ)) | (via[ACR] & SR_OUT); eieio();
/* printk("cuda_interrupt: state=%d status=%x\n", cuda_state, status); */
switch (cuda_state) {
@@ -502,8 +508,7 @@
cuda_state = awaiting_reply;
} else {
current_req = req->next;
- if (req->done)
- (*req->done)(req);
+ complete = 1;
/* not sure about this */
cuda_state = idle;
cuda_start();
@@ -544,12 +549,18 @@
memmove(req->reply, req->reply + 2, req->reply_len);
}
}
- req->complete = 1;
current_req = req->next;
- if (req->done)
- (*req->done)(req);
+ complete = 1;
} else {
- cuda_input(cuda_rbuf, reply_ptr - cuda_rbuf, regs);
+ /* This is tricky. We must break the spinlock to call
+ * cuda_input. However, doing so means we might get
+ * re-entered from another CPU getting an interrupt
+ * or calling cuda_poll(). I ended up using the stack
+ * (it's only for 16 bytes) and moving the actual
+ * call to cuda_input to outside of the lock.
+ */
+ ibuf_len = reply_ptr - cuda_rbuf;
+ memcpy(ibuf, cuda_rbuf, ibuf_len);
}
if (status == TREQ) {
via[B] &= ~TIP; eieio();
@@ -565,6 +576,19 @@
default:
printk("cuda_interrupt: unknown cuda_state %d?\n", cuda_state);
}
+ spin_unlock(&cuda_lock);
+ if (complete && req) {
+ void (*done)(struct adb_request *) = req->done;
+ mb();
+ req->complete = 1;
+ /* Here, we assume that if the request has a done member, the
+ * struct request will survive to setting req->complete to 1
+ */
+ if (done)
+ (*done)(req);
+ }
+ if (ibuf_len)
+ cuda_input(ibuf, ibuf_len, regs);
}

static void
diff -urN linux-2.5/drivers/macintosh/via-pmu.c pmac-2.5/drivers/macintosh/via-pmu.c
--- linux-2.5/drivers/macintosh/via-pmu.c 2002-08-29 09:08:29.000000000 +1000
+++ pmac-2.5/drivers/macintosh/via-pmu.c 2002-10-13 15:00:41.000000000 +1000
@@ -9,11 +9,7 @@
* and the RTC (real time clock) chip.
*
* Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
- * Copyright (C) 2001 Benjamin Herrenschmidt
- *
- * todo: - Cleanup synchro between VIA interrupt and GPIO-based PMU
- * interrupt.
- *
+ * Copyright (C) 2001-2002 Benjamin Herrenschmidt
*
*/
#include <stdarg.h>
@@ -111,14 +107,24 @@
reading_intr,
} 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[256]; /* Made bigger: I've been told that might happen */
+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, bright_req_3;
static struct device_node *vias;
static int pmu_kind = PMU_UNKNOWN;
@@ -174,12 +180,6 @@
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 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);
@@ -521,8 +521,8 @@

/* ack all pending interrupts */
timeout = 100000;
- interrupt_data[0] = 1;
- while (interrupt_data[0] || pmu_state != idle) {
+ 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;
@@ -1176,7 +1176,7 @@
return 0;
}

-static void __openfirmware
+static inline void
wait_for_ack(void)
{
/* Sightly increased the delay, I had one occurence of the message
@@ -1194,7 +1194,7 @@

/* New PMU seems to be very sensitive to those timings, so we make sure
* PCI is flushed immediately */
-static void __openfirmware
+static inline void
send_byte(int x)
{
volatile unsigned char *v = via;
@@ -1205,8 +1205,8 @@
(void)in_8(&v[B]);
}

-static void __openfirmware
-recv_byte()
+static inline void
+recv_byte(void)
{
volatile unsigned char *v = via;

@@ -1216,7 +1216,18 @@
(void)in_8(&v[B]);
}

-static volatile int disable_poll;
+static inline void
+pmu_done(struct adb_request *req)
+{
+ void (*done)(struct adb_request *) = req->done;
+ mb();
+ req->complete = 1;
+ /* Here, we assume that if the request has a done member, the
+ * struct request will survive to setting req->complete to 1
+ */
+ if (done)
+ (*done)(req);
+}

static void __openfirmware
pmu_start()
@@ -1281,9 +1292,9 @@
}

do {
- spin_unlock(&pmu_lock);
+ spin_unlock_irqrestore(&pmu_lock, flags);
via_pmu_interrupt(0, 0, 0);
- spin_lock(&pmu_lock);
+ spin_lock_irqsave(&pmu_lock, flags);
if (!adb_int_pending && pmu_state == idle && !req_awaiting_reply) {
#ifdef SUSPEND_USES_PMU
pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0);
@@ -1330,61 +1341,83 @@
#endif /* SUSPEND_USES_PMU */
}

+/* Interrupt data could be the result data from an ADB cmd */
static void __openfirmware
-via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs)
+pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs)
{
- unsigned long flags;
- int intr;
- int nloop = 0;
-
- /* This is a bit brutal, we can probably do better */
- spin_lock_irqsave(&pmu_lock, flags);
- ++disable_poll;
-
- for (;;) {
- intr = in_8(&via[IFR]) & (SR_INT | CB1_INT);
- if (intr == 0)
- break;
- if (++nloop > 1000) {
- printk(KERN_DEBUG "PMU: stuck in intr loop, "
- "intr=%x, ier=%x pmu_state=%d\n",
- intr, in_8(&via[IER]), pmu_state);
- break;
- }
- out_8(&via[IFR], intr);
- if (intr & SR_INT)
- pmu_sr_intr(regs);
- if (intr & CB1_INT)
- adb_int_pending = 1;
+ asleep = 0;
+ if (drop_interrupts || len < 1) {
+ adb_int_pending = 0;
+ return;
}
-
- if (pmu_state == idle) {
- if (adb_int_pending) {
- pmu_state = intack;
- /* Sounds safer to make sure ACK is high before writing.
- * This helped kill a problem with ADB and some iBooks
+ /* Note: for some reason, we get an interrupt with len=1,
+ * data[0]==0 after each normal ADB interrupt, at least
+ * on the Pismo. Still investigating... --BenH
+ */
+ if (data[0] & PMU_INT_ADB) {
+ if ((data[0] & PMU_INT_ADB_AUTO) == 0) {
+ struct adb_request *req = req_awaiting_reply;
+ if (req == 0) {
+ printk(KERN_ERR "PMU: extra ADB reply\n");
+ return;
+ }
+ req_awaiting_reply = 0;
+ if (len <= 2)
+ req->reply_len = 0;
+ else {
+ memcpy(req->reply, data + 1, len - 1);
+ req->reply_len = len - 1;
+ }
+ pmu_done(req);
+ } else {
+#ifdef CONFIG_XMON
+ if (len == 4 && data[1] == 0x2c) {
+ extern int xmon_wants_key, xmon_adb_keycode;
+ if (xmon_wants_key) {
+ xmon_adb_keycode = data[2];
+ return;
+ }
+ }
+#endif /* CONFIG_XMON */
+#ifdef CONFIG_ADB
+ /*
+ * 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.
*/
- wait_for_ack();
- send_byte(PMU_INT_ACK);
- adb_int_pending = 0;
- } else if (current_req)
- pmu_start();
- }
-
- --disable_poll;
- spin_unlock_irqrestore(&pmu_lock, flags);
-}
-
-static void __openfirmware
-gpio1_interrupt(int irq, void *arg, struct pt_regs *regs)
-{
- if ((in_8(gpio_reg + 0x9) & 0x02) == 0) {
- adb_int_pending = 1;
- via_pmu_interrupt(0, 0, 0);
+ 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);
+#endif /* CONFIG_ADB */
+ }
+ } else {
+ /* Sound/brightness button pressed */
+ if ((data[0] & PMU_INT_SNDBRT) && len == 3) {
+#ifdef CONFIG_PMAC_BACKLIGHT
+#ifdef CONFIG_INPUT_ADBHID
+ if (!disable_kernel_backlight)
+#endif /* CONFIG_INPUT_ADBHID */
+ set_backlight_level(data[1] >> 4);
+#endif /* CONFIG_PMAC_BACKLIGHT */
+ }
+#ifdef CONFIG_PMAC_PBOOK
+ /* Environement or tick interrupt, query batteries */
+ if (pmu_battery_count && (data[0] & PMU_INT_TICK)) {
+ if ((--query_batt_timer) == 0) {
+ query_battery_state();
+ query_batt_timer = BATTERY_POLLING_COUNT;
+ }
+ } else if (pmu_battery_count && (data[0] & PMU_INT_ENVIRONMENT))
+ query_battery_state();
+ if (data[0])
+ pmu_pass_intr(data, len);
+#endif /* CONFIG_PMAC_PBOOK */
}
}

-static void __openfirmware
+static struct adb_request* __openfirmware
pmu_sr_intr(struct pt_regs *regs)
{
struct adb_request *req;
@@ -1393,7 +1426,7 @@
if (via[B] & TREQ) {
printk(KERN_ERR "PMU: spurious SR intr (%x)\n", via[B]);
out_8(&via[IFR], SR_INT);
- return;
+ return NULL;
}
/* The ack may not yet be low when we get the interrupt */
while ((in_8(&via[B]) & TACK) != 0)
@@ -1426,11 +1459,8 @@
current_req = req->next;
if (req->reply_expected)
req_awaiting_reply = req;
- else {
- spin_unlock(&pmu_lock);
- pmu_done(req);
- spin_lock(&pmu_lock);
- }
+ else
+ return req;
} else {
pmu_state = reading;
data_index = 0;
@@ -1443,7 +1473,7 @@
data_index = 0;
data_len = -1;
pmu_state = reading_intr;
- reply_ptr = interrupt_data;
+ reply_ptr = interrupt_data[int_data_last];
recv_byte();
break;

@@ -1462,108 +1492,113 @@
}

if (pmu_state == reading_intr) {
- spin_unlock(&pmu_lock);
- pmu_handle_data(interrupt_data, data_index, regs);
- spin_lock(&pmu_lock);
+ pmu_state = idle;
+ int_data_state[int_data_last] = int_data_ready;
+ interrupt_data_len[int_data_last] = data_len;
} else {
req = current_req;
current_req = req->next;
req->reply_len += data_index;
- spin_unlock(&pmu_lock);
- pmu_done(req);
- spin_lock(&pmu_lock);
+ pmu_state = idle;
+ return req;
}
- pmu_state = idle;
-
break;

default:
printk(KERN_ERR "via_pmu_interrupt: unknown state %d?\n",
pmu_state);
}
+ return NULL;
}

static void __openfirmware
-pmu_done(struct adb_request *req)
+via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs)
{
- req->complete = 1;
- if (req->done)
- (*req->done)(req);
+ unsigned long flags;
+ int intr;
+ int nloop = 0;
+ int int_data = -1;
+ struct adb_request *req = NULL;
+
+ /* This is a bit brutal, we can probably do better */
+ spin_lock_irqsave(&pmu_lock, flags);
+ ++disable_poll;
+
+ for (;;) {
+ intr = in_8(&via[IFR]) & (SR_INT | CB1_INT);
+ if (intr == 0)
+ break;
+ if (++nloop > 1000) {
+ printk(KERN_DEBUG "PMU: stuck in intr loop, "
+ "intr=%x, ier=%x pmu_state=%d\n",
+ intr, in_8(&via[IER]), pmu_state);
+ break;
+ }
+ out_8(&via[IFR], intr);
+ if (intr & CB1_INT)
+ adb_int_pending = 1;
+ if (intr & SR_INT) {
+ req = pmu_sr_intr(regs);
+ if (req)
+ break;
+ }
+ }
+
+recheck:
+ if (pmu_state == idle) {
+ if (adb_int_pending) {
+ if (int_data_state[0] == int_data_empty)
+ int_data_last = 0;
+ else if (int_data_state[1] == int_data_empty)
+ int_data_last = 1;
+ else
+ goto no_free_slot;
+ pmu_state = intack;
+ int_data_state[int_data_last] = int_data_fill;
+ /* Sounds safer to make sure ACK is high before writing.
+ * This helped kill a problem with ADB and some iBooks
+ */
+ wait_for_ack();
+ send_byte(PMU_INT_ACK);
+ adb_int_pending = 0;
+no_free_slot:
+ } else if (current_req)
+ pmu_start();
+ }
+ /* Mark the oldest buffer for flushing */
+ if (int_data_state[!int_data_last] == int_data_ready) {
+ int_data_state[!int_data_last] = int_data_flush;
+ int_data = !int_data_last;
+ } else if (int_data_state[int_data_last] == int_data_ready) {
+ int_data_state[int_data_last] = int_data_flush;
+ int_data = int_data_last;
+ }
+ --disable_poll;
+ spin_unlock_irqrestore(&pmu_lock, flags);
+
+ /* Deal with completed PMU requests outside of the lock */
+ if (req) {
+ pmu_done(req);
+ req = NULL;
+ }
+
+ /* Deal with interrupt datas outside of the lock */
+ if (int_data >= 0) {
+ pmu_handle_data(interrupt_data[int_data], interrupt_data_len[int_data], regs);
+ spin_lock_irqsave(&pmu_lock, flags);
+ ++disable_poll;
+ int_data_state[int_data] = int_data_empty;
+ int_data = -1;
+ goto recheck;
+ }
}

-/* Interrupt data could be the result data from an ADB cmd */
static void __openfirmware
-pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs)
+gpio1_interrupt(int irq, void *arg, struct pt_regs *regs)
{
- asleep = 0;
- if (drop_interrupts || len < 1) {
- adb_int_pending = 0;
- return;
- }
- /* Note: for some reason, we get an interrupt with len=1,
- * data[0]==0 after each normal ADB interrupt, at least
- * on the Pismo. Still investigating... --BenH
- */
- if (data[0] & PMU_INT_ADB) {
- if ((data[0] & PMU_INT_ADB_AUTO) == 0) {
- struct adb_request *req = req_awaiting_reply;
- if (req == 0) {
- printk(KERN_ERR "PMU: extra ADB reply\n");
- return;
- }
- req_awaiting_reply = 0;
- if (len <= 2)
- req->reply_len = 0;
- else {
- memcpy(req->reply, data + 1, len - 1);
- req->reply_len = len - 1;
- }
- pmu_done(req);
- } else {
-#ifdef CONFIG_XMON
- if (len == 4 && data[1] == 0x2c) {
- extern int xmon_wants_key, xmon_adb_keycode;
- if (xmon_wants_key) {
- xmon_adb_keycode = data[2];
- return;
- }
- }
-#endif /* CONFIG_XMON */
-#ifdef CONFIG_ADB
- /*
- * 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);
-#endif /* CONFIG_ADB */
- }
- } else {
- /* Sound/brightness button pressed */
- if ((data[0] & PMU_INT_SNDBRT) && len == 3) {
-#ifdef CONFIG_PMAC_BACKLIGHT
-#ifdef CONFIG_INPUT_ADBHID
- if (!disable_kernel_backlight)
-#endif /* CONFIG_INPUT_ADBHID */
- set_backlight_level(data[1] >> 4);
-#endif /* CONFIG_PMAC_BACKLIGHT */
- }
-#ifdef CONFIG_PMAC_PBOOK
- /* Environement or tick interrupt, query batteries */
- if (pmu_battery_count && (data[0] & PMU_INT_TICK)) {
- if ((--query_batt_timer) == 0) {
- query_battery_state();
- query_batt_timer = BATTERY_POLLING_COUNT;
- }
- } else if (pmu_battery_count && (data[0] & PMU_INT_ENVIRONMENT))
- query_battery_state();
- if (data[0])
- pmu_pass_intr(data, len);
-#endif /* CONFIG_PMAC_PBOOK */
+ if ((in_8(gpio_reg + 0x9) & 0x02) == 0) {
+ adb_int_pending = 1;
+ via_pmu_interrupt(0, 0, 0);
}
}

@@ -1635,7 +1670,7 @@
{
struct adb_request req;

- cli();
+ local_irq_disable();

drop_interrupts = 1;

@@ -1658,7 +1693,7 @@
{
struct adb_request req;

- cli();
+ local_irq_disable();

drop_interrupts = 1;

@@ -1979,7 +2014,7 @@
unsigned long save_l2cr;
unsigned short pmcr1;
struct adb_request req;
- int ret, timeout;
+ int ret;
struct pci_dev *grackle;

grackle = pci_find_slot(0, 0);
@@ -2036,17 +2071,16 @@
mb();
asm volatile("mtdec %0" : : "r" (0x7fffffff));

- /* Giveup the FPU */
- if (current->thread.regs && (current->thread.regs->msr & MSR_FP) != 0)
- giveup_fpu(current);
-
/* We can now disable MSR_EE */
- cli();
+ local_irq_disable();
+
+ /* Giveup the FPU */
+ enable_kernel_fp();

/* For 750, save backside cache setting and disable it */
- save_l2cr = _get_L2CR(); /* (returns 0 if not 750) */
- if (save_l2cr)
- _set_L2CR(0);
+ save_l2cr = _get_L2CR(); /* (returns -1 if not available) */
+ if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0)
+ _set_L2CR(save_l2cr & 0x7fffffff);

/* Ask the PMU to put us to sleep */
pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
@@ -2077,7 +2111,7 @@
restore_via_state();

/* Restore L2 cache */
- if (save_l2cr)
+ if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0)
_set_L2CR(save_l2cr);

/* Restore userland MMU context */
@@ -2096,18 +2130,6 @@
while (!req.complete)
pmu_poll();

- /* ack all pending interrupts */
- timeout = 100000;
- interrupt_data[0] = 1;
- while (interrupt_data[0] || pmu_state != idle) {
- if (--timeout < 0)
- break;
- if (pmu_state == idle)
- adb_int_pending = 1;
- via_pmu_interrupt(0, 0, 0);
- udelay(10);
- }
-
/* reenable interrupt controller */
pmac_sleep_restore_intrs();

@@ -2116,7 +2138,13 @@

/* Restart jiffies & scheduling */
wakeup_decrementer();
- sti();
+
+ /* Force a poll of ADB interrupts */
+ adb_int_pending = 1;
+ via_pmu_interrupt(0, 0, 0);
+
+ /* Re-enable local CPU interrupts */
+ local_irq_enable();

/* Notify drivers */
broadcast_wake();
@@ -2127,8 +2155,9 @@
int __openfirmware powerbook_sleep_Core99(void)
{
unsigned long save_l2cr;
+ unsigned long save_l3cr;
struct adb_request req;
- int ret, timeout;
+ int ret;

if (!can_sleep) {
printk(KERN_ERR "Sleep mode not supported on this machine\n");
@@ -2187,6 +2216,9 @@
mb();
asm volatile("mtdec %0" : : "r" (0x7fffffff));

+ /* We can now disable MSR_EE */
+ local_irq_disable();
+
/* Giveup the FPU & vec */
enable_kernel_fp();

@@ -2195,12 +2227,12 @@
enable_kernel_altivec();
#endif /* CONFIG_ALTIVEC */

- /* We can now disable MSR_EE */
- cli();
-
- /* For 750, save backside cache setting and disable it */
- save_l2cr = _get_L2CR(); /* (returns 0 if not 750) */
- if (save_l2cr)
+ /* Save & disable L2 and L3 caches*/
+ save_l3cr = _get_L3CR(); /* (returns -1 if not available) */
+ save_l2cr = _get_L2CR(); /* (returns -1 if not available) */
+ if (save_l3cr != 0xffffffff && (save_l3cr & L3CR_L3E) != 0)
+ _set_L3CR(save_l3cr & 0x7fffffff);
+ if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0)
_set_L2CR(save_l2cr & 0x7fffffff);

/* Save the state of PCI config space for some slots */
@@ -2248,8 +2280,11 @@
pmu_blink(2);

/* Restore L2 cache */
- if (save_l2cr)
+ if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0)
_set_L2CR(save_l2cr);
+ /* Restore L3 cache */
+ if (save_l3cr != 0xffffffff && (save_l3cr & L3CR_L3E) != 0)
+ _set_L3CR(save_l3cr);

/* Restore userland MMU context */
set_context(current->active_mm->context, current->active_mm->pgd);
@@ -2262,18 +2297,6 @@
while (!req.complete)
pmu_poll();

- /* ack all pending interrupts */
- timeout = 100000;
- interrupt_data[0] = 1;
- while (interrupt_data[0] || pmu_state != idle) {
- if (--timeout < 0)
- break;
- if (pmu_state == idle)
- adb_int_pending = 1;
- via_pmu_interrupt(0, 0, 0);
- udelay(10);
- }
-
/* reenable interrupt controller */
openpic_sleep_restore_intrs();

@@ -2282,7 +2305,13 @@

/* Restart jiffies & scheduling */
wakeup_decrementer();
- sti();
+
+ /* Force a poll of ADB interrupts */
+ adb_int_pending = 1;
+ via_pmu_interrupt(0, 0, 0);
+
+ /* Re-enable local CPU interrupts */
+ local_irq_enable();

/* Notify drivers */
broadcast_wake();
@@ -2402,7 +2431,9 @@

/* Restart jiffies & scheduling */
wakeup_decrementer();
- sti();
+
+ /* Re-enable local CPU interrupts */
+ local_irq_enable();

/* Notify drivers */
broadcast_wake();
@@ -2718,7 +2749,7 @@
if (l >= 0 && req->nbytes != l + 1)
return -EINVAL;

- save_flags(flags); cli();
+ local_irq_save(flags);
while (pmu_state != idle)
pmu_poll();

@@ -2741,7 +2772,7 @@
if (req->done)
(*req->done)(req);

- restore_flags(flags);
+ local_irq_restore(flags);
return 0;
}
#endif /* DEBUG_SLEEP */
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/