diff options
author | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
---|---|---|
committer | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
commit | f07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch) | |
tree | 12b07c7e578fc1a5f53dbfde2632408491ff2a70 /i386/i386at/gpl/linux/block/cmd640.c |
Initial source
Diffstat (limited to 'i386/i386at/gpl/linux/block/cmd640.c')
-rw-r--r-- | i386/i386at/gpl/linux/block/cmd640.c | 738 |
1 files changed, 738 insertions, 0 deletions
diff --git a/i386/i386at/gpl/linux/block/cmd640.c b/i386/i386at/gpl/linux/block/cmd640.c new file mode 100644 index 0000000..99a139d --- /dev/null +++ b/i386/i386at/gpl/linux/block/cmd640.c @@ -0,0 +1,738 @@ +/* + * linux/drivers/block/cmd640.c Version 0.07 Jan 27, 1996 + * + * Copyright (C) 1995-1996 Linus Torvalds & author (see below) + */ + +/* + * Principal Author/Maintainer: abramov@cecmow.enet.dec.com (Igor Abramov) + * + * This file provides support for the advanced features and bugs + * of IDE interfaces using the CMD Technologies 0640 IDE interface chip. + * + * 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 Fixes for vlb initialization code, enable + * read-ahead for versions 'B' and 'C' of chip by + * default, some code cleanup. + * + * Version 0.03 Added reset of secondary interface, + * and black list for devices which are not compatible + * with read ahead mode. Separate function for setting + * readahead is added, possibly it will be called some + * day from ioctl processing code. + * + * Version 0.04 Now configs/compiles separate from ide.c -ml + * + * Version 0.05 Major rewrite of interface timing code. + * Added new function cmd640_set_mode to set PIO mode + * from ioctl call. New drives added to black list. + * + * Version 0.06 More code cleanup. Readahead is enabled only for + * detected hard drives, not included in readahed + * black list. + * + * Version 0.07 Changed to more conservative drive tuning policy. + * Unknown drives, which report PIO < 4 are set to + * (reported_PIO - 1) if it is supported, or to PIO0. + * List of known drives extended by info provided by + * CMD at their ftp site. + * + * Version 0.08 Added autotune/noautotune support. -ml + * + */ + +#undef REALLY_SLOW_IO /* most systems can safely undef this */ + +#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 <asm/io.h> +#include "ide.h" +#include "ide_modes.h" + +int cmd640_vlb = 0; + +/* + * CMD640 specific registers definition. + */ + +#define VID 0x00 +#define DID 0x02 +#define PCMD 0x04 +#define PSTTS 0x06 +#define REVID 0x08 +#define PROGIF 0x09 +#define SUBCL 0x0a +#define BASCL 0x0b +#define BaseA0 0x10 +#define BaseA1 0x14 +#define BaseA2 0x18 +#define BaseA3 0x1c +#define INTLINE 0x3c +#define INPINE 0x3d + +#define CFR 0x50 +#define CFR_DEVREV 0x03 +#define CFR_IDE01INTR 0x04 +#define CFR_DEVID 0x18 +#define CFR_AT_VESA_078h 0x20 +#define CFR_DSA1 0x40 +#define CFR_DSA0 0x80 + +#define CNTRL 0x51 +#define CNTRL_DIS_RA0 0x40 +#define CNTRL_DIS_RA1 0x80 +#define CNTRL_ENA_2ND 0x08 + +#define CMDTIM 0x52 +#define ARTTIM0 0x53 +#define DRWTIM0 0x54 +#define ARTTIM1 0x55 +#define DRWTIM1 0x56 +#define ARTTIM23 0x57 +#define DIS_RA2 0x04 +#define DIS_RA3 0x08 +#define DRWTIM23 0x58 +#define BRST 0x59 + +static ide_tuneproc_t cmd640_tune_drive; + +/* Interface to access cmd640x registers */ +static void (*put_cmd640_reg)(int reg_no, int val); +static byte (*get_cmd640_reg)(int reg_no); + +enum { none, vlb, pci1, pci2 }; +static int bus_type = none; +static int cmd640_chip_version; +static int cmd640_key; +static int bus_speed; /* MHz */ + +/* + * For some unknown reasons pcibios functions which read and write registers + * do not always work with cmd640. We use direct IO instead. + */ + +/* PCI method 1 access */ + +static void put_cmd640_reg_pci1(int reg_no, int val) +{ + unsigned long flags; + + save_flags(flags); + cli(); + outl_p((reg_no & 0xfc) | cmd640_key, 0xcf8); + outb_p(val, (reg_no & 3) + 0xcfc); + restore_flags(flags); +} + +static byte get_cmd640_reg_pci1(int reg_no) +{ + byte b; + unsigned long flags; + + save_flags(flags); + cli(); + outl_p((reg_no & 0xfc) | cmd640_key, 0xcf8); + b = inb_p(0xcfc + (reg_no & 3)); + restore_flags(flags); + return b; +} + +/* PCI method 2 access (from CMD datasheet) */ + +static void put_cmd640_reg_pci2(int reg_no, int val) +{ + unsigned long flags; + + save_flags(flags); + cli(); + outb_p(0x10, 0xcf8); + outb_p(val, cmd640_key + reg_no); + outb_p(0, 0xcf8); + restore_flags(flags); +} + +static byte get_cmd640_reg_pci2(int reg_no) +{ + byte b; + unsigned long flags; + + save_flags(flags); + cli(); + outb_p(0x10, 0xcf8); + b = inb_p(cmd640_key + reg_no); + outb_p(0, 0xcf8); + restore_flags(flags); + return b; +} + +/* VLB access */ + +static void put_cmd640_reg_vlb(int reg_no, int val) +{ + unsigned long flags; + + save_flags(flags); + cli(); + outb_p(reg_no, cmd640_key + 8); + outb_p(val, cmd640_key + 0xc); + restore_flags(flags); +} + +static byte get_cmd640_reg_vlb(int reg_no) +{ + byte b; + unsigned long flags; + + save_flags(flags); + cli(); + outb_p(reg_no, cmd640_key + 8); + b = inb_p(cmd640_key + 0xc); + restore_flags(flags); + return b; +} + +/* + * Probe for CMD640x -- pci method 1 + */ + +static int probe_for_cmd640_pci1(void) +{ + long id; + int k; + + for (k = 0x80000000; k <= 0x8000f800; k += 0x800) { + outl(k, 0xcf8); + id = inl(0xcfc); + if (id != 0x06401095) + continue; + put_cmd640_reg = put_cmd640_reg_pci1; + get_cmd640_reg = get_cmd640_reg_pci1; + cmd640_key = k; + return 1; + } + return 0; +} + +/* + * Probe for CMD640x -- pci method 2 + */ + +static int probe_for_cmd640_pci2(void) +{ + int i; + int v_id; + int d_id; + + for (i = 0xc000; i <= 0xcf00; i += 0x100) { + outb(0x10, 0xcf8); + v_id = inw(i); + d_id = inw(i + 2); + outb(0, 0xcf8); + if (v_id != 0x1095 || d_id != 0x640) + continue; + put_cmd640_reg = put_cmd640_reg_pci2; + get_cmd640_reg = get_cmd640_reg_pci2; + cmd640_key = i; + return 1; + } + return 0; +} + +/* + * Probe for CMD640x -- vlb + */ + +static int probe_for_cmd640_vlb(void) { + byte b; + + outb(CFR, 0x178); + b = inb(0x17c); + if (b == 0xff || b == 0 || (b & CFR_AT_VESA_078h)) { + outb(CFR, 0x78); + b = inb(0x7c); + if (b == 0xff || b == 0 || !(b & CFR_AT_VESA_078h)) + return 0; + cmd640_key = 0x70; + } else { + cmd640_key = 0x170; + } + put_cmd640_reg = put_cmd640_reg_vlb; + get_cmd640_reg = get_cmd640_reg_vlb; + return 1; +} + +/* + * Low level reset for controller, actually it has nothing specific for + * CMD640, but I don't know how to use standard reset routine before + * we recognized any drives. + */ + +static void cmd640_reset_controller(int iface_no) +{ + int retry_count = 600; + int base_port = iface_no ? 0x170 : 0x1f0; + + outb_p(4, base_port + 7); + udelay(5); + outb_p(0, base_port + 7); + + do { + udelay(5); + retry_count -= 1; + } while ((inb_p(base_port + 7) & 0x80) && retry_count); + + if (retry_count == 0) + printk("cmd640: failed to reset controller %d\n", iface_no); +#if 0 + else + printk("cmd640: controller %d reset [%d]\n", + iface_no, retry_count); +#endif +} + +/* + * Probe for Cmd640x and initialize it if found + */ + +int ide_probe_for_cmd640x(void) +{ + int second_port; + byte b; + + if (probe_for_cmd640_pci1()) { + bus_type = pci1; + } else if (probe_for_cmd640_pci2()) { + bus_type = pci2; + } else if (cmd640_vlb && probe_for_cmd640_vlb()) { + /* May be remove cmd640_vlb at all, and probe in any case */ + bus_type = vlb; + } else { + return 0; + } + + ide_hwifs[0].serialized = 1; /* ensure this *always* gets set */ + +#if 0 + /* Dump initial state of chip registers */ + for (b = 0; b != 0xff; b++) { + printk(" %2x%c", get_cmd640_reg(b), + ((b&0xf) == 0xf) ? '\n' : ','); + } + +#endif + + /* + * Undocumented magic. (There is no 0x5b port in specs) + */ + + put_cmd640_reg(0x5b, 0xbd); + if (get_cmd640_reg(0x5b) != 0xbd) { + printk("ide: can't initialize cmd640 -- wrong value in 0x5b\n"); + return 0; + } + put_cmd640_reg(0x5b, 0); + + /* + * Documented magic. + */ + + cmd640_chip_version = get_cmd640_reg(CFR) & CFR_DEVREV; + if (cmd640_chip_version == 0) { + printk ("ide: wrong CMD640 version -- 0\n"); + return 0; + } + + /* + * Setup the most conservative timings for all drives, + */ + put_cmd640_reg(ARTTIM0, 0xc0); + put_cmd640_reg(ARTTIM1, 0xc0); + put_cmd640_reg(ARTTIM23, 0xcc); /* 0xc0? */ + + /* + * Do not initialize secondary controller for vlbus + */ + second_port = (bus_type != vlb); + + /* + * Set the maximum allowed bus speed (it is safest until we + * find how to detect bus speed) + * Normally PCI bus runs at 33MHz, but often works overclocked to 40 + */ + bus_speed = (bus_type == vlb) ? 50 : 40; + + /* + * Setup Control Register + */ + b = get_cmd640_reg(CNTRL); + + if (second_port) + b |= CNTRL_ENA_2ND; + else + b &= ~CNTRL_ENA_2ND; + + /* + * Disable readahead for drives at primary interface + */ + b |= (CNTRL_DIS_RA0 | CNTRL_DIS_RA1); + + put_cmd640_reg(CNTRL, b); + + /* + * Note that we assume that the first interface is at 0x1f0, + * and that the second interface, if enabled, is at 0x170. + */ + ide_hwifs[0].chipset = ide_cmd640; + ide_hwifs[0].tuneproc = &cmd640_tune_drive; + if (ide_hwifs[0].drives[0].autotune == 0) + ide_hwifs[0].drives[0].autotune = 1; + if (ide_hwifs[0].drives[1].autotune == 0) + ide_hwifs[0].drives[1].autotune = 1; + + /* + * Initialize 2nd IDE port, if required + */ + if (second_port) { + ide_hwifs[1].chipset = ide_cmd640; + ide_hwifs[1].tuneproc = &cmd640_tune_drive; + if (ide_hwifs[1].drives[0].autotune == 0) + ide_hwifs[1].drives[0].autotune = 1; + if (ide_hwifs[1].drives[1].autotune == 0) + ide_hwifs[1].drives[1].autotune = 1; + /* We reset timings, and disable read-ahead */ + put_cmd640_reg(ARTTIM23, (DIS_RA2 | DIS_RA3)); + put_cmd640_reg(DRWTIM23, 0); + + cmd640_reset_controller(1); + } + + printk("ide: buggy CMD640%c interface at ", + 'A' - 1 + cmd640_chip_version); + switch (bus_type) { + case vlb : + printk("local bus, port 0x%x", cmd640_key); + break; + case pci1: + printk("pci, (0x%x)", cmd640_key); + break; + case pci2: + printk("pci,(access method 2) (0x%x)", cmd640_key); + break; + } + + /* + * Reset interface timings + */ + put_cmd640_reg(CMDTIM, 0); + + printk("\n ... serialized, secondary interface %s\n", + second_port ? "enabled" : "disabled"); + + return 1; +} + +int cmd640_off(void) { + static int a = 0; + byte b; + + if (bus_type == none || a == 1) + return 0; + a = 1; + b = get_cmd640_reg(CNTRL); + b &= ~CNTRL_ENA_2ND; + put_cmd640_reg(CNTRL, b); + return 1; +} + +/* + * Sets readahead mode for specific drive + * in the future it could be called from ioctl + */ + +static void set_readahead_mode(int mode, int if_num, int dr_num) +{ + static int masks[2][2] = + { + {CNTRL_DIS_RA0, CNTRL_DIS_RA1}, + {DIS_RA2, DIS_RA3} + }; + + int port = (if_num == 0) ? CNTRL : ARTTIM23; + int mask = masks[if_num][dr_num]; + byte b; + + b = get_cmd640_reg(port); + if (mode) + b &= ~mask; /* Enable readahead for specific drive */ + else + b |= mask; /* Disable readahed for specific drive */ + put_cmd640_reg(port, b); +} + +static struct readahead_black_list { + const char* name; + int mode; +} drives_ra[] = { + { "ST3655A", 0 }, + { "SAMSUNG", 0 }, /* Be conservative */ + { NULL, 0 } +}; + +static int strmatch(const char* pattern, const char* name) { + char c1, c2; + + while (1) { + c1 = *pattern++; + c2 = *name++; + if (c1 == 0) { + return 0; + } + if (c1 != c2) + return 1; + } +} + +static int known_drive_readahead(char* name) { + int i; + + for (i = 0; drives_ra[i].name != NULL; i++) { + if (strmatch(drives_ra[i].name, name) == 0) { + return drives_ra[i].mode; + } + } + return -1; +} + +static int arttim[4] = {2, 2, 2, 2}; /* Address setup count (in clocks) */ +static int a_count[4] = {1, 1, 1, 1}; /* Active count (encoded) */ +static int r_count[4] = {1, 1, 1, 1}; /* Recovery count (encoded) */ + +/* + * Convert address setup count from number of clocks + * to representation used by controller + */ + +inline static int pack_arttim(int clocks) +{ + if (clocks <= 2) return 0x40; + else if (clocks == 3) return 0x80; + else if (clocks == 4) return 0x00; + else return 0xc0; +} + +/* + * Pack active and recovery counts into single byte representation + * used by controller + */ + +inline static int pack_counts(int act_count, int rec_count) +{ + return ((act_count & 0x0f)<<4) | (rec_count & 0x0f); +} + +inline int max(int a, int b) { return a > b ? a : b; } +inline int max4(int *p) { return max(p[0], max(p[1], max(p[2], p[3]))); } + +/* + * Set timing parameters + */ + +static void cmd640_set_timing(int if_num, int dr_num) +{ + int b_reg; + int ac, rc, at; + + /* + * Set address setup count and drive read/write timing registers. + * Primary interface has individual count/timing registers for + * each drive. Secondary interface has common set of registers, and + * we should set timings for the slowest drive. + */ + + if (if_num == 0) { + b_reg = dr_num ? ARTTIM1 : ARTTIM0; + at = arttim[dr_num]; + ac = a_count[dr_num]; + rc = r_count[dr_num]; + } else { + b_reg = ARTTIM23; + at = max(arttim[2], arttim[3]); + ac = max(a_count[2], a_count[3]); + rc = max(r_count[2], r_count[3]); + } + + put_cmd640_reg(b_reg, pack_arttim(at)); + put_cmd640_reg(b_reg + 1, pack_counts(ac, rc)); + + /* + * Update CMDTIM (IDE Command Block Timing Register) + */ + + ac = max4(r_count); + rc = max4(a_count); + put_cmd640_reg(CMDTIM, pack_counts(ac, rc)); +} + +/* + * Standard timings for PIO modes + */ + +static struct pio_timing { + int mc_time; /* Minimal cycle time (ns) */ + int av_time; /* Address valid to DIOR-/DIOW- setup (ns) */ + int ds_time; /* DIOR data setup (ns) */ +} pio_timings[6] = { + { 70, 165, 600 }, /* PIO Mode 0 */ + { 50, 125, 383 }, /* PIO Mode 1 */ + { 30, 100, 240 }, /* PIO Mode 2 */ + { 30, 80, 180 }, /* PIO Mode 3 */ + { 25, 70, 125 }, /* PIO Mode 4 -- should be 120, not 125 */ + { 20, 50, 100 } /* PIO Mode ? (nonstandard) */ +}; + +static void cmd640_timings_to_clocks(int mc_time, int av_time, int ds_time, + int clock_time, int drv_idx) +{ + int a, b; + + arttim[drv_idx] = (mc_time + clock_time - 1)/clock_time; + + a = (av_time + clock_time - 1)/clock_time; + if (a < 2) + a = 2; + b = (ds_time + clock_time - 1)/clock_time - a; + if (b < 2) + b = 2; + if (b > 0x11) { + a += b - 0x11; + b = 0x11; + } + if (a > 0x10) + a = 0x10; + if (cmd640_chip_version > 1) + b -= 1; + if (b > 0x10) + b = 0x10; + + a_count[drv_idx] = a; + r_count[drv_idx] = b; +} + +static void set_pio_mode(int if_num, int drv_num, int mode_num) { + int p_base; + int i; + + p_base = if_num ? 0x170 : 0x1f0; + outb_p(3, p_base + 1); + outb_p(mode_num | 8, p_base + 2); + outb_p((drv_num | 0xa) << 4, p_base + 6); + outb_p(0xef, p_base + 7); + for (i = 0; (i < 100) && (inb (p_base + 7) & 0x80); i++) + udelay(10000); +} + +/* + * Set a specific pio_mode for a drive + */ + +static void cmd640_set_mode(ide_drive_t* drive, int pio_mode) { + int interface_number; + int drive_number; + int clock_time; /* ns */ + int mc_time, av_time, ds_time; + + interface_number = HWIF(drive)->index; + drive_number = drive->select.b.unit; + clock_time = 1000/bus_speed; + + mc_time = pio_timings[pio_mode].mc_time; + av_time = pio_timings[pio_mode].av_time; + ds_time = pio_timings[pio_mode].ds_time; + + cmd640_timings_to_clocks(mc_time, av_time, ds_time, clock_time, + interface_number*2 + drive_number); + set_pio_mode(interface_number, drive_number, pio_mode); + cmd640_set_timing(interface_number, drive_number); +} + +/* + * Drive PIO mode "autoconfiguration". + * Ideally, this code should *always* call cmd640_set_mode(), but it doesn't. + */ + +static void cmd640_tune_drive(ide_drive_t *drive, byte pio_mode) { + int interface_number; + int drive_number; + int clock_time; /* ns */ + int max_pio; + int mc_time, av_time, ds_time; + struct hd_driveid* id; + int readahead; /* there is a global named read_ahead */ + + if (pio_mode != 255) { + cmd640_set_mode(drive, pio_mode); + return; + } + + interface_number = HWIF(drive)->index; + drive_number = drive->select.b.unit; + clock_time = 1000/bus_speed; + id = drive->id; + if ((max_pio = ide_scan_pio_blacklist(id->model)) != -1) { + ds_time = pio_timings[max_pio].ds_time; + } else { + max_pio = id->tPIO; + ds_time = pio_timings[max_pio].ds_time; + if (id->field_valid & 2) { + if ((id->capability & 8) && (id->eide_pio_modes & 7)) { + if (id->eide_pio_modes & 4) max_pio = 5; + else if (id->eide_pio_modes & 2) max_pio = 4; + else max_pio = 3; + ds_time = id->eide_pio_iordy; + } else { + ds_time = id->eide_pio; + } + if (ds_time == 0) + ds_time = pio_timings[max_pio].ds_time; + } + + /* + * Conservative "downgrade" + */ + if (max_pio < 4 && max_pio != 0) { + max_pio -= 1; + ds_time = pio_timings[max_pio].ds_time; + } + } + mc_time = pio_timings[max_pio].mc_time; + av_time = pio_timings[max_pio].av_time; + cmd640_timings_to_clocks(mc_time, av_time, ds_time, clock_time, + interface_number*2 + drive_number); + set_pio_mode(interface_number, drive_number, max_pio); + cmd640_set_timing(interface_number, drive_number); + + /* + * Disable (or set) readahead mode + */ + + readahead = 0; + if (cmd640_chip_version > 1) { /* Mmmm.. probably should be > 2 ?? */ + readahead = known_drive_readahead(id->model); + if (readahead == -1) + readahead = 1; /* Mmmm.. probably be 0 ?? */ + set_readahead_mode(readahead, interface_number, drive_number); + } + + printk ("Mode and Timing set to PIO%d, Readahead is %s\n", + max_pio, readahead ? "enabled" : "disabled"); +} + |