summaryrefslogtreecommitdiff
path: root/i386/i386at/gpl/linux/block/cmd640.c
diff options
context:
space:
mode:
authorThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
committerThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
commitf07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch)
tree12b07c7e578fc1a5f53dbfde2632408491ff2a70 /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.c738
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");
+}
+