summaryrefslogtreecommitdiffstats
path: root/drivers/block/ht6560b.c
blob: a3e1229bca653ef0f4d863a697a89cec2bb58f48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
 *  linux/drivers/block/ht6580.c       Version 0.04  Mar 19, 1996
 *
 *  Copyright (C) 1995-1996  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
 *
 * 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.
 *
 * Port 0x3e6 bit 0x20 sets these timings on/off. If 0x20 bit is set
 * these timings are disabled.
 *
 * Mikko Ala-Fossi
 *
 * More notes:
 *
 * There's something still missing from the initialization code, though.
 * If I have booted to dos sometime after power on, I can get smaller
 * timing values working. Perhaps I could soft-ice the initialization.
 *
 * -=- malafoss@snakemail.hut.fi -=- searching the marvels of universe -=-
 */

#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"

/*
 * 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 stuff is courtesy of malafoss@snakemail.hut.fi
 *                          (or maf@nemesis.tky.hut.fi)
 *
 * At least one user has reported that this code can confuse the floppy
 * controller and/or driver -- perhaps this should be changed to use
 * a read-modify-write sequence, so as not to disturb other bits in the reg?
 */

/*
 * The special i/o-port that HT-6560B uses to select interfaces:
 */
#define HT_SELECT_PORT     0x3e6

/*
 * We don't know what all of the bits are for, but we *do* know about these:
 *	bit5 (0x20): "1" selects slower speed by disabling use of timing values
 *	bit0 (0x01): "1" selects second interface
 */
static byte ht6560b_selects [2][MAX_DRIVES] = {{0x3c,0x3c}, {0x3d,0x3d}};

/*
 * VLB ht6560b Timing values:
 *
 * Timing byte consists of
 *      High nibble:  Recovery Time  (rt)
 *           The valid values range from 2 to 15. The default is 15.
 *
 *      Low nibble:   Active 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.
 *
 * Here's an example to make it clearer:
 *
 * DOS:    DEVICE=C:\bin\HTIDE\HTIDE.SYS /D0=2,4 /D1=4,5 /D2=10,10 /D3=15,15
 * Linux:  byte ht6560b_timings [][] = {{0x24, 0x45}, {0xaa, 0xff}};
 *
 * Note: There are no ioctls to change these values directly,
 * but settings can be approximated as PIO modes, using "hdparm":
 *
 * rc.local:  hdparm -p3 /dev/hda -p2 /dev/hdb -p1 /dev/hdc -p0 /dev/hdd
 */

static byte ht6560b_timings [2][MAX_DRIVES] = {{0xff,0xff}, {0xff,0xff}};

static byte pio_to_timings[6] = {0xff, 0xaa, 0x45, 0x24, 0x13, 0x12};

/*
 * This routine is invoked from ide.c to prepare for access to a given drive.
 */
static void ht6560b_selectproc (ide_drive_t *drive)
{
	byte t;
	unsigned long flags;
	static byte current_select = 0;
	static byte current_timing = 0;
	byte select = ht6560b_selects[HWIF(drive)->index][drive->select.b.unit];
        byte timing = ht6560b_timings[HWIF(drive)->index][drive->select.b.unit];

	if (select != current_select || timing != current_timing) {
		current_select = select;
		current_timing = timing;
		__save_flags (flags);	/* local CPU only */
		__cli();		/* local CPU only */
		(void) inb(HT_SELECT_PORT);
		(void) inb(HT_SELECT_PORT);
		(void) inb(HT_SELECT_PORT);
		/*
		 * Note: input bits are reversed to output bits!!
		 */
		t = inb(HT_SELECT_PORT) ^ 0x3f;
		t &= (~0x21);
		t |= (current_select & 0x21);
		outb(t, HT_SELECT_PORT);
                /*
                 * Set timing for this drive:
                 */
                outb (timing, IDE_SELECT_REG);
                (void) inb (IDE_STATUS_REG);
		__restore_flags (flags);	/* local CPU only */
#ifdef DEBUG
		printk("ht6560b: %s: select=%#x timing=%#x\n", drive->name, t, timing);
#endif
	}
}

/*
 * Autodetection and initialization of ht6560b
 */
int try_to_init_ht6560b(void)
{
	byte orig_value;
	int i;

	/* Autodetect ht6560b */
	if ((orig_value=inb(HT_SELECT_PORT)) == 0xff)
		return 0;

	for (i=3;i>0;i--) {
		outb(0x00, HT_SELECT_PORT);
		if (!( (~inb(HT_SELECT_PORT)) & 0x3f )) {
			  outb(orig_value, HT_SELECT_PORT);
			  return 0;
		}
	}
	outb(0x00, HT_SELECT_PORT);
	if ((~inb(HT_SELECT_PORT))& 0x3f) {
		outb(orig_value, HT_SELECT_PORT);
		return 0;
	}
	/*
	 * Ht6560b autodetected:
	 *     reverse input bits to output bits
	 *     initialize bit1 to 0
	 */
	outb((orig_value ^ 0x3f) & 0xfd, HT_SELECT_PORT);

	printk("\nht6560b: detected and initialized");
	return 1;
}

static void tune_ht6560b (ide_drive_t *drive, byte pio)
{
	unsigned int hwif, unit;

	if (pio == 255)  {	/* auto-tune */
		if (drive->media != ide_disk)
			pio = 0; /* some CDROMs don't like fast modes (?) */
		else
			pio = ide_get_best_pio_mode(drive, pio, 5, NULL);
	}
	unit = drive->select.b.unit;
	hwif = HWIF(drive)->index;
	ht6560b_timings[hwif][unit] = pio_to_timings[pio];
	if (pio == 0)
		ht6560b_selects[hwif][unit] |= 0x20;
	else
		ht6560b_selects[hwif][unit] &= ~0x20;
}

void init_ht6560b (void)
{
	if (check_region(HT_SELECT_PORT,1)) {
		printk("\nht6560b: PORT 0x3e6 ALREADY IN USE\n");
	} else {
		if (try_to_init_ht6560b()) {
			request_region(HT_SELECT_PORT, 1, ide_hwifs[0].name);
			ide_hwifs[0].chipset = ide_ht6560b;
			ide_hwifs[1].chipset = ide_ht6560b;
			ide_hwifs[0].selectproc = &ht6560b_selectproc;
			ide_hwifs[1].selectproc = &ht6560b_selectproc;
			ide_hwifs[0].tuneproc = &tune_ht6560b;
			ide_hwifs[1].tuneproc = &tune_ht6560b;
			ide_hwifs[0].serialized = 1;
			ide_hwifs[1].serialized = 1;
			ide_hwifs[0].mate = &ide_hwifs[1];
			ide_hwifs[1].mate = &ide_hwifs[0];
			ide_hwifs[1].channel = 1;
		} else
			printk("\nht6560b: not found\n");
	}
}