18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 1996 Linus Torvalds & author (see below) 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci/* 78c2ecf20Sopenharmony_ci * ALI M14xx chipset EIDE controller 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Works for ALI M1439/1443/1445/1487/1489 chipsets. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * Adapted from code developed by derekn@vw.ece.cmu.edu. -ml 128c2ecf20Sopenharmony_ci * Derek's notes follow: 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * I think the code should be pretty understandable, 158c2ecf20Sopenharmony_ci * but I'll be happy to (try to) answer questions. 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * The critical part is in the setupDrive function. The initRegisters 188c2ecf20Sopenharmony_ci * function doesn't seem to be necessary, but the DOS driver does it, so 198c2ecf20Sopenharmony_ci * I threw it in. 208c2ecf20Sopenharmony_ci * 218c2ecf20Sopenharmony_ci * I've only tested this on my system, which only has one disk. I posted 228c2ecf20Sopenharmony_ci * it to comp.sys.linux.hardware, so maybe some other people will try it 238c2ecf20Sopenharmony_ci * out. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * Derek Noonburg (derekn@ece.cmu.edu) 268c2ecf20Sopenharmony_ci * 95-sep-26 278c2ecf20Sopenharmony_ci * 288c2ecf20Sopenharmony_ci * Update 96-jul-13: 298c2ecf20Sopenharmony_ci * 308c2ecf20Sopenharmony_ci * I've since upgraded to two disks and a CD-ROM, with no trouble, and 318c2ecf20Sopenharmony_ci * I've also heard from several others who have used it successfully. 328c2ecf20Sopenharmony_ci * This driver appears to work with both the 1443/1445 and the 1487/1489 338c2ecf20Sopenharmony_ci * chipsets. I've added support for PIO mode 4 for the 1487. This 348c2ecf20Sopenharmony_ci * seems to work just fine on the 1443 also, although I'm not sure it's 358c2ecf20Sopenharmony_ci * advertised as supporting mode 4. (I've been running a WDC AC21200 in 368c2ecf20Sopenharmony_ci * mode 4 for a while now with no trouble.) -Derek 378c2ecf20Sopenharmony_ci */ 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci#include <linux/module.h> 408c2ecf20Sopenharmony_ci#include <linux/types.h> 418c2ecf20Sopenharmony_ci#include <linux/kernel.h> 428c2ecf20Sopenharmony_ci#include <linux/delay.h> 438c2ecf20Sopenharmony_ci#include <linux/timer.h> 448c2ecf20Sopenharmony_ci#include <linux/mm.h> 458c2ecf20Sopenharmony_ci#include <linux/ioport.h> 468c2ecf20Sopenharmony_ci#include <linux/blkdev.h> 478c2ecf20Sopenharmony_ci#include <linux/ide.h> 488c2ecf20Sopenharmony_ci#include <linux/init.h> 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci#include <asm/io.h> 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#define DRV_NAME "ali14xx" 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* port addresses for auto-detection */ 558c2ecf20Sopenharmony_ci#define ALI_NUM_PORTS 4 568c2ecf20Sopenharmony_cistatic const int ports[ALI_NUM_PORTS] __initconst = 578c2ecf20Sopenharmony_ci { 0x074, 0x0f4, 0x034, 0x0e4 }; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci/* register initialization data */ 608c2ecf20Sopenharmony_citypedef struct { u8 reg, data; } RegInitializer; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic const RegInitializer initData[] __initconst = { 638c2ecf20Sopenharmony_ci {0x01, 0x0f}, {0x02, 0x00}, {0x03, 0x00}, {0x04, 0x00}, 648c2ecf20Sopenharmony_ci {0x05, 0x00}, {0x06, 0x00}, {0x07, 0x2b}, {0x0a, 0x0f}, 658c2ecf20Sopenharmony_ci {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, {0x28, 0x00}, 668c2ecf20Sopenharmony_ci {0x29, 0x00}, {0x2a, 0x00}, {0x2f, 0x00}, {0x2b, 0x00}, 678c2ecf20Sopenharmony_ci {0x2c, 0x00}, {0x2d, 0x00}, {0x2e, 0x00}, {0x30, 0x00}, 688c2ecf20Sopenharmony_ci {0x31, 0x00}, {0x32, 0x00}, {0x33, 0x00}, {0x34, 0xff}, 698c2ecf20Sopenharmony_ci {0x35, 0x03}, {0x00, 0x00} 708c2ecf20Sopenharmony_ci}; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci/* timing parameter registers for each drive */ 738c2ecf20Sopenharmony_cistatic struct { u8 reg1, reg2, reg3, reg4; } regTab[4] = { 748c2ecf20Sopenharmony_ci {0x03, 0x26, 0x04, 0x27}, /* drive 0 */ 758c2ecf20Sopenharmony_ci {0x05, 0x28, 0x06, 0x29}, /* drive 1 */ 768c2ecf20Sopenharmony_ci {0x2b, 0x30, 0x2c, 0x31}, /* drive 2 */ 778c2ecf20Sopenharmony_ci {0x2d, 0x32, 0x2e, 0x33}, /* drive 3 */ 788c2ecf20Sopenharmony_ci}; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic int basePort; /* base port address */ 818c2ecf20Sopenharmony_cistatic int regPort; /* port for register number */ 828c2ecf20Sopenharmony_cistatic int dataPort; /* port for register data */ 838c2ecf20Sopenharmony_cistatic u8 regOn; /* output to base port to access registers */ 848c2ecf20Sopenharmony_cistatic u8 regOff; /* output to base port to close registers */ 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci/*------------------------------------------------------------------------*/ 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci/* 898c2ecf20Sopenharmony_ci * Read a controller register. 908c2ecf20Sopenharmony_ci */ 918c2ecf20Sopenharmony_cistatic inline u8 inReg(u8 reg) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci outb_p(reg, regPort); 948c2ecf20Sopenharmony_ci return inb(dataPort); 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci/* 988c2ecf20Sopenharmony_ci * Write a controller register. 998c2ecf20Sopenharmony_ci */ 1008c2ecf20Sopenharmony_cistatic void outReg(u8 data, u8 reg) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci outb_p(reg, regPort); 1038c2ecf20Sopenharmony_ci outb_p(data, dataPort); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(ali14xx_lock); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci/* 1098c2ecf20Sopenharmony_ci * Set PIO mode for the specified drive. 1108c2ecf20Sopenharmony_ci * This function computes timing parameters 1118c2ecf20Sopenharmony_ci * and sets controller registers accordingly. 1128c2ecf20Sopenharmony_ci */ 1138c2ecf20Sopenharmony_cistatic void ali14xx_set_pio_mode(ide_hwif_t *hwif, ide_drive_t *drive) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci int driveNum; 1168c2ecf20Sopenharmony_ci int time1, time2; 1178c2ecf20Sopenharmony_ci u8 param1, param2, param3, param4; 1188c2ecf20Sopenharmony_ci unsigned long flags; 1198c2ecf20Sopenharmony_ci int bus_speed = ide_vlb_clk ? ide_vlb_clk : 50; 1208c2ecf20Sopenharmony_ci const u8 pio = drive->pio_mode - XFER_PIO_0; 1218c2ecf20Sopenharmony_ci struct ide_timing *t = ide_timing_find_mode(XFER_PIO_0 + pio); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* calculate timing, according to PIO mode */ 1248c2ecf20Sopenharmony_ci time1 = ide_pio_cycle_time(drive, pio); 1258c2ecf20Sopenharmony_ci time2 = t->active; 1268c2ecf20Sopenharmony_ci param3 = param1 = (time2 * bus_speed + 999) / 1000; 1278c2ecf20Sopenharmony_ci param4 = param2 = (time1 * bus_speed + 999) / 1000 - param1; 1288c2ecf20Sopenharmony_ci if (pio < 3) { 1298c2ecf20Sopenharmony_ci param3 += 8; 1308c2ecf20Sopenharmony_ci param4 += 8; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s: PIO mode%d, t1=%dns, t2=%dns, cycles = %d+%d, %d+%d\n", 1338c2ecf20Sopenharmony_ci drive->name, pio, time1, time2, param1, param2, param3, param4); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* stuff timing parameters into controller registers */ 1368c2ecf20Sopenharmony_ci driveNum = (drive->hwif->index << 1) + (drive->dn & 1); 1378c2ecf20Sopenharmony_ci spin_lock_irqsave(&ali14xx_lock, flags); 1388c2ecf20Sopenharmony_ci outb_p(regOn, basePort); 1398c2ecf20Sopenharmony_ci outReg(param1, regTab[driveNum].reg1); 1408c2ecf20Sopenharmony_ci outReg(param2, regTab[driveNum].reg2); 1418c2ecf20Sopenharmony_ci outReg(param3, regTab[driveNum].reg3); 1428c2ecf20Sopenharmony_ci outReg(param4, regTab[driveNum].reg4); 1438c2ecf20Sopenharmony_ci outb_p(regOff, basePort); 1448c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ali14xx_lock, flags); 1458c2ecf20Sopenharmony_ci} 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci/* 1488c2ecf20Sopenharmony_ci * Auto-detect the IDE controller port. 1498c2ecf20Sopenharmony_ci */ 1508c2ecf20Sopenharmony_cistatic int __init findPort(void) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci int i; 1538c2ecf20Sopenharmony_ci u8 t; 1548c2ecf20Sopenharmony_ci unsigned long flags; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci local_irq_save(flags); 1578c2ecf20Sopenharmony_ci for (i = 0; i < ALI_NUM_PORTS; ++i) { 1588c2ecf20Sopenharmony_ci basePort = ports[i]; 1598c2ecf20Sopenharmony_ci regOff = inb(basePort); 1608c2ecf20Sopenharmony_ci for (regOn = 0x30; regOn <= 0x33; ++regOn) { 1618c2ecf20Sopenharmony_ci outb_p(regOn, basePort); 1628c2ecf20Sopenharmony_ci if (inb(basePort) == regOn) { 1638c2ecf20Sopenharmony_ci regPort = basePort + 4; 1648c2ecf20Sopenharmony_ci dataPort = basePort + 8; 1658c2ecf20Sopenharmony_ci t = inReg(0) & 0xf0; 1668c2ecf20Sopenharmony_ci outb_p(regOff, basePort); 1678c2ecf20Sopenharmony_ci local_irq_restore(flags); 1688c2ecf20Sopenharmony_ci if (t != 0x50) 1698c2ecf20Sopenharmony_ci return 0; 1708c2ecf20Sopenharmony_ci return 1; /* success */ 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci outb_p(regOff, basePort); 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci local_irq_restore(flags); 1768c2ecf20Sopenharmony_ci return 0; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci/* 1808c2ecf20Sopenharmony_ci * Initialize controller registers with default values. 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_cistatic int __init initRegisters(void) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci const RegInitializer *p; 1858c2ecf20Sopenharmony_ci u8 t; 1868c2ecf20Sopenharmony_ci unsigned long flags; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci local_irq_save(flags); 1898c2ecf20Sopenharmony_ci outb_p(regOn, basePort); 1908c2ecf20Sopenharmony_ci for (p = initData; p->reg != 0; ++p) 1918c2ecf20Sopenharmony_ci outReg(p->data, p->reg); 1928c2ecf20Sopenharmony_ci outb_p(0x01, regPort); 1938c2ecf20Sopenharmony_ci t = inb(regPort) & 0x01; 1948c2ecf20Sopenharmony_ci outb_p(regOff, basePort); 1958c2ecf20Sopenharmony_ci local_irq_restore(flags); 1968c2ecf20Sopenharmony_ci return t; 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic const struct ide_port_ops ali14xx_port_ops = { 2008c2ecf20Sopenharmony_ci .set_pio_mode = ali14xx_set_pio_mode, 2018c2ecf20Sopenharmony_ci}; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic const struct ide_port_info ali14xx_port_info = { 2048c2ecf20Sopenharmony_ci .name = DRV_NAME, 2058c2ecf20Sopenharmony_ci .chipset = ide_ali14xx, 2068c2ecf20Sopenharmony_ci .port_ops = &ali14xx_port_ops, 2078c2ecf20Sopenharmony_ci .host_flags = IDE_HFLAG_NO_DMA, 2088c2ecf20Sopenharmony_ci .pio_mask = ATA_PIO4, 2098c2ecf20Sopenharmony_ci}; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic int __init ali14xx_probe(void) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci printk(KERN_DEBUG "ali14xx: base=0x%03x, regOn=0x%02x.\n", 2148c2ecf20Sopenharmony_ci basePort, regOn); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci /* initialize controller registers */ 2178c2ecf20Sopenharmony_ci if (!initRegisters()) { 2188c2ecf20Sopenharmony_ci printk(KERN_ERR "ali14xx: Chip initialization failed.\n"); 2198c2ecf20Sopenharmony_ci return 1; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci return ide_legacy_device_add(&ali14xx_port_info, 0); 2238c2ecf20Sopenharmony_ci} 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cistatic bool probe_ali14xx; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cimodule_param_named(probe, probe_ali14xx, bool, 0); 2288c2ecf20Sopenharmony_ciMODULE_PARM_DESC(probe, "probe for ALI M14xx chipsets"); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cistatic int __init ali14xx_init(void) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci if (probe_ali14xx == 0) 2338c2ecf20Sopenharmony_ci goto out; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* auto-detect IDE controller port */ 2368c2ecf20Sopenharmony_ci if (findPort()) { 2378c2ecf20Sopenharmony_ci if (ali14xx_probe()) 2388c2ecf20Sopenharmony_ci return -ENODEV; 2398c2ecf20Sopenharmony_ci return 0; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci printk(KERN_ERR "ali14xx: not found.\n"); 2428c2ecf20Sopenharmony_ciout: 2438c2ecf20Sopenharmony_ci return -ENODEV; 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cimodule_init(ali14xx_init); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ciMODULE_AUTHOR("see local file"); 2498c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("support of ALI 14XX IDE chipsets"); 2508c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 251