android_kernel_xiaomi_sm8350/drivers/ide/legacy/qd65xx.c
Bartlomiej Zolnierkiewicz 849138827c ide: make legacy IDE VLB modules check for the "probe" kernel params (v2)
Legacy IDE VLB host drivers didn't check for "probe" options when compiled
as modules, which was obviously wrong as we don't want module to poke at
random I/O ports by simply loading it.  Fix it by adding "probe" module param
to legacy IDE VLB host drivers.

v2:
* don't obsolete old "ide0=dtc2278/ht6560b/qd65xx/ali14xx/umc8672"
  IDE driver options yet (per Alan Cox's request) and enhance documentation

Signed-off-by: Bartlomiej Zolnierkiewicz <bzolnier@gmail.com>
2007-03-03 17:48:55 +01:00

517 lines
13 KiB
C

/*
* 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 "hda=autotune hdb=autotune"
* To enable 2nd channel tuning (qd6580 only), use "hdc=autotune hdd=autotune"
*/
/*
* Rewritten from the work of Colten Edwards <pje120@cs.usask.ca> by
* Samuel Thibault <samuel.thibault@fnac.net>
*/
#include <linux/module.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;
}
int probe_qd65xx = 0;
module_param_named(probe, probe_qd65xx, bool, 0);
MODULE_PARM_DESC(probe, "probe for QD65xx chipsets");
/* Can be called directly from ide.c. */
int __init qd65xx_init(void)
{
if (probe_qd65xx == 0)
return -ENODEV;
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");