diff options
Diffstat (limited to 'drivers/ide/legacy')
-rw-r--r-- | drivers/ide/legacy/Makefile | 13 | ||||
-rw-r--r-- | drivers/ide/legacy/ali14xx.c | 253 | ||||
-rw-r--r-- | drivers/ide/legacy/buddha.c | 235 | ||||
-rw-r--r-- | drivers/ide/legacy/dtc2278.c | 165 | ||||
-rw-r--r-- | drivers/ide/legacy/falconide.c | 78 | ||||
-rw-r--r-- | drivers/ide/legacy/gayle.c | 186 | ||||
-rw-r--r-- | drivers/ide/legacy/hd.c | 864 | ||||
-rw-r--r-- | drivers/ide/legacy/ht6560b.c | 370 | ||||
-rw-r--r-- | drivers/ide/legacy/ide-cs.c | 481 | ||||
-rw-r--r-- | drivers/ide/legacy/macide.c | 155 | ||||
-rw-r--r-- | drivers/ide/legacy/q40ide.c | 150 | ||||
-rw-r--r-- | drivers/ide/legacy/qd65xx.c | 511 | ||||
-rw-r--r-- | drivers/ide/legacy/qd65xx.h | 140 | ||||
-rw-r--r-- | drivers/ide/legacy/umc8672.c | 183 |
14 files changed, 3784 insertions, 0 deletions
diff --git a/drivers/ide/legacy/Makefile b/drivers/ide/legacy/Makefile new file mode 100644 index 000000000000..c7971061767e --- /dev/null +++ b/drivers/ide/legacy/Makefile @@ -0,0 +1,13 @@ + +obj-$(CONFIG_BLK_DEV_ALI14XX) += ali14xx.o +obj-$(CONFIG_BLK_DEV_DTC2278) += dtc2278.o +obj-$(CONFIG_BLK_DEV_HT6560B) += ht6560b.o +obj-$(CONFIG_BLK_DEV_QD65XX) += qd65xx.o +obj-$(CONFIG_BLK_DEV_UMC8672) += umc8672.o + +obj-$(CONFIG_BLK_DEV_IDECS) += ide-cs.o + +# Last of all +obj-$(CONFIG_BLK_DEV_HD) += hd.o + +EXTRA_CFLAGS := -Idrivers/ide diff --git a/drivers/ide/legacy/ali14xx.c b/drivers/ide/legacy/ali14xx.c new file mode 100644 index 000000000000..fb88711812e6 --- /dev/null +++ b/drivers/ide/legacy/ali14xx.c @@ -0,0 +1,253 @@ +/* + * linux/drivers/ide/legacy/ali14xx.c Version 0.03 Feb 09, 1996 + * + * Copyright (C) 1996 Linus Torvalds & author (see below) + */ + +/* + * ALI M14xx chipset EIDE controller + * + * Works for ALI M1439/1443/1445/1487/1489 chipsets. + * + * Adapted from code developed by derekn@vw.ece.cmu.edu. -ml + * Derek's notes follow: + * + * I think the code should be pretty understandable, + * but I'll be happy to (try to) answer questions. + * + * The critical part is in the setupDrive function. The initRegisters + * function doesn't seem to be necessary, but the DOS driver does it, so + * I threw it in. + * + * I've only tested this on my system, which only has one disk. I posted + * it to comp.sys.linux.hardware, so maybe some other people will try it + * out. + * + * Derek Noonburg (derekn@ece.cmu.edu) + * 95-sep-26 + * + * Update 96-jul-13: + * + * I've since upgraded to two disks and a CD-ROM, with no trouble, and + * I've also heard from several others who have used it successfully. + * This driver appears to work with both the 1443/1445 and the 1487/1489 + * chipsets. I've added support for PIO mode 4 for the 1487. This + * seems to work just fine on the 1443 also, although I'm not sure it's + * advertised as supporting mode 4. (I've been running a WDC AC21200 in + * mode 4 for a while now with no trouble.) -Derek + */ + +#undef REALLY_SLOW_IO /* most systems can safely undef this */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> +#include <linux/init.h> + +#include <asm/io.h> + +/* port addresses for auto-detection */ +#define ALI_NUM_PORTS 4 +static int ports[ALI_NUM_PORTS] __initdata = {0x074, 0x0f4, 0x034, 0x0e4}; + +/* register initialization data */ +typedef struct { u8 reg, data; } RegInitializer; + +static RegInitializer initData[] __initdata = { + {0x01, 0x0f}, {0x02, 0x00}, {0x03, 0x00}, {0x04, 0x00}, + {0x05, 0x00}, {0x06, 0x00}, {0x07, 0x2b}, {0x0a, 0x0f}, + {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, {0x28, 0x00}, + {0x29, 0x00}, {0x2a, 0x00}, {0x2f, 0x00}, {0x2b, 0x00}, + {0x2c, 0x00}, {0x2d, 0x00}, {0x2e, 0x00}, {0x30, 0x00}, + {0x31, 0x00}, {0x32, 0x00}, {0x33, 0x00}, {0x34, 0xff}, + {0x35, 0x03}, {0x00, 0x00} +}; + +#define ALI_MAX_PIO 4 + +/* timing parameter registers for each drive */ +static struct { u8 reg1, reg2, reg3, reg4; } regTab[4] = { + {0x03, 0x26, 0x04, 0x27}, /* drive 0 */ + {0x05, 0x28, 0x06, 0x29}, /* drive 1 */ + {0x2b, 0x30, 0x2c, 0x31}, /* drive 2 */ + {0x2d, 0x32, 0x2e, 0x33}, /* drive 3 */ +}; + +static int basePort; /* base port address */ +static int regPort; /* port for register number */ +static int dataPort; /* port for register data */ +static u8 regOn; /* output to base port to access registers */ +static u8 regOff; /* output to base port to close registers */ + +/*------------------------------------------------------------------------*/ + +/* + * Read a controller register. + */ +static inline u8 inReg (u8 reg) +{ + outb_p(reg, regPort); + return inb(dataPort); +} + +/* + * Write a controller register. + */ +static void outReg (u8 data, u8 reg) +{ + outb_p(reg, regPort); + outb_p(data, dataPort); +} + +/* + * Set PIO mode for the specified drive. + * This function computes timing parameters + * and sets controller registers accordingly. + */ +static void ali14xx_tune_drive (ide_drive_t *drive, u8 pio) +{ + int driveNum; + int time1, time2; + u8 param1, param2, param3, param4; + unsigned long flags; + ide_pio_data_t d; + int bus_speed = system_bus_clock(); + + pio = ide_get_best_pio_mode(drive, pio, ALI_MAX_PIO, &d); + + /* calculate timing, according to PIO mode */ + time1 = d.cycle_time; + time2 = ide_pio_timings[pio].active_time; + param3 = param1 = (time2 * bus_speed + 999) / 1000; + param4 = param2 = (time1 * bus_speed + 999) / 1000 - param1; + if (pio < 3) { + param3 += 8; + param4 += 8; + } + printk(KERN_DEBUG "%s: PIO mode%d, t1=%dns, t2=%dns, cycles = %d+%d, %d+%d\n", + drive->name, pio, time1, time2, param1, param2, param3, param4); + + /* stuff timing parameters into controller registers */ + driveNum = (HWIF(drive)->index << 1) + drive->select.b.unit; + spin_lock_irqsave(&ide_lock, flags); + outb_p(regOn, basePort); + outReg(param1, regTab[driveNum].reg1); + outReg(param2, regTab[driveNum].reg2); + outReg(param3, regTab[driveNum].reg3); + outReg(param4, regTab[driveNum].reg4); + outb_p(regOff, basePort); + spin_unlock_irqrestore(&ide_lock, flags); +} + +/* + * Auto-detect the IDE controller port. + */ +static int __init findPort (void) +{ + int i; + u8 t; + unsigned long flags; + + local_irq_save(flags); + for (i = 0; i < ALI_NUM_PORTS; ++i) { + basePort = ports[i]; + regOff = inb(basePort); + for (regOn = 0x30; regOn <= 0x33; ++regOn) { + outb_p(regOn, basePort); + if (inb(basePort) == regOn) { + regPort = basePort + 4; + dataPort = basePort + 8; + t = inReg(0) & 0xf0; + outb_p(regOff, basePort); + local_irq_restore(flags); + if (t != 0x50) + return 0; + return 1; /* success */ + } + } + outb_p(regOff, basePort); + } + local_irq_restore(flags); + return 0; +} + +/* + * Initialize controller registers with default values. + */ +static int __init initRegisters (void) { + RegInitializer *p; + u8 t; + unsigned long flags; + + local_irq_save(flags); + outb_p(regOn, basePort); + for (p = initData; p->reg != 0; ++p) + outReg(p->data, p->reg); + outb_p(0x01, regPort); + t = inb(regPort) & 0x01; + outb_p(regOff, basePort); + local_irq_restore(flags); + return t; +} + +static int __init ali14xx_probe(void) +{ + ide_hwif_t *hwif, *mate; + + printk(KERN_DEBUG "ali14xx: base=0x%03x, regOn=0x%02x.\n", + basePort, regOn); + + /* initialize controller registers */ + if (!initRegisters()) { + printk(KERN_ERR "ali14xx: Chip initialization failed.\n"); + return 1; + } + + hwif = &ide_hwifs[0]; + mate = &ide_hwifs[1]; + + hwif->chipset = ide_ali14xx; + hwif->tuneproc = &ali14xx_tune_drive; + hwif->mate = mate; + + mate->chipset = ide_ali14xx; + mate->tuneproc = &ali14xx_tune_drive; + mate->mate = hwif; + mate->channel = 1; + + probe_hwif_init(hwif); + probe_hwif_init(mate); + + create_proc_ide_interfaces(); + + return 0; +} + +/* Can be called directly from ide.c. */ +int __init ali14xx_init(void) +{ + /* auto-detect IDE controller port */ + if (findPort()) { + if (ali14xx_probe()) + return -ENODEV; + return 0; + } + printk(KERN_ERR "ali14xx: not found.\n"); + return -ENODEV; +} + +#ifdef MODULE +module_init(ali14xx_init); +#endif + +MODULE_AUTHOR("see local file"); +MODULE_DESCRIPTION("support of ALI 14XX IDE chipsets"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ide/legacy/buddha.c b/drivers/ide/legacy/buddha.c new file mode 100644 index 000000000000..0391a3122878 --- /dev/null +++ b/drivers/ide/legacy/buddha.c @@ -0,0 +1,235 @@ +/* + * linux/drivers/ide/legacy/buddha.c -- Amiga Buddha, Catweasel and X-Surf IDE Driver + * + * Copyright (C) 1997, 2001 by Geert Uytterhoeven and others + * + * This driver was written based on the specifications in README.buddha and + * the X-Surf info from Inside_XSurf.txt available at + * http://www.jschoenfeld.com + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + * TODO: + * - test it :-) + * - tune the timings using the speed-register + */ + +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/zorro.h> +#include <linux/ide.h> +#include <linux/init.h> + +#include <asm/amigahw.h> +#include <asm/amigaints.h> + + + /* + * The Buddha has 2 IDE interfaces, the Catweasel has 3, X-Surf has 2 + */ + +#define BUDDHA_NUM_HWIFS 2 +#define CATWEASEL_NUM_HWIFS 3 +#define XSURF_NUM_HWIFS 2 + + /* + * Bases of the IDE interfaces (relative to the board address) + */ + +#define BUDDHA_BASE1 0x800 +#define BUDDHA_BASE2 0xa00 +#define BUDDHA_BASE3 0xc00 + +#define XSURF_BASE1 0xb000 /* 2.5" Interface */ +#define XSURF_BASE2 0xd000 /* 3.5" Interface */ + +static u_int buddha_bases[CATWEASEL_NUM_HWIFS] __initdata = { + BUDDHA_BASE1, BUDDHA_BASE2, BUDDHA_BASE3 +}; + +static u_int xsurf_bases[XSURF_NUM_HWIFS] __initdata = { + XSURF_BASE1, XSURF_BASE2 +}; + + + /* + * Offsets from one of the above bases + */ + +#define BUDDHA_DATA 0x00 +#define BUDDHA_ERROR 0x06 /* see err-bits */ +#define BUDDHA_NSECTOR 0x0a /* nr of sectors to read/write */ +#define BUDDHA_SECTOR 0x0e /* starting sector */ +#define BUDDHA_LCYL 0x12 /* starting cylinder */ +#define BUDDHA_HCYL 0x16 /* high byte of starting cyl */ +#define BUDDHA_SELECT 0x1a /* 101dhhhh , d=drive, hhhh=head */ +#define BUDDHA_STATUS 0x1e /* see status-bits */ +#define BUDDHA_CONTROL 0x11a +#define XSURF_CONTROL -1 /* X-Surf has no CS1* (Control/AltStat) */ + +static int buddha_offsets[IDE_NR_PORTS] __initdata = { + BUDDHA_DATA, BUDDHA_ERROR, BUDDHA_NSECTOR, BUDDHA_SECTOR, BUDDHA_LCYL, + BUDDHA_HCYL, BUDDHA_SELECT, BUDDHA_STATUS, BUDDHA_CONTROL, -1 +}; + +static int xsurf_offsets[IDE_NR_PORTS] __initdata = { + BUDDHA_DATA, BUDDHA_ERROR, BUDDHA_NSECTOR, BUDDHA_SECTOR, BUDDHA_LCYL, + BUDDHA_HCYL, BUDDHA_SELECT, BUDDHA_STATUS, XSURF_CONTROL, -1 +}; + + /* + * Other registers + */ + +#define BUDDHA_IRQ1 0xf00 /* MSB = 1, Harddisk is source of */ +#define BUDDHA_IRQ2 0xf40 /* interrupt */ +#define BUDDHA_IRQ3 0xf80 + +#define XSURF_IRQ1 0x7e +#define XSURF_IRQ2 0x7e + +static int buddha_irqports[CATWEASEL_NUM_HWIFS] __initdata = { + BUDDHA_IRQ1, BUDDHA_IRQ2, BUDDHA_IRQ3 +}; + +static int xsurf_irqports[XSURF_NUM_HWIFS] __initdata = { + XSURF_IRQ1, XSURF_IRQ2 +}; + +#define BUDDHA_IRQ_MR 0xfc0 /* master interrupt enable */ + + + /* + * Board information + */ + +typedef enum BuddhaType_Enum { + BOARD_BUDDHA, BOARD_CATWEASEL, BOARD_XSURF +} BuddhaType; + + + /* + * Check and acknowledge the interrupt status + */ + +static int buddha_ack_intr(ide_hwif_t *hwif) +{ + unsigned char ch; + + ch = z_readb(hwif->io_ports[IDE_IRQ_OFFSET]); + if (!(ch & 0x80)) + return 0; + return 1; +} + +static int xsurf_ack_intr(ide_hwif_t *hwif) +{ + unsigned char ch; + + ch = z_readb(hwif->io_ports[IDE_IRQ_OFFSET]); + /* X-Surf needs a 0 written to IRQ register to ensure ISA bit A11 stays at 0 */ + z_writeb(0, hwif->io_ports[IDE_IRQ_OFFSET]); + if (!(ch & 0x80)) + return 0; + return 1; +} + + /* + * Probe for a Buddha or Catweasel IDE interface + */ + +void __init buddha_init(void) +{ + hw_regs_t hw; + ide_hwif_t *hwif; + int i, index; + + struct zorro_dev *z = NULL; + u_long buddha_board = 0; + BuddhaType type; + int buddha_num_hwifs; + + while ((z = zorro_find_device(ZORRO_WILDCARD, z))) { + unsigned long board; + if (z->id == ZORRO_PROD_INDIVIDUAL_COMPUTERS_BUDDHA) { + buddha_num_hwifs = BUDDHA_NUM_HWIFS; + type=BOARD_BUDDHA; + } else if (z->id == ZORRO_PROD_INDIVIDUAL_COMPUTERS_CATWEASEL) { + buddha_num_hwifs = CATWEASEL_NUM_HWIFS; + type=BOARD_CATWEASEL; + } else if (z->id == ZORRO_PROD_INDIVIDUAL_COMPUTERS_X_SURF) { + buddha_num_hwifs = XSURF_NUM_HWIFS; + type=BOARD_XSURF; + } else + continue; + + board = z->resource.start; + +/* + * FIXME: we now have selectable mmio v/s iomio transports. + */ + + if(type != BOARD_XSURF) { + if (!request_mem_region(board+BUDDHA_BASE1, 0x800, "IDE")) + continue; + } else { + if (!request_mem_region(board+XSURF_BASE1, 0x1000, "IDE")) + continue; + if (!request_mem_region(board+XSURF_BASE2, 0x1000, "IDE")) + goto fail_base2; + if (!request_mem_region(board+XSURF_IRQ1, 0x8, "IDE")) { + release_mem_region(board+XSURF_BASE2, 0x1000); +fail_base2: + release_mem_region(board+XSURF_BASE1, 0x1000); + continue; + } + } + buddha_board = ZTWO_VADDR(board); + + /* write to BUDDHA_IRQ_MR to enable the board IRQ */ + /* X-Surf doesn't have this. IRQs are always on */ + if (type != BOARD_XSURF) + z_writeb(0, buddha_board+BUDDHA_IRQ_MR); + + for(i=0;i<buddha_num_hwifs;i++) { + if(type != BOARD_XSURF) { + ide_setup_ports(&hw, (buddha_board+buddha_bases[i]), + buddha_offsets, 0, + (buddha_board+buddha_irqports[i]), + buddha_ack_intr, +// budda_iops, + IRQ_AMIGA_PORTS); + } else { + ide_setup_ports(&hw, (buddha_board+xsurf_bases[i]), + xsurf_offsets, 0, + (buddha_board+xsurf_irqports[i]), + xsurf_ack_intr, +// xsurf_iops, + IRQ_AMIGA_PORTS); + } + + index = ide_register_hw(&hw, &hwif); + if (index != -1) { + hwif->mmio = 2; + printk("ide%d: ", index); + switch(type) { + case BOARD_BUDDHA: + printk("Buddha"); + break; + case BOARD_CATWEASEL: + printk("Catweasel"); + break; + case BOARD_XSURF: + printk("X-Surf"); + break; + } + printk(" IDE interface\n"); + } + } + } +} diff --git a/drivers/ide/legacy/dtc2278.c b/drivers/ide/legacy/dtc2278.c new file mode 100644 index 000000000000..20eb5b872ca9 --- /dev/null +++ b/drivers/ide/legacy/dtc2278.c @@ -0,0 +1,165 @@ +/* + * linux/drivers/ide/legacy/dtc2278.c Version 0.02 Feb 10, 1996 + * + * Copyright (C) 1996 Linus Torvalds & author (see below) + */ + +#undef REALLY_SLOW_IO /* most systems can safely undef this */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> +#include <linux/init.h> + +#include <asm/io.h> + +/* + * Changing this #undef to #define may solve start up problems in some systems. + */ +#undef ALWAYS_SET_DTC2278_PIO_MODE + +/* + * From: andy@cercle.cts.com (Dyan Wile) + * + * Below is a patch for DTC-2278 - alike software-programmable controllers + * The code enables the secondary IDE controller and the PIO4 (3?) timings on + * the primary (EIDE). You may probably have to enable the 32-bit support to + * get the full speed. You better get the disk interrupts disabled ( hdparm -u0 + * /dev/hd.. ) for the drives connected to the EIDE interface. (I get my + * filesystem corrupted with -u1, but under heavy disk load only :-) + * + * This card is now forced to use the "serialize" feature, + * and irq-unmasking is disallowed. If io_32bit is enabled, + * it must be done for BOTH drives on each interface. + * + * This code was written for the DTC2278E, but might work with any of these: + * + * DTC2278S has only a single IDE interface. + * DTC2278D has two IDE interfaces and is otherwise identical to the S version. + * DTC2278E also has serial ports and a printer port + * DTC2278EB: has onboard BIOS, and "works like a charm" -- Kent Bradford <kent@theory.caltech.edu> + * + * There may be a fourth controller type. The S and D versions use the + * Winbond chip, and I think the E version does also. + * + */ + +static void sub22 (char b, char c) +{ + int i; + + for(i = 0; i < 3; ++i) { + inb(0x3f6); + outb_p(b,0xb0); + inb(0x3f6); + outb_p(c,0xb4); + inb(0x3f6); + if(inb(0xb4) == c) { + outb_p(7,0xb0); + inb(0x3f6); + return; /* success */ + } + } +} + +static void tune_dtc2278 (ide_drive_t *drive, u8 pio) +{ + unsigned long flags; + + pio = ide_get_best_pio_mode(drive, pio, 4, NULL); + + if (pio >= 3) { + spin_lock_irqsave(&ide_lock, flags); + /* + * This enables PIO mode4 (3?) on the first interface + */ + sub22(1,0xc3); + sub22(0,0xa0); + spin_unlock_irqrestore(&ide_lock, flags); + } else { + /* we don't know how to set it back again.. */ + } + + /* + * 32bit I/O has to be enabled for *both* drives at the same time. + */ + drive->io_32bit = 1; + HWIF(drive)->drives[!drive->select.b.unit].io_32bit = 1; +} + +static int __init probe_dtc2278(void) +{ + unsigned long flags; + ide_hwif_t *hwif, *mate; + + hwif = &ide_hwifs[0]; + mate = &ide_hwifs[1]; + + if (hwif->chipset != ide_unknown || mate->chipset != ide_unknown) + return 1; + + local_irq_save(flags); + /* + * This enables the second interface + */ + outb_p(4,0xb0); + inb(0x3f6); + outb_p(0x20,0xb4); + inb(0x3f6); +#ifdef ALWAYS_SET_DTC2278_PIO_MODE + /* + * This enables PIO mode4 (3?) on the first interface + * and may solve start-up problems for some people. + */ + sub22(1,0xc3); + sub22(0,0xa0); +#endif + local_irq_restore(flags); + + hwif->serialized = 1; + hwif->chipset = ide_dtc2278; + hwif->tuneproc = &tune_dtc2278; + hwif->drives[0].no_unmask = 1; + hwif->drives[1].no_unmask = 1; + hwif->mate = mate; + + mate->serialized = 1; + mate->chipset = ide_dtc2278; + mate->drives[0].no_unmask = 1; + mate->drives[1].no_unmask = 1; + mate->mate = hwif; + mate->channel = 1; + + probe_hwif_init(hwif); + probe_hwif_init(mate); + + create_proc_ide_interfaces(); + + return 0; +} + +/* Can be called directly from ide.c. */ +int __init dtc2278_init(void) +{ + if (probe_dtc2278()) { + printk(KERN_ERR "dtc2278: ide interfaces already in use!\n"); + return -EBUSY; + } + return 0; +} + +#ifdef MODULE +module_init(dtc2278_init); +#endif + +MODULE_AUTHOR("See Local File"); +MODULE_DESCRIPTION("support of DTC-2278 VLB IDE chipsets"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ide/legacy/falconide.c b/drivers/ide/legacy/falconide.c new file mode 100644 index 000000000000..a9f2cd5bb81e --- /dev/null +++ b/drivers/ide/legacy/falconide.c @@ -0,0 +1,78 @@ +/* + * linux/drivers/ide/legacy/falconide.c -- Atari Falcon IDE Driver + * + * Created 12 Jul 1997 by Geert Uytterhoeven + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> +#include <linux/init.h> + +#include <asm/setup.h> +#include <asm/atarihw.h> +#include <asm/atariints.h> +#include <asm/atari_stdma.h> + + + /* + * Base of the IDE interface + */ + +#define ATA_HD_BASE 0xfff00000 + + /* + * Offsets from the above base + */ + +#define ATA_HD_DATA 0x00 +#define ATA_HD_ERROR 0x05 /* see err-bits */ +#define ATA_HD_NSECTOR 0x09 /* nr of sectors to read/write */ +#define ATA_HD_SECTOR 0x0d /* starting sector */ +#define ATA_HD_LCYL 0x11 /* starting cylinder */ +#define ATA_HD_HCYL 0x15 /* high byte of starting cyl */ +#define ATA_HD_SELECT 0x19 /* 101dhhhh , d=drive, hhhh=head */ +#define ATA_HD_STATUS 0x1d /* see status-bits */ +#define ATA_HD_CONTROL 0x39 + +static int falconide_offsets[IDE_NR_PORTS] __initdata = { + ATA_HD_DATA, ATA_HD_ERROR, ATA_HD_NSECTOR, ATA_HD_SECTOR, ATA_HD_LCYL, + ATA_HD_HCYL, ATA_HD_SELECT, ATA_HD_STATUS, ATA_HD_CONTROL, -1 +}; + + + /* + * falconide_intr_lock is used to obtain access to the IDE interrupt, + * which is shared between several drivers. + */ + +int falconide_intr_lock; + + + /* + * Probe for a Falcon IDE interface + */ + +void __init falconide_init(void) +{ + if (MACH_IS_ATARI && ATARIHW_PRESENT(IDE)) { + hw_regs_t hw; + int index; + + ide_setup_ports(&hw, ATA_HD_BASE, falconide_offsets, + 0, 0, NULL, +// falconide_iops, + IRQ_MFP_IDE); + index = ide_register_hw(&hw, NULL); + + if (index != -1) + printk("ide%d: Falcon IDE interface\n", index); + } +} diff --git a/drivers/ide/legacy/gayle.c b/drivers/ide/legacy/gayle.c new file mode 100644 index 000000000000..3fac3e9ec47d --- /dev/null +++ b/drivers/ide/legacy/gayle.c @@ -0,0 +1,186 @@ +/* + * linux/drivers/ide/legacy/gayle.c -- Amiga Gayle IDE Driver + * + * Created 9 Jul 1997 by Geert Uytterhoeven + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> +#include <linux/init.h> +#include <linux/zorro.h> + +#include <asm/setup.h> +#include <asm/amigahw.h> +#include <asm/amigaints.h> +#include <asm/amigayle.h> + + + /* + * Bases of the IDE interfaces + */ + +#define GAYLE_BASE_4000 0xdd2020 /* A4000/A4000T */ +#define GAYLE_BASE_1200 0xda0000 /* A1200/A600 and E-Matrix 530 */ + + /* + * Offsets from one of the above bases + */ + +#define GAYLE_DATA 0x00 +#define GAYLE_ERROR 0x06 /* see err-bits */ +#define GAYLE_NSECTOR 0x0a /* nr of sectors to read/write */ +#define GAYLE_SECTOR 0x0e /* starting sector */ +#define GAYLE_LCYL 0x12 /* starting cylinder */ +#define GAYLE_HCYL 0x16 /* high byte of starting cyl */ +#define GAYLE_SELECT 0x1a /* 101dhhhh , d=drive, hhhh=head */ +#define GAYLE_STATUS 0x1e /* see status-bits */ +#define GAYLE_CONTROL 0x101a + +static int gayle_offsets[IDE_NR_PORTS] __initdata = { + GAYLE_DATA, GAYLE_ERROR, GAYLE_NSECTOR, GAYLE_SECTOR, GAYLE_LCYL, + GAYLE_HCYL, GAYLE_SELECT, GAYLE_STATUS, -1, -1 +}; + + + /* + * These are at different offsets from the base + */ + +#define GAYLE_IRQ_4000 0xdd3020 /* MSB = 1, Harddisk is source of */ +#define GAYLE_IRQ_1200 0xda9000 /* interrupt */ + + + /* + * Offset of the secondary port for IDE doublers + * Note that GAYLE_CONTROL is NOT available then! + */ + +#define GAYLE_NEXT_PORT 0x1000 + +#ifndef CONFIG_BLK_DEV_IDEDOUBLER +#define GAYLE_NUM_HWIFS 1 +#define GAYLE_NUM_PROBE_HWIFS GAYLE_NUM_HWIFS +#define GAYLE_HAS_CONTROL_REG 1 +#define GAYLE_IDEREG_SIZE 0x2000 +#else /* CONFIG_BLK_DEV_IDEDOUBLER */ +#define GAYLE_NUM_HWIFS 2 +#define GAYLE_NUM_PROBE_HWIFS (ide_doubler ? GAYLE_NUM_HWIFS : \ + GAYLE_NUM_HWIFS-1) +#define GAYLE_HAS_CONTROL_REG (!ide_doubler) +#define GAYLE_IDEREG_SIZE (ide_doubler ? 0x1000 : 0x2000) +int ide_doubler = 0; /* support IDE doublers? */ +#endif /* CONFIG_BLK_DEV_IDEDOUBLER */ + + + /* + * Check and acknowledge the interrupt status + */ + +static int gayle_ack_intr_a4000(ide_hwif_t *hwif) +{ + unsigned char ch; + + ch = z_readb(hwif->io_ports[IDE_IRQ_OFFSET]); + if (!(ch & GAYLE_IRQ_IDE)) + return 0; + return 1; +} + +static int gayle_ack_intr_a1200(ide_hwif_t *hwif) +{ + unsigned char ch; + + ch = z_readb(hwif->io_ports[IDE_IRQ_OFFSET]); + if (!(ch & GAYLE_IRQ_IDE)) + return 0; + (void)z_readb(hwif->io_ports[IDE_STATUS_OFFSET]); + z_writeb(0x7c, hwif->io_ports[IDE_IRQ_OFFSET]); + return 1; +} + + /* + * Probe for a Gayle IDE interface (and optionally for an IDE doubler) + */ + +void __init gayle_init(void) +{ + int a4000, i; + + if (!MACH_IS_AMIGA) + return; + + if ((a4000 = AMIGAHW_PRESENT(A4000_IDE)) || AMIGAHW_PRESENT(A1200_IDE)) + goto found; + +#ifdef CONFIG_ZORRO + if (zorro_find_device(ZORRO_PROD_MTEC_VIPER_MK_V_E_MATRIX_530_SCSI_IDE, + NULL)) + goto found; +#endif + return; + +found: + for (i = 0; i < GAYLE_NUM_PROBE_HWIFS; i++) { + unsigned long base, ctrlport, irqport; + ide_ack_intr_t *ack_intr; + hw_regs_t hw; + ide_hwif_t *hwif; + int index; + unsigned long phys_base, res_start, res_n; + + if (a4000) { + phys_base = GAYLE_BASE_4000; + irqport = (unsigned long)ZTWO_VADDR(GAYLE_IRQ_4000); + ack_intr = gayle_ack_intr_a4000; + } else { + phys_base = GAYLE_BASE_1200; + irqport = (unsigned long)ZTWO_VADDR(GAYLE_IRQ_1200); + ack_intr = gayle_ack_intr_a1200; + } +/* + * FIXME: we now have selectable modes between mmio v/s iomio + */ + + phys_base += i*GAYLE_NEXT_PORT; + + res_start = ((unsigned long)phys_base) & ~(GAYLE_NEXT_PORT-1); + res_n = GAYLE_IDEREG_SIZE; + + if (!request_mem_region(res_start, res_n, "IDE")) + continue; + + base = (unsigned long)ZTWO_VADDR(phys_base); + ctrlport = GAYLE_HAS_CONTROL_REG ? (base + GAYLE_CONTROL) : 0; + + ide_setup_ports(&hw, base, gayle_offsets, + ctrlport, irqport, ack_intr, +// &gayle_iops, + IRQ_AMIGA_PORTS); + + index = ide_register_hw(&hw, &hwif); + if (index != -1) { + hwif->mmio = 2; + switch (i) { + case 0: + printk("ide%d: Gayle IDE interface (A%d style)\n", index, + a4000 ? 4000 : 1200); + break; +#ifdef CONFIG_BLK_DEV_IDEDOUBLER + case 1: + printk("ide%d: IDE doubler\n", index); + break; +#endif /* CONFIG_BLK_DEV_IDEDOUBLER */ + } + } else + release_mem_region(res_start, res_n); + } +} diff --git a/drivers/ide/legacy/hd.c b/drivers/ide/legacy/hd.c new file mode 100644 index 000000000000..c4090550ec13 --- /dev/null +++ b/drivers/ide/legacy/hd.c @@ -0,0 +1,864 @@ +/* + * Copyright (C) 1991, 1992 Linus Torvalds + * + * This is the low-level hd interrupt support. It traverses the + * request-list, using interrupts to jump between functions. As + * all the functions are called within interrupts, we may not + * sleep. Special care is recommended. + * + * modified by Drew Eckhardt to check nr of hd's from the CMOS. + * + * Thanks to Branko Lankester, lankeste@fwi.uva.nl, who found a bug + * in the early extended-partition checks and added DM partitions + * + * IRQ-unmask, drive-id, multiple-mode, support for ">16 heads", + * and general streamlining by Mark Lord. + * + * Removed 99% of above. Use Mark's ide driver for those options. + * This is now a lightweight ST-506 driver. (Paul Gortmaker) + * + * Modified 1995 Russell King for ARM processor. + * + * Bugfix: max_sectors must be <= 255 or the wheels tend to come + * off in a hurry once you queue things up - Paul G. 02/2001 + */ + +/* Uncomment the following if you want verbose error reports. */ +/* #define VERBOSE_ERRORS */ + +#include <linux/blkdev.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/genhd.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/mc146818rtc.h> /* CMOS defines */ +#include <linux/init.h> +#include <linux/blkpg.h> +#include <linux/hdreg.h> + +#define REALLY_SLOW_IO +#include <asm/system.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#ifdef __arm__ +#undef HD_IRQ +#endif +#include <asm/irq.h> +#ifdef __arm__ +#define HD_IRQ IRQ_HARDDISK +#endif + +/* Hd controller regster ports */ + +#define HD_DATA 0x1f0 /* _CTL when writing */ +#define HD_ERROR 0x1f1 /* see err-bits */ +#define HD_NSECTOR 0x1f2 /* nr of sectors to read/write */ +#define HD_SECTOR 0x1f3 /* starting sector */ +#define HD_LCYL 0x1f4 /* starting cylinder */ +#define HD_HCYL 0x1f5 /* high byte of starting cyl */ +#define HD_CURRENT 0x1f6 /* 101dhhhh , d=drive, hhhh=head */ +#define HD_STATUS 0x1f7 /* see status-bits */ +#define HD_FEATURE HD_ERROR /* same io address, read=error, write=feature */ +#define HD_PRECOMP HD_FEATURE /* obsolete use of this port - predates IDE */ +#define HD_COMMAND HD_STATUS /* same io address, read=status, write=cmd */ + +#define HD_CMD 0x3f6 /* used for resets */ +#define HD_ALTSTATUS 0x3f6 /* same as HD_STATUS but doesn't clear irq */ + +/* Bits of HD_STATUS */ +#define ERR_STAT 0x01 +#define INDEX_STAT 0x02 +#define ECC_STAT 0x04 /* Corrected error */ +#define DRQ_STAT 0x08 +#define SEEK_STAT 0x10 +#define SERVICE_STAT SEEK_STAT +#define WRERR_STAT 0x20 +#define READY_STAT 0x40 +#define BUSY_STAT 0x80 + +/* Bits for HD_ERROR */ +#define MARK_ERR 0x01 /* Bad address mark */ +#define TRK0_ERR 0x02 /* couldn't find track 0 */ +#define ABRT_ERR 0x04 /* Command aborted */ +#define MCR_ERR 0x08 /* media change request */ +#define ID_ERR 0x10 /* ID field not found */ +#define MC_ERR 0x20 /* media changed */ +#define ECC_ERR 0x40 /* Uncorrectable ECC error */ +#define BBD_ERR 0x80 /* pre-EIDE meaning: block marked bad */ +#define ICRC_ERR 0x80 /* new meaning: CRC error during transfer */ + +static DEFINE_SPINLOCK(hd_lock); +static struct request_queue *hd_queue; + +#define MAJOR_NR HD_MAJOR +#define QUEUE (hd_queue) +#define CURRENT elv_next_request(hd_queue) + +#define TIMEOUT_VALUE (6*HZ) +#define HD_DELAY 0 + +#define MAX_ERRORS 16 /* Max read/write errors/sector */ +#define RESET_FREQ 8 /* Reset controller every 8th retry */ +#define RECAL_FREQ 4 /* Recalibrate every 4th retry */ +#define MAX_HD 2 + +#define STAT_OK (READY_STAT|SEEK_STAT) +#define OK_STATUS(s) (((s)&(STAT_OK|(BUSY_STAT|WRERR_STAT|ERR_STAT)))==STAT_OK) + +static void recal_intr(void); +static void bad_rw_intr(void); + +static int reset; +static int hd_error; + +/* + * This struct defines the HD's and their types. + */ +struct hd_i_struct { + unsigned int head,sect,cyl,wpcom,lzone,ctl; + int unit; + int recalibrate; + int special_op; +}; + +#ifdef HD_TYPE +static struct hd_i_struct hd_info[] = { HD_TYPE }; +static int NR_HD = ((sizeof (hd_info))/(sizeof (struct hd_i_struct))); +#else +static struct hd_i_struct hd_info[MAX_HD]; +static int NR_HD; +#endif + +static struct gendisk *hd_gendisk[MAX_HD]; + +static struct timer_list device_timer; + +#define TIMEOUT_VALUE (6*HZ) + +#define SET_TIMER \ + do { \ + mod_timer(&device_timer, jiffies + TIMEOUT_VALUE); \ + } while (0) + +static void (*do_hd)(void) = NULL; +#define SET_HANDLER(x) \ +if ((do_hd = (x)) != NULL) \ + SET_TIMER; \ +else \ + del_timer(&device_timer); + + +#if (HD_DELAY > 0) +unsigned long last_req; + +unsigned long read_timer(void) +{ + extern spinlock_t i8253_lock; + unsigned long t, flags; + int i; + + spin_lock_irqsave(&i8253_lock, flags); + t = jiffies * 11932; + outb_p(0, 0x43); + i = inb_p(0x40); + i |= inb(0x40) << 8; + spin_unlock_irqrestore(&i8253_lock, flags); + return(t - i); +} +#endif + +static void __init hd_setup(char *str, int *ints) +{ + int hdind = 0; + + if (ints[0] != 3) + return; + if (hd_info[0].head != 0) + hdind=1; + hd_info[hdind].head = ints[2]; + hd_info[hdind].sect = ints[3]; + hd_info[hdind].cyl = ints[1]; + hd_info[hdind].wpcom = 0; + hd_info[hdind].lzone = ints[1]; + hd_info[hdind].ctl = (ints[2] > 8 ? 8 : 0); + NR_HD = hdind+1; +} + +static void dump_status (const char *msg, unsigned int stat) +{ + char *name = "hd?"; + if (CURRENT) + name = CURRENT->rq_disk->disk_name; + +#ifdef VERBOSE_ERRORS + printk("%s: %s: status=0x%02x { ", name, msg, stat & 0xff); + if (stat & BUSY_STAT) printk("Busy "); + if (stat & READY_STAT) printk("DriveReady "); + if (stat & WRERR_STAT) printk("WriteFault "); + if (stat & SEEK_STAT) printk("SeekComplete "); + if (stat & DRQ_STAT) printk("DataRequest "); + if (stat & ECC_STAT) printk("CorrectedError "); + if (stat & INDEX_STAT) printk("Index "); + if (stat & ERR_STAT) printk("Error "); + printk("}\n"); + if ((stat & ERR_STAT) == 0) { + hd_error = 0; + } else { + hd_error = inb(HD_ERROR); + printk("%s: %s: error=0x%02x { ", name, msg, hd_error & 0xff); + if (hd_error & BBD_ERR) printk("BadSector "); + if (hd_error & ECC_ERR) printk("UncorrectableError "); + if (hd_error & ID_ERR) printk("SectorIdNotFound "); + if (hd_error & ABRT_ERR) printk("DriveStatusError "); + if (hd_error & TRK0_ERR) printk("TrackZeroNotFound "); + if (hd_error & MARK_ERR) printk("AddrMarkNotFound "); + printk("}"); + if (hd_error & (BBD_ERR|ECC_ERR|ID_ERR|MARK_ERR)) { + printk(", CHS=%d/%d/%d", (inb(HD_HCYL)<<8) + inb(HD_LCYL), + inb(HD_CURRENT) & 0xf, inb(HD_SECTOR)); + if (CURRENT) + printk(", sector=%ld", CURRENT->sector); + } + printk("\n"); + } +#else + printk("%s: %s: status=0x%02x.\n", name, msg, stat & 0xff); + if ((stat & ERR_STAT) == 0) { + hd_error = 0; + } else { + hd_error = inb(HD_ERROR); + printk("%s: %s: error=0x%02x.\n", name, msg, hd_error & 0xff); + } +#endif +} + +static void check_status(void) +{ + int i = inb_p(HD_STATUS); + + if (!OK_STATUS(i)) { + dump_status("check_status", i); + bad_rw_intr(); + } +} + +static int controller_busy(void) +{ + int retries = 100000; + unsigned char status; + + do { + status = inb_p(HD_STATUS); + } while ((status & BUSY_STAT) && --retries); + return status; +} + +static int status_ok(void) +{ + unsigned char status = inb_p(HD_STATUS); + + if (status & BUSY_STAT) + return 1; /* Ancient, but does it make sense??? */ + if (status & WRERR_STAT) + return 0; + if (!(status & READY_STAT)) + return 0; + if (!(status & SEEK_STAT)) + return 0; + return 1; +} + +static int controller_ready(unsigned int drive, unsigned int head) +{ + int retry = 100; + + do { + if (controller_busy() & BUSY_STAT) + return 0; + outb_p(0xA0 | (drive<<4) | head, HD_CURRENT); + if (status_ok()) + return 1; + } while (--retry); + return 0; +} + + +static void hd_out(struct hd_i_struct *disk, + unsigned int nsect, + unsigned int sect, + unsigned int head, + unsigned int cyl, + unsigned int cmd, + void (*intr_addr)(void)) +{ + unsigned short port; + +#if (HD_DELAY > 0) + while (read_timer() - last_req < HD_DELAY) + /* nothing */; +#endif + if (reset) + return; + if (!controller_ready(disk->unit, head)) { + reset = 1; + return; + } + SET_HANDLER(intr_addr); + outb_p(disk->ctl,HD_CMD); + port=HD_DATA; + outb_p(disk->wpcom>>2,++port); + outb_p(nsect,++port); + outb_p(sect,++port); + outb_p(cyl,++port); + outb_p(cyl>>8,++port); + outb_p(0xA0|(disk->unit<<4)|head,++port); + outb_p(cmd,++port); +} + +static void hd_request (void); + +static int drive_busy(void) +{ + unsigned int i; + unsigned char c; + + for (i = 0; i < 500000 ; i++) { + c = inb_p(HD_STATUS); + if ((c & (BUSY_STAT | READY_STAT | SEEK_STAT)) == STAT_OK) + return 0; + } + dump_status("reset timed out", c); + return 1; +} + +static void reset_controller(void) +{ + int i; + + outb_p(4,HD_CMD); + for(i = 0; i < 1000; i++) barrier(); + outb_p(hd_info[0].ctl & 0x0f,HD_CMD); + for(i = 0; i < 1000; i++) barrier(); + if (drive_busy()) + printk("hd: controller still busy\n"); + else if ((hd_error = inb(HD_ERROR)) != 1) + printk("hd: controller reset failed: %02x\n",hd_error); +} + +static void reset_hd(void) +{ + static int i; + +repeat: + if (reset) { + reset = 0; + i = -1; + reset_controller(); + } else { + check_status(); + if (reset) + goto repeat; + } + if (++i < NR_HD) { + struct hd_i_struct *disk = &hd_info[i]; + disk->special_op = disk->recalibrate = 1; + hd_out(disk,disk->sect,disk->sect,disk->head-1, + disk->cyl,WIN_SPECIFY,&reset_hd); + if (reset) + goto repeat; + } else + hd_request(); +} + +/* + * Ok, don't know what to do with the unexpected interrupts: on some machines + * doing a reset and a retry seems to result in an eternal loop. Right now I + * ignore it, and just set the timeout. + * + * On laptops (and "green" PCs), an unexpected interrupt occurs whenever the + * drive enters "idle", "standby", or "sleep" mode, so if the status looks + * "good", we just ignore the interrupt completely. + */ +static void unexpected_hd_interrupt(void) +{ + unsigned int stat = inb_p(HD_STATUS); + + if (stat & (BUSY_STAT|DRQ_STAT|ECC_STAT|ERR_STAT)) { + dump_status ("unexpected interrupt", stat); + SET_TIMER; + } +} + +/* + * bad_rw_intr() now tries to be a bit smarter and does things + * according to the error returned by the controller. + * -Mika Liljeberg (liljeber@cs.Helsinki.FI) + */ +static void bad_rw_intr(void) +{ + struct request *req = CURRENT; + if (req != NULL) { + struct hd_i_struct *disk = req->rq_disk->private_data; + if (++req->errors >= MAX_ERRORS || (hd_error & BBD_ERR)) { + end_request(req, 0); + disk->special_op = disk->recalibrate = 1; + } else if (req->errors % RESET_FREQ == 0) + reset = 1; + else if ((hd_error & TRK0_ERR) || req->errors % RECAL_FREQ == 0) + disk->special_op = disk->recalibrate = 1; + /* Otherwise just retry */ + } +} + +static inline int wait_DRQ(void) +{ + int retries = 100000, stat; + + while (--retries > 0) + if ((stat = inb_p(HD_STATUS)) & DRQ_STAT) + return 0; + dump_status("wait_DRQ", stat); + return -1; +} + +static void read_intr(void) +{ + struct request *req; + int i, retries = 100000; + + do { + i = (unsigned) inb_p(HD_STATUS); + if (i & BUSY_STAT) + continue; + if (!OK_STATUS(i)) + break; + if (i & DRQ_STAT) + goto ok_to_read; + } while (--retries > 0); + dump_status("read_intr", i); + bad_rw_intr(); + hd_request(); + return; +ok_to_read: + req = CURRENT; + insw(HD_DATA,req->buffer,256); + req->sector++; + req->buffer += 512; + req->errors = 0; + i = --req->nr_sectors; + --req->current_nr_sectors; +#ifdef DEBUG + printk("%s: read: sector %ld, remaining = %ld, buffer=%p\n", + req->rq_disk->disk_name, req->sector, req->nr_sectors, + req->buffer+512)); +#endif + if (req->current_nr_sectors <= 0) + end_request(req, 1); + if (i > 0) { + SET_HANDLER(&read_intr); + return; + } + (void) inb_p(HD_STATUS); +#if (HD_DELAY > 0) + last_req = read_timer(); +#endif + if (elv_next_request(QUEUE)) + hd_request(); + return; +} + +static void write_intr(void) +{ + struct request *req = CURRENT; + int i; + int retries = 100000; + + do { + i = (unsigned) inb_p(HD_STATUS); + if (i & BUSY_STAT) + continue; + if (!OK_STATUS(i)) + break; + if ((req->nr_sectors <= 1) || (i & DRQ_STAT)) + goto ok_to_write; + } while (--retries > 0); + dump_status("write_intr", i); + bad_rw_intr(); + hd_request(); + return; +ok_to_write: + req->sector++; + i = --req->nr_sectors; + --req->current_nr_sectors; + req->buffer += 512; + if (!i || (req->bio && req->current_nr_sectors <= 0)) + end_request(req, 1); + if (i > 0) { + SET_HANDLER(&write_intr); + outsw(HD_DATA,req->buffer,256); + local_irq_enable(); + } else { +#if (HD_DELAY > 0) + last_req = read_timer(); +#endif + hd_request(); + } + return; +} + +static void recal_intr(void) +{ + check_status(); +#if (HD_DELAY > 0) + last_req = read_timer(); +#endif + hd_request(); +} + +/* + * This is another of the error-routines I don't know what to do with. The + * best idea seems to just set reset, and start all over again. + */ +static void hd_times_out(unsigned long dummy) +{ + char *name; + + do_hd = NULL; + + if (!CURRENT) + return; + + disable_irq(HD_IRQ); + local_irq_enable(); + reset = 1; + name = CURRENT->rq_disk->disk_name; + printk("%s: timeout\n", name); + if (++CURRENT->errors >= MAX_ERRORS) { +#ifdef DEBUG + printk("%s: too many errors\n", name); +#endif + end_request(CURRENT, 0); + } + local_irq_disable(); + hd_request(); + enable_irq(HD_IRQ); +} + +static int do_special_op(struct hd_i_struct *disk, struct request *req) +{ + if (disk->recalibrate) { + disk->recalibrate = 0; + hd_out(disk,disk->sect,0,0,0,WIN_RESTORE,&recal_intr); + return reset; + } + if (disk->head > 16) { + printk ("%s: cannot handle device with more than 16 heads - giving up\n", req->rq_disk->disk_name); + end_request(req, 0); + } + disk->special_op = 0; + return 1; +} + +/* + * The driver enables interrupts as much as possible. In order to do this, + * (a) the device-interrupt is disabled before entering hd_request(), + * and (b) the timeout-interrupt is disabled before the sti(). + * + * Interrupts are still masked (by default) whenever we are exchanging + * data/cmds with a drive, because some drives seem to have very poor + * tolerance for latency during I/O. The IDE driver has support to unmask + * interrupts for non-broken hardware, so use that driver if required. + */ +static void hd_request(void) +{ + unsigned int block, nsect, sec, track, head, cyl; + struct hd_i_struct *disk; + struct request *req; + + if (do_hd) + return; +repeat: + del_timer(&device_timer); + local_irq_enable(); + + req = CURRENT; + if (!req) { + do_hd = NULL; + return; + } + + if (reset) { + local_irq_disable(); + reset_hd(); + return; + } + disk = req->rq_disk->private_data; + block = req->sector; + nsect = req->nr_sectors; + if (block >= get_capacity(req->rq_disk) || + ((block+nsect) > get_capacity(req->rq_disk))) { + printk("%s: bad access: block=%d, count=%d\n", + req->rq_disk->disk_name, block, nsect); + end_request(req, 0); + goto repeat; + } + + if (disk->special_op) { + if (do_special_op(disk, req)) + goto repeat; + return; + } + sec = block % disk->sect + 1; + track = block / disk->sect; + head = track % disk->head; + cyl = track / disk->head; +#ifdef DEBUG + printk("%s: %sing: CHS=%d/%d/%d, sectors=%d, buffer=%p\n", + req->rq_disk->disk_name, (req->cmd == READ)?"read":"writ", + cyl, head, sec, nsect, req->buffer); +#endif + if (req->flags & REQ_CMD) { + switch (rq_data_dir(req)) { + case READ: + hd_out(disk,nsect,sec,head,cyl,WIN_READ,&read_intr); + if (reset) + goto repeat; + break; + case WRITE: + hd_out(disk,nsect,sec,head,cyl,WIN_WRITE,&write_intr); + if (reset) + goto repeat; + if (wait_DRQ()) { + bad_rw_intr(); + goto repeat; + } + outsw(HD_DATA,req->buffer,256); + break; + default: + printk("unknown hd-command\n"); + end_request(req, 0); + break; + } + } +} + +static void do_hd_request (request_queue_t * q) +{ + disable_irq(HD_IRQ); + hd_request(); + enable_irq(HD_IRQ); +} + +static int hd_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, unsigned long arg) +{ + struct hd_i_struct *disk = inode->i_bdev->bd_disk->private_data; + struct hd_geometry __user *loc = (struct hd_geometry __user *) arg; + struct hd_geometry g; + + if (cmd != HDIO_GETGEO) + return -EINVAL; + if (!loc) + return -EINVAL; + g.heads = disk->head; + g.sectors = disk->sect; + g.cylinders = disk->cyl; + g.start = get_start_sect(inode->i_bdev); + return copy_to_user(loc, &g, sizeof g) ? -EFAULT : 0; +} + +/* + * Releasing a block device means we sync() it, so that it can safely + * be forgotten about... + */ + +static irqreturn_t hd_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + void (*handler)(void) = do_hd; + + do_hd = NULL; + del_timer(&device_timer); + if (!handler) + handler = unexpected_hd_interrupt; + handler(); + local_irq_enable(); + return IRQ_HANDLED; +} + +static struct block_device_operations hd_fops = { + .ioctl = hd_ioctl, +}; + +/* + * This is the hard disk IRQ description. The SA_INTERRUPT in sa_flags + * means we run the IRQ-handler with interrupts disabled: this is bad for + * interrupt latency, but anything else has led to problems on some + * machines. + * + * We enable interrupts in some of the routines after making sure it's + * safe. + */ + +static int __init hd_init(void) +{ + int drive; + + if (register_blkdev(MAJOR_NR,"hd")) + return -1; + + hd_queue = blk_init_queue(do_hd_request, &hd_lock); + if (!hd_queue) { + unregister_blkdev(MAJOR_NR,"hd"); + return -ENOMEM; + } + + blk_queue_max_sectors(hd_queue, 255); + init_timer(&device_timer); + device_timer.function = hd_times_out; + blk_queue_hardsect_size(hd_queue, 512); + +#ifdef __i386__ + if (!NR_HD) { + extern struct drive_info drive_info; + unsigned char *BIOS = (unsigned char *) &drive_info; + unsigned long flags; + int cmos_disks; + + for (drive=0 ; drive<2 ; drive++) { + hd_info[drive].cyl = *(unsigned short *) BIOS; + hd_info[drive].head = *(2+BIOS); + hd_info[drive].wpcom = *(unsigned short *) (5+BIOS); + hd_info[drive].ctl = *(8+BIOS); + hd_info[drive].lzone = *(unsigned short *) (12+BIOS); + hd_info[drive].sect = *(14+BIOS); +#ifdef does_not_work_for_everybody_with_scsi_but_helps_ibm_vp + if (hd_info[drive].cyl && NR_HD == drive) + NR_HD++; +#endif + BIOS += 16; + } + + /* + We query CMOS about hard disks : it could be that + we have a SCSI/ESDI/etc controller that is BIOS + compatible with ST-506, and thus showing up in our + BIOS table, but not register compatible, and therefore + not present in CMOS. + + Furthermore, we will assume that our ST-506 drives + <if any> are the primary drives in the system, and + the ones reflected as drive 1 or 2. + + The first drive is stored in the high nibble of CMOS + byte 0x12, the second in the low nibble. This will be + either a 4 bit drive type or 0xf indicating use byte 0x19 + for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS. + + Needless to say, a non-zero value means we have + an AT controller hard disk for that drive. + + Currently the rtc_lock is a bit academic since this + driver is non-modular, but someday... ? Paul G. + */ + + spin_lock_irqsave(&rtc_lock, flags); + cmos_disks = CMOS_READ(0x12); + spin_unlock_irqrestore(&rtc_lock, flags); + + if (cmos_disks & 0xf0) { + if (cmos_disks & 0x0f) + NR_HD = 2; + else + NR_HD = 1; + } + } +#endif /* __i386__ */ +#ifdef __arm__ + if (!NR_HD) { + /* We don't know anything about the drive. This means + * that you *MUST* specify the drive parameters to the + * kernel yourself. + */ + printk("hd: no drives specified - use hd=cyl,head,sectors" + " on kernel command line\n"); + } +#endif + if (!NR_HD) + goto out; + + for (drive=0 ; drive < NR_HD ; drive++) { + struct gendisk *disk = alloc_disk(64); + struct hd_i_struct *p = &hd_info[drive]; + if (!disk) + goto Enomem; + disk->major = MAJOR_NR; + disk->first_minor = drive << 6; + disk->fops = &hd_fops; + sprintf(disk->disk_name, "hd%c", 'a'+drive); + disk->private_data = p; + set_capacity(disk, p->head * p->sect * p->cyl); + disk->queue = hd_queue; + p->unit = drive; + hd_gendisk[drive] = disk; + printk ("%s: %luMB, CHS=%d/%d/%d\n", + disk->disk_name, (unsigned long)get_capacity(disk)/2048, + p->cyl, p->head, p->sect); + } + + if (request_irq(HD_IRQ, hd_interrupt, SA_INTERRUPT, "hd", NULL)) { + printk("hd: unable to get IRQ%d for the hard disk driver\n", + HD_IRQ); + goto out1; + } + if (!request_region(HD_DATA, 8, "hd")) { + printk(KERN_WARNING "hd: port 0x%x busy\n", HD_DATA); + goto out2; + } + if (!request_region(HD_CMD, 1, "hd(cmd)")) { + printk(KERN_WARNING "hd: port 0x%x busy\n", HD_CMD); + goto out3; + } + + /* Let them fly */ + for(drive=0; drive < NR_HD; drive++) + add_disk(hd_gendisk[drive]); + + return 0; + +out3: + release_region(HD_DATA, 8); +out2: + free_irq(HD_IRQ, NULL); +out1: + for (drive = 0; drive < NR_HD; drive++) + put_disk(hd_gendisk[drive]); + NR_HD = 0; +out: + del_timer(&device_timer); + unregister_blkdev(MAJOR_NR,"hd"); + blk_cleanup_queue(hd_queue); + return -1; +Enomem: + while (drive--) + put_disk(hd_gendisk[drive]); + goto out; +} + +static int parse_hd_setup (char *line) { + int ints[6]; + + (void) get_options(line, ARRAY_SIZE(ints), ints); + hd_setup(NULL, ints); + + return 1; +} +__setup("hd=", parse_hd_setup); + +module_init(hd_init); diff --git a/drivers/ide/legacy/ht6560b.c b/drivers/ide/legacy/ht6560b.c new file mode 100644 index 000000000000..a77fb249d5cf --- /dev/null +++ b/drivers/ide/legacy/ht6560b.c @@ -0,0 +1,370 @@ +/* + * linux/drivers/ide/legacy/ht6560b.c Version 0.07 Feb 1, 2000 + * + * Copyright (C) 1995-2000 Linus Torvalds & author (see below) + */ + +/* + * + * Version 0.01 Initial version hacked out of ide.c + * + * Version 0.02 Added support for PIO modes, auto-tune + * + * Version 0.03 Some cleanups + * + * Version 0.05 PIO mode cycle timings auto-tune using bus-speed + * + * Version 0.06 Prefetch mode now defaults no OFF. To set + * prefetch mode OFF/ON use "hdparm -p8/-p9". + * Unmask irq is disabled when prefetch mode + * is enabled. + * + * Version 0.07 Trying to fix CD-ROM detection problem. + * "Prefetch" mode bit OFF for ide disks and + * ON for anything else. + * + * + * HT-6560B EIDE-controller support + * To activate controller support use kernel parameter "ide0=ht6560b". + * Use hdparm utility to enable PIO mode support. + * + * Author: Mikko Ala-Fossi <maf@iki.fi> + * Jan Evert van Grootheest <janevert@iae.nl> + * + * Try: http://www.maf.iki.fi/~maf/ht6560b/ + */ + +#define HT6560B_VERSION "v0.07" + +#undef REALLY_SLOW_IO /* most systems can safely undef this */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> +#include <linux/init.h> + +#include <asm/io.h> + +/* #define DEBUG */ /* remove comments for DEBUG messages */ + +/* + * The special i/o-port that HT-6560B uses to configuration: + * bit0 (0x01): "1" selects secondary interface + * bit2 (0x04): "1" enables FIFO function + * bit5 (0x20): "1" enables prefetched data read function (???) + * + * The special i/o-port that HT-6560A uses to configuration: + * bit0 (0x01): "1" selects secondary interface + * bit1 (0x02): "1" enables prefetched data read function + * bit2 (0x04): "0" enables multi-master system (?) + * bit3 (0x08): "1" 3 cycle time, "0" 2 cycle time (?) + */ +#define HT_CONFIG_PORT 0x3e6 +#define HT_CONFIG(drivea) (u8)(((drivea)->drive_data & 0xff00) >> 8) +/* + * FIFO + PREFETCH (both a/b-model) + */ +#define HT_CONFIG_DEFAULT 0x1c /* no prefetch */ +/* #define HT_CONFIG_DEFAULT 0x3c */ /* with prefetch */ +#define HT_SECONDARY_IF 0x01 +#define HT_PREFETCH_MODE 0x20 + +/* + * ht6560b Timing values: + * + * I reviewed some assembler source listings of htide drivers and found + * out how they setup those cycle time interfacing values, as they at Holtek + * call them. IDESETUP.COM that is supplied with the drivers figures out + * optimal values and fetches those values to drivers. I found out that + * they use IDE_SELECT_REG to fetch timings to the ide board right after + * interface switching. After that it was quite easy to add code to + * ht6560b.c. + * + * IDESETUP.COM gave me values 0x24, 0x45, 0xaa, 0xff that worked fine + * for hda and hdc. But hdb needed higher values to work, so I guess + * that sometimes it is necessary to give higher value than IDESETUP + * gives. [see cmd640.c for an extreme example of this. -ml] + * + * Perhaps I should explain something about these timing values: + * The higher nibble of value is the Recovery Time (rt) and the lower nibble + * of the value is the Active Time (at). Minimum value 2 is the fastest and + * the maximum value 15 is the slowest. Default values should be 15 for both. + * So 0x24 means 2 for rt and 4 for at. Each of the drives should have + * both values, and IDESETUP gives automatically rt=15 st=15 for CDROMs or + * similar. If value is too small there will be all sorts of failures. + * + * Timing byte consists of + * High nibble: Recovery Cycle Time (rt) + * The valid values range from 2 to 15. The default is 15. + * + * Low nibble: Active Cycle Time (at) + * The valid values range from 2 to 15. The default is 15. + * + * You can obtain optimized timing values by running Holtek IDESETUP.COM + * for DOS. DOS drivers get their timing values from command line, where + * the first value is the Recovery Time and the second value is the + * Active Time for each drive. Smaller value gives higher speed. + * In case of failures you should probably fall back to a higher value. + */ +#define HT_TIMING(drivea) (u8)((drivea)->drive_data & 0x00ff) +#define HT_TIMING_DEFAULT 0xff + +/* + * This routine handles interface switching for the peculiar hardware design + * on the F.G.I./Holtek HT-6560B VLB IDE interface. + * The HT-6560B can only enable one IDE port at a time, and requires a + * silly sequence (below) whenever we switch between primary and secondary. + */ + +/* + * This routine is invoked from ide.c to prepare for access to a given drive. + */ +static void ht6560b_selectproc (ide_drive_t *drive) +{ + unsigned long flags; + static u8 current_select = 0; + static u8 current_timing = 0; + u8 select, timing; + + local_irq_save(flags); + + select = HT_CONFIG(drive); + timing = HT_TIMING(drive); + + if (select != current_select || timing != current_timing) { + current_select = select; + current_timing = timing; + if (drive->media != ide_disk || !drive->present) + select |= HT_PREFETCH_MODE; + (void) HWIF(drive)->INB(HT_CONFIG_PORT); + (void) HWIF(drive)->INB(HT_CONFIG_PORT); + (void) HWIF(drive)->INB(HT_CONFIG_PORT); + (void) HWIF(drive)->INB(HT_CONFIG_PORT); + HWIF(drive)->OUTB(select, HT_CONFIG_PORT); + /* + * Set timing for this drive: + */ + HWIF(drive)->OUTB(timing, IDE_SELECT_REG); + (void) HWIF(drive)->INB(IDE_STATUS_REG); +#ifdef DEBUG + printk("ht6560b: %s: select=%#x timing=%#x\n", + drive->name, select, timing); +#endif + } + local_irq_restore(flags); +} + +/* + * Autodetection and initialization of ht6560b + */ +static int __init try_to_init_ht6560b(void) +{ + u8 orig_value; + int i; + + /* Autodetect ht6560b */ + if ((orig_value = inb(HT_CONFIG_PORT)) == 0xff) + return 0; + + for (i=3;i>0;i--) { + outb(0x00, HT_CONFIG_PORT); + if (!( (~inb(HT_CONFIG_PORT)) & 0x3f )) { + outb(orig_value, HT_CONFIG_PORT); + return 0; + } + } + outb(0x00, HT_CONFIG_PORT); + if ((~inb(HT_CONFIG_PORT))& 0x3f) { + outb(orig_value, HT_CONFIG_PORT); + return 0; + } + /* + * Ht6560b autodetected + */ + outb(HT_CONFIG_DEFAULT, HT_CONFIG_PORT); + outb(HT_TIMING_DEFAULT, 0x1f6); /* IDE_SELECT_REG */ + (void) inb(0x1f7); /* IDE_STATUS_REG */ + + printk("\nht6560b " HT6560B_VERSION + ": chipset detected and initialized" +#ifdef DEBUG + " with debug enabled" +#endif + ); + return 1; +} + +static u8 ht_pio2timings(ide_drive_t *drive, u8 pio) +{ + int active_time, recovery_time; + int active_cycles, recovery_cycles; + ide_pio_data_t d; + int bus_speed = system_bus_clock(); + + if (pio) { + pio = ide_get_best_pio_mode(drive, pio, 5, &d); + + /* + * Just like opti621.c we try to calculate the + * actual cycle time for recovery and activity + * according system bus speed. + */ + active_time = ide_pio_timings[pio].active_time; + recovery_time = d.cycle_time + - active_time + - ide_pio_timings[pio].setup_time; + /* + * Cycle times should be Vesa bus cycles + */ + active_cycles = (active_time * bus_speed + 999) / 1000; + recovery_cycles = (recovery_time * bus_speed + 999) / 1000; + /* + * Upper and lower limits + */ + if (active_cycles < 2) active_cycles = 2; + if (recovery_cycles < 2) recovery_cycles = 2; + if (active_cycles > 15) active_cycles = 15; + if (recovery_cycles > 15) recovery_cycles = 0; /* 0==16 */ + +#ifdef DEBUG + printk("ht6560b: drive %s setting pio=%d recovery=%d (%dns) active=%d (%dns)\n", drive->name, pio, recovery_cycles, recovery_time, active_cycles, active_time); +#endif + + return (u8)((recovery_cycles << 4) | active_cycles); + } else { + +#ifdef DEBUG + printk("ht6560b: drive %s setting pio=0\n", drive->name); +#endif + + return HT_TIMING_DEFAULT; /* default setting */ + } +} + +/* + * Enable/Disable so called prefetch mode + */ +static void ht_set_prefetch(ide_drive_t *drive, u8 state) +{ + unsigned long flags; + int t = HT_PREFETCH_MODE << 8; + + spin_lock_irqsave(&ide_lock, flags); + + /* + * Prefetch mode and unmask irq seems to conflict + */ + if (state) { + drive->drive_data |= t; /* enable prefetch mode */ + drive->no_unmask = 1; + drive->unmask = 0; + } else { + drive->drive_data &= ~t; /* disable prefetch mode */ + drive->no_unmask = 0; + } + + spin_unlock_irqrestore(&ide_lock, flags); + +#ifdef DEBUG + printk("ht6560b: drive %s prefetch mode %sabled\n", drive->name, (state ? "en" : "dis")); +#endif +} + +static void tune_ht6560b (ide_drive_t *drive, u8 pio) +{ + unsigned long flags; + u8 timing; + + switch (pio) { + case 8: /* set prefetch off */ + case 9: /* set prefetch on */ + ht_set_prefetch(drive, pio & 1); + return; + } + + timing = ht_pio2timings(drive, pio); + + spin_lock_irqsave(&ide_lock, flags); + + drive->drive_data &= 0xff00; + drive->drive_data |= timing; + + spin_unlock_irqrestore(&ide_lock, flags); + +#ifdef DEBUG + printk("ht6560b: drive %s tuned to pio mode %#x timing=%#x\n", drive->name, pio, timing); +#endif +} + +/* Can be called directly from ide.c. */ +int __init ht6560b_init(void) +{ + ide_hwif_t *hwif, *mate; + int t; + + hwif = &ide_hwifs[0]; + mate = &ide_hwifs[1]; + + if (!request_region(HT_CONFIG_PORT, 1, hwif->name)) { + printk(KERN_NOTICE "%s: HT_CONFIG_PORT not found\n", + __FUNCTION__); + return -ENODEV; + } + + if (!try_to_init_ht6560b()) { + printk(KERN_NOTICE "%s: HBA not found\n", __FUNCTION__); + goto release_region; + } + + hwif->chipset = ide_ht6560b; + hwif->selectproc = &ht6560b_selectproc; + hwif->tuneproc = &tune_ht6560b; + hwif->serialized = 1; /* is this needed? */ + hwif->mate = mate; + + mate->chipset = ide_ht6560b; + mate->selectproc = &ht6560b_selectproc; + mate->tuneproc = &tune_ht6560b; + mate->serialized = 1; /* is this needed? */ + mate->mate = hwif; + mate->channel = 1; + + /* + * Setting default configurations for drives + */ + t = (HT_CONFIG_DEFAULT << 8); + t |= HT_TIMING_DEFAULT; + hwif->drives[0].drive_data = t; + hwif->drives[1].drive_data = t; + + t |= (HT_SECONDARY_IF << 8); + mate->drives[0].drive_data = t; + mate->drives[1].drive_data = t; + + probe_hwif_init(hwif); + probe_hwif_init(mate); + + create_proc_ide_interfaces(); + + return 0; + +release_region: + release_region(HT_CONFIG_PORT, 1); + return -ENODEV; +} + +#ifdef MODULE +module_init(ht6560b_init); +#endif + +MODULE_AUTHOR("See Local File"); +MODULE_DESCRIPTION("HT-6560B EIDE-controller support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ide/legacy/ide-cs.c b/drivers/ide/legacy/ide-cs.c new file mode 100644 index 000000000000..e20327e54b1a --- /dev/null +++ b/drivers/ide/legacy/ide-cs.c @@ -0,0 +1,481 @@ +/*====================================================================== + + A driver for PCMCIA IDE/ATA disk cards + + ide-cs.c 1.3 2002/10/26 05:45:31 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/ide.h> +#include <linux/hdreg.h> +#include <linux/major.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("PCMCIA ATA/IDE card driver"); +MODULE_LICENSE("Dual MPL/GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"ide-cs.c 1.3 2002/10/26 05:45:31 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +static const char ide_major[] = { + IDE0_MAJOR, IDE1_MAJOR, IDE2_MAJOR, IDE3_MAJOR, + IDE4_MAJOR, IDE5_MAJOR +}; + +typedef struct ide_info_t { + dev_link_t link; + int ndev; + dev_node_t node; + int hd; +} ide_info_t; + +static void ide_release(dev_link_t *); +static int ide_event(event_t event, int priority, + event_callback_args_t *args); + +static dev_info_t dev_info = "ide-cs"; + +static dev_link_t *ide_attach(void); +static void ide_detach(dev_link_t *); + +static dev_link_t *dev_list = NULL; + +/*====================================================================== + + ide_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *ide_attach(void) +{ + ide_info_t *info; + dev_link_t *link; + client_reg_t client_reg; + int ret; + + DEBUG(0, "ide_attach()\n"); + + /* Create new ide device */ + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) return NULL; + memset(info, 0, sizeof(*info)); + link = &info->link; link->priv = info; + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->io.IOAddrLines = 3; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &ide_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + ide_detach(link); + return NULL; + } + + return link; +} /* ide_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void ide_detach(dev_link_t *link) +{ + dev_link_t **linkp; + int ret; + + DEBUG(0, "ide_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) + ide_release(link); + + if (link->handle) { + ret = pcmcia_deregister_client(link->handle); + if (ret != CS_SUCCESS) + cs_error(link->handle, DeregisterClient, ret); + } + + /* Unlink, free device structure */ + *linkp = link->next; + kfree(link->priv); + +} /* ide_detach */ + +static int idecs_register(unsigned long io, unsigned long ctl, unsigned long irq) +{ + hw_regs_t hw; + memset(&hw, 0, sizeof(hw)); + ide_init_hwif_ports(&hw, io, ctl, NULL); + hw.irq = irq; + hw.chipset = ide_pci; + return ide_register_hw_with_fixup(&hw, NULL, ide_undecoded_slave); +} + +/*====================================================================== + + ide_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ide device available to the system. + +======================================================================*/ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void ide_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + ide_info_t *info = link->priv; + tuple_t tuple; + struct { + u_short buf[128]; + cisparse_t parse; + config_info_t conf; + cistpl_cftable_entry_t dflt; + } *stk = NULL; + cistpl_cftable_entry_t *cfg; + int i, pass, last_ret = 0, last_fn = 0, hd, is_kme = 0; + unsigned long io_base, ctl_base; + + DEBUG(0, "ide_config(0x%p)\n", link); + + stk = kmalloc(sizeof(*stk), GFP_KERNEL); + if (!stk) goto err_mem; + memset(stk, 0, sizeof(*stk)); + cfg = &stk->parse.cftable_entry; + + tuple.TupleData = (cisdata_t *)&stk->buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &stk->parse)); + link->conf.ConfigBase = stk->parse.config.base; + link->conf.Present = stk->parse.config.rmask[0]; + + tuple.DesiredTuple = CISTPL_MANFID; + if (!pcmcia_get_first_tuple(handle, &tuple) && + !pcmcia_get_tuple_data(handle, &tuple) && + !pcmcia_parse_tuple(handle, &tuple, &stk->parse)) + is_kme = ((stk->parse.manfid.manf == MANFID_KME) && + ((stk->parse.manfid.card == PRODID_KME_KXLC005_A) || + (stk->parse.manfid.card == PRODID_KME_KXLC005_B))); + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* Not sure if this is right... look up the current Vcc */ + CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(handle, &stk->conf)); + link->conf.Vcc = stk->conf.Vcc; + + pass = io_base = ctl_base = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + tuple.Attributes = 0; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + while (1) { + if (pcmcia_get_tuple_data(handle, &tuple) != 0) goto next_entry; + if (pcmcia_parse_tuple(handle, &tuple, &stk->parse) != 0) goto next_entry; + + /* Check for matching Vcc, unless we're desperate */ + if (!pass) { + if (cfg->vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (stk->conf.Vcc != cfg->vcc.param[CISTPL_POWER_VNOM] / 10000) + goto next_entry; + } else if (stk->dflt.vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (stk->conf.Vcc != stk->dflt.vcc.param[CISTPL_POWER_VNOM] / 10000) + goto next_entry; + } + } + + if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM)) + link->conf.Vpp1 = link->conf.Vpp2 = + cfg->vpp1.param[CISTPL_POWER_VNOM] / 10000; + else if (stk->dflt.vpp1.present & (1 << CISTPL_POWER_VNOM)) + link->conf.Vpp1 = link->conf.Vpp2 = + stk->dflt.vpp1.param[CISTPL_POWER_VNOM] / 10000; + + if ((cfg->io.nwin > 0) || (stk->dflt.io.nwin > 0)) { + cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &stk->dflt.io; + link->conf.ConfigIndex = cfg->index; + link->io.BasePort1 = io->win[0].base; + link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; + if (!(io->flags & CISTPL_IO_16BIT)) + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + if (io->nwin == 2) { + link->io.NumPorts1 = 8; + link->io.BasePort2 = io->win[1].base; + link->io.NumPorts2 = (is_kme) ? 2 : 1; + if (pcmcia_request_io(link->handle, &link->io) != 0) + goto next_entry; + io_base = link->io.BasePort1; + ctl_base = link->io.BasePort2; + } else if ((io->nwin == 1) && (io->win[0].len >= 16)) { + link->io.NumPorts1 = io->win[0].len; + link->io.NumPorts2 = 0; + if (pcmcia_request_io(link->handle, &link->io) != 0) + goto next_entry; + io_base = link->io.BasePort1; + ctl_base = link->io.BasePort1 + 0x0e; + } else goto next_entry; + /* If we've got this far, we're done */ + break; + } + + next_entry: + if (cfg->flags & CISTPL_CFTABLE_DEFAULT) + memcpy(&stk->dflt, cfg, sizeof(stk->dflt)); + if (pass) { + CS_CHECK(GetNextTuple, pcmcia_get_next_tuple(handle, &tuple)); + } else if (pcmcia_get_next_tuple(handle, &tuple) != 0) { + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + memset(&stk->dflt, 0, sizeof(stk->dflt)); + pass++; + } + } + + CS_CHECK(RequestIRQ, pcmcia_request_irq(handle, &link->irq)); + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(handle, &link->conf)); + + /* disable drive interrupts during IDE probe */ + outb(0x02, ctl_base); + + /* special setup for KXLC005 card */ + if (is_kme) + outb(0x81, ctl_base+1); + + /* retry registration in case device is still spinning up */ + for (hd = -1, i = 0; i < 10; i++) { + hd = idecs_register(io_base, ctl_base, link->irq.AssignedIRQ); + if (hd >= 0) break; + if (link->io.NumPorts1 == 0x20) { + outb(0x02, ctl_base + 0x10); + hd = idecs_register(io_base + 0x10, ctl_base + 0x10, + link->irq.AssignedIRQ); + if (hd >= 0) { + io_base += 0x10; + ctl_base += 0x10; + break; + } + } + __set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/10); + } + + if (hd < 0) { + printk(KERN_NOTICE "ide-cs: ide_register() at 0x%3lx & 0x%3lx" + ", irq %u failed\n", io_base, ctl_base, + link->irq.AssignedIRQ); + goto failed; + } + + info->ndev = 1; + sprintf(info->node.dev_name, "hd%c", 'a' + (hd * 2)); + info->node.major = ide_major[hd]; + info->node.minor = 0; + info->hd = hd; + link->dev = &info->node; + printk(KERN_INFO "ide-cs: %s: Vcc = %d.%d, Vpp = %d.%d\n", + info->node.dev_name, link->conf.Vcc / 10, link->conf.Vcc % 10, + link->conf.Vpp1 / 10, link->conf.Vpp1 % 10); + + link->state &= ~DEV_CONFIG_PENDING; + kfree(stk); + return; + +err_mem: + printk(KERN_NOTICE "ide-cs: ide_config failed memory allocation\n"); + goto failed; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + kfree(stk); + ide_release(link); + link->state &= ~DEV_CONFIG_PENDING; +} /* ide_config */ + +/*====================================================================== + + After a card is removed, ide_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +void ide_release(dev_link_t *link) +{ + ide_info_t *info = link->priv; + + DEBUG(0, "ide_release(0x%p)\n", link); + + if (info->ndev) { + /* FIXME: if this fails we need to queue the cleanup somehow + -- need to investigate the required PCMCIA magic */ + ide_unregister(info->hd); + } + info->ndev = 0; + link->dev = NULL; + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; + +} /* ide_release */ + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the ide drivers from + talking to the ports. + +======================================================================*/ + +int ide_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + + DEBUG(1, "ide_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + ide_release(link); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + ide_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + pcmcia_request_configuration(link->handle, &link->conf); + break; + } + return 0; +} /* ide_event */ + +static struct pcmcia_driver ide_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "ide-cs", + }, + .attach = ide_attach, + .detach = ide_detach, +}; + +static int __init init_ide_cs(void) +{ + return pcmcia_register_driver(&ide_cs_driver); +} + +static void __exit exit_ide_cs(void) +{ + pcmcia_unregister_driver(&ide_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_ide_cs); +module_exit(exit_ide_cs); diff --git a/drivers/ide/legacy/macide.c b/drivers/ide/legacy/macide.c new file mode 100644 index 000000000000..90cac609d9cf --- /dev/null +++ b/drivers/ide/legacy/macide.c @@ -0,0 +1,155 @@ +/* + * linux/drivers/ide/legacy/macide.c -- Macintosh IDE Driver + * + * Copyright (C) 1998 by Michael Schmitz + * + * This driver was written based on information obtained from the MacOS IDE + * driver binary by Mikael Forselius + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/delay.h> +#include <linux/ide.h> + +#include <asm/machw.h> +#include <asm/macintosh.h> +#include <asm/macints.h> +#include <asm/mac_baboon.h> + +#define IDE_BASE 0x50F1A000 /* Base address of IDE controller */ + +/* + * Generic IDE registers as offsets from the base + * These match MkLinux so they should be correct. + */ + +#define IDE_DATA 0x00 +#define IDE_ERROR 0x04 /* see err-bits */ +#define IDE_NSECTOR 0x08 /* nr of sectors to read/write */ +#define IDE_SECTOR 0x0c /* starting sector */ +#define IDE_LCYL 0x10 /* starting cylinder */ +#define IDE_HCYL 0x14 /* high byte of starting cyl */ +#define IDE_SELECT 0x18 /* 101dhhhh , d=drive, hhhh=head */ +#define IDE_STATUS 0x1c /* see status-bits */ +#define IDE_CONTROL 0x38 /* control/altstatus */ + +/* + * Mac-specific registers + */ + +/* + * this register is odd; it doesn't seem to do much and it's + * not word-aligned like virtually every other hardware register + * on the Mac... + */ + +#define IDE_IFR 0x101 /* (0x101) IDE interrupt flags on Quadra: + * + * Bit 0+1: some interrupt flags + * Bit 2+3: some interrupt enable + * Bit 4: ?? + * Bit 5: IDE interrupt flag (any hwif) + * Bit 6: maybe IDE interrupt enable (any hwif) ?? + * Bit 7: Any interrupt condition + */ + +volatile unsigned char *ide_ifr = (unsigned char *) (IDE_BASE + IDE_IFR); + +static int macide_offsets[IDE_NR_PORTS] = { + IDE_DATA, IDE_ERROR, IDE_NSECTOR, IDE_SECTOR, IDE_LCYL, + IDE_HCYL, IDE_SELECT, IDE_STATUS, IDE_CONTROL +}; + +int macide_ack_intr(ide_hwif_t* hwif) +{ + if (*ide_ifr & 0x20) { + *ide_ifr &= ~0x20; + return 1; + } + return 0; +} + +#ifdef CONFIG_BLK_DEV_MAC_MEDIABAY +static void macide_mediabay_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + int state = baboon->mb_status & 0x04; + + printk(KERN_INFO "macide: media bay %s detected\n", state? "removal":"insertion"); +} +#endif + +/* + * Probe for a Macintosh IDE interface + */ + +void macide_init(void) +{ + hw_regs_t hw; + ide_hwif_t *hwif; + int index = -1; + + switch (macintosh_config->ide_type) { + case MAC_IDE_QUADRA: + ide_setup_ports(&hw, IDE_BASE, macide_offsets, + 0, 0, macide_ack_intr, +// quadra_ide_iops, + IRQ_NUBUS_F); + index = ide_register_hw(&hw, &hwif); + break; + case MAC_IDE_PB: + ide_setup_ports(&hw, IDE_BASE, macide_offsets, + 0, 0, macide_ack_intr, +// macide_pb_iops, + IRQ_NUBUS_C); + index = ide_register_hw(&hw, &hwif); + break; + case MAC_IDE_BABOON: + ide_setup_ports(&hw, BABOON_BASE, macide_offsets, + 0, 0, NULL, +// macide_baboon_iops, + IRQ_BABOON_1); + index = ide_register_hw(&hw, &hwif); + if (index == -1) break; + if (macintosh_config->ident == MAC_MODEL_PB190) { + + /* Fix breakage in ide-disk.c: drive capacity */ + /* is not initialized for drives without a */ + /* hardware ID, and we can't get that without */ + /* probing the drive which freezes a 190. */ + + ide_drive_t *drive = &ide_hwifs[index].drives[0]; + drive->capacity64 = drive->cyl*drive->head*drive->sect; + +#ifdef CONFIG_BLK_DEV_MAC_MEDIABAY + request_irq(IRQ_BABOON_2, macide_mediabay_interrupt, + IRQ_FLG_FAST, "mediabay", + macide_mediabay_interrupt); +#endif + } + break; + + default: + return; + } + + if (index != -1) { + hwif->mmio = 2; + if (macintosh_config->ide_type == MAC_IDE_QUADRA) + printk(KERN_INFO "ide%d: Macintosh Quadra IDE interface\n", index); + else if (macintosh_config->ide_type == MAC_IDE_PB) + printk(KERN_INFO "ide%d: Macintosh Powerbook IDE interface\n", index); + else if (macintosh_config->ide_type == MAC_IDE_BABOON) + printk(KERN_INFO "ide%d: Macintosh Powerbook Baboon IDE interface\n", index); + else + printk(KERN_INFO "ide%d: Unknown Macintosh IDE interface\n", index); + } +} diff --git a/drivers/ide/legacy/q40ide.c b/drivers/ide/legacy/q40ide.c new file mode 100644 index 000000000000..2a78b792f7fb --- /dev/null +++ b/drivers/ide/legacy/q40ide.c @@ -0,0 +1,150 @@ +/* + * linux/drivers/ide/legacy/q40ide.c -- Q40 I/O port IDE Driver + * + * (c) Richard Zidlicky + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + * + */ + +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> + +#include <linux/ide.h> + + /* + * Bases of the IDE interfaces + */ + +#define Q40IDE_NUM_HWIFS 2 + +#define PCIDE_BASE1 0x1f0 +#define PCIDE_BASE2 0x170 +#define PCIDE_BASE3 0x1e8 +#define PCIDE_BASE4 0x168 +#define PCIDE_BASE5 0x1e0 +#define PCIDE_BASE6 0x160 + +static const unsigned long pcide_bases[Q40IDE_NUM_HWIFS] = { + PCIDE_BASE1, PCIDE_BASE2, /* PCIDE_BASE3, PCIDE_BASE4 , PCIDE_BASE5, + PCIDE_BASE6 */ +}; + + + /* + * Offsets from one of the above bases + */ + +/* used to do addr translation here but it is easier to do in setup ports */ +/*#define IDE_OFF_B(x) ((unsigned long)Q40_ISA_IO_B((IDE_##x##_OFFSET)))*/ + +#define IDE_OFF_B(x) ((unsigned long)((IDE_##x##_OFFSET))) +#define IDE_OFF_W(x) ((unsigned long)((IDE_##x##_OFFSET))) + +static const int pcide_offsets[IDE_NR_PORTS] = { + IDE_OFF_W(DATA), IDE_OFF_B(ERROR), IDE_OFF_B(NSECTOR), IDE_OFF_B(SECTOR), + IDE_OFF_B(LCYL), IDE_OFF_B(HCYL), 6 /*IDE_OFF_B(CURRENT)*/, IDE_OFF_B(STATUS), + 518/*IDE_OFF(CMD)*/ +}; + +static int q40ide_default_irq(unsigned long base) +{ + switch (base) { + case 0x1f0: return 14; + case 0x170: return 15; + case 0x1e8: return 11; + default: + return 0; + } +} + + +/* + * This is very similar to ide_setup_ports except that addresses + * are pretranslated for q40 ISA access + */ +void q40_ide_setup_ports ( hw_regs_t *hw, + unsigned long base, int *offsets, + unsigned long ctrl, unsigned long intr, + ide_ack_intr_t *ack_intr, +/* + * ide_io_ops_t *iops, + */ + int irq) +{ + int i; + + for (i = 0; i < IDE_NR_PORTS; i++) { + /* BIG FAT WARNING: + assumption: only DATA port is ever used in 16 bit mode */ + if ( i==0 ) + hw->io_ports[i] = Q40_ISA_IO_W(base + offsets[i]); + else + hw->io_ports[i] = Q40_ISA_IO_B(base + offsets[i]); + } + + hw->irq = irq; + hw->dma = NO_DMA; + hw->ack_intr = ack_intr; +/* + * hw->iops = iops; + */ +} + + + +/* + * the static array is needed to have the name reported in /proc/ioports, + * hwif->name unfortunately isnīt available yet + */ +static const char *q40_ide_names[Q40IDE_NUM_HWIFS]={ + "ide0", "ide1" +}; + +/* + * Probe for Q40 IDE interfaces + */ + +void q40ide_init(void) +{ + int i; + ide_hwif_t *hwif; + int index; + const char *name; + + if (!MACH_IS_Q40) + return ; + + for (i = 0; i < Q40IDE_NUM_HWIFS; i++) { + hw_regs_t hw; + + name = q40_ide_names[i]; + if (!request_region(pcide_bases[i], 8, name)) { + printk("could not reserve ports %lx-%lx for %s\n", + pcide_bases[i],pcide_bases[i]+8,name); + continue; + } + if (!request_region(pcide_bases[i]+0x206, 1, name)) { + printk("could not reserve port %lx for %s\n", + pcide_bases[i]+0x206,name); + release_region(pcide_bases[i], 8); + continue; + } + q40_ide_setup_ports(&hw,(unsigned long) pcide_bases[i], (int *)pcide_offsets, + pcide_bases[i]+0x206, + 0, NULL, +// m68kide_iops, + q40ide_default_irq(pcide_bases[i])); + index = ide_register_hw(&hw, &hwif); + // **FIXME** + if (index != -1) + hwif->mmio = 2; + } +} + diff --git a/drivers/ide/legacy/qd65xx.c b/drivers/ide/legacy/qd65xx.c new file mode 100644 index 000000000000..563fab0098be --- /dev/null +++ b/drivers/ide/legacy/qd65xx.c @@ -0,0 +1,511 @@ +/* + * linux/drivers/ide/legacy/qd65xx.c Version 0.07 Sep 30, 2001 + * + * Copyright (C) 1996-2001 Linus Torvalds & author (see below) + */ + +/* + * Version 0.03 Cleaned auto-tune, added probe + * Version 0.04 Added second channel tuning + * Version 0.05 Enhanced tuning ; added qd6500 support + * Version 0.06 Added dos driver's list + * Version 0.07 Second channel bug fix + * + * QDI QD6500/QD6580 EIDE controller fast support + * + * Please set local bus speed using kernel parameter idebus + * for example, "idebus=33" stands for 33Mhz VLbus + * To activate controller support, use "ide0=qd65xx" + * To enable tuning, use "ide0=autotune" + * To enable second channel tuning (qd6580 only), use "ide1=autotune" + */ + +/* + * Rewritten from the work of Colten Edwards <pje120@cs.usask.ca> by + * Samuel Thibault <samuel.thibault@fnac.net> + */ + +#undef REALLY_SLOW_IO /* most systems can safely undef this */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> +#include <linux/init.h> +#include <asm/system.h> +#include <asm/io.h> + +#include "qd65xx.h" + +/* + * I/O ports are 0x30-0x31 (and 0x32-0x33 for qd6580) + * or 0xb0-0xb1 (and 0xb2-0xb3 for qd6580) + * -- qd6500 is a single IDE interface + * -- qd6580 is a dual IDE interface + * + * More research on qd6580 being done by willmore@cig.mot.com (David) + * More Information given by Petr Soucek (petr@ryston.cz) + * http://www.ryston.cz/petr/vlb + */ + +/* + * base: Timer1 + * + * + * base+0x01: Config (R/O) + * + * bit 0: ide baseport: 1 = 0x1f0 ; 0 = 0x170 (only useful for qd6500) + * bit 1: qd65xx baseport: 1 = 0xb0 ; 0 = 0x30 + * bit 2: ID3: bus speed: 1 = <=33MHz ; 0 = >33MHz + * bit 3: qd6500: 1 = disabled, 0 = enabled + * qd6580: 1 + * upper nibble: + * qd6500: 1100 + * qd6580: either 1010 or 0101 + * + * + * base+0x02: Timer2 (qd6580 only) + * + * + * base+0x03: Control (qd6580 only) + * + * bits 0-3 must always be set 1 + * bit 4 must be set 1, but is set 0 by dos driver while measuring vlb clock + * bit 0 : 1 = Only primary port enabled : channel 0 for hda, channel 1 for hdb + * 0 = Primary and Secondary ports enabled : channel 0 for hda & hdb + * channel 1 for hdc & hdd + * bit 1 : 1 = only disks on primary port + * 0 = disks & ATAPI devices on primary port + * bit 2-4 : always 0 + * bit 5 : status, but of what ? + * bit 6 : always set 1 by dos driver + * bit 7 : set 1 for non-ATAPI devices on primary port + * (maybe read-ahead and post-write buffer ?) + */ + +static int timings[4]={-1,-1,-1,-1}; /* stores current timing for each timer */ + +static void qd_write_reg (u8 content, unsigned long reg) +{ + unsigned long flags; + + spin_lock_irqsave(&ide_lock, flags); + outb(content,reg); + spin_unlock_irqrestore(&ide_lock, flags); +} + +static u8 __init qd_read_reg (unsigned long reg) +{ + unsigned long flags; + u8 read; + + spin_lock_irqsave(&ide_lock, flags); + read = inb(reg); + spin_unlock_irqrestore(&ide_lock, flags); + return read; +} + +/* + * qd_select: + * + * This routine is invoked from ide.c to prepare for access to a given drive. + */ + +static void qd_select (ide_drive_t *drive) +{ + u8 index = (( (QD_TIMREG(drive)) & 0x80 ) >> 7) | + (QD_TIMREG(drive) & 0x02); + + if (timings[index] != QD_TIMING(drive)) + qd_write_reg(timings[index] = QD_TIMING(drive), QD_TIMREG(drive)); +} + +/* + * qd6500_compute_timing + * + * computes the timing value where + * lower nibble represents active time, in count of VLB clocks + * upper nibble represents recovery time, in count of VLB clocks + */ + +static u8 qd6500_compute_timing (ide_hwif_t *hwif, int active_time, int recovery_time) +{ + u8 active_cycle,recovery_cycle; + + if (system_bus_clock()<=33) { + active_cycle = 9 - IDE_IN(active_time * system_bus_clock() / 1000 + 1, 2, 9); + recovery_cycle = 15 - IDE_IN(recovery_time * system_bus_clock() / 1000 + 1, 0, 15); + } else { + active_cycle = 8 - IDE_IN(active_time * system_bus_clock() / 1000 + 1, 1, 8); + recovery_cycle = 18 - IDE_IN(recovery_time * system_bus_clock() / 1000 + 1, 3, 18); + } + + return((recovery_cycle<<4) | 0x08 | active_cycle); +} + +/* + * qd6580_compute_timing + * + * idem for qd6580 + */ + +static u8 qd6580_compute_timing (int active_time, int recovery_time) +{ + u8 active_cycle = 17 - IDE_IN(active_time * system_bus_clock() / 1000 + 1, 2, 17); + u8 recovery_cycle = 15 - IDE_IN(recovery_time * system_bus_clock() / 1000 + 1, 2, 15); + + return((recovery_cycle<<4) | active_cycle); +} + +/* + * qd_find_disk_type + * + * tries to find timing from dos driver's table + */ + +static int qd_find_disk_type (ide_drive_t *drive, + int *active_time, int *recovery_time) +{ + struct qd65xx_timing_s *p; + char model[40]; + + if (!*drive->id->model) return 0; + + strncpy(model,drive->id->model,40); + ide_fixstring(model,40,1); /* byte-swap */ + + for (p = qd65xx_timing ; p->offset != -1 ; p++) { + if (!strncmp(p->model, model+p->offset, 4)) { + printk(KERN_DEBUG "%s: listed !\n", drive->name); + *active_time = p->active; + *recovery_time = p->recovery; + return 1; + } + } + return 0; +} + +/* + * qd_timing_ok: + * + * check whether timings don't conflict + */ + +static int qd_timing_ok (ide_drive_t drives[]) +{ + return (IDE_IMPLY(drives[0].present && drives[1].present, + IDE_IMPLY(QD_TIMREG(drives) == QD_TIMREG(drives+1), + QD_TIMING(drives) == QD_TIMING(drives+1)))); + /* if same timing register, must be same timing */ +} + +/* + * qd_set_timing: + * + * records the timing, and enables selectproc as needed + */ + +static void qd_set_timing (ide_drive_t *drive, u8 timing) +{ + ide_hwif_t *hwif = HWIF(drive); + + drive->drive_data &= 0xff00; + drive->drive_data |= timing; + if (qd_timing_ok(hwif->drives)) { + qd_select(drive); /* selects once */ + hwif->selectproc = NULL; + } else + hwif->selectproc = &qd_select; + + printk(KERN_DEBUG "%s: %#x\n", drive->name, timing); +} + +/* + * qd6500_tune_drive + */ + +static void qd6500_tune_drive (ide_drive_t *drive, u8 pio) +{ + int active_time = 175; + int recovery_time = 415; /* worst case values from the dos driver */ + + if (drive->id && !qd_find_disk_type(drive, &active_time, &recovery_time) + && drive->id->tPIO && (drive->id->field_valid & 0x02) + && drive->id->eide_pio >= 240) { + + printk(KERN_INFO "%s: PIO mode%d\n", drive->name, + drive->id->tPIO); + active_time = 110; + recovery_time = drive->id->eide_pio - 120; + } + + qd_set_timing(drive, qd6500_compute_timing(HWIF(drive), active_time, recovery_time)); +} + +/* + * qd6580_tune_drive + */ + +static void qd6580_tune_drive (ide_drive_t *drive, u8 pio) +{ + ide_pio_data_t d; + int base = HWIF(drive)->select_data; + int active_time = 175; + int recovery_time = 415; /* worst case values from the dos driver */ + + if (drive->id && !qd_find_disk_type(drive, &active_time, &recovery_time)) { + pio = ide_get_best_pio_mode(drive, pio, 255, &d); + pio = min_t(u8, pio, 4); + + switch (pio) { + case 0: break; + case 3: + if (d.cycle_time >= 110) { + active_time = 86; + recovery_time = d.cycle_time - 102; + } else + printk(KERN_WARNING "%s: Strange recovery time !\n",drive->name); + break; + case 4: + if (d.cycle_time >= 69) { + active_time = 70; + recovery_time = d.cycle_time - 61; + } else + printk(KERN_WARNING "%s: Strange recovery time !\n",drive->name); + break; + default: + if (d.cycle_time >= 180) { + active_time = 110; + recovery_time = d.cycle_time - 120; + } else { + active_time = ide_pio_timings[pio].active_time; + recovery_time = d.cycle_time + -active_time; + } + } + printk(KERN_INFO "%s: PIO mode%d\n", drive->name,pio); + } + + if (!HWIF(drive)->channel && drive->media != ide_disk) { + qd_write_reg(0x5f, QD_CONTROL_PORT); + printk(KERN_WARNING "%s: ATAPI: disabled read-ahead FIFO " + "and post-write buffer on %s.\n", + drive->name, HWIF(drive)->name); + } + + qd_set_timing(drive, qd6580_compute_timing(active_time, recovery_time)); +} + +/* + * qd_testreg + * + * tests if the given port is a register + */ + +static int __init qd_testreg(int port) +{ + u8 savereg; + u8 readreg; + unsigned long flags; + + spin_lock_irqsave(&ide_lock, flags); + savereg = inb_p(port); + outb_p(QD_TESTVAL, port); /* safe value */ + readreg = inb_p(port); + outb(savereg, port); + spin_unlock_irqrestore(&ide_lock, flags); + + if (savereg == QD_TESTVAL) { + printk(KERN_ERR "Outch ! the probe for qd65xx isn't reliable !\n"); + printk(KERN_ERR "Please contact maintainers to tell about your hardware\n"); + printk(KERN_ERR "Assuming qd65xx is not present.\n"); + return 1; + } + + return (readreg != QD_TESTVAL); +} + +/* + * qd_setup: + * + * called to setup an ata channel : adjusts attributes & links for tuning + */ + +static void __init qd_setup(ide_hwif_t *hwif, int base, int config, + unsigned int data0, unsigned int data1, + void (*tuneproc) (ide_drive_t *, u8 pio)) +{ + hwif->chipset = ide_qd65xx; + hwif->channel = hwif->index; + hwif->select_data = base; + hwif->config_data = config; + hwif->drives[0].drive_data = data0; + hwif->drives[1].drive_data = data1; + hwif->drives[0].io_32bit = + hwif->drives[1].io_32bit = 1; + hwif->tuneproc = tuneproc; + probe_hwif_init(hwif); +} + +/* + * qd_unsetup: + * + * called to unsetup an ata channel : back to default values, unlinks tuning + */ +/* +static void __exit qd_unsetup(ide_hwif_t *hwif) +{ + u8 config = hwif->config_data; + int base = hwif->select_data; + void *tuneproc = (void *) hwif->tuneproc; + + if (hwif->chipset != ide_qd65xx) + return; + + printk(KERN_NOTICE "%s: back to defaults\n", hwif->name); + + hwif->selectproc = NULL; + hwif->tuneproc = NULL; + + if (tuneproc == (void *) qd6500_tune_drive) { + // will do it for both + qd_write_reg(QD6500_DEF_DATA, QD_TIMREG(&hwif->drives[0])); + } else if (tuneproc == (void *) qd6580_tune_drive) { + if (QD_CONTROL(hwif) & QD_CONTR_SEC_DISABLED) { + qd_write_reg(QD6580_DEF_DATA, QD_TIMREG(&hwif->drives[0])); + qd_write_reg(QD6580_DEF_DATA2, QD_TIMREG(&hwif->drives[1])); + } else { + qd_write_reg(hwif->channel ? QD6580_DEF_DATA2 : QD6580_DEF_DATA, QD_TIMREG(&hwif->drives[0])); + } + } else { + printk(KERN_WARNING "Unknown qd65xx tuning fonction !\n"); + printk(KERN_WARNING "keeping settings !\n"); + } +} +*/ + +/* + * qd_probe: + * + * looks at the specified baseport, and if qd found, registers & initialises it + * return 1 if another qd may be probed + */ + +static int __init qd_probe(int base) +{ + ide_hwif_t *hwif; + u8 config; + u8 unit; + + config = qd_read_reg(QD_CONFIG_PORT); + + if (! ((config & QD_CONFIG_BASEPORT) >> 1 == (base == 0xb0)) ) + return 1; + + unit = ! (config & QD_CONFIG_IDE_BASEPORT); + + if ((config & 0xf0) == QD_CONFIG_QD6500) { + + if (qd_testreg(base)) return 1; /* bad register */ + + /* qd6500 found */ + + hwif = &ide_hwifs[unit]; + printk(KERN_NOTICE "%s: qd6500 at %#x\n", hwif->name, base); + printk(KERN_DEBUG "qd6500: config=%#x, ID3=%u\n", + config, QD_ID3); + + if (config & QD_CONFIG_DISABLED) { + printk(KERN_WARNING "qd6500 is disabled !\n"); + return 1; + } + + qd_setup(hwif, base, config, QD6500_DEF_DATA, QD6500_DEF_DATA, + &qd6500_tune_drive); + + create_proc_ide_interfaces(); + + return 1; + } + + if (((config & 0xf0) == QD_CONFIG_QD6580_A) || + ((config & 0xf0) == QD_CONFIG_QD6580_B)) { + + u8 control; + + if (qd_testreg(base) || qd_testreg(base+0x02)) return 1; + /* bad registers */ + + /* qd6580 found */ + + control = qd_read_reg(QD_CONTROL_PORT); + + printk(KERN_NOTICE "qd6580 at %#x\n", base); + printk(KERN_DEBUG "qd6580: config=%#x, control=%#x, ID3=%u\n", + config, control, QD_ID3); + + if (control & QD_CONTR_SEC_DISABLED) { + /* secondary disabled */ + + hwif = &ide_hwifs[unit]; + printk(KERN_INFO "%s: qd6580: single IDE board\n", + hwif->name); + qd_setup(hwif, base, config | (control << 8), + QD6580_DEF_DATA, QD6580_DEF_DATA2, + &qd6580_tune_drive); + qd_write_reg(QD_DEF_CONTR,QD_CONTROL_PORT); + + create_proc_ide_interfaces(); + + return 1; + } else { + ide_hwif_t *mate; + + hwif = &ide_hwifs[0]; + mate = &ide_hwifs[1]; + /* secondary enabled */ + printk(KERN_INFO "%s&%s: qd6580: dual IDE board\n", + hwif->name, mate->name); + + qd_setup(hwif, base, config | (control << 8), + QD6580_DEF_DATA, QD6580_DEF_DATA, + &qd6580_tune_drive); + qd_setup(mate, base, config | (control << 8), + QD6580_DEF_DATA2, QD6580_DEF_DATA2, + &qd6580_tune_drive); + qd_write_reg(QD_DEF_CONTR,QD_CONTROL_PORT); + + create_proc_ide_interfaces(); + + return 0; /* no other qd65xx possible */ + } + } + /* no qd65xx found */ + return 1; +} + +/* Can be called directly from ide.c. */ +int __init qd65xx_init(void) +{ + if (qd_probe(0x30)) + qd_probe(0xb0); + if (ide_hwifs[0].chipset != ide_qd65xx && + ide_hwifs[1].chipset != ide_qd65xx) + return -ENODEV; + return 0; +} + +#ifdef MODULE +module_init(qd65xx_init); +#endif + +MODULE_AUTHOR("Samuel Thibault"); +MODULE_DESCRIPTION("support of qd65xx vlb ide chipset"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ide/legacy/qd65xx.h b/drivers/ide/legacy/qd65xx.h new file mode 100644 index 000000000000..633a42456ef6 --- /dev/null +++ b/drivers/ide/legacy/qd65xx.h @@ -0,0 +1,140 @@ +/* + * linux/drivers/ide/legacy/qd65xx.h + * + * Copyright (c) 2000 Linus Torvalds & authors + */ + +/* + * Authors: Petr Soucek <petr@ryston.cz> + * Samuel Thibault <samuel.thibault@fnac.net> + */ + +/* truncates a in [b,c] */ +#define IDE_IN(a,b,c) ( ((a)<(b)) ? (b) : ( (a)>(c) ? (c) : (a)) ) + +#define IDE_IMPLY(a,b) ((!(a)) || (b)) + +#define QD_TIM1_PORT (base) +#define QD_CONFIG_PORT (base+0x01) +#define QD_TIM2_PORT (base+0x02) +#define QD_CONTROL_PORT (base+0x03) + +#define QD_CONFIG_IDE_BASEPORT 0x01 +#define QD_CONFIG_BASEPORT 0x02 +#define QD_CONFIG_ID3 0x04 +#define QD_CONFIG_DISABLED 0x08 +#define QD_CONFIG_QD6500 0xc0 +#define QD_CONFIG_QD6580_A 0xa0 +#define QD_CONFIG_QD6580_B 0x50 + +#define QD_CONTR_SEC_DISABLED 0x01 + +#define QD_ID3 ((config & QD_CONFIG_ID3)!=0) + +#define QD_CONFIG(hwif) ((hwif)->config_data & 0x00ff) +#define QD_CONTROL(hwif) (((hwif)->config_data & 0xff00) >> 8) + +#define QD_TIMING(drive) (byte)(((drive)->drive_data) & 0x00ff) +#define QD_TIMREG(drive) (byte)((((drive)->drive_data) & 0xff00) >> 8) + +#define QD6500_DEF_DATA ((QD_TIM1_PORT<<8) | (QD_ID3 ? 0x0c : 0x08)) +#define QD6580_DEF_DATA ((QD_TIM1_PORT<<8) | (QD_ID3 ? 0x0a : 0x00)) +#define QD6580_DEF_DATA2 ((QD_TIM2_PORT<<8) | (QD_ID3 ? 0x0a : 0x00)) +#define QD_DEF_CONTR (0x40 | ((control & 0x02) ? 0x9f : 0x1f)) + +#define QD_TESTVAL 0x19 /* safe value */ + +/* Drive specific timing taken from DOS driver v3.7 */ + +static struct qd65xx_timing_s { + s8 offset; /* ofset from the beginning of Model Number" */ + char model[4]; /* 4 chars from Model number, no conversion */ + s16 active; /* active time */ + s16 recovery; /* recovery time */ +} qd65xx_timing [] = { + { 30, "2040", 110, 225 }, /* Conner CP30204 */ + { 30, "2045", 135, 225 }, /* Conner CP30254 */ + { 30, "1040", 155, 325 }, /* Conner CP30104 */ + { 30, "1047", 135, 265 }, /* Conner CP30174 */ + { 30, "5344", 135, 225 }, /* Conner CP3544 */ + { 30, "01 4", 175, 405 }, /* Conner CP-3104 */ + { 27, "C030", 175, 375 }, /* Conner CP3000 */ + { 8, "PL42", 110, 295 }, /* Quantum LP240 */ + { 8, "PL21", 110, 315 }, /* Quantum LP120 */ + { 8, "PL25", 175, 385 }, /* Quantum LP52 */ + { 4, "PA24", 110, 285 }, /* WD Piranha SP4200 */ + { 6, "2200", 110, 260 }, /* WD Caviar AC2200 */ + { 6, "3204", 110, 235 }, /* WD Caviar AC2340 */ + { 6, "1202", 110, 265 }, /* WD Caviar AC2120 */ + { 0, "DS3-", 135, 315 }, /* Teac SD340 */ + { 8, "KM32", 175, 355 }, /* Toshiba MK234 */ + { 2, "53A1", 175, 355 }, /* Seagate ST351A */ + { 2, "4108", 175, 295 }, /* Seagate ST1480A */ + { 2, "1344", 175, 335 }, /* Seagate ST3144A */ + { 6, "7 12", 110, 225 }, /* Maxtor 7213A */ + { 30, "02F4", 145, 295 }, /* Conner 3204F */ + { 2, "1302", 175, 335 }, /* Seagate ST3120A */ + { 2, "2334", 145, 265 }, /* Seagate ST3243A */ + { 2, "2338", 145, 275 }, /* Seagate ST3283A */ + { 2, "3309", 145, 275 }, /* Seagate ST3390A */ + { 2, "5305", 145, 275 }, /* Seagate ST3550A */ + { 2, "4100", 175, 295 }, /* Seagate ST1400A */ + { 2, "4110", 175, 295 }, /* Seagate ST1401A */ + { 2, "6300", 135, 265 }, /* Seagate ST3600A */ + { 2, "5300", 135, 265 }, /* Seagate ST3500A */ + { 6, "7 31", 135, 225 }, /* Maxtor 7131 AT */ + { 6, "7 43", 115, 265 }, /* Maxtor 7345 AT */ + { 6, "7 42", 110, 255 }, /* Maxtor 7245 AT */ + { 6, "3 04", 135, 265 }, /* Maxtor 340 AT */ + { 6, "61 0", 135, 285 }, /* WD AC160 */ + { 6, "1107", 135, 235 }, /* WD AC1170 */ + { 6, "2101", 110, 220 }, /* WD AC1210 */ + { 6, "4202", 135, 245 }, /* WD AC2420 */ + { 6, "41 0", 175, 355 }, /* WD Caviar 140 */ + { 6, "82 0", 175, 355 }, /* WD Caviar 280 */ + { 8, "PL01", 175, 375 }, /* Quantum LP105 */ + { 8, "PL25", 110, 295 }, /* Quantum LP525 */ + { 10, "4S 2", 175, 385 }, /* Quantum ELS42 */ + { 10, "8S 5", 175, 385 }, /* Quantum ELS85 */ + { 10, "1S72", 175, 385 }, /* Quantum ELS127 */ + { 10, "1S07", 175, 385 }, /* Quantum ELS170 */ + { 8, "ZE42", 135, 295 }, /* Quantum EZ240 */ + { 8, "ZE21", 175, 385 }, /* Quantum EZ127 */ + { 8, "ZE58", 175, 385 }, /* Quantum EZ85 */ + { 8, "ZE24", 175, 385 }, /* Quantum EZ42 */ + { 27, "C036", 155, 325 }, /* Conner CP30064 */ + { 27, "C038", 155, 325 }, /* Conner CP30084 */ + { 6, "2205", 110, 255 }, /* WDC AC2250 */ + { 2, " CHA", 140, 415 }, /* WDC AH series; WDC AH260, WDC */ + { 2, " CLA", 140, 415 }, /* WDC AL series: WDC AL2120, 2170, */ + { 4, "UC41", 140, 415 }, /* WDC CU140 */ + { 6, "1207", 130, 275 }, /* WDC AC2170 */ + { 6, "2107", 130, 275 }, /* WDC AC1270 */ + { 6, "5204", 130, 275 }, /* WDC AC2540 */ + { 30, "3004", 110, 235 }, /* Conner CP30340 */ + { 30, "0345", 135, 255 }, /* Conner CP30544 */ + { 12, "12A3", 175, 320 }, /* MAXTOR LXT-213A */ + { 12, "43A0", 145, 240 }, /* MAXTOR LXT-340A */ + { 6, "7 21", 180, 290 }, /* Maxtor 7120 AT */ + { 6, "7 71", 135, 240 }, /* Maxtor 7170 AT */ + { 12, "45\0000", 110, 205 }, /* MAXTOR MXT-540 */ + { 8, "PL11", 180, 290 }, /* QUANTUM LP110A */ + { 8, "OG21", 150, 275 }, /* QUANTUM GO120 */ + { 12, "42A5", 175, 320 }, /* MAXTOR LXT-245A */ + { 2, "2309", 175, 295 }, /* ST3290A */ + { 2, "3358", 180, 310 }, /* ST3385A */ + { 2, "6355", 180, 310 }, /* ST3655A */ + { 2, "1900", 175, 270 }, /* ST9100A */ + { 2, "1954", 175, 270 }, /* ST9145A */ + { 2, "1909", 175, 270 }, /* ST9190AG */ + { 2, "2953", 175, 270 }, /* ST9235A */ + { 2, "1359", 175, 270 }, /* ST3195A */ + { 24, "3R11", 175, 290 }, /* ALPS ELECTRIC Co.,LTD, DR311C */ + { 0, "2M26", 175, 215 }, /* M262XT-0Ah */ + { 4, "2253", 175, 300 }, /* HP C2235A */ + { 4, "-32A", 145, 245 }, /* H3133-A2 */ + { 30, "0326", 150, 270 }, /* Samsung Electronics 120MB */ + { 30, "3044", 110, 195 }, /* Conner CFA340A */ + { 30, "43A0", 110, 195 }, /* Conner CFA340A */ + { -1, " ", 175, 415 } /* unknown disk name */ +}; diff --git a/drivers/ide/legacy/umc8672.c b/drivers/ide/legacy/umc8672.c new file mode 100644 index 000000000000..cdbdb2ff9f15 --- /dev/null +++ b/drivers/ide/legacy/umc8672.c @@ -0,0 +1,183 @@ +/* + * linux/drivers/ide/legacy/umc8672.c Version 0.05 Jul 31, 1996 + * + * Copyright (C) 1995-1996 Linus Torvalds & author (see below) + */ + +/* + * Principal Author/Maintainer: PODIEN@hml2.atlas.de (Wolfram Podien) + * + * This file provides support for the advanced features + * of the UMC 8672 IDE interface. + * + * Version 0.01 Initial version, hacked out of ide.c, + * and #include'd rather than compiled separately. + * This will get cleaned up in a subsequent release. + * + * Version 0.02 now configs/compiles separate from ide.c -ml + * Version 0.03 enhanced auto-tune, fix display bug + * Version 0.05 replace sti() with restore_flags() -ml + * add detection of possible race condition -ml + */ + +/* + * VLB Controller Support from + * Wolfram Podien + * Rohoefe 3 + * D28832 Achim + * Germany + * + * To enable UMC8672 support there must a lilo line like + * append="ide0=umc8672"... + * To set the speed according to the abilities of the hardware there must be a + * line like + * #define UMC_DRIVE0 11 + * in the beginning of the driver, which sets the speed of drive 0 to 11 (there + * are some lines present). 0 - 11 are allowed speed values. These values are + * the results from the DOS speed test program supplied from UMC. 11 is the + * highest speed (about PIO mode 3) + */ +#define REALLY_SLOW_IO /* some systems can safely undef this */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> +#include <linux/init.h> + +#include <asm/io.h> + +/* + * Default speeds. These can be changed with "auto-tune" and/or hdparm. + */ +#define UMC_DRIVE0 1 /* DOS measured drive speeds */ +#define UMC_DRIVE1 1 /* 0 to 11 allowed */ +#define UMC_DRIVE2 1 /* 11 = Fastest Speed */ +#define UMC_DRIVE3 1 /* In case of crash reduce speed */ + +static u8 current_speeds[4] = {UMC_DRIVE0, UMC_DRIVE1, UMC_DRIVE2, UMC_DRIVE3}; +static const u8 pio_to_umc [5] = {0,3,7,10,11}; /* rough guesses */ + +/* 0 1 2 3 4 5 6 7 8 9 10 11 */ +static const u8 speedtab [3][12] = { + {0xf, 0xb, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }, + {0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }, + {0xff,0xcb,0xc0,0x58,0x36,0x33,0x23,0x22,0x21,0x11,0x10,0x0}}; + +static void out_umc (char port,char wert) +{ + outb_p(port,0x108); + outb_p(wert,0x109); +} + +static inline u8 in_umc (char port) +{ + outb_p(port,0x108); + return inb_p(0x109); +} + +static void umc_set_speeds (u8 speeds[]) +{ + int i, tmp; + + outb_p(0x5A,0x108); /* enable umc */ + + out_umc (0xd7,(speedtab[0][speeds[2]] | (speedtab[0][speeds[3]]<<4))); + out_umc (0xd6,(speedtab[0][speeds[0]] | (speedtab[0][speeds[1]]<<4))); + tmp = 0; + for (i = 3; i >= 0; i--) { + tmp = (tmp << 2) | speedtab[1][speeds[i]]; + } + out_umc (0xdc,tmp); + for (i = 0;i < 4; i++) { + out_umc (0xd0+i,speedtab[2][speeds[i]]); + out_umc (0xd8+i,speedtab[2][speeds[i]]); + } + outb_p(0xa5,0x108); /* disable umc */ + + printk ("umc8672: drive speeds [0 to 11]: %d %d %d %d\n", + speeds[0], speeds[1], speeds[2], speeds[3]); +} + +static void tune_umc (ide_drive_t *drive, u8 pio) +{ + unsigned long flags; + ide_hwgroup_t *hwgroup = ide_hwifs[HWIF(drive)->index^1].hwgroup; + + pio = ide_get_best_pio_mode(drive, pio, 4, NULL); + printk("%s: setting umc8672 to PIO mode%d (speed %d)\n", + drive->name, pio, pio_to_umc[pio]); + spin_lock_irqsave(&ide_lock, flags); + if (hwgroup && hwgroup->handler != NULL) { + printk(KERN_ERR "umc8672: other interface is busy: exiting tune_umc()\n"); + } else { + current_speeds[drive->name[2] - 'a'] = pio_to_umc[pio]; + umc_set_speeds (current_speeds); + } + spin_unlock_irqrestore(&ide_lock, flags); +} + +static int __init umc8672_probe(void) +{ + unsigned long flags; + ide_hwif_t *hwif, *mate; + + if (!request_region(0x108, 2, "umc8672")) { + printk(KERN_ERR "umc8672: ports 0x108-0x109 already in use.\n"); + return 1; + } + local_irq_save(flags); + outb_p(0x5A,0x108); /* enable umc */ + if (in_umc (0xd5) != 0xa0) { + local_irq_restore(flags); + printk(KERN_ERR "umc8672: not found\n"); + release_region(0x108, 2); + return 1; + } + outb_p(0xa5,0x108); /* disable umc */ + + umc_set_speeds (current_speeds); + local_irq_restore(flags); + + hwif = &ide_hwifs[0]; + mate = &ide_hwifs[1]; + + hwif->chipset = ide_umc8672; + hwif->tuneproc = &tune_umc; + hwif->mate = mate; + + mate->chipset = ide_umc8672; + mate->tuneproc = &tune_umc; + mate->mate = hwif; + mate->channel = 1; + + probe_hwif_init(hwif); + probe_hwif_init(mate); + + create_proc_ide_interfaces(); + + return 0; +} + +/* Can be called directly from ide.c. */ +int __init umc8672_init(void) +{ + if (umc8672_probe()) + return -ENODEV; + return 0; +} + +#ifdef MODULE +module_init(umc8672_init); +#endif + +MODULE_AUTHOR("Wolfram Podien"); +MODULE_DESCRIPTION("Support for UMC 8672 IDE chipset"); +MODULE_LICENSE("GPL"); |