162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * pata_cs5536.c - CS5536 PATA for new ATA layer 462306a36Sopenharmony_ci * (C) 2007 Martin K. Petersen <mkp@mkp.net> 562306a36Sopenharmony_ci * (C) 2011 Bartlomiej Zolnierkiewicz 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Documentation: 862306a36Sopenharmony_ci * Available from AMD web site. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * The IDE timing registers for the CS5536 live in the Geode Machine 1162306a36Sopenharmony_ci * Specific Register file and not PCI config space. Most BIOSes 1262306a36Sopenharmony_ci * virtualize the PCI registers so the chip looks like a standard IDE 1362306a36Sopenharmony_ci * controller. Unfortunately not all implementations get this right. 1462306a36Sopenharmony_ci * In particular some have problems with unaligned accesses to the 1562306a36Sopenharmony_ci * virtualized PCI registers. This driver always does full dword 1662306a36Sopenharmony_ci * writes to work around the issue. Also, in case of a bad BIOS this 1762306a36Sopenharmony_ci * driver can be loaded with the "msr=1" parameter which forces using 1862306a36Sopenharmony_ci * the Machine Specific Registers to configure the device. 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include <linux/kernel.h> 2262306a36Sopenharmony_ci#include <linux/module.h> 2362306a36Sopenharmony_ci#include <linux/pci.h> 2462306a36Sopenharmony_ci#include <linux/blkdev.h> 2562306a36Sopenharmony_ci#include <linux/delay.h> 2662306a36Sopenharmony_ci#include <linux/libata.h> 2762306a36Sopenharmony_ci#include <scsi/scsi_host.h> 2862306a36Sopenharmony_ci#include <linux/dmi.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#ifdef CONFIG_X86_32 3162306a36Sopenharmony_ci#include <asm/msr.h> 3262306a36Sopenharmony_cistatic int use_msr; 3362306a36Sopenharmony_cimodule_param_named(msr, use_msr, int, 0644); 3462306a36Sopenharmony_ciMODULE_PARM_DESC(msr, "Force using MSR to configure IDE function (Default: 0)"); 3562306a36Sopenharmony_ci#else 3662306a36Sopenharmony_ci#undef rdmsr /* avoid accidental MSR usage on, e.g. x86-64 */ 3762306a36Sopenharmony_ci#undef wrmsr 3862306a36Sopenharmony_ci#define rdmsr(x, y, z) do { } while (0) 3962306a36Sopenharmony_ci#define wrmsr(x, y, z) do { } while (0) 4062306a36Sopenharmony_ci#define use_msr 0 4162306a36Sopenharmony_ci#endif 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#define DRV_NAME "pata_cs5536" 4462306a36Sopenharmony_ci#define DRV_VERSION "0.0.8" 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cienum { 4762306a36Sopenharmony_ci MSR_IDE_CFG = 0x51300010, 4862306a36Sopenharmony_ci PCI_IDE_CFG = 0x40, 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci CFG = 0, 5162306a36Sopenharmony_ci DTC = 2, 5262306a36Sopenharmony_ci CAST = 3, 5362306a36Sopenharmony_ci ETC = 4, 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci IDE_CFG_CHANEN = (1 << 1), 5662306a36Sopenharmony_ci IDE_CFG_CABLE = (1 << 17) | (1 << 16), 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci IDE_D0_SHIFT = 24, 5962306a36Sopenharmony_ci IDE_D1_SHIFT = 16, 6062306a36Sopenharmony_ci IDE_DRV_MASK = 0xff, 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci IDE_CAST_D0_SHIFT = 6, 6362306a36Sopenharmony_ci IDE_CAST_D1_SHIFT = 4, 6462306a36Sopenharmony_ci IDE_CAST_DRV_MASK = 0x3, 6562306a36Sopenharmony_ci IDE_CAST_CMD_MASK = 0xff, 6662306a36Sopenharmony_ci IDE_CAST_CMD_SHIFT = 24, 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci IDE_ETC_UDMA_MASK = 0xc0, 6962306a36Sopenharmony_ci}; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci/* Some Bachmann OT200 devices have a non working UDMA support due a 7262306a36Sopenharmony_ci * missing resistor. 7362306a36Sopenharmony_ci */ 7462306a36Sopenharmony_cistatic const struct dmi_system_id udma_quirk_dmi_table[] = { 7562306a36Sopenharmony_ci { 7662306a36Sopenharmony_ci .ident = "Bachmann electronic OT200", 7762306a36Sopenharmony_ci .matches = { 7862306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Bachmann electronic"), 7962306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "OT200"), 8062306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_VERSION, "1") 8162306a36Sopenharmony_ci }, 8262306a36Sopenharmony_ci }, 8362306a36Sopenharmony_ci { } 8462306a36Sopenharmony_ci}; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int cs5536_read(struct pci_dev *pdev, int reg, u32 *val) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci if (unlikely(use_msr)) { 8962306a36Sopenharmony_ci u32 dummy __maybe_unused; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci rdmsr(MSR_IDE_CFG + reg, *val, dummy); 9262306a36Sopenharmony_ci return 0; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return pci_read_config_dword(pdev, PCI_IDE_CFG + reg * 4, val); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic int cs5536_write(struct pci_dev *pdev, int reg, int val) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci if (unlikely(use_msr)) { 10162306a36Sopenharmony_ci wrmsr(MSR_IDE_CFG + reg, val, 0); 10262306a36Sopenharmony_ci return 0; 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci return pci_write_config_dword(pdev, PCI_IDE_CFG + reg * 4, val); 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic void cs5536_program_dtc(struct ata_device *adev, u8 tim) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(adev->link->ap->host->dev); 11162306a36Sopenharmony_ci int dshift = adev->devno ? IDE_D1_SHIFT : IDE_D0_SHIFT; 11262306a36Sopenharmony_ci u32 dtc; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci cs5536_read(pdev, DTC, &dtc); 11562306a36Sopenharmony_ci dtc &= ~(IDE_DRV_MASK << dshift); 11662306a36Sopenharmony_ci dtc |= tim << dshift; 11762306a36Sopenharmony_ci cs5536_write(pdev, DTC, dtc); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci/** 12162306a36Sopenharmony_ci * cs5536_cable_detect - detect cable type 12262306a36Sopenharmony_ci * @ap: Port to detect on 12362306a36Sopenharmony_ci * 12462306a36Sopenharmony_ci * Perform cable detection for ATA66 capable cable. 12562306a36Sopenharmony_ci * 12662306a36Sopenharmony_ci * Returns a cable type. 12762306a36Sopenharmony_ci */ 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic int cs5536_cable_detect(struct ata_port *ap) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(ap->host->dev); 13262306a36Sopenharmony_ci u32 cfg; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci cs5536_read(pdev, CFG, &cfg); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (cfg & IDE_CFG_CABLE) 13762306a36Sopenharmony_ci return ATA_CBL_PATA80; 13862306a36Sopenharmony_ci else 13962306a36Sopenharmony_ci return ATA_CBL_PATA40; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci/** 14362306a36Sopenharmony_ci * cs5536_set_piomode - PIO setup 14462306a36Sopenharmony_ci * @ap: ATA interface 14562306a36Sopenharmony_ci * @adev: device on the interface 14662306a36Sopenharmony_ci */ 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic void cs5536_set_piomode(struct ata_port *ap, struct ata_device *adev) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci static const u8 drv_timings[5] = { 15162306a36Sopenharmony_ci 0x98, 0x55, 0x32, 0x21, 0x20, 15262306a36Sopenharmony_ci }; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci static const u8 addr_timings[5] = { 15562306a36Sopenharmony_ci 0x2, 0x1, 0x0, 0x0, 0x0, 15662306a36Sopenharmony_ci }; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci static const u8 cmd_timings[5] = { 15962306a36Sopenharmony_ci 0x99, 0x92, 0x90, 0x22, 0x20, 16062306a36Sopenharmony_ci }; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(ap->host->dev); 16362306a36Sopenharmony_ci struct ata_device *pair = ata_dev_pair(adev); 16462306a36Sopenharmony_ci int mode = adev->pio_mode - XFER_PIO_0; 16562306a36Sopenharmony_ci int cmdmode = mode; 16662306a36Sopenharmony_ci int cshift = adev->devno ? IDE_CAST_D1_SHIFT : IDE_CAST_D0_SHIFT; 16762306a36Sopenharmony_ci u32 cast; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci if (pair) 17062306a36Sopenharmony_ci cmdmode = min(mode, pair->pio_mode - XFER_PIO_0); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci cs5536_program_dtc(adev, drv_timings[mode]); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci cs5536_read(pdev, CAST, &cast); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci cast &= ~(IDE_CAST_DRV_MASK << cshift); 17762306a36Sopenharmony_ci cast |= addr_timings[mode] << cshift; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci cast &= ~(IDE_CAST_CMD_MASK << IDE_CAST_CMD_SHIFT); 18062306a36Sopenharmony_ci cast |= cmd_timings[cmdmode] << IDE_CAST_CMD_SHIFT; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci cs5536_write(pdev, CAST, cast); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci/** 18662306a36Sopenharmony_ci * cs5536_set_dmamode - DMA timing setup 18762306a36Sopenharmony_ci * @ap: ATA interface 18862306a36Sopenharmony_ci * @adev: Device being configured 18962306a36Sopenharmony_ci * 19062306a36Sopenharmony_ci */ 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic void cs5536_set_dmamode(struct ata_port *ap, struct ata_device *adev) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci static const u8 udma_timings[6] = { 19562306a36Sopenharmony_ci 0xc2, 0xc1, 0xc0, 0xc4, 0xc5, 0xc6, 19662306a36Sopenharmony_ci }; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci static const u8 mwdma_timings[3] = { 19962306a36Sopenharmony_ci 0x67, 0x21, 0x20, 20062306a36Sopenharmony_ci }; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(ap->host->dev); 20362306a36Sopenharmony_ci u32 etc; 20462306a36Sopenharmony_ci int mode = adev->dma_mode; 20562306a36Sopenharmony_ci int dshift = adev->devno ? IDE_D1_SHIFT : IDE_D0_SHIFT; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci cs5536_read(pdev, ETC, &etc); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci if (mode >= XFER_UDMA_0) { 21062306a36Sopenharmony_ci etc &= ~(IDE_DRV_MASK << dshift); 21162306a36Sopenharmony_ci etc |= udma_timings[mode - XFER_UDMA_0] << dshift; 21262306a36Sopenharmony_ci } else { /* MWDMA */ 21362306a36Sopenharmony_ci etc &= ~(IDE_ETC_UDMA_MASK << dshift); 21462306a36Sopenharmony_ci cs5536_program_dtc(adev, mwdma_timings[mode - XFER_MW_DMA_0]); 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci cs5536_write(pdev, ETC, etc); 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic const struct scsi_host_template cs5536_sht = { 22162306a36Sopenharmony_ci ATA_BMDMA_SHT(DRV_NAME), 22262306a36Sopenharmony_ci}; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic struct ata_port_operations cs5536_port_ops = { 22562306a36Sopenharmony_ci .inherits = &ata_bmdma32_port_ops, 22662306a36Sopenharmony_ci .cable_detect = cs5536_cable_detect, 22762306a36Sopenharmony_ci .set_piomode = cs5536_set_piomode, 22862306a36Sopenharmony_ci .set_dmamode = cs5536_set_dmamode, 22962306a36Sopenharmony_ci}; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci/** 23262306a36Sopenharmony_ci * cs5536_init_one 23362306a36Sopenharmony_ci * @dev: PCI device 23462306a36Sopenharmony_ci * @id: Entry in match table 23562306a36Sopenharmony_ci * 23662306a36Sopenharmony_ci */ 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic int cs5536_init_one(struct pci_dev *dev, const struct pci_device_id *id) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci static const struct ata_port_info info = { 24162306a36Sopenharmony_ci .flags = ATA_FLAG_SLAVE_POSS, 24262306a36Sopenharmony_ci .pio_mask = ATA_PIO4, 24362306a36Sopenharmony_ci .mwdma_mask = ATA_MWDMA2, 24462306a36Sopenharmony_ci .udma_mask = ATA_UDMA5, 24562306a36Sopenharmony_ci .port_ops = &cs5536_port_ops, 24662306a36Sopenharmony_ci }; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci static const struct ata_port_info no_udma_info = { 24962306a36Sopenharmony_ci .flags = ATA_FLAG_SLAVE_POSS, 25062306a36Sopenharmony_ci .pio_mask = ATA_PIO4, 25162306a36Sopenharmony_ci .port_ops = &cs5536_port_ops, 25262306a36Sopenharmony_ci }; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci const struct ata_port_info *ppi[2]; 25662306a36Sopenharmony_ci u32 cfg; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (dmi_check_system(udma_quirk_dmi_table)) 25962306a36Sopenharmony_ci ppi[0] = &no_udma_info; 26062306a36Sopenharmony_ci else 26162306a36Sopenharmony_ci ppi[0] = &info; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci ppi[1] = &ata_dummy_port_info; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci if (use_msr) 26662306a36Sopenharmony_ci dev_err(&dev->dev, DRV_NAME ": Using MSR regs instead of PCI\n"); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci cs5536_read(dev, CFG, &cfg); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci if ((cfg & IDE_CFG_CHANEN) == 0) { 27162306a36Sopenharmony_ci dev_err(&dev->dev, DRV_NAME ": disabled by BIOS\n"); 27262306a36Sopenharmony_ci return -ENODEV; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci return ata_pci_bmdma_init_one(dev, ppi, &cs5536_sht, NULL, 0); 27662306a36Sopenharmony_ci} 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_cistatic const struct pci_device_id cs5536[] = { 27962306a36Sopenharmony_ci { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_CS5536_IDE), }, 28062306a36Sopenharmony_ci { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_CS5536_DEV_IDE), }, 28162306a36Sopenharmony_ci { }, 28262306a36Sopenharmony_ci}; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic struct pci_driver cs5536_pci_driver = { 28562306a36Sopenharmony_ci .name = DRV_NAME, 28662306a36Sopenharmony_ci .id_table = cs5536, 28762306a36Sopenharmony_ci .probe = cs5536_init_one, 28862306a36Sopenharmony_ci .remove = ata_pci_remove_one, 28962306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 29062306a36Sopenharmony_ci .suspend = ata_pci_device_suspend, 29162306a36Sopenharmony_ci .resume = ata_pci_device_resume, 29262306a36Sopenharmony_ci#endif 29362306a36Sopenharmony_ci}; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cimodule_pci_driver(cs5536_pci_driver); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ciMODULE_AUTHOR("Martin K. Petersen"); 29862306a36Sopenharmony_ciMODULE_DESCRIPTION("low-level driver for the CS5536 IDE controller"); 29962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 30062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, cs5536); 30162306a36Sopenharmony_ciMODULE_VERSION(DRV_VERSION); 302