2007-02-07 20:18:45 +03:00
/*
* Copyright ( C ) 2002 Toshiba Corporation
* Copyright ( C ) 2005 - 2006 MontaVista Software , Inc . < source @ mvista . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/types.h>
# include <linux/pci.h>
# include <linux/ide.h>
2011-07-03 21:41:29 +04:00
# include <linux/module.h>
2007-02-07 20:18:45 +03:00
2008-07-25 00:53:32 +04:00
# define DRV_NAME "tc86c001"
2008-07-25 00:53:17 +04:00
2010-01-19 12:45:29 +03:00
static void tc86c001_set_mode ( ide_hwif_t * hwif , ide_drive_t * drive )
2007-02-07 20:18:45 +03:00
{
unsigned long scr_port = hwif - > config_data + ( drive - > dn ? 0x02 : 0x00 ) ;
2007-10-19 02:30:08 +04:00
u16 mode , scr = inw ( scr_port ) ;
2010-01-19 12:45:29 +03:00
const u8 speed = drive - > dma_mode ;
2007-02-07 20:18:45 +03:00
switch ( speed ) {
2008-04-26 19:36:39 +04:00
case XFER_UDMA_4 : mode = 0x00c0 ; break ;
case XFER_UDMA_3 : mode = 0x00b0 ; break ;
case XFER_UDMA_2 : mode = 0x00a0 ; break ;
case XFER_UDMA_1 : mode = 0x0090 ; break ;
case XFER_UDMA_0 : mode = 0x0080 ; break ;
case XFER_MW_DMA_2 : mode = 0x0070 ; break ;
case XFER_MW_DMA_1 : mode = 0x0060 ; break ;
case XFER_MW_DMA_0 : mode = 0x0050 ; break ;
case XFER_PIO_4 : mode = 0x0400 ; break ;
case XFER_PIO_3 : mode = 0x0300 ; break ;
case XFER_PIO_2 : mode = 0x0200 ; break ;
case XFER_PIO_1 : mode = 0x0100 ; break ;
case XFER_PIO_0 :
default : mode = 0x0000 ; break ;
2007-02-07 20:18:45 +03:00
}
scr & = ( speed < XFER_MW_DMA_0 ) ? 0xf8ff : 0xff0f ;
scr | = mode ;
2007-02-17 04:40:25 +03:00
outw ( scr , scr_port ) ;
2007-02-07 20:18:45 +03:00
}
2010-01-19 12:44:41 +03:00
static void tc86c001_set_pio_mode ( ide_hwif_t * hwif , ide_drive_t * drive )
2007-02-07 20:18:45 +03:00
{
2010-01-19 12:45:29 +03:00
drive - > dma_mode = drive - > pio_mode ;
tc86c001_set_mode ( hwif , drive ) ;
2007-02-07 20:18:45 +03:00
}
/*
* HACKITY HACK
*
* This is a workaround for the limitation 5 of the TC86C001 IDE controller :
* if a DMA transfer terminates prematurely , the controller leaves the device ' s
* interrupt request ( INTRQ ) pending and does not generate a PCI interrupt ( or
* set the interrupt bit in the DMA status register ) , thus no PCI interrupt
* will occur until a DMA transfer has been successfully completed .
*
* We work around this by initiating dummy , zero - length DMA transfer on
* a DMA timeout expiration . I found no better way to do this with the current
* IDE core than to temporarily replace a higher level driver ' s timer expiry
* handler with our own backing up to that handler in case our recovery fails .
*/
static int tc86c001_timer_expiry ( ide_drive_t * drive )
{
2009-01-06 19:20:52 +03:00
ide_hwif_t * hwif = drive - > hwif ;
2007-02-07 20:18:45 +03:00
ide_expiry_t * expiry = ide_get_hwifdata ( hwif ) ;
2008-07-23 21:55:51 +04:00
u8 dma_stat = inb ( hwif - > dma_base + ATA_DMA_STATUS ) ;
2007-02-07 20:18:45 +03:00
/* Restore a higher level driver's expiry handler first. */
2009-01-06 19:20:50 +03:00
hwif - > expiry = expiry ;
2007-02-07 20:18:45 +03:00
if ( ( dma_stat & 5 ) = = 1 ) { /* DMA active and no interrupt */
unsigned long sc_base = hwif - > config_data ;
unsigned long twcr_port = sc_base + ( drive - > dn ? 0x06 : 0x04 ) ;
2008-07-23 21:55:51 +04:00
u8 dma_cmd = inb ( hwif - > dma_base + ATA_DMA_CMD ) ;
2007-02-07 20:18:45 +03:00
printk ( KERN_WARNING " %s: DMA interrupt possibly stuck, "
" attempting recovery... \n " , drive - > name ) ;
/* Stop DMA */
2008-07-23 21:55:51 +04:00
outb ( dma_cmd & ~ 0x01 , hwif - > dma_base + ATA_DMA_CMD ) ;
2007-02-07 20:18:45 +03:00
/* Setup the dummy DMA transfer */
2007-02-17 04:40:25 +03:00
outw ( 0 , sc_base + 0x0a ) ; /* Sector Count */
outw ( 0 , twcr_port ) ; /* Transfer Word Count 1 or 2 */
2007-02-07 20:18:45 +03:00
/* Start the dummy DMA transfer */
2008-07-23 21:55:51 +04:00
/* clear R_OR_WCTR for write */
outb ( 0x00 , hwif - > dma_base + ATA_DMA_CMD ) ;
/* set START_STOPBM */
outb ( 0x01 , hwif - > dma_base + ATA_DMA_CMD ) ;
2007-02-07 20:18:45 +03:00
/*
* If an interrupt was pending , it should come thru shortly .
* If not , a higher level driver ' s expiry handler should
* eventually cause some kind of recovery from the DMA stall .
*/
return WAIT_MIN_SLEEP ;
}
/* Chain to the restored expiry handler if DMA wasn't active. */
if ( likely ( expiry ! = NULL ) )
return expiry ( drive ) ;
/* If there was no handler, "emulate" that for ide_timer_expiry()... */
return - 1 ;
}
static void tc86c001_dma_start ( ide_drive_t * drive )
{
2009-01-06 19:20:52 +03:00
ide_hwif_t * hwif = drive - > hwif ;
2007-02-07 20:18:45 +03:00
unsigned long sc_base = hwif - > config_data ;
unsigned long twcr_port = sc_base + ( drive - > dn ? 0x06 : 0x04 ) ;
2009-05-07 17:24:40 +04:00
unsigned long nsectors = blk_rq_sectors ( hwif - > rq ) ;
2007-02-07 20:18:45 +03:00
/*
* We have to manually load the sector count and size into
* the appropriate system control registers for DMA to work
* with LBA48 and ATAPI devices . . .
*/
2007-02-17 04:40:25 +03:00
outw ( nsectors , sc_base + 0x0a ) ; /* Sector Count */
outw ( SECTOR_SIZE / 2 , twcr_port ) ; /* Transfer Word Count 1/2 */
2007-02-07 20:18:45 +03:00
/* Install our timeout expiry hook, saving the current handler... */
2009-01-06 19:20:50 +03:00
ide_set_hwifdata ( hwif , hwif - > expiry ) ;
hwif - > expiry = & tc86c001_timer_expiry ;
2007-02-07 20:18:45 +03:00
ide_dma_start ( drive ) ;
}
2008-08-05 20:17:04 +04:00
static u8 tc86c001_cable_detect ( ide_hwif_t * hwif )
2008-02-02 21:56:31 +03:00
{
struct pci_dev * dev = to_pci_dev ( hwif - > dev ) ;
unsigned long sc_base = pci_resource_start ( dev , 5 ) ;
u16 scr1 = inw ( sc_base + 0x00 ) ;
/*
* System Control 1 Register bit 13 ( PDIAGN ) :
* 0 = 80 - pin cable , 1 = 40 - pin cable
*/
return ( scr1 & 0x2000 ) ? ATA_CBL_PATA40 : ATA_CBL_PATA80 ;
}
2012-12-22 01:21:03 +04:00
static void init_hwif_tc86c001 ( ide_hwif_t * hwif )
2007-02-07 20:18:45 +03:00
{
2008-02-02 01:09:31 +03:00
struct pci_dev * dev = to_pci_dev ( hwif - > dev ) ;
unsigned long sc_base = pci_resource_start ( dev , 5 ) ;
2007-10-19 02:30:08 +04:00
u16 scr1 = inw ( sc_base + 0x00 ) ;
2007-02-07 20:18:45 +03:00
/* System Control 1 Register bit 15 (Soft Reset) set */
2007-02-17 04:40:25 +03:00
outw ( scr1 | 0x8000 , sc_base + 0x00 ) ;
2007-02-07 20:18:45 +03:00
/* System Control 1 Register bit 14 (FIFO Reset) set */
2007-02-17 04:40:25 +03:00
outw ( scr1 | 0x4000 , sc_base + 0x00 ) ;
2007-02-07 20:18:45 +03:00
/* System Control 1 Register: reset clear */
2007-02-17 04:40:25 +03:00
outw ( scr1 & ~ 0xc000 , sc_base + 0x00 ) ;
2007-02-07 20:18:45 +03:00
/* Store the system control register base for convenience... */
hwif - > config_data = sc_base ;
if ( ! hwif - > dma_base )
return ;
/*
* Sector Count Control Register bits 0 and 1 set :
* software sets Sector Count Register for master and slave device
*/
2007-02-17 04:40:25 +03:00
outw ( 0x0003 , sc_base + 0x0c ) ;
2007-02-07 20:18:45 +03:00
/* Sector Count Register limit */
hwif - > rqsize = 0xffff ;
}
2008-04-27 00:25:14 +04:00
static const struct ide_port_ops tc86c001_port_ops = {
. set_pio_mode = tc86c001_set_pio_mode ,
. set_dma_mode = tc86c001_set_mode ,
. cable_detect = tc86c001_cable_detect ,
} ;
2008-04-27 00:25:24 +04:00
static const struct ide_dma_ops tc86c001_dma_ops = {
. dma_host_set = ide_dma_host_set ,
. dma_setup = ide_dma_setup ,
2008-04-27 00:25:24 +04:00
. dma_start = tc86c001_dma_start ,
2008-10-13 23:39:46 +04:00
. dma_end = ide_dma_end ,
2008-04-27 00:25:24 +04:00
. dma_test_irq = ide_dma_test_irq ,
. dma_lost_irq = ide_dma_lost_irq ,
2009-03-27 14:46:47 +03:00
. dma_timer_expiry = ide_dma_sff_timer_expiry ,
2009-01-06 19:21:02 +03:00
. dma_sff_read_status = ide_dma_sff_read_status ,
2008-04-27 00:25:24 +04:00
} ;
2012-12-22 01:21:03 +04:00
static const struct ide_port_info tc86c001_chipset = {
2008-07-25 00:53:32 +04:00
. name = DRV_NAME ,
2007-02-07 20:18:45 +03:00
. init_hwif = init_hwif_tc86c001 ,
2008-04-27 00:25:14 +04:00
. port_ops = & tc86c001_port_ops ,
2008-04-27 00:25:24 +04:00
. dma_ops = & tc86c001_dma_ops ,
2008-07-23 21:55:56 +04:00
. host_flags = IDE_HFLAG_SINGLE | IDE_HFLAG_OFF_BOARD ,
2007-07-20 03:11:59 +04:00
. pio_mask = ATA_PIO4 ,
2007-10-19 02:30:07 +04:00
. mwdma_mask = ATA_MWDMA2 ,
. udma_mask = ATA_UDMA4 ,
2007-02-07 20:18:45 +03:00
} ;
2012-12-22 01:21:03 +04:00
static int tc86c001_init_one ( struct pci_dev * dev ,
const struct pci_device_id * id )
2007-02-07 20:18:45 +03:00
{
2008-07-25 00:53:17 +04:00
int rc ;
rc = pci_enable_device ( dev ) ;
if ( rc )
goto out ;
rc = pci_request_region ( dev , 5 , DRV_NAME ) ;
if ( rc ) {
printk ( KERN_ERR DRV_NAME " : system control regs already in use " ) ;
goto out_disable ;
}
rc = ide_pci_init_one ( dev , & tc86c001_chipset , NULL ) ;
if ( rc )
goto out_release ;
goto out ;
out_release :
pci_release_region ( dev , 5 ) ;
out_disable :
pci_disable_device ( dev ) ;
out :
return rc ;
2007-02-07 20:18:45 +03:00
}
2012-12-22 01:21:03 +04:00
static void tc86c001_remove ( struct pci_dev * dev )
2008-07-25 00:53:26 +04:00
{
ide_pci_remove ( dev ) ;
pci_release_region ( dev , 5 ) ;
pci_disable_device ( dev ) ;
}
2007-10-17 00:29:56 +04:00
static const struct pci_device_id tc86c001_pci_tbl [ ] = {
{ PCI_VDEVICE ( TOSHIBA_2 , PCI_DEVICE_ID_TOSHIBA_TC86C001_IDE ) , 0 } ,
2007-02-07 20:18:45 +03:00
{ 0 , }
} ;
MODULE_DEVICE_TABLE ( pci , tc86c001_pci_tbl ) ;
2008-10-13 23:39:41 +04:00
static struct pci_driver tc86c001_pci_driver = {
2007-02-07 20:18:45 +03:00
. name = " TC86C001 " ,
. id_table = tc86c001_pci_tbl ,
2008-07-25 00:53:26 +04:00
. probe = tc86c001_init_one ,
2012-12-22 01:21:03 +04:00
. remove = tc86c001_remove ,
2007-02-07 20:18:45 +03:00
} ;
2007-02-07 20:19:09 +03:00
static int __init tc86c001_ide_init ( void )
2007-02-07 20:18:45 +03:00
{
2008-10-13 23:39:41 +04:00
return ide_pci_register_driver ( & tc86c001_pci_driver ) ;
2007-02-07 20:18:45 +03:00
}
2008-07-25 00:53:26 +04:00
static void __exit tc86c001_ide_exit ( void )
{
2008-10-13 23:39:41 +04:00
pci_unregister_driver ( & tc86c001_pci_driver ) ;
2008-07-25 00:53:26 +04:00
}
2007-02-07 20:18:45 +03:00
module_init ( tc86c001_ide_init ) ;
2008-07-25 00:53:26 +04:00
module_exit ( tc86c001_ide_exit ) ;
2007-02-07 20:18:45 +03:00
MODULE_AUTHOR ( " MontaVista Software, Inc. <source@mvista.com> " ) ;
MODULE_DESCRIPTION ( " PCI driver module for TC86C001 IDE " ) ;
MODULE_LICENSE ( " GPL " ) ;