2005-04-17 02:20:36 +04:00
/*
* 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
*
* To activate controller support , use " ide0=qd65xx "
*/
/*
* Rewritten from the work of Colten Edwards < pje120 @ cs . usask . ca > by
2009-02-03 15:12:58 +03:00
* Samuel Thibault < samuel . thibault @ ens - lyon . org >
2005-04-17 02:20:36 +04:00
*/
# 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/ide.h>
# include <linux/init.h>
# include <asm/system.h>
# include <asm/io.h>
2008-04-27 00:25:18 +04:00
# define DRV_NAME "qd65xx"
2005-04-17 02:20:36 +04:00
# 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 = < = 33 MHz ; 0 = > 33 MHz
* 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 */
/*
2008-04-26 19:36:42 +04:00
* qd65xx_select :
2005-04-17 02:20:36 +04:00
*
2008-04-26 19:36:42 +04:00
* This routine is invoked to prepare for access to a given drive .
2005-04-17 02:20:36 +04:00
*/
2009-03-31 22:15:32 +04:00
static void qd65xx_dev_select ( ide_drive_t * drive )
2005-04-17 02:20:36 +04:00
{
u8 index = ( ( ( QD_TIMREG ( drive ) ) & 0x80 ) > > 7 ) |
( QD_TIMREG ( drive ) & 0x02 ) ;
if ( timings [ index ] ! = QD_TIMING ( drive ) )
2007-10-20 02:32:37 +04:00
outb ( timings [ index ] = QD_TIMING ( drive ) , QD_TIMREG ( drive ) ) ;
2009-03-31 22:15:32 +04:00
outb ( drive - > select | ATA_DEVICE_OBS , drive - > hwif - > io_ports . device_addr ) ;
2005-04-17 02:20:36 +04:00
}
/*
* 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 )
{
2008-07-15 23:21:46 +04:00
int clk = ide_vlb_clk ? ide_vlb_clk : 50 ;
2008-04-27 17:38:29 +04:00
u8 act_cyc , rec_cyc ;
2005-04-17 02:20:36 +04:00
2008-04-27 17:38:29 +04:00
if ( clk < = 33 ) {
act_cyc = 9 - IDE_IN ( active_time * clk / 1000 + 1 , 2 , 9 ) ;
rec_cyc = 15 - IDE_IN ( recovery_time * clk / 1000 + 1 , 0 , 15 ) ;
2005-04-17 02:20:36 +04:00
} else {
2008-04-27 17:38:29 +04:00
act_cyc = 8 - IDE_IN ( active_time * clk / 1000 + 1 , 1 , 8 ) ;
rec_cyc = 18 - IDE_IN ( recovery_time * clk / 1000 + 1 , 3 , 18 ) ;
2005-04-17 02:20:36 +04:00
}
2008-04-27 17:38:29 +04:00
return ( rec_cyc < < 4 ) | 0x08 | act_cyc ;
2005-04-17 02:20:36 +04:00
}
/*
* qd6580_compute_timing
*
* idem for qd6580
*/
static u8 qd6580_compute_timing ( int active_time , int recovery_time )
{
2008-07-15 23:21:46 +04:00
int clk = ide_vlb_clk ? ide_vlb_clk : 50 ;
2008-04-27 17:38:29 +04:00
u8 act_cyc , rec_cyc ;
2005-04-17 02:20:36 +04:00
2008-04-27 17:38:29 +04:00
act_cyc = 17 - IDE_IN ( active_time * clk / 1000 + 1 , 2 , 17 ) ;
rec_cyc = 15 - IDE_IN ( recovery_time * clk / 1000 + 1 , 2 , 15 ) ;
return ( rec_cyc < < 4 ) | act_cyc ;
2005-04-17 02:20:36 +04:00
}
/*
* 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 ;
2008-10-11 00:39:19 +04:00
char * m = ( char * ) & drive - > id [ ATA_ID_PROD ] ;
char model [ ATA_ID_PROD_LEN ] ;
2005-04-17 02:20:36 +04:00
2008-10-11 00:39:19 +04:00
if ( * m = = 0 )
return 0 ;
2005-04-17 02:20:36 +04:00
2008-10-11 00:39:19 +04:00
strncpy ( model , m , ATA_ID_PROD_LEN ) ;
ide_fixstring ( model , ATA_ID_PROD_LEN , 1 ) ; /* byte-swap */
2005-04-17 02:20:36 +04:00
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_set_timing :
*
2008-04-26 19:36:42 +04:00
* records the timing
2005-04-17 02:20:36 +04:00
*/
static void qd_set_timing ( ide_drive_t * drive , u8 timing )
{
drive - > drive_data & = 0xff00 ;
drive - > drive_data | = timing ;
printk ( KERN_DEBUG " %s: %#x \n " , drive - > name , timing ) ;
}
2007-10-12 01:54:00 +04:00
static void qd6500_set_pio_mode ( ide_drive_t * drive , const u8 pio )
2005-04-17 02:20:36 +04:00
{
2008-10-11 00:39:19 +04:00
u16 * id = drive - > id ;
2005-04-17 02:20:36 +04:00
int active_time = 175 ;
int recovery_time = 415 ; /* worst case values from the dos driver */
2007-10-12 01:54:00 +04:00
/*
* FIXME : use " pio " value
*/
2008-10-11 00:39:19 +04:00
if ( ! qd_find_disk_type ( drive , & active_time , & recovery_time ) & &
2008-10-11 00:39:19 +04:00
( id [ ATA_ID_OLD_PIO_MODES ] & 0xff ) & & ( id [ ATA_ID_FIELD_VALID ] & 2 ) & &
2008-10-11 00:39:19 +04:00
id [ ATA_ID_EIDE_PIO ] > = 240 ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO " %s: PIO mode%d \n " , drive - > name ,
2008-10-11 00:39:19 +04:00
id [ ATA_ID_OLD_PIO_MODES ] & 0xff ) ;
2005-04-17 02:20:36 +04:00
active_time = 110 ;
2008-10-11 00:39:19 +04:00
recovery_time = drive - > id [ ATA_ID_EIDE_PIO ] - 120 ;
2005-04-17 02:20:36 +04:00
}
2009-01-06 19:20:52 +03:00
qd_set_timing ( drive , qd6500_compute_timing ( drive - > hwif ,
active_time , recovery_time ) ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-12 01:54:00 +04:00
static void qd6580_set_pio_mode ( ide_drive_t * drive , const u8 pio )
2005-04-17 02:20:36 +04:00
{
2008-04-26 19:36:42 +04:00
ide_hwif_t * hwif = drive - > hwif ;
2008-07-16 22:33:37 +04:00
struct ide_timing * t = ide_timing_find_mode ( XFER_PIO_0 + pio ) ;
2007-07-20 03:11:56 +04:00
unsigned int cycle_time ;
2005-04-17 02:20:36 +04:00
int active_time = 175 ;
int recovery_time = 415 ; /* worst case values from the dos driver */
2008-04-26 19:36:42 +04:00
u8 base = ( hwif - > config_data & 0xff00 ) > > 8 ;
2005-04-17 02:20:36 +04:00
if ( drive - > id & & ! qd_find_disk_type ( drive , & active_time , & recovery_time ) ) {
2007-07-20 03:11:56 +04:00
cycle_time = ide_pio_cycle_time ( drive , pio ) ;
2005-04-17 02:20:36 +04:00
switch ( pio ) {
case 0 : break ;
case 3 :
2007-07-20 03:11:56 +04:00
if ( cycle_time > = 110 ) {
2005-04-17 02:20:36 +04:00
active_time = 86 ;
2007-07-20 03:11:56 +04:00
recovery_time = cycle_time - 102 ;
2005-04-17 02:20:36 +04:00
} else
printk ( KERN_WARNING " %s: Strange recovery time ! \n " , drive - > name ) ;
break ;
case 4 :
2007-07-20 03:11:56 +04:00
if ( cycle_time > = 69 ) {
2005-04-17 02:20:36 +04:00
active_time = 70 ;
2007-07-20 03:11:56 +04:00
recovery_time = cycle_time - 61 ;
2005-04-17 02:20:36 +04:00
} else
printk ( KERN_WARNING " %s: Strange recovery time ! \n " , drive - > name ) ;
break ;
default :
2007-07-20 03:11:56 +04:00
if ( cycle_time > = 180 ) {
2005-04-17 02:20:36 +04:00
active_time = 110 ;
2007-07-20 03:11:56 +04:00
recovery_time = cycle_time - 120 ;
2005-04-17 02:20:36 +04:00
} else {
2008-07-16 22:33:37 +04:00
active_time = t - > active ;
2007-07-20 03:11:56 +04:00
recovery_time = cycle_time - active_time ;
2005-04-17 02:20:36 +04:00
}
}
printk ( KERN_INFO " %s: PIO mode%d \n " , drive - > name , pio ) ;
}
2009-01-06 19:20:52 +03:00
if ( ! hwif - > channel & & drive - > media ! = ide_disk ) {
2007-10-20 02:32:37 +04:00
outb ( 0x5f , QD_CONTROL_PORT ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_WARNING " %s: ATAPI: disabled read-ahead FIFO "
" and post-write buffer on %s. \n " ,
2009-01-06 19:20:52 +03:00
drive - > name , hwif - > name ) ;
2005-04-17 02:20:36 +04:00
}
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 )
{
unsigned long flags ;
2007-10-20 02:32:35 +04:00
u8 savereg , readreg ;
2005-04-17 02:20:36 +04:00
2007-10-20 02:32:37 +04:00
local_irq_save ( flags ) ;
2005-04-17 02:20:36 +04:00
savereg = inb_p ( port ) ;
outb_p ( QD_TESTVAL , port ) ; /* safe value */
readreg = inb_p ( port ) ;
outb ( savereg , port ) ;
2007-10-20 02:32:37 +04:00
local_irq_restore ( flags ) ;
2005-04-17 02:20:36 +04:00
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 ) ;
}
2008-07-16 22:33:42 +04:00
static void __init qd6500_init_dev ( ide_drive_t * drive )
2008-02-02 21:56:40 +03:00
{
2008-07-16 22:33:42 +04:00
ide_hwif_t * hwif = drive - > hwif ;
2008-04-26 19:36:42 +04:00
u8 base = ( hwif - > config_data & 0xff00 ) > > 8 ;
u8 config = QD_CONFIG ( hwif ) ;
2008-02-02 21:56:40 +03:00
2008-07-16 22:33:42 +04:00
drive - > drive_data = QD6500_DEF_DATA ;
2008-02-02 21:56:40 +03:00
}
2008-07-16 22:33:42 +04:00
static void __init qd6580_init_dev ( ide_drive_t * drive )
2008-02-02 21:56:40 +03:00
{
2008-07-16 22:33:42 +04:00
ide_hwif_t * hwif = drive - > hwif ;
2008-02-02 21:56:40 +03:00
u16 t1 , t2 ;
2008-04-26 19:36:42 +04:00
u8 base = ( hwif - > config_data & 0xff00 ) > > 8 ;
u8 config = QD_CONFIG ( hwif ) ;
2008-02-02 21:56:40 +03:00
2008-04-26 19:36:42 +04:00
if ( hwif - > host_flags & IDE_HFLAG_SINGLE ) {
2008-02-02 21:56:40 +03:00
t1 = QD6580_DEF_DATA ;
t2 = QD6580_DEF_DATA2 ;
} else
t2 = t1 = hwif - > channel ? QD6580_DEF_DATA2 : QD6580_DEF_DATA ;
2008-10-13 23:39:40 +04:00
drive - > drive_data = ( drive - > dn & 1 ) ? t2 : t1 ;
2005-04-17 02:20:36 +04:00
}
2009-03-31 22:15:32 +04:00
static const struct ide_tp_ops qd65xx_tp_ops = {
. exec_command = ide_exec_command ,
. read_status = ide_read_status ,
. read_altstatus = ide_read_altstatus ,
. write_devctl = ide_write_devctl ,
. dev_select = qd65xx_dev_select ,
. tf_load = ide_tf_load ,
. tf_read = ide_tf_read ,
. input_data = ide_input_data ,
. output_data = ide_output_data ,
} ;
2008-04-27 00:25:14 +04:00
static const struct ide_port_ops qd6500_port_ops = {
2008-07-16 22:33:42 +04:00
. init_dev = qd6500_init_dev ,
2008-04-27 00:25:14 +04:00
. set_pio_mode = qd6500_set_pio_mode ,
} ;
static const struct ide_port_ops qd6580_port_ops = {
2008-07-16 22:33:42 +04:00
. init_dev = qd6580_init_dev ,
2008-04-27 00:25:14 +04:00
. set_pio_mode = qd6580_set_pio_mode ,
} ;
2008-02-02 21:56:31 +03:00
static const struct ide_port_info qd65xx_port_info __initdata = {
2008-04-27 00:25:18 +04:00
. name = DRV_NAME ,
2009-03-31 22:15:32 +04:00
. tp_ops = & qd65xx_tp_ops ,
2008-02-02 21:56:31 +03:00
. chipset = ide_qd65xx ,
. host_flags = IDE_HFLAG_IO_32BIT |
2008-04-27 17:38:29 +04:00
IDE_HFLAG_NO_DMA ,
2008-02-02 21:56:31 +03:00
. pio_mask = ATA_PIO4 ,
} ;
2005-04-17 02:20:36 +04:00
/*
* 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 )
{
2008-04-27 00:25:16 +04:00
int rc ;
2008-04-27 00:25:16 +04:00
u8 config , unit , control ;
2008-04-26 19:36:36 +04:00
struct ide_port_info d = qd65xx_port_info ;
2005-04-17 02:20:36 +04:00
2007-10-20 02:32:37 +04:00
config = inb ( QD_CONFIG_PORT ) ;
2005-04-17 02:20:36 +04:00
if ( ! ( ( config & QD_CONFIG_BASEPORT ) > > 1 = = ( base = = 0xb0 ) ) )
2008-04-26 19:36:35 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
unit = ! ( config & QD_CONFIG_IDE_BASEPORT ) ;
2008-04-26 19:36:36 +04:00
if ( unit )
d . host_flags | = IDE_HFLAG_QD_2ND_PORT ;
2008-04-27 00:25:16 +04:00
switch ( config & 0xf0 ) {
case QD_CONFIG_QD6500 :
2008-04-26 19:36:35 +04:00
if ( qd_testreg ( base ) )
return - ENODEV ; /* bad register */
2005-04-17 02:20:36 +04:00
if ( config & QD_CONFIG_DISABLED ) {
printk ( KERN_WARNING " qd6500 is disabled ! \n " ) ;
2008-04-26 19:36:35 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
}
2008-04-26 19:36:36 +04:00
printk ( KERN_NOTICE " qd6500 at %#x \n " , base ) ;
printk ( KERN_DEBUG " qd6500: config=%#x, ID3=%u \n " ,
config , QD_ID3 ) ;
2008-04-27 00:25:14 +04:00
d . port_ops = & qd6500_port_ops ;
2008-04-26 19:36:42 +04:00
d . host_flags | = IDE_HFLAG_SINGLE ;
2008-04-27 00:25:16 +04:00
break ;
case QD_CONFIG_QD6580_A :
case QD_CONFIG_QD6580_B :
2008-04-26 19:36:35 +04:00
if ( qd_testreg ( base ) | | qd_testreg ( base + 0x02 ) )
return - ENODEV ; /* bad registers */
2005-04-17 02:20:36 +04:00
2007-10-20 02:32:37 +04:00
control = inb ( QD_CONTROL_PORT ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_NOTICE " qd6580 at %#x \n " , base ) ;
printk ( KERN_DEBUG " qd6580: config=%#x, control=%#x, ID3=%u \n " ,
config , control , QD_ID3 ) ;
2008-02-26 23:50:35 +03:00
outb ( QD_DEF_CONTR , QD_CONTROL_PORT ) ;
2008-04-27 00:25:14 +04:00
d . port_ops = & qd6580_port_ops ;
2008-04-27 00:25:16 +04:00
if ( control & QD_CONTR_SEC_DISABLED )
2008-04-26 19:36:42 +04:00
d . host_flags | = IDE_HFLAG_SINGLE ;
2008-04-27 00:25:16 +04:00
printk ( KERN_INFO " qd6580: %s IDE board \n " ,
( control & QD_CONTR_SEC_DISABLED ) ? " single " : " dual " ) ;
break ;
default :
return - ENODEV ;
}
2007-10-12 01:54:00 +04:00
2008-04-27 00:25:16 +04:00
rc = ide_legacy_device_add ( & d , ( base < < 8 ) | config ) ;
2008-04-26 19:36:36 +04:00
2008-04-27 00:25:16 +04:00
if ( d . host_flags & IDE_HFLAG_SINGLE )
return ( rc = = 0 ) ? 1 : rc ;
2005-04-17 02:20:36 +04:00
2008-04-27 00:25:16 +04:00
return rc ;
2005-04-17 02:20:36 +04:00
}
2008-04-27 17:38:24 +04:00
static int probe_qd65xx ;
2007-03-03 19:48:55 +03:00
module_param_named ( probe , probe_qd65xx , bool , 0 ) ;
MODULE_PARM_DESC ( probe , " probe for QD65xx chipsets " ) ;
2008-01-26 22:13:07 +03:00
static int __init qd65xx_init ( void )
2005-04-17 02:20:36 +04:00
{
2008-04-26 19:36:35 +04:00
int rc1 , rc2 = - ENODEV ;
2007-03-03 19:48:55 +03:00
if ( probe_qd65xx = = 0 )
return - ENODEV ;
2008-04-26 19:36:35 +04:00
rc1 = qd_probe ( 0x30 ) ;
if ( rc1 )
rc2 = qd_probe ( 0xb0 ) ;
if ( rc1 < 0 & & rc2 < 0 )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
2008-04-26 19:36:35 +04:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
module_init ( qd65xx_init ) ;
MODULE_AUTHOR ( " Samuel Thibault " ) ;
MODULE_DESCRIPTION ( " support of qd65xx vlb ide chipset " ) ;
MODULE_LICENSE ( " GPL " ) ;