18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * CS5536 PATA support 48c2ecf20Sopenharmony_ci * (C) 2007 Martin K. Petersen <mkp@mkp.net> 58c2ecf20Sopenharmony_ci * (C) 2009 Bartlomiej Zolnierkiewicz 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Documentation: 88c2ecf20Sopenharmony_ci * Available from AMD web site. 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * The IDE timing registers for the CS5536 live in the Geode Machine 118c2ecf20Sopenharmony_ci * Specific Register file and not PCI config space. Most BIOSes 128c2ecf20Sopenharmony_ci * virtualize the PCI registers so the chip looks like a standard IDE 138c2ecf20Sopenharmony_ci * controller. Unfortunately not all implementations get this right. 148c2ecf20Sopenharmony_ci * In particular some have problems with unaligned accesses to the 158c2ecf20Sopenharmony_ci * virtualized PCI registers. This driver always does full dword 168c2ecf20Sopenharmony_ci * writes to work around the issue. Also, in case of a bad BIOS this 178c2ecf20Sopenharmony_ci * driver can be loaded with the "msr=1" parameter which forces using 188c2ecf20Sopenharmony_ci * the Machine Specific Registers to configure the device. 198c2ecf20Sopenharmony_ci */ 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <linux/kernel.h> 228c2ecf20Sopenharmony_ci#include <linux/module.h> 238c2ecf20Sopenharmony_ci#include <linux/pci.h> 248c2ecf20Sopenharmony_ci#include <linux/init.h> 258c2ecf20Sopenharmony_ci#include <linux/ide.h> 268c2ecf20Sopenharmony_ci#include <asm/msr.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define DRV_NAME "cs5536" 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cienum { 318c2ecf20Sopenharmony_ci MSR_IDE_CFG = 0x51300010, 328c2ecf20Sopenharmony_ci PCI_IDE_CFG = 0x40, 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci CFG = 0, 358c2ecf20Sopenharmony_ci DTC = 2, 368c2ecf20Sopenharmony_ci CAST = 3, 378c2ecf20Sopenharmony_ci ETC = 4, 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci IDE_CFG_CHANEN = (1 << 1), 408c2ecf20Sopenharmony_ci IDE_CFG_CABLE = (1 << 17) | (1 << 16), 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci IDE_D0_SHIFT = 24, 438c2ecf20Sopenharmony_ci IDE_D1_SHIFT = 16, 448c2ecf20Sopenharmony_ci IDE_DRV_MASK = 0xff, 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci IDE_CAST_D0_SHIFT = 6, 478c2ecf20Sopenharmony_ci IDE_CAST_D1_SHIFT = 4, 488c2ecf20Sopenharmony_ci IDE_CAST_DRV_MASK = 0x3, 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci IDE_CAST_CMD_SHIFT = 24, 518c2ecf20Sopenharmony_ci IDE_CAST_CMD_MASK = 0xff, 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci IDE_ETC_UDMA_MASK = 0xc0, 548c2ecf20Sopenharmony_ci}; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic int use_msr; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic int cs5536_read(struct pci_dev *pdev, int reg, u32 *val) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci if (unlikely(use_msr)) { 618c2ecf20Sopenharmony_ci u32 dummy; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci rdmsr(MSR_IDE_CFG + reg, *val, dummy); 648c2ecf20Sopenharmony_ci return 0; 658c2ecf20Sopenharmony_ci } 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci return pci_read_config_dword(pdev, PCI_IDE_CFG + reg * 4, val); 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic int cs5536_write(struct pci_dev *pdev, int reg, int val) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci if (unlikely(use_msr)) { 738c2ecf20Sopenharmony_ci wrmsr(MSR_IDE_CFG + reg, val, 0); 748c2ecf20Sopenharmony_ci return 0; 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci return pci_write_config_dword(pdev, PCI_IDE_CFG + reg * 4, val); 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic void cs5536_program_dtc(ide_drive_t *drive, u8 tim) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(drive->hwif->dev); 838c2ecf20Sopenharmony_ci int dshift = (drive->dn & 1) ? IDE_D1_SHIFT : IDE_D0_SHIFT; 848c2ecf20Sopenharmony_ci u32 dtc; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci cs5536_read(pdev, DTC, &dtc); 878c2ecf20Sopenharmony_ci dtc &= ~(IDE_DRV_MASK << dshift); 888c2ecf20Sopenharmony_ci dtc |= tim << dshift; 898c2ecf20Sopenharmony_ci cs5536_write(pdev, DTC, dtc); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci/** 938c2ecf20Sopenharmony_ci * cs5536_cable_detect - detect cable type 948c2ecf20Sopenharmony_ci * @hwif: Port to detect on 958c2ecf20Sopenharmony_ci * 968c2ecf20Sopenharmony_ci * Perform cable detection for ATA66 capable cable. 978c2ecf20Sopenharmony_ci * 988c2ecf20Sopenharmony_ci * Returns a cable type. 998c2ecf20Sopenharmony_ci */ 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic u8 cs5536_cable_detect(ide_hwif_t *hwif) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(hwif->dev); 1048c2ecf20Sopenharmony_ci u32 cfg; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci cs5536_read(pdev, CFG, &cfg); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci if (cfg & IDE_CFG_CABLE) 1098c2ecf20Sopenharmony_ci return ATA_CBL_PATA80; 1108c2ecf20Sopenharmony_ci else 1118c2ecf20Sopenharmony_ci return ATA_CBL_PATA40; 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci/** 1158c2ecf20Sopenharmony_ci * cs5536_set_pio_mode - PIO timing setup 1168c2ecf20Sopenharmony_ci * @hwif: ATA port 1178c2ecf20Sopenharmony_ci * @drive: ATA device 1188c2ecf20Sopenharmony_ci */ 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic void cs5536_set_pio_mode(ide_hwif_t *hwif, ide_drive_t *drive) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci static const u8 drv_timings[5] = { 1238c2ecf20Sopenharmony_ci 0x98, 0x55, 0x32, 0x21, 0x20, 1248c2ecf20Sopenharmony_ci }; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci static const u8 addr_timings[5] = { 1278c2ecf20Sopenharmony_ci 0x2, 0x1, 0x0, 0x0, 0x0, 1288c2ecf20Sopenharmony_ci }; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci static const u8 cmd_timings[5] = { 1318c2ecf20Sopenharmony_ci 0x99, 0x92, 0x90, 0x22, 0x20, 1328c2ecf20Sopenharmony_ci }; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(hwif->dev); 1358c2ecf20Sopenharmony_ci ide_drive_t *pair = ide_get_pair_dev(drive); 1368c2ecf20Sopenharmony_ci int cshift = (drive->dn & 1) ? IDE_CAST_D1_SHIFT : IDE_CAST_D0_SHIFT; 1378c2ecf20Sopenharmony_ci unsigned long timings = (unsigned long)ide_get_drivedata(drive); 1388c2ecf20Sopenharmony_ci u32 cast; 1398c2ecf20Sopenharmony_ci const u8 pio = drive->pio_mode - XFER_PIO_0; 1408c2ecf20Sopenharmony_ci u8 cmd_pio = pio; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci if (pair) 1438c2ecf20Sopenharmony_ci cmd_pio = min_t(u8, pio, pair->pio_mode - XFER_PIO_0); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci timings &= (IDE_DRV_MASK << 8); 1468c2ecf20Sopenharmony_ci timings |= drv_timings[pio]; 1478c2ecf20Sopenharmony_ci ide_set_drivedata(drive, (void *)timings); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci cs5536_program_dtc(drive, drv_timings[pio]); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci cs5536_read(pdev, CAST, &cast); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci cast &= ~(IDE_CAST_DRV_MASK << cshift); 1548c2ecf20Sopenharmony_ci cast |= addr_timings[pio] << cshift; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci cast &= ~(IDE_CAST_CMD_MASK << IDE_CAST_CMD_SHIFT); 1578c2ecf20Sopenharmony_ci cast |= cmd_timings[cmd_pio] << IDE_CAST_CMD_SHIFT; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci cs5536_write(pdev, CAST, cast); 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci/** 1638c2ecf20Sopenharmony_ci * cs5536_set_dma_mode - DMA timing setup 1648c2ecf20Sopenharmony_ci * @hwif: ATA port 1658c2ecf20Sopenharmony_ci * @drive: ATA device 1668c2ecf20Sopenharmony_ci */ 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic void cs5536_set_dma_mode(ide_hwif_t *hwif, ide_drive_t *drive) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci static const u8 udma_timings[6] = { 1718c2ecf20Sopenharmony_ci 0xc2, 0xc1, 0xc0, 0xc4, 0xc5, 0xc6, 1728c2ecf20Sopenharmony_ci }; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci static const u8 mwdma_timings[3] = { 1758c2ecf20Sopenharmony_ci 0x67, 0x21, 0x20, 1768c2ecf20Sopenharmony_ci }; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(hwif->dev); 1798c2ecf20Sopenharmony_ci int dshift = (drive->dn & 1) ? IDE_D1_SHIFT : IDE_D0_SHIFT; 1808c2ecf20Sopenharmony_ci unsigned long timings = (unsigned long)ide_get_drivedata(drive); 1818c2ecf20Sopenharmony_ci u32 etc; 1828c2ecf20Sopenharmony_ci const u8 mode = drive->dma_mode; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci cs5536_read(pdev, ETC, &etc); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (mode >= XFER_UDMA_0) { 1878c2ecf20Sopenharmony_ci etc &= ~(IDE_DRV_MASK << dshift); 1888c2ecf20Sopenharmony_ci etc |= udma_timings[mode - XFER_UDMA_0] << dshift; 1898c2ecf20Sopenharmony_ci } else { /* MWDMA */ 1908c2ecf20Sopenharmony_ci etc &= ~(IDE_ETC_UDMA_MASK << dshift); 1918c2ecf20Sopenharmony_ci timings &= IDE_DRV_MASK; 1928c2ecf20Sopenharmony_ci timings |= mwdma_timings[mode - XFER_MW_DMA_0] << 8; 1938c2ecf20Sopenharmony_ci ide_set_drivedata(drive, (void *)timings); 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci cs5536_write(pdev, ETC, etc); 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic void cs5536_dma_start(ide_drive_t *drive) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci unsigned long timings = (unsigned long)ide_get_drivedata(drive); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci if (drive->current_speed < XFER_UDMA_0 && 2048c2ecf20Sopenharmony_ci (timings >> 8) != (timings & IDE_DRV_MASK)) 2058c2ecf20Sopenharmony_ci cs5536_program_dtc(drive, timings >> 8); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci ide_dma_start(drive); 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cistatic int cs5536_dma_end(ide_drive_t *drive) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci int ret = ide_dma_end(drive); 2138c2ecf20Sopenharmony_ci unsigned long timings = (unsigned long)ide_get_drivedata(drive); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci if (drive->current_speed < XFER_UDMA_0 && 2168c2ecf20Sopenharmony_ci (timings >> 8) != (timings & IDE_DRV_MASK)) 2178c2ecf20Sopenharmony_ci cs5536_program_dtc(drive, timings & IDE_DRV_MASK); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci return ret; 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic const struct ide_port_ops cs5536_port_ops = { 2238c2ecf20Sopenharmony_ci .set_pio_mode = cs5536_set_pio_mode, 2248c2ecf20Sopenharmony_ci .set_dma_mode = cs5536_set_dma_mode, 2258c2ecf20Sopenharmony_ci .cable_detect = cs5536_cable_detect, 2268c2ecf20Sopenharmony_ci}; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic const struct ide_dma_ops cs5536_dma_ops = { 2298c2ecf20Sopenharmony_ci .dma_host_set = ide_dma_host_set, 2308c2ecf20Sopenharmony_ci .dma_setup = ide_dma_setup, 2318c2ecf20Sopenharmony_ci .dma_start = cs5536_dma_start, 2328c2ecf20Sopenharmony_ci .dma_end = cs5536_dma_end, 2338c2ecf20Sopenharmony_ci .dma_test_irq = ide_dma_test_irq, 2348c2ecf20Sopenharmony_ci .dma_lost_irq = ide_dma_lost_irq, 2358c2ecf20Sopenharmony_ci .dma_timer_expiry = ide_dma_sff_timer_expiry, 2368c2ecf20Sopenharmony_ci .dma_sff_read_status = ide_dma_sff_read_status, 2378c2ecf20Sopenharmony_ci}; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cistatic const struct ide_port_info cs5536_info = { 2408c2ecf20Sopenharmony_ci .name = DRV_NAME, 2418c2ecf20Sopenharmony_ci .port_ops = &cs5536_port_ops, 2428c2ecf20Sopenharmony_ci .dma_ops = &cs5536_dma_ops, 2438c2ecf20Sopenharmony_ci .host_flags = IDE_HFLAG_SINGLE, 2448c2ecf20Sopenharmony_ci .pio_mask = ATA_PIO4, 2458c2ecf20Sopenharmony_ci .mwdma_mask = ATA_MWDMA2, 2468c2ecf20Sopenharmony_ci .udma_mask = ATA_UDMA5, 2478c2ecf20Sopenharmony_ci}; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci/** 2508c2ecf20Sopenharmony_ci * cs5536_init_one 2518c2ecf20Sopenharmony_ci * @dev: PCI device 2528c2ecf20Sopenharmony_ci * @id: Entry in match table 2538c2ecf20Sopenharmony_ci */ 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic int cs5536_init_one(struct pci_dev *dev, const struct pci_device_id *id) 2568c2ecf20Sopenharmony_ci{ 2578c2ecf20Sopenharmony_ci u32 cfg; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci if (use_msr) 2608c2ecf20Sopenharmony_ci printk(KERN_INFO DRV_NAME ": Using MSR regs instead of PCI\n"); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci cs5536_read(dev, CFG, &cfg); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci if ((cfg & IDE_CFG_CHANEN) == 0) { 2658c2ecf20Sopenharmony_ci printk(KERN_ERR DRV_NAME ": disabled by BIOS\n"); 2668c2ecf20Sopenharmony_ci return -ENODEV; 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci return ide_pci_init_one(dev, &cs5536_info, NULL); 2708c2ecf20Sopenharmony_ci} 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic const struct pci_device_id cs5536_pci_tbl[] = { 2738c2ecf20Sopenharmony_ci { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_CS5536_IDE), }, 2748c2ecf20Sopenharmony_ci { }, 2758c2ecf20Sopenharmony_ci}; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_cistatic struct pci_driver cs5536_pci_driver = { 2788c2ecf20Sopenharmony_ci .name = DRV_NAME, 2798c2ecf20Sopenharmony_ci .id_table = cs5536_pci_tbl, 2808c2ecf20Sopenharmony_ci .probe = cs5536_init_one, 2818c2ecf20Sopenharmony_ci .remove = ide_pci_remove, 2828c2ecf20Sopenharmony_ci .suspend = ide_pci_suspend, 2838c2ecf20Sopenharmony_ci .resume = ide_pci_resume, 2848c2ecf20Sopenharmony_ci}; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cimodule_pci_driver(cs5536_pci_driver); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ciMODULE_AUTHOR("Martin K. Petersen, Bartlomiej Zolnierkiewicz"); 2898c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("low-level driver for the CS5536 IDE controller"); 2908c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2918c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, cs5536_pci_tbl); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cimodule_param_named(msr, use_msr, int, 0644); 2948c2ecf20Sopenharmony_ciMODULE_PARM_DESC(msr, "Force using MSR to configure IDE function (Default: 0)"); 295