diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/block/swim3.c | 168 | ||||
| -rw-r--r-- | drivers/char/Makefile | 4 | ||||
| -rw-r--r-- | drivers/char/generic_nvram.c | 145 | ||||
| -rw-r--r-- | drivers/char/keyboard.c | 10 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-keywest.c | 371 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-keywest.h | 10 | ||||
| -rw-r--r-- | drivers/ide/ppc/pmac.c | 250 | ||||
| -rw-r--r-- | drivers/macintosh/Kconfig | 156 | ||||
| -rw-r--r-- | drivers/macintosh/Makefile | 7 | ||||
| -rw-r--r-- | drivers/macintosh/adb.c | 9 | ||||
| -rw-r--r-- | drivers/macintosh/adbhid.c | 71 | ||||
| -rw-r--r-- | drivers/macintosh/macio_asic.c | 281 | ||||
| -rw-r--r-- | drivers/macintosh/mediabay.c | 79 | ||||
| -rw-r--r-- | drivers/macintosh/therm_adt7467.c | 562 | ||||
| -rw-r--r-- | drivers/macintosh/therm_pm72.c | 1241 | ||||
| -rw-r--r-- | drivers/macintosh/therm_pm72.h | 236 | ||||
| -rw-r--r-- | drivers/macintosh/therm_windtunnel.c | 456 | ||||
| -rw-r--r-- | drivers/macintosh/via-pmu.c | 55 | ||||
| -rw-r--r-- | drivers/scsi/mac53c94.c | 503 | ||||
| -rw-r--r-- | drivers/scsi/mesh.c | 1760 | ||||
| -rw-r--r-- | drivers/serial/pmac_zilog.c | 89 |
22 files changed, 4909 insertions, 1556 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index 28e0da6c8c5d..5da149c6ab4e 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -26,6 +26,8 @@ source "drivers/ieee1394/Kconfig" source "drivers/message/i2o/Kconfig" +source "drivers/macintosh/Kconfig" + source "net/Kconfig" source "drivers/isdn/Kconfig" diff --git a/drivers/block/swim3.c b/drivers/block/swim3.c index 4bf309826005..e4cbabf61514 100644 --- a/drivers/block/swim3.c +++ b/drivers/block/swim3.c @@ -24,7 +24,10 @@ #include <linux/delay.h> #include <linux/fd.h> #include <linux/ioctl.h> +#include <linux/blkdev.h> #include <linux/devfs_fs_kernel.h> +#include <linux/interrupt.h> +#include <linux/module.h> #include <asm/io.h> #include <asm/dbdma.h> #include <asm/prom.h> @@ -144,7 +147,7 @@ struct swim3 { #define RELAX 3 /* also eject in progress */ #define READ_DATA_0 4 #define TWOMEG_DRIVE 5 -#define SINGLE_SIDED 6 +#define SINGLE_SIDED 6 /* drive or diskette is 4MB type? */ #define DRIVE_PRESENT 7 #define DISK_IN 8 #define WRITE_PROT 9 @@ -184,6 +187,7 @@ struct floppy_state { int req_sector; /* sector number ditto */ int scount; /* # sectors we're transferring at present */ int retries; + int settle_time; int secpercyl; /* disk geometry information */ int secpertrack; int total_secs; @@ -232,8 +236,9 @@ static void setup_transfer(struct floppy_state *fs); static void act(struct floppy_state *fs); static void scan_timeout(unsigned long data); static void seek_timeout(unsigned long data); +static void settle_timeout(unsigned long data); static void xfer_timeout(unsigned long data); -static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static irqreturn_t swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs); /*static void fd_dma_interrupt(int irq, void *dev_id, struct pt_regs *regs);*/ static int grab_drive(struct floppy_state *fs, enum swim_state state, int interruptible); @@ -274,7 +279,6 @@ static void swim3_action(struct floppy_state *fs, int action) udelay(2); out_8(&sw->select, sw->select & ~LSTRB); udelay(1); - out_8(&sw->select, RELAX); } static int swim3_readbit(struct floppy_state *fs, int bit) @@ -283,9 +287,8 @@ static int swim3_readbit(struct floppy_state *fs, int bit) int stat; swim3_select(fs, bit); - udelay(10); + udelay(1); stat = in_8(&sw->status); - out_8(&sw->select, RELAX); return (stat & DATA) == 0; } @@ -374,13 +377,13 @@ static void set_timeout(struct floppy_state *fs, int nticks, static inline void scan_track(struct floppy_state *fs) { volatile struct swim3 *sw = fs->swim3; - int xx; swim3_select(fs, READ_DATA_0); - xx = sw->intr; /* clear SEEN_SECTOR bit */ + in_8(&sw->intr); /* clear SEEN_SECTOR bit */ + in_8(&sw->error); + out_8(&sw->intr_enable, SEEN_SECTOR); out_8(&sw->control_bis, DO_ACTION); /* enable intr when track found */ - out_8(&sw->intr_enable, ERROR_INTR | SEEN_SECTOR); set_timeout(fs, HZ, scan_timeout); /* enable timeout */ } @@ -395,12 +398,14 @@ static inline void seek_track(struct floppy_state *fs, int n) swim3_action(fs, SEEK_NEGATIVE); sw->nseek = -n; } - fs->expect_cyl = (fs->cur_cyl > 0)? fs->cur_cyl + n: -1; + fs->expect_cyl = (fs->cur_cyl >= 0)? fs->cur_cyl + n: -1; swim3_select(fs, STEP); - out_8(&sw->control_bis, DO_SEEK); + in_8(&sw->error); /* enable intr when seek finished */ - out_8(&sw->intr_enable, ERROR_INTR | SEEK_DONE); - set_timeout(fs, HZ/2, seek_timeout); /* enable timeout */ + out_8(&sw->intr_enable, SEEK_DONE); + out_8(&sw->control_bis, DO_SEEK); + set_timeout(fs, 3*HZ, seek_timeout); /* enable timeout */ + fs->settle_time = 0; } static inline void init_dma(struct dbdma_cmd *cp, int cmd, @@ -448,18 +453,21 @@ static inline void setup_transfer(struct floppy_state *fs) } ++cp; out_le16(&cp->command, DBDMA_STOP); + out_8(&sw->control_bic, DO_ACTION | WRITE_SECTORS); + in_8(&sw->error); + out_8(&sw->control_bic, DO_ACTION | WRITE_SECTORS); + if (rq_data_dir(fd_req) == WRITE) + out_8(&sw->control_bis, WRITE_SECTORS); + in_8(&sw->intr); out_le32(&dr->control, (RUN << 16) | RUN); - out_8(&sw->control_bis, - (rq_data_dir(fd_req) == WRITE? WRITE_SECTORS: 0) | DO_ACTION); /* enable intr when transfer complete */ - out_8(&sw->intr_enable, ERROR_INTR | TRANSFER_DONE); + out_8(&sw->intr_enable, TRANSFER_DONE); + out_8(&sw->control_bis, DO_ACTION); set_timeout(fs, 2*HZ, xfer_timeout); /* enable timeout */ } static void act(struct floppy_state *fs) { - volatile struct swim3 *sw = fs->swim3; - for (;;) { switch (fs->state) { case idle: @@ -492,20 +500,10 @@ static void act(struct floppy_state *fs) return; case settling: - /* wait for SEEK_COMPLETE to become true */ - swim3_select(fs, SEEK_COMPLETE); - udelay(10); - out_8(&sw->intr_enable, ERROR_INTR | DATA_CHANGED); - in_8(&sw->intr); /* clear DATA_CHANGED */ - if (in_8(&sw->status) & DATA) { - /* seek_complete is not yet true */ - set_timeout(fs, HZ/2, seek_timeout); - return; - } - out_8(&sw->intr_enable, 0); - in_8(&sw->intr); - fs->state = locating; - break; + /* check for SEEK_COMPLETE after 30ms */ + fs->settle_time = (HZ + 32) / 33; + set_timeout(fs, fs->settle_time, settle_timeout); + return; case do_transfer: if (fs->cur_cyl != fs->req_cyl) { @@ -537,7 +535,7 @@ static void scan_timeout(unsigned long data) volatile struct swim3 *sw = fs->swim3; fs->timeout_pending = 0; - out_8(&sw->control_bic, DO_ACTION); + out_8(&sw->control_bic, DO_ACTION | WRITE_SECTORS); out_8(&sw->select, RELAX); out_8(&sw->intr_enable, 0); fs->cur_cyl = -1; @@ -557,20 +555,34 @@ static void seek_timeout(unsigned long data) volatile struct swim3 *sw = fs->swim3; fs->timeout_pending = 0; - if (fs->state == settling) { - printk(KERN_ERR "swim3: MSI sel=%x ctrl=%x stat=%x intr=%x ie=%x\n", - sw->select, sw->control, sw->status, sw->intr, sw->intr_enable); - } out_8(&sw->control_bic, DO_SEEK); out_8(&sw->select, RELAX); out_8(&sw->intr_enable, 0); - if (fs->state == settling && swim3_readbit(fs, SEEK_COMPLETE)) { - /* printk(KERN_DEBUG "swim3: missed settling interrupt\n"); */ + printk(KERN_ERR "swim3: seek timeout\n"); + end_request(fd_req, 0); + fs->state = idle; + start_request(fs); +} + +static void settle_timeout(unsigned long data) +{ + struct floppy_state *fs = (struct floppy_state *) data; + volatile struct swim3 *sw = fs->swim3; + + fs->timeout_pending = 0; + if (swim3_readbit(fs, SEEK_COMPLETE)) { + out_8(&sw->select, RELAX); fs->state = locating; act(fs); return; } - printk(KERN_ERR "swim3: seek timeout\n"); + out_8(&sw->select, RELAX); + if (fs->settle_time < 2*HZ) { + ++fs->settle_time; + set_timeout(fs, 1, settle_timeout); + return; + } + printk(KERN_ERR "swim3: seek settle timeout\n"); end_request(fd_req, 0); fs->state = idle; start_request(fs); @@ -583,9 +595,13 @@ static void xfer_timeout(unsigned long data) struct dbdma_regs *dr = fs->dma; struct dbdma_cmd *cp = fs->dma_cmd; unsigned long s; + int n; fs->timeout_pending = 0; st_le32(&dr->control, RUN << 16); + /* We must wait a bit for dbdma to stop */ + for (n = 0; (in_le32(&dr->status) & ACTIVE) && n < 1000; n++) + udelay(1); out_8(&sw->intr_enable, 0); out_8(&sw->control_bic, WRITE_SECTORS | DO_ACTION); out_8(&sw->select, RELAX); @@ -604,7 +620,7 @@ static void xfer_timeout(unsigned long data) start_request(fs); } -static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs) +static irqreturn_t swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct floppy_state *fs = (struct floppy_state *) dev_id; volatile struct swim3 *sw = fs->swim3; @@ -613,18 +629,15 @@ static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs) struct dbdma_regs *dr; struct dbdma_cmd *cp; - err = in_8(&sw->error); intr = in_8(&sw->intr); -#if 0 - printk("swim3 intr state=%d intr=%x err=%x\n", fs->state, intr, err); -#endif + err = (intr & ERROR_INTR)? in_8(&sw->error): 0; if ((intr & ERROR_INTR) && fs->state != do_transfer) printk(KERN_ERR "swim3_interrupt, state=%d, dir=%lx, intr=%x, err=%x\n", fs->state, rq_data_dir(fd_req), intr, err); switch (fs->state) { case locating: if (intr & SEEN_SECTOR) { - out_8(&sw->control_bic, DO_ACTION); + out_8(&sw->control_bic, DO_ACTION | WRITE_SECTORS); out_8(&sw->select, RELAX); out_8(&sw->intr_enable, 0); del_timer(&fs->timeout); @@ -674,19 +687,33 @@ static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs) case do_transfer: if ((intr & (ERROR_INTR | TRANSFER_DONE)) == 0) break; - dr = fs->dma; - cp = fs->dma_cmd; - /* 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; + dr = fs->dma; + cp = fs->dma_cmd; if (rq_data_dir(fd_req) == WRITE) ++cp; + /* + * Check that the main data transfer has finished. + * On writing, the swim3 sometimes doesn't use + * up all the bytes of the postamble, so we can still + * see DMA active here. That doesn't matter as long + * as all the sector data has been transferred. + */ + if ((intr & ERROR_INTR) == 0 && cp->xfer_status == 0) { + /* wait a little while for DMA to complete */ + for (n = 0; n < 100; ++n) { + if (cp->xfer_status != 0) + break; + udelay(1); + barrier(); + } + } + /* turn off DMA */ + out_le32(&dr->control, (RUN | PAUSE) << 16); stat = ld_le16(&cp->xfer_status); resid = ld_le16(&cp->res_count); if (intr & ERROR_INTR) { @@ -742,6 +769,7 @@ static void swim3_interrupt(int irq, void *dev_id, struct pt_regs *regs) default: printk(KERN_ERR "swim3: don't know what to do in state %d\n", fs->state); } + return IRQ_HANDLED; } /* @@ -793,16 +821,19 @@ static int fd_eject(struct floppy_state *fs) if (err) return err; swim3_action(fs, EJECT); - for (n = 2*HZ; n > 0; --n) { - if (swim3_readbit(fs, RELAX)) - break; + for (n = 20; n > 0; --n) { if (signal_pending(current)) { err = -EINTR; break; } + swim3_select(fs, RELAX); current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); + if (swim3_readbit(fs, DISK_IN) == 0) + break; } + swim3_select(fs, RELAX); + udelay(150); fs->ejected = 1; release_drive(fs); return err; @@ -847,29 +878,31 @@ static int floppy_open(struct inode *inode, struct file *filp) if (fs->ref_count == 0) { if (fs->media_bay && check_media_bay(fs->media_bay, MB_FD)) return -ENXIO; - out_8(&sw->mode, 0x95); - out_8(&sw->control_bic, 0xff); out_8(&sw->setup, S_IBM_DRIVE | S_FCLK_DIV2); + out_8(&sw->control_bic, 0xff); + out_8(&sw->mode, 0x95); udelay(10); out_8(&sw->intr_enable, 0); out_8(&sw->control_bis, DRIVE_ENABLE | INTR_ENABLE); swim3_action(fs, MOTOR_ON); fs->write_prot = -1; fs->cur_cyl = -1; - for (n = HZ; n > 0; --n) { - if (swim3_readbit(fs, SEEK_COMPLETE)) + for (n = 0; n < 2 * HZ; ++n) { + if (n >= HZ/30 && swim3_readbit(fs, SEEK_COMPLETE)) break; if (signal_pending(current)) { err = -EINTR; break; } + swim3_select(fs, RELAX); current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); } if (err == 0 && (swim3_readbit(fs, SEEK_COMPLETE) == 0 || swim3_readbit(fs, DISK_IN) == 0)) err = -ENXIO; - swim3_action(fs, 9); + swim3_action(fs, SETMFM); + swim3_select(fs, RELAX); } else if (fs->ref_count == -1 || filp->f_flags & O_EXCL) return -EBUSY; @@ -892,6 +925,7 @@ static int floppy_open(struct inode *inode, struct file *filp) if (fs->ref_count == 0) { swim3_action(fs, MOTOR_OFF); out_8(&sw->control_bic, DRIVE_ENABLE | INTR_ENABLE); + swim3_select(fs, RELAX); } return err; } @@ -911,6 +945,7 @@ static int floppy_release(struct inode *inode, struct file *filp) if (fs->ref_count > 0 && --fs->ref_count == 0) { swim3_action(fs, MOTOR_OFF); out_8(&sw->control_bic, 0xff); + swim3_select(fs, RELAX); } return 0; } @@ -933,15 +968,17 @@ static int floppy_revalidate(struct gendisk *disk) sw = fs->swim3; grab_drive(fs, revalidating, 0); out_8(&sw->intr_enable, 0); - out_8(&sw->control_bis, DRIVE_ENABLE | INTR_ENABLE); - swim3_action(fs, MOTOR_ON); + out_8(&sw->control_bis, DRIVE_ENABLE); + swim3_action(fs, MOTOR_ON); /* necessary? */ fs->write_prot = -1; fs->cur_cyl = -1; + mdelay(1); for (n = HZ; n > 0; --n) { if (swim3_readbit(fs, SEEK_COMPLETE)) break; if (signal_pending(current)) break; + swim3_select(fs, RELAX); current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); } @@ -951,17 +988,14 @@ static int floppy_revalidate(struct gendisk *disk) swim3_action(fs, MOTOR_OFF); else { fs->ejected = 0; - swim3_action(fs, 9); + swim3_action(fs, SETMFM); } + swim3_select(fs, RELAX); release_drive(fs); return ret; } -static void floppy_off(unsigned int nr) -{ -} - static struct block_device_operations floppy_fops = { .open = floppy_open, .release = floppy_release, @@ -1104,3 +1138,5 @@ static int swim3_add_device(struct device_node *swim) return 0; } + +module_init(swim3_init) diff --git a/drivers/char/Makefile b/drivers/char/Makefile index ca8aa47ef43e..302afc35f077 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -57,7 +57,9 @@ obj-$(CONFIG_SONYPI) += sonypi.o obj-$(CONFIG_RTC) += rtc.o obj-$(CONFIG_GEN_RTC) += genrtc.o obj-$(CONFIG_EFI_RTC) += efirtc.o -ifeq ($(CONFIG_PPC),) +ifeq ($(CONFIG_GENERIC_NVRAM),y) + obj-$(CONFIG_NVRAM) += generic_nvram.o +else obj-$(CONFIG_NVRAM) += nvram.o endif obj-$(CONFIG_TOSHIBA) += toshiba.o diff --git a/drivers/char/generic_nvram.c b/drivers/char/generic_nvram.c new file mode 100644 index 000000000000..b4d657d02d44 --- /dev/null +++ b/drivers/char/generic_nvram.c @@ -0,0 +1,145 @@ +/* + * Generic /dev/nvram driver for architectures providing some + * "generic" hooks, that is : + * + * nvram_read_byte, nvram_write_byte, nvram_sync + * + * Note that an additional hook is supported for PowerMac only + * for getting the nvram "partition" informations + * + */ + +#define NVRAM_VERSION "1.1" + +#include <linux/module.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <asm/uaccess.h> +#include <asm/nvram.h> + +#define NVRAM_SIZE 8192 + +static loff_t nvram_llseek(struct file *file, loff_t offset, int origin) +{ + lock_kernel(); + switch (origin) { + case 1: + offset += file->f_pos; + break; + case 2: + offset += NVRAM_SIZE; + break; + } + if (offset < 0) { + unlock_kernel(); + return -EINVAL; + } + file->f_pos = offset; + unlock_kernel(); + return file->f_pos; +} + +static ssize_t read_nvram(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int i; + char __user *p = buf; + + if (verify_area(VERIFY_WRITE, buf, count)) + return -EFAULT; + if (*ppos >= NVRAM_SIZE) + return 0; + for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count) + if (__put_user(nvram_read_byte(i), p)) + return -EFAULT; + *ppos = i; + return p - buf; +} + +static ssize_t write_nvram(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int i; + const char __user *p = buf; + char c; + + if (verify_area(VERIFY_READ, buf, count)) + return -EFAULT; + if (*ppos >= NVRAM_SIZE) + return 0; + for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count) { + if (__get_user(c, p)) + return -EFAULT; + nvram_write_byte(c, i); + } + *ppos = i; + return p - buf; +} + +static int nvram_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch(cmd) { +#ifdef CONFIG_PPC_PMAC + case OBSOLETE_PMAC_NVRAM_GET_OFFSET: + printk(KERN_WARNING "nvram: Using obsolete PMAC_NVRAM_GET_OFFSET ioctl\n"); + case IOC_NVRAM_GET_OFFSET: { + int part, offset; + + if (_machine != _MACH_Pmac) + return -EINVAL; + if (copy_from_user(&part, (void __user*)arg, sizeof(part)) != 0) + return -EFAULT; + if (part < pmac_nvram_OF || part > pmac_nvram_NR) + return -EINVAL; + offset = pmac_get_partition(part); + if (copy_to_user((void __user*)arg, &offset, sizeof(offset)) != 0) + return -EFAULT; + break; + } +#endif /* CONFIG_PPC_PMAC */ + case IOC_NVRAM_SYNC: + nvram_sync(); + break; + default: + return -EINVAL; + } + + return 0; +} + +struct file_operations nvram_fops = { + .owner = THIS_MODULE, + .llseek = nvram_llseek, + .read = read_nvram, + .write = write_nvram, + .ioctl = nvram_ioctl, +}; + +static struct miscdevice nvram_dev = { + NVRAM_MINOR, + "nvram", + &nvram_fops +}; + +int __init nvram_init(void) +{ + printk(KERN_INFO "Macintosh non-volatile memory driver v%s\n", + NVRAM_VERSION); + return misc_register(&nvram_dev); +} + +void __exit nvram_cleanup(void) +{ + misc_deregister( &nvram_dev ); +} + +module_init(nvram_init); +module_exit(nvram_cleanup); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/keyboard.c b/drivers/char/keyboard.c index dc03b1a99956..643014bf8c4c 100644 --- a/drivers/char/keyboard.c +++ b/drivers/char/keyboard.c @@ -972,11 +972,6 @@ extern void sun_do_break(void); static int emulate_raw(struct vc_data *vc, unsigned int keycode, unsigned char up_flag) { -#ifdef CONFIG_MAC_EMUMOUSEBTN - if (mac_hid_mouse_emulate_buttons(1, keycode, !up_flag)) - return 0; -#endif /* CONFIG_MAC_EMUMOUSEBTN */ - if (keycode > 255 || !x86_keycodes[keycode]) return -1; @@ -1055,6 +1050,11 @@ void kbd_keycode(unsigned int keycode, int down, struct pt_regs *regs) rep = (down == 2); +#ifdef CONFIG_MAC_EMUMOUSEBTN + if (mac_hid_mouse_emulate_buttons(1, keycode, down)) + return; +#endif /* CONFIG_MAC_EMUMOUSEBTN */ + if ((raw_mode = (kbd->kbdmode == VC_RAW))) if (emulate_raw(vc, keycode, !down << 7)) if (keycode < BTN_MISC) diff --git a/drivers/i2c/busses/i2c-keywest.c b/drivers/i2c/busses/i2c-keywest.c index 64819889dd45..9d9aa44fefeb 100644 --- a/drivers/i2c/busses/i2c-keywest.c +++ b/drivers/i2c/busses/i2c-keywest.c @@ -26,6 +26,9 @@ 2001/12/13 BenH New implementation 2001/12/15 BenH Add support for "byte" and "quick" transfers. Add i2c_xfer routine. + 2003/09/21 BenH Rework state machine with Paulus help + 2004/01/21 BenH Merge in Greg KH changes, polled mode is back + 2004/02/05 BenH Merge 64 bits fixes from the g5 ppc64 tree My understanding of the various modes supported by keywest are: @@ -67,9 +70,35 @@ #include <asm/prom.h> #include <asm/machdep.h> #include <asm/pmac_feature.h> +#include <asm/pmac_low_i2c.h> #include "i2c-keywest.h" +/* Currently, we don't deal with the weird interrupt cascade of the G5 + * machines with the ppc64 kernel, so use Polled mode on these + */ +#ifdef CONFIG_PPC64 +#define POLLED_MODE +#else +#undef POLLED_MODE +#endif + +/* Some debug macros */ +#define WRONG_STATE(name) do {\ + pr_debug("KW: wrong state. Got %s, state: %s (isr: %02x)\n", \ + name, __kw_state_names[iface->state], isr); \ + } while(0) + +#ifdef DEBUG +static const char *__kw_state_names[] = { + "state_idle", + "state_addr", + "state_read", + "state_write", + "state_stop", + "state_dead" +}; +#endif /* DEBUG */ MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); MODULE_DESCRIPTION("I2C driver for Apple's Keywest"); @@ -78,121 +107,154 @@ MODULE_PARM(probe, "i"); static int probe = 0; +#ifdef POLLED_MODE +/* Don't schedule, the g5 fan controller is too + * timing sensitive + */ +static u8 +wait_interrupt(struct keywest_iface* iface) +{ + int i; + u8 isr; + + for (i = 0; i < 200000; i++) { + isr = read_reg(reg_isr) & KW_I2C_IRQ_MASK; + if (isr != 0) + return isr; + udelay(10); + } + return isr; +} +#endif /* POLLED_MODE */ + static void do_stop(struct keywest_iface* iface, int result) { - write_reg(reg_control, read_reg(reg_control) | KW_I2C_CTL_STOP); + write_reg(reg_control, KW_I2C_CTL_STOP); iface->state = state_stop; iface->result = result; } /* Main state machine for standard & standard sub mode */ -static int +static void handle_interrupt(struct keywest_iface *iface, u8 isr) { int ack; - int rearm_timer = 1; - pr_debug("handle_interrupt(), got: %x, status: %x, state: %d\n", - isr, read_reg(reg_status), iface->state); - if (isr == 0 && iface->state != state_stop) { - do_stop(iface, -1); - return rearm_timer; - } - if (isr & KW_I2C_IRQ_STOP && iface->state != state_stop) { - iface->result = -1; - iface->state = state_stop; - } - switch(iface->state) { - case state_addr: - if (!(isr & KW_I2C_IRQ_ADDR)) { - do_stop(iface, -1); - break; - } - ack = read_reg(reg_status); - pr_debug("ack on set address: %x\n", ack); - if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { - do_stop(iface, -1); - break; + if (isr == 0) { + if (iface->state != state_stop) { + pr_debug("KW: Timeout !\n"); + do_stop(iface, -EIO); } - /* Handle rw "quick" mode */ - if (iface->datalen == 0) - do_stop(iface, 0); - else if (iface->read_write == I2C_SMBUS_READ) { - iface->state = state_read; - if (iface->datalen > 1) - write_reg(reg_control, read_reg(reg_control) - | KW_I2C_CTL_AAK); - } else { - iface->state = state_write; - pr_debug("write byte: %x\n", *(iface->data)); - write_reg(reg_data, *(iface->data++)); - iface->datalen--; - } - - break; - case state_read: - if (!(isr & KW_I2C_IRQ_DATA)) { - do_stop(iface, -1); - break; - } - *(iface->data++) = read_reg(reg_data); - pr_debug("read byte: %x\n", *(iface->data-1)); - iface->datalen--; - if (iface->datalen == 0) - iface->state = state_stop; - else - write_reg(reg_control, 0); - break; - case state_write: - if (!(isr & KW_I2C_IRQ_DATA)) { - do_stop(iface, -1); - break; + if (iface->state == state_stop) { + ack = read_reg(reg_status); + if (!(ack & KW_I2C_STAT_BUSY)) { + iface->state = state_idle; + write_reg(reg_ier, 0x00); +#ifndef POLLED_MODE + complete(&iface->complete); +#endif /* POLLED_MODE */ + } } - /* Check ack status */ + return; + } + + if (isr & KW_I2C_IRQ_ADDR) { ack = read_reg(reg_status); - pr_debug("ack on data write: %x\n", ack); + if (iface->state != state_addr) { + write_reg(reg_isr, KW_I2C_IRQ_ADDR); + WRONG_STATE("KW_I2C_IRQ_ADDR"); + do_stop(iface, -EIO); + return; + } if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { - do_stop(iface, -1); - break; + iface->state = state_stop; + iface->result = -ENODEV; + pr_debug("KW: NAK on address\n"); + } else { + /* Handle rw "quick" mode */ + if (iface->datalen == 0) { + do_stop(iface, 0); + } else if (iface->read_write == I2C_SMBUS_READ) { + iface->state = state_read; + if (iface->datalen > 1) + write_reg(reg_control, KW_I2C_CTL_AAK); + } else { + iface->state = state_write; + write_reg(reg_data, *(iface->data++)); + iface->datalen--; + } } - if (iface->datalen) { - pr_debug("write byte: %x\n", *(iface->data)); - write_reg(reg_data, *(iface->data++)); + write_reg(reg_isr, KW_I2C_IRQ_ADDR); + } + + if (isr & KW_I2C_IRQ_DATA) { + if (iface->state == state_read) { + *(iface->data++) = read_reg(reg_data); + write_reg(reg_isr, KW_I2C_IRQ_DATA); iface->datalen--; - } else - do_stop(iface, 0); - break; - - case state_stop: - if (!(isr & KW_I2C_IRQ_STOP) && (++iface->stopretry) < 10) - do_stop(iface, -1); - else { - rearm_timer = 0; - iface->state = state_idle; - write_reg(reg_control, 0x00); - write_reg(reg_ier, 0x00); - complete(&iface->complete); + if (iface->datalen == 0) + iface->state = state_stop; + else if (iface->datalen == 1) + write_reg(reg_control, 0); + } else if (iface->state == state_write) { + /* Check ack status */ + ack = read_reg(reg_status); + if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { + pr_debug("KW: nack on data write (%x): %x\n", + iface->data[-1], ack); + do_stop(iface, -EIO); + } else if (iface->datalen) { + write_reg(reg_data, *(iface->data++)); + iface->datalen--; + } else { + write_reg(reg_control, KW_I2C_CTL_STOP); + iface->state = state_stop; + iface->result = 0; + } + write_reg(reg_isr, KW_I2C_IRQ_DATA); + } else { + write_reg(reg_isr, KW_I2C_IRQ_DATA); + WRONG_STATE("KW_I2C_IRQ_DATA"); + if (iface->state != state_stop) + do_stop(iface, -EIO); } - break; } - - write_reg(reg_isr, isr); - return rearm_timer; + if (isr & KW_I2C_IRQ_STOP) { + write_reg(reg_isr, KW_I2C_IRQ_STOP); + if (iface->state != state_stop) { + WRONG_STATE("KW_I2C_IRQ_STOP"); + iface->result = -EIO; + } + iface->state = state_idle; + write_reg(reg_ier, 0x00); +#ifndef POLLED_MODE + complete(&iface->complete); +#endif /* POLLED_MODE */ + } + + if (isr & KW_I2C_IRQ_START) + write_reg(reg_isr, KW_I2C_IRQ_START); } +#ifndef POLLED_MODE + /* Interrupt handler */ static irqreturn_t keywest_irq(int irq, void *dev_id, struct pt_regs *regs) { struct keywest_iface *iface = (struct keywest_iface *)dev_id; + unsigned long flags; - spin_lock(&iface->lock); + spin_lock_irqsave(&iface->lock, flags); del_timer(&iface->timeout_timer); - if (handle_interrupt(iface, read_reg(reg_isr))) - mod_timer(&iface->timeout_timer, jiffies + POLL_TIMEOUT); - spin_unlock(&iface->lock); + handle_interrupt(iface, read_reg(reg_isr)); + if (iface->state != state_idle) { + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); + } + spin_unlock_irqrestore(&iface->lock, flags); return IRQ_HANDLED; } @@ -200,14 +262,20 @@ static void keywest_timeout(unsigned long data) { struct keywest_iface *iface = (struct keywest_iface *)data; + unsigned long flags; pr_debug("timeout !\n"); - spin_lock_irq(&iface->lock); - if (handle_interrupt(iface, read_reg(reg_isr))) - mod_timer(&iface->timeout_timer, jiffies + POLL_TIMEOUT); - spin_unlock(&iface->lock); + spin_lock_irqsave(&iface->lock, flags); + handle_interrupt(iface, read_reg(reg_isr)); + if (iface->state != state_idle) { + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); + } + spin_unlock_irqrestore(&iface->lock, flags); } +#endif /* POLLED_MODE */ + /* * SMBUS-type transfer entrypoint */ @@ -228,46 +296,54 @@ keywest_smbus_xfer( struct i2c_adapter* adap, int rc = 0; if (iface->state == state_dead) - return -1; + return -ENXIO; /* Prepare datas & select mode */ iface->cur_mode &= ~KW_I2C_MODE_MODE_MASK; switch (size) { - case I2C_SMBUS_QUICK: + case I2C_SMBUS_QUICK: len = 0; buffer = NULL; iface->cur_mode |= KW_I2C_MODE_STANDARD; break; - case I2C_SMBUS_BYTE: + case I2C_SMBUS_BYTE: len = 1; buffer = &data->byte; iface->cur_mode |= KW_I2C_MODE_STANDARD; break; - case I2C_SMBUS_BYTE_DATA: + case I2C_SMBUS_BYTE_DATA: len = 1; buffer = &data->byte; iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; break; - case I2C_SMBUS_WORD_DATA: + case I2C_SMBUS_WORD_DATA: len = 2; cur_word = cpu_to_le16(data->word); buffer = (u8 *)&cur_word; iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; break; - case I2C_SMBUS_BLOCK_DATA: + case I2C_SMBUS_BLOCK_DATA: len = data->block[0]; buffer = &data->block[1]; iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; break; - default: + default: return -1; } + /* Turn a standardsub read into a combined mode access */ + if (read_write == I2C_SMBUS_READ + && (iface->cur_mode & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB) { + iface->cur_mode &= ~KW_I2C_MODE_MODE_MASK; + iface->cur_mode |= KW_I2C_MODE_COMBINED; + } + /* Original driver had this limitation */ if (len > 32) len = 32; - down(&iface->sem); + if (pmac_low_i2c_lock(iface->node)) + return -ENXIO; pr_debug("chan: %d, addr: 0x%x, transfer len: %d, read: %d\n", chan->chan_no, addr, len, read_write == I2C_SMBUS_READ); @@ -276,12 +352,11 @@ keywest_smbus_xfer( struct i2c_adapter* adap, iface->datalen = len; iface->state = state_addr; iface->result = 0; - iface->stopretry = 0; iface->read_write = read_write; /* Setup channel & clear pending irqs */ - write_reg(reg_mode, iface->cur_mode | (chan->chan_no << 4)); write_reg(reg_isr, read_reg(reg_isr)); + write_reg(reg_mode, iface->cur_mode | (chan->chan_no << 4)); write_reg(reg_status, 0); /* Set up address and r/w bit */ @@ -293,15 +368,31 @@ keywest_smbus_xfer( struct i2c_adapter* adap, || (iface->cur_mode & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_COMBINED) write_reg(reg_subaddr, command); +#ifndef POLLED_MODE /* Arm timeout */ - mod_timer(&iface->timeout_timer, jiffies + POLL_TIMEOUT); + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); +#endif /* Start sending address & enable interrupt*/ - write_reg(reg_control, read_reg(reg_control) | KW_I2C_CTL_XADDR); + write_reg(reg_control, KW_I2C_CTL_XADDR); write_reg(reg_ier, KW_I2C_IRQ_MASK); - /* Wait interrupt operations completion */ +#ifdef POLLED_MODE + pr_debug("using polled mode...\n"); + /* State machine, to turn into an interrupt handler */ + while(iface->state != state_idle) { + unsigned long flags; + + u8 isr = wait_interrupt(iface); + spin_lock_irqsave(&iface->lock, flags); + handle_interrupt(iface, isr); + spin_unlock_irqrestore(&iface->lock, flags); + } +#else /* POLLED_MODE */ + pr_debug("using interrupt mode...\n"); wait_for_completion(&iface->complete); +#endif /* POLLED_MODE */ rc = iface->result; pr_debug("transfer done, result: %d\n", rc); @@ -310,7 +401,7 @@ keywest_smbus_xfer( struct i2c_adapter* adap, data->word = le16_to_cpu(cur_word); /* Release sem */ - up(&iface->sem); + pmac_low_i2c_unlock(iface->node); return rc; } @@ -329,7 +420,11 @@ keywest_xfer( struct i2c_adapter *adap, int i, completed; int rc = 0; - down(&iface->sem); + if (iface->state == state_dead) + return -ENXIO; + + if (pmac_low_i2c_lock(iface->node)) + return -ENXIO; /* Set adapter to standard mode */ iface->cur_mode &= ~KW_I2C_MODE_MODE_MASK; @@ -360,7 +455,6 @@ keywest_xfer( struct i2c_adapter *adap, iface->datalen = pmsg->len; iface->state = state_addr; iface->result = 0; - iface->stopretry = 0; if (pmsg->flags & I2C_M_RD) iface->read_write = I2C_SMBUS_READ; else @@ -373,15 +467,27 @@ keywest_xfer( struct i2c_adapter *adap, (addr << 1) | ((iface->read_write == I2C_SMBUS_READ) ? 0x01 : 0x00)); +#ifndef POLLED_MODE /* Arm timeout */ - mod_timer(&iface->timeout_timer, jiffies + POLL_TIMEOUT); + iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; + add_timer(&iface->timeout_timer); +#endif /* Start sending address & enable interrupt*/ - write_reg(reg_control, read_reg(reg_control) | KW_I2C_CTL_XADDR); write_reg(reg_ier, KW_I2C_IRQ_MASK); - - /* Wait interrupt operations completion */ + write_reg(reg_control, KW_I2C_CTL_XADDR); + +#ifdef POLLED_MODE + pr_debug("using polled mode...\n"); + /* State machine, to turn into an interrupt handler */ + while(iface->state != state_idle) { + u8 isr = wait_interrupt(iface); + handle_interrupt(iface, isr); + } +#else /* POLLED_MODE */ + pr_debug("using interrupt mode...\n"); wait_for_completion(&iface->complete); +#endif /* POLLED_MODE */ rc = iface->result; if (rc == 0) @@ -390,7 +496,7 @@ keywest_xfer( struct i2c_adapter *adap, } /* Release sem */ - up(&iface->sem); + pmac_low_i2c_unlock(iface->node); return completed; } @@ -416,19 +522,23 @@ static struct i2c_algorithm keywest_algorithm = { static int create_iface(struct device_node *np, struct device *dev) { - unsigned long steps, *psteps, *prate; + unsigned long steps; unsigned bsteps, tsize, i, nchan, addroffset; struct keywest_iface* iface; + u32 *psteps, *prate; int rc; - psteps = (unsigned long *)get_property(np, "AAPL,address-step", NULL); + if (pmac_low_i2c_lock(np)) + return -ENODEV; + + psteps = (u32 *)get_property(np, "AAPL,address-step", NULL); steps = psteps ? (*psteps) : 0x10; /* Hrm... maybe we can be smarter here */ for (bsteps = 0; (steps & 0x01) == 0; bsteps++) steps >>= 1; - if (!strcmp(np->parent->name, "uni-n")) { + if (np->parent->name[0] == 'u') { nchan = 2; addroffset = 3; } else { @@ -441,12 +551,13 @@ create_iface(struct device_node *np, struct device *dev) iface = (struct keywest_iface *) kmalloc(tsize, GFP_KERNEL); if (iface == NULL) { printk(KERN_ERR "i2c-keywest: can't allocate inteface !\n"); + pmac_low_i2c_unlock(np); return -ENOMEM; } memset(iface, 0, tsize); - init_MUTEX(&iface->sem); spin_lock_init(&iface->lock); init_completion(&iface->complete); + iface->node = of_node_get(np); iface->bsteps = bsteps; iface->chan_count = nchan; iface->state = state_idle; @@ -458,16 +569,19 @@ create_iface(struct device_node *np, struct device *dev) if (iface->base == 0) { printk(KERN_ERR "i2c-keywest: can't map inteface !\n"); kfree(iface); + pmac_low_i2c_unlock(np); return -ENOMEM; } +#ifndef POLLED_MODE init_timer(&iface->timeout_timer); iface->timeout_timer.function = keywest_timeout; iface->timeout_timer.data = (unsigned long)iface; +#endif /* Select interface rate */ iface->cur_mode = KW_I2C_MODE_100KHZ; - prate = (unsigned long *)get_property(np, "AAPL,i2c-rate", NULL); + prate = (u32 *)get_property(np, "AAPL,i2c-rate", NULL); if (prate) switch(*prate) { case 100: iface->cur_mode = KW_I2C_MODE_100KHZ; @@ -480,11 +594,11 @@ create_iface(struct device_node *np, struct device *dev) break; default: printk(KERN_WARNING "i2c-keywest: unknown rate %ldKhz, using 100KHz\n", - *prate); + (long)*prate); } - /* Select standard sub mode */ - iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; + /* Select standard mode by default */ + iface->cur_mode |= KW_I2C_MODE_STANDARD; /* Write mode */ write_reg(reg_mode, iface->cur_mode); @@ -493,14 +607,17 @@ create_iface(struct device_node *np, struct device *dev) write_reg(reg_ier, 0x00); write_reg(reg_isr, KW_I2C_IRQ_MASK); +#ifndef POLLED_MODE /* Request chip interrupt */ - rc = request_irq(iface->irq, keywest_irq, 0, "keywest i2c", iface); + rc = request_irq(iface->irq, keywest_irq, SA_INTERRUPT, "keywest i2c", iface); if (rc) { printk(KERN_ERR "i2c-keywest: can't get IRQ %d !\n", iface->irq); iounmap((void *)iface->base); kfree(iface); + pmac_low_i2c_unlock(np); return -ENODEV; } +#endif /* POLLED_MODE */ dev_set_drvdata(dev, iface); @@ -539,6 +656,7 @@ create_iface(struct device_node *np, struct device *dev) printk(KERN_INFO "Found KeyWest i2c on \"%s\", %d channel%s, stepping: %d bits\n", np->parent->name, nchan, nchan > 1 ? "s" : "", bsteps); + pmac_low_i2c_unlock(np); return 0; } @@ -549,8 +667,10 @@ dispose_iface(struct device *dev) int i, rc; /* Make sure we stop all activity */ - down(&iface->sem); + if (pmac_low_i2c_lock(iface->node)) + return -ENODEV; +#ifndef POLLED_MODE spin_lock_irq(&iface->lock); while (iface->state != state_idle) { spin_unlock_irq(&iface->lock); @@ -558,10 +678,14 @@ dispose_iface(struct device *dev) schedule_timeout(HZ/10); spin_lock_irq(&iface->lock); } +#endif /* POLLED_MODE */ iface->state = state_dead; +#ifndef POLLED_MODE spin_unlock_irq(&iface->lock); free_irq(iface->irq, iface); - up(&iface->sem); +#endif /* POLLED_MODE */ + + pmac_low_i2c_unlock(iface->node); /* Release all channels */ for (i=0; i<iface->chan_count; i++) { @@ -576,6 +700,7 @@ dispose_iface(struct device *dev) } iounmap((void *)iface->base); dev_set_drvdata(dev, NULL); + of_node_put(iface->node); kfree(iface); return 0; @@ -634,8 +759,8 @@ static struct of_platform_driver i2c_keywest_of_platform_driver = static int __init i2c_keywest_init(void) { - macio_register_driver(&i2c_keywest_macio_driver); of_register_driver(&i2c_keywest_of_platform_driver); + macio_register_driver(&i2c_keywest_macio_driver); return 0; } @@ -643,8 +768,8 @@ i2c_keywest_init(void) static void __exit i2c_keywest_cleanup(void) { - macio_unregister_driver(&i2c_keywest_macio_driver); of_unregister_driver(&i2c_keywest_of_platform_driver); + macio_unregister_driver(&i2c_keywest_macio_driver); } module_init(i2c_keywest_init); diff --git a/drivers/i2c/busses/i2c-keywest.h b/drivers/i2c/busses/i2c-keywest.h index 8f6e975dc27b..ecc279d5ccae 100644 --- a/drivers/i2c/busses/i2c-keywest.h +++ b/drivers/i2c/busses/i2c-keywest.h @@ -51,20 +51,19 @@ typedef enum { /* Physical interface */ struct keywest_iface { + struct device_node *node; unsigned long base; unsigned bsteps; int irq; - struct semaphore sem; spinlock_t lock; - struct keywest_chan* channels; + struct keywest_chan *channels; unsigned chan_count; u8 cur_mode; char read_write; - u8* data; + u8 *data; unsigned datalen; int state; int result; - int stopretry; struct timer_list timeout_timer; struct completion complete; }; @@ -98,8 +97,7 @@ static inline void __write_reg(struct keywest_iface *iface, reg_t reg, u8 val) { out_8(((volatile u8 *)iface->base) + (((unsigned)reg) << iface->bsteps), val); - (void)__read_reg(iface, reg); - udelay(10); + (void)__read_reg(iface, reg_subaddr); } #define write_reg(reg, val) __write_reg(iface, reg, val) diff --git a/drivers/ide/ppc/pmac.c b/drivers/ide/ppc/pmac.c index 8612c8d2bc04..a97f5a164a25 100644 --- a/drivers/ide/ppc/pmac.c +++ b/drivers/ide/ppc/pmac.c @@ -69,7 +69,7 @@ typedef struct pmac_ide_hwif { #ifdef CONFIG_BLK_DEV_IDEDMA_PMAC /* Those fields are duplicating what is in hwif. We currently * can't use the hwif ones because of some assumptions that are - * being done by the generic code about the kind of dma controller + * beeing done by the generic code about the kind of dma controller * and format of the dma table. This will have to be fixed though. */ volatile struct dbdma_regs* dma_regs; @@ -90,15 +90,17 @@ enum { controller_heathrow, /* Heathrow/Paddington */ controller_kl_ata3, /* KeyLargo ATA-3 */ controller_kl_ata4, /* KeyLargo ATA-4 */ - controller_un_ata6 /* UniNorth2 ATA-6 */ + controller_un_ata6, /* UniNorth2 ATA-6 */ + controller_k2_ata6 /* K2 ATA-6 */ }; static const char* model_name[] = { "OHare ATA", /* OHare based */ "Heathrow ATA", /* Heathrow/Paddington */ - "KeyLargo ATA-3", /* KeyLargo ATA-3 */ - "KeyLargo ATA-4", /* KeyLargo ATA-4 */ - "UniNorth ATA-6" /* UniNorth2 ATA-6 */ + "KeyLargo ATA-3", /* KeyLargo ATA-3 (MDMA only) */ + "KeyLargo ATA-4", /* KeyLargo ATA-4 (UDMA/66) */ + "UniNorth ATA-6", /* UniNorth2 ATA-6 (UDMA/100) */ + "K2 ATA-6", /* K2 ATA-6 (UDMA/100) */ }; /* @@ -336,16 +338,19 @@ kauai_lookup_timing(struct kauai_timing* table, int cycle_time) /* allow up to 256 DBDMA commands per xfer */ #define MAX_DCMDS 256 -/* Wait 2s for disk to answer on IDE bus after - * enable operation. - * NOTE: There is at least one case I know of a disk that needs about 10sec - * before anwering on the bus. I beleive we could add a kernel command - * line arg to override this delay for such cases. - * - * NOTE2: This has to be fixed with a BSY wait loop. I'm working on adding - * that to the generic probe code. +/* + * Wait 1s for disk to answer on IDE bus after a hard reset + * of the device (via GPIO/FCR). + * + * Some devices seem to "pollute" the bus even after dropping + * the BSY bit (typically some combo drives slave on the UDMA + * bus) after a hard reset. Since we hard reset all drives on + * KeyLargo ATA66, we have to keep that delay around. I may end + * up not hard resetting anymore on these and keep the delay only + * for older interfaces instead (we have to reset when coming + * from MacOS...) --BenH. */ -#define IDE_WAKEUP_DELAY_MS 2000 +#define IDE_WAKEUP_DELAY (1*HZ) static void pmac_ide_setup_dma(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif); static int pmac_ide_build_dmatable(ide_drive_t *drive, struct request *rq); @@ -357,9 +362,16 @@ static int pmac_ide_dma_begin (ide_drive_t *drive); #endif /* CONFIG_BLK_DEV_IDEDMA_PMAC */ +/* + * Below is the code for blinking the laptop LED along with hard + * disk activity. + */ + #ifdef CONFIG_BLK_DEV_IDE_PMAC_BLINK -/* Set to 50ms */ +/* Set to 50ms minimum led-on time (also used to limit frequency + * of requests sent to the PMU + */ #define PMU_HD_BLINK_TIME (HZ/50) static struct adb_request pmu_blink_on, pmu_blink_off; @@ -402,6 +414,7 @@ pmu_hd_kick_blink(void *data, int rw) pmu_blink_stoptime = jiffies + PMU_HD_BLINK_TIME; wmb(); mod_timer(&pmu_blink_timer, pmu_blink_stoptime); + /* Fast path when LED is already ON */ if (pmu_blink_ledstate == 1) return; spin_lock_irqsave(&pmu_blink_lock, flags); @@ -418,6 +431,11 @@ pmu_hd_blink_init(void) struct device_node *dt; const char *model; + /* Currently, I only enable this feature on KeyLargo based laptops, + * older laptops may support it (at least heathrow/paddington) but + * I don't feel like loading those venerable old machines with so + * much additional interrupt & PMU activity... + */ if (pmu_get_model() != PMU_KEYLARGO_BASED) return 0; @@ -476,9 +494,11 @@ pmac_ide_init_hwif_ports(hw_regs_t *hw, *irq = pmac_ide[ix].irq; } -/* Setup timings for the selected drive (master/slave). I still need to verify if this - * is enough, I beleive selectproc will be called whenever an IDE command is started, - * but... */ +/* + * Apply the timings of the proper unit (master/slave) to the shared + * timing register when selecting that unit. This version is for + * ASICs with a single timing register + */ static void __pmac pmac_ide_selectproc(ide_drive_t *drive) { @@ -496,6 +516,11 @@ pmac_ide_selectproc(ide_drive_t *drive) (void)readl((unsigned *)(IDE_DATA_REG+IDE_TIMING_CONFIG)); } +/* + * Apply the timings of the proper unit (master/slave) to the shared + * timing register when selecting that unit. This version is for + * ASICs with a dual timing register (Kauai) + */ static void __pmac pmac_ide_kauai_selectproc(ide_drive_t *drive) { @@ -518,6 +543,9 @@ pmac_ide_kauai_selectproc(ide_drive_t *drive) (void)readl((unsigned *)(IDE_DATA_REG + IDE_KAUAI_PIO_CONFIG)); } +/* + * Force an update of controller timing values for a given drive + */ static void __pmac pmac_ide_do_update_timings(ide_drive_t *drive) { @@ -526,12 +554,29 @@ pmac_ide_do_update_timings(ide_drive_t *drive) if (pmif == NULL) return; - if (pmif->kind == controller_un_ata6) + if (pmif->kind == controller_un_ata6 || pmif->kind == controller_k2_ata6) pmac_ide_kauai_selectproc(drive); else pmac_ide_selectproc(drive); } +static void +pmac_outbsync(ide_drive_t *drive, u8 value, unsigned long port) +{ + u32 tmp; + + writeb(value, port); + tmp = readl((unsigned *)(IDE_DATA_REG + IDE_TIMING_CONFIG)); +} + +/* + * Send the SET_FEATURE IDE command to the drive and update drive->id with + * the new state. We currently don't use the generic routine as it used to + * cause various trouble, especially with older mediabays. + * This code is sometimes triggering a spurrious interrupt though, I need + * to sort that out sooner or later and see if I can finally get the + * common version to work properly in all cases + */ static int __pmac pmac_ide_do_setfeature(ide_drive_t *drive, u8 command) { @@ -606,7 +651,9 @@ out: return result; } -/* Calculate PIO timings */ +/* + * Old tuning functions (called on hdparm -p), sets up drive PIO timings + */ static void __pmac pmac_ide_tuneproc(ide_drive_t *drive, u8 pio) { @@ -625,7 +672,8 @@ pmac_ide_tuneproc(ide_drive_t *drive, u8 pio) pio = ide_get_best_pio_mode(drive, pio, 4, &d); switch (pmif->kind) { - case controller_un_ata6: { + case controller_un_ata6: + case controller_k2_ata6: { /* 100Mhz cell */ u32 tr = kauai_lookup_timing(kauai_pio_timings, d.cycle_time); if (tr == 0) @@ -685,6 +733,10 @@ pmac_ide_tuneproc(ide_drive_t *drive, u8 pio) } #ifdef CONFIG_BLK_DEV_IDEDMA_PMAC + +/* + * Calculate KeyLargo ATA/66 UDMA timings + */ static int __pmac set_timings_udma_ata4(u32 *timings, u8 speed) { @@ -710,6 +762,9 @@ set_timings_udma_ata4(u32 *timings, u8 speed) return 0; } +/* + * Calculate Kauai ATA/100 UDMA timings + */ static int __pmac set_timings_udma_ata6(u32 *pio_timings, u32 *ultra_timings, u8 speed) { @@ -727,6 +782,9 @@ set_timings_udma_ata6(u32 *pio_timings, u32 *ultra_timings, u8 speed) return 0; } +/* + * Calculate MDMA timings for all cells + */ static int __pmac set_timings_mdma(ide_drive_t *drive, int intf_type, u32 *timings, u32 *timings2, u8 speed, int drive_cycle_time) @@ -753,6 +811,7 @@ set_timings_mdma(ide_drive_t *drive, int intf_type, u32 *timings, u32 *timings2, /* Get the proper timing array for this controller */ switch(intf_type) { case controller_un_ata6: + case controller_k2_ata6: break; case controller_kl_ata4: tm = mdma_timings_66; @@ -784,7 +843,8 @@ set_timings_mdma(ide_drive_t *drive, int intf_type, u32 *timings, u32 *timings2, #endif } switch(intf_type) { - case controller_un_ata6: { + case controller_un_ata6: + case controller_k2_ata6: { /* 100Mhz cell */ u32 tr = kauai_lookup_timing(kauai_mdma_timings, cycleTime); if (tr == 0) @@ -854,8 +914,12 @@ set_timings_mdma(ide_drive_t *drive, int intf_type, u32 *timings, u32 *timings2, } #endif /* #ifdef CONFIG_BLK_DEV_IDEDMA_PMAC */ -/* You may notice we don't use this function on normal operation, - * our, normal mdma function is supposed to be more precise +/* + * Speedproc. This function is called by the core to set any of the standard + * timing (PIO, MDMA or UDMA) to both the drive and the controller. + * You may notice we don't use this function on normal "dma check" operation, + * our dedicated function is more precise as it uses the drive provided + * cycle time value. We should probably fix this one to deal with that too... */ static int __pmac pmac_ide_tune_chipset (ide_drive_t *drive, byte speed) @@ -874,7 +938,8 @@ pmac_ide_tune_chipset (ide_drive_t *drive, byte speed) switch(speed) { #ifdef CONFIG_BLK_DEV_IDEDMA_PMAC case XFER_UDMA_5: - if (pmif->kind != controller_un_ata6) + if (pmif->kind != controller_un_ata6 && + pmif->kind != controller_k2_ata6) return 1; case XFER_UDMA_4: case XFER_UDMA_3: @@ -885,7 +950,8 @@ pmac_ide_tune_chipset (ide_drive_t *drive, byte speed) case XFER_UDMA_0: if (pmif->kind == controller_kl_ata4) ret = set_timings_udma_ata4(timings, speed); - else if (pmif->kind == controller_un_ata6) + else if (pmif->kind == controller_un_ata6 + || pmif->kind == controller_k2_ata6) ret = set_timings_udma_ata6(timings, timings2, speed); else ret = 1; @@ -923,6 +989,10 @@ pmac_ide_tune_chipset (ide_drive_t *drive, byte speed) return 0; } +/* + * Blast some well known "safe" values to the timing registers at init or + * wakeup from sleep time, before we do real calculation + */ static void __pmac sanitize_timings(pmac_ide_hwif_t *pmif) { @@ -930,6 +1000,7 @@ sanitize_timings(pmac_ide_hwif_t *pmif) switch(pmif->kind) { case controller_un_ata6: + case controller_k2_ata6: value = 0x08618a92; value2 = 0x00002921; break; @@ -1052,9 +1123,11 @@ pmac_ide_do_resume(ide_hwif_t *hwif) if (!pmif->mediabay) { ppc_md.feature_call(PMAC_FTR_IDE_RESET, pmif->node, pmif->aapl_bus_id, 1); ppc_md.feature_call(PMAC_FTR_IDE_ENABLE, pmif->node, pmif->aapl_bus_id, 1); - mdelay(10); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/100); ppc_md.feature_call(PMAC_FTR_IDE_RESET, pmif->node, pmif->aapl_bus_id, 0); - mdelay(100); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(IDE_WAKEUP_DELAY); } /* Sanitize drive timings */ @@ -1063,6 +1136,13 @@ pmac_ide_do_resume(ide_hwif_t *hwif) return 0; } +/* + * Setup, register & probe an IDE channel driven by this driver, this is + * called by one of the 2 probe functions (macio or PCI). Note that a channel + * that ends up beeing free of any device is not kept around by this driver + * (it is kept in 2.4). This introduce an interface numbering change on some + * rare machines unfortunately, but it's better this way. + */ static int pmac_ide_setup_device(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif) { @@ -1073,6 +1153,8 @@ pmac_ide_setup_device(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif) pmif->broken_dma = pmif->broken_dma_warn = 0; if (device_is_compatible(np, "kauai-ata")) pmif->kind = controller_un_ata6; + else if (device_is_compatible(np, "K2-UATA")) + pmif->kind = controller_k2_ata6; else if (device_is_compatible(np, "keylargo-ata")) { if (strcmp(np->name, "ata-4") == 0) pmif->kind = controller_kl_ata4; @@ -1089,7 +1171,8 @@ pmac_ide_setup_device(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif) pmif->aapl_bus_id = bidp ? *bidp : 0; /* Get cable type from device-tree */ - if (pmif->kind == controller_kl_ata4 || pmif->kind == controller_un_ata6) { + if (pmif->kind == controller_kl_ata4 || pmif->kind == controller_un_ata6 + || pmif->kind == controller_k2_ata6) { char* cable = get_property(np, "cable-type", NULL); if (cable && !strncmp(cable, "80-", 3)) pmif->cable_80 = 1; @@ -1119,13 +1202,16 @@ pmac_ide_setup_device(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif) /* This is necessary to enable IDE when net-booting */ ppc_md.feature_call(PMAC_FTR_IDE_RESET, np, pmif->aapl_bus_id, 1); ppc_md.feature_call(PMAC_FTR_IDE_ENABLE, np, pmif->aapl_bus_id, 1); - mdelay(10); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/100); ppc_md.feature_call(PMAC_FTR_IDE_RESET, np, pmif->aapl_bus_id, 0); - mdelay(100); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(IDE_WAKEUP_DELAY); } /* Setup MMIO ops */ default_hwif_mmiops(hwif); + hwif->OUTBSYNC = pmac_outbsync; /* Tell common code _not_ to mess with resources */ hwif->mmio = 2; @@ -1139,7 +1225,7 @@ pmac_ide_setup_device(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif) hwif->drives[0].unmask = 1; hwif->drives[1].unmask = 1; hwif->tuneproc = pmac_ide_tuneproc; - if (pmif->kind == controller_un_ata6) + if (pmif->kind == controller_un_ata6 || pmif->kind == controller_k2_ata6) hwif->selectproc = pmac_ide_kauai_selectproc; else hwif->selectproc = pmac_ide_selectproc; @@ -1187,6 +1273,9 @@ pmac_ide_setup_device(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif) return 0; } +/* + * Attach to a macio probed interface + */ static int __devinit pmac_ide_macio_attach(struct macio_dev *mdev, const struct of_match *match) { @@ -1215,17 +1304,8 @@ pmac_ide_macio_attach(struct macio_dev *mdev, const struct of_match *match) return -ENXIO; } - /* - * Some older OFs have bogus sizes, causing request_OF_resource - * to fail. We fix them up here - */ - if (mdev->ofdev.node->addrs[0].size > 0x1000) - mdev->ofdev.node->addrs[0].size = 0x1000; - if (mdev->ofdev.node->n_addrs > 1 && mdev->ofdev.node->addrs[1].size > 0x100) - mdev->ofdev.node->addrs[1].size = 0x100; - /* Request memory resource for IO ports */ - if (request_OF_resource(mdev->ofdev.node, 0, " (mac-io ata ports)") == NULL) { + if (macio_request_resource(mdev, 0, "ide-pmac (ports)")) { printk(KERN_ERR "ide%d: can't request mmio resource !\n", i); return -EBUSY; } @@ -1235,14 +1315,14 @@ pmac_ide_macio_attach(struct macio_dev *mdev, const struct of_match *match) * fixes in irq.c. That works well enough for the single case * where that happens though... */ - if (mdev->ofdev.node->n_intrs == 0) { + if (macio_irq_count(mdev) == 0) { printk(KERN_WARNING "ide%d: no intrs for device %s, using 13\n", i, mdev->ofdev.node->full_name); irq = 13; } else - irq = mdev->ofdev.node->intrs[0].line; + irq = macio_irq(mdev, 0); - base = (unsigned long) ioremap(mdev->ofdev.node->addrs[0].address, 0x400); + base = (unsigned long)ioremap(macio_resource_start(mdev, 0), 0x400); regbase = base; hwif->pci_dev = mdev->bus->pdev; @@ -1253,10 +1333,13 @@ pmac_ide_macio_attach(struct macio_dev *mdev, const struct of_match *match) pmif->regbase = regbase; pmif->irq = irq; #ifdef CONFIG_BLK_DEV_IDEDMA_PMAC - if (mdev->ofdev.node->n_addrs >= 2) - pmif->dma_regs = (volatile struct dbdma_regs*) - ioremap(mdev->ofdev.node->addrs[1].address, 0x1000); - else + if (macio_resource_count(mdev) >= 2) { + if (macio_request_resource(mdev, 1, "ide-pmac (dma)")) + printk(KERN_WARNING "ide%d: can't request DMA resource !\n", i); + else + pmif->dma_regs = (volatile struct dbdma_regs*) + ioremap(macio_resource_start(mdev, 1), 0x1000); + } else pmif->dma_regs = NULL; #endif /* CONFIG_BLK_DEV_IDEDMA_PMAC */ dev_set_drvdata(&mdev->ofdev.dev, hwif); @@ -1269,7 +1352,9 @@ pmac_ide_macio_attach(struct macio_dev *mdev, const struct of_match *match) if (pmif->dma_regs) iounmap((void *)pmif->dma_regs); memset(pmif, 0, sizeof(*pmif)); - release_OF_resource(mdev->ofdev.node, 0); + macio_release_resource(mdev, 0); + if (pmif->dma_regs) + macio_release_resource(mdev, 1); } return rc; @@ -1305,6 +1390,9 @@ pmac_ide_macio_resume(struct macio_dev *mdev) return rc; } +/* + * Attach to a PCI probed interface + */ static int __devinit pmac_ide_pci_attach(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -1439,8 +1527,10 @@ static struct macio_driver pmac_ide_macio_driver = .resume = pmac_ide_macio_resume, }; -static struct pci_device_id pmac_ide_pci_match[] __devinitdata = { - { PCI_VENDOR_ID_APPLE, PCI_DEVICE_ID_APPLE_KAUAI_ATA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, +static struct pci_device_id pmac_ide_pci_match[] = { + { PCI_VENDOR_ID_APPLE, PCI_DEVIEC_ID_APPLE_UNI_N_ATA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { PCI_VENDOR_ID_APPLE, PCI_DEVICE_ID_APPLE_IPID_ATA100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { PCI_VENDOR_ID_APPLE, PCI_DEVICE_ID_APPLE_K2_ATA100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, }; static struct pci_driver pmac_ide_pci_driver = { @@ -1468,6 +1558,11 @@ pmac_ide_probe(void) #ifdef CONFIG_BLK_DEV_IDEDMA_PMAC +/* + * This is very close to the generic ide-dma version of the function except + * that we don't use the fields in the hwif but our own copies for sg_table + * and friends. We build & map the sglist for a given request + */ static int __pmac pmac_ide_build_sglist(ide_drive_t *drive, struct request *rq) { @@ -1489,6 +1584,9 @@ pmac_ide_build_sglist(ide_drive_t *drive, struct request *rq) return pci_map_sg(hwif->pci_dev, sg, nents, pmif->sg_dma_direction); } +/* + * Same as above but for a "raw" taskfile request + */ static int __pmac pmac_ide_raw_build_sglist(ide_drive_t *drive, struct request *rq) { @@ -1630,7 +1728,9 @@ pmac_ide_destroy_dmatable (ide_drive_t *drive) } } -/* Calculate MultiWord DMA timings */ +/* + * Pick up best MDMA timing for the drive and apply it + */ static int __pmac pmac_ide_mdma_enable(ide_drive_t *drive, u16 mode) { @@ -1685,7 +1785,9 @@ pmac_ide_mdma_enable(ide_drive_t *drive, u16 mode) return 1; } -/* Calculate Ultra DMA timings */ +/* + * Pick up best UDMA timing for the drive and apply it + */ static int __pmac pmac_ide_udma_enable(ide_drive_t *drive, u16 mode) { @@ -1704,7 +1806,7 @@ pmac_ide_udma_enable(ide_drive_t *drive, u16 mode) timing_local[1] = *timings2; /* Calculate timings for interface */ - if (pmif->kind == controller_un_ata6) + if (pmif->kind == controller_un_ata6 || pmif->kind == controller_k2_ata6) ret = set_timings_udma_ata6( &timing_local[0], &timing_local[1], mode); @@ -1733,6 +1835,10 @@ pmac_ide_udma_enable(ide_drive_t *drive, u16 mode) return 1; } +/* + * Check what is the best DMA timing setting for the drive and + * call appropriate functions to apply it. + */ static int __pmac pmac_ide_dma_check(ide_drive_t *drive) { @@ -1754,11 +1860,13 @@ pmac_ide_dma_check(ide_drive_t *drive) short mode; map = XFER_MWDMA; - if (pmif->kind == controller_kl_ata4 || pmif->kind == controller_un_ata6) { + if (pmif->kind == controller_kl_ata4 || pmif->kind == controller_un_ata6 + || pmif->kind == controller_k2_ata6) { map |= XFER_UDMA; if (pmif->cable_80) { map |= XFER_UDMA_66; - if (pmif->kind == controller_un_ata6) + if (pmif->kind == controller_un_ata6 || + pmif->kind == controller_k2_ata6) map |= XFER_UDMA_100; } } @@ -1774,6 +1882,10 @@ pmac_ide_dma_check(ide_drive_t *drive) return 0; } +/* + * Prepare a DMA transfer. We build the DMA table, adjust the timings for + * a read on KeyLargo ATA/66 and mark us as waiting for DMA completion + */ static int __pmac pmac_ide_dma_start(ide_drive_t *drive, int reading) { @@ -1802,6 +1914,9 @@ pmac_ide_dma_start(ide_drive_t *drive, int reading) return 0; } +/* + * Start a DMA READ command + */ static int __pmac pmac_ide_dma_read(ide_drive_t *drive) { @@ -1831,6 +1946,9 @@ pmac_ide_dma_read(ide_drive_t *drive) return pmac_ide_dma_begin(drive); } +/* + * Start a DMA WRITE command + */ static int __pmac pmac_ide_dma_write (ide_drive_t *drive) { @@ -1865,6 +1983,10 @@ pmac_ide_dma_count (ide_drive_t *drive) return HWIF(drive)->ide_dma_begin(drive); } +/* + * Kick the DMA controller into life after the DMA command has been issued + * to the drive. + */ static int __pmac pmac_ide_dma_begin (ide_drive_t *drive) { @@ -1881,6 +2003,9 @@ pmac_ide_dma_begin (ide_drive_t *drive) return 0; } +/* + * After a DMA transfer, make sure the controller is stopped + */ static int __pmac pmac_ide_dma_end (ide_drive_t *drive) { @@ -1900,6 +2025,12 @@ pmac_ide_dma_end (ide_drive_t *drive) return (dstat & (RUN|DEAD|ACTIVE)) != RUN; } +/* + * Check out that the interrupt we got was for us. We can't always know this + * for sure with those Apple interfaces (well, we could on the recent ones but + * that's not implemented yet), on the other hand, we don't have shared interrupts + * so it's not really a problem + */ static int __pmac pmac_ide_dma_test_irq (ide_drive_t *drive) { @@ -1982,6 +2113,10 @@ pmac_ide_dma_lostirq (ide_drive_t *drive) return 0; } +/* + * Allocate the data structures needed for using DMA with an interface + * and fill the proper list of functions pointers + */ static void __init pmac_ide_setup_dma(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif) { @@ -2041,6 +2176,7 @@ pmac_ide_setup_dma(pmac_ide_hwif_t *pmif, ide_hwif_t *hwif) hwif->atapi_dma = 1; switch(pmif->kind) { case controller_un_ata6: + case controller_k2_ata6: hwif->ultra_mask = pmif->cable_80 ? 0x3f : 0x07; hwif->mwdma_mask = 0x07; hwif->swdma_mask = 0x00; diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig new file mode 100644 index 000000000000..6501e3a9ede2 --- /dev/null +++ b/drivers/macintosh/Kconfig @@ -0,0 +1,156 @@ + +menu "Macintosh device drivers" + +# we want to change this to something like CONFIG_SYSCTRL_CUDA/PMU +config ADB_CUDA + bool "Support for CUDA based PowerMacs" + depends on PPC_PMAC && !POWER4 + help + This provides support for CUDA based Power Macintosh systems. This + includes most OldWorld PowerMacs, the first generation iMacs, the + Blue&White G3 and the "Yikes" G4 (PCI Graphics). All later models + should use CONFIG_ADB_PMU instead. It is safe to say Y here even if + your machine doesn't have a CUDA. + + If unsure say Y. + +config ADB_PMU + bool "Support for PMU based PowerMacs" + depends on PPC_PMAC + help + On PowerBooks, iBooks, and recent iMacs and Power Macintoshes, the + PMU is an embedded microprocessor whose primary function is to + control system power, and battery charging on the portable models. + The PMU also controls the ADB (Apple Desktop Bus) which connects to + the keyboard and mouse on some machines, as well as the non-volatile + RAM and the RTC (real time clock) chip. Say Y to enable support for + this device; you should do so if your machine is one of those + mentioned above. + +config PMAC_PBOOK + bool "Power management support for PowerBooks" + depends on ADB_PMU + ---help--- + This provides support for putting a PowerBook to sleep; it also + enables media bay support. Power management works on the + PB2400/3400/3500, Wallstreet, Lombard, and Bronze PowerBook G3 and + the Titanium Powerbook G4, as well as the iBooks. You should get + the power management daemon, pmud, to make it work and you must have + the /dev/pmu device (see the pmud README). + + Get pmud from <ftp://ftp.samba.org/pub/ppclinux/pmud/>. + + If you have a PowerBook, you should say Y here. + + You may also want to compile the dma sound driver as a module and + have it autoloaded. The act of removing the module shuts down the + sound hardware for more power savings. + +config PM + bool + depends on PPC_PMAC && ADB_PMU && PMAC_PBOOK + default y + +config PMAC_APM_EMU + tristate "APM emulation" + depends on PMAC_PBOOK + +# made a separate option since backlight may end up beeing used +# on non-powerbook machines (but only on PMU based ones AFAIK) +config PMAC_BACKLIGHT + bool "Backlight control for LCD screens" + depends on ADB_PMU + help + Say Y here to build in code to manage the LCD backlight on a + Macintosh PowerBook. With this code, the backlight will be turned + on and off appropriately on power-management and lid-open/lid-closed + events; also, the PowerBook button device will be enabled so you can + change the screen brightness. + +config MAC_FLOPPY + bool "Support for PowerMac floppy" + depends on PPC_PMAC && !POWER4 + help + If you have a SWIM-3 (Super Woz Integrated Machine 3; from Apple) + floppy controller, say Y here. Most commonly found in PowerMacs. + +config MAC_SERIAL + tristate "Support for PowerMac serial ports (OBSOLETE DRIVER)" + depends on PPC_PMAC + help + This driver is obsolete. Use CONFIG_SERIAL_PMACZILOG in + "Character devices --> Serial drivers --> PowerMac z85c30" option. + +config ADB + bool "Apple Desktop Bus (ADB) support" + depends on PPC_PMAC + help + Apple Desktop Bus (ADB) support is for support of devices which + are connected to an ADB port. ADB devices tend to have 4 pins. + If you have an Apple Macintosh prior to the iMac, an iBook or + PowerBook, or a "Blue and White G3", you probably want to say Y + here. Otherwise say N. + +config ADB_MACIO + bool "Include MacIO (CHRP) ADB driver" + depends on ADB && !POWER4 + help + Say Y here to include direct support for the ADB controller in the + Hydra chip used on PowerPC Macintoshes of the CHRP type. (The Hydra + also includes a MESH II SCSI controller, DBDMA controller, VIA chip, + OpenPIC controller and two RS422/Geoports.) + +config INPUT_ADBHID + bool "Support for ADB input devices (keyboard, mice, ...)" + depends on ADB && INPUT=y + help + Say Y here if you want to have ADB (Apple Desktop Bus) HID devices + such as keyboards, mice, joysticks, trackpads or graphic tablets + handled by the input layer. If you say Y here, make sure to say Y to + the corresponding drivers "Keyboard support" (CONFIG_INPUT_KEYBDEV), + "Mouse Support" (CONFIG_INPUT_MOUSEDEV) and "Event interface + support" (CONFIG_INPUT_EVDEV) as well. + + If unsure, say Y. + +config MAC_EMUMOUSEBTN + bool "Support for mouse button 2+3 emulation" + depends on INPUT_ADBHID + help + This provides generic support for emulating the 2nd and 3rd mouse + button with keypresses. If you say Y here, the emulation is still + disabled by default. The emulation is controlled by these sysctl + entries: + /proc/sys/dev/mac_hid/mouse_button_emulation + /proc/sys/dev/mac_hid/mouse_button2_keycode + /proc/sys/dev/mac_hid/mouse_button3_keycode + + If you have an Apple machine with a 1-button mouse, say Y here. + +config THERM_WINDTUNNEL + tristate "Support for thermal management on Windtunnel G4s" + depends on I2C && I2C_KEYWEST && !POWER4 + help + This driver provides some thermostat and fan control for the desktop + G4 "Windtunnel" + +config THERM_ADT7467 + tristate "Support for thermal mgmnt on laptops with ADT 7467 chipset" + depends on I2C && I2C_KEYWEST && !POWER4 + help + This driver provides some thermostat and fan control for the + iBook G4, and the ATI based aluminium PowerBooks, allowing slighlty + better fan behaviour by default, and some manual control. + +config THERM_PM72 + tristate "Support for thermal management on PowerMac G5" + depends on I2C && I2C_KEYWEST && POWER4 + help + This driver provides thermostat and fan control for the desktop + G5 machines. + +config ANSLCD + bool "Support for ANS LCD display" + depends on ADB_CUDA + +endmenu diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile index da5aadbd07da..7cecab08df21 100644 --- a/drivers/macintosh/Makefile +++ b/drivers/macintosh/Makefile @@ -8,9 +8,6 @@ obj-$(CONFIG_PPC_PMAC) += macio_asic.o obj-$(CONFIG_PMAC_PBOOK) += mediabay.o obj-$(CONFIG_MAC_SERIAL) += macserial.o -ifneq ($(CONFIG_MAC),y) - obj-$(CONFIG_NVRAM) += nvram.o -endif obj-$(CONFIG_MAC_EMUMOUSEBTN) += mac_hid.o obj-$(CONFIG_INPUT_ADBHID) += adbhid.o obj-$(CONFIG_ANSLCD) += ans-lcd.o @@ -25,3 +22,7 @@ obj-$(CONFIG_ADB_MACIISI) += via-maciisi.o obj-$(CONFIG_ADB_IOP) += adb-iop.o obj-$(CONFIG_ADB_PMU68K) += via-pmu68k.o obj-$(CONFIG_ADB_MACIO) += macio-adb.o + +obj-$(CONFIG_THERM_PM72) += therm_pm72.o +obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o +obj-$(CONFIG_THERM_ADT7467) += therm_adt7467.o diff --git a/drivers/macintosh/adb.c b/drivers/macintosh/adb.c index a20a9e899ef4..f1d34776308e 100644 --- a/drivers/macintosh/adb.c +++ b/drivers/macintosh/adb.c @@ -83,6 +83,7 @@ static pid_t adb_probe_task_pid; static DECLARE_MUTEX(adb_probe_mutex); static struct completion adb_probe_task_comp; static int sleepy_trackpad; +static int autopoll_devs; int __adb_probe_sync; #ifdef CONFIG_PMAC_PBOOK @@ -379,7 +380,7 @@ adb_notify_sleep(struct pmu_sleep_notifier *self, int when) static int do_adb_reset_bus(void) { - int ret, nret, devs; + int ret, nret; if (adb_controller == NULL) return -ENXIO; @@ -390,7 +391,7 @@ do_adb_reset_bus(void) nret = notifier_call_chain(&adb_client_list, ADB_MSG_PRE_RESET, NULL); if (nret & NOTIFY_STOP_MASK) { if (adb_controller->autopoll) - adb_controller->autopoll(devs); + adb_controller->autopoll(autopoll_devs); return -EBUSY; } @@ -416,9 +417,9 @@ do_adb_reset_bus(void) } if (!ret) { - devs = adb_scan_bus(); + autopoll_devs = adb_scan_bus(); if (adb_controller->autopoll) - adb_controller->autopoll(devs); + adb_controller->autopoll(autopoll_devs); } up(&adb_handler_sem); diff --git a/drivers/macintosh/adbhid.c b/drivers/macintosh/adbhid.c index 01f95a2687d3..4fa4065f18cc 100644 --- a/drivers/macintosh/adbhid.c +++ b/drivers/macintosh/adbhid.c @@ -30,6 +30,8 @@ * To do: * * Improve Kensington support. + * Split mouse/kbd + * Move to syfs */ #include <linux/config.h> @@ -63,6 +65,15 @@ static struct notifier_block adbhid_adb_notifier = { .notifier_call = adb_message_handler, }; +/* Some special keys */ +#define ADB_KEY_DEL 0x33 +#define ADB_KEY_CMD 0x37 +#define ADB_KEY_CAPSLOCK 0x39 +#define ADB_KEY_FN 0x3f +#define ADB_KEY_FWDEL 0x75 +#define ADB_KEY_POWER_OLD 0x7e +#define ADB_KEY_POWER 0x7f + unsigned char adb_to_linux_keycodes[128] = { 30, 31, 32, 33, 35, 34, 44, 45, 46, 47, 86, 48, 16, 17, 18, 19, 21, 20, 2, 3, 4, 5, 7, 6, 13, 10, 8, 12, 9, 11, 27, 24, @@ -84,8 +95,13 @@ struct adbhid { unsigned char *keycode; char name[64]; char phys[32]; + int flags; }; +#define FLAG_FN_KEY_PRESSED 0x00000001 +#define FLAG_POWER_FROM_FN 0x00000002 +#define FLAG_EMU_FWDEL_DOWN 0x00000004 + static struct adbhid *adbhid[16] = { 0 }; static void adbhid_probe(void); @@ -148,28 +164,64 @@ adbhid_keyboard_input(unsigned char *data, int nb, struct pt_regs *regs, int apo static void adbhid_input_keycode(int id, int keycode, int repeat, struct pt_regs *regs) { + struct adbhid *ahid = adbhid[id]; int up_flag; up_flag = (keycode & 0x80); keycode &= 0x7f; switch (keycode) { - case 0x39: /* Generate down/up events for CapsLock everytime. */ - input_regs(&adbhid[id]->input, regs); - input_report_key(&adbhid[id]->input, KEY_CAPSLOCK, 1); - input_report_key(&adbhid[id]->input, KEY_CAPSLOCK, 0); - input_sync(&adbhid[id]->input); - return; - case 0x3f: /* ignore Powerbook Fn key */ + case ADB_KEY_CAPSLOCK: /* Generate down/up events for CapsLock everytime. */ + input_regs(&ahid->input, regs); + input_report_key(&ahid->input, KEY_CAPSLOCK, 1); + input_report_key(&ahid->input, KEY_CAPSLOCK, 0); + input_sync(&ahid->input); return; #ifdef CONFIG_PPC_PMAC - case 0x7e: /* Power key on PBook 3400 needs remapping */ + case ADB_KEY_POWER_OLD: /* Power key on PBook 3400 needs remapping */ switch(pmac_call_feature(PMAC_FTR_GET_MB_INFO, NULL, PMAC_MB_INFO_MODEL, 0)) { case PMAC_TYPE_COMET: case PMAC_TYPE_HOOPER: case PMAC_TYPE_KANGA: - keycode = 0x7f; + keycode = ADB_KEY_POWER; + } + break; + case ADB_KEY_POWER: + /* Fn + Command will produce a bogus "power" keycode */ + if (ahid->flags & FLAG_FN_KEY_PRESSED) { + keycode = ADB_KEY_CMD; + if (up_flag) + ahid->flags &= ~FLAG_POWER_FROM_FN; + else + ahid->flags |= FLAG_POWER_FROM_FN; + } else if (ahid->flags & FLAG_POWER_FROM_FN) { + keycode = ADB_KEY_CMD; + ahid->flags &= ~FLAG_POWER_FROM_FN; + } + break; + case ADB_KEY_FN: + /* Keep track of the Fn key state */ + if (up_flag) { + ahid->flags &= ~FLAG_FN_KEY_PRESSED; + /* Emulate Fn+delete = forward delete */ + if (ahid->flags & FLAG_EMU_FWDEL_DOWN) { + ahid->flags &= ~FLAG_EMU_FWDEL_DOWN; + keycode = ADB_KEY_FWDEL; + break; + } + } else + ahid->flags |= FLAG_FN_KEY_PRESSED; + /* Swallow the key press */ + return; + case ADB_KEY_DEL: + /* Emulate Fn+delete = forward delete */ + if (ahid->flags & FLAG_FN_KEY_PRESSED) { + keycode = ADB_KEY_FWDEL; + if (up_flag) + ahid->flags &= ~FLAG_EMU_FWDEL_DOWN; + else + ahid->flags |= FLAG_EMU_FWDEL_DOWN; } break; #endif /* CONFIG_PPC_PMAC */ @@ -500,6 +552,7 @@ adbhid_input_register(int id, int default_id, int original_handler_id, adbhid[id]->original_handler_id = original_handler_id; adbhid[id]->current_handler_id = current_handler_id; adbhid[id]->mouse_kind = mouse_kind; + adbhid[id]->flags = 0; adbhid[id]->input.private = adbhid[id]; adbhid[id]->input.name = adbhid[id]->name; adbhid[id]->input.phys = adbhid[id]->phys; diff --git a/drivers/macintosh/macio_asic.c b/drivers/macintosh/macio_asic.c index c8d7d791aaec..a35d44c77f30 100644 --- a/drivers/macintosh/macio_asic.c +++ b/drivers/macintosh/macio_asic.c @@ -23,10 +23,13 @@ #include <asm/prom.h> #include <asm/pci-bridge.h> +#define DEBUG + +#define MAX_NODE_NAME_SIZE (BUS_ID_SIZE - 12) + static struct macio_chip *macio_on_hold; -static int -macio_bus_match(struct device *dev, struct device_driver *drv) +static int macio_bus_match(struct device *dev, struct device_driver *drv) { struct macio_dev * macio_dev = to_macio_device(dev); struct macio_driver * macio_drv = to_macio_driver(drv); @@ -85,41 +88,42 @@ static int macio_device_probe(struct device *dev) static int macio_device_remove(struct device *dev) { struct macio_dev * macio_dev = to_macio_device(dev); - struct macio_driver * drv = to_macio_driver(macio_dev->ofdev.dev.driver); + struct macio_driver * drv = to_macio_driver(dev->driver); - if (drv && drv->remove) + if (dev->driver && drv->remove) drv->remove(macio_dev); macio_dev_put(macio_dev); return 0; } +static void macio_device_shutdown(struct device *dev) +{ + struct macio_dev * macio_dev = to_macio_device(dev); + struct macio_driver * drv = to_macio_driver(dev->driver); + + if (dev->driver && drv->shutdown) + drv->shutdown(macio_dev); +} + static int macio_device_suspend(struct device *dev, u32 state) { struct macio_dev * macio_dev = to_macio_device(dev); - struct macio_driver * drv; - int error = 0; + struct macio_driver * drv = to_macio_driver(dev->driver); - if (macio_dev->ofdev.dev.driver == NULL) - return 0; - drv = to_macio_driver(macio_dev->ofdev.dev.driver); - if (drv->suspend) - error = drv->suspend(macio_dev, state); - return error; + if (dev->driver && drv->suspend) + return drv->suspend(macio_dev, state); + return 0; } static int macio_device_resume(struct device * dev) { struct macio_dev * macio_dev = to_macio_device(dev); - struct macio_driver * drv; - int error = 0; + struct macio_driver * drv = to_macio_driver(dev->driver); - if (macio_dev->ofdev.dev.driver == NULL) - return 0; - drv = to_macio_driver(macio_dev->ofdev.dev.driver); - if (drv->resume) - error = drv->resume(macio_dev); - return error; + if (dev->driver && drv->resume) + return drv->resume(macio_dev); + return 0; } struct bus_type macio_bus_type = { @@ -129,8 +133,7 @@ struct bus_type macio_bus_type = { .resume = macio_device_resume, }; -static int __init -macio_bus_driver_init(void) +static int __init macio_bus_driver_init(void) { return bus_register(&macio_bus_type); } @@ -155,6 +158,58 @@ static void macio_release_dev(struct device *dev) } /** + * macio_resource_quirks - tweak or skip some resources for a device + * @np: pointer to the device node + * @res: resulting resource + * @index: index of resource in node + * + * If this routine returns non-null, then the resource is completely + * skipped. + */ +static int macio_resource_quirks(struct device_node *np, struct resource *res, int index) +{ + if (res->flags & IORESOURCE_MEM) { + /* Grand Central has too large resource 0 on some machines */ + if (index == 0 && !strcmp(np->name, "gc")) { + np->addrs[0].size = 0x20000; + res->end = res->start + 0x1ffff; + } + /* Airport has bogus resource 2 */ + if (index >= 2 && !strcmp(np->name, "radio")) + return 1; + /* DBDMAs may have bogus sizes */ + if ((res->start & 0x0001f000) == 0x00008000) { + np->addrs[index].size = 0x100; + res->end = res->start + 0xff; + } + /* ESCC parent eats child resources. We could have added a level of hierarchy, + * but I don't really feel the need for it */ + if (!strcmp(np->name, "escc")) + return 1; + /* ESCC has bogus resources >= 3 */ + if (index >= 3 && !(strcmp(np->name, "ch-a") && strcmp(np->name, "ch-b"))) + return 1; + /* Media bay has too many resources, keep only first one */ + if (index > 0 && !strcmp(np->name, "media-bay")) + return 1; + /* Some older IDE resources have bogus sizes */ + if (!(strcmp(np->name, "IDE") && strcmp(np->name, "ATA") && + strcmp(np->type, "ide") && strcmp(np->type, "ata"))) { + if (index == 0 && np->addrs[0].size > 0x1000) { + np->addrs[0].size = 0x1000; + res->end = res->start + 0xfff; + } + if (index == 1 && np->addrs[1].size > 0x100) { + np->addrs[1].size = 0x100; + res->end = res->start + 0xff; + } + } + } + return 0; +} + + +/** * macio_add_one_device - Add one device from OF node to the device tree * @chip: pointer to the macio_chip holding the device * @np: pointer to the device node in the OF tree @@ -164,9 +219,11 @@ static void macio_release_dev(struct device *dev) * be exposed to the bay driver some way... */ static struct macio_dev * macio_add_one_device(struct macio_chip *chip, struct device *parent, - struct device_node *np, struct macio_dev *in_bay) + struct device_node *np, struct macio_dev *in_bay, + struct resource *parent_res) { struct macio_dev *dev; + int i, j; u32 *reg; if (np == NULL) @@ -186,22 +243,76 @@ static struct macio_dev * macio_add_one_device(struct macio_chip *chip, struct d dev->ofdev.dev.bus = &macio_bus_type; dev->ofdev.dev.release = macio_release_dev; +#ifdef DEBUG + printk("preparing mdev @%p, ofdev @%p, dev @%p, kobj @%p\n", + dev, &dev->ofdev, &dev->ofdev.dev, &dev->ofdev.dev.kobj); +#endif + /* MacIO itself has a different reg, we use it's PCI base */ if (np == chip->of_node) { - sprintf(dev->ofdev.dev.bus_id, "%1d.%08lx:%.8s", chip->lbus.index, + sprintf(dev->ofdev.dev.bus_id, "%1d.%08lx:%.*s", chip->lbus.index, #ifdef CONFIG_PCI pci_resource_start(chip->lbus.pdev, 0), #else 0, /* NuBus may want to do something better here */ #endif - np->name); + MAX_NODE_NAME_SIZE, np->name); } else { reg = (u32 *)get_property(np, "reg", NULL); - sprintf(dev->ofdev.dev.bus_id, "%1d.%08x:%.8s", chip->lbus.index, - reg ? *reg : 0, np->name); + sprintf(dev->ofdev.dev.bus_id, "%1d.%08x:%.*s", chip->lbus.index, + reg ? *reg : 0, MAX_NODE_NAME_SIZE, np->name); } + /* For now, we use pre-parsed entries in the device-tree for + * interrupt routing and addresses, but we should change that + * to dynamically parsed entries and so get rid of most of the + * clutter in struct device_node + */ + for (i = j = 0; i < np->n_intrs; i++) { + struct resource *res = &dev->interrupt[j]; + + if (j >= MACIO_DEV_COUNT_IRQS) + break; + res->start = np->intrs[i].line; + res->flags = IORESOURCE_IO; + if (np->intrs[j].sense) + res->flags |= IORESOURCE_IRQ_LOWLEVEL; + else + res->flags |= IORESOURCE_IRQ_HIGHEDGE; + res->name = dev->ofdev.dev.bus_id; + if (macio_resource_quirks(np, res, i)) + memset(res, 0, sizeof(struct resource)); + else + j++; + } + dev->n_interrupts = j; + for (i = j = 0; i < np->n_addrs; i++) { + struct resource *res = &dev->resource[j]; + + if (j >= MACIO_DEV_COUNT_RESOURCES) + break; + res->start = np->addrs[i].address; + res->end = np->addrs[i].address + np->addrs[i].size - 1; + res->flags = IORESOURCE_MEM; + res->name = dev->ofdev.dev.bus_id; + if (macio_resource_quirks(np, res, i)) + memset(res, 0, sizeof(struct resource)); + else { + j++; + /* Currently, we consider failure as harmless, this may + * change in the future, once I've found all the device + * tree bugs in older machines & worked around them + */ + if (insert_resource(parent_res, res)) + printk(KERN_WARNING "Can't request resource %d for MacIO" + " device %s\n", i, dev->ofdev.dev.bus_id); + } + } + dev->n_resources = j; + if (of_device_register(&dev->ofdev) != 0) { + printk(KERN_DEBUG"macio: device registration error for %s!\n", + dev->ofdev.dev.bus_id); kfree(dev); return NULL; } @@ -234,25 +345,30 @@ static void macio_pci_add_devices(struct macio_chip *chip) struct device_node *np, *pnode; struct macio_dev *rdev, *mdev, *mbdev = NULL, *sdev = NULL; struct device *parent = NULL; + struct resource *root_res = &iomem_resource; /* Add a node for the macio bus itself */ #ifdef CONFIG_PCI - if (chip->lbus.pdev) + if (chip->lbus.pdev) { parent = &chip->lbus.pdev->dev; + root_res = &chip->lbus.pdev->resource[0]; + } #endif pnode = of_node_get(chip->of_node); if (pnode == NULL) return; - rdev = macio_add_one_device(chip, parent, pnode, NULL); + /* Add macio itself to hierarchy */ + rdev = macio_add_one_device(chip, parent, pnode, NULL, root_res); if (rdev == NULL) return; + root_res = &rdev->resource[0]; /* First scan 1st level */ for (np = NULL; (np = of_get_next_child(pnode, np)) != NULL;) { if (!macio_skip_device(np)) { of_node_get(np); - mdev = macio_add_one_device(chip, &rdev->ofdev.dev, np, NULL); + mdev = macio_add_one_device(chip, &rdev->ofdev.dev, np, NULL, root_res); if (mdev == NULL) of_node_put(np); else if (strncmp(np->name, "media-bay", 9) == 0) @@ -267,17 +383,20 @@ static void macio_pci_add_devices(struct macio_chip *chip) for (np = NULL; (np = of_get_next_child(mbdev->ofdev.node, np)) != NULL;) if (!macio_skip_device(np)) { of_node_get(np); - if (macio_add_one_device(chip, &mbdev->ofdev.dev, np, mbdev) == NULL) + if (macio_add_one_device(chip, &mbdev->ofdev.dev, np, mbdev, + root_res) == NULL) of_node_put(np); } /* Add serial ports if any */ - if (sdev) + if (sdev) { for (np = NULL; (np = of_get_next_child(sdev->ofdev.node, np)) != NULL;) if (!macio_skip_device(np)) { of_node_get(np); - if (macio_add_one_device(chip, &sdev->ofdev.dev, np, NULL) == NULL) + if (macio_add_one_device(chip, &sdev->ofdev.dev, np, NULL, + root_res) == NULL) of_node_put(np); } + } } @@ -294,6 +413,7 @@ int macio_register_driver(struct macio_driver *drv) drv->driver.bus = &macio_bus_type; drv->driver.probe = macio_device_probe; drv->driver.remove = macio_device_remove; + drv->driver.shutdown = macio_device_shutdown; /* register with core */ count = driver_register(&drv->driver); @@ -309,6 +429,97 @@ void macio_unregister_driver(struct macio_driver *drv) driver_unregister(&drv->driver); } +/** + * macio_request_resource - Request an MMIO resource + * @dev: pointer to the device holding the resource + * @resource_no: resource number to request + * @name: resource name + * + * Mark memory region number @resource_no associated with MacIO + * device @dev as being reserved by owner @name. Do not access + * any address inside the memory regions unless this call returns + * successfully. + * + * Returns 0 on success, or %EBUSY on error. A warning + * message is also printed on failure. + */ +int macio_request_resource(struct macio_dev *dev, int resource_no, const char *name) +{ + if (macio_resource_len(dev, resource_no) == 0) + return 0; + + if (!request_mem_region(macio_resource_start(dev, resource_no), + macio_resource_len(dev, resource_no), + name)) + goto err_out; + + return 0; + +err_out: + printk (KERN_WARNING "MacIO: Unable to reserve resource #%d:%lx@%lx" + " for device %s\n", + resource_no, + macio_resource_len(dev, resource_no), + macio_resource_start(dev, resource_no), + dev->ofdev.dev.bus_id); + return -EBUSY; +} + +/** + * macio_release_resource - Release an MMIO resource + * @dev: pointer to the device holding the resource + * @resource_no: resource number to release + */ +void macio_release_resource(struct macio_dev *dev, int resource_no) +{ + if (macio_resource_len(dev, resource_no) == 0) + return; + release_mem_region(macio_resource_start(dev, resource_no), + macio_resource_len(dev, resource_no)); +} + +/** + * macio_request_resources - Reserve all memory resources + * @dev: MacIO device whose resources are to be reserved + * @name: Name to be associated with resource. + * + * Mark all memory regions associated with MacIO device @dev as + * being reserved by owner @name. Do not access any address inside + * the memory regions unless this call returns successfully. + * + * Returns 0 on success, or %EBUSY on error. A warning + * message is also printed on failure. + */ +int macio_request_resources(struct macio_dev *dev, const char *name) +{ + int i; + + for (i = 0; i < dev->n_resources; i++) + if (macio_request_resource(dev, i, name)) + goto err_out; + return 0; + +err_out: + while(--i >= 0) + macio_release_resource(dev, i); + + return -EBUSY; +} + +/** + * macio_release_resources - Release reserved memory resources + * @dev: MacIO device whose resources were previously reserved + */ + +void macio_release_resources(struct macio_dev *dev) +{ + int i; + + for (i = 0; i < dev->n_resources; i++) + macio_release_resource(dev, i); +} + + #ifdef CONFIG_PCI static int __devinit macio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) @@ -416,3 +627,7 @@ EXPORT_SYMBOL(macio_register_driver); EXPORT_SYMBOL(macio_unregister_driver); EXPORT_SYMBOL(macio_dev_get); EXPORT_SYMBOL(macio_dev_put); +EXPORT_SYMBOL(macio_request_resource); +EXPORT_SYMBOL(macio_release_resource); +EXPORT_SYMBOL(macio_request_resources); +EXPORT_SYMBOL(macio_release_resources); diff --git a/drivers/macintosh/mediabay.c b/drivers/macintosh/mediabay.c index 89d4aaaf622f..507a419d2666 100644 --- a/drivers/macintosh/mediabay.c +++ b/drivers/macintosh/mediabay.c @@ -107,6 +107,11 @@ int media_bay_count = 0; #define MS_TO_HZ(ms) ((ms * HZ + 999) / 1000) /* + * Wait that number of ms between each step in normal polling mode + */ +#define MB_POLL_DELAY 25 + +/* * Consider the media-bay ID value stable if it is the same for * this number of milliseconds */ @@ -121,7 +126,7 @@ int media_bay_count = 0; * Hold the media-bay reset signal true for this many ticks * after a device is inserted before releasing it. */ -#define MB_RESET_DELAY 40 +#define MB_RESET_DELAY 50 /* * Wait this long after the reset signal is released and before doing @@ -390,24 +395,28 @@ static void __pmac poll_media_bay(struct media_bay_info* bay) int id = bay->ops->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", bay->index); - } - 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) { + if (id != bay->content_id) { + bay->value_count += MS_TO_HZ(MB_POLL_DELAY); + if (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); + } + 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; + bay->cd_retry = 0; #endif - printk(KERN_INFO "media bay %d is empty\n", bay->index); + printk(KERN_INFO "media bay %d is empty\n", bay->index); + } + } } - } } else { bay->last_value = id; bay->value_count = 0; @@ -496,8 +505,12 @@ static void __pmac media_bay_step(int 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)) - return; + if ((bay->state != mb_ide_waiting) && (bay->timer != 0)) { + bay->timer -= MS_TO_HZ(MB_POLL_DELAY); + if (bay->timer > 0) + return; + bay->timer = 0; + } switch(bay->state) { case mb_powering_up: @@ -572,12 +585,13 @@ static void __pmac media_bay_step(int i) } break; } else if (bay->timer > 0) - bay->timer--; - if (bay->timer == 0) { + bay->timer -= MS_TO_HZ(MB_POLL_DELAY); + if (bay->timer <= 0) { printk("\nIDE Timeout in bay %d !, IDE state is: 0x%02x\n", i, readb(bay->cd_base + 0x70)); MBDBG("mediabay%d: nIDE Timeout !\n", i); set_mb_power(bay, 0); + bay->timer = 0; } break; #endif /* CONFIG_BLK_DEV_IDE */ @@ -630,7 +644,7 @@ static int __pmac media_bay_task(void *x) } current->state = TASK_INTERRUPTIBLE; - schedule_timeout(MS_TO_HZ(10)); + schedule_timeout(MS_TO_HZ(MB_POLL_DELAY)); if (signal_pending(current)) return 0; } @@ -645,17 +659,16 @@ static int __devinit media_bay_attach(struct macio_dev *mdev, const struct of_ma ofnode = mdev->ofdev.node; - if (!request_OF_resource(ofnode, 0, NULL)) - return -ENXIO; - + if (macio_resource_count(mdev) < 1) + return -ENODEV; + if (macio_request_resources(mdev, "media-bay")) + return -EBUSY; /* Media bay registers are located at the beginning of the * mac-io chip, we get the parent address for now (hrm...) */ - if (ofnode->parent->n_addrs == 0) - return -ENODEV; regbase = (volatile u32 *)ioremap(ofnode->parent->addrs[0].address, 0x100); if (regbase == NULL) { - release_OF_resource(ofnode, 0); + macio_release_resources(mdev); return -ENOMEM; } @@ -684,13 +697,13 @@ static int __devinit media_bay_attach(struct macio_dev *mdev, const struct of_ma bay->state = mb_empty; do { set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout(MS_TO_HZ(10)); + schedule_timeout(MS_TO_HZ(MB_POLL_DELAY)); media_bay_step(i); } while((bay->state != mb_empty) && (bay->state != mb_up)); /* Mark us ready by filling our mdev data */ - dev_set_drvdata(&mdev->ofdev.dev, bay); + macio_set_drvdata(mdev, bay); /* Startup kernel thread */ if (i == 0) @@ -702,7 +715,7 @@ static int __devinit media_bay_attach(struct macio_dev *mdev, const struct of_ma static int __pmac media_bay_suspend(struct macio_dev *mdev, u32 state) { - struct media_bay_info *bay = dev_get_drvdata(&mdev->ofdev.dev); + struct media_bay_info *bay = macio_get_drvdata(mdev); if (state != mdev->ofdev.dev.power_state && state >= 2) { down(&bay->lock); @@ -710,7 +723,7 @@ static int __pmac media_bay_suspend(struct macio_dev *mdev, u32 state) set_mb_power(bay, 0); up(&bay->lock); set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout(MS_TO_HZ(10)); + schedule_timeout(MS_TO_HZ(MB_POLL_DELAY)); mdev->ofdev.dev.power_state = state; } return 0; @@ -718,7 +731,7 @@ static int __pmac media_bay_suspend(struct macio_dev *mdev, u32 state) static int __pmac media_bay_resume(struct macio_dev *mdev) { - struct media_bay_info *bay = dev_get_drvdata(&mdev->ofdev.dev); + struct media_bay_info *bay = macio_get_drvdata(mdev); if (mdev->ofdev.dev.power_state != 0) { mdev->ofdev.dev.power_state = 0; @@ -746,7 +759,7 @@ static int __pmac media_bay_resume(struct macio_dev *mdev) #endif do { set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout(MS_TO_HZ(10)); + schedule_timeout(MS_TO_HZ(MB_POLL_DELAY)); media_bay_step(bay->index); } while((bay->state != mb_empty) && (bay->state != mb_up)); diff --git a/drivers/macintosh/therm_adt7467.c b/drivers/macintosh/therm_adt7467.c new file mode 100644 index 000000000000..5de422f0a5fe --- /dev/null +++ b/drivers/macintosh/therm_adt7467.c @@ -0,0 +1,562 @@ +/* + * Device driver for the i2c thermostat found on the iBook G4, Albook G4 + * + * Copyright (C) 2003, 2004 Colin Leroy, Rasmus Rohde, Benjamin Herrenschmidt + * + * Documentation from + * http://www.analog.com/UploadedFiles/Data_Sheets/115254175ADT7467_pra.pdf + * http://www.analog.com/UploadedFiles/Data_Sheets/3686221171167ADT7460_b.pdf + * + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/smp_lock.h> +#include <linux/wait.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <asm/of_device.h> + +#undef DEBUG + +#define CONFIG_REG 0x40 +#define MANUAL_MASK 0xe0 +#define AUTO_MASK 0x20 + +static u8 TEMP_REG[3] = {0x26, 0x25, 0x27}; /* local, cpu, gpu */ +static u8 LIMIT_REG[3] = {0x6b, 0x6a, 0x6c}; /* local, cpu, gpu */ +static u8 MANUAL_MODE[2] = {0x5c, 0x5d}; +static u8 REM_CONTROL[2] = {0x00, 0x40}; +static u8 FAN_SPEED[2] = {0x28, 0x2a}; +static u8 FAN_SPD_SET[2] = {0x30, 0x31}; + +static u8 default_limits_local[3] = {70, 50, 70}; /* local, cpu, gpu */ +static u8 default_limits_chip[3] = {80, 65, 80}; /* local, cpu, gpu */ + +static int limit_adjust = 0; +static int fan_speed = -1; + +MODULE_AUTHOR("Colin Leroy <colin@colino.net>"); +MODULE_DESCRIPTION("Driver for ADT7467 thermostat in iBook G4"); +MODULE_LICENSE("GPL"); + +MODULE_PARM(limit_adjust,"i"); +MODULE_PARM_DESC(limit_adjust,"Adjust maximum temperatures (50°C cpu, 70°C gpu) by N °C."); +MODULE_PARM(fan_speed,"i"); +MODULE_PARM_DESC(fan_speed,"Specify fan speed (0-255) when lim < temp < lim+8 (default 128)"); + +struct thermostat { + struct i2c_client clt; + u8 cached_temp[3]; + u8 initial_limits[3]; + u8 limits[3]; + int last_speed[2]; + int overriding[2]; +}; + +static enum {ADT7460, ADT7467} therm_type; +static int therm_bus, therm_address; +static struct of_device * of_dev; +static struct thermostat* thermostat; +static pid_t monitor_thread_id; +static int monitor_running; +static struct completion monitor_task_compl; + +static int attach_one_thermostat(struct i2c_adapter *adapter, int addr, int busno); +static void write_both_fan_speed(struct thermostat *th, int speed); +static void write_fan_speed(struct thermostat *th, int speed, int fan); + +static int +write_reg(struct thermostat* th, int reg, u8 data) +{ + u8 tmp[2]; + int rc; + + tmp[0] = reg; + tmp[1] = data; + rc = i2c_master_send(&th->clt, (const char *)tmp, 2); + if (rc < 0) + return rc; + if (rc != 2) + return -ENODEV; + return 0; +} + +static int +read_reg(struct thermostat* th, int reg) +{ + u8 reg_addr, data; + int rc; + + reg_addr = (u8)reg; + rc = i2c_master_send(&th->clt, ®_addr, 1); + if (rc < 0) + return rc; + if (rc != 1) + return -ENODEV; + rc = i2c_master_recv(&th->clt, (char *)&data, 1); + if (rc < 0) + return rc; + return data; +} + +static int +attach_thermostat(struct i2c_adapter *adapter) +{ + unsigned long bus_no; + + if (strncmp(adapter->name, "uni-n", 5)) + return -ENODEV; + bus_no = simple_strtoul(adapter->name + 6, NULL, 10); + if (bus_no != therm_bus) + return -ENODEV; + return attach_one_thermostat(adapter, therm_address, bus_no); +} + +static int +detach_thermostat(struct i2c_adapter *adapter) +{ + struct thermostat* th; + int i; + + if (thermostat == NULL) + return 0; + + th = thermostat; + + if (monitor_running) { + monitor_running = 0; + wait_for_completion(&monitor_task_compl); + } + + printk(KERN_INFO "adt746x: Putting max temperatures back from %d, %d, %d," + " to %d, %d, %d, (°C)\n", + th->limits[0], th->limits[1], th->limits[2], + th->initial_limits[0], th->initial_limits[1], th->initial_limits[2]); + + for (i = 0; i < 3; i++) + write_reg(th, LIMIT_REG[i], th->initial_limits[i]); + + write_both_fan_speed(th, -1); + + i2c_detach_client(&th->clt); + + thermostat = NULL; + + kfree(th); + + return 0; +} + +static struct i2c_driver thermostat_driver = { + .name ="Apple Thermostat ADT7467", + .id =0xDEAD7467, + .flags =I2C_DF_NOTIFY, + .attach_adapter =&attach_thermostat, + .detach_adapter =&detach_thermostat, +}; + +static int read_fan_speed(struct thermostat *th, u8 addr) +{ + u8 tmp[2]; + u16 res; + + /* should start with low byte */ + tmp[1] = read_reg(th, addr); + tmp[0] = read_reg(th, addr + 1); + + res = tmp[1] + (tmp[0] << 8); + return (90000*60)/res; +} + +static void write_both_fan_speed(struct thermostat *th, int speed) +{ + write_fan_speed(th, speed, 0); + if (therm_type == ADT7460) + write_fan_speed(th, speed, 1); +} + +static void write_fan_speed(struct thermostat *th, int speed, int fan) +{ + u8 manual; + + if (speed > 0xff) + speed = 0xff; + else if (speed < -1) + speed = 0; + + if (therm_type == ADT7467 && fan == 1) + return; + + if (th->last_speed[fan] != speed) { + if (speed == -1) + printk(KERN_INFO "adt746x: Setting speed to: automatic for %s fan.\n", + fan?"GPU":"CPU"); + else + printk(KERN_INFO "adt746x: Setting speed to: %d for %s fan.\n", + speed, fan?"GPU":"CPU"); + } else + return; + + if (speed >= 0) { + manual = read_reg(th, MANUAL_MODE[fan]); + write_reg(th, MANUAL_MODE[fan], manual|MANUAL_MASK); + write_reg(th, FAN_SPD_SET[fan], speed); + } else { + /* back to automatic */ + if(therm_type == ADT7460) { + manual = read_reg(th, MANUAL_MODE[fan]) & (~MANUAL_MASK); + write_reg(th, MANUAL_MODE[fan], manual|REM_CONTROL[fan]); + } else { + manual = read_reg(th, MANUAL_MODE[fan]); + write_reg(th, MANUAL_MODE[fan], manual&(~AUTO_MASK)); + } + } + + th->last_speed[fan] = speed; +} + +static int monitor_task(void *arg) +{ + struct thermostat* th = arg; + u8 temps[3]; + u8 lims[3]; + int i; +#ifdef DEBUG + int mfan_speed; +#endif + + lock_kernel(); + daemonize("kfand"); + unlock_kernel(); + strcpy(current->comm, "thermostat"); + monitor_running = 1; + + while(monitor_running) + { + set_task_state(current, TASK_UNINTERRUPTIBLE); + schedule_timeout(2*HZ); + + /* Check status */ + /* local : chip */ + /* remote 1: CPU ?*/ + /* remote 2: GPU ?*/ +#ifndef DEBUG + if (fan_speed != -1) { +#endif + for (i = 0; i < 3; i++) { + temps[i] = read_reg(th, TEMP_REG[i]); + lims[i] = th->limits[i]; + } +#ifndef DEBUG + } +#endif + if (fan_speed != -1) { + int lastvar = 0; /* for iBook */ + for (i = 1; i < 3; i++) { /* we don't care about local sensor */ + int started = 0; + int fan_number = (therm_type == ADT7460 && i == 2); + int var = temps[i] - lims[i]; + if (var > 8) { + if (th->overriding[fan_number] == 0) + printk(KERN_INFO "adt746x: Limit exceeded by %d°C, overriding specified fan speed for %s.\n", + var, fan_number?"GPU":"CPU"); + th->overriding[fan_number] = 1; + write_fan_speed(th, 255, fan_number); + started = 1; + } else if ((!th->overriding[fan_number] || var < 6) && var > 0) { + if (th->overriding[fan_number] == 1) + printk(KERN_INFO "adt746x: Limit exceeded by %d°C, setting speed to specified for %s.\n", + var, fan_number?"GPU":"CPU"); + th->overriding[fan_number] = 0; + write_fan_speed(th, fan_speed, fan_number); + started = 1; + } else if (var < -1) { + /* don't stop iBook fan if GPU is cold and CPU is not + * so cold (lastvar >= -1) */ + if (therm_type == ADT7460 || lastvar < -1 || i == 1) { + if (th->last_speed[fan_number] != 0) + printk(KERN_INFO "adt746x: Stopping %s fan.\n", + fan_number?"GPU":"CPU"); + write_fan_speed(th, 0, fan_number); + } + } + + lastvar = var; + + if (started && therm_type == ADT7467) + break; /* we don't want to re-stop the fan + * if CPU is heating and GPU is not */ + } + } +#ifdef DEBUG + mfan_speed = read_fan_speed(th, FAN_SPEED[0]); + /* only one fan in the iBook G4 */ + + if (temps[0] != th->cached_temp[0] + || temps[1] != th->cached_temp[1] + || temps[2] != th->cached_temp[2]) { + printk(KERN_INFO "adt746x: Temperature infos:" + " thermostats: %d,%d,%d °C;" + " limits: %d,%d,%d °C;" + " fan speed: %d RPM\n", + temps[0], temps[1], temps[2], + lims[0], lims[1], lims[2], + mfan_speed); + } + th->cached_temp[0] = temps[0]; + th->cached_temp[1] = temps[1]; + th->cached_temp[2] = temps[2]; +#endif + } + + complete_and_exit(&monitor_task_compl, 0); + return 0; +} + +static void +set_limit(struct thermostat *th, int i) +{ + /* Set CPU limit higher to avoid powerdowns */ + th->limits[i] = default_limits_chip[i] + limit_adjust; + write_reg(th, LIMIT_REG[i], th->limits[i]); + + /* set our limits to normal */ + th->limits[i] = default_limits_local[i] + limit_adjust; +} + +static int +attach_one_thermostat(struct i2c_adapter *adapter, int addr, int busno) +{ + struct thermostat* th; + int rc; + int i; + + if (thermostat) + return 0; + th = (struct thermostat *)kmalloc(sizeof(struct thermostat), GFP_KERNEL); + if (!th) + return -ENOMEM; + memset(th, 0, sizeof(*th)); + th->clt.addr = addr; + th->clt.adapter = adapter; + th->clt.driver = &thermostat_driver; + th->clt.id = 0xDEAD7467; + strcpy(th->clt.name, "thermostat"); + + rc = read_reg(th, 0); + if (rc < 0) { + printk(KERN_ERR "adt746x: Thermostat failed to read config from bus %d !\n", + busno); + kfree(th); + return -ENODEV; + } + /* force manual control to start the fan quieter */ + + if (fan_speed == -1) + fan_speed=128; + + if(therm_type == ADT7460) { + printk(KERN_INFO "adt746x: ADT7460 initializing\n"); + /* The 7460 needs to be started explicitly */ + write_reg(th, CONFIG_REG, 1); + } else + printk(KERN_INFO "adt746x: ADT7467 initializing\n"); + + for (i = 0; i < 3; i++) { + th->initial_limits[i] = read_reg(th, LIMIT_REG[i]); + set_limit(th, i); + } + + printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d" + " to %d, %d, %d (°C)\n", + th->initial_limits[0], th->initial_limits[1], th->initial_limits[2], + th->limits[0], th->limits[1], th->limits[2]); + + thermostat = th; + + if (i2c_attach_client(&th->clt)) { + printk("adt746x: Thermostat failed to attach client !\n"); + thermostat = NULL; + kfree(th); + return -ENODEV; + } + + /* be sure to really write fan speed the first time */ + th->last_speed[0] = -2; + th->last_speed[1] = -2; + + if (fan_speed != -1) { + write_both_fan_speed(th, 0); + } else { + write_both_fan_speed(th, -1); + } + + init_completion(&monitor_task_compl); + + monitor_thread_id = kernel_thread(monitor_task, th, + SIGCHLD | CLONE_KERNEL); + + return 0; +} + +/* + * Now, unfortunately, sysfs doesn't give us a nice void * we could + * pass around to the attribute functions, so we don't really have + * choice but implement a bunch of them... + * + */ +#define BUILD_SHOW_FUNC_DEG(name, data) \ +static ssize_t show_##name(struct device *dev, char *buf) \ +{ \ + return sprintf(buf, "%d°C\n", data); \ +} +#define BUILD_SHOW_FUNC_INT(name, data) \ +static ssize_t show_##name(struct device *dev, char *buf) \ +{ \ + return sprintf(buf, "%d\n", data); \ +} + +#define BUILD_STORE_FUNC_DEG(name, data) \ +static ssize_t store_##name(struct device *dev, const char *buf, size_t n) \ +{ \ + int val; \ + int i; \ + val = simple_strtol(buf, NULL, 10); \ + printk(KERN_INFO "Adjusting limits by %d°C\n", val); \ + limit_adjust = val; \ + for (i=0; i < 3; i++) \ + set_limit(thermostat, i); \ + return n; \ +} + +#define BUILD_STORE_FUNC_INT(name, data) \ +static ssize_t store_##name(struct device *dev, const char *buf, size_t n) \ +{ \ + u32 val; \ + val = simple_strtoul(buf, NULL, 10); \ + if (val < 0 || val > 255) \ + return -EINVAL; \ + printk(KERN_INFO "Setting fan speed to %d\n", val); \ + data = val; \ + return n; \ +} + +BUILD_SHOW_FUNC_DEG(cpu_temperature, (read_reg(thermostat, TEMP_REG[1]))) +BUILD_SHOW_FUNC_DEG(gpu_temperature, (read_reg(thermostat, TEMP_REG[2]))) +BUILD_SHOW_FUNC_DEG(cpu_limit, thermostat->limits[1]) +BUILD_SHOW_FUNC_DEG(gpu_limit, thermostat->limits[2]) + +BUILD_SHOW_FUNC_INT(specified_fan_speed, fan_speed) +BUILD_SHOW_FUNC_INT(cpu_fan_speed, (read_fan_speed(thermostat, FAN_SPEED[0]))) +BUILD_SHOW_FUNC_INT(gpu_fan_speed, (read_fan_speed(thermostat, FAN_SPEED[1]))) + +BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed) +BUILD_SHOW_FUNC_INT(limit_adjust, limit_adjust) +BUILD_STORE_FUNC_DEG(limit_adjust, thermostat) + +static DEVICE_ATTR(cpu_temperature, S_IRUGO, + show_cpu_temperature,NULL); +static DEVICE_ATTR(gpu_temperature, S_IRUGO, + show_gpu_temperature,NULL); +static DEVICE_ATTR(cpu_limit, S_IRUGO, + show_cpu_limit, NULL); +static DEVICE_ATTR(gpu_limit, S_IRUGO, + show_gpu_limit, NULL); + +static DEVICE_ATTR(specified_fan_speed, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, + show_specified_fan_speed,store_specified_fan_speed); + +static DEVICE_ATTR(cpu_fan_speed, S_IRUGO, + show_cpu_fan_speed, NULL); +static DEVICE_ATTR(gpu_fan_speed, S_IRUGO, + show_gpu_fan_speed, NULL); + +static DEVICE_ATTR(limit_adjust, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, + show_limit_adjust, store_limit_adjust); + + +static int __init +thermostat_init(void) +{ + struct device_node* np; + u32 *prop; + + /* Currently, we only deal with the iBook G4, we will support + * all "2003" powerbooks later on + */ + np = of_find_node_by_name(NULL, "fan"); + if (!np) + return -ENODEV; + if (device_is_compatible(np, "adt7460")) + therm_type = ADT7460; + else if (device_is_compatible(np, "adt7467")) + therm_type = ADT7467; + else + return -ENODEV; + + prop = (u32 *)get_property(np, "reg", NULL); + if (!prop) + return -ENODEV; + therm_bus = ((*prop) >> 8) & 0x0f; + therm_address = ((*prop) & 0xff) >> 1; + + printk(KERN_INFO "adt746x: Thermostat bus: %d, address: 0x%02x, limit_adjust: %d, fan_speed: %d\n", + therm_bus, therm_address, limit_adjust, fan_speed); + + of_dev = of_platform_device_create(np, "temperatures"); + + if (of_dev == NULL) { + printk(KERN_ERR "Can't register temperatures device !\n"); + return -ENODEV; + } + + device_create_file(&of_dev->dev, &dev_attr_cpu_temperature); + device_create_file(&of_dev->dev, &dev_attr_gpu_temperature); + device_create_file(&of_dev->dev, &dev_attr_cpu_limit); + device_create_file(&of_dev->dev, &dev_attr_gpu_limit); + device_create_file(&of_dev->dev, &dev_attr_limit_adjust); + device_create_file(&of_dev->dev, &dev_attr_specified_fan_speed); + device_create_file(&of_dev->dev, &dev_attr_cpu_fan_speed); + if(therm_type == ADT7460) + device_create_file(&of_dev->dev, &dev_attr_gpu_fan_speed); + +#ifndef CONFIG_I2C_KEYWEST + request_module("i2c-keywest"); +#endif + + return i2c_add_driver(&thermostat_driver); +} + +static void __exit +thermostat_exit(void) +{ + if (of_dev) { + device_remove_file(&of_dev->dev, &dev_attr_cpu_temperature); + device_remove_file(&of_dev->dev, &dev_attr_gpu_temperature); + device_remove_file(&of_dev->dev, &dev_attr_cpu_limit); + device_remove_file(&of_dev->dev, &dev_attr_gpu_limit); + device_remove_file(&of_dev->dev, &dev_attr_limit_adjust); + device_remove_file(&of_dev->dev, &dev_attr_specified_fan_speed); + device_remove_file(&of_dev->dev, &dev_attr_cpu_fan_speed); + if(therm_type == ADT7460) + device_remove_file(&of_dev->dev, &dev_attr_gpu_fan_speed); + of_device_unregister(of_dev); + } + i2c_del_driver(&thermostat_driver); +} + +module_init(thermostat_init); +module_exit(thermostat_exit); diff --git a/drivers/macintosh/therm_pm72.c b/drivers/macintosh/therm_pm72.c new file mode 100644 index 000000000000..c6af16a9e3eb --- /dev/null +++ b/drivers/macintosh/therm_pm72.c @@ -0,0 +1,1241 @@ +/* + * Device driver for the thermostats & fan controller of the + * Apple G5 "PowerMac7,2" desktop machines. + * + * (c) Copyright IBM Corp. 2003 + * + * Maintained by: Benjamin Herrenschmidt + * <benh@kernel.crashing.org> + * + * + * The algorithm used is the PID control algorithm, used the same + * way the published Darwin code does, using the same values that + * are present in the Darwin 7.0 snapshot property lists. + * + * As far as the CPUs control loops are concerned, I use the + * calibration & PID constants provided by the EEPROM, + * I do _not_ embed any value from the property lists, as the ones + * provided by Darwin 7.0 seem to always have an older version that + * what I've seen on the actual computers. + * It would be interesting to verify that though. Darwin has a + * version code of 1.0.0d11 for all control loops it seems, while + * so far, the machines EEPROMs contain a dataset versioned 1.0.0f + * + * Darwin doesn't provide source to all parts, some missing + * bits like the AppleFCU driver or the actual scale of some + * of the values returned by sensors had to be "guessed" some + * way... or based on what Open Firmware does. + * + * I didn't yet figure out how to get the slots power consumption + * out of the FCU, so that part has not been implemented yet and + * the slots fan is set to a fixed 50% PWM, hoping this value is + * safe enough ... + * + * Note: I have observed strange oscillations of the CPU control + * loop on a dual G5 here. When idle, the CPU exhaust fan tend to + * oscillates slowly (over several minutes) between the minimum + * of 300RPMs and approx. 1000 RPMs. I don't know what is causing + * this, it could be some incorrect constant or an error in the + * way I ported the algorithm, or it could be just normal. I + * don't have full understanding on the way Apple tweaked the PID + * algorithm for the CPU control, it is definitely not a standard + * implementation... + * + * TODO: - Check MPU structure version/signature + * - Add things like /sbin/overtemp for non-critical + * overtemp conditions so userland can take some policy + * decisions, like slewing down CPUs + * - Deal with fan failures + * + * History: + * + * Nov. 13, 2003 : 0.5 + * - First release + * + * Nov. 14, 2003 : 0.6 + * - Read fan speed from FCU, low level fan routines now deal + * with errors & check fan status, though higher level don't + * do much. + * - Move a bunch of definitions to .h file + * + * Nov. 18, 2003 : 0.7 + * - Fix build on ppc64 kernel + * - Move back statics definitions to .c file + * - Avoid calling schedule_timeout with a negative number + * + * Dev. 18, 2003 : 0.8 + * - Fix typo when reading back fan speed on 2 CPU machines + * + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/smp_lock.h> +#include <linux/wait.h> +#include <linux/reboot.h> +#include <linux/kmod.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <asm/of_device.h> + +#include "therm_pm72.h" + +#define VERSION "0.8" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) +#endif + + +/* + * Driver statics + */ + +static struct of_device * of_dev; +static struct i2c_adapter * u3_0; +static struct i2c_adapter * u3_1; +static struct i2c_client * fcu; +static struct cpu_pid_state cpu_state[2]; +static struct backside_pid_state backside_state; +static struct drives_pid_state drives_state; +static int state; +static int cpu_count; +static pid_t ctrl_task; +static struct completion ctrl_complete; +static int critical_state; +static DECLARE_MUTEX(driver_lock); + +/* + * i2c_driver structure to attach to the host i2c controller + */ + +static int therm_pm72_attach(struct i2c_adapter *adapter); +static int therm_pm72_detach(struct i2c_adapter *adapter); + +static struct i2c_driver therm_pm72_driver = +{ + .name = "therm_pm72", + .id = 0xDEADBEEF, + .flags = I2C_DF_NOTIFY, + .attach_adapter = therm_pm72_attach, + .detach_adapter = therm_pm72_detach, +}; + + +static inline void wait_ms(unsigned int ms) +{ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1 + (ms * HZ + 999) / 1000); +} + +/* + * Utility function to create an i2c_client structure and + * attach it to one of u3 adapters + */ +static struct i2c_client *attach_i2c_chip(int id, const char *name) +{ + struct i2c_client *clt; + struct i2c_adapter *adap; + + if (id & 0x100) + adap = u3_1; + else + adap = u3_0; + if (adap == NULL) + return NULL; + + clt = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (clt == NULL) + return NULL; + memset(clt, 0, sizeof(struct i2c_client)); + + clt->addr = (id >> 1) & 0x7f; + clt->adapter = adap; + clt->driver = &therm_pm72_driver; + clt->id = 0xDEADBEEF; + strncpy(clt->name, name, I2C_NAME_SIZE-1); + + if (i2c_attach_client(clt)) { + printk(KERN_ERR "therm_pm72: Failed to attach to i2c ID 0x%x\n", id); + kfree(clt); + return NULL; + } + return clt; +} + +/* + * Utility function to get rid of the i2c_client structure + * (will also detach from the adapter hopepfully) + */ +static void detach_i2c_chip(struct i2c_client *clt) +{ + i2c_detach_client(clt); + kfree(clt); +} + +/* + * Here are the i2c chip access wrappers + */ +static int read_smon_adc(struct i2c_client *chip, int chan) +{ + int ctrl; + + ctrl = i2c_smbus_read_byte_data(chip, 1); + i2c_smbus_write_byte_data(chip, 1, (ctrl & 0x1f) | (chan << 5)); + wait_ms(1); + return le16_to_cpu(i2c_smbus_read_word_data(chip, 4)) >> 6; +} + +static int fan_read_reg(int reg, unsigned char *buf, int nb) +{ + int tries, nr, nw; + + buf[0] = reg; + tries = 0; + for (;;) { + nw = i2c_master_send(fcu, buf, 1); + if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) + break; + wait_ms(10); + ++tries; + } + if (nw <= 0) { + printk(KERN_ERR "Failure writing address to FCU: %d", nw); + return -EIO; + } + tries = 0; + for (;;) { + nr = i2c_master_recv(fcu, buf, nb); + if (nr > 0 || (nr < 0 && nr != ENODEV) || tries >= 100) + break; + wait_ms(10); + ++tries; + } + if (nr <= 0) + printk(KERN_ERR "Failure reading data from FCU: %d", nw); + return nr; +} + +static int fan_write_reg(int reg, const unsigned char *ptr, int nb) +{ + int tries, nw; + unsigned char buf[16]; + + buf[0] = reg; + memcpy(buf+1, ptr, nb); + ++nb; + tries = 0; + for (;;) { + nw = i2c_master_send(fcu, buf, nb); + if (nw > 0 || (nw < 0 && nw != EIO) || tries >= 100) + break; + wait_ms(10); + ++tries; + } + if (nw < 0) + printk(KERN_ERR "Failure writing to FCU: %d", nw); + return nw; +} + +static int set_rpm_fan(int fan, int rpm) +{ + unsigned char buf[2]; + int rc; + + if (rpm < 300) + rpm = 300; + else if (rpm > 8191) + rpm = 8191; + buf[0] = rpm >> 5; + buf[1] = rpm << 3; + rc = fan_write_reg(0x10 + (fan * 2), buf, 2); + if (rc < 0) + return -EIO; + return 0; +} + +static int get_rpm_fan(int fan, int programmed) +{ + unsigned char failure; + unsigned char active; + unsigned char buf[2]; + int rc, reg_base; + + rc = fan_read_reg(0xb, &failure, 1); + if (rc != 1) + return -EIO; + if ((failure & (1 << fan)) != 0) + return -EFAULT; + rc = fan_read_reg(0xd, &active, 1); + if (rc != 1) + return -EIO; + if ((active & (1 << fan)) == 0) + return -ENXIO; + + /* Programmed value or real current speed */ + reg_base = programmed ? 0x10 : 0x11; + rc = fan_read_reg(reg_base + (fan * 2), buf, 2); + if (rc != 2) + return -EIO; + + return (buf[0] << 5) | buf[1] >> 3; +} + +static int set_pwm_fan(int fan, int pwm) +{ + unsigned char buf[2]; + int rc; + + if (pwm < 10) + pwm = 10; + else if (pwm > 100) + pwm = 100; + pwm = (pwm * 2559) / 1000; + buf[0] = pwm; + rc = fan_write_reg(0x30 + (fan * 2), buf, 1); + if (rc < 0) + return rc; + return 0; +} + +static int get_pwm_fan(int fan) +{ + unsigned char failure; + unsigned char active; + unsigned char buf[2]; + int rc; + + rc = fan_read_reg(0x2b, &failure, 1); + if (rc != 1) + return -EIO; + if ((failure & (1 << fan)) != 0) + return -EFAULT; + rc = fan_read_reg(0x2d, &active, 1); + if (rc != 1) + return -EIO; + if ((active & (1 << fan)) == 0) + return -ENXIO; + + /* Programmed value or real current speed */ + rc = fan_read_reg(0x30 + (fan * 2), buf, 1); + if (rc != 1) + return -EIO; + + return (buf[0] * 1000) / 2559; +} + +/* + * Utility routine to read the CPU calibration EEPROM data + * from the device-tree + */ +static int read_eeprom(int cpu, struct mpu_data *out) +{ + struct device_node *np; + char nodename[64]; + u8 *data; + int len; + + /* prom.c routine for finding a node by path is a bit brain dead + * and requires exact @xxx unit numbers. This is a bit ugly but + * will work for these machines + */ + sprintf(nodename, "/u3@0,f8000000/i2c@f8001000/cpuid@a%d", cpu ? 2 : 0); + np = of_find_node_by_path(nodename); + if (np == NULL) { + printk(KERN_ERR "therm_pm72: Failed to retreive cpuid node from device-tree\n"); + return -ENODEV; + } + data = (u8 *)get_property(np, "cpuid", &len); + if (data == NULL) { + printk(KERN_ERR "therm_pm72: Failed to retreive cpuid property from device-tree\n"); + of_node_put(np); + return -ENODEV; + } + memcpy(out, data, sizeof(struct mpu_data)); + of_node_put(np); + + return 0; +} + +/* + * Now, unfortunately, sysfs doesn't give us a nice void * we could + * pass around to the attribute functions, so we don't really have + * choice but implement a bunch of them... + * + * That sucks a bit, we take the lock because FIX32TOPRINT evaluates + * the input twice... I accept patches :) + */ +#define BUILD_SHOW_FUNC_FIX(name, data) \ +static ssize_t show_##name(struct device *dev, char *buf) \ +{ \ + ssize_t r; \ + down(&driver_lock); \ + r = sprintf(buf, "%d.%03d", FIX32TOPRINT(data)); \ + up(&driver_lock); \ + return r; \ +} +#define BUILD_SHOW_FUNC_INT(name, data) \ +static ssize_t show_##name(struct device *dev, char *buf) \ +{ \ + return sprintf(buf, "%d", data); \ +} + +BUILD_SHOW_FUNC_FIX(cpu0_temperature, cpu_state[0].last_temp) +BUILD_SHOW_FUNC_FIX(cpu0_voltage, cpu_state[0].voltage) +BUILD_SHOW_FUNC_FIX(cpu0_current, cpu_state[0].current_a) +BUILD_SHOW_FUNC_INT(cpu0_exhaust_fan_rpm, cpu_state[0].rpm) +BUILD_SHOW_FUNC_INT(cpu0_intake_fan_rpm, cpu_state[0].intake_rpm) + +BUILD_SHOW_FUNC_FIX(cpu1_temperature, cpu_state[1].last_temp) +BUILD_SHOW_FUNC_FIX(cpu1_voltage, cpu_state[1].voltage) +BUILD_SHOW_FUNC_FIX(cpu1_current, cpu_state[1].current_a) +BUILD_SHOW_FUNC_INT(cpu1_exhaust_fan_rpm, cpu_state[1].rpm) +BUILD_SHOW_FUNC_INT(cpu1_intake_fan_rpm, cpu_state[1].intake_rpm) + +BUILD_SHOW_FUNC_FIX(backside_temperature, backside_state.last_temp) +BUILD_SHOW_FUNC_INT(backside_fan_pwm, backside_state.pwm) + +BUILD_SHOW_FUNC_FIX(drives_temperature, drives_state.last_temp) +BUILD_SHOW_FUNC_INT(drives_fan_rpm, drives_state.rpm) + +static DEVICE_ATTR(cpu0_temperature,S_IRUGO,show_cpu0_temperature,NULL); +static DEVICE_ATTR(cpu0_voltage,S_IRUGO,show_cpu0_voltage,NULL); +static DEVICE_ATTR(cpu0_current,S_IRUGO,show_cpu0_current,NULL); +static DEVICE_ATTR(cpu0_exhaust_fan_rpm,S_IRUGO,show_cpu0_exhaust_fan_rpm,NULL); +static DEVICE_ATTR(cpu0_intake_fan_rpm,S_IRUGO,show_cpu0_intake_fan_rpm,NULL); + +static DEVICE_ATTR(cpu1_temperature,S_IRUGO,show_cpu1_temperature,NULL); +static DEVICE_ATTR(cpu1_voltage,S_IRUGO,show_cpu1_voltage,NULL); +static DEVICE_ATTR(cpu1_current,S_IRUGO,show_cpu1_current,NULL); +static DEVICE_ATTR(cpu1_exhaust_fan_rpm,S_IRUGO,show_cpu1_exhaust_fan_rpm,NULL); +static DEVICE_ATTR(cpu1_intake_fan_rpm,S_IRUGO,show_cpu1_intake_fan_rpm,NULL); + +static DEVICE_ATTR(backside_temperature,S_IRUGO,show_backside_temperature,NULL); +static DEVICE_ATTR(backside_fan_pwm,S_IRUGO,show_backside_fan_pwm,NULL); + +static DEVICE_ATTR(drives_temperature,S_IRUGO,show_drives_temperature,NULL); +static DEVICE_ATTR(drives_fan_rpm,S_IRUGO,show_drives_fan_rpm,NULL); + +/* + * CPUs fans control loop + */ +static void do_monitor_cpu(struct cpu_pid_state *state) +{ + s32 temp, voltage, current_a, power, power_target; + s32 integral, derivative, proportional, adj_in_target, sval; + s64 integ_p, deriv_p, prop_p, sum; + int i, intake, rc; + + DBG("cpu %d:\n", state->index); + + /* Read current fan status */ + if (state->index == 0) + rc = get_rpm_fan(CPUA_EXHAUST_FAN_RPM_ID, !RPM_PID_USE_ACTUAL_SPEED); + else + rc = get_rpm_fan(CPUB_EXHAUST_FAN_RPM_ID, !RPM_PID_USE_ACTUAL_SPEED); + if (rc < 0) { + printk(KERN_WARNING "Error %d reading CPU %d exhaust fan !\n", + rc, state->index); + /* XXX What do we do now ? */ + } else + state->rpm = rc; + DBG(" current rpm: %d\n", state->rpm); + + /* Get some sensor readings and scale it */ + temp = read_smon_adc(state->monitor, 1); + voltage = read_smon_adc(state->monitor, 3); + current_a = read_smon_adc(state->monitor, 4); + + /* Fixup temperature according to diode calibration + */ + DBG(" temp raw: %04x, m_diode: %04x, b_diode: %04x\n", + temp, state->mpu.mdiode, state->mpu.bdiode); + temp = ((s32)temp * (s32)state->mpu.mdiode + ((s32)state->mpu.bdiode << 12)) >> 2; + state->last_temp = temp; + DBG(" temp: %d.%03d\n", FIX32TOPRINT(temp)); + + /* Check tmax, increment overtemp if we are there. At tmax+8, we go + * full blown immediately and try to trigger a shutdown + */ + if (temp >= ((state->mpu.tmax + 8) << 16)) { + printk(KERN_WARNING "Warning ! CPU %d temperature way above maximum (%d) !\n", + state->index, temp >> 16); + state->overtemp = CPU_MAX_OVERTEMP; + } else if (temp > (state->mpu.tmax << 16)) + state->overtemp++; + else + state->overtemp = 0; + if (state->overtemp >= CPU_MAX_OVERTEMP) + critical_state = 1; + if (state->overtemp > 0) { + state->rpm = state->mpu.rmaxn_exhaust_fan; + state->intake_rpm = intake = state->mpu.rmaxn_intake_fan; + goto do_set_fans; + } + + /* Scale other sensor values according to fixed scales + * obtained in Darwin and calculate power from I and V + */ + state->voltage = voltage *= ADC_CPU_VOLTAGE_SCALE; + state->current_a = current_a *= ADC_CPU_CURRENT_SCALE; + power = (((u64)current_a) * ((u64)voltage)) >> 16; + + /* Calculate power target value (could be done once for all) + * and convert to a 16.16 fp number + */ + power_target = ((u32)(state->mpu.pmaxh - state->mpu.padjmax)) << 16; + + DBG(" current: %d.%03d, voltage: %d.%03d\n", + FIX32TOPRINT(current_a), FIX32TOPRINT(voltage)); + DBG(" power: %d.%03d W, target: %d.%03d, error: %d.%03d\n", FIX32TOPRINT(power), + FIX32TOPRINT(power_target), FIX32TOPRINT(power_target - power)); + + /* Store temperature and power in history array */ + state->cur_temp = (state->cur_temp + 1) % CPU_TEMP_HISTORY_SIZE; + state->temp_history[state->cur_temp] = temp; + state->cur_power = (state->cur_power + 1) % state->count_power; + state->power_history[state->cur_power] = power; + state->error_history[state->cur_power] = power_target - power; + + /* If first loop, fill the history table */ + if (state->first) { + for (i = 0; i < (state->count_power - 1); i++) { + state->cur_power = (state->cur_power + 1) % state->count_power; + state->power_history[state->cur_power] = power; + state->error_history[state->cur_power] = power_target - power; + } + for (i = 0; i < (CPU_TEMP_HISTORY_SIZE - 1); i++) { + state->cur_temp = (state->cur_temp + 1) % CPU_TEMP_HISTORY_SIZE; + state->temp_history[state->cur_temp] = temp; + } + state->first = 0; + } + + /* Calculate the integral term normally based on the "power" values */ + sum = 0; + integral = 0; + for (i = 0; i < state->count_power; i++) + integral += state->error_history[i]; + integral *= CPU_PID_INTERVAL; + DBG(" integral: %08x\n", integral); + + /* Calculate the adjusted input (sense value). + * G_r is 12.20 + * integ is 16.16 + * so the result is 28.36 + * + * input target is mpu.ttarget, input max is mpu.tmax + */ + integ_p = ((s64)state->mpu.pid_gr) * (s64)integral; + DBG(" integ_p: %d\n", (int)(deriv_p >> 36)); + sval = (state->mpu.tmax << 16) - ((integ_p >> 20) & 0xffffffff); + adj_in_target = (state->mpu.ttarget << 16); + if (adj_in_target > sval) + adj_in_target = sval; + DBG(" adj_in_target: %d.%03d, ttarget: %d\n", FIX32TOPRINT(adj_in_target), + state->mpu.ttarget); + + /* Calculate the derivative term */ + derivative = state->temp_history[state->cur_temp] - + state->temp_history[(state->cur_temp + CPU_TEMP_HISTORY_SIZE - 1) + % CPU_TEMP_HISTORY_SIZE]; + derivative /= CPU_PID_INTERVAL; + deriv_p = ((s64)state->mpu.pid_gd) * (s64)derivative; + DBG(" deriv_p: %d\n", (int)(deriv_p >> 36)); + sum += deriv_p; + + /* Calculate the proportional term */ + proportional = temp - adj_in_target; + prop_p = ((s64)state->mpu.pid_gp) * (s64)proportional; + DBG(" prop_p: %d\n", (int)(prop_p >> 36)); + sum += prop_p; + + /* Scale sum */ + sum >>= 36; + + DBG(" sum: %d\n", (int)sum); + state->rpm += (s32)sum; + + if (state->rpm < state->mpu.rminn_exhaust_fan) + state->rpm = state->mpu.rminn_exhaust_fan; + if (state->rpm > state->mpu.rmaxn_exhaust_fan) + state->rpm = state->mpu.rmaxn_exhaust_fan; + + intake = (state->rpm * CPU_INTAKE_SCALE) >> 16; + if (intake < state->mpu.rminn_intake_fan) + intake = state->mpu.rminn_intake_fan; + if (intake > state->mpu.rmaxn_intake_fan) + intake = state->mpu.rmaxn_intake_fan; + state->intake_rpm = intake; + + do_set_fans: + DBG("** CPU %d RPM: %d Ex, %d In, overtemp: %d\n", + state->index, (int)state->rpm, intake, state->overtemp); + + /* We should check for errors, shouldn't we ? But then, what + * do we do once the error occurs ? For FCU notified fan + * failures (-EFAULT) we probably want to notify userland + * some way... + */ + if (state->index == 0) { + set_rpm_fan(CPUA_INTAKE_FAN_RPM_ID, intake); + set_rpm_fan(CPUA_EXHAUST_FAN_RPM_ID, state->rpm); + } else { + set_rpm_fan(CPUB_INTAKE_FAN_RPM_ID, intake); + set_rpm_fan(CPUB_EXHAUST_FAN_RPM_ID, state->rpm); + } +} + +/* + * Initialize the state structure for one CPU control loop + */ +static int init_cpu_state(struct cpu_pid_state *state, int index) +{ + state->index = index; + state->first = 1; + state->rpm = 1000; + state->overtemp = 0; + + if (index == 0) + state->monitor = attach_i2c_chip(SUPPLY_MONITOR_ID, "CPU0_monitor"); + else if (index == 1) + state->monitor = attach_i2c_chip(SUPPLY_MONITORB_ID, "CPU1_monitor"); + if (state->monitor == NULL) + goto fail; + + if (read_eeprom(index, &state->mpu)) + goto fail; + + state->count_power = state->mpu.tguardband; + if (state->count_power > CPU_POWER_HISTORY_SIZE) { + printk(KERN_WARNING "Warning ! too many power history slots\n"); + state->count_power = CPU_POWER_HISTORY_SIZE; + } + DBG("CPU %d Using %d power history entries\n", index, state->count_power); + + if (index == 0) { + device_create_file(&of_dev->dev, &dev_attr_cpu0_temperature); + device_create_file(&of_dev->dev, &dev_attr_cpu0_voltage); + device_create_file(&of_dev->dev, &dev_attr_cpu0_current); + device_create_file(&of_dev->dev, &dev_attr_cpu0_exhaust_fan_rpm); + device_create_file(&of_dev->dev, &dev_attr_cpu0_intake_fan_rpm); + } else { + device_create_file(&of_dev->dev, &dev_attr_cpu1_temperature); + device_create_file(&of_dev->dev, &dev_attr_cpu1_voltage); + device_create_file(&of_dev->dev, &dev_attr_cpu1_current); + device_create_file(&of_dev->dev, &dev_attr_cpu1_exhaust_fan_rpm); + device_create_file(&of_dev->dev, &dev_attr_cpu1_intake_fan_rpm); + } + + return 0; + fail: + if (state->monitor) + detach_i2c_chip(state->monitor); + state->monitor = NULL; + + return -ENODEV; +} + +/* + * Dispose of the state data for one CPU control loop + */ +static void dispose_cpu_state(struct cpu_pid_state *state) +{ + if (state->monitor == NULL) + return; + + if (state->index == 0) { + device_remove_file(&of_dev->dev, &dev_attr_cpu0_temperature); + device_remove_file(&of_dev->dev, &dev_attr_cpu0_voltage); + device_remove_file(&of_dev->dev, &dev_attr_cpu0_current); + device_remove_file(&of_dev->dev, &dev_attr_cpu0_exhaust_fan_rpm); + device_remove_file(&of_dev->dev, &dev_attr_cpu0_intake_fan_rpm); + } else { + device_remove_file(&of_dev->dev, &dev_attr_cpu1_temperature); + device_remove_file(&of_dev->dev, &dev_attr_cpu1_voltage); + device_remove_file(&of_dev->dev, &dev_attr_cpu1_current); + device_remove_file(&of_dev->dev, &dev_attr_cpu1_exhaust_fan_rpm); + device_remove_file(&of_dev->dev, &dev_attr_cpu1_intake_fan_rpm); + } + + detach_i2c_chip(state->monitor); + state->monitor = NULL; +} + +/* + * Motherboard backside & U3 heatsink fan control loop + */ +static void do_monitor_backside(struct backside_pid_state *state) +{ + s32 temp, integral, derivative; + s64 integ_p, deriv_p, prop_p, sum; + int i, rc; + + if (--state->ticks != 0) + return; + state->ticks = BACKSIDE_PID_INTERVAL; + + DBG("backside:\n"); + + /* Check fan status */ + rc = get_pwm_fan(BACKSIDE_FAN_PWM_ID); + if (rc < 0) { + printk(KERN_WARNING "Error %d reading backside fan !\n", rc); + /* XXX What do we do now ? */ + } else + state->pwm = rc; + DBG(" current pwm: %d\n", state->pwm); + + /* Get some sensor readings */ + temp = i2c_smbus_read_byte_data(state->monitor, MAX6690_EXT_TEMP) << 16; + state->last_temp = temp; + DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp), + FIX32TOPRINT(BACKSIDE_PID_INPUT_TARGET)); + + /* Store temperature and error in history array */ + state->cur_sample = (state->cur_sample + 1) % BACKSIDE_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = temp - BACKSIDE_PID_INPUT_TARGET; + + /* If first loop, fill the history table */ + if (state->first) { + for (i = 0; i < (BACKSIDE_PID_HISTORY_SIZE - 1); i++) { + state->cur_sample = (state->cur_sample + 1) % + BACKSIDE_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = + temp - BACKSIDE_PID_INPUT_TARGET; + } + state->first = 0; + } + + /* Calculate the integral term */ + sum = 0; + integral = 0; + for (i = 0; i < BACKSIDE_PID_HISTORY_SIZE; i++) + integral += state->error_history[i]; + integral *= BACKSIDE_PID_INTERVAL; + DBG(" integral: %08x\n", integral); + integ_p = ((s64)BACKSIDE_PID_G_r) * (s64)integral; + DBG(" integ_p: %d\n", (int)(integ_p >> 36)); + sum += integ_p; + + /* Calculate the derivative term */ + derivative = state->error_history[state->cur_sample] - + state->error_history[(state->cur_sample + BACKSIDE_PID_HISTORY_SIZE - 1) + % BACKSIDE_PID_HISTORY_SIZE]; + derivative /= BACKSIDE_PID_INTERVAL; + deriv_p = ((s64)BACKSIDE_PID_G_d) * (s64)derivative; + DBG(" deriv_p: %d\n", (int)(deriv_p >> 36)); + sum += deriv_p; + + /* Calculate the proportional term */ + prop_p = ((s64)BACKSIDE_PID_G_p) * (s64)(state->error_history[state->cur_sample]); + DBG(" prop_p: %d\n", (int)(prop_p >> 36)); + sum += prop_p; + + /* Scale sum */ + sum >>= 36; + + DBG(" sum: %d\n", (int)sum); + state->pwm += (s32)sum; + if (state->pwm < BACKSIDE_PID_OUTPUT_MIN) + state->pwm = BACKSIDE_PID_OUTPUT_MIN; + if (state->pwm > BACKSIDE_PID_OUTPUT_MAX) + state->pwm = BACKSIDE_PID_OUTPUT_MAX; + + DBG("** BACKSIDE PWM: %d\n", (int)state->pwm); + set_pwm_fan(BACKSIDE_FAN_PWM_ID, state->pwm); +} + +/* + * Initialize the state structure for the backside fan control loop + */ +static int init_backside_state(struct backside_pid_state *state) +{ + state->ticks = 1; + state->first = 1; + state->pwm = 50; + + state->monitor = attach_i2c_chip(BACKSIDE_MAX_ID, "backside_temp"); + if (state->monitor == NULL) + return -ENODEV; + + device_create_file(&of_dev->dev, &dev_attr_backside_temperature); + device_create_file(&of_dev->dev, &dev_attr_backside_fan_pwm); + + return 0; +} + +/* + * Dispose of the state data for the backside control loop + */ +static void dispose_backside_state(struct backside_pid_state *state) +{ + if (state->monitor == NULL) + return; + + device_remove_file(&of_dev->dev, &dev_attr_backside_temperature); + device_remove_file(&of_dev->dev, &dev_attr_backside_fan_pwm); + + detach_i2c_chip(state->monitor); + state->monitor = NULL; +} + +/* + * Drives bay fan control loop + */ +static void do_monitor_drives(struct drives_pid_state *state) +{ + s32 temp, integral, derivative; + s64 integ_p, deriv_p, prop_p, sum; + int i, rc; + + if (--state->ticks != 0) + return; + state->ticks = DRIVES_PID_INTERVAL; + + DBG("drives:\n"); + + /* Check fan status */ + rc = get_rpm_fan(DRIVES_FAN_RPM_ID, !RPM_PID_USE_ACTUAL_SPEED); + if (rc < 0) { + printk(KERN_WARNING "Error %d reading drives fan !\n", rc); + /* XXX What do we do now ? */ + } else + state->rpm = rc; + DBG(" current rpm: %d\n", state->rpm); + + /* Get some sensor readings */ + temp = le16_to_cpu(i2c_smbus_read_word_data(state->monitor, DS1775_TEMP)) << 8; + state->last_temp = temp; + DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp), + FIX32TOPRINT(DRIVES_PID_INPUT_TARGET)); + + /* Store temperature and error in history array */ + state->cur_sample = (state->cur_sample + 1) % DRIVES_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = temp - DRIVES_PID_INPUT_TARGET; + + /* If first loop, fill the history table */ + if (state->first) { + for (i = 0; i < (DRIVES_PID_HISTORY_SIZE - 1); i++) { + state->cur_sample = (state->cur_sample + 1) % + DRIVES_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = + temp - DRIVES_PID_INPUT_TARGET; + } + state->first = 0; + } + + /* Calculate the integral term */ + sum = 0; + integral = 0; + for (i = 0; i < DRIVES_PID_HISTORY_SIZE; i++) + integral += state->error_history[i]; + integral *= DRIVES_PID_INTERVAL; + DBG(" integral: %08x\n", integral); + integ_p = ((s64)DRIVES_PID_G_r) * (s64)integral; + DBG(" integ_p: %d\n", (int)(integ_p >> 36)); + sum += integ_p; + + /* Calculate the derivative term */ + derivative = state->error_history[state->cur_sample] - + state->error_history[(state->cur_sample + DRIVES_PID_HISTORY_SIZE - 1) + % DRIVES_PID_HISTORY_SIZE]; + derivative /= DRIVES_PID_INTERVAL; + deriv_p = ((s64)DRIVES_PID_G_d) * (s64)derivative; + DBG(" deriv_p: %d\n", (int)(deriv_p >> 36)); + sum += deriv_p; + + /* Calculate the proportional term */ + prop_p = ((s64)DRIVES_PID_G_p) * (s64)(state->error_history[state->cur_sample]); + DBG(" prop_p: %d\n", (int)(prop_p >> 36)); + sum += prop_p; + + /* Scale sum */ + sum >>= 36; + + DBG(" sum: %d\n", (int)sum); + state->rpm += (s32)sum; + if (state->rpm < DRIVES_PID_OUTPUT_MIN) + state->rpm = DRIVES_PID_OUTPUT_MIN; + if (state->rpm > DRIVES_PID_OUTPUT_MAX) + state->rpm = DRIVES_PID_OUTPUT_MAX; + + DBG("** DRIVES RPM: %d\n", (int)state->rpm); + set_rpm_fan(DRIVES_FAN_RPM_ID, state->rpm); +} + +/* + * Initialize the state structure for the drives bay fan control loop + */ +static int init_drives_state(struct drives_pid_state *state) +{ + state->ticks = 1; + state->first = 1; + state->rpm = 1000; + + state->monitor = attach_i2c_chip(DRIVES_DALLAS_ID, "drives_temp"); + if (state->monitor == NULL) + return -ENODEV; + + device_create_file(&of_dev->dev, &dev_attr_drives_temperature); + device_create_file(&of_dev->dev, &dev_attr_drives_fan_rpm); + + return 0; +} + +/* + * Dispose of the state data for the drives control loop + */ +static void dispose_drives_state(struct drives_pid_state *state) +{ + if (state->monitor == NULL) + return; + + device_remove_file(&of_dev->dev, &dev_attr_drives_temperature); + device_remove_file(&of_dev->dev, &dev_attr_drives_fan_rpm); + + detach_i2c_chip(state->monitor); + state->monitor = NULL; +} + +static int call_critical_overtemp(void) +{ + char *argv[] = { critical_overtemp_path, NULL }; + static char *envp[] = { "HOME=/", + "TERM=linux", + "PATH=/sbin:/usr/sbin:/bin:/usr/bin", + NULL }; + + return call_usermodehelper(critical_overtemp_path, argv, envp, 0); +} + + +/* + * Here's the kernel thread that calls the various control loops + */ +static int main_control_loop(void *x) +{ + daemonize("kfand"); + + DBG("main_control_loop started\n"); + + /* Set the PCI fan once for now */ + set_pwm_fan(SLOTS_FAN_PWM_ID, SLOTS_FAN_DEFAULT_PWM); + + while (state == state_attached) { + unsigned long elapsed, start; + + start = jiffies; + + down(&driver_lock); + do_monitor_cpu(&cpu_state[0]); + if (cpu_state[1].monitor != NULL) + do_monitor_cpu(&cpu_state[1]); + do_monitor_backside(&backside_state); + do_monitor_drives(&drives_state); + up(&driver_lock); + + if (critical_state == 1) { + printk(KERN_WARNING "Temperature control detected a critical condition\n"); + printk(KERN_WARNING "Attempting to shut down...\n"); + if (call_critical_overtemp()) { + printk(KERN_WARNING "Can't call %s, power off now!\n", + critical_overtemp_path); + machine_power_off(); + } + } + if (critical_state > 0) + critical_state++; + if (critical_state > MAX_CRITICAL_STATE) { + printk(KERN_WARNING "Shutdown timed out, power off now !\n"); + machine_power_off(); + } + + // FIXME: Deal with signals + set_current_state(TASK_INTERRUPTIBLE); + elapsed = jiffies - start; + if (elapsed < HZ) + schedule_timeout(HZ - elapsed); + } + + DBG("main_control_loop ended\n"); + + ctrl_task = 0; + complete_and_exit(&ctrl_complete, 0); +} + +/* + * Dispose the control loops when tearing down + */ +static void dispose_control_loops(void) +{ + dispose_cpu_state(&cpu_state[0]); + dispose_cpu_state(&cpu_state[1]); + + dispose_backside_state(&backside_state); + dispose_drives_state(&drives_state); +} + +/* + * Create the control loops. U3-0 i2c bus is up, so we can now + * get to the various sensors + */ +static int create_control_loops(void) +{ + struct device_node *np; + + /* Count CPUs from the device-tree, we don't care how many are + * actually used by Linux + */ + cpu_count = 0; + for (np = NULL; NULL != (np = of_find_node_by_type(np, "cpu"));) + cpu_count++; + + DBG("counted %d CPUs in the device-tree\n", cpu_count); + + /* Create control loops for everything. If any fail, everything + * fails + */ + if (init_cpu_state(&cpu_state[0], 0)) + goto fail; + if (cpu_count > 1 && init_cpu_state(&cpu_state[1], 1)) + goto fail; + if (init_backside_state(&backside_state)) + goto fail; + if (init_drives_state(&drives_state)) + goto fail; + + DBG("all control loops up !\n"); + + return 0; + + fail: + DBG("failure creating control loops, disposing\n"); + + dispose_control_loops(); + + return -ENODEV; +} + +/* + * Start the control loops after everything is up, that is create + * the thread that will make them run + */ +static void start_control_loops(void) +{ + init_completion(&ctrl_complete); + + ctrl_task = kernel_thread(main_control_loop, NULL, SIGCHLD | CLONE_KERNEL); +} + +/* + * Stop the control loops when tearing down + */ +static void stop_control_loops(void) +{ + if (ctrl_task != 0) + wait_for_completion(&ctrl_complete); +} + +/* + * Attach to the i2c FCU after detecting U3-1 bus + */ +static int attach_fcu(void) +{ + fcu = attach_i2c_chip(FAN_CTRLER_ID, "fcu"); + if (fcu == NULL) + return -ENODEV; + + DBG("FCU attached\n"); + + return 0; +} + +/* + * Detach from the i2c FCU when tearing down + */ +static void detach_fcu(void) +{ + if (fcu) + detach_i2c_chip(fcu); + fcu = NULL; +} + +/* + * Attach to the i2c controller. We probe the various chips based + * on the device-tree nodes and build everything for the driver to + * run, we then kick the driver monitoring thread + */ +static int therm_pm72_attach(struct i2c_adapter *adapter) +{ + down(&driver_lock); + + /* Check state */ + if (state == state_detached) + state = state_attaching; + if (state != state_attaching) { + up(&driver_lock); + return 0; + } + + /* Check if we are looking for one of these */ + if (u3_0 == NULL && !strcmp(adapter->name, "u3 0")) { + u3_0 = adapter; + DBG("found U3-0, creating control loops\n"); + if (create_control_loops()) + u3_0 = NULL; + } else if (u3_1 == NULL && !strcmp(adapter->name, "u3 1")) { + u3_1 = adapter; + DBG("found U3-1, attaching FCU\n"); + if (attach_fcu()) + u3_1 = NULL; + } + /* We got all we need, start control loops */ + if (u3_0 != NULL && u3_1 != NULL) { + DBG("everything up, starting control loops\n"); + state = state_attached; + start_control_loops(); + } + up(&driver_lock); + + return 0; +} + +/* + * Called on every adapter when the driver or the i2c controller + * is going away. + */ +static int therm_pm72_detach(struct i2c_adapter *adapter) +{ + down(&driver_lock); + + if (state != state_detached) + state = state_detaching; + + /* Stop control loops if any */ + DBG("stopping control loops\n"); + up(&driver_lock); + stop_control_loops(); + down(&driver_lock); + + if (u3_0 != NULL && !strcmp(adapter->name, "u3 0")) { + DBG("lost U3-0, disposing control loops\n"); + dispose_control_loops(); + u3_0 = NULL; + } + + if (u3_1 != NULL && !strcmp(adapter->name, "u3 1")) { + DBG("lost U3-1, detaching FCU\n"); + detach_fcu(); + u3_1 = NULL; + } + if (u3_0 == NULL && u3_1 == NULL) + state = state_detached; + + up(&driver_lock); + + return 0; +} + +static int fcu_of_probe(struct of_device* dev, const struct of_match *match) +{ + int rc; + + state = state_detached; + + rc = i2c_add_driver(&therm_pm72_driver); + if (rc < 0) + return rc; + return 0; +} + +static int fcu_of_remove(struct of_device* dev) +{ + i2c_del_driver(&therm_pm72_driver); + + return 0; +} + +static struct of_match fcu_of_match[] = +{ + { + .name = OF_ANY_MATCH, + .type = "fcu", + .compatible = OF_ANY_MATCH + }, + {}, +}; + +static struct of_platform_driver fcu_of_platform_driver = +{ + .name = "temperature", + .match_table = fcu_of_match, + .probe = fcu_of_probe, + .remove = fcu_of_remove +}; + +/* + * Check machine type, attach to i2c controller + */ +static int __init therm_pm72_init(void) +{ + struct device_node *np; + + if (!machine_is_compatible("PowerMac7,2")) + return -ENODEV; + + printk(KERN_INFO "PowerMac G5 Thermal control driver %s\n", VERSION); + + np = of_find_node_by_type(NULL, "fcu"); + if (np == NULL) { + printk(KERN_ERR "Can't find FCU in device-tree !\n"); + return -ENODEV; + } + of_dev = of_platform_device_create(np, "temperature"); + if (of_dev == NULL) { + printk(KERN_ERR "Can't register FCU platform device !\n"); + return -ENODEV; + } + + of_register_driver(&fcu_of_platform_driver); + + return 0; +} + +static void __exit therm_pm72_exit(void) +{ + of_unregister_driver(&fcu_of_platform_driver); + + if (of_dev) + of_device_unregister(of_dev); +} + +module_init(therm_pm72_init); +module_exit(therm_pm72_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Driver for Apple's PowerMac7,2 G5 thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/therm_pm72.h b/drivers/macintosh/therm_pm72.h new file mode 100644 index 000000000000..3a566fff51d7 --- /dev/null +++ b/drivers/macintosh/therm_pm72.h @@ -0,0 +1,236 @@ +#ifndef __THERM_PMAC_7_2_H__ +#define __THERM_PMAC_7_2_H__ + +typedef unsigned short fu16; +typedef int fs32; +typedef short fs16; + +struct mpu_data +{ + u8 signature; /* 0x00 - EEPROM sig. */ + u8 bytes_used; /* 0x01 - Bytes used in eeprom (160 ?) */ + u8 size; /* 0x02 - EEPROM size (256 ?) */ + u8 version; /* 0x03 - EEPROM version */ + u32 data_revision; /* 0x04 - Dataset revision */ + u8 processor_bin_code[3]; /* 0x08 - Processor BIN code */ + u8 bin_code_expansion; /* 0x0b - ??? (padding ?) */ + u8 processor_num; /* 0x0c - Number of CPUs on this MPU */ + u8 input_mul_bus_div; /* 0x0d - Clock input multiplier/bus divider */ + u8 reserved1[2]; /* 0x0e - */ + u32 input_clk_freq_high; /* 0x10 - Input clock frequency high */ + u8 cpu_nb_target_cycles; /* 0x14 - ??? */ + u8 cpu_statlat; /* 0x15 - ??? */ + u8 cpu_snooplat; /* 0x16 - ??? */ + u8 cpu_snoopacc; /* 0x17 - ??? */ + u8 nb_paamwin; /* 0x18 - ??? */ + u8 nb_statlat; /* 0x19 - ??? */ + u8 nb_snooplat; /* 0x1a - ??? */ + u8 nb_snoopwin; /* 0x1b - ??? */ + u8 api_bus_mode; /* 0x1c - ??? */ + u8 reserved2[3]; /* 0x1d - */ + u32 input_clk_freq_low; /* 0x20 - Input clock frequency low */ + u8 processor_card_slot; /* 0x24 - Processor card slot number */ + u8 reserved3[2]; /* 0x25 - */ + u8 padjmax; /* 0x27 - Max power adjustment (Not in OF!) */ + u8 ttarget; /* 0x28 - Target temperature */ + u8 tmax; /* 0x29 - Max temperature */ + u8 pmaxh; /* 0x2a - Max power */ + u8 tguardband; /* 0x2b - Guardband temp ??? Hist. len in OSX */ + fs32 pid_gp; /* 0x2c - PID proportional gain */ + fs32 pid_gr; /* 0x30 - PID reset gain */ + fs32 pid_gd; /* 0x34 - PID derivative gain */ + fu16 voph; /* 0x38 - Vop High */ + fu16 vopl; /* 0x3a - Vop Low */ + fs16 nactual_die; /* 0x3c - nActual Die */ + fs16 nactual_heatsink; /* 0x3e - nActual Heatsink */ + fs16 nactual_system; /* 0x40 - nActual System */ + u16 calibration_flags; /* 0x42 - Calibration flags */ + fu16 mdiode; /* 0x44 - Diode M value (scaling factor) */ + fs16 bdiode; /* 0x46 - Diode B value (offset) */ + fs32 theta_heat_sink; /* 0x48 - Theta heat sink */ + u16 rminn_intake_fan; /* 0x4c - Intake fan min RPM */ + u16 rmaxn_intake_fan; /* 0x4e - Intake fan max RPM */ + u16 rminn_exhaust_fan; /* 0x50 - Exhaust fan min RPM */ + u16 rmaxn_exhaust_fan; /* 0x52 - Exhaust fan max RPM */ + u8 processor_part_num[8]; /* 0x54 - Processor part number */ + u32 processor_lot_num; /* 0x5c - Processor lot number */ + u8 orig_card_sernum[0x10]; /* 0x60 - Card original serial number */ + u8 curr_card_sernum[0x10]; /* 0x70 - Card current serial number */ + u8 mlb_sernum[0x18]; /* 0x80 - MLB serial number */ + u32 checksum1; /* 0x98 - */ + u32 checksum2; /* 0x9c - */ +}; /* Total size = 0xa0 */ + +/* Display a 16.16 fixed point value */ +#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16) + +/* + * Maximum number of seconds to be in critical state (after a + * normal shutdown attempt). If the machine isn't down after + * this counter elapses, we force an immediate machine power + * off. + */ +#define MAX_CRITICAL_STATE 30 +static char * critical_overtemp_path = "/sbin/critical_overtemp"; + +/* + * This option is "weird" :) Basically, if you define this to 1 + * the control loop for the RPMs fans (not PWMs) will apply the + * correction factor obtained from the PID to the _actual_ RPM + * speed read from the FCU. + * If you define the below constant to 0, then it will be + * applied to the setpoint RPM speed, that is basically the + * speed we proviously "asked" for. + * + * I'm not sure which of these Apple's algorithm is supposed + * to use + */ +#define RPM_PID_USE_ACTUAL_SPEED 1 + +/* + * i2c IDs. Currently, we hard code those and assume that + * the FCU is on U3 bus 1 while all sensors are on U3 bus + * 0. This appear to be safe enough for this first version + * of the driver, though I would accept any clean patch + * doing a better use of the device-tree without turning the + * while i2c registration mecanism into a racy mess + */ +#define FAN_CTRLER_ID 0x15e +#define SUPPLY_MONITOR_ID 0x58 +#define SUPPLY_MONITORB_ID 0x5a +#define DRIVES_DALLAS_ID 0x94 +#define BACKSIDE_MAX_ID 0x98 + +/* + * Some MAX6690 & DS1775 register definitions + */ +#define MAX6690_INT_TEMP 0 +#define MAX6690_EXT_TEMP 1 +#define DS1775_TEMP 0 + +/* + * Scaling factors for the AD7417 ADC converters (except + * for the CPU diode which is obtained from the EEPROM). + * Those values are obtained from the property list of + * the darwin driver + */ +#define ADC_12V_CURRENT_SCALE 0x0320 /* _AD2 */ +#define ADC_CPU_VOLTAGE_SCALE 0x00a0 /* _AD3 */ +#define ADC_CPU_CURRENT_SCALE 0x1f40 /* _AD4 */ + +/* + * PID factors for the U3/Backside fan control loop + */ +#define BACKSIDE_FAN_PWM_ID 1 +#define BACKSIDE_PID_G_d 0x02800000 +#define BACKSIDE_PID_G_p 0x00500000 +#define BACKSIDE_PID_G_r 0x00000000 +#define BACKSIDE_PID_INPUT_TARGET 0x00410000 +#define BACKSIDE_PID_INTERVAL 5 +#define BACKSIDE_PID_OUTPUT_MAX 100 +#define BACKSIDE_PID_OUTPUT_MIN 20 +#define BACKSIDE_PID_HISTORY_SIZE 2 + +struct backside_pid_state +{ + int ticks; + struct i2c_client * monitor; + s32 sample_history[BACKSIDE_PID_HISTORY_SIZE]; + s32 error_history[BACKSIDE_PID_HISTORY_SIZE]; + int cur_sample; + s32 last_temp; + int pwm; + int first; +}; + +/* + * PID factors for the Drive Bay fan control loop + */ +#define DRIVES_FAN_RPM_ID 2 +#define DRIVES_PID_G_d 0x01e00000 +#define DRIVES_PID_G_p 0x00500000 +#define DRIVES_PID_G_r 0x00000000 +#define DRIVES_PID_INPUT_TARGET 0x00280000 +#define DRIVES_PID_INTERVAL 5 +#define DRIVES_PID_OUTPUT_MAX 4000 +#define DRIVES_PID_OUTPUT_MIN 300 +#define DRIVES_PID_HISTORY_SIZE 2 + +struct drives_pid_state +{ + int ticks; + struct i2c_client * monitor; + s32 sample_history[BACKSIDE_PID_HISTORY_SIZE]; + s32 error_history[BACKSIDE_PID_HISTORY_SIZE]; + int cur_sample; + s32 last_temp; + int rpm; + int first; +}; + +#define SLOTS_FAN_PWM_ID 2 +#define SLOTS_FAN_DEFAULT_PWM 50 /* Do better here ! */ + +/* + * IDs in Darwin for the sensors & fans + * + * CPU A AD7417_TEMP 10 (CPU A ambient temperature) + * CPU A AD7417_AD1 11 (CPU A diode temperature) + * CPU A AD7417_AD2 12 (CPU A 12V current) + * CPU A AD7417_AD3 13 (CPU A voltage) + * CPU A AD7417_AD4 14 (CPU A current) + * + * CPU A FAKE POWER 48 (I_V_inputs: 13, 14) + * + * CPU B AD7417_TEMP 15 (CPU B ambient temperature) + * CPU B AD7417_AD1 16 (CPU B diode temperature) + * CPU B AD7417_AD2 17 (CPU B 12V current) + * CPU B AD7417_AD3 18 (CPU B voltage) + * CPU B AD7417_AD4 19 (CPU B current) + * + * CPU B FAKE POWER 49 (I_V_inputs: 18, 19) + */ + +#define CPUA_INTAKE_FAN_RPM_ID 3 +#define CPUA_EXHAUST_FAN_RPM_ID 4 +#define CPUB_INTAKE_FAN_RPM_ID 5 +#define CPUB_EXHAUST_FAN_RPM_ID 6 + +#define CPU_INTAKE_SCALE 0x0000f852 +#define CPU_TEMP_HISTORY_SIZE 2 +#define CPU_POWER_HISTORY_SIZE 10 +#define CPU_PID_INTERVAL 1 +#define CPU_MAX_OVERTEMP 30 + +struct cpu_pid_state +{ + int index; + struct i2c_client * monitor; + struct mpu_data mpu; + int overtemp; + s32 temp_history[CPU_TEMP_HISTORY_SIZE]; + int cur_temp; + s32 power_history[CPU_POWER_HISTORY_SIZE]; + s32 error_history[CPU_POWER_HISTORY_SIZE]; + int cur_power; + int count_power; + int rpm; + int intake_rpm; + s32 voltage; + s32 current_a; + s32 last_temp; + int first; +}; + +/* + * Driver state + */ +enum { + state_detached, + state_attaching, + state_attached, + state_detaching, +}; + + +#endif /* __THERM_PMAC_7_2_H__ */ diff --git a/drivers/macintosh/therm_windtunnel.c b/drivers/macintosh/therm_windtunnel.c new file mode 100644 index 000000000000..1836aff984d8 --- /dev/null +++ b/drivers/macintosh/therm_windtunnel.c @@ -0,0 +1,456 @@ +/* + * Creation Date: <2003/03/14 20:54:13 samuel> + * Time-stamp: <2003/03/15 18:55:53 samuel> + * + * <therm_windtunnel.c> + * + * The G4 "windtunnel" has a single fan controlled by a + * DS1775 fan controller and an ADM1030 thermostat. + * + * The fan controller is equipped with a temperature sensor + * which measures the case temperature. The ADM censor + * measures the CPU temperature. This driver tunes the + * behavior of the fan. It is based upon empirical observations + * of the 'AppleFan' driver under OSX. + * + * WARNING: This driver has only been testen on Apple's + * 1.25 MHz Dual G4 (March 03). Other machines might have + * a different thermal design. It is tuned for a CPU + * temperatur around 57 C. + * + * Copyright (C) 2003 Samuel Rydh (samuel@ibrium.se) + * + * Loosely based upon 'thermostat.c' written by Benjamin Herrenschmidt + * + * 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 + * + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/workqueue.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> + +MODULE_AUTHOR("Samuel Rydh <samuel@ibrium.se>"); +MODULE_DESCRIPTION("Apple G4 (windtunnel) fan driver"); +MODULE_LICENSE("GPL"); + +#define LOG_TEMP 0 /* continously log temperature */ + +/* scan 0x48-0x4f (DS1775) and 0x2c-2x2f (ADM1030) */ +static unsigned short normal_i2c[] = { 0x49, 0x2c, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { 0x48, 0x4f, 0x2c, 0x2f, I2C_CLIENT_END }; +static struct work_struct poll_work; + +I2C_CLIENT_INSMOD; + +#define I2C_DRIVERID_G4FAN 0x9001 /* fixme */ + +#define THERMOSTAT_CLIENT_ID 1 +#define FAN_CLIENT_ID 2 + +struct temp_range { + u8 high; /* start the fan */ + u8 low; /* stop the fan */ +}; +struct apple_thermal_info { + u8 id; /* implementation ID */ + u8 fan_count; /* number of fans */ + u8 thermostat_count; /* number of thermostats */ + u8 unused[5]; + struct temp_range ranges[4]; /* temperature ranges (may be [])*/ +}; + +static int do_detect( struct i2c_adapter *adapter, int addr, int kind); + +static struct { + struct i2c_client *thermostat; + struct i2c_client *fan; + int error; + struct timer_list timer; + + int overheat_temp; /* 100% fan at this temp */ + int overheat_hyst; + int temp; + int casetemp; + int fan_level; /* active fan_table setting */ + + int downind; + int upind; + + int r0, r1, r20, r23, r25; /* saved register */ +} x; + +static struct { + int temp; + int fan_setting; +} fan_up_table[] = { + { 0x0000, 11 }, /* min fan */ + { 0x3900, 8 }, /* 57.0 C */ + { 0x3a4a, 7 }, /* 58.3 C */ + { 0x3ad3, 6 }, /* 58.8 C */ + { 0x3b3c, 5 }, /* 59.2 C */ + { 0x3b94, 4 }, /* 59.6 C */ + { 0x3be3, 3 }, /* 58.9 C */ + { 0x3c29, 2 }, /* 59.2 C */ + { 0xffff, 1 } /* on fire */ +}; +static struct { + int temp; + int fan_setting; +} fan_down_table[] = { + { 0x3700, 11 }, /* 55.0 C */ + { 0x374a, 6 }, + { 0x3800, 7 }, /* 56.0 C */ + { 0x3900, 8 }, /* 57.0 C */ + { 0x3a4a, 7 }, /* 58.3 C */ + { 0x3ad3, 6 }, /* 58.8 C */ + { 0x3b3c, 5 }, /* 59.2 C */ + { 0x3b94, 4 }, /* 58.9 C */ + { 0x3be3, 3 }, /* 58.9 C */ + { 0x3c29, 2 }, /* 59.2 C */ + { 0xffff, 1 } +}; + +static int +write_reg( struct i2c_client *cl, int reg, int data, int len ) +{ + u8 tmp[3]; + + if( len < 1 || len > 2 || data < 0 ) + return -EINVAL; + + tmp[0] = reg; + tmp[1] = (len == 1) ? data : (data >> 8); + tmp[2] = data; + len++; + + if( i2c_master_send(cl, tmp, len) != len ) + return -ENODEV; + return 0; +} + +static int +read_reg( struct i2c_client *cl, int reg, int len ) +{ + u8 buf[2]; + + if( len != 1 && len != 2 ) + return -EINVAL; + buf[0] = reg; + if( i2c_master_send(cl, buf, 1) != 1 ) + return -ENODEV; + if( i2c_master_recv(cl, buf, len) != len ) + return -ENODEV; + return (len == 2)? ((unsigned int)buf[0] << 8) | buf[1] : buf[0]; +} + + +static void +print_temp( const char *s, int temp ) +{ + printk("%s%d.%d C", s ? s : "", temp>>8, (temp & 255)*10/256 ); +} + +static void +tune_fan( int fan_setting ) +{ + int val = (fan_setting << 3) | 7; + x.fan_level = fan_setting; + + //write_reg( x.fan, 0x24, val, 1 ); + write_reg( x.fan, 0x25, val, 1 ); + write_reg( x.fan, 0x20, 0, 1 ); + print_temp("CPU-temp: ", x.temp ); + if( x.casetemp ) + print_temp(", Case: ", x.casetemp ); + printk(" Tuning fan: %d (%02x)\n", fan_setting, val ); +} + +static void +poll_temp( void *param ) +{ + int temp = read_reg( x.thermostat, 0, 2 ); + int i, level, casetemp; + + /* this actually occurs when the computer is loaded */ + if( temp < 0 ) + goto out; + + casetemp = read_reg(x.fan, 0x0b, 1) << 8; + casetemp |= (read_reg(x.fan, 0x06, 1) & 0x7) << 5; + + if( LOG_TEMP && x.temp != temp ) { + print_temp("CPU-temp: ", temp ); + print_temp(", Case: ", casetemp ); + printk(", Fan: %d\n", x.fan_level ); + } + x.temp = temp; + x.casetemp = casetemp; + + level = -1; + for( i=0; (temp & 0xffff) > fan_down_table[i].temp ; i++ ) + ; + if( i < x.downind ) + level = fan_down_table[i].fan_setting; + x.downind = i; + + for( i=0; (temp & 0xfffe) >= fan_up_table[i+1].temp ; i++ ) + ; + if( x.upind < i ) + level = fan_up_table[i].fan_setting; + x.upind = i; + + if( level >= 0 ) + tune_fan( level ); + out: + x.timer.expires = jiffies + 8*HZ; + add_timer( &x.timer ); +} + +static void +schedule_poll( unsigned long t ) +{ + schedule_work(&poll_work); +} + +/************************************************************************/ +/* i2c probing and setup */ +/************************************************************************/ + +static int +do_attach( struct i2c_adapter *adapter ) +{ + return i2c_probe( adapter, &addr_data, &do_detect ); +} + +static int +do_detach( struct i2c_client *client ) +{ + int err; + + printk("do_detach: id %d\n", client->id ); + if( (err=i2c_detach_client(client)) ) { + printk("failed to detach thermostat client\n"); + return err; + } + kfree( client ); + return 0; +} + +static struct i2c_driver g4fan_driver = { + .name = "Apple G4 Thermostat/Fan", + .id = I2C_DRIVERID_G4FAN, + .flags = I2C_DF_NOTIFY, + .attach_adapter = &do_attach, + .detach_client = &do_detach, + .command = NULL, +}; + +static int +detect_fan( struct i2c_client *cl ) +{ + /* check that this is an ADM1030 */ + if( read_reg(cl, 0x3d, 1) != 0x30 || read_reg(cl, 0x3e, 1) != 0x41 ) + goto out; + printk("ADM1030 fan controller detected at %02x\n", cl->addr ); + + if( x.fan ) { + x.error |= 2; + goto out; + } + x.fan = cl; + cl->id = FAN_CLIENT_ID; + strncpy( cl->name, "ADM1030 fan controller", sizeof(cl->name) ); + + if( i2c_attach_client( cl ) ) + goto out; + return 0; + out: + if( cl != x.fan ) + kfree( cl ); + return 0; +} + +static int +detect_thermostat( struct i2c_client *cl ) +{ + int hyst_temp, os_temp, temp; + + if( (temp=read_reg(cl, 0, 2)) < 0 ) + goto out; + + /* temperature sanity check */ + if( temp < 0x1600 || temp > 0x3c00 ) + goto out; + hyst_temp = read_reg(cl, 2, 2); + os_temp = read_reg(cl, 3, 2); + if( hyst_temp < 0 || os_temp < 0 ) + goto out; + + printk("DS1775 digital thermometer detected at %02x\n", cl->addr ); + print_temp("Temp: ", temp ); + print_temp(" Hyst: ", hyst_temp ); + print_temp(" OS: ", os_temp ); + printk("\n"); + + if( x.thermostat ) { + x.error |= 1; + goto out; + } + x.temp = temp; + x.thermostat = cl; + x.overheat_temp = os_temp; + x.overheat_hyst = hyst_temp; + + cl->id = THERMOSTAT_CLIENT_ID; + strncpy( cl->name, "DS1775 thermostat", sizeof(cl->name) ); + + if( i2c_attach_client( cl ) ) + goto out; + return 0; +out: + kfree( cl ); + return 0; +} + +static int +do_detect( struct i2c_adapter *adapter, int addr, int kind ) +{ + struct i2c_client *cl; + + if( strncmp(adapter->name, "uni-n", 5) ) + return 0; + if( !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE) ) + return 0; + + if( !(cl=kmalloc( sizeof(struct i2c_client), GFP_KERNEL )) ) + return -ENOMEM; + memset( cl, 0, sizeof(struct i2c_client) ); + + cl->addr = addr; + cl->adapter = adapter; + cl->driver = &g4fan_driver; + cl->flags = 0; + + if( addr < 0x48 ) + return detect_fan( cl ); + return detect_thermostat( cl ); +} + +#define PRINT_REG( r ) printk("reg %02x = %02x\n", r, read_reg(x.fan, r, 1) ) + +static int __init +g4fan_init( void ) +{ + struct apple_thermal_info *info; + struct device_node *np; + int ret, val; + + np = of_find_node_by_name(NULL, "power-mgt"); + if (np == NULL) + return -ENODEV; + info = (struct apple_thermal_info*)get_property(np, "thermal-info", NULL); + of_node_put(np); + if (info == NULL) + return -ENODEV; + + /* check for G4 "Windtunnel" SMP */ + if( machine_is_compatible("PowerMac3,6") ) { + if( info->id != 3 ) { + printk(KERN_ERR "g4fan: design id %d unknown\n", info->id); + return -ENODEV; + } + } else { + printk(KERN_ERR "g4fan: unsupported machine type\n"); + return -ENODEV; + } + if( (ret=i2c_add_driver(&g4fan_driver)) ) + return ret; + + if( !x.thermostat || !x.fan ) { + i2c_del_driver(&g4fan_driver ); + return -ENODEV; + } + + /* save registers (if we unload the module) */ + x.r0 = read_reg( x.fan, 0x00, 1 ); + x.r1 = read_reg( x.fan, 0x01, 1 ); + x.r20 = read_reg( x.fan, 0x20, 1 ); + x.r23 = read_reg( x.fan, 0x23, 1 ); + x.r25 = read_reg( x.fan, 0x25, 1 ); + + /* improve measurement resolution (convergence time 1.5s) */ + if( (val=read_reg( x.thermostat, 1, 1 )) >= 0 ) { + val |= 0x60; + if( write_reg( x.thermostat, 1, val, 1 ) ) + printk("Failed writing config register\n"); + } + /* disable interrupts and TAC input */ + write_reg( x.fan, 0x01, 0x01, 1 ); + /* enable filter */ + write_reg( x.fan, 0x23, 0x91, 1 ); + /* remote temp. controls fan */ + write_reg( x.fan, 0x00, 0x95, 1 ); + + /* The thermostat (which besides measureing temperature controls + * has a THERM output which puts the fan on 100%) is usually + * set to kick in at 80 C (chip default). We reduce this a bit + * to be on the safe side (OSX doesn't)... + */ + if( x.overheat_temp == (80 << 8) ) { + x.overheat_temp = 65 << 8; + x.overheat_hyst = 60 << 8; + write_reg( x.thermostat, 2, x.overheat_hyst, 2 ); + write_reg( x.thermostat, 3, x.overheat_temp, 2 ); + + print_temp("Reducing overheating limit to ", x.overheat_temp ); + print_temp(" (Hyst: ", x.overheat_hyst ); + printk(")\n"); + } + + /* set an initial fan setting */ + x.upind = x.downind = 1; + tune_fan( fan_up_table[x.upind].fan_setting ); + + INIT_WORK(&poll_work, poll_temp, NULL); + + init_timer( &x.timer ); + x.timer.expires = jiffies + 8*HZ; + x.timer.function = schedule_poll; + add_timer( &x.timer ); + return 0; +} + +static void __exit +g4fan_exit( void ) +{ + del_timer( &x.timer ); + + write_reg( x.fan, 0x01, x.r1, 1 ); + write_reg( x.fan, 0x20, x.r20, 1 ); + write_reg( x.fan, 0x23, x.r23, 1 ); + write_reg( x.fan, 0x25, x.r25, 1 ); + write_reg( x.fan, 0x00, x.r0, 1 ); + + i2c_del_driver( &g4fan_driver ); +} + +module_init(g4fan_init); +module_exit(g4fan_exit); + diff --git a/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c index 99007e8a3981..46dd331673c2 100644 --- a/drivers/macintosh/via-pmu.c +++ b/drivers/macintosh/via-pmu.c @@ -168,6 +168,7 @@ static struct proc_dir_entry *proc_pmu_root; static struct proc_dir_entry *proc_pmu_info; static struct proc_dir_entry *proc_pmu_irqstats; static struct proc_dir_entry *proc_pmu_options; +static int option_server_mode; #ifdef CONFIG_PMAC_PBOOK int pmu_battery_count; @@ -334,7 +335,8 @@ find_via_pmu(void) pmu_kind = PMU_PADDINGTON_BASED; else if (device_is_compatible(vias->parent, "heathrow")) pmu_kind = PMU_HEATHROW_BASED; - else if (device_is_compatible(vias->parent, "Keylargo")) { + else if (device_is_compatible(vias->parent, "Keylargo") + || device_is_compatible(vias->parent, "K2-Keylargo")) { struct device_node *gpio, *gpiop; pmu_kind = PMU_KEYLARGO_BASED; @@ -349,6 +351,8 @@ find_via_pmu(void) if (gpiop && gpiop->n_addrs) { gpio_reg = ioremap(gpiop->addrs->address, 0x10); gpio = find_devices("extint-gpio1"); + if (gpio == NULL) + gpio = find_devices("pmu-interrupt"); if (gpio && gpio->parent == gpiop && gpio->n_intrs) gpio_irq = gpio->intrs[0].line; } @@ -564,7 +568,19 @@ init_pmu(void) pmu_wait_complete(&req); if (req.reply_len > 0) pmu_version = req.reply[0]; - + + /* Read server mode setting */ + if (pmu_kind == PMU_KEYLARGO_BASED) { + pmu_request(&req, NULL, 2, PMU_POWER_EVENTS, + PMU_PWR_GET_POWERUP_EVENTS); + pmu_wait_complete(&req); + if (req.reply_len == 2) { + if (req.reply[1] & PMU_PWR_WAKEUP_AC_INSERT) + option_server_mode = 1; + printk(KERN_INFO "via-pmu: Server Mode is %s\n", + option_server_mode ? "enabled" : "disabled"); + } + } return 1; } @@ -583,6 +599,28 @@ static inline void wakeup_decrementer(void) last_jiffy_stamp(0) = tb_last_stamp = get_tbl(); } +static void pmu_set_server_mode(int server_mode) +{ + struct adb_request req; + + if (pmu_kind != PMU_KEYLARGO_BASED) + return; + + option_server_mode = server_mode; + pmu_request(&req, NULL, 2, PMU_POWER_EVENTS, PMU_PWR_GET_POWERUP_EVENTS); + pmu_wait_complete(&req); + if (req.reply_len < 2) + return; + if (server_mode) + pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, + PMU_PWR_SET_POWERUP_EVENTS, + req.reply[0], PMU_PWR_WAKEUP_AC_INSERT); + else + pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, + PMU_PWR_CLR_POWERUP_EVENTS, + req.reply[0], PMU_PWR_WAKEUP_AC_INSERT); + pmu_wait_complete(&req); +} #ifdef CONFIG_PMAC_PBOOK @@ -845,6 +883,8 @@ proc_read_options(char *page, char **start, off_t off, if (pmu_kind == PMU_KEYLARGO_BASED && can_sleep) p += sprintf(p, "lid_wakeup=%d\n", option_lid_wakeup); #endif /* CONFIG_PMAC_PBOOK */ + if (pmu_kind == PMU_KEYLARGO_BASED) + p += sprintf(p, "server_mode=%d\n", option_server_mode); return p - page; } @@ -884,6 +924,12 @@ proc_write_options(struct file *file, const char __user *buffer, if (!strcmp(label, "lid_wakeup")) option_lid_wakeup = ((*val) == '1'); #endif /* CONFIG_PMAC_PBOOK */ + if (pmu_kind == PMU_KEYLARGO_BASED && !strcmp(label, "server_mode")) { + int new_value; + new_value = ((*val) == '1'); + if (new_value != option_server_mode) + pmu_set_server_mode(new_value); + } return fcount; } @@ -1758,6 +1804,11 @@ pmu_shutdown(void) pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB | PMU_INT_TICK ); pmu_wait_complete(&req); + } else { + /* Disable server mode on shutdown or we'll just + * wake up again + */ + pmu_set_server_mode(0); } pmu_request(&req, NULL, 5, PMU_SHUTDOWN, diff --git a/drivers/scsi/mac53c94.c b/drivers/scsi/mac53c94.c index 62a146b53b2d..9415c2bbb2e0 100644 --- a/drivers/scsi/mac53c94.c +++ b/drivers/scsi/mac53c94.c @@ -23,6 +23,7 @@ #include <asm/prom.h> #include <asm/system.h> #include <asm/pci-bridge.h> +#include <asm/macio.h> #include "scsi.h" #include "hosts.h" @@ -37,13 +38,12 @@ enum fsc_phase { }; struct fsc_state { - volatile struct mac53c94_regs *regs; + struct mac53c94_regs *regs; int intr; - volatile struct dbdma_regs *dma; + struct dbdma_regs *dma; int dmaintr; int clk_freq; struct Scsi_Host *host; - struct fsc_state *next; Scsi_Cmnd *request_q; Scsi_Cmnd *request_qtail; Scsi_Cmnd *current_req; /* req we're currently working on */ @@ -52,151 +52,23 @@ struct fsc_state { void *dma_cmd_space; struct pci_dev *pdev; dma_addr_t dma_addr; + struct macio_dev *mdev; }; -static struct fsc_state *all_53c94s; - static void mac53c94_init(struct fsc_state *); static void mac53c94_start(struct fsc_state *); static void mac53c94_interrupt(int, void *, struct pt_regs *); static irqreturn_t do_mac53c94_interrupt(int, void *, struct pt_regs *); static void cmd_done(struct fsc_state *, int result); static void set_dma_cmds(struct fsc_state *, Scsi_Cmnd *); -static int data_goes_out(Scsi_Cmnd *); - -int -mac53c94_detect(Scsi_Host_Template *tp) -{ - struct device_node *node; - int nfscs; - struct fsc_state *state, **prev_statep; - struct Scsi_Host *host; - void *dma_cmd_space; - unsigned char *clkprop; - int proplen; - struct pci_dev *pdev; - u8 pbus, devfn; - - nfscs = 0; - prev_statep = &all_53c94s; - for (node = find_devices("53c94"); node != 0; node = node->next) { - if (node->n_addrs != 2 || node->n_intrs != 2) { - printk(KERN_ERR "mac53c94: expected 2 addrs and intrs" - " (got %d/%d) for node %s\n", - node->n_addrs, node->n_intrs, node->full_name); - continue; - } - - pdev = NULL; - if (node->parent != NULL - && !pci_device_from_OF_node(node->parent, &pbus, &devfn)) - pdev = pci_find_slot(pbus, devfn); - if (pdev == NULL) { - printk(KERN_ERR "mac53c94: can't find PCI device " - "for %s\n", node->full_name); - continue; - } - - host = scsi_register(tp, sizeof(struct fsc_state)); - if (host == NULL) - break; - host->unique_id = nfscs; - - state = (struct fsc_state *) host->hostdata; - if (state == 0) { - /* "can't happen" */ - printk(KERN_ERR "mac53c94: no state for %s?!\n", - node->full_name); - scsi_unregister(host); - break; - } - state->host = host; - state->pdev = pdev; - - state->regs = (volatile struct mac53c94_regs *) - ioremap(node->addrs[0].address, 0x1000); - state->intr = node->intrs[0].line; - state->dma = (volatile struct dbdma_regs *) - ioremap(node->addrs[1].address, 0x1000); - state->dmaintr = node->intrs[1].line; - if (state->regs == NULL || state->dma == NULL) { - printk(KERN_ERR "mac53c94: ioremap failed for %s\n", - node->full_name); - if (state->dma != NULL) - iounmap(state->dma); - if (state->regs != NULL) - iounmap(state->regs); - scsi_unregister(host); - break; - } - - clkprop = get_property(node, "clock-frequency", &proplen); - if (clkprop == NULL || proplen != sizeof(int)) { - printk(KERN_ERR "%s: can't get clock frequency, " - "assuming 25MHz\n", node->full_name); - state->clk_freq = 25000000; - } else - state->clk_freq = *(int *)clkprop; - - /* Space for dma command list: +1 for stop command, - +1 to allow for aligning. */ - dma_cmd_space = kmalloc((host->sg_tablesize + 2) * - sizeof(struct dbdma_cmd), GFP_KERNEL); - if (dma_cmd_space == 0) { - printk(KERN_ERR "mac53c94: couldn't allocate dma " - "command space for %s\n", node->full_name); - goto err_cleanup; - } - state->dma_cmds = (struct dbdma_cmd *) - DBDMA_ALIGN(dma_cmd_space); - memset(state->dma_cmds, 0, (host->sg_tablesize + 1) - * sizeof(struct dbdma_cmd)); - state->dma_cmd_space = dma_cmd_space; - - *prev_statep = state; - prev_statep = &state->next; - - if (request_irq(state->intr, do_mac53c94_interrupt, 0, - "53C94", state)) { - printk(KERN_ERR "mac53C94: can't get irq %d for %s\n", - state->intr, node->full_name); - err_cleanup: - iounmap(state->dma); - iounmap(state->regs); - scsi_unregister(host); - break; - } - mac53c94_init(state); - ++nfscs; - } - return nfscs; -} - -int -mac53c94_release(struct Scsi_Host *host) -{ - struct fsc_state *fp = (struct fsc_state *) host->hostdata; - - if (fp == 0) - return 0; - if (fp->regs) - iounmap((void *) fp->regs); - if (fp->dma) - iounmap((void *) fp->dma); - kfree(fp->dma_cmd_space); - free_irq(fp->intr, fp); - return 0; -} - -int -mac53c94_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) +static int mac53c94_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) { struct fsc_state *state; #if 0 - if (data_goes_out(cmd)) { + if (cmd->sc_data_direction == SCSI_DATA_WRITE) { int i; printk(KERN_DEBUG "mac53c94_queue %p: command is", cmd); for (i = 0; i < cmd->cmd_len; ++i) @@ -223,60 +95,52 @@ mac53c94_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) return 0; } -int -mac53c94_abort(Scsi_Cmnd *cmd) +static int mac53c94_abort(Scsi_Cmnd *cmd) { return SCSI_ABORT_SNOOZE; } -int -mac53c94_host_reset(Scsi_Cmnd *cmd) +static int mac53c94_host_reset(Scsi_Cmnd *cmd) { struct fsc_state *state = (struct fsc_state *) cmd->device->host->hostdata; - volatile struct mac53c94_regs *regs = state->regs; - volatile struct dbdma_regs *dma = state->dma; + struct mac53c94_regs *regs = state->regs; + struct dbdma_regs *dma = state->dma; st_le32(&dma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); - regs->command = CMD_SCSI_RESET; /* assert RST */ - eieio(); + writeb(CMD_SCSI_RESET, ®s->command); /* assert RST */ udelay(100); /* leave it on for a while (>= 25us) */ - regs->command = CMD_RESET; - eieio(); + writeb(CMD_RESET, ®s->command); udelay(20); mac53c94_init(state); - regs->command = CMD_NOP; - eieio(); + writeb(CMD_NOP, ®s->command); return SUCCESS; } -static void -mac53c94_init(struct fsc_state *state) +static void mac53c94_init(struct fsc_state *state) { - volatile struct mac53c94_regs *regs = state->regs; - volatile struct dbdma_regs *dma = state->dma; + struct mac53c94_regs *regs = state->regs; + struct dbdma_regs *dma = state->dma; int x; - regs->config1 = state->host->this_id | CF1_PAR_ENABLE; - regs->sel_timeout = TIMO_VAL(250); /* 250ms */ - regs->clk_factor = CLKF_VAL(state->clk_freq); - regs->config2 = CF2_FEATURE_EN; - regs->config3 = 0; - regs->sync_period = 0; - regs->sync_offset = 0; - eieio(); - x = regs->interrupt; - st_le32(&dma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); + writeb(state->host->this_id | CF1_PAR_ENABLE, ®s->config1); + writeb(TIMO_VAL(250), ®s->sel_timeout); /* 250ms */ + writeb(CLKF_VAL(state->clk_freq), ®s->clk_factor); + writeb(CF2_FEATURE_EN, ®s->config2); + writeb(0, ®s->config3); + writeb(0, ®s->sync_period); + writeb(0, ®s->sync_offset); + x = readb(®s->interrupt); + writel((RUN|PAUSE|FLUSH|WAKE) << 16, &dma->control); } /* * Start the next command for a 53C94. * Should be called with interrupts disabled. */ -static void -mac53c94_start(struct fsc_state *state) +static void mac53c94_start(struct fsc_state *state) { Scsi_Cmnd *cmd; - volatile struct mac53c94_regs *regs = state->regs; + struct mac53c94_regs *regs = state->regs; int i; if (state->phase != idle || state->current_req != NULL) @@ -287,37 +151,30 @@ mac53c94_start(struct fsc_state *state) state->request_q = (Scsi_Cmnd *) cmd->host_scribble; /* Off we go */ - regs->count_lo = 0; - regs->count_mid = 0; - regs->count_hi = 0; - eieio(); - regs->command = CMD_NOP + CMD_DMA_MODE; + writeb(0, ®s->count_lo); + writeb(0, ®s->count_mid); + writeb(0, ®s->count_hi); + writeb(CMD_NOP + CMD_DMA_MODE, ®s->command); udelay(1); - eieio(); - regs->command = CMD_FLUSH; + writeb(CMD_FLUSH, ®s->command); udelay(1); - eieio(); - regs->dest_id = cmd->device->id; - regs->sync_period = 0; - regs->sync_offset = 0; - eieio(); + writeb(cmd->device->id, ®s->dest_id); + writeb(0, ®s->sync_period); + writeb(0, ®s->sync_offset); /* load the command into the FIFO */ - for (i = 0; i < cmd->cmd_len; ++i) { - regs->fifo = cmd->cmnd[i]; - eieio(); - } + for (i = 0; i < cmd->cmd_len; ++i) + writeb(cmd->cmnd[i], ®s->fifo); /* do select without ATN XXX */ - regs->command = CMD_SELECT; + writeb(CMD_SELECT, ®s->command); state->phase = selecting; if (cmd->use_sg > 0 || cmd->request_bufflen != 0) set_dma_cmds(state, cmd); } -static irqreturn_t -do_mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) +static irqreturn_t do_mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) { unsigned long flags; struct Scsi_Host *dev = ((struct fsc_state *) dev_id)->current_req->device->host; @@ -328,12 +185,11 @@ do_mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) return IRQ_HANDLED; } -static void -mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) +static void mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) { struct fsc_state *state = (struct fsc_state *) dev_id; - volatile struct mac53c94_regs *regs = state->regs; - volatile struct dbdma_regs *dma = state->dma; + struct mac53c94_regs *regs = state->regs; + struct dbdma_regs *dma = state->dma; Scsi_Cmnd *cmd = state->current_req; int nb, stat, seq, intr; static int mac53c94_errors; @@ -343,9 +199,9 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) * Apparently, reading the interrupt register unlatches * the status and sequence step registers. */ - seq = regs->seqstep; - stat = regs->status; - intr = regs->interrupt; + seq = readb(®s->seqstep); + stat = readb(®s->status); + intr = readb(®s->interrupt); #if 0 printk(KERN_DEBUG "mac53c94_intr, intr=%x stat=%x seq=%x phase=%d\n", @@ -355,8 +211,8 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) if (intr & INTR_RESET) { /* SCSI bus was reset */ printk(KERN_INFO "external SCSI bus reset detected\n"); - regs->command = CMD_NOP; - st_le32(&dma->control, RUN << 16); /* stop dma */ + writeb(CMD_NOP, ®s->command); + writel(RUN << 16, &dma->control); /* stop dma */ cmd_done(state, DID_RESET << 16); return; } @@ -373,8 +229,7 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) intr, stat, seq, state->phase); #endif ++mac53c94_errors; - regs->command = CMD_NOP + CMD_DMA_MODE; - eieio(); + writeb(CMD_NOP + CMD_DMA_MODE, ®s->command); } if (cmd == 0) { printk(KERN_DEBUG "53c94: interrupt with no command active?\n"); @@ -402,7 +257,7 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) cmd_done(state, DID_ERROR << 16); return; } - regs->command = CMD_NOP; + writeb(CMD_NOP, ®s->command); /* set DMA controller going if any data to transfer */ if ((stat & (STAT_MSG|STAT_CD)) == 0 && (cmd->use_sg > 0 || cmd->request_bufflen != 0)) { @@ -410,20 +265,17 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) if (nb > 0xfff0) nb = 0xfff0; cmd->SCp.this_residual -= nb; - regs->count_lo = nb; - regs->count_mid = nb >> 8; - eieio(); - regs->command = CMD_DMA_MODE + CMD_NOP; - eieio(); - st_le32(&dma->cmdptr, virt_to_phys(state->dma_cmds)); - st_le32(&dma->control, (RUN << 16) | RUN); - eieio(); - regs->command = CMD_DMA_MODE + CMD_XFER_DATA; + writeb(nb, ®s->count_lo); + writeb(nb >> 8, ®s->count_mid); + writeb(CMD_DMA_MODE + CMD_NOP, ®s->command); + writel(virt_to_phys(state->dma_cmds), &dma->cmdptr); + writel((RUN << 16) | RUN, &dma->control); + writeb(CMD_DMA_MODE + CMD_XFER_DATA, ®s->command); state->phase = dataing; break; } else if ((stat & STAT_PHASE) == STAT_CD + STAT_IO) { /* up to status phase already */ - regs->command = CMD_I_COMPLETE; + writeb(CMD_I_COMPLETE, ®s->command); state->phase = completing; } else { printk(KERN_DEBUG "in unexpected phase %x after cmd\n", @@ -446,18 +298,16 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) if (nb > 0xfff0) nb = 0xfff0; cmd->SCp.this_residual -= nb; - regs->count_lo = nb; - regs->count_mid = nb >> 8; - eieio(); - regs->command = CMD_DMA_MODE + CMD_NOP; - eieio(); - regs->command = CMD_DMA_MODE + CMD_XFER_DATA; + writeb(nb, ®s->count_lo); + writeb(nb >> 8, ®s->count_mid); + writeb(CMD_DMA_MODE + CMD_NOP, ®s->command); + writeb(CMD_DMA_MODE + CMD_XFER_DATA, ®s->command); break; } if ((stat & STAT_PHASE) != STAT_CD + STAT_IO) { printk(KERN_DEBUG "intr %x before data xfer complete\n", intr); } - out_le32(&dma->control, RUN << 16); /* stop dma */ + writel(RUN << 16, &dma->control); /* stop dma */ dma_dir = scsi_to_pci_dma_dir(cmd->sc_data_direction); if (cmd->use_sg != 0) { pci_unmap_sg(state->pdev, @@ -468,7 +318,7 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) cmd->request_bufflen, dma_dir); } /* should check dma status */ - regs->command = CMD_I_COMPLETE; + writeb(CMD_I_COMPLETE, ®s->command); state->phase = completing; break; case completing: @@ -477,10 +327,10 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) cmd_done(state, DID_ERROR << 16); return; } - cmd->SCp.Status = regs->fifo; eieio(); - cmd->SCp.Message = regs->fifo; eieio(); - cmd->result = - regs->command = CMD_ACCEPT_MSG; + cmd->SCp.Status = readb(®s->fifo); + cmd->SCp.Message = readb(®s->fifo); + cmd->result = CMD_ACCEPT_MSG; + writeb(CMD_ACCEPT_MSG, ®s->command); state->phase = busfreeing; break; case busfreeing: @@ -495,8 +345,7 @@ mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) } } -static void -cmd_done(struct fsc_state *state, int result) +static void cmd_done(struct fsc_state *state, int result) { Scsi_Cmnd *cmd; @@ -513,8 +362,7 @@ cmd_done(struct fsc_state *state, int result) /* * Set up DMA commands for transferring data. */ -static void -set_dma_cmds(struct fsc_state *state, Scsi_Cmnd *cmd) +static void set_dma_cmds(struct fsc_state *state, Scsi_Cmnd *cmd) { int i, dma_cmd, total; struct scatterlist *scl; @@ -523,7 +371,8 @@ set_dma_cmds(struct fsc_state *state, Scsi_Cmnd *cmd) u32 dma_len; int dma_dir = scsi_to_pci_dma_dir(cmd->sc_data_direction); - dma_cmd = data_goes_out(cmd)? OUTPUT_MORE: INPUT_MORE; + dma_cmd = cmd->sc_data_direction == SCSI_DATA_WRITE? OUTPUT_MORE: + INPUT_MORE; dcmds = state->dma_cmds; if (cmd->use_sg > 0) { int nseg; @@ -562,63 +411,9 @@ set_dma_cmds(struct fsc_state *state, Scsi_Cmnd *cmd) cmd->SCp.this_residual = total; } -/* - * Work out whether data will be going out from the host adaptor or into it. - */ -static int -data_goes_out(Scsi_Cmnd *cmd) -{ - switch (cmd->sc_data_direction) { - case SCSI_DATA_WRITE: - return 1; - case SCSI_DATA_READ: - return 0; - } - - /* for SCSI_DATA_UNKNOWN or SCSI_DATA_NONE, fall back on the - old method for now... */ - switch (cmd->cmnd[0]) { - case CHANGE_DEFINITION: - case COMPARE: - case COPY: - case COPY_VERIFY: - case FORMAT_UNIT: - case LOG_SELECT: - case MEDIUM_SCAN: - case MODE_SELECT: - case MODE_SELECT_10: - case REASSIGN_BLOCKS: - case RESERVE: - case SEARCH_EQUAL: - case SEARCH_EQUAL_12: - case SEARCH_HIGH: - case SEARCH_HIGH_12: - case SEARCH_LOW: - case SEARCH_LOW_12: - case SEND_DIAGNOSTIC: - case SEND_VOLUME_TAG: - case SET_WINDOW: - case UPDATE_BLOCK: - case WRITE_BUFFER: - case WRITE_6: - case WRITE_10: - case WRITE_12: - case WRITE_LONG: - case WRITE_LONG_2: /* alternate code for WRITE_LONG */ - case WRITE_SAME: - case WRITE_VERIFY: - case WRITE_VERIFY_12: - return 1; - default: - return 0; - } -} - -static Scsi_Host_Template driver_template = { +static Scsi_Host_Template mac53c94_template = { .proc_name = "53c94", .name = "53C94", - .detect = mac53c94_detect, - .release = mac53c94_release, .queuecommand = mac53c94_queue, .eh_abort_handler = mac53c94_abort, .eh_host_reset_handler = mac53c94_host_reset, @@ -629,4 +424,158 @@ static Scsi_Host_Template driver_template = { .use_clustering = DISABLE_CLUSTERING, }; -#include "scsi_module.c" +static int mac53c94_probe(struct macio_dev *mdev, const struct of_match *match) +{ + struct device_node *node = macio_get_of_node(mdev); + struct pci_dev *pdev = macio_get_pci_dev(mdev); + struct fsc_state *state; + struct Scsi_Host *host; + void *dma_cmd_space; + unsigned char *clkprop; + int proplen; + + if (macio_resource_count(mdev) != 2 || macio_irq_count(mdev) != 2) { + printk(KERN_ERR "mac53c94: expected 2 addrs and intrs (got %d/%d)\n", + node->n_addrs, node->n_intrs); + return -ENODEV; + } + + if (macio_request_resources(mdev, "mac53c94") != 0) { + printk(KERN_ERR "mac53c94: unable to request memory resources"); + return -EBUSY; + } + + host = scsi_host_alloc(&mac53c94_template, sizeof(struct fsc_state)); + if (host == NULL) { + printk(KERN_ERR "mac53c94: couldn't register host"); + goto out_release; + } + + state = (struct fsc_state *) host->hostdata; + macio_set_drvdata(mdev, state); + state->host = host; + state->pdev = pdev; + state->mdev = mdev; + + state->regs = (struct mac53c94_regs *) + ioremap(macio_resource_start(mdev, 0), 0x1000); + state->intr = macio_irq(mdev, 0); + state->dma = (struct dbdma_regs *) + ioremap(macio_resource_start(mdev, 1), 0x1000); + state->dmaintr = macio_irq(mdev, 1); + if (state->regs == NULL || state->dma == NULL) { + printk(KERN_ERR "mac53c94: ioremap failed for %s\n", + node->full_name); + goto out_free; + } + + clkprop = get_property(node, "clock-frequency", &proplen); + if (clkprop == NULL || proplen != sizeof(int)) { + printk(KERN_ERR "%s: can't get clock frequency, " + "assuming 25MHz\n", node->full_name); + state->clk_freq = 25000000; + } else + state->clk_freq = *(int *)clkprop; + + /* Space for dma command list: +1 for stop command, + * +1 to allow for aligning. + * XXX FIXME: Use DMA consistent routines + */ + dma_cmd_space = kmalloc((host->sg_tablesize + 2) * + sizeof(struct dbdma_cmd), GFP_KERNEL); + if (dma_cmd_space == 0) { + printk(KERN_ERR "mac53c94: couldn't allocate dma " + "command space for %s\n", node->full_name); + goto out_free; + } + state->dma_cmds = (struct dbdma_cmd *)DBDMA_ALIGN(dma_cmd_space); + memset(state->dma_cmds, 0, (host->sg_tablesize + 1) + * sizeof(struct dbdma_cmd)); + state->dma_cmd_space = dma_cmd_space; + + mac53c94_init(state); + + if (request_irq(state->intr, do_mac53c94_interrupt, 0, "53C94", state)) { + printk(KERN_ERR "mac53C94: can't get irq %d for %s\n", + state->intr, node->full_name); + goto out_free_dma; + } + + /* XXX FIXME: handle failure */ + scsi_add_host(host, &mdev->ofdev.dev); + scsi_scan_host(host); + + return 0; + + out_free_dma: + kfree(state->dma_cmd_space); + out_free: + if (state->dma != NULL) + iounmap(state->dma); + if (state->regs != NULL) + iounmap(state->regs); + scsi_host_put(host); + out_release: + macio_release_resources(mdev); + + return -ENODEV; +} + +static int mac53c94_remove(struct macio_dev *mdev) +{ + struct fsc_state *fp = (struct fsc_state *)macio_get_drvdata(mdev); + struct Scsi_Host *host = fp->host; + + scsi_remove_host(host); + + free_irq(fp->intr, fp); + + if (fp->regs) + iounmap((void *) fp->regs); + if (fp->dma) + iounmap((void *) fp->dma); + kfree(fp->dma_cmd_space); + + scsi_host_put(host); + + macio_release_resources(mdev); + + return 0; +} + + +static struct of_match mac53c94_match[] = +{ + { + .name = "53c94", + .type = OF_ANY_MATCH, + .compatible = OF_ANY_MATCH + }, + {}, +}; + +static struct macio_driver mac53c94_driver = +{ + .name = "mac53c94", + .match_table = mac53c94_match, + .probe = mac53c94_probe, + .remove = mac53c94_remove, +}; + + +static int __init init_mac53c94(void) +{ + return macio_register_driver(&mac53c94_driver); +} + +static void __exit exit_mac53c94(void) +{ + return macio_unregister_driver(&mac53c94_driver); +} + +module_init(init_mac53c94); +module_exit(exit_mac53c94); + +MODULE_DESCRIPTION("PowerMac 53c94 SCSI driver"); +MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/mesh.c b/drivers/scsi/mesh.c index e9dae68a11f0..ec0f629c280a 100644 --- a/drivers/scsi/mesh.c +++ b/drivers/scsi/mesh.c @@ -10,9 +10,13 @@ * Apr. 21 2002 - BenH Rework bus reset code for new error handler * Add delay after initial bus reset * Add module parameters + * + * Sep. 27 2003 - BenH Move to new driver model, fix some write posting + * issues * To do: * - handle aborts correctly * - retry arbitration if lost (unless higher levels do this for us) + * - power down the chip when no device is detected */ #include <linux/config.h> #include <linux/module.h> @@ -38,10 +42,7 @@ #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> -#endif +#include <asm/macio.h> #include "scsi.h" #include "hosts.h" @@ -164,10 +165,12 @@ struct mesh_state { int last_n_msgout; u8 msgout[16]; struct dbdma_cmd *dma_cmds; /* space for dbdma commands, aligned */ + dma_addr_t dma_cmd_bus; + void *dma_cmd_space; + int dma_cmd_size; int clk_freq; struct mesh_target tgts[8]; - void *dma_cmd_space; - struct device_node *ofnode; + struct macio_dev *mdev; struct pci_dev* pdev; #ifdef MESH_DBG int log_ix; @@ -176,324 +179,124 @@ struct mesh_state { #endif }; -#ifdef MESH_DBG - -static void dlog(struct mesh_state *ms, char *fmt, int a); -static void dumplog(struct mesh_state *ms, int tgt); -static void dumpslog(struct mesh_state *ms); - -#else -static inline void dlog(struct mesh_state *ms, char *fmt, int a) -{} -static inline void dumplog(struct mesh_state *ms, int tgt) -{} -static inline void dumpslog(struct mesh_state *ms) -{} +/* + * Driver is too messy, we need a few prototypes... + */ +static void mesh_done(struct mesh_state *ms, int start_next); +static void mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs); +static void cmd_complete(struct mesh_state *ms); +static void set_dma_cmds(struct mesh_state *ms, Scsi_Cmnd *cmd); +static void halt_dma(struct mesh_state *ms); +static void phase_mismatch(struct mesh_state *ms); -#endif /* MESH_DBG */ -#define MKWORD(a, b, c, d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) -static struct mesh_state *all_meshes; - -static void mesh_init(struct mesh_state *); -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 *); -static void add_sdtr_msg(struct mesh_state *); -static void set_sdtr(struct mesh_state *, int, int); -static void start_phase(struct mesh_state *); -static void get_msgin(struct mesh_state *); -static int msgin_length(struct mesh_state *); -static void cmd_complete(struct mesh_state *); -static void phase_mismatch(struct mesh_state *); -static void reselected(struct mesh_state *); -static void handle_reset(struct mesh_state *); -static void handle_error(struct mesh_state *); -static void handle_exception(struct mesh_state *); -static void mesh_interrupt(int, void *, struct pt_regs *); -static irqreturn_t do_mesh_interrupt(int, void *, struct pt_regs *); -static void handle_msgin(struct mesh_state *); -static void mesh_done(struct mesh_state *, int); -static void mesh_completed(struct mesh_state *, Scsi_Cmnd *); -static void set_dma_cmds(struct mesh_state *, Scsi_Cmnd *); -static void halt_dma(struct mesh_state *); -static int data_goes_out(Scsi_Cmnd *); -static void do_abort(struct mesh_state *ms); -static void set_mesh_power(struct mesh_state *ms, int state); - -#ifdef CONFIG_PMAC_PBOOK -static int mesh_notify_sleep(struct pmu_sleep_notifier *self, int when); -static struct pmu_sleep_notifier mesh_sleep_notifier = { - mesh_notify_sleep, - SLEEP_LEVEL_BLOCK, -}; -#endif +/* + * Some debugging & logging routines + */ -static struct notifier_block mesh_notifier = { - mesh_notify_reboot, - NULL, - 0 -}; +#ifdef MESH_DBG -int -mesh_detect(Scsi_Host_Template *tp) +static inline u32 readtb(void) { - struct device_node *mesh; - int nmeshes, tgt, *cfp, minper; - struct mesh_state *ms, **prev_statep; - struct Scsi_Host *mesh_host; - void *dma_cmd_space; - - if (_machine == _MACH_Pmac) { - use_active_neg = (find_devices("mac-io") ? 0 : SEQ_ACTIVE_NEG); - } else { - /* CHRP mac-io */ - 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; - /* - * On powermacs, the MESH node has device_type "mesh". - * On chrp machines, its device_type is "scsi" with - * "chrp,mesh0" as its `compatible' property. - */ - mesh = find_devices("mesh"); - if (mesh == 0) - mesh = find_compatible_devices("scsi", "chrp,mesh0"); - for (; mesh != 0; mesh = mesh->next) { - 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)\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; - } + u32 tb; - mesh_host = scsi_register(tp, sizeof(struct mesh_state)); - if (mesh_host == 0) { - printk(KERN_ERR "mesh: couldn't register host"); - continue; - } - mesh_host->unique_id = nmeshes; -#if !defined(MODULE) - note_scsi_host(mesh, mesh_host); +#ifdef DBG_USE_TB + /* Beware: if you enable this, it will crash on 601s. */ + asm ("mftb %0" : "=r" (tb) : ); +#else + tb = 0; #endif - - ms = (struct mesh_state *) mesh_host->hostdata; - if (ms == 0) - panic("no mesh state"); - 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 *) - ioremap(mesh->addrs[1].address, 0x1000); - ms->meshintr = mesh->intrs[0].line; - ms->dmaintr = mesh->intrs[1].line; - - /* Space for dma command list: +1 for stop command, - +1 to allow for aligning. */ - dma_cmd_space = kmalloc((mesh_host->sg_tablesize + 2) * - sizeof(struct dbdma_cmd), GFP_KERNEL); - if (dma_cmd_space == 0) - panic("mesh: couldn't allocate dma command space"); - ms->dma_cmds = (struct dbdma_cmd *) DBDMA_ALIGN(dma_cmd_space); - memset(ms->dma_cmds, 0, (mesh_host->sg_tablesize + 1) - * sizeof(struct dbdma_cmd)); - ms->dma_cmd_space = dma_cmd_space; - - ms->current_req = 0; - for (tgt = 0; tgt < 8; ++tgt) { - ms->tgts[tgt].sdtr_state = do_sdtr; - ms->tgts[tgt].sync_params = ASYNC_PARAMS; - ms->tgts[tgt].current_req = 0; - } - *prev_statep = ms; - prev_statep = &ms->next; - - if ((cfp = (int *) get_property(mesh, "clock-frequency", - NULL))) { - ms->clk_freq = *cfp; - } else { - printk(KERN_INFO "mesh: assuming 50MHz clock frequency\n"); - ms->clk_freq = 50000000; - } - /* The maximum sync rate is clock / 5; increase - mesh_sync_period if necessary. */ - minper = 1000000000 / (ms->clk_freq / 5); /* ns */ - if (mesh_sync_period < minper) - mesh_sync_period = minper; - - set_mesh_power(ms, 1); - - mesh_init(ms); - - if (request_irq(ms->meshintr, do_mesh_interrupt, 0, "MESH", ms)) { - printk(KERN_ERR "MESH: can't get irq %d\n", ms->meshintr); - } - - ++nmeshes; - } - - if ((_machine == _MACH_Pmac) && (nmeshes > 0)) { -#ifdef CONFIG_PMAC_PBOOK - pmu_register_sleep_notifier(&mesh_sleep_notifier); -#endif /* CONFIG_PMAC_PBOOK */ - register_reboot_notifier(&mesh_notifier); - } - - return nmeshes; + return tb; } -int -mesh_release(struct Scsi_Host *host) +static void dlog(struct mesh_state *ms, char *fmt, int a) { - struct mesh_state *ms = (struct mesh_state *) host->hostdata; + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + struct dbglog *tlp, *slp; - if (ms == 0) - return 0; - if (ms->mesh) - iounmap((void *) ms->mesh); - if (ms->dma) - iounmap((void *) ms->dma); - kfree(ms->dma_cmd_space); - free_irq(ms->meshintr, ms); - pmac_call_feature(PMAC_FTR_MESH_ENABLE, ms->ofnode, 0, 0); - return 0; + tlp = &tp->log[tp->log_ix]; + slp = &ms->log[ms->log_ix]; + tlp->fmt = fmt; + tlp->tb = readtb(); + tlp->phase = (ms->msgphase << 4) + ms->phase; + tlp->bs0 = ms->mesh->bus_status0; + tlp->bs1 = ms->mesh->bus_status1; + tlp->tgt = ms->conn_tgt; + tlp->d = a; + *slp = *tlp; + if (++tp->log_ix >= N_DBG_LOG) + tp->log_ix = 0; + if (tp->n_log < N_DBG_LOG) + ++tp->n_log; + if (++ms->log_ix >= N_DBG_SLOG) + ms->log_ix = 0; + if (ms->n_log < N_DBG_SLOG) + ++ms->n_log; } -static void -set_mesh_power(struct mesh_state *ms, int state) +static void dumplog(struct mesh_state *ms, int t) { - if (_machine != _MACH_Pmac) - return; - if (state) { - pmac_call_feature(PMAC_FTR_MESH_ENABLE, ms->ofnode, 0, 1); - mdelay(200); - } else { - pmac_call_feature(PMAC_FTR_MESH_ENABLE, ms->ofnode, 0, 0); - mdelay(10); - } -} + struct mesh_target *tp = &ms->tgts[t]; + struct dbglog *lp; + int i; -#ifdef CONFIG_PMAC_PBOOK -/* - * notify clients before sleep and reset bus afterwards - */ -int -mesh_notify_sleep(struct pmu_sleep_notifier *self, int when) -{ - struct mesh_state *ms; - - switch (when) { - case PBOOK_SLEEP_REQUEST: - /* XXX We should wait for current transactions and queue - * new ones that would be posted beyond this point - */ - break; - case PBOOK_SLEEP_REJECT: - break; - - 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; - } - return PBOOK_SLEEP_OK; + if (tp->n_log == 0) + return; + i = tp->log_ix - tp->n_log; + if (i < 0) + i += N_DBG_LOG; + tp->n_log = 0; + do { + lp = &tp->log[i]; + printk(KERN_DEBUG "mesh log %d: bs=%.2x%.2x ph=%.2x ", + t, lp->bs1, lp->bs0, lp->phase); +#ifdef DBG_USE_TB + printk("tb=%10u ", lp->tb); +#endif + printk(lp->fmt, lp->d); + printk("\n"); + if (++i >= N_DBG_LOG) + i = 0; + } while (i != tp->log_ix); } -#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 *)) +static void dumpslog(struct mesh_state *ms) { - struct mesh_state *ms; - - cmd->scsi_done = done; - cmd->host_scribble = NULL; - - ms = (struct mesh_state *) cmd->device->host->hostdata; + struct dbglog *lp; + int i; - if (ms->request_q == NULL) - ms->request_q = cmd; - else - ms->request_qtail->host_scribble = (void *) cmd; - ms->request_qtail = cmd; + if (ms->n_log == 0) + return; + i = ms->log_ix - ms->n_log; + if (i < 0) + i += N_DBG_SLOG; + ms->n_log = 0; + do { + lp = &ms->log[i]; + printk(KERN_DEBUG "mesh log: bs=%.2x%.2x ph=%.2x t%d ", + lp->bs1, lp->bs0, lp->phase, lp->tgt); +#ifdef DBG_USE_TB + printk("tb=%10u ", lp->tb); +#endif + printk(lp->fmt, lp->d); + printk("\n"); + if (++i >= N_DBG_SLOG) + i = 0; + } while (i != ms->log_ix); +} - if (ms->phase == idle) - mesh_start(ms); +#else - return 0; -} +static inline void dlog(struct mesh_state *ms, char *fmt, int a) +{} +static inline void dumplog(struct mesh_state *ms, int tgt) +{} +static inline void dumpslog(struct mesh_state *ms) +{} -/* 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->device->host->hostdata; +#endif /* MESH_DBG */ - printk(KERN_DEBUG "mesh_abort(%p)\n", cmd); - mesh_dump_regs(ms); - dumplog(ms, cmd->device->id); - dumpslog(ms); - return SCSI_ABORT_SNOOZE; -} +#define MKWORD(a, b, c, d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) static void mesh_dump_regs(struct mesh_state *ms) @@ -528,79 +331,35 @@ 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 + * Flush write buffers on the bus path to the mesh */ -int -mesh_host_reset(Scsi_Cmnd *cmd) +static inline void mesh_flush_io(volatile struct mesh_regs *mr) { - struct mesh_state *ms = (struct mesh_state *) cmd->device->host->hostdata; - volatile struct mesh_regs *mr = ms->mesh; - volatile struct dbdma_regs *md = ms->dma; - - 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 */ - 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 */ - - /* Complete pending commands */ - handle_reset(ms); - - return SUCCESS; + (void)in_8(&mr->mesh_id); } + /* - * If we leave drives set for synchronous transfers (especially - * CDROMs), and reboot to MacOS, it gets confused, poor thing. - * So, on reboot we reset the SCSI bus. + * Complete a SCSI command */ -static int -mesh_notify_reboot(struct notifier_block *this, unsigned long code, void *x) +static void mesh_completed(struct mesh_state *ms, Scsi_Cmnd *cmd) { - struct mesh_state *ms; - volatile struct mesh_regs *mr; - - if (code == SYS_DOWN) { - printk(KERN_INFO "resetting MESH scsi bus(es)\n"); - for (ms = all_meshes; ms != 0; ms = ms->next) { - mr = ms->mesh; - out_8(&mr->intr_mask, 0); - out_8(&mr->interrupt, - INT_ERROR | INT_EXCEPTION | INT_CMDDONE); - out_8(&mr->bus_status1, BS1_RST); - udelay(30); - out_8(&mr->bus_status1, 0); - } - } - return NOTIFY_DONE; + (*cmd->scsi_done)(cmd); } + /* 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) +static void mesh_init(struct mesh_state *ms) { volatile struct mesh_regs *mr = ms->mesh; volatile struct dbdma_regs *md = ms->dma; + mesh_flush_io(mr); udelay(100); /* Reset controller */ @@ -608,6 +367,7 @@ mesh_init(struct mesh_state *ms) out_8(&mr->exception, 0xff); /* clear all exception bits */ out_8(&mr->error, 0xff); /* clear all error bits */ out_8(&mr->sequence, SEQ_RESETMESH); + mesh_flush_io(mr); udelay(10); out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->source_id, ms->host->this_id); @@ -619,8 +379,10 @@ mesh_init(struct mesh_state *ms) /* Reset bus */ out_8(&mr->bus_status1, BS1_RST); /* assert RST */ + mesh_flush_io(mr); udelay(30); /* leave it on for >= 25us */ out_8(&mr->bus_status1, 0); /* negate RST */ + mesh_flush_io(mr); /* Wait for bus to come back */ current->state = TASK_UNINTERRUPTIBLE; @@ -630,6 +392,7 @@ mesh_init(struct mesh_state *ms) /* Reconfigure controller */ out_8(&mr->interrupt, 0xff); /* clear all interrupt bits */ out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); udelay(1); out_8(&mr->sync_params, ASYNC_PARAMS); out_8(&mr->sequence, SEQ_ENBRESEL); @@ -638,51 +401,15 @@ mesh_init(struct mesh_state *ms) ms->msgphase = msg_none; } -/* - * Start the next command for a MESH. - * Should be called with interrupts disabled. - */ -static void -mesh_start(struct mesh_state *ms) -{ - Scsi_Cmnd *cmd, *prev, *next; - if (ms->phase != idle || ms->current_req != NULL) { - printk(KERN_ERR "inappropriate mesh_start (phase=%d, ms=%p)", - ms->phase, ms); - return; - } - - while (ms->phase == idle) { - prev = NULL; - for (cmd = ms->request_q; ; cmd = (Scsi_Cmnd *) cmd->host_scribble) { - if (cmd == NULL) - return; - if (ms->tgts[cmd->device->id].current_req == NULL) - break; - prev = cmd; - } - next = (Scsi_Cmnd *) cmd->host_scribble; - if (prev == NULL) - ms->request_q = next; - else - prev->host_scribble = (void *) next; - if (next == NULL) - ms->request_qtail = prev; - - mesh_start_cmd(ms, cmd); - } -} - -static void -mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd) +static void mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd) { volatile struct mesh_regs *mr = ms->mesh; int t, id; id = cmd->device->id; ms->current_req = cmd; - ms->tgts[id].data_goes_out = data_goes_out(cmd); + ms->tgts[id].data_goes_out = cmd->sc_data_direction == SCSI_DATA_WRITE; ms->tgts[id].current_req = cmd; #if 1 @@ -720,9 +447,10 @@ mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd) MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); out_8(&mr->interrupt, INT_CMDDONE); out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); udelay(1); - if (mr->bus_status1 & (BS1_BSY | BS1_SEL)) { + if (in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) { /* * Some other device has the bus or is arbitrating for it - * probably a target which is about to reselect us. @@ -731,7 +459,7 @@ mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd) MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); for (t = 100; t > 0; --t) { - if ((mr->bus_status1 & (BS1_BSY | BS1_SEL)) == 0) + if ((in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) == 0) break; if (in_8(&mr->interrupt) != 0) { dlog(ms, "intr b4 arb, intr/exc/err/fc=%.8x", @@ -743,7 +471,7 @@ mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd) } udelay(1); } - if (mr->bus_status1 & (BS1_BSY | BS1_SEL)) { + if (in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) { /* XXX should try again in a little while */ ms->stat = DID_BUS_BUSY; ms->phase = idle; @@ -792,23 +520,25 @@ mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd) } dlog(ms, "after arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); - if (mr->interrupt == 0 && (mr->bus_status1 & BS1_SEL) - && (mr->bus_status0 & BS0_IO)) { + if (in_8(&mr->interrupt) == 0 && (in_8(&mr->bus_status1) & BS1_SEL) + && (in_8(&mr->bus_status0) & BS0_IO)) { /* looks like a reselection - try resetting the mesh */ dlog(ms, "resel? after arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); out_8(&mr->sequence, SEQ_RESETMESH); + mesh_flush_io(mr); udelay(10); out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->sequence, SEQ_ENBRESEL); - for (t = 10; t > 0 && mr->interrupt == 0; --t) + mesh_flush_io(mr); + for (t = 10; t > 0 && in_8(&mr->interrupt) == 0; --t) udelay(1); dlog(ms, "tried reset after arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); #ifndef MESH_MULTIPLE_HOSTS - if (mr->interrupt == 0 && (mr->bus_status1 & BS1_SEL) - && (mr->bus_status0 & BS0_IO)) { + if (in_8(&mr->interrupt) == 0 && (in_8(&mr->bus_status1) & BS1_SEL) + && (in_8(&mr->bus_status0) & BS0_IO)) { printk(KERN_ERR "mesh: controller not responding" " to reselection!\n"); /* @@ -822,8 +552,76 @@ mesh_start_cmd(struct mesh_state *ms, Scsi_Cmnd *cmd) } } -static inline void -add_sdtr_msg(struct mesh_state *ms) +/* + * Start the next command for a MESH. + * Should be called with interrupts disabled. + */ +static void mesh_start(struct mesh_state *ms) +{ + Scsi_Cmnd *cmd, *prev, *next; + + if (ms->phase != idle || ms->current_req != NULL) { + printk(KERN_ERR "inappropriate mesh_start (phase=%d, ms=%p)", + ms->phase, ms); + return; + } + + while (ms->phase == idle) { + prev = NULL; + for (cmd = ms->request_q; ; cmd = (Scsi_Cmnd *) cmd->host_scribble) { + if (cmd == NULL) + return; + if (ms->tgts[cmd->device->id].current_req == NULL) + break; + prev = cmd; + } + next = (Scsi_Cmnd *) cmd->host_scribble; + if (prev == NULL) + ms->request_q = next; + else + prev->host_scribble = (void *) next; + if (next == NULL) + ms->request_qtail = prev; + + mesh_start_cmd(ms, cmd); + } +} + +static void mesh_done(struct mesh_state *ms, int start_next) +{ + Scsi_Cmnd *cmd; + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + + cmd = ms->current_req; + ms->current_req = 0; + tp->current_req = 0; + if (cmd) { + cmd->result = (ms->stat << 16) + cmd->SCp.Status; + if (ms->stat == DID_OK) + cmd->result += (cmd->SCp.Message << 8); + if (DEBUG_TARGET(cmd)) { + printk(KERN_DEBUG "mesh_done: result = %x, data_ptr=%d, buflen=%d\n", + cmd->result, ms->data_ptr, cmd->request_bufflen); + if ((cmd->cmnd[0] == 0 || cmd->cmnd[0] == 0x12 || cmd->cmnd[0] == 3) + && cmd->request_buffer != 0) { + unsigned char *b = cmd->request_buffer; + printk(KERN_DEBUG "buffer = %x %x %x %x %x %x %x %x\n", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); + } + } + cmd->SCp.this_residual -= ms->data_ptr; + mesh_completed(ms, cmd); + } + if (start_next) { + out_8(&ms->mesh->sequence, SEQ_ENBRESEL); + mesh_flush_io(ms->mesh); + udelay(1); + ms->phase = idle; + mesh_start(ms); + } +} + +static inline void add_sdtr_msg(struct mesh_state *ms) { int i = ms->n_msgout; @@ -835,8 +633,7 @@ add_sdtr_msg(struct mesh_state *ms) ms->n_msgout = i + 5; } -static void -set_sdtr(struct mesh_state *ms, int period, int offset) +static void set_sdtr(struct mesh_state *ms, int period, int offset) { struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; volatile struct mesh_regs *mr = ms->mesh; @@ -877,8 +674,7 @@ set_sdtr(struct mesh_state *ms, int period, int offset) ms->conn_tgt, tr/10, tr%10); } -static void -start_phase(struct mesh_state *ms) +static void start_phase(struct mesh_state *ms) { int i, seq, nb; volatile struct mesh_regs *mr = ms->mesh; @@ -925,14 +721,16 @@ start_phase(struct mesh_state *ms) ms->msgout[1], ms->msgout[2])); out_8(&mr->count_hi, 0); out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); udelay(1); /* * If ATN is not already asserted, we assert it, then * issue a SEQ_MSGOUT to get the mesh to drop ACK. */ - if ((mr->bus_status0 & BS0_ATN) == 0) { + if ((in_8(&mr->bus_status0) & BS0_ATN) == 0) { dlog(ms, "bus0 was %.2x explictly asserting ATN", mr->bus_status0); out_8(&mr->bus_status0, BS0_ATN); /* explicit ATN */ + mesh_flush_io(mr); udelay(1); out_8(&mr->count_lo, 1); out_8(&mr->sequence, SEQ_MSGOUT + seq); @@ -1006,6 +804,7 @@ start_phase(struct mesh_state *ms) case busfreeing: case disconnecting: out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); udelay(1); dlog(ms, "enbresel intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, @@ -1020,8 +819,7 @@ start_phase(struct mesh_state *ms) } -static inline void -get_msgin(struct mesh_state *ms) +static inline void get_msgin(struct mesh_state *ms) { volatile struct mesh_regs *mr = ms->mesh; int i, n; @@ -1035,8 +833,7 @@ get_msgin(struct mesh_state *ms) } } -static inline int -msgin_length(struct mesh_state *ms) +static inline int msgin_length(struct mesh_state *ms) { int b, n; @@ -1054,265 +851,7 @@ msgin_length(struct mesh_state *ms) return n; } -static void -cmd_complete(struct mesh_state *ms) -{ - volatile struct mesh_regs *mr = ms->mesh; - Scsi_Cmnd *cmd = ms->current_req; - struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; - int seq, n, t; - - dlog(ms, "cmd_complete fc=%x", mr->fifo_count); - seq = use_active_neg + (ms->n_msgout? SEQ_ATN: 0); - switch (ms->msgphase) { - case msg_out_xxx: - /* huh? we expected a phase mismatch */ - ms->n_msgin = 0; - ms->msgphase = msg_in; - /* fall through */ - - case msg_in: - /* should have some message bytes in fifo */ - get_msgin(ms); - n = msgin_length(ms); - if (ms->n_msgin < n) { - out_8(&mr->count_lo, n - ms->n_msgin); - out_8(&mr->sequence, SEQ_MSGIN + seq); - } else { - ms->msgphase = msg_none; - handle_msgin(ms); - start_phase(ms); - } - break; - - case msg_in_bad: - out_8(&mr->sequence, SEQ_FLUSHFIFO); - udelay(1); - out_8(&mr->count_lo, 1); - out_8(&mr->sequence, SEQ_MSGIN + SEQ_ATN + use_active_neg); - break; - - case msg_out: - /* - * To get the right timing on ATN wrt ACK, we have - * to get the MESH to drop ACK, wait until REQ gets - * asserted, then drop ATN. To do this we first - * issue a SEQ_MSGOUT with ATN and wait for REQ, - * then change the command to a SEQ_MSGOUT w/o ATN. - * If we don't see REQ in a reasonable time, we - * change the command to SEQ_MSGIN with ATN, - * wait for the phase mismatch interrupt, then - * issue the SEQ_MSGOUT without ATN. - */ - out_8(&mr->count_lo, 1); - out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg + SEQ_ATN); - t = 30; /* wait up to 30us */ - while ((mr->bus_status0 & BS0_REQ) == 0 && --t >= 0) - udelay(1); - dlog(ms, "last_mbyte err/exc/fc/cl=%.8x", - MKWORD(mr->error, mr->exception, - mr->fifo_count, mr->count_lo)); - if (in_8(&mr->interrupt) & (INT_ERROR | INT_EXCEPTION)) { - /* whoops, target didn't do what we expected */ - ms->last_n_msgout = ms->n_msgout; - ms->n_msgout = 0; - if (in_8(&mr->interrupt) & INT_ERROR) { - printk(KERN_ERR "mesh: error %x in msg_out\n", - in_8(&mr->error)); - handle_error(ms); - return; - } - if (in_8(&mr->exception) != EXC_PHASEMM) - printk(KERN_ERR "mesh: exc %x in msg_out\n", - in_8(&mr->exception)); - else - printk(KERN_DEBUG "mesh: bs0=%x in msg_out\n", - in_8(&mr->bus_status0)); - handle_exception(ms); - return; - } - if (mr->bus_status0 & BS0_REQ) { - out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg); - udelay(1); - out_8(&mr->fifo, ms->msgout[ms->n_msgout-1]); - ms->msgphase = msg_out_last; - } else { - out_8(&mr->sequence, SEQ_MSGIN + use_active_neg + SEQ_ATN); - ms->msgphase = msg_out_xxx; - } - break; - - case msg_out_last: - ms->last_n_msgout = ms->n_msgout; - ms->n_msgout = 0; - ms->msgphase = ms->expect_reply? msg_in: msg_none; - start_phase(ms); - break; - - case msg_none: - switch (ms->phase) { - case idle: - printk(KERN_ERR "mesh: interrupt in idle phase?\n"); - dumpslog(ms); - return; - case selecting: - dlog(ms, "Selecting phase at command completion",0); - ms->msgout[0] = IDENTIFY(ALLOW_RESEL(ms->conn_tgt), - (cmd? cmd->device->lun: 0)); - ms->n_msgout = 1; - ms->expect_reply = 0; - if (ms->aborting) { - ms->msgout[0] = ABORT; - ms->n_msgout++; - } else if (tp->sdtr_state == do_sdtr) { - /* add SDTR message */ - add_sdtr_msg(ms); - ms->expect_reply = 1; - tp->sdtr_state = sdtr_sent; - } - ms->msgphase = msg_out; - /* - * We need to wait for REQ before dropping ATN. - * We wait for at most 30us, then fall back to - * a scheme where we issue a SEQ_COMMAND with ATN, - * which will give us a phase mismatch interrupt - * when REQ does come, and then we send the message. - */ - t = 230; /* wait up to 230us */ - while ((mr->bus_status0 & BS0_REQ) == 0) { - if (--t < 0) { - dlog(ms, "impatient for req", ms->n_msgout); - ms->msgphase = msg_none; - break; - } - udelay(1); - } - break; - case dataing: - if (ms->dma_count != 0) { - start_phase(ms); - return; - } - /* - * We can get a phase mismatch here if the target - * changes to the status phase, even though we have - * had a command complete interrupt. Then, if we - * issue the SEQ_STATUS command, we'll get a sequence - * error interrupt. Which isn't so bad except that - * occasionally the mesh actually executes the - * SEQ_STATUS *as well as* giving us the sequence - * error and phase mismatch exception. - */ - out_8(&mr->sequence, 0); - out_8(&mr->interrupt, - INT_ERROR | INT_EXCEPTION | INT_CMDDONE); - halt_dma(ms); - break; - case statusing: - if (cmd) { - cmd->SCp.Status = mr->fifo; - if (DEBUG_TARGET(cmd)) - printk(KERN_DEBUG "mesh: status is %x\n", - cmd->SCp.Status); - } - ms->msgphase = msg_in; - break; - case busfreeing: - mesh_done(ms, 1); - return; - case disconnecting: - ms->current_req = 0; - ms->phase = idle; - mesh_start(ms); - return; - default: - break; - } - ++ms->phase; - start_phase(ms); - break; - } -} - -static void phase_mismatch(struct mesh_state *ms) -{ - volatile struct mesh_regs *mr = ms->mesh; - int phase; - - dlog(ms, "phasemm ch/cl/seq/fc=%.8x", - MKWORD(mr->count_hi, mr->count_lo, mr->sequence, mr->fifo_count)); - phase = mr->bus_status0 & BS0_PHASE; - if (ms->msgphase == msg_out_xxx && phase == BP_MSGOUT) { - /* output the last byte of the message, without ATN */ - out_8(&mr->count_lo, 1); - out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg); - udelay(1); - out_8(&mr->fifo, ms->msgout[ms->n_msgout-1]); - ms->msgphase = msg_out_last; - return; - } - - if (ms->msgphase == msg_in) { - get_msgin(ms); - if (ms->n_msgin) - handle_msgin(ms); - } - - if (ms->dma_started) - halt_dma(ms); - if (mr->fifo_count) { - out_8(&mr->sequence, SEQ_FLUSHFIFO); - udelay(1); - } - - ms->msgphase = msg_none; - switch (phase) { - case BP_DATAIN: - ms->tgts[ms->conn_tgt].data_goes_out = 0; - ms->phase = dataing; - break; - case BP_DATAOUT: - ms->tgts[ms->conn_tgt].data_goes_out = 1; - ms->phase = dataing; - break; - case BP_COMMAND: - ms->phase = commanding; - break; - case BP_STATUS: - ms->phase = statusing; - break; - case BP_MSGIN: - ms->msgphase = msg_in; - ms->n_msgin = 0; - break; - case BP_MSGOUT: - ms->msgphase = msg_out; - if (ms->n_msgout == 0) { - if (ms->aborting) { - do_abort(ms); - } else { - if (ms->last_n_msgout == 0) { - printk(KERN_DEBUG - "mesh: no msg to repeat\n"); - ms->msgout[0] = NOP; - ms->last_n_msgout = 1; - } - ms->n_msgout = ms->last_n_msgout; - } - } - break; - default: - printk(KERN_DEBUG "mesh: unknown scsi phase %x\n", phase); - ms->stat = DID_ERROR; - mesh_done(ms, 1); - return; - } - - start_phase(ms); -} - -static void -reselected(struct mesh_state *ms) +static void reselected(struct mesh_state *ms) { volatile struct mesh_regs *mr = ms->mesh; Scsi_Cmnd *cmd; @@ -1360,26 +899,30 @@ reselected(struct mesh_state *ms) /* * We seem to get abortive reselections sometimes. */ - while ((mr->bus_status1 & BS1_BSY) == 0) { + while ((in_8(&mr->bus_status1) & BS1_BSY) == 0) { static int mesh_aborted_resels; mesh_aborted_resels++; out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + mesh_flush_io(mr); udelay(1); out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); udelay(5); dlog(ms, "extra resel err/exc/fc = %.6x", MKWORD(0, mr->error, mr->exception, mr->fifo_count)); } out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + mesh_flush_io(mr); udelay(1); out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); udelay(1); out_8(&mr->sync_params, ASYNC_PARAMS); /* * Find out who reselected us. */ - if (mr->fifo_count == 0) { + if (in_8(&mr->fifo_count) == 0) { printk(KERN_ERR "mesh: reselection but nothing in fifo?\n"); ms->conn_tgt = ms->host->this_id; goto bogus; @@ -1438,8 +981,7 @@ static void do_abort(struct mesh_state *ms) dlog(ms, "abort", 0); } -static void -handle_reset(struct mesh_state *ms) +static void handle_reset(struct mesh_state *ms) { int tgt; struct mesh_target *tp; @@ -1466,13 +1008,13 @@ handle_reset(struct mesh_state *ms) ms->msgphase = msg_none; out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); udelay(1); out_8(&mr->sync_params, ASYNC_PARAMS); out_8(&mr->sequence, SEQ_ENBRESEL); } -static irqreturn_t -do_mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) +static irqreturn_t do_mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) { unsigned long flags; struct Scsi_Host *dev = ((struct mesh_state *)dev_id)->host; @@ -1497,7 +1039,7 @@ static void handle_error(struct mesh_state *ms) /* SCSI bus was reset */ printk(KERN_INFO "mesh: SCSI bus reset detected: " "waiting for end..."); - while ((mr->bus_status1 & BS1_RST) != 0) + while ((in_8(&mr->bus_status1) & BS1_RST) != 0) udelay(1); printk("done\n"); handle_reset(ms); @@ -1567,7 +1109,7 @@ static void handle_error(struct mesh_state *ms) } mesh_dump_regs(ms); dumplog(ms, ms->conn_tgt); - if (ms->phase > selecting && (mr->bus_status1 & BS1_BSY)) { + if (ms->phase > selecting && (in_8(&mr->bus_status1) & BS1_BSY)) { /* try to do what the target wants */ do_abort(ms); phase_mismatch(ms); @@ -1609,36 +1151,7 @@ static void handle_exception(struct mesh_state *ms) } } -static void -mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) -{ - struct mesh_state *ms = (struct mesh_state *) dev_id; - volatile struct mesh_regs *mr = ms->mesh; - int intr; - -#if 0 - if (ALLOW_DEBUG(ms->conn_tgt)) - printk(KERN_DEBUG "mesh_intr, bs0=%x int=%x exc=%x err=%x " - "phase=%d msgphase=%d\n", mr->bus_status0, - mr->interrupt, mr->exception, mr->error, - ms->phase, ms->msgphase); -#endif - while ((intr = in_8(&mr->interrupt)) != 0) { - dlog(ms, "interrupt intr/err/exc/seq=%.8x", - MKWORD(intr, mr->error, mr->exception, mr->sequence)); - if (intr & INT_ERROR) { - handle_error(ms); - } else if (intr & INT_EXCEPTION) { - handle_exception(ms); - } else if (intr & INT_CMDDONE) { - out_8(&mr->interrupt, INT_CMDDONE); - cmd_complete(ms); - } - } -} - -static void -handle_msgin(struct mesh_state *ms) +static void handle_msgin(struct mesh_state *ms) { int i, code; Scsi_Cmnd *cmd = ms->current_req; @@ -1736,51 +1249,10 @@ handle_msgin(struct mesh_state *ms) ms->msgphase = msg_out; } -static void -mesh_done(struct mesh_state *ms, int start_next) -{ - Scsi_Cmnd *cmd; - struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; - - cmd = ms->current_req; - ms->current_req = 0; - tp->current_req = 0; - if (cmd) { - cmd->result = (ms->stat << 16) + cmd->SCp.Status; - if (ms->stat == DID_OK) - cmd->result += (cmd->SCp.Message << 8); - if (DEBUG_TARGET(cmd)) { - printk(KERN_DEBUG "mesh_done: result = %x, data_ptr=%d, buflen=%d\n", - cmd->result, ms->data_ptr, cmd->request_bufflen); - if ((cmd->cmnd[0] == 0 || cmd->cmnd[0] == 0x12 || cmd->cmnd[0] == 3) - && cmd->request_buffer != 0) { - unsigned char *b = cmd->request_buffer; - printk(KERN_DEBUG "buffer = %x %x %x %x %x %x %x %x\n", - b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); - } - } - cmd->SCp.this_residual -= ms->data_ptr; - mesh_completed(ms, cmd); - } - if (start_next) { - out_8(&ms->mesh->sequence, SEQ_ENBRESEL); - udelay(1); - ms->phase = idle; - mesh_start(ms); - } -} - -static void -mesh_completed(struct mesh_state *ms, Scsi_Cmnd *cmd) -{ - (*cmd->scsi_done)(cmd); -} - /* * Set up DMA commands for transferring data. */ -static void -set_dma_cmds(struct mesh_state *ms, Scsi_Cmnd *cmd) +static void set_dma_cmds(struct mesh_state *ms, Scsi_Cmnd *cmd) { int i, dma_cmd, total, off, dtot; struct scatterlist *scl; @@ -1848,8 +1320,7 @@ set_dma_cmds(struct mesh_state *ms, Scsi_Cmnd *cmd) ms->dma_count = dtot; } -static void -halt_dma(struct mesh_state *ms) +static void halt_dma(struct mesh_state *ms) { volatile struct dbdma_regs *md = ms->dma; volatile struct mesh_regs *mr = ms->mesh; @@ -1859,7 +1330,7 @@ halt_dma(struct mesh_state *ms) if (!ms->tgts[ms->conn_tgt].data_goes_out) { /* wait a little while until the fifo drains */ t = 50; - while (t > 0 && mr->fifo_count != 0 + while (t > 0 && in_8(&mr->fifo_count) != 0 && (in_le32(&md->status) & ACTIVE) != 0) { --t; udelay(1); @@ -1899,154 +1370,471 @@ halt_dma(struct mesh_state *ms) ms->dma_started = 0; } -/* - * Work out whether we expect data to go out from the host adaptor or into it. - */ -static int -data_goes_out(Scsi_Cmnd *cmd) +static void phase_mismatch(struct mesh_state *ms) { - switch (cmd->sc_data_direction) { - case SCSI_DATA_WRITE: - return 1; - case SCSI_DATA_READ: - return 0; + volatile struct mesh_regs *mr = ms->mesh; + int phase; + + dlog(ms, "phasemm ch/cl/seq/fc=%.8x", + MKWORD(mr->count_hi, mr->count_lo, mr->sequence, mr->fifo_count)); + phase = in_8(&mr->bus_status0) & BS0_PHASE; + if (ms->msgphase == msg_out_xxx && phase == BP_MSGOUT) { + /* output the last byte of the message, without ATN */ + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->fifo, ms->msgout[ms->n_msgout-1]); + ms->msgphase = msg_out_last; + return; + } + + if (ms->msgphase == msg_in) { + get_msgin(ms); + if (ms->n_msgin) + handle_msgin(ms); } - /* for SCSI_DATA_UNKNOWN or SCSI_DATA_NONE, fall back on the - old method for now... */ - switch (cmd->cmnd[0]) { - case CHANGE_DEFINITION: - case COMPARE: - case COPY: - case COPY_VERIFY: - case FORMAT_UNIT: - case LOG_SELECT: - case MEDIUM_SCAN: - case MODE_SELECT: - case MODE_SELECT_10: - case REASSIGN_BLOCKS: - case RESERVE: - case SEARCH_EQUAL: - case SEARCH_EQUAL_12: - case SEARCH_HIGH: - case SEARCH_HIGH_12: - case SEARCH_LOW: - case SEARCH_LOW_12: - case SEND_DIAGNOSTIC: - case SEND_VOLUME_TAG: - case SET_WINDOW: - case UPDATE_BLOCK: - case WRITE_BUFFER: - case WRITE_6: - case WRITE_10: - case WRITE_12: - case WRITE_LONG: - case WRITE_LONG_2: /* alternate code for WRITE_LONG */ - case WRITE_SAME: - case WRITE_VERIFY: - case WRITE_VERIFY_12: - return 1; + if (ms->dma_started) + halt_dma(ms); + if (mr->fifo_count) { + out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); + udelay(1); + } + + ms->msgphase = msg_none; + switch (phase) { + case BP_DATAIN: + ms->tgts[ms->conn_tgt].data_goes_out = 0; + ms->phase = dataing; + break; + case BP_DATAOUT: + ms->tgts[ms->conn_tgt].data_goes_out = 1; + ms->phase = dataing; + break; + case BP_COMMAND: + ms->phase = commanding; + break; + case BP_STATUS: + ms->phase = statusing; + break; + case BP_MSGIN: + ms->msgphase = msg_in; + ms->n_msgin = 0; + break; + case BP_MSGOUT: + ms->msgphase = msg_out; + if (ms->n_msgout == 0) { + if (ms->aborting) { + do_abort(ms); + } else { + if (ms->last_n_msgout == 0) { + printk(KERN_DEBUG + "mesh: no msg to repeat\n"); + ms->msgout[0] = NOP; + ms->last_n_msgout = 1; + } + ms->n_msgout = ms->last_n_msgout; + } + } + break; default: - return 0; + printk(KERN_DEBUG "mesh: unknown scsi phase %x\n", phase); + ms->stat = DID_ERROR; + mesh_done(ms, 1); + return; } + + start_phase(ms); } -#ifdef MESH_DBG -static inline u32 readtb(void) +static void cmd_complete(struct mesh_state *ms) { - u32 tb; + volatile struct mesh_regs *mr = ms->mesh; + Scsi_Cmnd *cmd = ms->current_req; + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + int seq, n, t; -#ifdef DBG_USE_TB - /* Beware: if you enable this, it will crash on 601s. */ - asm ("mftb %0" : "=r" (tb) : ); -#else - tb = 0; -#endif - return tb; + dlog(ms, "cmd_complete fc=%x", mr->fifo_count); + seq = use_active_neg + (ms->n_msgout? SEQ_ATN: 0); + switch (ms->msgphase) { + case msg_out_xxx: + /* huh? we expected a phase mismatch */ + ms->n_msgin = 0; + ms->msgphase = msg_in; + /* fall through */ + + case msg_in: + /* should have some message bytes in fifo */ + get_msgin(ms); + n = msgin_length(ms); + if (ms->n_msgin < n) { + out_8(&mr->count_lo, n - ms->n_msgin); + out_8(&mr->sequence, SEQ_MSGIN + seq); + } else { + ms->msgphase = msg_none; + handle_msgin(ms); + start_phase(ms); + } + break; + + case msg_in_bad: + out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_MSGIN + SEQ_ATN + use_active_neg); + break; + + case msg_out: + /* + * To get the right timing on ATN wrt ACK, we have + * to get the MESH to drop ACK, wait until REQ gets + * asserted, then drop ATN. To do this we first + * issue a SEQ_MSGOUT with ATN and wait for REQ, + * then change the command to a SEQ_MSGOUT w/o ATN. + * If we don't see REQ in a reasonable time, we + * change the command to SEQ_MSGIN with ATN, + * wait for the phase mismatch interrupt, then + * issue the SEQ_MSGOUT without ATN. + */ + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg + SEQ_ATN); + t = 30; /* wait up to 30us */ + while ((in_8(&mr->bus_status0) & BS0_REQ) == 0 && --t >= 0) + udelay(1); + dlog(ms, "last_mbyte err/exc/fc/cl=%.8x", + MKWORD(mr->error, mr->exception, + mr->fifo_count, mr->count_lo)); + if (in_8(&mr->interrupt) & (INT_ERROR | INT_EXCEPTION)) { + /* whoops, target didn't do what we expected */ + ms->last_n_msgout = ms->n_msgout; + ms->n_msgout = 0; + if (in_8(&mr->interrupt) & INT_ERROR) { + printk(KERN_ERR "mesh: error %x in msg_out\n", + in_8(&mr->error)); + handle_error(ms); + return; + } + if (in_8(&mr->exception) != EXC_PHASEMM) + printk(KERN_ERR "mesh: exc %x in msg_out\n", + in_8(&mr->exception)); + else + printk(KERN_DEBUG "mesh: bs0=%x in msg_out\n", + in_8(&mr->bus_status0)); + handle_exception(ms); + return; + } + if (in_8(&mr->bus_status0) & BS0_REQ) { + out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->fifo, ms->msgout[ms->n_msgout-1]); + ms->msgphase = msg_out_last; + } else { + out_8(&mr->sequence, SEQ_MSGIN + use_active_neg + SEQ_ATN); + ms->msgphase = msg_out_xxx; + } + break; + + case msg_out_last: + ms->last_n_msgout = ms->n_msgout; + ms->n_msgout = 0; + ms->msgphase = ms->expect_reply? msg_in: msg_none; + start_phase(ms); + break; + + case msg_none: + switch (ms->phase) { + case idle: + printk(KERN_ERR "mesh: interrupt in idle phase?\n"); + dumpslog(ms); + return; + case selecting: + dlog(ms, "Selecting phase at command completion",0); + ms->msgout[0] = IDENTIFY(ALLOW_RESEL(ms->conn_tgt), + (cmd? cmd->device->lun: 0)); + ms->n_msgout = 1; + ms->expect_reply = 0; + if (ms->aborting) { + ms->msgout[0] = ABORT; + ms->n_msgout++; + } else if (tp->sdtr_state == do_sdtr) { + /* add SDTR message */ + add_sdtr_msg(ms); + ms->expect_reply = 1; + tp->sdtr_state = sdtr_sent; + } + ms->msgphase = msg_out; + /* + * We need to wait for REQ before dropping ATN. + * We wait for at most 30us, then fall back to + * a scheme where we issue a SEQ_COMMAND with ATN, + * which will give us a phase mismatch interrupt + * when REQ does come, and then we send the message. + */ + t = 230; /* wait up to 230us */ + while ((in_8(&mr->bus_status0) & BS0_REQ) == 0) { + if (--t < 0) { + dlog(ms, "impatient for req", ms->n_msgout); + ms->msgphase = msg_none; + break; + } + udelay(1); + } + break; + case dataing: + if (ms->dma_count != 0) { + start_phase(ms); + return; + } + /* + * We can get a phase mismatch here if the target + * changes to the status phase, even though we have + * had a command complete interrupt. Then, if we + * issue the SEQ_STATUS command, we'll get a sequence + * error interrupt. Which isn't so bad except that + * occasionally the mesh actually executes the + * SEQ_STATUS *as well as* giving us the sequence + * error and phase mismatch exception. + */ + out_8(&mr->sequence, 0); + out_8(&mr->interrupt, + INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + halt_dma(ms); + break; + case statusing: + if (cmd) { + cmd->SCp.Status = mr->fifo; + if (DEBUG_TARGET(cmd)) + printk(KERN_DEBUG "mesh: status is %x\n", + cmd->SCp.Status); + } + ms->msgphase = msg_in; + break; + case busfreeing: + mesh_done(ms, 1); + return; + case disconnecting: + ms->current_req = 0; + ms->phase = idle; + mesh_start(ms); + return; + default: + break; + } + ++ms->phase; + start_phase(ms); + break; + } } -static void dlog(struct mesh_state *ms, char *fmt, int a) + +/* + * Called by midlayer with host locked to queue a new + * request + */ +static int mesh_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) { - struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; - struct dbglog *tlp, *slp; + struct mesh_state *ms; - tlp = &tp->log[tp->log_ix]; - slp = &ms->log[ms->log_ix]; - tlp->fmt = fmt; - tlp->tb = readtb(); - tlp->phase = (ms->msgphase << 4) + ms->phase; - tlp->bs0 = ms->mesh->bus_status0; - tlp->bs1 = ms->mesh->bus_status1; - tlp->tgt = ms->conn_tgt; - tlp->d = a; - *slp = *tlp; - if (++tp->log_ix >= N_DBG_LOG) - tp->log_ix = 0; - if (tp->n_log < N_DBG_LOG) - ++tp->n_log; - if (++ms->log_ix >= N_DBG_SLOG) - ms->log_ix = 0; - if (ms->n_log < N_DBG_SLOG) - ++ms->n_log; + cmd->scsi_done = done; + cmd->host_scribble = NULL; + + ms = (struct mesh_state *) cmd->device->host->hostdata; + + if (ms->request_q == NULL) + ms->request_q = cmd; + else + ms->request_qtail->host_scribble = (void *) cmd; + ms->request_qtail = cmd; + + if (ms->phase == idle) + mesh_start(ms); + + return 0; } -static void dumplog(struct mesh_state *ms, int t) +/* + * Called to handle interrupts, either call by the interrupt + * handler (do_mesh_interrupt) or by other functions in + * exceptional circumstances + */ +static void mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) { - struct mesh_target *tp = &ms->tgts[t]; - struct dbglog *lp; - int i; + struct mesh_state *ms = (struct mesh_state *) dev_id; + volatile struct mesh_regs *mr = ms->mesh; + int intr; - if (tp->n_log == 0) - return; - i = tp->log_ix - tp->n_log; - if (i < 0) - i += N_DBG_LOG; - tp->n_log = 0; - do { - lp = &tp->log[i]; - printk(KERN_DEBUG "mesh log %d: bs=%.2x%.2x ph=%.2x ", - t, lp->bs1, lp->bs0, lp->phase); -#ifdef DBG_USE_TB - printk("tb=%10u ", lp->tb); +#if 0 + if (ALLOW_DEBUG(ms->conn_tgt)) + printk(KERN_DEBUG "mesh_intr, bs0=%x int=%x exc=%x err=%x " + "phase=%d msgphase=%d\n", mr->bus_status0, + mr->interrupt, mr->exception, mr->error, + ms->phase, ms->msgphase); #endif - printk(lp->fmt, lp->d); - printk("\n"); - if (++i >= N_DBG_LOG) - i = 0; - } while (i != tp->log_ix); + while ((intr = in_8(&mr->interrupt)) != 0) { + dlog(ms, "interrupt intr/err/exc/seq=%.8x", + MKWORD(intr, mr->error, mr->exception, mr->sequence)); + if (intr & INT_ERROR) { + handle_error(ms); + } else if (intr & INT_EXCEPTION) { + handle_exception(ms); + } else if (intr & INT_CMDDONE) { + out_8(&mr->interrupt, INT_CMDDONE); + cmd_complete(ms); + } + } } -static void dumpslog(struct mesh_state *ms) +/* 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. + */ +static int mesh_abort(Scsi_Cmnd *cmd) { - struct dbglog *lp; - int i; + struct mesh_state *ms = (struct mesh_state *) cmd->device->host->hostdata; - if (ms->n_log == 0) + printk(KERN_DEBUG "mesh_abort(%p)\n", cmd); + mesh_dump_regs(ms); + dumplog(ms, cmd->device->id); + dumpslog(ms); + return SCSI_ABORT_SNOOZE; +} + +/* + * 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 + */ +static int mesh_host_reset(Scsi_Cmnd *cmd) +{ + struct mesh_state *ms = (struct mesh_state *) cmd->device->host->hostdata; + volatile struct mesh_regs *mr = ms->mesh; + volatile struct dbdma_regs *md = ms->dma; + + 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 */ + out_8(&mr->sequence, SEQ_RESETMESH); + mesh_flush_io(mr); + 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 */ + mesh_flush_io(mr); + udelay(30); /* leave it on for >= 25us */ + out_8(&mr->bus_status1, 0); /* negate RST */ + + /* Complete pending commands */ + handle_reset(ms); + + return SUCCESS; +} + +static void set_mesh_power(struct mesh_state *ms, int state) +{ + if (_machine != _MACH_Pmac) return; - i = ms->log_ix - ms->n_log; - if (i < 0) - i += N_DBG_SLOG; - ms->n_log = 0; - do { - lp = &ms->log[i]; - printk(KERN_DEBUG "mesh log: bs=%.2x%.2x ph=%.2x t%d ", - lp->bs1, lp->bs0, lp->phase, lp->tgt); -#ifdef DBG_USE_TB - printk("tb=%10u ", lp->tb); -#endif - printk(lp->fmt, lp->d); - printk("\n"); - if (++i >= N_DBG_SLOG) - i = 0; - } while (i != ms->log_ix); + if (state) { + pmac_call_feature(PMAC_FTR_MESH_ENABLE, macio_get_of_node(ms->mdev), 0, 1); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/5); + } else { + pmac_call_feature(PMAC_FTR_MESH_ENABLE, macio_get_of_node(ms->mdev), 0, 0); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/100); + } +} + + +#ifdef CONFIG_PM +static int mesh_suspend(struct macio_dev *mdev, u32 state) +{ + struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); + unsigned long flags; + + if (state == mdev->ofdev.dev.power_state || state < 2) + return 0; + + 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(HZ/100); + 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); + + mdev->ofdev.dev.power_state = state; + + return 0; +} + +static int mesh_resume(struct macio_dev *mdev) +{ + struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); + unsigned long flags; + + if (mdev->ofdev.dev.power_state == 0) + return 0; + + 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); + + mdev->ofdev.dev.power_state = 0; + + return 0; } -#endif /* MESH_DBG */ -static Scsi_Host_Template driver_template = { +#endif /* CONFIG_PM */ + +/* + * If we leave drives set for synchronous transfers (especially + * CDROMs), and reboot to MacOS, it gets confused, poor thing. + * So, on reboot we reset the SCSI bus. + */ +static int mesh_shutdown(struct macio_dev *mdev) +{ + struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); + volatile struct mesh_regs *mr; + unsigned long flags; + + printk(KERN_INFO "resetting MESH scsi bus(es)\n"); + spin_lock_irqsave(ms->host->host_lock, flags); + mr = ms->mesh; + out_8(&mr->intr_mask, 0); + out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + out_8(&mr->bus_status1, BS1_RST); + mesh_flush_io(mr); + udelay(30); + out_8(&mr->bus_status1, 0); + spin_unlock_irqrestore(ms->host->host_lock, flags); + + return 0; +} + +static Scsi_Host_Template mesh_template = { .proc_name = "mesh", .name = "MESH", - .detect = mesh_detect, - .release = mesh_release, .queuecommand = mesh_queue, .eh_abort_handler = mesh_abort, .eh_host_reset_handler = mesh_host_reset, @@ -2057,4 +1845,222 @@ static Scsi_Host_Template driver_template = { .use_clustering = DISABLE_CLUSTERING, }; -#include "scsi_module.c" +static int mesh_probe(struct macio_dev *mdev, const struct of_match *match) +{ + struct device_node *mesh = macio_get_of_node(mdev); + struct pci_dev* pdev = macio_get_pci_dev(mdev); + int tgt, *cfp, minper; + struct mesh_state *ms; + struct Scsi_Host *mesh_host; + void *dma_cmd_space; + dma_addr_t dma_cmd_bus; + + switch (mdev->bus->chip->type) { + case macio_heathrow: + case macio_gatwick: + case macio_paddington: + use_active_neg = 0; + break; + default: + use_active_neg = SEQ_ACTIVE_NEG; + } + + if (macio_resource_count(mdev) != 2 || macio_irq_count(mdev) != 2) { + printk(KERN_ERR "mesh: expected 2 addrs and 2 intrs" + " (got %d,%d)\n", mesh->n_addrs, mesh->n_intrs); + return -ENODEV; + } + + if (macio_request_resources(mdev, "mesh") != 0) { + printk(KERN_ERR "mesh: unable to request memory resources"); + return -EBUSY; + } + mesh_host = scsi_host_alloc(&mesh_template, sizeof(struct mesh_state)); + if (mesh_host == NULL) { + printk(KERN_ERR "mesh: couldn't register host"); + goto out_release; + } + + /* Old junk for root discovery, that will die ultimately */ +#if !defined(MODULE) + note_scsi_host(mesh, mesh_host); +#endif + + mesh_host->base = macio_resource_start(mdev, 0); + mesh_host->irq = macio_irq(mdev, 0); + ms = (struct mesh_state *) mesh_host->hostdata; + macio_set_drvdata(mdev, ms); + ms->host = mesh_host; + ms->mdev = mdev; + ms->pdev = pdev; + + ms->mesh = (volatile struct mesh_regs *) + ioremap(macio_resource_start(mdev, 0), 0x1000); + if (ms->mesh == NULL) { + printk(KERN_ERR "mesh: can't map registers\n"); + goto out_free; + } + ms->dma = (volatile struct dbdma_regs *) + ioremap(macio_resource_start(mdev, 1), 0x1000); + if (ms->dma == NULL) { + printk(KERN_ERR "mesh: can't map registers\n"); + iounmap((void *)ms->mesh); + goto out_free; + } + + ms->meshintr = macio_irq(mdev, 0); + ms->dmaintr = macio_irq(mdev, 1); + + /* Space for dma command list: +1 for stop command, + * +1 to allow for aligning. + */ + ms->dma_cmd_size = (mesh_host->sg_tablesize + 2) * sizeof(struct dbdma_cmd); + + /* We use the PCI APIs for now until the generic one gets fixed + * enough or until we get some macio-specific versions + */ + dma_cmd_space = pci_alloc_consistent(macio_get_pci_dev(mdev), + ms->dma_cmd_size, + &dma_cmd_bus); + if (dma_cmd_space == NULL) { + printk(KERN_ERR "mesh: can't allocate DMA table\n"); + goto out_unmap; + } + memset(dma_cmd_space, 0, ms->dma_cmd_size); + + ms->dma_cmds = (struct dbdma_cmd *) DBDMA_ALIGN(dma_cmd_space); + ms->dma_cmd_space = dma_cmd_space; + ms->dma_cmd_bus = dma_cmd_bus + ((unsigned long)ms->dma_cmds) + - (unsigned long)dma_cmd_space; + ms->current_req = NULL; + for (tgt = 0; tgt < 8; ++tgt) { + ms->tgts[tgt].sdtr_state = do_sdtr; + ms->tgts[tgt].sync_params = ASYNC_PARAMS; + ms->tgts[tgt].current_req = 0; + } + + if ((cfp = (int *) get_property(mesh, "clock-frequency", NULL))) + ms->clk_freq = *cfp; + else { + printk(KERN_INFO "mesh: assuming 50MHz clock frequency\n"); + ms->clk_freq = 50000000; + } + + /* The maximum sync rate is clock / 5; increase + * mesh_sync_period if necessary. + */ + minper = 1000000000 / (ms->clk_freq / 5); /* ns */ + if (mesh_sync_period < minper) + mesh_sync_period = minper; + + /* Power up the chip */ + set_mesh_power(ms, 1); + + /* Set it up */ + mesh_init(ms); + + /* XXX FIXME: error should be fatal */ + if (request_irq(ms->meshintr, do_mesh_interrupt, 0, "MESH", ms)) + printk(KERN_ERR "MESH: can't get irq %d\n", ms->meshintr); + + /* XXX FIXME: handle failure */ + scsi_add_host(mesh_host, &mdev->ofdev.dev); + scsi_scan_host(mesh_host); + + return 0; + +out_unmap: + iounmap((void *)ms->dma); + iounmap((void *)ms->mesh); +out_free: + scsi_host_put(mesh_host); +out_release: + macio_release_resources(mdev); + + return -ENODEV; +} + +static int mesh_remove(struct macio_dev *mdev) +{ + struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); + struct Scsi_Host *mesh_host = ms->host; + + scsi_remove_host(mesh_host); + + free_irq(ms->meshintr, ms); + + /* Reset scsi bus */ + mesh_shutdown(mdev); + + /* Shut down chip & termination */ + set_mesh_power(ms, 0); + + /* Unmap registers & dma controller */ + iounmap((void *) ms->mesh); + iounmap((void *) ms->dma); + + /* Free DMA commands memory */ + pci_free_consistent(macio_get_pci_dev(mdev), ms->dma_cmd_size, + ms->dma_cmd_space, ms->dma_cmd_bus); + + /* Release memory resources */ + macio_release_resources(mdev); + + scsi_host_put(mesh_host); + + return 0; +} + + +static struct of_match mesh_match[] = +{ + { + .name = "mesh", + .type = OF_ANY_MATCH, + .compatible = OF_ANY_MATCH + }, + { + .name = OF_ANY_MATCH, + .type = "scsi", + .compatible = "chrp,mesh0" + }, + {}, +}; + +static struct macio_driver mesh_driver = +{ + .name = "mesh", + .match_table = mesh_match, + .probe = mesh_probe, + .remove = mesh_remove, + .shutdown = mesh_shutdown, +#ifdef CONFIG_PM + .suspend = mesh_suspend, + .resume = mesh_resume, +#endif +}; + + +static int __init init_mesh(void) +{ + + /* 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"); + + return macio_register_driver(&mesh_driver); +} + +static void __exit exit_mesh(void) +{ + return macio_unregister_driver(&mesh_driver); +} + +module_init(init_mesh); +module_exit(exit_mesh); diff --git a/drivers/serial/pmac_zilog.c b/drivers/serial/pmac_zilog.c index a53e837aba21..43c13e34dcd5 100644 --- a/drivers/serial/pmac_zilog.c +++ b/drivers/serial/pmac_zilog.c @@ -1120,7 +1120,7 @@ static struct uart_ops pmz_pops = { * Unlike sunzilog, we don't need to pre-init the spinlock as we don't * register our console before uart_add_one_port() is called */ -static int __init pmz_setup_port(struct uart_pmac_port *up, int early) +static int __init pmz_setup_port(struct uart_pmac_port *up) { struct device_node *np = up->node; char *conn; @@ -1133,11 +1133,6 @@ static int __init pmz_setup_port(struct uart_pmac_port *up, int early) /* * Request & map chip registers */ - if (!early && request_OF_resource(np, 0, NULL) == NULL) { - printk("pmac_zilog: failed to request resources for %s\n", - np->full_name); - return -EBUSY; - } up->port.mapbase = np->addrs[0].address; up->port.membase = ioremap(up->port.mapbase, 0x1000); @@ -1152,27 +1147,23 @@ static int __init pmz_setup_port(struct uart_pmac_port *up, int early) up->flags |= PMACZILOG_FLAG_HAS_DMA; #endif if (ZS_HAS_DMA(up)) { - if (!early && request_OF_resource(np, np->n_addrs - 2, " (tx dma)") == NULL) { - printk(KERN_ERR "pmac_zilog: can't request TX DMA resource !\n"); + up->tx_dma_regs = (volatile struct dbdma_regs *) + ioremap(np->addrs[np->n_addrs - 2].address, 0x1000); + if (up->tx_dma_regs == NULL) { up->flags &= ~PMACZILOG_FLAG_HAS_DMA; goto no_dma; } - if (!early && request_OF_resource(np, np->n_addrs - 1, " (rx dma)") == NULL) { - release_OF_resource(np, np->n_addrs - 2); - printk(KERN_ERR "pmac_zilog: can't request RX DMA resource !\n"); + up->rx_dma_regs = (volatile struct dbdma_regs *) + ioremap(np->addrs[np->n_addrs - 1].address, 0x1000); + if (up->rx_dma_regs == NULL) { + iounmap((void *)up->tx_dma_regs); up->flags &= ~PMACZILOG_FLAG_HAS_DMA; goto no_dma; } - up->tx_dma_regs = (volatile struct dbdma_regs *) - ioremap(np->addrs[np->n_addrs - 2].address, 0x1000); - up->rx_dma_regs = (volatile struct dbdma_regs *) - ioremap(np->addrs[np->n_addrs - 1].address, 0x1000); up->tx_dma_irq = np->intrs[1].line; up->rx_dma_irq = np->intrs[2].line; } no_dma: - if (!early) - up->flags |= PMACZILOG_FLAG_RSRC_REQUESTED; /* * Detect port type @@ -1258,8 +1249,15 @@ static int pmz_attach(struct macio_dev *mdev, const struct of_match *match) */ for (i = 0; i < MAX_ZS_PORTS; i++) if (pmz_ports[i].node == mdev->ofdev.node) { - pmz_ports[i].dev = mdev; - dev_set_drvdata(&mdev->ofdev.dev, &pmz_ports[i]); + struct uart_pmac_port *up = &pmz_ports[i]; + + up->dev = mdev; + dev_set_drvdata(&mdev->ofdev.dev, up); + if (macio_request_resources(up->dev, "pmac_zilog")) + printk(KERN_WARNING "%s: Failed to request resource, port still active\n", + up->node->name); + else + up->flags |= PMACZILOG_FLAG_RSRC_REQUESTED; return 0; } return -ENODEV; @@ -1271,13 +1269,17 @@ static int pmz_attach(struct macio_dev *mdev, const struct of_match *match) */ static int pmz_detach(struct macio_dev *mdev) { - struct uart_pmac_port *port = dev_get_drvdata(&mdev->ofdev.dev); + struct uart_pmac_port *up = dev_get_drvdata(&mdev->ofdev.dev); - if (!port) + if (!up) return -ENODEV; + if (up->flags & PMACZILOG_FLAG_RSRC_REQUESTED) { + macio_release_resources(up->dev); + up->flags &= ~PMACZILOG_FLAG_RSRC_REQUESTED; + } dev_set_drvdata(&mdev->ofdev.dev, NULL); - port->dev = NULL; + up->dev = NULL; return 0; } @@ -1288,7 +1290,7 @@ static int pmz_detach(struct macio_dev *mdev) * used later to "attach" to the sysfs tree so we get power management * events */ -static int __init pmz_probe(int early) +static int __init pmz_probe(void) { struct device_node *node_p, *node_a, *node_b, *np; int count = 0; @@ -1333,9 +1335,9 @@ static int __init pmz_probe(int early) /* * Setup the ports for real */ - rc = pmz_setup_port(&pmz_ports[count], early); + rc = pmz_setup_port(&pmz_ports[count]); if (rc == 0) - rc = pmz_setup_port(&pmz_ports[count+1], early); + rc = pmz_setup_port(&pmz_ports[count+1]); if (rc != 0) { of_node_put(node_a); of_node_put(node_b); @@ -1436,43 +1438,10 @@ static struct macio_driver pmz_driver = // .resume = pmz_resume, *** NYI }; -static void pmz_fixup_resources(void) -{ - int i; - for (i=0; i<pmz_ports_count; i++) { - struct uart_pmac_port *up = &pmz_ports[i]; - - if (up->node == NULL) - continue; - if (up->flags & PMACZILOG_FLAG_RSRC_REQUESTED) - continue; - if (request_OF_resource(up->node, 0, NULL) == NULL) - printk(KERN_WARNING "%s: Failed to do late IO resource request, port still active\n", - up->node->name); - up->flags |= PMACZILOG_FLAG_RSRC_REQUESTED; - if (!ZS_HAS_DMA(up)) - continue; - if (request_OF_resource(up->node, up->node->n_addrs - 2, NULL) == NULL) - printk(KERN_WARNING "%s: Failed to do late DMA resource request, port still active\n", - up->node->name); - if (request_OF_resource(up->node, up->node->n_addrs - 1, NULL) == NULL) - printk(KERN_WARNING "%s: Failed to do late DMA resource request, port still active\n", - up->node->name); - } - -} - static int __init init_pmz(void) { printk(KERN_DEBUG "%s\n", version); - /* - * If we had serial console, then we didn't request - * resources yet. We fix that up now - */ - if (pmz_ports_count > 0) - pmz_fixup_resources(); - /* * First, we need to do a direct OF-based probe pass. We * do that because we want serial console up before the @@ -1481,7 +1450,7 @@ static int __init init_pmz(void) * uart_register_driver() */ if (pmz_ports_count == 0) - pmz_probe(0); + pmz_probe(); /* * Bail early if no port found @@ -1610,7 +1579,7 @@ static int __init pmz_console_setup(struct console *co, char *options) static int __init pmz_console_init(void) { /* Probe ports */ - pmz_probe(1); + pmz_probe(); /* TODO: Autoprobe console based on OF */ /* pmz_console.index = i; */ |
