Commit fe2bb3f5 authored by Paul Mackerras's avatar Paul Mackerras

PPC32: This changeset updates several of the powermac-specific

drivers.  Most of this is from 2.4.  Almost all of this work was
done by Ben Herrenschmidt.
parent 2b5adb1d
......@@ -29,13 +29,15 @@
#include <asm/prom.h>
#include <asm/uaccess.h>
#include <asm/mediabay.h>
#include <asm/feature.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#define MAJOR_NR FLOPPY_MAJOR
#include <linux/blk.h>
#include <linux/devfs_fs_kernel.h>
static int floppy_sizes[2] = {2880,2880};
static int floppy_blocksizes[2] = {512,512};
static int floppy_sizes[2] = {1440,1440};
#define MAX_FLOPPIES 2
......@@ -312,10 +314,10 @@ static void start_request(struct floppy_state *fs)
return;
}
while (!QUEUE_EMPTY && fs->state == idle) {
if (MAJOR(CURRENT->rq_dev) != MAJOR_NR)
if (major(CURRENT->rq_dev) != MAJOR_NR)
panic(DEVICE_NAME ": request list destroyed");
if (CURRENT->bh && !buffer_locked(CURRENT->bh))
panic(DEVICE_NAME ": block not locked");
// if (CURRENT->bh && !buffer_locked(CURRENT->bh))
// panic(DEVICE_NAME ": block not locked");
#if 0
printk("do_fd_req: dev=%x cmd=%d sec=%ld nr_sec=%ld buf=%p\n",
kdev_t_to_nr(CURRENT->rq_dev), CURRENT->cmd,
......@@ -337,7 +339,7 @@ static void start_request(struct floppy_state *fs)
continue;
}
if (CURRENT->cmd == WRITE) {
if (rq_data_dir(CURRENT) == WRITE) {
if (fs->write_prot < 0)
fs->write_prot = swim3_readbit(fs, WRITE_PROT);
if (fs->write_prot) {
......@@ -425,7 +427,7 @@ static inline void setup_transfer(struct floppy_state *fs)
printk(KERN_ERR "swim3: transfer 0 sectors?\n");
return;
}
if (CURRENT->cmd == WRITE)
if (rq_data_dir(CURRENT) == WRITE)
n = 1;
else {
n = fs->secpertrack - fs->req_sector + 1;
......@@ -438,21 +440,21 @@ static inline void setup_transfer(struct floppy_state *fs)
out_8(&sw->nsect, n);
out_8(&sw->gap3, 0);
st_le32(&dr->cmdptr, virt_to_bus(cp));
if (CURRENT->cmd == WRITE) {
if (rq_data_dir(CURRENT) == WRITE) {
/* Set up 3 dma commands: write preamble, data, postamble */
init_dma(cp, OUTPUT_MORE, write_preamble, sizeof(write_preamble));
++cp;
init_dma(cp, OUTPUT_MORE, CURRENT->buffer, 512);
++cp;
init_dma(cp, OUTPUT_MORE, write_postamble, sizeof(write_postamble));
init_dma(cp, OUTPUT_LAST, write_postamble, sizeof(write_postamble));
} else {
init_dma(cp, INPUT_MORE, CURRENT->buffer, n * 512);
init_dma(cp, INPUT_LAST, CURRENT->buffer, n * 512);
}
++cp;
out_le16(&cp->command, DBDMA_STOP);
out_le32(&dr->control, (RUN << 16) | RUN);
out_8(&sw->control_bis,
(CURRENT->cmd == WRITE? WRITE_SECTORS: 0) | DO_ACTION);
(rq_data_dir(CURRENT) == WRITE? WRITE_SECTORS: 0) | DO_ACTION);
/* enable intr when transfer complete */
out_8(&sw->intr_enable, ERROR_INTR | TRANSFER_DONE);
set_timeout(fs, 2*HZ, xfer_timeout); /* enable timeout */
......@@ -591,7 +593,7 @@ static void xfer_timeout(unsigned long data)
out_8(&sw->intr_enable, 0);
out_8(&sw->control_bic, WRITE_SECTORS | DO_ACTION);
out_8(&sw->select, RELAX);
if (CURRENT->cmd == WRITE)
if (rq_data_dir(CURRENT) == WRITE)
++cp;
if (ld_le16(&cp->xfer_status) != 0)
s = fs->scount - ((ld_le16(&cp->res_count) + 511) >> 9);
......@@ -600,7 +602,7 @@ static void xfer_timeout(unsigned long data)
CURRENT->sector += s;
CURRENT->current_nr_sectors -= s;
printk(KERN_ERR "swim3: timeout %sing sector %ld\n",
(CURRENT->cmd==WRITE? "writ": "read"), CURRENT->sector);
(rq_data_dir(CURRENT)==WRITE? "writ": "read"), CURRENT->sector);
end_request(0);
fs->state = idle;
start_request(fs);
......@@ -621,8 +623,8 @@ static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs)
printk("swim3 intr state=%d intr=%x err=%x\n", fs->state, intr, err);
#endif
if ((intr & ERROR_INTR) && fs->state != do_transfer)
printk(KERN_ERR "swim3_interrupt, state=%d, cmd=%x, intr=%x, err=%x\n",
fs->state, CURRENT->cmd, intr, err);
printk(KERN_ERR "swim3_interrupt, state=%d, dir=%lx, intr=%x, err=%x\n",
fs->state, rq_data_dir(CURRENT), intr, err);
switch (fs->state) {
case locating:
if (intr & SEEN_SECTOR) {
......@@ -678,13 +680,16 @@ static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs)
break;
dr = fs->dma;
cp = fs->dma_cmd;
st_le32(&dr->control, RUN << 16);
/* We must wait a bit for dbdma to complete */
for (n=0; (in_le32(&dr->status) & ACTIVE) && n < 1000; n++)
udelay(10);
DBDMA_DO_STOP(dr);
out_8(&sw->intr_enable, 0);
out_8(&sw->control_bic, WRITE_SECTORS | DO_ACTION);
out_8(&sw->select, RELAX);
del_timer(&fs->timeout);
fs->timeout_pending = 0;
if (CURRENT->cmd == WRITE)
if (rq_data_dir(CURRENT) == WRITE)
++cp;
stat = ld_le16(&cp->xfer_status);
resid = ld_le16(&cp->res_count);
......@@ -701,7 +706,7 @@ static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs)
act(fs);
} else {
printk("swim3: error %sing block %ld (err=%x)\n",
CURRENT->cmd == WRITE? "writ": "read",
rq_data_dir(CURRENT) == WRITE? "writ": "read",
CURRENT->sector, err);
end_request(0);
fs->state = idle;
......@@ -710,8 +715,8 @@ static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs)
if ((stat & ACTIVE) == 0 || resid != 0) {
/* musta been an error */
printk(KERN_ERR "swim3: fd dma: stat=%x resid=%d\n", stat, resid);
printk(KERN_ERR " state=%d, cmd=%x, intr=%x, err=%x\n",
fs->state, CURRENT->cmd, intr, err);
printk(KERN_ERR " state=%d, dir=%lx, intr=%x, err=%x\n",
fs->state, rq_data_dir(CURRENT), intr, err);
end_request(0);
fs->state = idle;
start_request(fs);
......@@ -815,7 +820,7 @@ static int floppy_ioctl(struct inode *inode, struct file *filp,
{
struct floppy_state *fs;
int err;
int devnum = MINOR(inode->i_rdev);
int devnum = minor(inode->i_rdev);
if (devnum >= floppy_count)
return -ENODEV;
......@@ -847,7 +852,7 @@ static int floppy_open(struct inode *inode, struct file *filp)
struct floppy_state *fs;
volatile struct swim3 *sw;
int n, err;
int devnum = MINOR(inode->i_rdev);
int devnum = minor(inode->i_rdev);
if (devnum >= floppy_count)
return -ENODEV;
......@@ -921,7 +926,7 @@ static int floppy_release(struct inode *inode, struct file *filp)
{
struct floppy_state *fs;
volatile struct swim3 *sw;
int devnum = MINOR(inode->i_rdev);
int devnum = minor(inode->i_rdev);
if (devnum >= floppy_count)
return -ENODEV;
......@@ -938,9 +943,9 @@ static int floppy_release(struct inode *inode, struct file *filp)
static int floppy_check_change(kdev_t dev)
{
struct floppy_state *fs;
int devnum = MINOR(dev);
int devnum = minor(dev);
if (MAJOR(dev) != MAJOR_NR || (devnum >= floppy_count))
if (major(dev) != MAJOR_NR || (devnum >= floppy_count))
return 0;
fs = &floppy_states[devnum];
......@@ -952,9 +957,9 @@ static int floppy_revalidate(kdev_t dev)
struct floppy_state *fs;
volatile struct swim3 *sw;
int ret, n;
int devnum = MINOR(dev);
int devnum = minor(dev);
if (MAJOR(dev) != MAJOR_NR || (devnum >= floppy_count))
if (major(dev) != MAJOR_NR || (devnum >= floppy_count))
return 0;
fs = &floppy_states[devnum];
......@@ -1031,8 +1036,8 @@ int swim3_init(void)
MAJOR_NR);
return -EBUSY;
}
blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), do_fd_request,
&swim3_lock);
blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), DEVICE_REQUEST,&swim3_lock);
blksize_size[MAJOR_NR] = floppy_blocksizes;
blk_size[MAJOR_NR] = floppy_sizes;
}
......@@ -1060,9 +1065,14 @@ static int swim3_add_device(struct device_node *swim)
return -EINVAL;
}
if (!request_OF_resource(swim, 0, NULL)) {
printk(KERN_INFO "swim3: can't request IO resource !\n");
return -EINVAL;
}
mediabay = (strcasecmp(swim->parent->type, "media-bay") == 0) ? swim->parent : NULL;
if (mediabay == NULL)
feature_set(swim, FEATURE_SWIM3_enable);
pmac_call_feature(PMAC_FTR_SWIM3_ENABLE, swim, 0, 1);
memset(fs, 0, sizeof(*fs));
fs->state = idle;
......@@ -1084,14 +1094,14 @@ static int swim3_add_device(struct device_node *swim)
if (request_irq(fs->swim3_intr, swim3_interrupt, 0, "SWIM3", fs)) {
printk(KERN_ERR "Couldn't get irq %d for SWIM3\n", fs->swim3_intr);
feature_clear(swim, FEATURE_SWIM3_enable);
pmac_call_feature(PMAC_FTR_SWIM3_ENABLE, swim, 0, 0);
return -EBUSY;
}
/*
if (request_irq(fs->dma_intr, fd_dma_interrupt, 0, "SWIM3-dma", fs)) {
printk(KERN_ERR "Couldn't get irq %d for SWIM3 DMA",
fs->dma_intr);
feature_clear(swim, FEATURE_SWIM3_enable);
pmac_call_feature(PMAC_FTR_SWIM3_ENABLE, swim, 0, 0);
return -EBUSY;
}
*/
......
......@@ -32,9 +32,11 @@ obj-$(CONFIG_NVRAM) += nvram.o
obj-$(CONFIG_MAC_HID) += mac_hid.o
obj-$(CONFIG_INPUT_ADBHID) += adbhid.o
obj-$(CONFIG_PPC_RTC) += rtc.o
obj-$(CONFIG_ANSLCD) += ans-lcd.o
obj-$(CONFIG_ADB_PMU) += via-pmu.o
obj-$(CONFIG_ADB_CUDA) += via-cuda.o
obj-$(CONFIG_PMAC_APM_EMU) += apm_emu.o
obj-$(CONFIG_ADB) += adb.o
obj-$(CONFIG_ADB_KEYBOARD) += mac_keyb.o
......
......@@ -34,6 +34,7 @@
#include <linux/wait.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/completion.h>
#include <asm/uaccess.h>
#ifdef CONFIG_PPC
#include <asm/prom.h>
......@@ -77,8 +78,8 @@ struct notifier_block *adb_client_list = NULL;
static int adb_got_sleep = 0;
static int adb_inited = 0;
static pid_t adb_probe_task_pid;
static unsigned long adb_probe_task_flag;
static wait_queue_head_t adb_probe_task_wq;
static DECLARE_MUTEX(adb_probe_mutex);
static struct completion adb_probe_task_comp;
static int sleepy_trackpad;
int __adb_probe_sync;
......@@ -241,7 +242,8 @@ adb_probe_task(void *x)
printk(KERN_INFO "adb: finished probe task...\n");
adb_probe_task_pid = 0;
clear_bit(0, &adb_probe_task_flag);
up(&adb_probe_mutex);
return 0;
}
......@@ -263,14 +265,8 @@ adb_reset_bus(void)
do_adb_reset_bus();
return 0;
}
/* We need to get a lock on the probe thread */
while (test_and_set_bit(0, &adb_probe_task_flag))
schedule();
/* Just wait for PID to be 0 just in case (possible race) */
while (adb_probe_task_pid != 0)
schedule();
down(&adb_probe_mutex);
/* Create probe thread as a child of keventd */
if (current_is_keventd())
......@@ -318,7 +314,7 @@ int __init adb_init(void)
if (machine_is_compatible("AAPL,PowerBook1998") ||
machine_is_compatible("PowerBook1,1"))
sleepy_trackpad = 1;
init_waitqueue_head(&adb_probe_task_wq);
init_completion(&adb_probe_task_comp);
adbdev_init();
adb_reset_bus();
}
......@@ -340,21 +336,20 @@ adb_notify_sleep(struct pmu_sleep_notifier *self, int when)
case PBOOK_SLEEP_REQUEST:
adb_got_sleep = 1;
/* We need to get a lock on the probe thread */
while (test_and_set_bit(0, &adb_probe_task_flag))
schedule();
/* Just wait for PID to be 0 just in case (possible race) */
while (adb_probe_task_pid != 0)
schedule();
down(&adb_probe_mutex);
/* Stop autopoll */
if (adb_controller->autopoll)
adb_controller->autopoll(0);
ret = notifier_call_chain(&adb_client_list, ADB_MSG_POWERDOWN, NULL);
if (ret & NOTIFY_STOP_MASK)
if (ret & NOTIFY_STOP_MASK) {
up(&adb_probe_mutex);
return PBOOK_SLEEP_REFUSE;
}
break;
case PBOOK_SLEEP_REJECT:
if (adb_got_sleep) {
adb_got_sleep = 0;
clear_bit(0, &adb_probe_task_flag);
up(&adb_probe_mutex);
adb_reset_bus();
}
break;
......@@ -363,7 +358,7 @@ adb_notify_sleep(struct pmu_sleep_notifier *self, int when)
break;
case PBOOK_WAKE:
adb_got_sleep = 0;
clear_bit(0, &adb_probe_task_flag);
up(&adb_probe_mutex);
adb_reset_bus();
break;
}
......@@ -435,9 +430,10 @@ adb_poll(void)
static void
adb_probe_wakeup(struct adb_request *req)
{
wake_up(&adb_probe_task_wq);
complete(&adb_probe_task_comp);
}
/* Static request used during probe */
static struct adb_request adb_sreq;
static unsigned long adb_sreq_lock; // Use semaphore ! */
......@@ -484,20 +480,11 @@ adb_request(struct adb_request *req, void (*done)(struct adb_request *),
if ((flags & ADBREQ_SYNC) &&
(current->pid && adb_probe_task_pid &&
adb_probe_task_pid == current->pid)) {
DECLARE_WAITQUEUE(wait, current);
req->done = adb_probe_wakeup;
add_wait_queue(&adb_probe_task_wq, &wait);
rc = adb_controller->send_request(req, 0);
if (rc || req->complete)
goto bail;
for (;;) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (req->complete)
break;
schedule();
}
current->state = TASK_RUNNING;
remove_wait_queue(&adb_probe_task_wq, &wait);
wait_for_completion(&adb_probe_task_comp);
rc = 0;
goto bail;
}
......@@ -652,7 +639,7 @@ static int adb_open(struct inode *inode, struct file *file)
{
struct adbdev_state *state;
if (MINOR(inode->i_rdev) > 0 || adb_controller == NULL)
if (minor(inode->i_rdev) > 0 || adb_controller == NULL)
return -ENXIO;
state = kmalloc(sizeof(struct adbdev_state), GFP_KERNEL);
if (state == 0)
......@@ -672,6 +659,7 @@ static int adb_release(struct inode *inode, struct file *file)
struct adbdev_state *state = file->private_data;
unsigned long flags;
lock_kernel();
if (state) {
file->private_data = NULL;
spin_lock_irqsave(&state->lock, flags);
......@@ -684,6 +672,7 @@ static int adb_release(struct inode *inode, struct file *file)
spin_unlock_irqrestore(&state->lock, flags);
}
}
unlock_kernel();
return 0;
}
......@@ -705,17 +694,16 @@ static ssize_t adb_read(struct file *file, char *buf,
return ret;
req = NULL;
spin_lock_irqsave(&state->lock, flags);
add_wait_queue(&state->wait_queue, &wait);
current->state = TASK_INTERRUPTIBLE;
for (;;) {
spin_lock_irqsave(&state->lock, flags);
req = state->completed;
if (req != NULL)
state->completed = req->next;
else if (atomic_read(&state->n_pending) == 0)
ret = -EIO;
spin_unlock_irqrestore(&state->lock, flags);
if (req != NULL || ret != 0)
break;
......@@ -727,12 +715,15 @@ static ssize_t adb_read(struct file *file, char *buf,
ret = -ERESTARTSYS;
break;
}
spin_unlock_irqrestore(&state->lock, flags);
schedule();
spin_lock_irqsave(&state->lock, flags);
}
current->state = TASK_RUNNING;
remove_wait_queue(&state->wait_queue, &wait);
spin_unlock_irqrestore(&state->lock, flags);
if (ret)
return ret;
......@@ -755,6 +746,8 @@ static ssize_t adb_write(struct file *file, const char *buf,
if (count < 2 || count > sizeof(req->data))
return -EINVAL;
if (adb_controller == NULL)
return -ENXIO;
ret = verify_area(VERIFY_READ, buf, count);
if (ret)
return ret;
......@@ -774,7 +767,10 @@ static ssize_t adb_write(struct file *file, const char *buf,
goto out;
atomic_inc(&state->n_pending);
if (adb_controller == NULL) return -ENXIO;
/* If a probe is in progress or we are sleeping, wait for it to complete */
down(&adb_probe_mutex);
up(&adb_probe_mutex);
/* Special case for ADB_BUSRESET request, all others are sent to
the controller */
......@@ -782,6 +778,8 @@ static ssize_t adb_write(struct file *file, const char *buf,
&&(req->data[1] == ADB_BUSRESET)) {
ret = do_adb_reset_bus();
atomic_dec(&state->n_pending);
if (ret == 0)
ret = count;
goto out;
} else {
req->reply_expected = ((req->data[1] & 0xc) == 0xc);
......
......@@ -96,6 +96,11 @@ static struct adb_ids keyboard_ids;
static struct adb_ids mouse_ids;
static struct adb_ids buttons_ids;
#ifdef CONFIG_PMAC_BACKLIGHT
/* Exported to via-pmu.c */
int disable_kernel_backlight = 0;
#endif /* CONFIG_PMAC_BACKLIGHT */
/* Kind of keyboard, see Apple technote 1152 */
#define ADB_KEYBOARD_UNKNOWN 0
#define ADB_KEYBOARD_ANSI 0x0100
......@@ -273,43 +278,60 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto
break;
case 0x1f: /* Powerbook button device */
{
int down = (data[1] == (data[1] & 0xf));
#ifdef CONFIG_PMAC_BACKLIGHT
int backlight = get_backlight_level();
#endif
/*
* XXX: Where is the contrast control for the passive?
* -- Cort
*/
switch (data[1]) {
switch (data[1] & 0x0f) {
case 0x8: /* mute */
input_report_key(&adbhid[id]->input, KEY_MUTE, down);
break;
case 0x7: /* contrast decrease */
case 0x7: /* volume decrease */
input_report_key(&adbhid[id]->input, KEY_VOLUMEDOWN, down);
break;
case 0x6: /* contrast increase */
break;
case 0x6: /* volume increase */
input_report_key(&adbhid[id]->input, KEY_VOLUMEUP, down);
break;
case 0xb: /* eject */
input_report_key(&adbhid[id]->input, KEY_EJECTCD, down);
break;
case 0xa: /* brightness decrease */
if (backlight < 0)
#ifdef CONFIG_PMAC_BACKLIGHT
if (!disable_kernel_backlight) {
if (!down || backlight < 0)
break;
if (backlight > BACKLIGHT_OFF)
set_backlight_level(backlight-1);
else
set_backlight_level(BACKLIGHT_OFF);
break;
if (backlight > BACKLIGHT_OFF)
set_backlight_level(backlight-1);
else
set_backlight_level(BACKLIGHT_OFF);
}
#endif /* CONFIG_PMAC_BACKLIGHT */
input_report_key(&adbhid[id]->input, KEY_BRIGHTNESSDOWN, down);
break;
case 0x9: /* brightness increase */
if (backlight < 0)
#ifdef CONFIG_PMAC_BACKLIGHT
if (!disable_kernel_backlight) {
if (!down || backlight < 0)
break;
if (backlight < BACKLIGHT_MAX)
set_backlight_level(backlight+1);
else
set_backlight_level(BACKLIGHT_MAX);
break;
if (backlight < BACKLIGHT_MAX)
set_backlight_level(backlight+1);
else
set_backlight_level(BACKLIGHT_MAX);
}
#endif /* CONFIG_PMAC_BACKLIGHT */
input_report_key(&adbhid[id]->input, KEY_BRIGHTNESSUP, down);
break;
}
#endif /* CONFIG_PMAC_BACKLIGHT */
}
break;
}
......@@ -504,6 +526,13 @@ adbhid_input_register(int id, int default_id, int original_handler_id,
case 0x1f: /* Powerbook button device */
sprintf(adbhid[id]->name, "ADB Powerbook buttons on ID %d:%d.%02x",
id, default_id, original_handler_id);
adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
set_bit(KEY_MUTE, adbhid[id]->input.keybit);
set_bit(KEY_VOLUMEUP, adbhid[id]->input.keybit);
set_bit(KEY_VOLUMEDOWN, adbhid[id]->input.keybit);
set_bit(KEY_BRIGHTNESSUP, adbhid[id]->input.keybit);
set_bit(KEY_BRIGHTNESSDOWN, adbhid[id]->input.keybit);
set_bit(KEY_EJECTCD, adbhid[id]->input.keybit);
break;
}
if (adbhid[id]->name[0])
......@@ -542,16 +571,38 @@ static void adbhid_input_unregister(int id)
}
static u16
adbhid_input_reregister(int id, int default_id, int org_handler_id,
int cur_handler_id, int mk)
{
if (adbhid[id]) {
if (adbhid[id]->input.idproduct !=
((id << 12)|(default_id << 8)|org_handler_id)) {
adbhid_input_unregister(id);
adbhid_input_register(id, default_id, org_handler_id,
cur_handler_id, mk);
}
} else
adbhid_input_register(id, default_id, org_handler_id,
cur_handler_id, mk);
return 1<<id;
}
static void
adbhid_input_devcleanup(u16 exist)
{
int i;
for(i=1; i<16; i++)
if (adbhid[i] && !(exist&(1<<i)))
adbhid_input_unregister(i);
}
static void
adbhid_probe(void)
{
struct adb_request req;
int i, default_id, org_handler_id, cur_handler_id;
for (i = 1; i < 16; i++) {
if (adbhid[i])
adbhid_input_unregister(i);
}
u16 reg = 0;
adb_register(ADB_MOUSE, 0, &mouse_ids, adbhid_mouse_input);
adb_register(ADB_KEYBOARD, 0, &keyboard_ids, adbhid_keyboard_input);
......@@ -580,14 +631,14 @@ adbhid_probe(void)
printk("ADB keyboard at %d, handler 1\n", id);
adb_get_infos(id, &default_id, &cur_handler_id);
adbhid_input_register(id, default_id, org_handler_id, cur_handler_id, 0);
reg |= adbhid_input_reregister(id, default_id, org_handler_id, cur_handler_id, 0);
}
for (i = 0; i < buttons_ids.nids; i++) {
int id = buttons_ids.id[i];
adb_get_infos(id, &default_id, &org_handler_id);
adbhid_input_register(id, default_id, org_handler_id, org_handler_id, 0);
reg |= adbhid_input_reregister(id, default_id, org_handler_id, org_handler_id, 0);
}
/* Try to switch all mice to handler 4, or 2 for three-button
......@@ -676,9 +727,10 @@ adbhid_probe(void)
printk("\n");
adb_get_infos(id, &default_id, &cur_handler_id);
adbhid_input_register(id, default_id, org_handler_id,
reg |= adbhid_input_reregister(id, default_id, org_handler_id,
cur_handler_id, mouse_kind);
}
adbhid_input_devcleanup(reg);
}
static void
......
/*
* /dev/lcd driver for Apple Network Servers.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/sections.h>
#include <asm/prom.h>
#include <asm/ans-lcd.h>
#include <asm/io.h>
#define ANSLCD_ADDR 0xf301c000
#define ANSLCD_CTRL_IX 0x00
#define ANSLCD_DATA_IX 0x10
static unsigned long anslcd_short_delay = 80;
static unsigned long anslcd_long_delay = 3280;
static volatile unsigned char* anslcd_ptr;
#undef DEBUG
static void __pmac
anslcd_write_byte_ctrl ( unsigned char c )
{
#ifdef DEBUG
printk(KERN_DEBUG "LCD: CTRL byte: %02x\n",c);
#endif
out_8(anslcd_ptr + ANSLCD_CTRL_IX, c);
switch(c) {
case 1:
case 2:
case 3:
udelay(anslcd_long_delay); break;
default: udelay(anslcd_short_delay);
}
}
static void __pmac
anslcd_write_byte_data ( unsigned char c )
{
out_8(anslcd_ptr + ANSLCD_DATA_IX, c);
udelay(anslcd_short_delay);
}
static ssize_t __pmac
anslcd_write( struct file * file, const char * buf,
size_t count, loff_t *ppos )
{
const char * p = buf;
int i;
#ifdef DEBUG
printk(KERN_DEBUG "LCD: write\n");
#endif
if ( verify_area(VERIFY_READ, buf, count) )
return -EFAULT;
for ( i = *ppos; count > 0; ++i, ++p, --count )
{
char c;
__get_user(c, p);
anslcd_write_byte_data( c );
}
*ppos = i;
return p - buf;
}
static int __pmac
anslcd_ioctl( struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg )
{
char ch, *temp;
#ifdef DEBUG
printk(KERN_DEBUG "LCD: ioctl(%d,%d)\n",cmd,arg);
#endif
switch ( cmd )
{
case ANSLCD_CLEAR:
anslcd_write_byte_ctrl ( 0x38 );
anslcd_write_byte_ctrl ( 0x0f );
anslcd_write_byte_ctrl ( 0x06 );
anslcd_write_byte_ctrl ( 0x01 );
anslcd_write_byte_ctrl ( 0x02 );
return 0;
case ANSLCD_SENDCTRL:
temp = (char *) arg;
__get_user(ch, temp);
for (; ch; temp++) { /* FIXME: This is ugly, but should work, as a \0 byte is not a valid command code */
anslcd_write_byte_ctrl ( ch );
__get_user(ch, temp);
}
return 0;
case ANSLCD_SETSHORTDELAY:
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
anslcd_short_delay=arg;
return 0;
case ANSLCD_SETLONGDELAY:
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
anslcd_long_delay=arg;
return 0;
default:
return -EINVAL;
}
}
static int __pmac
anslcd_open( struct inode * inode, struct file * file )
{
return 0;
}
struct file_operations anslcd_fops = {
write: anslcd_write,
ioctl: anslcd_ioctl,
open: anslcd_open,
};
static struct miscdevice anslcd_dev = {
ANSLCD_MINOR,
"anslcd",
&anslcd_fops
};
const char anslcd_logo[] = "********************" /* Line #1 */
"* LINUX! *" /* Line #3 */
"* Welcome to *" /* Line #2 */
"********************"; /* Line #4 */
int __init
anslcd_init(void)
{
int a;
struct device_node* node;
node = find_devices("lcd");
if (!node || !node->parent)
return -ENODEV;
if (strcmp(node->parent->name, "gc"))
return -ENODEV;
anslcd_ptr = (volatile unsigned char*)ioremap(ANSLCD_ADDR, 0x20);
misc_register(&anslcd_dev);
#ifdef DEBUG
printk(KERN_DEBUG "LCD: init\n");
#endif
anslcd_write_byte_ctrl ( 0x38 );
anslcd_write_byte_ctrl ( 0x0c );
anslcd_write_byte_ctrl ( 0x06 );
anslcd_write_byte_ctrl ( 0x01 );
anslcd_write_byte_ctrl ( 0x02 );
for(a=0;a<80;a++) {
anslcd_write_byte_data(anslcd_logo[a]);
}
return 0;
}
__initcall(anslcd_init);
/* APM emulation layer for PowerMac
*
* Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org)
*
* Lots of code inherited from apm.c, see appropriate notice in
* arch/i386/kernel/apm.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/types.h>
#include <linux/stddef.h>
#include <linux/timer.h>
#include <linux/fcntl.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/proc_fs.h>
#include <linux/miscdevice.h>
#include <linux/apm_bios.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/kernel.h>
#include <linux/smp_lock.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/machdep.h>
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(KERN_DEBUG args)
//#define DBG(args...) xmon_printf(args)
#else
#define DBG(args...) do { } while (0)
#endif
/*
* The apm_bios device is one of the misc char devices.
* This is its minor number.
*/
#define APM_MINOR_DEV 134
/*
* Maximum number of events stored
*/
#define APM_MAX_EVENTS 20
#define FAKE_APM_BIOS_VERSION 0x0101
#define APM_USER_NOTIFY_TIMEOUT (5*HZ)
/*
* The per-file APM data
*/
struct apm_user {
int magic;
struct apm_user * next;
int suser: 1;
int suspend_waiting: 1;
int suspends_pending;
int suspends_read;
int event_head;
int event_tail;
apm_event_t events[APM_MAX_EVENTS];
};
/*
* The magic number in apm_user
*/
#define APM_BIOS_MAGIC 0x4101
/*
* Local variables
*/
static int suspends_pending;
static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
static struct apm_user * user_list;
static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when);
static struct pmu_sleep_notifier apm_sleep_notifier = {
apm_notify_sleep,
SLEEP_LEVEL_USERLAND,
};
static char driver_version[] = "0.5"; /* no spaces */
#ifdef DEBUG
static char * apm_event_name[] = {
"system standby",
"system suspend",
"normal resume",
"critical resume",
"low battery",
"power status change",
"update time",
"critical suspend",
"user standby",
"user suspend",
"system standby resume",
"capabilities change"
};
#define NR_APM_EVENT_NAME \
(sizeof(apm_event_name) / sizeof(apm_event_name[0]))
#endif
static int queue_empty(struct apm_user *as)
{
return as->event_head == as->event_tail;
}
static apm_event_t get_queued_event(struct apm_user *as)
{
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
return as->events[as->event_tail];
}
static void queue_event(apm_event_t event, struct apm_user *sender)
{
struct apm_user * as;
DBG("apm_emu: queue_event(%s)\n", apm_event_name[event-1]);
if (user_list == NULL)
return;
for (as = user_list; as != NULL; as = as->next) {
if (as == sender)
continue;
as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
if (as->event_head == as->event_tail) {
static int notified;
if (notified++ == 0)
printk(KERN_ERR "apm_emu: an event queue overflowed\n");
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
}
as->events[as->event_head] = event;
if (!as->suser)
continue;
switch (event) {
case APM_SYS_SUSPEND:
case APM_USER_SUSPEND:
as->suspends_pending++;
suspends_pending++;
break;
case APM_NORMAL_RESUME:
as->suspend_waiting = 0;
break;
}
}
wake_up_interruptible(&apm_waitqueue);
}
static int check_apm_user(struct apm_user *as, const char *func)
{
if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
printk(KERN_ERR "apm_emu: %s passed bad filp\n", func);
return 1;
}
return 0;
}
static ssize_t do_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
{
struct apm_user * as;
int i;
apm_event_t event;
DECLARE_WAITQUEUE(wait, current);
as = fp->private_data;
if (check_apm_user(as, "read"))
return -EIO;
if (count < sizeof(apm_event_t))
return -EINVAL;
if (queue_empty(as)) {
if (fp->f_flags & O_NONBLOCK)
return -EAGAIN;
add_wait_queue(&apm_waitqueue, &wait);
repeat:
set_current_state(TASK_INTERRUPTIBLE);
if (queue_empty(as) && !signal_pending(current)) {
schedule();
goto repeat;
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&apm_waitqueue, &wait);
}
i = count;
while ((i >= sizeof(event)) && !queue_empty(as)) {
event = get_queued_event(as);
DBG("apm_emu: do_read, returning: %s\n", apm_event_name[event-1]);
if (copy_to_user(buf, &event, sizeof(event))) {
if (i < count)
break;
return -EFAULT;
}
switch (event) {
case APM_SYS_SUSPEND:
case APM_USER_SUSPEND:
as->suspends_read++;
break;
}
buf += sizeof(event);
i -= sizeof(event);
}
if (i < count)
return count - i;
if (signal_pending(current))
return -ERESTARTSYS;
return 0;
}
static unsigned int do_poll(struct file *fp, poll_table * wait)
{
struct apm_user * as;
as = fp->private_data;
if (check_apm_user(as, "poll"))
return 0;
poll_wait(fp, &apm_waitqueue, wait);
if (!queue_empty(as))
return POLLIN | POLLRDNORM;
return 0;
}
static int do_ioctl(struct inode * inode, struct file *filp,
u_int cmd, u_long arg)
{
struct apm_user * as;
DECLARE_WAITQUEUE(wait, current);
as = filp->private_data;
if (check_apm_user(as, "ioctl"))
return -EIO;
if (!as->suser)
return -EPERM;
switch (cmd) {
case APM_IOC_SUSPEND:
/* If a suspend message was sent to userland, we
* consider this as a confirmation message
*/
if (as->suspends_read > 0) {
as->suspends_read--;
as->suspends_pending--;
suspends_pending--;
} else {
// Route to PMU suspend ?
break;
}
as->suspend_waiting = 1;
add_wait_queue(&apm_waitqueue, &wait);
DBG("apm_emu: ioctl waking up sleep waiter !\n");
wake_up(&apm_suspend_waitqueue);
mb();
while(as->suspend_waiting && !signal_pending(current)) {
set_current_state(TASK_INTERRUPTIBLE);
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&apm_waitqueue, &wait);
break;
default:
return -EINVAL;
}
return 0;
}
static int do_release(struct inode * inode, struct file * filp)
{
struct apm_user * as;
as = filp->private_data;
if (check_apm_user(as, "release"))
return 0;
filp->private_data = NULL;
lock_kernel();
if (as->suspends_pending > 0) {
suspends_pending -= as->suspends_pending;
if (suspends_pending <= 0)
wake_up(&apm_suspend_waitqueue);
}
if (user_list == as)
user_list = as->next;
else {
struct apm_user * as1;
for (as1 = user_list;
(as1 != NULL) && (as1->next != as);
as1 = as1->next)
;
if (as1 == NULL)
printk(KERN_ERR "apm: filp not in user list\n");
else
as1->next = as->next;
}
unlock_kernel();
kfree(as);
return 0;
}
static int do_open(struct inode * inode, struct file * filp)
{
struct apm_user * as;
as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
if (as == NULL) {
printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n",
sizeof(*as));
return -ENOMEM;
}
as->magic = APM_BIOS_MAGIC;
as->event_tail = as->event_head = 0;
as->suspends_pending = 0;
as->suspends_read = 0;
/*
* XXX - this is a tiny bit broken, when we consider BSD
* process accounting. If the device is opened by root, we
* instantly flag that we used superuser privs. Who knows,
* we might close the device immediately without doing a
* privileged operation -- cevans
*/
as->suser = capable(CAP_SYS_ADMIN);
as->next = user_list;
user_list = as;
filp->private_data = as;
DBG("apm_emu: opened by %s, suser: %d\n", current->comm, (int)as->suser);
return 0;
}
/* Wait for all clients to ack the suspend request. APM API
* doesn't provide a way to NAK, but this could be added
* here.
*/
static int wait_all_suspend(void)
{
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&apm_suspend_waitqueue, &wait);
DBG("apm_emu: wait_all_suspend(), suspends_pending: %d\n", suspends_pending);
while(suspends_pending > 0) {
set_current_state(TASK_UNINTERRUPTIBLE);
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&apm_suspend_waitqueue, &wait);
DBG("apm_emu: wait_all_suspend() - complete !\n");
return 1;
}
static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
{
switch(when) {
case PBOOK_SLEEP_REQUEST:
queue_event(APM_SYS_SUSPEND, NULL);
if (!wait_all_suspend())
return PBOOK_SLEEP_REFUSE;
break;
case PBOOK_SLEEP_REJECT:
case PBOOK_WAKE:
queue_event(APM_NORMAL_RESUME, NULL);
break;
}
return PBOOK_SLEEP_OK;
}
#define APM_CRITICAL 10
#define APM_LOW 30
static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length)
{
/* Arguments, with symbols from linux/apm_bios.h. Information is
from the Get Power Status (0x0a) call unless otherwise noted.
0) Linux driver version (this will change if format changes)
1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
2) APM flags from APM Installation Check (0x00):
bit 0: APM_16_BIT_SUPPORT
bit 1: APM_32_BIT_SUPPORT
bit 2: APM_IDLE_SLOWS_CLOCK
bit 3: APM_BIOS_DISABLED
bit 4: APM_BIOS_DISENGAGED
3) AC line status
0x00: Off-line
0x01: On-line
0x02: On backup power (BIOS >= 1.1 only)
0xff: Unknown
4) Battery status
0x00: High
0x01: Low
0x02: Critical
0x03: Charging
0x04: Selected battery not present (BIOS >= 1.2 only)
0xff: Unknown
5) Battery flag
bit 0: High
bit 1: Low
bit 2: Critical
bit 3: Charging
bit 7: No system battery
0xff: Unknown
6) Remaining battery life (percentage of charge):
0-100: valid
-1: Unknown
7) Remaining battery life (time units):
Number of remaining minutes or seconds
-1: Unknown
8) min = minutes; sec = seconds */
unsigned short ac_line_status = 0xff;
unsigned short battery_status = 0xff;
unsigned short battery_flag = 0xff;
int percentage = -1;
int time_units = -1;
int real_count = 0;
int charge = -1;
int current = 0;
int i;
char * p = buf;
char charging = 0;
ac_line_status = ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0);
for (i=0; i<pmu_battery_count; i++) {
if (percentage < 0)
percentage = 0;
if (charge < 0)
charge = 0;
if (pmu_batteries[i].flags & PMU_BATT_PRESENT) {
percentage += (pmu_batteries[i].charge * 100) /
pmu_batteries[i].max_charge;
/* hrm... should we provide the remaining charge
* time when AC is plugged ? If yes, just remove
* that test --BenH
*/
if (!ac_line_status) {
charge += pmu_batteries[i].charge;
current += pmu_batteries[i].current;
}
real_count++;
if ((pmu_batteries[i].flags & PMU_BATT_CHARGING))
charging++;
}
}
if (real_count) {
time_units = (charge * 59) / (current * -1);
if(!charging)
battery_flag &= ~0x08;
percentage /= real_count;
if (battery_flag & 0x08) {
battery_status = 0x03;
battery_flag = 0x08;
} else if (percentage <= APM_CRITICAL) {
battery_status = 0x02;
battery_flag = 0x04;
} else if (percentage <= APM_LOW) {
battery_status = 0x01;
battery_flag = 0x02;
} else {
battery_status = 0x00;
battery_flag = 0x01;
}
}
p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
driver_version,
(FAKE_APM_BIOS_VERSION >> 8) & 0xff,
FAKE_APM_BIOS_VERSION & 0xff,
0,
ac_line_status,
battery_status,
battery_flag,
percentage,
time_units,
"min");
return p - buf;
}
static struct file_operations apm_bios_fops = {
owner: THIS_MODULE,
read: do_read,
poll: do_poll,
ioctl: do_ioctl,
open: do_open,
release: do_release,
};
static struct miscdevice apm_device = {
APM_MINOR_DEV,
"apm_bios",
&apm_bios_fops
};
static int __init apm_emu_init(void)
{
struct proc_dir_entry *apm_proc;
if (sys_ctrler != SYS_CTRLER_PMU) {
printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
return -ENODEV;
}
apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
if (apm_proc)
SET_MODULE_OWNER(apm_proc);
misc_register(&apm_device);
pmu_register_sleep_notifier(&apm_sleep_notifier);
printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
return 0;
}
static void __exit apm_emu_exit(void)
{
pmu_unregister_sleep_notifier(&apm_sleep_notifier);
misc_deregister(&apm_device);
remove_proc_entry("apm", NULL);
printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
}
module_init(apm_emu_init);
module_exit(apm_emu_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt");
MODULE_DESCRIPTION("APM emulation layer for PowerMac");
MODULE_LICENSE("GPL");
EXPORT_NO_SYMBOLS;
......@@ -200,15 +200,16 @@ static unsigned char e0_keys[128] = {
0, 0, 0, KEY_KPCOMMA, 0, KEY_INTL3, 0, 0, /* 0x00-0x07 */
0, 0, 0, 0, KEY_LANG1, KEY_LANG2, 0, 0, /* 0x08-0x0f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x10-0x17 */
0, 0, 0, 0, KEY_KPENTER, KEY_RIGHTCTRL, 0, 0, /* 0x18-0x1f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x20-0x27 */
0, 0, 0, 0, KEY_KPENTER, KEY_RIGHTCTRL, KEY_VOLUMEUP, 0,/* 0x18-0x1f */
0, 0, 0, 0, 0, KEY_VOLUMEDOWN, KEY_MUTE, 0, /* 0x20-0x27 */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x28-0x2f */
0, 0, 0, 0, 0, KEY_KPSLASH, 0, KEY_SYSRQ, /* 0x30-0x37 */
KEY_RIGHTALT, 0, 0, 0, 0, 0, 0, 0, /* 0x38-0x3f */
KEY_RIGHTALT, KEY_BRIGHTNESSUP, KEY_BRIGHTNESSDOWN,
KEY_EJECTCD, 0, 0, 0, 0, /* 0x38-0x3f */
0, 0, 0, 0, 0, 0, 0, KEY_HOME, /* 0x40-0x47 */
KEY_UP, KEY_PAGEUP, 0, KEY_LEFT, 0, KEY_RIGHT, 0, KEY_END, /* 0x48-0x4f */
KEY_DOWN, KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE, 0, 0, 0, 0, /* 0x50-0x57 */
0, 0, 0, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_COMPOSE, 0, 0, /* 0x58-0x5f */
0, 0, 0, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_COMPOSE, KEY_POWER, 0, /* 0x58-0x5f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x67 */
0, 0, 0, 0, 0, 0, 0, KEY_MACRO, /* 0x68-0x6f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x70-0x77 */
......
......@@ -38,8 +38,10 @@
#include <asm/irq.h>
#include <asm/prom.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/bitops.h>
#include <asm/feature.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#ifdef CONFIG_KGDB
......@@ -58,6 +60,7 @@ static struct pmu_sleep_notifier serial_sleep_notifier = {
#endif
#define SUPPORT_SERIAL_DMA
#define MACSERIAL_VERSION "2.0"
/*
* It would be nice to dynamically allocate everything that
......@@ -181,7 +184,7 @@ static DECLARE_MUTEX(tmp_buf_sem);
static inline int __pmac
serial_paranoia_check(struct mac_serial *info,
dev_t device, const char *routine)
kdev_t device, const char *routine)
{
#ifdef SERIAL_PARANOIA_CHECK
static const char badmagic[] = KERN_WARNING
......@@ -190,11 +193,11 @@ serial_paranoia_check(struct mac_serial *info,
"Warning: null mac_serial for (%d, %d) in %s\n";
if (!info) {
printk(badinfo, MAJOR(device), MINOR(device), routine);
printk(badinfo, major(device), minor(device), routine);
return 1;
}
if (info->magic != SERIAL_MAGIC) {
printk(badmagic, MAJOR(device), MINOR(device), routine);
printk(badmagic, major(device), minor(device), routine);
return 1;
}
#endif
......@@ -1030,7 +1033,7 @@ static int setup_scc(struct mac_serial * info)
{
unsigned long flags;
OPNDBG("setting up ttys%d SCC...\n", info->line);
OPNDBG("setting up ttyS%d SCC...\n", info->line);
save_flags(flags); cli(); /* Disable interrupts */
......@@ -1178,7 +1181,7 @@ static void shutdown(struct mac_serial * info)
}
memset(info->curregs, 0, sizeof(info->curregs));
memset(info->curregs, 0, sizeof(info->pendregs));
memset(info->pendregs, 0, sizeof(info->pendregs));
info->flags &= ~ZILOG_INITIALIZED;
}
......@@ -1193,69 +1196,34 @@ static int set_scc_power(struct mac_serial * info, int state)
{
int delay = 0;
if (feature_test(info->dev_node, FEATURE_Serial_enable) < 0)
return 0; /* don't have serial power control */
/* The timings looks strange but that's the ones MacOS seems
to use for the internal modem. I think we can use a lot faster
ones, at least whe not using the modem, this should be tested.
*/
if (state) {
PWRDBG("ttyS%02d: powering up hardware\n", info->line);
if (feature_test(info->dev_node, FEATURE_Serial_enable) == 0) {
feature_set(info->dev_node, FEATURE_Serial_enable);
mdelay(10);
feature_set(info->dev_node, FEATURE_Serial_reset);
mdelay(15);
feature_clear(info->dev_node, FEATURE_Serial_reset);
mdelay(10);
}
if (info->zs_chan_a == info->zs_channel)
feature_set(info->dev_node, FEATURE_Serial_IO_A);
else
feature_set(info->dev_node, FEATURE_Serial_IO_B);
delay = 10;
if (info->is_cobalt_modem) {
feature_set_modem_power(info->dev_node, 1);
PWRDBG("ttyS%d: powering up hardware\n", info->line);
pmac_call_feature(
PMAC_FTR_SCC_ENABLE,
info->dev_node, info->port_type, 1);
if (info->is_internal_modem) {
pmac_call_feature(
PMAC_FTR_MODEM_ENABLE,
info->dev_node, 0, 1);
delay = 2500; /* wait for 2.5s before using */
}
#ifdef CONFIG_PMAC_PBOOK
if (info->is_irda)
pmu_enable_irled(1);
#endif /* CONFIG_PMAC_PBOOK */
} else if (info->is_irda)
mdelay(50); /* Do better here once the problems
* with blocking have been ironed out
*/
} else {
/* TODO: Make that depend on a timer, don't power down
* immediately
*/
PWRDBG("ttyS%02d: shutting down hardware\n", info->line);
if (info->is_cobalt_modem) {
PWRDBG("ttyS%02d: shutting down modem\n", info->line);
feature_set_modem_power(info->dev_node, 0);
}
#ifdef CONFIG_PMAC_PBOOK
if (info->is_irda)
pmu_enable_irled(0);
#endif /* CONFIG_PMAC_PBOOK */
if (info->zs_chan_a == info->zs_channel && !info->is_irda) {
PWRDBG("ttyS%02d: shutting down SCC channel A\n", info->line);
feature_clear(info->dev_node, FEATURE_Serial_IO_A);
} else if (!info->is_irda) {
PWRDBG("ttyS%02d: shutting down SCC channel B\n", info->line);
feature_clear(info->dev_node, FEATURE_Serial_IO_B);
}
/* XXX for now, shut down SCC core only on powerbooks */
if (is_powerbook
&& !(feature_test(info->dev_node, FEATURE_Serial_IO_A) ||
feature_test(info->dev_node, FEATURE_Serial_IO_B))) {
PWRDBG("ttyS%02d: shutting down SCC core\n", info->line);
feature_set(info->dev_node, FEATURE_Serial_reset);
mdelay(15);
feature_clear(info->dev_node, FEATURE_Serial_reset);
mdelay(25);
feature_clear(info->dev_node, FEATURE_Serial_enable);
mdelay(5);
PWRDBG("ttyS%d: shutting down hardware\n", info->line);
if (info->is_internal_modem) {
PWRDBG("ttyS%d: shutting down modem\n", info->line);
pmac_call_feature(
PMAC_FTR_MODEM_ENABLE,
info->dev_node, 0, 0);
}
pmac_call_feature(
PMAC_FTR_SCC_ENABLE,
info->dev_node, info->port_type, 0);
}
return delay;
}
......@@ -1988,7 +1956,7 @@ static void rs_close(struct tty_struct *tty, struct file * filp)
return;
}
OPNDBG("rs_close ttys%d, count = %d\n", info->line, info->count);
OPNDBG("rs_close ttyS%d, count = %d\n", info->line, info->count);
if ((tty->count == 1) && (info->count != 1)) {
/*
* Uh, oh. tty->count is 1, which means that the tty
......@@ -2003,7 +1971,7 @@ static void rs_close(struct tty_struct *tty, struct file * filp)
}
if (--info->count < 0) {
printk(KERN_ERR "rs_close: bad serial port count for "
"ttys%d: %d\n", info->line, info->count);
"ttyS%d: %d\n", info->line, info->count);
info->count = 0;
}
if (info->count) {
......@@ -2220,7 +2188,7 @@ static int block_til_ready(struct tty_struct *tty, struct file * filp,
*/
retval = 0;
add_wait_queue(&info->open_wait, &wait);
OPNDBG("block_til_ready before block: ttys%d, count = %d\n",
OPNDBG("block_til_ready before block: ttyS%d, count = %d\n",
info->line, info->count);
cli();
if (!tty_hung_up_p(filp))
......@@ -2255,7 +2223,7 @@ static int block_til_ready(struct tty_struct *tty, struct file * filp,
retval = -ERESTARTSYS;
break;
}
OPNDBG("block_til_ready blocking: ttys%d, count = %d\n",
OPNDBG("block_til_ready blocking: ttyS%d, count = %d\n",
info->line, info->count);
schedule();
}
......@@ -2264,7 +2232,7 @@ static int block_til_ready(struct tty_struct *tty, struct file * filp,
if (!tty_hung_up_p(filp))
info->count++;
info->blocked_open--;
OPNDBG("block_til_ready after blocking: ttys%d, count = %d\n",
OPNDBG("block_til_ready after blocking: ttyS%d, count = %d\n",
info->line, info->count);
if (retval)
return retval;
......@@ -2285,7 +2253,7 @@ static int rs_open(struct tty_struct *tty, struct file * filp)
unsigned long page;
MOD_INC_USE_COUNT;
line = MINOR(tty->device) - tty->driver.minor_start;
line = minor(tty->device) - tty->driver.minor_start;
if ((line < 0) || (line >= zs_channels_found)) {
MOD_DEC_USE_COUNT;
return -ENODEV;
......@@ -2365,7 +2333,7 @@ static int rs_open(struct tty_struct *tty, struct file * filp)
info->session = current->session;
info->pgrp = current->pgrp;
OPNDBG("rs_open ttys%d successful...\n", info->line);
OPNDBG("rs_open ttyS%d successful...\n", info->line);
return 0;
}
......@@ -2373,14 +2341,14 @@ static int rs_open(struct tty_struct *tty, struct file * filp)
static void show_serial_version(void)
{
printk(KERN_INFO "PowerMac Z8530 serial driver version 2.0\n");
printk(KERN_INFO "PowerMac Z8530 serial driver version " MACSERIAL_VERSION "\n");
}
/*
* Initialize one channel, both the mac_serial and mac_zschannel
* structs. We use the dev_node field of the mac_serial struct.
*/
static void
static int
chan_init(struct mac_serial *zss, struct mac_zschannel *zs_chan,
struct mac_zschannel *zs_chan_a)
{
......@@ -2410,12 +2378,15 @@ chan_init(struct mac_serial *zss, struct mac_zschannel *zs_chan,
/* setup misc varariables */
zss->kgdb_channel = 0;
zss->is_cobalt_modem = device_is_compatible(ch, "cobalt");
/* XXX tested only with wallstreet PowerBook,
should do no harm anyway */
/* For now, we assume you either have a slot-names property
* with "Modem" in it, or your channel is compatible with
* "cobalt". Might need additional fixups
*/
zss->is_internal_modem = device_is_compatible(ch, "cobalt");
conn = get_property(ch, "AAPL,connector", &len);
zss->is_irda = conn && (strcmp(conn, "infrared") == 0);
zss->port_type = PMAC_SCC_ASYNC;
/* 1999 Powerbook G3 has slot-names property instead */
slots = (struct slot_names_prop *)get_property(ch, "slot-names", &len);
if (slots && slots->count > 0) {
......@@ -2424,8 +2395,29 @@ chan_init(struct mac_serial *zss, struct mac_zschannel *zs_chan,
else if (strcmp(slots->name, "Modem") == 0)
zss->is_internal_modem = 1;
}
if (zss->is_irda)
zss->port_type = PMAC_SCC_IRDA;
if (zss->is_internal_modem) {
struct device_node* i2c_modem = find_devices("i2c-modem");
if (i2c_modem) {
char* mid = get_property(i2c_modem, "modem-id", NULL);
if (mid) switch(*mid) {
case 0x04 :
case 0x05 :
case 0x07 :
case 0x08 :
case 0x0b :
case 0x0c :
zss->port_type = PMAC_SCC_I2S1;
}
printk(KERN_INFO "macserial: i2c-modem detected, id: %d\n",
mid ? (*mid) : 0);
} else {
printk(KERN_INFO "macserial: serial modem detected\n");
}
}
if (zss->has_dma) {
while (zss->has_dma) {
zss->dma_priv = NULL;
/* it seems that the last two addresses are the
DMA controllers */
......@@ -2436,11 +2428,69 @@ chan_init(struct mac_serial *zss, struct mac_zschannel *zs_chan,
zss->tx_dma_irq = ch->intrs[1].line;
zss->rx_dma_irq = ch->intrs[2].line;
spin_lock_init(&zss->rx_dma_lock);
break;
}
init_timer(&zss->powerup_timer);
zss->powerup_timer.function = powerup_done;
zss->powerup_timer.data = (unsigned long) zss;
return 0;
}
/*
* /proc fs routines. TODO: Add status lines & error stats
*/
static inline int
line_info(char *buf, struct mac_serial *info)
{
int ret=0;
unsigned char* connector;
int lenp;
ret += sprintf(buf, "%d: port:0x%X irq:%d", info->line, info->port, info->irq);
connector = get_property(info->dev_node, "AAPL,connector", &lenp);
if (connector)
ret+=sprintf(buf+ret," con:%s ", connector);
if (info->is_internal_modem) {
if (!connector)
ret+=sprintf(buf+ret," con:");
ret+=sprintf(buf+ret,"%s", " (internal modem)");
}
if (info->is_irda) {
if (!connector)
ret+=sprintf(buf+ret," con:");
ret+=sprintf(buf+ret,"%s", " (IrDA)");
}
ret+=sprintf(buf+ret,"\n");
return ret;
}
int macserial_read_proc(char *page, char **start, off_t off, int count,
int *eof, void *data)
{
int l, len = 0;
off_t begin = 0;
struct mac_serial *info;
len += sprintf(page, "serinfo:1.0 driver:" MACSERIAL_VERSION "\n");
for (info = zs_chain; info && len < 4000; info = info->zs_next) {
l = line_info(page + len, info);
len += l;
if (len+begin > off+count)
goto done;
if (len+begin < off) {
begin += len;
len = 0;
}
}
*eof = 1;
done:
if (off >= len+begin)
return 0;
*start = page + (off-begin);
return ((count < begin+len-off) ? count : begin+len-off);
}
/* Ask the PROM how many Z8530s we have and initialize their zs_channels */
......@@ -2495,13 +2545,15 @@ probe_sccs()
continue;
/* set up A side */
chan_init(&zs_soft[chip + chan_a_index], zs_chan, zs_chan);
if (chan_init(&zs_soft[chip + chan_a_index], zs_chan, zs_chan))
continue;
++zs_chan;
/* set up B side, if it exists */
if (nchan > 1)
chan_init(&zs_soft[chip + 1 - chan_a_index],
zs_chan, zs_chan - 1);
if (chan_init(&zs_soft[chip + 1 - chan_a_index],
zs_chan, zs_chan - 1))
continue;
++zs_chan;
}
*pp = 0;
......@@ -2527,13 +2579,35 @@ int macserial_init(void)
if (zs_chain == 0)
probe_sccs();
/* XXX assume it's a powerbook if we have a via-pmu */
/* XXX assume it's a powerbook if we have a via-pmu
*
* This is OK for core99 machines as well.
*/
is_powerbook = find_devices("via-pmu") != 0;
/* Register the interrupt handler for each one */
/* Register the interrupt handler for each one
* We also request the OF resources here as probe_sccs()
* might be called too early for that
*/
save_flags(flags); cli();
for (i = 0; i < zs_channels_found; ++i) {
struct device_node* ch = zs_soft[i].dev_node;
if (!request_OF_resource(ch, 0, NULL)) {
printk(KERN_ERR "macserial: can't request IO resource !\n");
return -ENODEV;
}
if (zs_soft[i].has_dma) {
if (!request_OF_resource(ch, ch->n_addrs - 2, " (tx dma)")) {
printk(KERN_ERR "macserial: can't request TX DMA resource !\n");
zs_soft[i].has_dma = 0;
goto no_dma;
}
if (!request_OF_resource(ch, ch->n_addrs - 1, " (rx dma)")) {
release_OF_resource(ch, ch->n_addrs - 2);
printk(KERN_ERR "macserial: can't request RX DMA resource !\n");
zs_soft[i].has_dma = 0;
goto no_dma;
}
if (request_irq(zs_soft[i].tx_dma_irq, rs_txdma_irq, 0,
"SCC-txdma", &zs_soft[i]))
printk(KERN_ERR "macserial: can't get irq %d\n",
......@@ -2545,6 +2619,7 @@ int macserial_init(void)
zs_soft[i].rx_dma_irq);
disable_irq(zs_soft[i].rx_dma_irq);
}
no_dma:
if (request_irq(zs_soft[i].irq, rs_interrupt, 0,
"SCC", &zs_soft[i]))
printk(KERN_ERR "macserial: can't get irq %d\n",
......@@ -2560,6 +2635,7 @@ int macserial_init(void)
memset(&serial_driver, 0, sizeof(struct tty_driver));
serial_driver.magic = TTY_DRIVER_MAGIC;
serial_driver.driver_name = "macserial";
#ifdef CONFIG_DEVFS_FS
serial_driver.name = "tts/%d";
#else
......@@ -2596,6 +2672,7 @@ int macserial_init(void)
serial_driver.hangup = rs_hangup;
serial_driver.break_ctl = rs_break;
serial_driver.wait_until_sent = rs_wait_until_sent;
serial_driver.read_proc = macserial_read_proc;
/*
* The callout device is just like normal device except for
......@@ -2609,6 +2686,8 @@ int macserial_init(void)
#endif /* CONFIG_DEVFS_FS */
callout_driver.major = TTYAUX_MAJOR;
callout_driver.subtype = SERIAL_TYPE_CALLOUT;
callout_driver.read_proc = 0;
callout_driver.proc_entry = 0;
if (tty_register_driver(&serial_driver))
panic("Couldn't register serial driver\n");
......@@ -2675,21 +2754,11 @@ int macserial_init(void)
connector = get_property(info->dev_node, "AAPL,connector", &lenp);
if (connector)
printk(", port = %s", connector);
if (info->is_cobalt_modem)
printk(" (cobalt modem)");
else if (info->is_internal_modem)
if (info->is_internal_modem)
printk(" (internal modem)");
if (info->is_irda)
printk(" (IrDA)");
printk("\n");
#ifndef CONFIG_XMON
#ifdef CONFIG_KGDB
if (!info->kgdb_channel)
#endif /* CONFIG_KGDB */
/* By default, disable the port */
set_scc_power(info, 0);
#endif /* CONFIG_XMON */
}
tmp_buf = 0;
......@@ -2711,6 +2780,12 @@ void macserial_cleanup(void)
free_irq(zs_soft[i].tx_dma_irq, &zs_soft[i]);
free_irq(zs_soft[i].rx_dma_irq, &zs_soft[i]);
}
release_OF_resource(zs_soft[i].dev_node, 0);
if (zs_soft[i].has_dma) {
struct device_node* ch = zs_soft[i].dev_node;
release_OF_resource(ch, ch->n_addrs - 2);
release_OF_resource(ch, ch->n_addrs - 1);
}
}
restore_flags(flags);
tty_unregister_driver(&callout_driver);
......@@ -2791,6 +2866,30 @@ static void serial_console_write(struct console *co, const char *s,
/* Don't disable the transmitter. */
}
/*
* Receive character from the serial port
*/
static int serial_console_wait_key(struct console *co)
{
struct mac_serial *info = zs_soft + co->index;
int val;
/* Turn of interrupts and enable the transmitter. */
write_zsreg(info->zs_channel, R1, info->curregs[1] & ~INT_ALL_Rx);
write_zsreg(info->zs_channel, R3, info->curregs[3] | RxENABLE);
/* Wait for something in the receive buffer. */
while((read_zsreg(info->zs_channel, 0) & Rx_CH_AV) == 0)
eieio();
val = read_zsdata(info->zs_channel);
/* Restore the values in the registers. */
write_zsreg(info->zs_channel, R1, info->curregs[1]);
write_zsreg(info->zs_channel, R3, info->curregs[3]);
return val;
}
static kdev_t serial_console_device(struct console *c)
{
return MKDEV(TTY_MAJOR, 64 + c->index);
......@@ -2981,6 +3080,7 @@ static struct console sercons = {
name: "ttyS",
write: serial_console_write,
device: serial_console_device,
wait_key: serial_console_wait_key,
setup: serial_console_setup,
flags: CON_PRINTBUFFER,
index: -1,
......
......@@ -111,8 +111,8 @@ struct mac_serial {
char kgdb_channel; /* Kgdb is running on this channel */
char is_cons; /* Is this our console. */
char is_internal_modem; /* is connected to an internal modem */
char is_cobalt_modem; /* is a gatwick-based cobalt modem */
char is_irda; /* is connected to an IrDA codec */
int port_type; /* Port type for pmac_feature */
unsigned char tx_active; /* character is being xmitted */
unsigned char tx_stopped; /* output is suspended */
unsigned char power_wait; /* waiting for power-up delay to expire */
......
......@@ -22,12 +22,17 @@
#include <linux/hdreg.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/init.h>
#include <asm/prom.h>
#include <asm/pgtable.h>
#include <asm/io.h>
#include <asm/feature.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#include <asm/mediabay.h>
#include <asm/sections.h>
#include <asm/ohare.h>
#include <asm/heathrow.h>
#include <asm/keylargo.h>
#include <linux/adb.h>
#include <linux/pmu.h>
......@@ -40,7 +45,7 @@ static struct pmu_sleep_notifier mb_sleep_notifier = {
#endif
#undef MB_USE_INTERRUPTS
#undef MB_DEBUG
#define MB_DEBUG
#define MB_IGNORE_SIGNALS
#ifdef MB_DEBUG
......@@ -49,24 +54,46 @@ static struct pmu_sleep_notifier mb_sleep_notifier = {
#define MBDBG(fmt, arg...) do { } while (0)
#endif
struct media_bay_hw {
unsigned char b0;
unsigned char contents;
unsigned char b2;
unsigned char b3;
/* Type of media bay */
enum {
mb_ohare,
mb_heathrow,
mb_keylargo
};
#define MB_FCR32(bay, r) ((bay)->base + ((r) >> 2))
#define MB_FCR8(bay, r) (((volatile u8*)((bay)->base)) + (r))
#define MB_IN32(bay,r) (in_le32(MB_FCR32(bay,r)))
#define MB_OUT32(bay,r,v) (out_le32(MB_FCR32(bay,r), (v)))
#define MB_BIS(bay,r,v) (MB_OUT32((bay), (r), MB_IN32((bay), r) | (v)))
#define MB_BIC(bay,r,v) (MB_OUT32((bay), (r), MB_IN32((bay), r) & ~(v)))
#define MB_IN8(bay,r) (in_8(MB_FCR8(bay,r)))
#define MB_OUT8(bay,r,v) (out_8(MB_FCR8(bay,r), (v)))
struct media_bay_info;
struct mb_ops {
char* name;
u8 (*content)(struct media_bay_info* bay);
void (*power)(struct media_bay_info* bay, int on_off);
int (*setup_bus)(struct media_bay_info* bay, u8 device_id);
void (*un_reset)(struct media_bay_info* bay);
void (*un_reset_ide)(struct media_bay_info* bay);
};
struct media_bay_info {
volatile struct media_bay_hw* addr;
volatile u8* extint_gpio;
volatile u32* base;
int content_id;
int state;
int last_value;
int value_count;
int timer;
struct device_node* dev_node;
int pismo; /* New PowerBook3,1 */
int gpio_cache;
int mb_type;
struct mb_ops* ops;
int index;
int cached_gpio;
#ifdef CONFIG_BLK_DEV_IDE
unsigned long cd_base;
int cd_index;
......@@ -77,33 +104,12 @@ struct media_bay_info {
#define MAX_BAYS 2
static volatile struct media_bay_info media_bays[MAX_BAYS];
static struct media_bay_info media_bays[MAX_BAYS];
int media_bay_count = 0;
inline int mb_content(volatile struct media_bay_info *bay)
{
if (bay->pismo) {
unsigned char new_gpio = in_8(bay->extint_gpio + 0xe) & 2;
if (new_gpio) {
bay->gpio_cache = new_gpio;
return MB_NO;
} else if (bay->gpio_cache != new_gpio) {
/* make sure content bits are set */
feature_set(bay->dev_node, FEATURE_Mediabay_content);
udelay(5);
bay->gpio_cache = new_gpio;
}
return (in_le32((unsigned*)bay->addr) >> 4) & 0xf;
} else {
int cont = (in_8(&bay->addr->contents) >> 4) & 7;
return (cont == 7) ? MB_NO : cont;
}
}
#ifdef CONFIG_BLK_DEV_IDE
/* check the busy bit in the media-bay ide interface
(assumes the media-bay contains an ide device) */
//#define MB_IDE_READY(i) ((inb(media_bays[i].cd_base + 0x70) & 0xc0) == 0x40)
#define MB_IDE_READY(i) ((inb(media_bays[i].cd_base + 0x70) & 0x80) == 0)
#endif
......@@ -116,7 +122,7 @@ inline int mb_content(volatile struct media_bay_info *bay)
* Consider the media-bay ID value stable if it is the same for
* this number of milliseconds
*/
#define MB_STABLE_DELAY 40
#define MB_STABLE_DELAY 100
/* Wait after powering up the media bay this delay in ms
* timeout bumped for some powerbooks
......@@ -166,175 +172,259 @@ enum {
mb_powering_down /* Powering down (avoid too fast down/up) */
};
static void poll_media_bay(int which);
static void set_media_bay(int which, int id);
static void set_mb_power(int which, int onoff);
static void media_bay_step(int i);
static int media_bay_task(void *);
#define MB_POWER_SOUND 0x08
#define MB_POWER_FLOPPY 0x04
#define MB_POWER_ATA 0x02
#define MB_POWER_PCI 0x01
#define MB_POWER_OFF 0x00
#ifdef MB_USE_INTERRUPTS
static void media_bay_intr(int irq, void *devid, struct pt_regs *regs);
#endif
/*
* Functions for polling content of media bay
*/
static u8 __pmac
ohare_mb_content(struct media_bay_info *bay)
{
return (MB_IN32(bay, OHARE_MBCR) >> 12) & 7;
}
static u8 __pmac
heathrow_mb_content(struct media_bay_info *bay)
{
return (MB_IN32(bay, HEATHROW_MBCR) >> 12) & 7;
}
static u8 __pmac
keylargo_mb_content(struct media_bay_info *bay)
{
int new_gpio;
new_gpio = MB_IN8(bay, KL_GPIO_MEDIABAY_IRQ) & KEYLARGO_GPIO_INPUT_DATA;
if (new_gpio) {
bay->cached_gpio = new_gpio;
return MB_NO;
} else if (bay->cached_gpio != new_gpio) {
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE);
(void)MB_IN32(bay, KEYLARGO_MBCR);
udelay(5);
MB_BIC(bay, KEYLARGO_MBCR, 0x0000000F);
(void)MB_IN32(bay, KEYLARGO_MBCR);
udelay(5);
bay->cached_gpio = new_gpio;
}
return (MB_IN32(bay, KEYLARGO_MBCR) >> 4) & 7;
}
/*
* It seems that the bit for the media-bay interrupt in the IRQ_LEVEL
* register is always set when there is something in the media bay.
* This causes problems for the interrupt code if we attach an interrupt
* handler to the media-bay interrupt, because it tends to go into
* an infinite loop calling the media bay interrupt handler.
* Therefore we do it all by polling the media bay once each tick.
* Functions for powering up/down the bay, puts the bay device
* into reset state as well
*/
void __pmac
media_bay_init(void)
static void __pmac
ohare_mb_power(struct media_bay_info* bay, int on_off)
{
struct device_node *np;
int n,i;
for (i=0; i<MAX_BAYS; i++) {
memset((char *)&media_bays[i], 0, sizeof(struct media_bay_info));
media_bays[i].content_id = -1;
#ifdef CONFIG_BLK_DEV_IDE
media_bays[i].cd_index = -1;
#endif
if (on_off) {
/* Power up device, assert it's reset line */
MB_BIC(bay, OHARE_FCR, OH_BAY_RESET_N);
MB_BIC(bay, OHARE_FCR, OH_BAY_POWER_N);
} else {
/* Disable all devices */
MB_BIC(bay, OHARE_FCR, OH_BAY_DEV_MASK);
MB_BIC(bay, OHARE_FCR, OH_FLOPPY_ENABLE);
/* Cut power from bay, release reset line */
MB_BIS(bay, OHARE_FCR, OH_BAY_POWER_N);
MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N);
MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N);
}
np = find_devices("media-bay");
n = 0;
while(np && (n<MAX_BAYS)) {
if (np->n_addrs == 0)
continue;
media_bays[n].addr = (volatile struct media_bay_hw *)
ioremap(np->addrs[0].address, sizeof(struct media_bay_hw));
MB_BIC(bay, OHARE_MBCR, 0x00000F00);
}
media_bays[n].pismo = device_is_compatible(np, "keylargo-media-bay");
if (media_bays[n].pismo) {
if (!np->parent || strcmp(np->parent->name, "mac-io")) {
printk(KERN_ERR "Pismo media-bay has no mac-io parent !\n");
continue;
}
media_bays[n].extint_gpio = ioremap(np->parent->addrs[0].address
+ 0x58, 0x10);
}
static void __pmac
heathrow_mb_power(struct media_bay_info* bay, int on_off)
{
if (on_off) {
/* Power up device, assert it's reset line */
MB_BIC(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
MB_BIC(bay, HEATHROW_FCR, HRW_BAY_POWER_N);
} else {
/* Disable all devices */
MB_BIC(bay, HEATHROW_FCR, HRW_BAY_DEV_MASK);
MB_BIC(bay, HEATHROW_FCR, HRW_SWIM_ENABLE);
/* Cut power from bay, release reset line */
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_POWER_N);
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
}
MB_BIC(bay, HEATHROW_MBCR, 0x00000F00);
}
#ifdef MB_USE_INTERRUPTS
if (np->n_intrs == 0) {
printk(KERN_ERR "media bay %d has no irq\n",n);
continue;
}
static void __pmac
keylargo_mb_power(struct media_bay_info* bay, int on_off)
{
if (on_off) {
/* Power up device, assert it's reset line */
MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_POWER);
} else {
/* Disable all devices */
MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_MASK);
MB_BIC(bay, KEYLARGO_FCR1, KL1_EIDE0_ENABLE);
/* Cut power from bay, release reset line */
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_POWER);
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
}
MB_BIC(bay, KEYLARGO_MBCR, 0x0000000F);
}
if (request_irq(np->intrs[0].line, media_bay_intr, 0, "Media bay", (void *)n)) {
printk(KERN_ERR "Couldn't get IRQ %d for media bay %d\n",
np->intrs[0].line, n);
continue;
}
#endif
media_bay_count++;
media_bays[n].dev_node = np;
/*
* Functions for configuring the media bay for a given type of device,
* enable the related busses
*/
/* Force an immediate detect */
set_mb_power(n,0);
mdelay(MB_POWER_DELAY);
if(!media_bays[n].pismo)
out_8(&media_bays[n].addr->contents, 0x70);
mdelay(MB_STABLE_DELAY);
media_bays[n].content_id = MB_NO;
media_bays[n].last_value = mb_content(&media_bays[n]);
media_bays[n].value_count = MS_TO_HZ(MB_STABLE_DELAY);
media_bays[n].state = mb_empty;
do {
mdelay(1000/HZ);
media_bay_step(n);
} while((media_bays[n].state != mb_empty) &&
(media_bays[n].state != mb_up));
static int __pmac
ohare_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
{
switch(device_id) {
case MB_FD:
case MB_FD1:
MB_BIS(bay, OHARE_FCR, OH_BAY_FLOPPY_ENABLE);
MB_BIS(bay, OHARE_FCR, OH_FLOPPY_ENABLE);
return 0;
case MB_CD:
MB_BIC(bay, OHARE_FCR, OH_IDE1_RESET_N);
MB_BIS(bay, OHARE_FCR, OH_BAY_IDE_ENABLE);
return 0;
case MB_PCI:
MB_BIS(bay, OHARE_FCR, OH_BAY_PCI_ENABLE);
return 0;
}
return -ENODEV;
}
n++;
np=np->next;
static int __pmac
heathrow_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
{
switch(device_id) {
case MB_FD:
case MB_FD1:
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_FLOPPY_ENABLE);
MB_BIS(bay, HEATHROW_FCR, HRW_SWIM_ENABLE);
return 0;
case MB_CD:
MB_BIC(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_IDE_ENABLE);
return 0;
case MB_PCI:
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_PCI_ENABLE);
return 0;
}
return -ENODEV;
}
if (media_bay_count)
{
printk(KERN_INFO "Registered %d media-bay(s)\n", media_bay_count);
static int __pmac
keylargo_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
{
switch(device_id) {
case MB_CD:
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_IDE_ENABLE);
MB_BIC(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_ENABLE);
return 0;
case MB_PCI:
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_PCI_ENABLE);
return 0;
case MB_SOUND:
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_SOUND_ENABLE);
return 0;
}
return -ENODEV;
}
#ifdef CONFIG_PMAC_PBOOK
pmu_register_sleep_notifier(&mb_sleep_notifier);
#endif /* CONFIG_PMAC_PBOOK */
/*
* Functions for tweaking resets
*/
kernel_thread(media_bay_task, NULL,
CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
}
static void __pmac
ohare_mb_un_reset(struct media_bay_info* bay)
{
MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N);
}
#ifdef MB_USE_INTERRUPTS
static void __pmac
media_bay_intr(int irq, void *devid, struct pt_regs *regs)
heathrow_mb_un_reset(struct media_bay_info* bay)
{
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
}
static void __pmac
keylargo_mb_un_reset(struct media_bay_info* bay)
{
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
}
static void __pmac
ohare_mb_un_reset_ide(struct media_bay_info* bay)
{
MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N);
}
static void __pmac
heathrow_mb_un_reset_ide(struct media_bay_info* bay)
{
MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
}
#endif
static void __pmac
set_mb_power(int which, int onoff)
keylargo_mb_un_reset_ide(struct media_bay_info* bay)
{
volatile struct media_bay_info* mb = &media_bays[which];
MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
}
static inline void __pmac
set_mb_power(struct media_bay_info* bay, int onoff)
{
/* Power up up and assert the bay reset line */
if (onoff) {
feature_set(mb->dev_node, FEATURE_Mediabay_power);
udelay(10);
feature_set(mb->dev_node, FEATURE_Mediabay_reset);
udelay(10);
mb->state = mb_powering_up;
MBDBG("mediabay%d: powering up\n", which);
} else {
feature_clear(mb->dev_node, FEATURE_Mediabay_floppy_enable);
if (mb->pismo)
feature_clear(mb->dev_node, FEATURE_IDE0_enable);
else
feature_clear(mb->dev_node, FEATURE_IDE1_enable);
feature_clear(mb->dev_node, FEATURE_Mediabay_IDE_switch);
feature_clear(mb->dev_node, FEATURE_Mediabay_PCI_enable);
feature_clear(mb->dev_node, FEATURE_SWIM3_enable);
feature_clear(mb->dev_node, FEATURE_Mediabay_power);
mb->state = mb_powering_down;
MBDBG("mediabay%d: powering down\n", which);
bay->ops->power(bay, 1);
bay->state = mb_powering_up;
MBDBG("mediabay%d: powering up\n", bay->index);
} else {
/* Make sure everything is powered down & disabled */
bay->ops->power(bay, 0);
bay->state = mb_powering_down;
MBDBG("mediabay%d: powering down\n", bay->index);
}
mb->timer = MS_TO_HZ(MB_POWER_DELAY);
bay->timer = MS_TO_HZ(MB_POWER_DELAY);
}
static void __pmac
set_media_bay(int which, int id)
poll_media_bay(struct media_bay_info* bay)
{
volatile struct media_bay_info* bay;
int id = bay->ops->content(bay);
bay = &media_bays[which];
switch (id) {
case MB_CD:
if (bay->pismo) {
feature_set(bay->dev_node, FEATURE_Mediabay_IDE_switch);
udelay(10);
feature_set(bay->dev_node, FEATURE_IDE0_enable);
udelay(10);
feature_set(bay->dev_node, FEATURE_IDE0_reset);
} else {
feature_set(bay->dev_node, FEATURE_IDE1_enable);
udelay(10);
feature_set(bay->dev_node, FEATURE_IDE1_reset);
if (id == bay->last_value) {
if (id != bay->content_id
&& ++bay->value_count >= MS_TO_HZ(MB_STABLE_DELAY)) {
/* If the device type changes without going thru "MB_NO", we force
a pass by "MB_NO" to make sure things are properly reset */
if ((id != MB_NO) && (bay->content_id != MB_NO)) {
id = MB_NO;
MBDBG("mediabay%d: forcing MB_NO\n", bay->index);
}
printk(KERN_INFO "media bay %d contains a CD-ROM drive\n", which);
break;
case MB_FD:
case MB_FD1:
feature_set(bay->dev_node, FEATURE_Mediabay_floppy_enable);
feature_set(bay->dev_node, FEATURE_SWIM3_enable);
printk(KERN_INFO "media bay %d contains a floppy disk drive\n", which);
break;
case MB_NO:
break;
default:
printk(KERN_INFO "media bay %d contains an unknown device (%d)\n",
which, id);
break;
MBDBG("mediabay%d: switching to %d\n", bay->index, id);
set_mb_power(bay, id != MB_NO);
bay->content_id = id;
if (id == MB_NO) {
#ifdef CONFIG_BLK_DEV_IDE
bay->cd_retry = 0;
#endif
printk(KERN_INFO "media bay %d is empty\n", bay->index);
}
}
} else {
bay->last_value = id;
bay->value_count = 0;
}
}
......@@ -412,11 +502,11 @@ media_bay_set_ide_infos(struct device_node* which_bay, unsigned long base,
static void __pmac
media_bay_step(int i)
{
volatile struct media_bay_info* bay = &media_bays[i];
struct media_bay_info* bay = &media_bays[i];
/* We don't poll when powering down */
if (bay->state != mb_powering_down)
poll_media_bay(i);
poll_media_bay(bay);
/* If timer expired or polling IDE busy, run state machine */
if ((bay->state != mb_ide_waiting) && (bay->timer != 0) && ((--bay->timer) != 0))
......@@ -424,13 +514,17 @@ media_bay_step(int i)
switch(bay->state) {
case mb_powering_up:
set_media_bay(i, bay->last_value);
if (bay->ops->setup_bus(bay, bay->last_value) < 0) {
MBDBG("mediabay%d: device not supported (kind:%d)\n", i, bay->content_id);
set_mb_power(bay, 0);
break;
}
bay->timer = MS_TO_HZ(MB_RESET_DELAY);
bay->state = mb_enabling_bay;
MBDBG("mediabay%d: enabling (kind:%d)\n", i, bay->content_id);
break;
case mb_enabling_bay:
feature_clear(bay->dev_node, FEATURE_Mediabay_reset);
bay->ops->un_reset(bay);
bay->timer = MS_TO_HZ(MB_SETUP_DELAY);
bay->state = mb_resetting;
MBDBG("mediabay%d: waiting reset (kind:%d)\n", i, bay->content_id);
......@@ -444,16 +538,13 @@ media_bay_step(int i)
}
#ifdef CONFIG_BLK_DEV_IDE
MBDBG("mediabay%d: waiting IDE reset (kind:%d)\n", i, bay->content_id);
if (bay->pismo)
feature_clear(bay->dev_node, FEATURE_IDE0_reset);
else
feature_clear(bay->dev_node, FEATURE_IDE1_reset);
bay->ops->un_reset_ide(bay);
bay->timer = MS_TO_HZ(MB_IDE_WAIT);
bay->state = mb_ide_resetting;
#else
printk(KERN_DEBUG "media-bay %d is ide (not compiled in kernel)\n", i);
set_mb_power(i, 0);
#endif // #ifdef CONFIG_BLK_DEV_IDE
set_mb_power(bay, 0);
#endif /* CONFIG_BLK_DEV_IDE */
break;
#ifdef CONFIG_BLK_DEV_IDE
......@@ -481,7 +572,7 @@ media_bay_step(int i)
/* We eventually do a retry */
bay->cd_retry++;
printk("IDE register error\n");
set_mb_power(i, 0);
set_mb_power(bay, 0);
} else {
printk(KERN_DEBUG "media-bay %d is ide %d\n", i, bay->cd_index);
MBDBG("mediabay %d IDE ready\n", i);
......@@ -491,10 +582,10 @@ media_bay_step(int i)
if (bay->timer == 0) {
printk("\nIDE Timeout in bay %d !\n", i);
MBDBG("mediabay%d: nIDE Timeout !\n", i);
set_mb_power(i, 0);
set_mb_power(bay, 0);
}
break;
#endif // #ifdef CONFIG_BLK_DEV_IDE
#endif /* CONFIG_BLK_DEV_IDE */
case mb_powering_down:
bay->state = mb_empty;
......@@ -514,7 +605,7 @@ media_bay_step(int i)
bay->content_id = MB_NO;
}
}
#endif
#endif /* CONFIG_BLK_DEV_IDE */
MBDBG("mediabay%d: end of power down\n", i);
break;
}
......@@ -526,7 +617,7 @@ media_bay_step(int i)
* with the IDE driver. It needs to be a thread because
* ide_register can't be called from interrupt context.
*/
int __pmac
static int __pmac
media_bay_task(void *x)
{
int i;
......@@ -547,37 +638,12 @@ media_bay_task(void *x)
}
}
void __pmac
poll_media_bay(int which)
#ifdef MB_USE_INTERRUPTS
static void __pmac
media_bay_intr(int irq, void *devid, struct pt_regs *regs)
{
volatile struct media_bay_info* bay = &media_bays[which];
int id = mb_content(bay);
if (id == bay->last_value) {
if (id != bay->content_id
&& ++bay->value_count >= MS_TO_HZ(MB_STABLE_DELAY)) {
/* If the device type changes without going thru "MB_NO", we force
a pass by "MB_NO" to make sure things are properly reset */
if ((id != MB_NO) && (bay->content_id != MB_NO)) {
id = MB_NO;
MBDBG("mediabay%d: forcing MB_NO\n", which);
}
MBDBG("mediabay%d: switching to %d\n", which, id);
set_mb_power(which, id != MB_NO);
bay->content_id = id;
if (id == MB_NO) {
#ifdef CONFIG_BLK_DEV_IDE
bay->cd_retry = 0;
#endif
printk(KERN_INFO "media bay %d is empty\n", which);
}
}
} else {
bay->last_value = id;
bay->value_count = 0;
}
}
#endif
#ifdef CONFIG_PMAC_PBOOK
/*
......@@ -586,7 +652,7 @@ poll_media_bay(int which)
int __pmac
mb_notify_sleep(struct pmu_sleep_notifier *self, int when)
{
volatile struct media_bay_info* bay;
struct media_bay_info* bay;
int i;
switch (when) {
......@@ -597,7 +663,7 @@ mb_notify_sleep(struct pmu_sleep_notifier *self, int when)
case PBOOK_SLEEP_NOW:
for (i=0; i<media_bay_count; i++) {
bay = &media_bays[i];
set_mb_power(i, 0);
set_mb_power(bay, 0);
mdelay(10);
}
break;
......@@ -609,14 +675,11 @@ mb_notify_sleep(struct pmu_sleep_notifier *self, int when)
they seem to help the 3400 get it right.
*/
/* Force MB power to 0 */
set_mb_power(i, 0);
set_mb_power(bay, 0);
mdelay(MB_POWER_DELAY);
if (!bay->pismo)
out_8(&bay->addr->contents, 0x70);
mdelay(MB_STABLE_DELAY);
if (mb_content(bay) != bay->content_id)
if (bay->ops->content(bay) != bay->content_id)
continue;
set_mb_power(i, 1);
set_mb_power(bay, 1);
bay->last_value = bay->content_id;
bay->value_count = MS_TO_HZ(MB_STABLE_DELAY);
bay->timer = MS_TO_HZ(MB_POWER_DELAY);
......@@ -635,3 +698,136 @@ mb_notify_sleep(struct pmu_sleep_notifier *self, int when)
}
#endif /* CONFIG_PMAC_PBOOK */
/* Definitions of "ops" structures.
*/
static struct mb_ops ohare_mb_ops __pmacdata = {
name: "Ohare",
content: ohare_mb_content,
power: ohare_mb_power,
setup_bus: ohare_mb_setup_bus,
un_reset: ohare_mb_un_reset,
un_reset_ide: ohare_mb_un_reset_ide,
};
static struct mb_ops heathrow_mb_ops __pmacdata = {
name: "Heathrow",
content: heathrow_mb_content,
power: heathrow_mb_power,
setup_bus: heathrow_mb_setup_bus,
un_reset: heathrow_mb_un_reset,
un_reset_ide: heathrow_mb_un_reset_ide,
};
static struct mb_ops keylargo_mb_ops __pmacdata = {
name: "KeyLargo",
content: keylargo_mb_content,
power: keylargo_mb_power,
setup_bus: keylargo_mb_setup_bus,
un_reset: keylargo_mb_un_reset,
un_reset_ide: keylargo_mb_un_reset_ide,
};
/*
* It seems that the bit for the media-bay interrupt in the IRQ_LEVEL
* register is always set when there is something in the media bay.
* This causes problems for the interrupt code if we attach an interrupt
* handler to the media-bay interrupt, because it tends to go into
* an infinite loop calling the media bay interrupt handler.
* Therefore we do it all by polling the media bay once each tick.
*/
static int __init
media_bay_init(void)
{
struct device_node *np;
int n,i;
for (i=0; i<MAX_BAYS; i++) {
memset((char *)&media_bays[i], 0, sizeof(struct media_bay_info));
media_bays[i].content_id = -1;
#ifdef CONFIG_BLK_DEV_IDE
media_bays[i].cd_index = -1;
#endif
}
np = find_devices("media-bay");
n = 0;
while(np && (n<MAX_BAYS)) {
struct media_bay_info* bay = &media_bays[n];
if (!np->parent || np->n_addrs == 0 || !request_OF_resource(np, 0, NULL)) {
np = np->next;
printk(KERN_ERR "media-bay: Can't request IO resource !\n");
continue;
}
bay->mb_type = mb_ohare;
if (device_is_compatible(np, "keylargo-media-bay")) {
bay->mb_type = mb_keylargo;
bay->ops = &keylargo_mb_ops;
} else if (device_is_compatible(np, "heathrow-media-bay")) {
bay->mb_type = mb_heathrow;
bay->ops = &heathrow_mb_ops;
} else if (device_is_compatible(np, "ohare-media-bay")) {
bay->mb_type = mb_ohare;
bay->ops = &ohare_mb_ops;
} else {
printk(KERN_ERR "mediabay: Unknown bay type !\n");
np = np->next;
continue;
}
bay->base = (volatile u32*)ioremap(np->parent->addrs[0].address, 0x1000);
/* Enable probe logic on keylargo */
if (bay->mb_type == mb_keylargo)
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE);
#ifdef MB_USE_INTERRUPTS
if (np->n_intrs == 0) {
printk(KERN_ERR "media bay %d has no irq\n",n);
np = np->next;
continue;
}
if (request_irq(np->intrs[0].line, media_bay_intr, 0, "Media bay", (void *)n)) {
printk(KERN_ERR "Couldn't get IRQ %d for media bay %d\n",
np->intrs[0].line, n);
np = np->next;
continue;
}
#endif
media_bay_count++;
printk(KERN_INFO "mediabay%d: Registered %s media-bay\n", n, bay->ops->name);
bay->dev_node = np;
bay->index = n;
/* Force an immediate detect */
set_mb_power(bay, 0);
mdelay(MB_POWER_DELAY);
bay->content_id = MB_NO;
bay->last_value = bay->ops->content(bay);
bay->value_count = MS_TO_HZ(MB_STABLE_DELAY);
bay->state = mb_empty;
do {
mdelay(1000/HZ);
media_bay_step(n);
} while((bay->state != mb_empty) &&
(bay->state != mb_up));
n++;
np=np->next;
}
if (media_bay_count)
{
#ifdef CONFIG_PMAC_PBOOK
pmu_register_sleep_notifier(&mb_sleep_notifier);
#endif /* CONFIG_PMAC_PBOOK */
kernel_thread(media_bay_task, NULL,
CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
}
return 0;
}
device_initcall(media_bay_init);
......@@ -34,14 +34,13 @@ static int rtc_busy = 0;
void get_rtc_time(struct rtc_time *t)
{
unsigned long nowtime;
nowtime = (ppc_md.get_rtc_time)();
to_tm(nowtime, t);
t->tm_year -= 1900;
t->tm_mon -= 1;
t->tm_wday -= 1;
t->tm_mon -= 1; /* Make sure userland has a 0-based month */
}
/* Set the current date and time in the real time clock. */
......@@ -49,7 +48,8 @@ void set_rtc_time(struct rtc_time *t)
{
unsigned long nowtime;
nowtime = mktime(t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
nowtime = mktime(t->tm_year+1900, t->tm_mon+1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
(ppc_md.set_rtc_time)(nowtime);
}
......
......@@ -125,7 +125,7 @@ struct adb_driver via_cuda_driver = {
#endif /* CONFIG_ADB */
#ifdef CONFIG_PPC
int
int __init
find_via_cuda(void)
{
int err;
......@@ -186,11 +186,13 @@ find_via_cuda(void)
}
#endif /* CONFIG_PPC */
int via_cuda_start(void)
static int __init via_cuda_start(void)
{
if (via == NULL)
return -ENODEV;
request_OF_resource(vias, 0, NULL);
if (request_irq(CUDA_IRQ, cuda_interrupt, 0, "ADB", cuda_interrupt)) {
printk(KERN_ERR "cuda_init: can't get irq %d\n", CUDA_IRQ);
return -EAGAIN;
......@@ -202,6 +204,8 @@ int via_cuda_start(void)
return 0;
}
device_initcall(via_cuda_start);
#ifdef CONFIG_ADB
static int
cuda_probe()
......@@ -217,7 +221,7 @@ cuda_probe()
return 0;
}
static int
static int __init
cuda_init(void)
{
if (via == NULL)
......
......@@ -9,9 +9,9 @@
* and the RTC (real time clock) chip.
*
* Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
* Copyright (C) 2001 Benjamin Herrenschmidt
*
* todo: - Check this driver for smp safety (new Core99 motherboards).
* - Cleanup synchro between VIA interrupt and GPIO-based PMU
* todo: - Cleanup synchro between VIA interrupt and GPIO-based PMU
* interrupt.
*
*
......@@ -45,10 +45,12 @@
#include <asm/sections.h>
#include <asm/irq.h>
#include <asm/hardirq.h>
#include <asm/feature.h>
#include <asm/pmac_feature.h>
#include <asm/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/sections.h>
#include <asm/cputable.h>
#include <asm/time.h>
#ifdef CONFIG_PMAC_BACKLIGHT
#include <asm/backlight.h>
#endif
......@@ -56,9 +58,14 @@
/* Some compile options */
#undef SUSPEND_USES_PMU
#define DEBUG_SLEEP
#undef HACKED_PCI_SAVE
#define NEW_OHARE_CODE
/* Misc minor number allocated for /dev/pmu */
#define PMU_MINOR 154
#define PMU_MINOR 154
/* How many iterations between battery polls */
#define BATTERY_POLLING_COUNT 2
static volatile unsigned char *via;
......@@ -108,7 +115,7 @@ static volatile enum 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[32];
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;
......@@ -126,9 +133,30 @@ 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;
static int can_sleep;
#endif /* CONFIG_PMAC_PBOOK */
static struct proc_dir_entry *proc_pmu_root;
static struct proc_dir_entry *proc_pmu_info;
static struct proc_dir_entry *proc_pmu_options;
#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;
......@@ -153,15 +181,22 @@ 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);
static int proc_get_info(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);
#endif
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 *buffer,
unsigned long count, void *data);
#ifdef CONFIG_ADB
struct adb_driver via_pmu_driver = {
......@@ -309,8 +344,12 @@ find_via_pmu()
} else
pmu_kind = PMU_UNKNOWN;
#ifdef CONFIG_PMAC_PBOOK
if (pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0)
can_sleep = 1;
#endif /* CONFIG_PMAC_PBOOK */
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 */
......@@ -341,7 +380,7 @@ pmu_probe()
return vias == NULL? -ENODEV: 0;
}
static int __openfirmware
static int __init
pmu_init(void)
{
if (vias == NULL)
......@@ -355,9 +394,9 @@ pmu_init(void)
* 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().
* Thus this is called with arch_initcall rather than device_initcall.
*/
int via_pmu_start(void)
static int __init via_pmu_start(void)
{
if (vias == NULL)
return -ENODEV;
......@@ -365,6 +404,9 @@ int via_pmu_start(void)
bright_req_1.complete = 1;
bright_req_2.complete = 1;
bright_req_3.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)) {
......@@ -383,11 +425,6 @@ int via_pmu_start(void)
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
......@@ -403,6 +440,66 @@ int via_pmu_start(void)
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;
request_OF_resource(vias, 0, NULL);
#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"))
pmu_battery_count = 1;
else if (machine_is_compatible("AAPL,PowerBook1998") ||
machine_is_compatible("PowerBook1,1"))
pmu_battery_count = 2;
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;
}
}
#endif /* CONFIG_PMAC_PBOOK */
/* Create /proc/pmu */
proc_pmu_root = proc_mkdir("pmu", 0);
if (proc_pmu_root) {
int i;
proc_pmu_info = create_proc_read_entry("info", 0, proc_pmu_root,
proc_get_info, NULL);
#ifdef CONFIG_PMAC_PBOOK
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_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()
{
......@@ -448,8 +545,8 @@ init_pmu()
pmu_request(&req, NULL, 1, PMU_GET_VERSION);
while (!req.complete)
pmu_poll();
if (req.reply_len > 1)
pmu_version = req.reply[1];
if (req.reply_len > 0)
pmu_version = req.reply[0];
return 1;
}
......@@ -460,6 +557,420 @@ pmu_get_model(void)
return pmu_kind;
}
#ifdef CONFIG_PMAC_PBOOK
/*
* WARNING ! This code probably needs some debugging... -- BenH.
*/
#ifdef NEW_OHARE_CODE
static void __pmac
done_battery_state_ohare(struct adb_request* req)
{
unsigned int bat_flags = 0;
int current = 0;
unsigned int capa, max, voltage, time;
int lrange[] = { 0, 275, 850, 1680, 2325,
2765, 3160, 3500, 3830, 4115,
4360, 4585, 4795, 4990, 5170,
5340, 5510, 5710, 5930, 6150,
6370, 6500
};
if (req->reply[0] & 0x01)
pmu_power_flags |= PMU_PWR_AC_PRESENT;
else
pmu_power_flags &= ~PMU_PWR_AC_PRESENT;
if (req->reply[0] & 0x04) {
int vb, i, j, k, charge, pcharge;
bat_flags |= PMU_BATT_PRESENT;
vb = (req->reply[1] << 8) | req->reply[2];
voltage = ((vb * 2650) + 726650)/100;
vb *= 100;
current = req->reply[5];
if ((req->reply[0] & 0x01) == 0 && (current > 200))
vb += (current - 200) * 15;
else if (req->reply[0] & 0x02)
vb = (vb - 2000);
i = (33000 - vb) / 10;
j = i - (i % 100);
k = j/100;
if (k <= 0)
charge = 0;
else if (k >= 21)
charge = 650000;
else
charge = (lrange[k + 1] - lrange[k]) * (i - j) + (lrange[k] * 100);
charge = (1000 - charge / 650) / 10;
if (req->reply[0] & 0x40) {
pcharge = (req->reply[6] << 8) + req->reply[7];
if (pcharge > 6500)
pcharge = 6500;
pcharge *= 100;
pcharge = (1000 - pcharge / 650) / 10;
if (pcharge < charge)
charge = pcharge;
}
capa = charge;
max = 100;
time = (charge * 16440) / current;
current = -current;
} else
capa = max = current = voltage = time = 0;
if (req->reply[0] & 0x02)
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].current = current;
pmu_batteries[pmu_cur_battery].voltage = voltage;
pmu_batteries[pmu_cur_battery].time_remaining = time;
}
#else /* NEW_OHARE_CODE */
static void __pmac
done_battery_state_ohare(struct adb_request* req)
{
unsigned int bat_flags = 0;
int current = 0;
unsigned int capa, max, voltage, time;
int lrange[] = { 0, 275, 850, 1680, 2325,
2765, 3160, 3500, 3830, 4115,
4360, 4585, 4795, 4990, 5170,
5340, 5510, 5710, 5930, 6150,
6370, 6500
};
if (req->reply[0] & 0x01)
pmu_power_flags |= PMU_PWR_AC_PRESENT;
else
pmu_power_flags &= ~PMU_PWR_AC_PRESENT;
if (req->reply[0] & 0x04) {
int vb, i, j, charge, pcharge;
bat_flags |= PMU_BATT_PRESENT;
vb = (req->reply[1] << 8) | req->reply[2];
voltage = ((vb * 2650) + 726650)/100;
current = *((signed char *)&req->reply[5]);
if ((req->reply[0] & 0x01) == 0 && (current > 200))
vb += (current - 200) * 15;
else if (req->reply[0] & 0x02)
vb = (vb - 10) * 100;
i = (33000 - vb) / 10;
j = i - (i % 100);
if (j <= 0)
charge = 0;
else if (j >= 21)
charge = 650000;
else
charge = (lrange[j + 1] - lrange[j]) * (i - j) + (lrange[j] * 100);
charge = (1000 - charge / 650) / 10;
if (req->reply[0] & 0x40) {
pcharge = (req->reply[6] << 8) + req->reply[7];
if (pcharge > 6500)
pcharge = 6500;
pcharge *= 100;
pcharge = (1000 - pcharge / 650) / 10;
if (pcharge < charge)
charge = pcharge;
}
capa = charge;
max = 100;
time = (charge * 274) / current;
current = -current;
} else
capa = max = current = voltage = time = 0;
if ((req->reply[0] & 0x02) && (current > 0))
bat_flags |= PMU_BATT_CHARGING;
if (req->reply[0] & 0x04) /* CHECK THIS ONE */
bat_flags |= PMU_BATT_PRESENT;
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].current = current;
pmu_batteries[pmu_cur_battery].voltage = voltage;
pmu_batteries[pmu_cur_battery].time_remaining = time;
}
#endif /* NEW_OHARE_CODE */
static void __pmac
done_battery_state_comet(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 = 0;
int current = 0;
unsigned int max = 100;
unsigned int charge, voltage, time;
int lrange[] = { 0, 600, 750, 900, 1000, 1080,
1180, 1250, 1300, 1340, 1360,
1390, 1420, 1440, 1470, 1490,
1520, 1550, 1580, 1610, 1650,
1700
};
if (req->reply[0] & 0x01)
pmu_power_flags |= PMU_PWR_AC_PRESENT;
else
pmu_power_flags &= ~PMU_PWR_AC_PRESENT;
if (req->reply[0] & 0x04) { /* battery exist */
int vb, i;
bat_flags |= PMU_BATT_PRESENT;
vb = (req->reply[1] << 8) | req->reply[2];
voltage = ((vb * 2650) + 726650)/100;
vb *= 10;
current = req->reply[5];
if ((req->reply[0] & 0x01) == 0 && (current > 200))
vb += ((current - 200) * 3); /* vb = 500<->1800 */
else if (req->reply[0] & 0x02)
vb = ((vb - 800) * 1700/13)/100; /* in charging vb = 1300<->2130 */
if (req->reply[0] & 0x20) { /* full charged */
charge = max;
} else {
if (lrange[21] < vb)
charge = max;
else {
if (vb < lrange[1])
charge = 0;
else {
for (i=21; vb < lrange[i]; --i);
charge = (i * 100)/21;
}
}
if (charge > max) charge = max;
}
time = (charge * 72);
current = -current;
} else
max = current = voltage = time = 0;
if (req->reply[0] & 0x02)
bat_flags |= PMU_BATT_CHARGING;
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].current = current;
pmu_batteries[pmu_cur_battery].voltage = voltage;
pmu_batteries[pmu_cur_battery].time_remaining = time;
}
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 = 0;
int current;
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;
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];
current = *((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];
current = *((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;
}
} else
capa = max = current = voltage = 0;
if ((req->reply[1] & 0x01) && (current > 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].current = current;
pmu_batteries[pmu_cur_battery].voltage = voltage;
if (current) {
if ((req->reply[1] & 0x01) && (current > 0))
pmu_batteries[pmu_cur_battery].time_remaining
= ((max-capa) * 3600) / current;
else
pmu_batteries[pmu_cur_battery].time_remaining
= (capa * 3600) / (-current);
} else
pmu_batteries[pmu_cur_battery].time_remaining = 0;
pmu_cur_battery = (pmu_cur_battery + 1) % pmu_battery_count;
}
static void __pmac
query_battery_state(void)
{
if (!batt_req.complete)
return;
if (pmu_kind == PMU_OHARE_BASED) {
int mb = pmac_call_feature(PMAC_FTR_GET_MB_INFO,
NULL, PMAC_MB_INFO_MODEL, 0);
if (mb == PMAC_TYPE_COMET)
pmu_request(&batt_req, done_battery_state_comet,
1, PMU_BATTERY_STATE);
else
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
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;
}
#ifdef CONFIG_PMAC_PBOOK
static int
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].current);
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
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 && can_sleep)
p += sprintf(p, "lid_wakeup=%d\n", option_lid_wakeup);
#endif /* CONFIG_PMAC_PBOOK */
return p - page;
}
static int
proc_write_options(struct file *file, const char *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 && can_sleep)
if (!strcmp(label, "lid_wakeup"))
option_lid_wakeup = ((*val) == '1');
#endif /* CONFIG_PMAC_PBOOK */
return fcount;
}
#ifdef CONFIG_ADB
/* Send an ADB command */
static int __openfirmware
......@@ -622,11 +1133,7 @@ pmu_request(struct adb_request *req, void (*done)(struct adb_request *),
for (i = 0; i < nbytes; ++i)
req->data[i] = va_arg(list, int);
va_end(list);
if (pmu_data_len[req->data[0]][1] != 0) {
req->reply[0] = ADB_RET_OK;
req->reply_len = 1;
} else
req->reply_len = 0;
req->reply_len = 0;
req->reply_expected = 0;
return pmu_queue_request(req);
}
......@@ -834,28 +1341,23 @@ via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs)
/* This is a bit brutal, we can probably do better */
spin_lock_irqsave(&pmu_lock, flags);
++disable_poll;
while ((intr = in_8(&via[IFR])) != 0) {
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 pmu_state=%d\n", intr, pmu_state);
"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);
else if (intr & CB1_INT)
if (intr & CB1_INT)
adb_int_pending = 1;
}
/* This is not necessary except if synchronous ADB requests are done
* with interrupts off, which should not happen. Since I'm not sure
* this "wiring" will remain, I'm commenting it out for now. Please do
* not remove. -- BenH.
*/
#if 0
if (gpio_reg && !pmu_suspended && (in_8(gpio_reg + 0x9) & 0x02) == 0)
adb_int_pending = 1;
#endif
if (pmu_state == idle) {
if (adb_int_pending) {
......@@ -866,9 +1368,8 @@ via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs)
wait_for_ack();
send_byte(PMU_INT_ACK);
adb_int_pending = 0;
} else if (current_req) {
} else if (current_req)
pmu_start();
}
}
--disable_poll;
......@@ -878,8 +1379,10 @@ via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs)
static void __openfirmware
gpio1_interrupt(int irq, void *arg, struct pt_regs *regs)
{
adb_int_pending = 1;
via_pmu_interrupt(0, 0, 0);
if ((in_8(gpio_reg + 0x9) & 0x02) == 0) {
adb_int_pending = 1;
via_pmu_interrupt(0, 0, 0);
}
}
static void __openfirmware
......@@ -1040,16 +1543,28 @@ pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs)
adb_input(data+1, len-1, regs, 1);
#endif /* CONFIG_ADB */
}
} else if (data[0] == 0x08 && len == 3) {
/* sound/brightness buttons pressed */
#ifdef CONFIG_PMAC_BACKLIGHT
set_backlight_level(data[1] >> 4);
#endif
set_volume(data[2]);
} 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
pmu_pass_intr(data, len);
#endif
/* 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 */
}
}
......@@ -1116,11 +1631,6 @@ pmu_enable_irled(int on)
pmu_poll();
}
static void __openfirmware
set_volume(int level)
{
}
void __openfirmware
pmu_restart(void)
{
......@@ -1266,50 +1776,96 @@ broadcast_wake(void)
* PCI devices which may get powered off when we sleep.
*/
static struct pci_save {
#ifndef HACKED_PCI_SAVE
u16 command;
u16 cache_lat;
u16 intr;
u32 rom_address;
#else
u32 config[16];
#endif
} *pbook_pci_saves;
static int n_pbook_pci_saves;
static int pbook_npci_saves;
static void __openfirmware
pbook_pci_save(void)
pbook_alloc_pci_save(void)
{
int npci;
struct pci_dev *pd;
struct pci_save *ps;
npci = 0;
pci_for_each_dev(pd) {
++npci;
}
n_pbook_pci_saves = npci;
if (npci == 0)
return;
ps = (struct pci_save *) kmalloc(npci * sizeof(*ps), GFP_KERNEL);
pbook_pci_saves = ps;
pbook_pci_saves = (struct pci_save *)
kmalloc(npci * sizeof(struct pci_save), GFP_KERNEL);
pbook_npci_saves = npci;
}
static void __openfirmware
pbook_free_pci_save(void)
{
if (pbook_pci_saves == NULL)
return;
kfree(pbook_pci_saves);
pbook_pci_saves = NULL;
pbook_npci_saves = 0;
}
static void __openfirmware
pbook_pci_save(void)
{
struct pci_save *ps = pbook_pci_saves;
struct pci_dev *pd;
int npci = pbook_npci_saves;
if (ps == NULL)
return;
pci_for_each_dev(pd) {
if (npci-- == 0)
return;
#ifndef HACKED_PCI_SAVE
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);
#else
int i;
for (i=1;i<16;i++)
pci_read_config_dword(pd, i<<4, &ps->config[i]);
#endif
++ps;
}
}
/* For this to work, we must take care of a few things: If gmac was enabled
* during boot, it will be in the pci dev list. If it's disabled at this point
* (and it will probably be), then you can't access it's config space.
*/
static void __openfirmware
pbook_pci_restore(void)
{
u16 cmd;
struct pci_save *ps = pbook_pci_saves - 1;
struct pci_dev *pd;
int npci = pbook_npci_saves;
int j;
pci_for_each_dev(pd) {
#ifdef HACKED_PCI_SAVE
int i;
if (npci-- == 0)
return;
ps++;
for (i=2;i<16;i++)
pci_write_config_dword(pd, i<<4, ps->config[i]);
pci_write_config_dword(pd, 4, ps->config[1]);
#else
if (npci-- == 0)
return;
ps++;
if (ps->command == 0)
continue;
......@@ -1330,8 +1886,8 @@ pbook_pci_restore(void)
ps->intr);
pci_write_config_word(pd, PCI_COMMAND, ps->command);
break;
/* other header types not restored at present */
}
#endif
}
}
......@@ -1370,7 +1926,7 @@ pmu_blink(int n)
}
mdelay(50);
}
#endif /* DEBUG_SLEEP */
#endif
/*
* Put the powerbook to sleep.
......@@ -1403,6 +1959,17 @@ static void restore_via_state(void)
out_8(&via[IER], IER_SET | SR_INT | CB1_INT);
}
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();
}
extern int sys_sync(void);
#define GRACKLE_PM (1<<7)
#define GRACKLE_DOZE (1<<5)
#define GRACKLE_NAP (1<<4)
......@@ -1411,7 +1978,6 @@ static void restore_via_state(void)
int __openfirmware powerbook_sleep_G3(void)
{
unsigned long save_l2cr;
unsigned long wait;
unsigned short pmcr1;
struct adb_request req;
int ret, timeout;
......@@ -1443,13 +2009,9 @@ int __openfirmware powerbook_sleep_G3(void)
return -EBUSY;
}
/* Give the disks a little time to actually finish writing */
for (wait = jiffies + (HZ/2); time_before(jiffies, wait); )
mb();
/* Wait for completion of async backlight requests */
while (!bright_req_1.complete || !bright_req_2.complete ||
!bright_req_3.complete)
!bright_req_3.complete || !batt_req.complete)
pmu_poll();
/* Turn off various things. Darwin does some retry tests here... */
......@@ -1495,7 +2057,7 @@ int __openfirmware powerbook_sleep_G3(void)
/* The VIA is supposed not to be restored correctly*/
save_via_state();
/* We shut down some HW */
feature_prepare_for_sleep();
pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1);
pci_read_config_word(grackle, 0x70, &pmcr1);
/* Apparently, MacOS uses NAP mode for Grackle ??? */
......@@ -1512,7 +2074,7 @@ int __openfirmware powerbook_sleep_G3(void)
pci_write_config_word(grackle, 0x70, pmcr1);
/* Make sure the PMU is idle */
feature_wake_up();
pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0);
restore_via_state();
/* Restore L2 cache */
......@@ -1522,11 +2084,6 @@ int __openfirmware powerbook_sleep_G3(void)
/* Restore userland MMU context */
set_context(current->active_mm->context, current->active_mm->pgd);
/* Re-enable DEC interrupts and kick DEC */
asm volatile("mtdec %0" : : "r" (0x7fffffff));
sti();
asm volatile("mtdec %0" : : "r" (0x10000000));
/* Power things up */
pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0xfc);
while (!req.complete)
......@@ -1558,6 +2115,10 @@ int __openfirmware powerbook_sleep_G3(void)
/* Leave some time for HW to settle down */
mdelay(100);
/* Restart jiffies & scheduling */
wakeup_decrementer();
sti();
/* Notify drivers */
broadcast_wake();
......@@ -1567,11 +2128,10 @@ int __openfirmware powerbook_sleep_G3(void)
int __openfirmware powerbook_sleep_Core99(void)
{
unsigned long save_l2cr;
unsigned long wait;
struct adb_request req;
int ret, timeout;
if (!feature_can_sleep()) {
if (!can_sleep) {
printk(KERN_ERR "Sleep mode not supported on this machine\n");
return -ENOSYS;
}
......@@ -1597,14 +2157,9 @@ int __openfirmware powerbook_sleep_Core99(void)
printk("pmu: sleep failed\n");
return -EBUSY;
}
/* Give the disks a little time to actually finish writing */
for (wait = jiffies + HZ; time_before(jiffies, wait); )
mb();
/* Wait for completion of async backlight requests */
while (!bright_req_1.complete || !bright_req_2.complete ||
!bright_req_3.complete)
!bright_req_3.complete || !batt_req.complete)
pmu_poll();
/* Tell PMU what events will wake us up */
......@@ -1614,7 +2169,8 @@ int __openfirmware powerbook_sleep_Core99(void)
pmu_poll();
pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, PMU_PWR_SET_WAKEUP_EVENTS,
0, PMU_PWR_WAKEUP_KEY | PMU_PWR_WAKEUP_LID_OPEN);
0, PMU_PWR_WAKEUP_KEY |
(option_lid_wakeup ? PMU_PWR_WAKEUP_LID_OPEN : 0));
while (!req.complete)
pmu_poll();
......@@ -1651,10 +2207,12 @@ int __openfirmware powerbook_sleep_Core99(void)
/* Save the state of PCI config space for some slots */
//pbook_pci_save();
/* Ask the PMU to put us to sleep */
pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
while (!req.complete && pmu_state != idle)
pmu_poll();
if (!__fake_sleep) {
/* Ask the PMU to put us to sleep */
pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
while (!req.complete && pmu_state != idle)
pmu_poll();
}
out_8(&via[B], in_8(&via[B]) | TREQ);
wait_for_ack();
......@@ -1666,13 +2224,16 @@ int __openfirmware powerbook_sleep_Core99(void)
* talk to the PMU after this, so I moved it to _after_ sending the
* sleep command to it. Still need to be checked.
*/
feature_prepare_for_sleep();
pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1);
/* Call low-level ASM sleep handler */
low_sleep_handler();
if (__fake_sleep)
mdelay(5000);
else
low_sleep_handler();
/* Restore Apple core ASICs state */
feature_wake_up();
pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0);
/* Restore VIA */
restore_via_state();
......@@ -1694,11 +2255,6 @@ int __openfirmware powerbook_sleep_Core99(void)
/* Restore userland MMU context */
set_context(current->active_mm->context, current->active_mm->pgd);
/* Re-enable DEC interrupts and kick DEC */
asm volatile("mtdec %0" : : "r" (0x7fffffff));
sti();
asm volatile("mtdec %0" : : "r" (0x10000000));
/* Tell PMU we are ready */
pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2);
while (!req.complete)
......@@ -1725,6 +2281,10 @@ int __openfirmware powerbook_sleep_Core99(void)
/* Leave some time for HW to settle down */
mdelay(100);
/* Restart jiffies & scheduling */
wakeup_decrementer();
sti();
/* Notify drivers */
broadcast_wake();
......@@ -1738,7 +2298,7 @@ int __openfirmware powerbook_sleep_3400(void)
{
int ret, i, x;
unsigned int hid0;
unsigned long p, wait;
unsigned long p;
struct adb_request sleep_req;
char *mem_ctrl;
unsigned int *mem_ctrl_sleep;
......@@ -1751,9 +2311,13 @@ int __openfirmware powerbook_sleep_3400(void)
}
mem_ctrl_sleep = (unsigned int *) (mem_ctrl + PB3400_MEM_CTRL_SLEEP);
/* Allocate room for PCI save */
pbook_alloc_pci_save();
/* Notify device drivers */
ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, PBOOK_SLEEP_REJECT);
if (ret != PBOOK_SLEEP_OK) {
pbook_free_pci_save();
printk("pmu: sleep rejected\n");
return -EBUSY;
}
......@@ -1770,16 +2334,13 @@ int __openfirmware powerbook_sleep_3400(void)
ret = broadcast_sleep(PBOOK_SLEEP_NOW, PBOOK_WAKE);
if (ret != PBOOK_SLEEP_OK) {
printk("pmu: sleep failed\n");
pbook_free_pci_save();
return -EBUSY;
}
/* Give the disks a little time to actually finish writing */
for (wait = jiffies + (HZ/4); time_before(jiffies, wait); )
mb();
/* Wait for completion of async backlight requests */
while (!bright_req_1.complete || !bright_req_2.complete ||
!bright_req_3.complete)
!bright_req_3.complete || !batt_req.complete)
pmu_poll();
/* Disable all interrupts except pmu */
......@@ -1811,6 +2372,8 @@ int __openfirmware powerbook_sleep_3400(void)
while (!sleep_req.complete)
mb();
pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1);
/* displacement-flush the L2 cache - necessary? */
for (p = KERNELBASE; p < KERNELBASE + 0x100000; p += 0x1000)
i = *(volatile int *)p;
......@@ -1825,23 +2388,27 @@ int __openfirmware powerbook_sleep_3400(void)
/* OK, we're awake again, start restoring things */
out_be32(mem_ctrl_sleep, 0x3f);
pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0);
pbook_pci_restore();
/* wait for the PMU interrupt sequence to complete */
while (asleep)
mb();
/* Re-enable DEC interrupts and kick DEC */
asm volatile("mtdec %0" : : "r" (0x7fffffff));
sti();
asm volatile("mtdec %0" : : "r" (0x10000000));
/* reenable interrupts */
pmac_sleep_restore_intrs();
/* Leave some time for HW to settle down */
mdelay(100);
/* Restart jiffies & scheduling */
wakeup_decrementer();
sti();
/* Notify drivers */
broadcast_wake();
pbook_free_pci_save();
iounmap(mem_ctrl);
return 0;
}
......@@ -1860,6 +2427,9 @@ struct pmu_private {
} rb_buf[RB_SIZE];
wait_queue_head_t wait;
spinlock_t lock;
#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT)
int backlight_locker;
#endif /* defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) */
};
static LIST_HEAD(all_pmu_pvt);
......@@ -1877,6 +2447,7 @@ static void pmu_pass_intr(unsigned char *data, int len)
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);
spin_lock(&pp->lock);
i = pp->rb_put + 1;
if (i >= RB_SIZE)
i = 0;
......@@ -1887,6 +2458,7 @@ static void pmu_pass_intr(unsigned char *data, int len)
pp->rb_put = i;
wake_up_interruptible(&pp->wait);
}
spin_unlock(&pp->lock);
}
spin_unlock_irqrestore(&all_pvt_lock, flags);
}
......@@ -1903,6 +2475,9 @@ static int __openfirmware pmu_open(struct inode *inode, struct file *file)
spin_lock_init(&pp->lock);
init_waitqueue_head(&pp->wait);
spin_lock_irqsave(&all_pvt_lock, flags);
#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT)
pp->backlight_locker = 0;
#endif /* defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) */
list_add(&pp->list, &all_pmu_pvt);
spin_unlock_irqrestore(&all_pvt_lock, flags);
file->private_data = pp;
......@@ -1914,6 +2489,7 @@ static ssize_t __openfirmware pmu_read(struct file *file, char *buf,
{
struct pmu_private *pp = file->private_data;
DECLARE_WAITQUEUE(wait, current);
unsigned long flags;
int ret;
if (count < 1 || pp == 0)
......@@ -1922,38 +2498,41 @@ static ssize_t __openfirmware pmu_read(struct file *file, char *buf,
if (ret)
return ret;
spin_lock_irqsave(&pp->lock, flags);
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;
spin_unlock_irqrestore(&pp->lock, flags);
if (ret > count)
ret = count;
if (ret > 0 && copy_to_user(buf, rp->data, ret))
ret = -EFAULT;
if (++i >= RB_SIZE)
i = 0;
spin_lock_irqsave(&pp->lock, flags);
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;
spin_unlock_irqrestore(&pp->lock, flags);
schedule();
spin_lock_irqsave(&pp->lock, flags);
}
current->state = TASK_RUNNING;
remove_wait_queue(&pp->wait, &wait);
spin_unlock_irqrestore(&pp->lock, flags);
return ret;
}
......@@ -1967,14 +2546,15 @@ static unsigned int pmu_fpoll(struct file *filp, poll_table *wait)
{
struct pmu_private *pp = filp->private_data;
unsigned int mask = 0;
unsigned long flags;
if (pp == 0)
return 0;
poll_wait(filp, &pp->wait, wait);
spin_lock(&pp->lock);
spin_lock_irqsave(&pp->lock, flags);
if (pp->rb_get != pp->rb_put)
mask |= POLLIN;
spin_unlock(&pp->lock);
spin_unlock_irqrestore(&pp->lock, flags);
return mask;
}
......@@ -1983,13 +2563,22 @@ static int pmu_release(struct inode *inode, struct file *file)
struct pmu_private *pp = file->private_data;
unsigned long flags;
lock_kernel();
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);
#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT)
if (pp->backlight_locker) {
spin_lock_irqsave(&pmu_lock, flags);
disable_kernel_backlight--;
spin_unlock_irqrestore(&pmu_lock, flags);
}
#endif defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT)
kfree(pp);
}
unlock_kernel();
return 0;
}
......@@ -1997,6 +2586,7 @@ static int pmu_release(struct inode *inode, struct file *file)
static int pmu_ioctl(struct inode * inode, struct file *filp,
u_int cmd, u_long arg)
{
struct pmu_private *pp = filp->private_data;
int error;
switch (cmd) {
......@@ -2023,7 +2613,7 @@ static int pmu_ioctl(struct inode * inode, struct file *filp,
sleep_in_progress = 0;
return error;
case PMU_IOC_CAN_SLEEP:
return put_user(feature_can_sleep(), (__u32 *)arg);
return put_user((u32)can_sleep, (__u32 *)arg);
#ifdef CONFIG_PMAC_BACKLIGHT
/* Backlight should have its own device or go via
......@@ -2046,6 +2636,18 @@ static int pmu_ioctl(struct inode * inode, struct file *filp,
error = set_backlight_level(value);
return error;
}
#ifdef CONFIG_INPUT_ADBHID
case PMU_IOC_GRAB_BACKLIGHT: {
unsigned long flags;
if (pp->backlight_locker)
return 0;
pp->backlight_locker = 1;
spin_lock_irqsave(&pmu_lock, flags);
disable_kernel_backlight++;
spin_unlock_irqrestore(&pmu_lock, flags);
return 0;
}
#endif /* CONFIG_INPUT_ADBHID */
#endif /* CONFIG_PMAC_BACKLIGHT */
case PMU_IOC_GET_MODEL:
return put_user(pmu_kind, (__u32 *)arg);
......@@ -2153,5 +2755,8 @@ EXPORT_SYMBOL(pmu_resume);
EXPORT_SYMBOL(pmu_register_sleep_notifier);
EXPORT_SYMBOL(pmu_unregister_sleep_notifier);
EXPORT_SYMBOL(pmu_enable_irled);
EXPORT_SYMBOL(pmu_battery_count);
EXPORT_SYMBOL(pmu_batteries);
EXPORT_SYMBOL(pmu_power_flags);
#endif /* CONFIG_PMAC_PBOOK */
......@@ -2079,7 +2079,6 @@ static int init_planb(struct planb *pb)
#endif
pb->tab_size = PLANB_MAXLINES + 40;
pb->suspend = 0;
pb->lock = 0;
init_MUTEX(&pb->lock);
pb->ch1_cmd = 0;
pb->ch2_cmd = 0;
......
......@@ -6,8 +6,16 @@
*
* Paul Mackerras, August 1996.
* Copyright (C) 1996 Paul Mackerras.
*
* Apr. 21 2002 - BenH Rework bus reset code for new error handler
* Add delay after initial bus reset
* Add module parameters
* To do:
* - handle aborts correctly
* - retry arbitration if lost (unless higher levels do this for us)
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/types.h>
......@@ -28,7 +36,9 @@
#include <asm/irq.h>
#include <asm/hydra.h>
#include <asm/processor.h>
#include <asm/feature.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#include <asm/pci-bridge.h>
#ifdef CONFIG_PMAC_PBOOK
#include <linux/adb.h>
#include <linux/pmu.h>
......@@ -38,35 +48,39 @@
#include "hosts.h"
#include "mesh.h"
/*
* To do:
* - handle aborts correctly
* - retry arbitration if lost (unless higher levels do this for us)
*/
#define MESH_NEW_STYLE_EH
#if 1
#undef KERN_DEBUG
#define KERN_DEBUG KERN_WARNING
#endif
#if CONFIG_SCSI_MESH_SYNC_RATE == 0
int mesh_sync_period = 100;
int mesh_sync_offset = 0;
#else
int mesh_sync_period = 1000 / CONFIG_SCSI_MESH_SYNC_RATE; /* ns */
int mesh_sync_offset = 15;
#endif
int mesh_sync_targets = 0xff; /* targets to set synchronous (bitmap) */
int mesh_resel_targets = 0xff; /* targets that we let disconnect (bitmap) */
int mesh_debug_targets = 0; /* print debug for these targets */
unsigned char use_active_neg = 0; /* bit mask for SEQ_ACTIVE_NEG if used */
#define ALLOW_SYNC(tgt) ((mesh_sync_targets >> (tgt)) & 1)
#define ALLOW_RESEL(tgt) ((mesh_resel_targets >> (tgt)) & 1)
#define ALLOW_DEBUG(tgt) ((mesh_debug_targets >> (tgt)) & 1)
MODULE_AUTHOR("Paul Mackerras (paulus@samba.org)");
MODULE_DESCRIPTION("PowerMac MESH SCSI driver");
MODULE_LICENSE("GPL");
MODULE_PARM(sync_rate, "i");
MODULE_PARM_DESC(sync_rate, "Synchronous rate (0..10, 0=async)");
MODULE_PARM(sync_targets, "i");
MODULE_PARM_DESC(sync_targets, "Bitmask of targets allowed to set synchronous");
MODULE_PARM(resel_targets, "i");
MODULE_PARM_DESC(resel_targets, "Bitmask of targets allowed to set disconnect");
MODULE_PARM(debug_targets, "i");
MODULE_PARM_DESC(debug_targets, "Bitmask of debugged targets");
MODULE_PARM(init_reset_delay, "i");
MODULE_PARM_DESC(init_reset_delay, "Initial bus reset delay (0=no reset)");
static int sync_rate = CONFIG_SCSI_MESH_SYNC_RATE;
static int sync_targets = 0xff;
static int resel_targets = 0xff;
static int debug_targets = 0; /* print debug for these targets */
static int init_reset_delay = CONFIG_SCSI_MESH_RESET_DELAY_MS;
static int mesh_sync_period = 100;
static int mesh_sync_offset = 0;
static unsigned char use_active_neg = 0; /* bit mask for SEQ_ACTIVE_NEG if used */
#define ALLOW_SYNC(tgt) ((sync_targets >> (tgt)) & 1)
#define ALLOW_RESEL(tgt) ((resel_targets >> (tgt)) & 1)
#define ALLOW_DEBUG(tgt) ((debug_targets >> (tgt)) & 1)
#define DEBUG_TARGET(cmd) ((cmd) && ALLOW_DEBUG((cmd)->target))
#undef MESH_DBG
......@@ -94,7 +108,8 @@ enum mesh_phase {
statusing,
busfreeing,
disconnecting,
reselecting
reselecting,
sleeping
};
enum msg_phase {
......@@ -118,7 +133,6 @@ struct mesh_target {
int data_goes_out; /* guess as to data direction */
Scsi_Cmnd *current_req;
u32 saved_ptr;
int want_abort;
#ifdef MESH_DBG
int log_ix;
int n_log;
......@@ -155,12 +169,7 @@ struct mesh_state {
struct mesh_target tgts[8];
void *dma_cmd_space;
struct device_node *ofnode;
u8* mio_base;
#ifndef MESH_NEW_STYLE_EH
Scsi_Cmnd *completed_q;
Scsi_Cmnd *completed_qtail;
struct tq_struct tqueue;
#endif
struct pci_dev* pdev;
#ifdef MESH_DBG
int log_ix;
int n_log;
......@@ -192,9 +201,6 @@ static int mesh_notify_reboot(struct notifier_block *, unsigned long, void *);
static void mesh_dump_regs(struct mesh_state *);
static void mesh_start(struct mesh_state *);
static void mesh_start_cmd(struct mesh_state *, Scsi_Cmnd *);
#ifndef MESH_NEW_STYLE_EH
static void finish_cmds(void *);
#endif
static void add_sdtr_msg(struct mesh_state *);
static void set_sdtr(struct mesh_state *, int, int);
static void start_phase(struct mesh_state *);
......@@ -247,6 +253,16 @@ mesh_detect(Scsi_Host_Template *tp)
use_active_neg = SEQ_ACTIVE_NEG;
}
/* Calculate sync rate from module parameters */
if (sync_rate > 10)
sync_rate = 10;
if (sync_rate > 0) {
printk(KERN_INFO "mesh: configured for synchronous %d MB/s\n", sync_rate);
mesh_sync_period = 1000 / sync_rate; /* ns */
mesh_sync_offset = 15;
} else
printk(KERN_INFO "mesh: configured for asynchronous\n");
nmeshes = 0;
prev_statep = &all_meshes;
/*
......@@ -258,13 +274,23 @@ mesh_detect(Scsi_Host_Template *tp)
if (mesh == 0)
mesh = find_compatible_devices("scsi", "chrp,mesh0");
for (; mesh != 0; mesh = mesh->next) {
struct device_node *mio;
u8 pci_bus, pci_devfn;
struct pci_dev* pdev = NULL;
if (mesh->n_addrs != 2 || mesh->n_intrs != 2) {
printk(KERN_ERR "mesh: expected 2 addrs and 2 intrs"
" (got %d,%d)", mesh->n_addrs, mesh->n_intrs);
" (got %d,%d)\n", mesh->n_addrs, mesh->n_intrs);
continue;
}
if (mesh->parent != NULL
&& pci_device_from_OF_node(mesh->parent, &pci_bus,
&pci_devfn) == 0)
pdev = pci_find_slot(pci_bus, pci_devfn);
if (pdev == NULL) {
printk(KERN_ERR "mesh: Can't locate PCI entry\n");
continue;
}
mesh_host = scsi_register(tp, sizeof(struct mesh_state));
if (mesh_host == 0) {
printk(KERN_ERR "mesh: couldn't register host");
......@@ -281,6 +307,7 @@ mesh_detect(Scsi_Host_Template *tp)
memset(ms, 0, sizeof(*ms));
ms->host = mesh_host;
ms->ofnode = mesh;
ms->pdev = pdev;
ms->mesh = (volatile struct mesh_regs *)
ioremap(mesh->addrs[0].address, 0x1000);
ms->dma = (volatile struct dbdma_regs *)
......@@ -305,10 +332,6 @@ mesh_detect(Scsi_Host_Template *tp)
ms->tgts[tgt].sync_params = ASYNC_PARAMS;
ms->tgts[tgt].current_req = 0;
}
#ifndef MESH_NEW_STYLE_EH
ms->tqueue.routine = finish_cmds;
ms->tqueue.data = ms;
#endif
*prev_statep = ms;
prev_statep = &ms->next;
......@@ -325,12 +348,6 @@ mesh_detect(Scsi_Host_Template *tp)
if (mesh_sync_period < minper)
mesh_sync_period = minper;
ms->mio_base = 0;
for (mio = ms->ofnode->parent; mio; mio = mio->parent)
if (strcmp(mio->name, "mac-io") == 0 && mio->n_addrs > 0)
break;
if (mio)
ms->mio_base = (u8 *) ioremap(mio->addrs[0].address, 0x40);
set_mesh_power(ms, 1);
mesh_init(ms);
......@@ -363,11 +380,9 @@ mesh_release(struct Scsi_Host *host)
iounmap((void *) ms->mesh);
if (ms->dma)
iounmap((void *) ms->dma);
if (ms->mio_base)
iounmap((void *) ms->mio_base);
kfree(ms->dma_cmd_space);
free_irq(ms->meshintr, ms);
feature_clear(ms->ofnode, FEATURE_MESH_enable);
pmac_call_feature(PMAC_FTR_MESH_ENABLE, ms->ofnode, 0, 0);
return 0;
}
......@@ -377,16 +392,10 @@ set_mesh_power(struct mesh_state *ms, int state)
if (_machine != _MACH_Pmac)
return;
if (state) {
feature_set(ms->ofnode, FEATURE_MESH_enable);
/* This seems to enable the termination power. strangely
this doesn't fully agree with OF, but with MacOS */
if (ms->mio_base)
out_8(ms->mio_base + 0x36, 0x70);
pmac_call_feature(PMAC_FTR_MESH_ENABLE, ms->ofnode, 0, 1);
mdelay(200);
} else {
feature_clear(ms->ofnode, FEATURE_MESH_enable);
if (ms->mio_base)
out_8(ms->mio_base + 0x36, 0x34);
pmac_call_feature(PMAC_FTR_MESH_ENABLE, ms->ofnode, 0, 0);
mdelay(10);
}
}
......@@ -411,15 +420,33 @@ mesh_notify_sleep(struct pmu_sleep_notifier *self, int when)
case PBOOK_SLEEP_NOW:
for (ms = all_meshes; ms != 0; ms = ms->next) {
unsigned long flags;
scsi_block_requests(ms->host);
spin_lock_irqsave(ms->host->host_lock, flags);
while(ms->phase != idle) {
spin_unlock_irqrestore(ms->host->host_lock, flags);
current->state = TASK_UNINTERRUPTIBLE;
schedule_timeout(1);
spin_lock_irqsave(ms->host->host_lock, flags);
}
ms->phase = sleeping;
spin_unlock_irqrestore(ms->host->host_lock, flags);
disable_irq(ms->meshintr);
set_mesh_power(ms, 0);
}
break;
case PBOOK_WAKE:
for (ms = all_meshes; ms != 0; ms = ms->next) {
unsigned long flags;
set_mesh_power(ms, 1);
mesh_init(ms);
spin_lock_irqsave(ms->host->host_lock, flags);
mesh_start(ms);
spin_unlock_irqrestore(ms->host->host_lock, flags);
enable_irq(ms->meshintr);
scsi_unblock_requests(ms->host);
}
break;
}
......@@ -427,10 +454,13 @@ mesh_notify_sleep(struct pmu_sleep_notifier *self, int when)
}
#endif /* CONFIG_PMAC_PBOOK */
/*
* Called by midlayer with host locked to queue a new
* request
*/
int
mesh_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *))
{
unsigned long flags;
struct mesh_state *ms;
cmd->scsi_done = done;
......@@ -438,8 +468,6 @@ mesh_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *))
ms = (struct mesh_state *) cmd->host->hostdata;
save_flags(flags);
cli();
if (ms->request_q == NULL)
ms->request_q = cmd;
else
......@@ -449,19 +477,25 @@ mesh_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *))
if (ms->phase == idle)
mesh_start(ms);
restore_flags(flags);
return 0;
}
/* Todo: here we can at least try to remove the command from the
* queue if it isn't connected yet, and for pending command, assert
* ATN until the bus gets freed.
*/
int
mesh_abort(Scsi_Cmnd *cmd)
{
struct mesh_state *ms = (struct mesh_state *) cmd->host->hostdata;
unsigned long flags;
printk(KERN_DEBUG "mesh_abort(%p)\n", cmd);
spin_lock_irqsave(ms->host->host_lock, flags);
mesh_dump_regs(ms);
dumplog(ms, cmd->target);
dumpslog(ms);
spin_unlock_irqrestore(ms->host->host_lock, flags);
return SCSI_ABORT_SNOOZE;
}
......@@ -498,49 +532,41 @@ mesh_dump_regs(struct mesh_state *ms)
}
}
/*
* Called by the midlayer with the lock held to reset the
* SCSI host and bus.
* The midlayer will wait for devices to come back, we don't need
* to do that ourselves
*/
int
mesh_reset(Scsi_Cmnd *cmd, unsigned how)
mesh_host_reset(Scsi_Cmnd *cmd)
{
struct mesh_state *ms = (struct mesh_state *) cmd->host->hostdata;
volatile struct mesh_regs *mr = ms->mesh;
volatile struct dbdma_regs *md = ms->dma;
unsigned long flags;
int ret;
printk(KERN_DEBUG "mesh_reset %x\n", how);
ret = SCSI_RESET_BUS_RESET;
save_flags(flags);
cli();
printk(KERN_DEBUG "mesh_host_reset\n");
/* Reset the controller & dbdma channel */
out_le32(&md->control, (RUN|PAUSE|FLUSH|WAKE) << 16); /* stop dma */
out_8(&mr->exception, 0xff); /* clear all exception bits */
out_8(&mr->error, 0xff); /* clear all error bits */
if (how & SCSI_RESET_SUGGEST_HOST_RESET) {
out_8(&mr->sequence, SEQ_RESETMESH);
ret |= SCSI_RESET_HOST_RESET;
udelay(1);
out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE);
out_8(&mr->source_id, ms->host->this_id);
out_8(&mr->sel_timeout, 25); /* 250ms */
out_8(&mr->sync_params, ASYNC_PARAMS);
}
out_8(&mr->sequence, SEQ_RESETMESH);
udelay(1);
out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE);
out_8(&mr->source_id, ms->host->this_id);
out_8(&mr->sel_timeout, 25); /* 250ms */
out_8(&mr->sync_params, ASYNC_PARAMS);
/* Reset the bus */
out_8(&mr->bus_status1, BS1_RST); /* assert RST */
udelay(30); /* leave it on for >= 25us */
out_8(&mr->bus_status1, 0); /* negate RST */
#ifdef DO_ASYNC_RESET
if (how & SCSI_RESET_ASYNCHRONOUS) {
restore_flags(flags);
ret |= SCSI_RESET_PENDING;
} else
#endif
{
handle_reset(ms);
restore_flags(flags);
#ifndef MESH_NEW_STYLE_EH
finish_cmds(ms);
#endif
ret |= SCSI_RESET_SUCCESS;
}
return ret;
/* Complete pending commands */
handle_reset(ms);
return SUCCESS;
}
/*
......@@ -569,13 +595,10 @@ mesh_notify_reboot(struct notifier_block *this, unsigned long code, void *x)
return NOTIFY_DONE;
}
int
mesh_command(Scsi_Cmnd *cmd)
{
printk(KERN_WARNING "whoops... mesh_command called\n");
return -1;
}
/* Called with meshinterrupt disabled, initialize the chipset
* and eventually do the initial bus reset. The lock must not be
* held since we can schedule.
*/
static void
mesh_init(struct mesh_state *ms)
{
......@@ -584,6 +607,7 @@ mesh_init(struct mesh_state *ms)
udelay(100);
/* Reset controller */
out_le32(&md->control, (RUN|PAUSE|FLUSH|WAKE) << 16); /* stop dma */
out_8(&mr->exception, 0xff); /* clear all exception bits */
out_8(&mr->error, 0xff); /* clear all error bits */
......@@ -594,15 +618,28 @@ mesh_init(struct mesh_state *ms)
out_8(&mr->sel_timeout, 25); /* 250ms */
out_8(&mr->sync_params, ASYNC_PARAMS);
out_8(&mr->bus_status1, BS1_RST); /* assert RST */
udelay(30); /* leave it on for >= 25us */
out_8(&mr->bus_status1, 0); /* negate RST */
if (init_reset_delay) {
printk(KERN_INFO "mesh: performing initial bus reset...\n");
/* Reset bus */
out_8(&mr->bus_status1, BS1_RST); /* assert RST */
udelay(30); /* leave it on for >= 25us */
out_8(&mr->bus_status1, 0); /* negate RST */
/* Wait for bus to come back */
current->state = TASK_UNINTERRUPTIBLE;
schedule_timeout((init_reset_delay * HZ) / 1000);
}
/* Reconfigure controller */
out_8(&mr->interrupt, 0xff); /* clear all interrupt bits */
out_8(&mr->sequence, SEQ_FLUSHFIFO);
udelay(1);
out_8(&mr->sync_params, ASYNC_PARAMS);
out_8(&mr->sequence, SEQ_ENBRESEL);
out_8(&mr->interrupt, 0xff); /* clear all interrupt bits */
ms->phase = idle;
ms->msgphase = msg_none;
}
/*
......@@ -662,6 +699,8 @@ mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd)
cmd->use_sg, cmd->request_buffer, cmd->request_bufflen);
}
#endif
if (ms->dma_started)
panic("mesh: double DMA start !\n");
ms->phase = arbitrating;
ms->msgphase = msg_none;
......@@ -786,28 +825,6 @@ mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd)
}
}
#ifndef MESH_NEW_STYLE_EH
static void
finish_cmds(void *data)
{
struct mesh_state *ms = data;
Scsi_Cmnd *cmd;
unsigned long flags;
for (;;) {
spin_lock_irqsave(ms->host->host_lock, flags);
cmd = ms->completed_q;
if (cmd == NULL) {
spin_unlock_irqrestore(ms->host->host_lock, flags);
break;
}
ms->completed_q = (Scsi_Cmnd *) cmd->host_scribble;
(*cmd->scsi_done)(cmd);
spin_unlock_irqrestore(ms->host->host_lock, flags);
}
}
#endif /* MESH_NEW_STYLE_EH */
static inline void
add_sdtr_msg(struct mesh_state *ms)
{
......@@ -1332,10 +1349,13 @@ reselected(struct mesh_state *ms)
dumpslog(ms);
}
if (ms->dma_started) {
printk(KERN_ERR "mesh: reselected with DMA started !\n");
halt_dma(ms);
}
ms->current_req = NULL;
ms->phase = dataing;
ms->msgphase = msg_in;
ms->dma_started = 0;
ms->n_msgout = 0;
ms->last_n_msgout = 0;
prev = ms->conn_tgt;
......@@ -1458,7 +1478,7 @@ static void
do_mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs)
{
unsigned long flags;
struct Scsi_Host *dev = ((struct mech_state *)dev_id)->host;
struct Scsi_Host *dev = ((struct mesh_state *)dev_id)->host;
spin_lock_irqsave(dev->host_lock, flags);
mesh_interrupt(irq, dev_id, ptregs);
......@@ -1755,18 +1775,7 @@ mesh_done(struct mesh_state *ms, int start_next)
static void
mesh_completed(struct mesh_state *ms, Scsi_Cmnd *cmd)
{
#ifdef MESH_NEW_STYLE_EH
(*cmd->scsi_done)(cmd);
#else
if (ms->completed_q == NULL)
ms->completed_q = cmd;
else
ms->completed_qtail->host_scribble = (void *) cmd;
ms->completed_qtail = cmd;
cmd->host_scribble = NULL;
queue_task(&ms->tqueue, &tq_immediate);
mark_bh(IMMEDIATE_BH);
#endif /* MESH_NEW_STYLE_EH */
}
/*
......@@ -1786,24 +1795,29 @@ set_dma_cmds(struct mesh_state *ms, Scsi_Cmnd *cmd)
if (cmd) {
cmd->SCp.this_residual = cmd->request_bufflen;
if (cmd->use_sg > 0) {
int nseg;
total = 0;
scl = (struct scatterlist *) cmd->buffer;
off = ms->data_ptr;
for (i = 0; i < cmd->use_sg; ++i, ++scl) {
nseg = pci_map_sg(ms->pdev, scl, cmd->use_sg,
scsi_to_pci_dma_dir(cmd ->sc_data_direction));
for (i = 0; i <nseg; ++i, ++scl) {
u32 dma_addr = sg_dma_address(scl);
u32 dma_len = sg_dma_len(scl);
total += scl->length;
if (off >= scl->length) {
off -= scl->length;
if (off >= dma_len) {
off -= dma_len;
continue;
}
if (scl->length > 0xffff)
if (dma_len > 0xffff)
panic("mesh: scatterlist element >= 64k");
st_le16(&dcmds->req_count, scl->length - off);
st_le16(&dcmds->req_count, dma_len - off);
st_le16(&dcmds->command, dma_cmd);
st_le32(&dcmds->phy_addr,
virt_to_phys(scl->address) + off);
st_le32(&dcmds->phy_addr, dma_addr + off);
dcmds->xfer_status = 0;
++dcmds;
dtot += scl->length - off;
dtot += dma_len - off;
off = 0;
}
} else if (ms->data_ptr < cmd->request_bufflen) {
......@@ -1811,6 +1825,7 @@ set_dma_cmds(struct mesh_state *ms, Scsi_Cmnd *cmd)
if (dtot > 0xffff)
panic("mesh: transfer size >= 64k");
st_le16(&dcmds->req_count, dtot);
/* XXX Use pci DMA API here ... */
st_le32(&dcmds->phy_addr,
virt_to_phys(cmd->request_buffer) + ms->data_ptr);
dcmds->xfer_status = 0;
......@@ -1877,6 +1892,12 @@ halt_dma(struct mesh_state *ms)
ms->conn_tgt, ms->data_ptr, cmd->request_bufflen,
ms->tgts[ms->conn_tgt].data_goes_out);
}
if (cmd->use_sg != 0) {
struct scatterlist *sg;
sg = (struct scatterlist *)cmd->request_buffer;
pci_unmap_sg(ms->pdev, sg, cmd->use_sg,
scsi_to_pci_dma_dir(cmd->sc_data_direction));
}
ms->dma_started = 0;
}
......
......@@ -9,25 +9,26 @@
int mesh_detect(Scsi_Host_Template *);
int mesh_release(struct Scsi_Host *);
int mesh_command(Scsi_Cmnd *);
int mesh_queue(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *));
int mesh_abort(Scsi_Cmnd *);
int mesh_reset(Scsi_Cmnd *, unsigned int);
int mesh_host_reset(Scsi_Cmnd *);
#define SCSI_MESH { \
proc_name: "mesh", \
name: "MESH", \
detect: mesh_detect, \
release: mesh_release, \
command: mesh_command, \
queuecommand: mesh_queue, \
abort: mesh_abort, \
reset: mesh_reset, \
can_queue: 20, \
this_id: 7, \
sg_tablesize: SG_ALL, \
cmd_per_lun: 2, \
use_clustering: DISABLE_CLUSTERING, \
#define SCSI_MESH { \
proc_name: "mesh", \
name: "MESH", \
detect: mesh_detect, \
release: mesh_release, \
command: NULL, \
queuecommand: mesh_queue, \
eh_abort_handler: mesh_abort, \
eh_device_reset_handler: NULL, \
eh_bus_reset_handler: NULL, \
eh_host_reset_handler: mesh_host_reset, \
can_queue: 20, \
this_id: 7, \
sg_tablesize: SG_ALL, \
cmd_per_lun: 2, \
use_clustering: DISABLE_CLUSTERING, \
}
/*
......
......@@ -29,7 +29,6 @@
#ifdef __KERNEL__
extern int find_via_cuda(void);
extern int via_cuda_start(void);
extern int cuda_request(struct adb_request *req,
void (*done)(struct adb_request *), int nbytes, ...);
extern void cuda_poll(void);
......
......@@ -325,6 +325,9 @@ struct input_event {
#define KEY_UNKNOWN 240
#define KEY_BRIGHTNESSDOWN 224
#define KEY_BRIGHTNESSUP 225
#define BTN_MISC 0x100
#define BTN_0 0x100
#define BTN_1 0x101
......
......@@ -113,11 +113,12 @@ enum {
#define PMU_IOC_HAS_ADB _IOR('B', 4, sizeof(__u32*))
/* out param: u32* can_sleep: 0 or 1 */
#define PMU_IOC_CAN_SLEEP _IOR('B', 5, sizeof(__u32*))
/* no param */
#define PMU_IOC_GRAB_BACKLIGHT _IOR('B', 6, 0)
#ifdef __KERNEL__
extern int find_via_pmu(void);
extern int via_pmu_start(void);
extern int pmu_request(struct adb_request *req,
void (*done)(struct adb_request *), int nbytes, ...);
......@@ -168,19 +169,41 @@ struct pmu_sleep_notifier
/* priority levels in notifiers */
#define SLEEP_LEVEL_VIDEO 100 /* Video driver (first wake) */
#define SLEEP_LEVEL_SOUND 90 /* Sound driver */
#define SLEEP_LEVEL_MEDIABAY 80 /* Media bay driver */
#define SLEEP_LEVEL_BLOCK 70 /* IDE, SCSI */
#define SLEEP_LEVEL_NET 60 /* bmac */
#define SLEEP_LEVEL_ADB 50 /* ADB */
#define SLEEP_LEVEL_MISC 30 /* Anything */
#define SLEEP_LEVEL_LAST 0 /* Reserved for apm_emu */
#define SLEEP_LEVEL_MEDIABAY 90 /* Media bay driver */
#define SLEEP_LEVEL_BLOCK 80 /* IDE, SCSI */
#define SLEEP_LEVEL_NET 70 /* bmac, gmac */
#define SLEEP_LEVEL_MISC 60 /* Anything else */
#define SLEEP_LEVEL_USERLAND 55 /* Reserved for apm_emu */
#define SLEEP_LEVEL_ADB 50 /* ADB (async) */
#define SLEEP_LEVEL_SOUND 40 /* Sound driver (blocking) */
/* special register notifier functions */
int pmu_register_sleep_notifier(struct pmu_sleep_notifier* notifier);
int pmu_unregister_sleep_notifier(struct pmu_sleep_notifier* notifier);
#endif /* CONFIG_PMAC_PBOOK */
#define PMU_MAX_BATTERIES 2
/* values for pmu_power_flags */
#define PMU_PWR_AC_PRESENT 0x00000001
/* values for pmu_battery_info.flags */
#define PMU_BATT_PRESENT 0x00000001
#define PMU_BATT_CHARGING 0x00000002
struct pmu_battery_info
{
unsigned int flags;
unsigned int charge; /* current charge */
unsigned int max_charge; /* maximum charge */
signed int current; /* current, positive if charging */
unsigned int voltage; /* voltage */
unsigned int time_remaining; /* remaining time */
};
extern int pmu_battery_count;
extern struct pmu_battery_info pmu_batteries[PMU_MAX_BATTERIES];
extern unsigned int pmu_power_flags;
#endif /* CONFIG_PMAC_PBOOK */
#endif /* __KERNEL__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment