18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Copyright (C) 2002 Toshiba Corporation
38c2ecf20Sopenharmony_ci * Copyright (C) 2005-2006 MontaVista Software, Inc. <source@mvista.com>
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public
68c2ecf20Sopenharmony_ci * License version 2.  This program is licensed "as is" without any
78c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied.
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/types.h>
118c2ecf20Sopenharmony_ci#include <linux/pci.h>
128c2ecf20Sopenharmony_ci#include <linux/ide.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#define DRV_NAME "tc86c001"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistatic void tc86c001_set_mode(ide_hwif_t *hwif, ide_drive_t *drive)
188c2ecf20Sopenharmony_ci{
198c2ecf20Sopenharmony_ci	unsigned long scr_port	= hwif->config_data + (drive->dn ? 0x02 : 0x00);
208c2ecf20Sopenharmony_ci	u16 mode, scr		= inw(scr_port);
218c2ecf20Sopenharmony_ci	const u8 speed		= drive->dma_mode;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci	switch (speed) {
248c2ecf20Sopenharmony_ci	case XFER_UDMA_4:	mode = 0x00c0; break;
258c2ecf20Sopenharmony_ci	case XFER_UDMA_3:	mode = 0x00b0; break;
268c2ecf20Sopenharmony_ci	case XFER_UDMA_2:	mode = 0x00a0; break;
278c2ecf20Sopenharmony_ci	case XFER_UDMA_1:	mode = 0x0090; break;
288c2ecf20Sopenharmony_ci	case XFER_UDMA_0:	mode = 0x0080; break;
298c2ecf20Sopenharmony_ci	case XFER_MW_DMA_2:	mode = 0x0070; break;
308c2ecf20Sopenharmony_ci	case XFER_MW_DMA_1:	mode = 0x0060; break;
318c2ecf20Sopenharmony_ci	case XFER_MW_DMA_0:	mode = 0x0050; break;
328c2ecf20Sopenharmony_ci	case XFER_PIO_4:	mode = 0x0400; break;
338c2ecf20Sopenharmony_ci	case XFER_PIO_3:	mode = 0x0300; break;
348c2ecf20Sopenharmony_ci	case XFER_PIO_2:	mode = 0x0200; break;
358c2ecf20Sopenharmony_ci	case XFER_PIO_1:	mode = 0x0100; break;
368c2ecf20Sopenharmony_ci	case XFER_PIO_0:
378c2ecf20Sopenharmony_ci	default:		mode = 0x0000; break;
388c2ecf20Sopenharmony_ci	}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	scr &= (speed < XFER_MW_DMA_0) ? 0xf8ff : 0xff0f;
418c2ecf20Sopenharmony_ci	scr |= mode;
428c2ecf20Sopenharmony_ci	outw(scr, scr_port);
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic void tc86c001_set_pio_mode(ide_hwif_t *hwif, ide_drive_t *drive)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	drive->dma_mode = drive->pio_mode;
488c2ecf20Sopenharmony_ci	tc86c001_set_mode(hwif, drive);
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci/*
528c2ecf20Sopenharmony_ci * HACKITY HACK
538c2ecf20Sopenharmony_ci *
548c2ecf20Sopenharmony_ci * This is a workaround for the limitation 5 of the TC86C001 IDE controller:
558c2ecf20Sopenharmony_ci * if a DMA transfer terminates prematurely, the controller leaves the device's
568c2ecf20Sopenharmony_ci * interrupt request (INTRQ) pending and does not generate a PCI interrupt (or
578c2ecf20Sopenharmony_ci * set the interrupt bit in the DMA status register), thus no PCI interrupt
588c2ecf20Sopenharmony_ci * will occur until a DMA transfer has been successfully completed.
598c2ecf20Sopenharmony_ci *
608c2ecf20Sopenharmony_ci * We work around this by initiating dummy, zero-length DMA transfer on
618c2ecf20Sopenharmony_ci * a DMA timeout expiration. I found no better way to do this with the current
628c2ecf20Sopenharmony_ci * IDE core than to temporarily replace a higher level driver's timer expiry
638c2ecf20Sopenharmony_ci * handler with our own backing up to that handler in case our recovery fails.
648c2ecf20Sopenharmony_ci */
658c2ecf20Sopenharmony_cistatic int tc86c001_timer_expiry(ide_drive_t *drive)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	ide_hwif_t *hwif	= drive->hwif;
688c2ecf20Sopenharmony_ci	ide_expiry_t *expiry	= ide_get_hwifdata(hwif);
698c2ecf20Sopenharmony_ci	u8 dma_stat		= inb(hwif->dma_base + ATA_DMA_STATUS);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	/* Restore a higher level driver's expiry handler first. */
728c2ecf20Sopenharmony_ci	hwif->expiry = expiry;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	if ((dma_stat & 5) == 1) {	/* DMA active and no interrupt */
758c2ecf20Sopenharmony_ci		unsigned long sc_base	= hwif->config_data;
768c2ecf20Sopenharmony_ci		unsigned long twcr_port	= sc_base + (drive->dn ? 0x06 : 0x04);
778c2ecf20Sopenharmony_ci		u8 dma_cmd		= inb(hwif->dma_base + ATA_DMA_CMD);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci		printk(KERN_WARNING "%s: DMA interrupt possibly stuck, "
808c2ecf20Sopenharmony_ci		       "attempting recovery...\n", drive->name);
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci		/* Stop DMA */
838c2ecf20Sopenharmony_ci		outb(dma_cmd & ~0x01, hwif->dma_base + ATA_DMA_CMD);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci		/* Setup the dummy DMA transfer */
868c2ecf20Sopenharmony_ci		outw(0, sc_base + 0x0a);	/* Sector Count */
878c2ecf20Sopenharmony_ci		outw(0, twcr_port);	/* Transfer Word Count 1 or 2 */
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci		/* Start the dummy DMA transfer */
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci		/* clear R_OR_WCTR for write */
928c2ecf20Sopenharmony_ci		outb(0x00, hwif->dma_base + ATA_DMA_CMD);
938c2ecf20Sopenharmony_ci		/* set START_STOPBM */
948c2ecf20Sopenharmony_ci		outb(0x01, hwif->dma_base + ATA_DMA_CMD);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci		/*
978c2ecf20Sopenharmony_ci		 * If an interrupt was pending, it should come thru shortly.
988c2ecf20Sopenharmony_ci		 * If not, a higher level driver's expiry handler should
998c2ecf20Sopenharmony_ci		 * eventually cause some kind of recovery from the DMA stall.
1008c2ecf20Sopenharmony_ci		 */
1018c2ecf20Sopenharmony_ci		return WAIT_MIN_SLEEP;
1028c2ecf20Sopenharmony_ci	}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	/* Chain to the restored expiry handler if DMA wasn't active. */
1058c2ecf20Sopenharmony_ci	if (likely(expiry != NULL))
1068c2ecf20Sopenharmony_ci		return expiry(drive);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	/* If there was no handler, "emulate" that for ide_timer_expiry()... */
1098c2ecf20Sopenharmony_ci	return -1;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic void tc86c001_dma_start(ide_drive_t *drive)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	ide_hwif_t *hwif	= drive->hwif;
1158c2ecf20Sopenharmony_ci	unsigned long sc_base	= hwif->config_data;
1168c2ecf20Sopenharmony_ci	unsigned long twcr_port	= sc_base + (drive->dn ? 0x06 : 0x04);
1178c2ecf20Sopenharmony_ci	unsigned long nsectors	= blk_rq_sectors(hwif->rq);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	/*
1208c2ecf20Sopenharmony_ci	 * We have to manually load the sector count and size into
1218c2ecf20Sopenharmony_ci	 * the appropriate system control registers for DMA to work
1228c2ecf20Sopenharmony_ci	 * with LBA48 and ATAPI devices...
1238c2ecf20Sopenharmony_ci	 */
1248c2ecf20Sopenharmony_ci	outw(nsectors, sc_base + 0x0a);	/* Sector Count */
1258c2ecf20Sopenharmony_ci	outw(SECTOR_SIZE / 2, twcr_port); /* Transfer Word Count 1/2 */
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	/* Install our timeout expiry hook, saving the current handler... */
1288c2ecf20Sopenharmony_ci	ide_set_hwifdata(hwif, hwif->expiry);
1298c2ecf20Sopenharmony_ci	hwif->expiry = &tc86c001_timer_expiry;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	ide_dma_start(drive);
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic u8 tc86c001_cable_detect(ide_hwif_t *hwif)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	struct pci_dev *dev = to_pci_dev(hwif->dev);
1378c2ecf20Sopenharmony_ci	unsigned long sc_base = pci_resource_start(dev, 5);
1388c2ecf20Sopenharmony_ci	u16 scr1 = inw(sc_base + 0x00);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	/*
1418c2ecf20Sopenharmony_ci	 * System Control  1 Register bit 13 (PDIAGN):
1428c2ecf20Sopenharmony_ci	 * 0=80-pin cable, 1=40-pin cable
1438c2ecf20Sopenharmony_ci	 */
1448c2ecf20Sopenharmony_ci	return (scr1 & 0x2000) ? ATA_CBL_PATA40 : ATA_CBL_PATA80;
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_cistatic void init_hwif_tc86c001(ide_hwif_t *hwif)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	struct pci_dev *dev	= to_pci_dev(hwif->dev);
1508c2ecf20Sopenharmony_ci	unsigned long sc_base	= pci_resource_start(dev, 5);
1518c2ecf20Sopenharmony_ci	u16 scr1		= inw(sc_base + 0x00);
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	/* System Control 1 Register bit 15 (Soft Reset) set */
1548c2ecf20Sopenharmony_ci	outw(scr1 |  0x8000, sc_base + 0x00);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	/* System Control 1 Register bit 14 (FIFO Reset) set */
1578c2ecf20Sopenharmony_ci	outw(scr1 |  0x4000, sc_base + 0x00);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	/* System Control 1 Register: reset clear */
1608c2ecf20Sopenharmony_ci	outw(scr1 & ~0xc000, sc_base + 0x00);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	/* Store the system control register base for convenience... */
1638c2ecf20Sopenharmony_ci	hwif->config_data = sc_base;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	if (!hwif->dma_base)
1668c2ecf20Sopenharmony_ci		return;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	/*
1698c2ecf20Sopenharmony_ci	 * Sector Count Control Register bits 0 and 1 set:
1708c2ecf20Sopenharmony_ci	 * software sets Sector Count Register for master and slave device
1718c2ecf20Sopenharmony_ci	 */
1728c2ecf20Sopenharmony_ci	outw(0x0003, sc_base + 0x0c);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	/* Sector Count Register limit */
1758c2ecf20Sopenharmony_ci	hwif->rqsize	 = 0xffff;
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic const struct ide_port_ops tc86c001_port_ops = {
1798c2ecf20Sopenharmony_ci	.set_pio_mode		= tc86c001_set_pio_mode,
1808c2ecf20Sopenharmony_ci	.set_dma_mode		= tc86c001_set_mode,
1818c2ecf20Sopenharmony_ci	.cable_detect		= tc86c001_cable_detect,
1828c2ecf20Sopenharmony_ci};
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cistatic const struct ide_dma_ops tc86c001_dma_ops = {
1858c2ecf20Sopenharmony_ci	.dma_host_set		= ide_dma_host_set,
1868c2ecf20Sopenharmony_ci	.dma_setup		= ide_dma_setup,
1878c2ecf20Sopenharmony_ci	.dma_start		= tc86c001_dma_start,
1888c2ecf20Sopenharmony_ci	.dma_end		= ide_dma_end,
1898c2ecf20Sopenharmony_ci	.dma_test_irq		= ide_dma_test_irq,
1908c2ecf20Sopenharmony_ci	.dma_lost_irq		= ide_dma_lost_irq,
1918c2ecf20Sopenharmony_ci	.dma_timer_expiry	= ide_dma_sff_timer_expiry,
1928c2ecf20Sopenharmony_ci	.dma_sff_read_status	= ide_dma_sff_read_status,
1938c2ecf20Sopenharmony_ci};
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic const struct ide_port_info tc86c001_chipset = {
1968c2ecf20Sopenharmony_ci	.name		= DRV_NAME,
1978c2ecf20Sopenharmony_ci	.init_hwif	= init_hwif_tc86c001,
1988c2ecf20Sopenharmony_ci	.port_ops	= &tc86c001_port_ops,
1998c2ecf20Sopenharmony_ci	.dma_ops	= &tc86c001_dma_ops,
2008c2ecf20Sopenharmony_ci	.host_flags	= IDE_HFLAG_SINGLE | IDE_HFLAG_OFF_BOARD,
2018c2ecf20Sopenharmony_ci	.pio_mask	= ATA_PIO4,
2028c2ecf20Sopenharmony_ci	.mwdma_mask	= ATA_MWDMA2,
2038c2ecf20Sopenharmony_ci	.udma_mask	= ATA_UDMA4,
2048c2ecf20Sopenharmony_ci};
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cistatic int tc86c001_init_one(struct pci_dev *dev,
2078c2ecf20Sopenharmony_ci			     const struct pci_device_id *id)
2088c2ecf20Sopenharmony_ci{
2098c2ecf20Sopenharmony_ci	int rc;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	rc = pci_enable_device(dev);
2128c2ecf20Sopenharmony_ci	if (rc)
2138c2ecf20Sopenharmony_ci		goto out;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	rc = pci_request_region(dev, 5, DRV_NAME);
2168c2ecf20Sopenharmony_ci	if (rc) {
2178c2ecf20Sopenharmony_ci		printk(KERN_ERR DRV_NAME ": system control regs already in use");
2188c2ecf20Sopenharmony_ci		goto out_disable;
2198c2ecf20Sopenharmony_ci	}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	rc = ide_pci_init_one(dev, &tc86c001_chipset, NULL);
2228c2ecf20Sopenharmony_ci	if (rc)
2238c2ecf20Sopenharmony_ci		goto out_release;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	goto out;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ciout_release:
2288c2ecf20Sopenharmony_ci	pci_release_region(dev, 5);
2298c2ecf20Sopenharmony_ciout_disable:
2308c2ecf20Sopenharmony_ci	pci_disable_device(dev);
2318c2ecf20Sopenharmony_ciout:
2328c2ecf20Sopenharmony_ci	return rc;
2338c2ecf20Sopenharmony_ci}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_cistatic void tc86c001_remove(struct pci_dev *dev)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	ide_pci_remove(dev);
2388c2ecf20Sopenharmony_ci	pci_release_region(dev, 5);
2398c2ecf20Sopenharmony_ci	pci_disable_device(dev);
2408c2ecf20Sopenharmony_ci}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_cistatic const struct pci_device_id tc86c001_pci_tbl[] = {
2438c2ecf20Sopenharmony_ci	{ PCI_VDEVICE(TOSHIBA_2, PCI_DEVICE_ID_TOSHIBA_TC86C001_IDE), 0 },
2448c2ecf20Sopenharmony_ci	{ 0, }
2458c2ecf20Sopenharmony_ci};
2468c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, tc86c001_pci_tbl);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_cistatic struct pci_driver tc86c001_pci_driver = {
2498c2ecf20Sopenharmony_ci	.name		= "TC86C001",
2508c2ecf20Sopenharmony_ci	.id_table	= tc86c001_pci_tbl,
2518c2ecf20Sopenharmony_ci	.probe		= tc86c001_init_one,
2528c2ecf20Sopenharmony_ci	.remove		= tc86c001_remove,
2538c2ecf20Sopenharmony_ci};
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic int __init tc86c001_ide_init(void)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	return ide_pci_register_driver(&tc86c001_pci_driver);
2588c2ecf20Sopenharmony_ci}
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_cistatic void __exit tc86c001_ide_exit(void)
2618c2ecf20Sopenharmony_ci{
2628c2ecf20Sopenharmony_ci	pci_unregister_driver(&tc86c001_pci_driver);
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cimodule_init(tc86c001_ide_init);
2668c2ecf20Sopenharmony_cimodule_exit(tc86c001_ide_exit);
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ciMODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>");
2698c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PCI driver module for TC86C001 IDE");
2708c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
271