This is the tenth part of my Xilleon port. I am sending the full set of patches to linux-mips@xxxxxxxxxxxxxx which is archived at: http://www.linux-mips.org/archives/ Only the patches that touch generic parts of the kernel are coming here. This patch adds the Xilleon's IDE driver. Patch against 2.6.14-rc2 from linux-mips.org Signed-off-by: David Daney <ddaney@xxxxxxxxxx> Xilleon IDE controller support. --- commit 2a66e82b3d2b02aca88cc2f60286fba0c114139d tree af6a21de182519250b49d5b127342b969c022174 parent 162fee1af111103bb6e6275c2e51a550ff393cbc author David Daney <daney@xxxxxxxxxxxxxxxxxx> Tue, 04 Oct 2005 13:59:58 -0700 committer David Daney <daney@xxxxxxxxxxxxxxxxxx> Tue, 04 Oct 2005 13:59:58 -0700 drivers/ide/Kconfig | 7 drivers/ide/pci/Makefile | 1 drivers/ide/pci/xilleon.c | 637 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 645 insertions(+), 0 deletions(-) diff --git a/drivers/ide/Kconfig b/drivers/ide/Kconfig --- a/drivers/ide/Kconfig +++ b/drivers/ide/Kconfig @@ -740,6 +740,13 @@ config BLK_DEV_VIA82CXXX This allows the kernel to change PIO, DMA and UDMA speeds and to configure the chip to optimum performance. +config BLK_DEV_IDE_XILLEON + bool "Xilleon chipset support" + depends on ATI_XILLEON + help + This driver provides support for the Xilleon's on chip IDE + controller. + endif config BLK_DEV_IDE_PMAC diff --git a/drivers/ide/pci/Makefile b/drivers/ide/pci/Makefile --- a/drivers/ide/pci/Makefile +++ b/drivers/ide/pci/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_BLK_DEV_SLC90E66) += slc90 obj-$(CONFIG_BLK_DEV_TRIFLEX) += triflex.o obj-$(CONFIG_BLK_DEV_TRM290) += trm290.o obj-$(CONFIG_BLK_DEV_VIA82CXXX) += via82cxxx.o +obj-$(CONFIG_BLK_DEV_IDE_XILLEON) += xilleon.o # Must appear at the end of the block obj-$(CONFIG_BLK_DEV_GENERIC) += generic.o diff --git a/drivers/ide/pci/xilleon.c b/drivers/ide/pci/xilleon.c new file mode 100644 --- /dev/null +++ b/drivers/ide/pci/xilleon.c @@ -0,0 +1,637 @@ +/* + * ATI Xilleon 225/226 UDMA controller driver + * + * Copyright (C) 2001, 2002 Metro Link, Inc. + * + * Port to kernel version 2.6.12 by David Daney loosely based on the 2.4.18 + * version by Metro Link and ATI. Also based on code in via82cxxx.c + * + * + * May be copied or modified under the terms of the GNU General Public License + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/pci.h> +#include <linux/ide.h> + +#include <linux/stat.h> +#include <linux/proc_fs.h> + +#include <asm/mach-xilleon/xilleon.h> +#include <asm/mach-xilleon/xilleonreg_kernel.h> +#include <asm/mach-xilleon/xilleonint.h> + +#include <asm/io.h> + +int xilleon_ide_proc; + +static struct pci_dev *bmide_dev; + +/* #define DEBUG 1 */ + +#if defined(CONFIG_PROC_FS) +static u8 xilleon_proc = 0; + +/** + * xilleon_ide_get_info - generate xilleon /proc file + * @buffer: buffer for data + * @addr: set to start of data to use + * @offset: current file offset + * @count: size of read + * + * Fills in buffer with the debugging/configuration information for + * the Xilleon tuning and attached drives + */ + +static int xilleon_ide_get_info(char *buffer, + char **addr, off_t offset, int count) +{ + char *p = buffer; + u32 bibma = pci_resource_start(bmide_dev, 4); + struct pci_dev *dev = bmide_dev; + u8 c0 = 0; + u8 c1 = 0; + u8 d0 = 0; + u8 d1 = 0; + u8 udma_control; + + p += sprintf(p, "\n ATI Xilleon Chipset.\n"); + + pci_read_config_byte(dev, PCI_REVISION_ID, &c1); + + p+= sprintf(p, " Revision: %#x\n", c1); + + /* + * at that point bibma+0x2 et bibma+0xa are byte registers + * to investigate: + */ + + pci_read_config_byte(bmide_dev, pciideIDE_UDMA_CONTROL, &udma_control); + pci_read_config_byte(bmide_dev, pciideX225_IDE_DMA_MODE + 0, &d0); + pci_read_config_byte(bmide_dev, pciideX225_IDE_DMA_MODE + 1, &d1); + + + c0 = inb_p((unsigned short)bibma + 0x02); + c1 = inb_p((unsigned short)bibma + 0x0a); + + p += sprintf(p, "--------------- Primary Channel ----------------\n"); + p += sprintf(p, " %sabled\n", + (c0&0x80) ? "dis" : " en"); + + p += sprintf(p, "--------------- drive0 --------- drive1 --------\n"); + p += sprintf(p, "DMA enabled: %s %s\n", + (c0&0x20) ? "yes" : "no ", + (c0&0x40) ? "yes" : "no "); + + p += sprintf(p, "UDMA enabled: %s %s\n", + (udma_control & 0x01) ? "yes" : "no ", + (udma_control & 0x02) ? "yes" : "no "); + + p += sprintf(p, "Mode: %sDMA %d %sDMA %d\n", + d0 & 0x10 ? "U" : " ", d0 & 0xf, + d1 & 0x10 ? "U" : " ", d1 & 0xf); + + return p-buffer; /* => must be less than 4k! */ +} + +#endif + +/* + * initialize the x225 chip + */ + +static unsigned int __devinit pci_init_x225_ide (struct pci_dev *dev, + const char *name) +{ + u32 bmbase = 0; + +#ifdef DEBUG + u8 rev = 0; + pci_read_config_byte(dev, PCI_REVISION_ID, &rev); + printk(KERN_INFO "XILLEON 210/225/226 IDE:" + " rev %02x IDE UDMA controller on pci%s\n", + rev, dev->slot_name); +#endif + bmide_dev = dev; + + /* Indicate drives are capable of dma transfers */ + pci_read_config_dword(bmide_dev, pciideX225_IDE_BM_BASE, &bmbase); + bmbase &= 0xfffffff0; + outb((inb(bmbase + ioideX225_IDE_PRI_BM_STATUS) | 0x60), + bmbase + ioideX225_IDE_PRI_BM_STATUS); + outb((inb(bmbase + ioideX225_IDE_SEC_BM_STATUS) | 0x60), + bmbase + ioideX225_IDE_SEC_BM_STATUS); + + return 0; +} + +/******************************************************** +* +* max_drive_pio_mode +* +* Determine the maximum pio mode supported by this drive +* +*********************************************************/ +static byte max_drive_pio_mode (ide_drive_t *drive) +{ + unsigned short pio_timing[6] = {960, 480, 240, 180, 120, 90}; + unsigned short adv_modes = drive->id->eide_pio_modes; + unsigned short iordy_time = drive->id->eide_pio_iordy; + byte mode_mask = 0x01; + byte max_pio = 2; + + if (drive->id->field_valid & 0x02) { + /* If the drive supports flow control, use the minimum + * iordy time to find the best pio mode supported by + * the drive. */ + if (drive->id->eide_pio_iordy > 0) { + /* iordy supported, use minimum drive timing to + find best mode. */ + for (max_pio = 5; + max_pio && iordy_time > pio_timing[max_pio]; + max_pio--); + } else { + /* iordy not supported, use advanced mode support + flags to find best mode. */ + for (max_pio = 2; + adv_modes & mode_mask; mode_mask <<= 1) { + max_pio++; + } + } + } +#ifdef DEBUG + printk("XILLEON 210/225/226 IDE drive %d, max pio mode = %d\n", + drive->dn, max_pio); +#endif + return max_pio; +} + +/******************************************************** +* +* max_drive_dma_mode +* +* Determine the maximum dma mode supported by this drive +* +*********************************************************/ +static byte max_drive_dma_mode (ide_drive_t *drive) +{ + unsigned short dma_modes = drive->id->dma_mword; + unsigned short mode_mask = 0x80; + byte max_dma = 0; + + if (drive->id->field_valid & 0x02 && dma_modes & 0xff) { + for (max_dma = 7; + (dma_modes & mode_mask) == 0; mode_mask >>= 1) { + max_dma--; + } +#ifdef DEBUG + printk("XILLEON 210/225/226 IDE drive %d," + " max dma mode = %d\n", drive->dn, max_dma); +#endif + return max_dma; + } else { +#ifdef DEBUG + printk("XILLEON 210/225/226 IDE drive %d," + " dma not supported\n", drive->dn); +#endif + return 0x0f; + } +} + +/******************************************************** +* +* max_drive_udma_mode +* +* Determine the maximum dma mode supported by this drive +* +*********************************************************/ +static byte max_drive_udma_mode (ide_drive_t *drive) +{ + unsigned short udma_modes = drive->id->dma_ultra; + unsigned short mode_mask = 0x80; + byte max_udma = 0; + + if (drive->id->field_valid & 0x04 && udma_modes & 0xff) { + for (max_udma = 7; + (udma_modes & mode_mask) == 0; mode_mask >>= 1) { + max_udma--; + } + if (eighty_ninty_three(drive)) { +#ifdef DEBUG + printk("XILLEON 210/225/226 IDE drive %d," + " max udma mode = %d\n", drive->dn, max_udma); +#endif + return max_udma; + } else { +#ifdef DEBUG + printk("XILLEON 210/225/226 IDE IDE cable does not" + " support UDMA modes > 2." + " Drive %d, max udma mode = %d\n", + drive->dn, max_udma > 2 ? 2 : max_udma); +#endif + return max_udma > 2 ? 2 : max_udma; + } + } else { +#ifdef DEBUG + printk("XILLEON 210/225/226 IDE drive %d," + " udma not supported\n", drive->dn); +#endif + return 0x0f; + } +} + + +static int x225_ide_tune_chipset (ide_drive_t *drive, byte speed) +{ + + ide_hwif_t *hwif = HWIF(drive); + struct pci_dev *dev = hwif->pci_dev; + int err; + + u32 work_dword; + + u8 tmg_cntl_reg_off; /* timing register offset */ + u8 pio_cntl_reg_off; /* pio control register offset */ + u8 pio_mode_reg_off; /* pio mode register offset */ + u8 pio_mode_reg; /* pio mode register contents */ + u8 pio_mode; /* pio mode */ +#if 0 + u8 pio_control; /* pio control register */ +#endif + u8 dma_mode_reg_off; /* dma mode register offset */ + u8 dma_mode; /* dma mode */ + + if (drive->dn > 3) /* we support four drives */ + return -1; + + /* Setup register offsets for current drive */ + /* Assume drives 0/1 are on primary, 2/3 are on secondary */ + if (drive->dn > 1) { + pio_mode_reg_off = pciideX225_IDE_PIO_MODE+1; + pio_cntl_reg_off = pciideX225_IDE_PIO_CONTROL+1; + tmg_cntl_reg_off = drive->dn&1 ? + pciideX225_IDE_TMG_CNTL+2 : pciideX225_IDE_TMG_CNTL+3; + dma_mode_reg_off = drive->dn&1 ? + pciideX225_IDE_DMA_MODE+3 : pciideX225_IDE_DMA_MODE+2; + } else { + pio_mode_reg_off = pciideX225_IDE_PIO_MODE; + pio_cntl_reg_off = pciideX225_IDE_PIO_CONTROL; + tmg_cntl_reg_off = drive->dn&1 ? + pciideX225_IDE_TMG_CNTL : pciideX225_IDE_TMG_CNTL+1; + dma_mode_reg_off = drive->dn&1 ? + pciideX225_IDE_DMA_MODE+1 : pciideX225_IDE_DMA_MODE; + } + + /* Reset auto-calc override so that controller calculates mode + timing. */ + pci_write_config_byte(dev, tmg_cntl_reg_off, 0x7f); + + /* All three transfer types require pio mode to be set to */ + /* maximum mode supported by both the drive and controller */ + /* Get the current contents and clear the mode field for this drive*/ + pci_read_config_byte(dev, pio_mode_reg_off, &pio_mode_reg); + pio_mode_reg = drive->dn&1 ? pio_mode_reg & 0x07 : pio_mode_reg & 0x70; + /* Get the highest mode the drive can support */ + pio_mode = max_drive_pio_mode(drive); + +#if 0 + /* All three transfer types require udma control to be set/reset */ + /* according to the transfer mode to be used */ + /* Get the current contents and clear the enable flag for this drive*/ + pci_read_config_byte(dev, pciideIDE_UDMA_CONTROL, &udma_control_reg); + udma_control_reg = drive->dn ? + udma_control_reg & 0xfd : pio_mode_reg & 0xfe; +#endif + switch(speed) { + case XFER_PIO_4: + case XFER_PIO_3: + case XFER_PIO_2: + case XFER_PIO_1: + case XFER_PIO_0: + /* Setting transfer mode to PIO */ + /* If requested mode is higher than drive supports, + set to highest supported */ + pio_mode = pio_mode > (speed - XFER_PIO_0) ? + speed - XFER_PIO_0 : pio_mode; + +#ifdef DEBUG + printk("xilleon_ide_tune_chipset for drive %s (%d)," + " pio mode %d\n", drive->name, drive->dn, pio_mode); +#endif + +#if 0 + /* Enable prefetch and write posting */ + pci_read_config_byte(dev, pio_cntl_reg_off, &pio_control); + pio_control = drive->dn ? + pio_control | 0x50 : pio_control | 0xa0; + pci_write_config_byte(dev, pio_cntl_reg_off, pio_control); +#endif + break; + + case XFER_MW_DMA_2: + case XFER_MW_DMA_1: + case XFER_MW_DMA_0: + /* Setting transfer mode to Multiword DMA */ + dma_mode = speed - XFER_MW_DMA_0; + pci_write_config_byte(dev, dma_mode_reg_off, dma_mode); +#ifdef DEBUG + printk("xilleon_ide_tune_chipset for drive %s (%d)," + " dma mode %d, pio mode %d\n", + drive->name, drive->dn, dma_mode, pio_mode); +#endif + break; + + case XFER_UDMA_5: + case XFER_UDMA_4: + case XFER_UDMA_3: + case XFER_UDMA_2: + case XFER_UDMA_1: + case XFER_UDMA_0: + /* Setting transfer mode to Ultra DMA */ + dma_mode = (speed - XFER_UDMA_0)|0x10; + pci_write_config_byte(dev, dma_mode_reg_off, dma_mode); +#ifdef DEBUG + printk("xilleon_ide_tune_chipset for drive %s (%d)," + " udma mode %d, pio mode %d\n", + drive->name, drive->dn, (speed - XFER_UDMA_0), + pio_mode); +#endif + default: + break; + } + + if (!drive->init_speed) + drive->init_speed = speed; + + /* Set Read and Write Combining */ + pci_read_config_dword(dev, pciideX225_IDE_PCI_BUSMASTER_CNTL, + &work_dword); + pci_write_config_dword(dev, pciideX225_IDE_PCI_BUSMASTER_CNTL, + work_dword|0x60); + + pio_mode_reg |= drive->dn&1 ? pio_mode << 4 : pio_mode; + pci_write_config_byte(dev, pio_mode_reg_off, pio_mode_reg); + err = ide_config_drive_speed(drive, speed); + drive->current_speed = speed; + +#if 0 + pci_read_config_word(dev, pciideIDE_DEVICE_ID, &work_word); + printk("IDE_DEVICE_ID = %04x\n", work_word); + + pci_read_config_dword(dev, pciideIDE_TMG_CNTL, &work_dword); + printk("IDE_TMG_CNTL = %08x\n", work_dword); + + pci_read_config_word(dev, pciideIDE_PIO_CONTROL, &work_word); + printk("IDE_PIO_CONTROL = %04x\n", work_word); + + pci_read_config_word(dev, pciideIDE_PIO_MODE, &work_word); + printk("IDE_PIO_MODE = %04x\n", work_word); + + pci_read_config_dword(dev, pciideIDE_DMA_MODE, &work_dword); + printk("IDE_DMA_MODE = %08x\n", work_dword); + + pci_read_config_dword(dev, pciideIDE_PCI_BUSMASTER_CNTL, &work_dword); + printk("IDE_PCI_BUSMASTER_CNTL = %08x\n", work_dword); +#endif + + return err; +} + +/************************************ + +config_chipset_for_pio + +Set the controller and drive to the +highest pio mode supported by both + +*************************************/ + +static void config_chipset_for_pio (ide_drive_t *drive) +{ + byte speed = XFER_PIO_0 + max_drive_pio_mode (drive); + + (void) x225_ide_tune_chipset(drive, speed); + + drive->current_speed = speed; + +#ifdef DEBUG + printk("config_chipset_for_pio, speed is %d \n", speed); +#endif +} + +static void x225_ide_tune_drive (ide_drive_t *drive, byte pio) +{ +#ifdef DEBUG + printk("tune drive %s (%d) for pio %d\n", drive->name, drive->dn, pio); +#endif + (void) x225_ide_tune_chipset(drive, XFER_PIO_0 + pio); +} + +static int config_chipset_for_dma (ide_drive_t *drive) +{ + struct hd_driveid *id = drive->id; + byte speed = 0x0f; + +#ifdef DEBUG + printk("config_chipset_for_dma for drive %s (%d)\n", + drive->name, drive->dn); +#endif + + /* Check if drive supports ultra dma - get highest supported mode */ + if ((speed = max_drive_udma_mode (drive)) != 0x0f) { + speed = speed > 5 ? 5 + XFER_UDMA_0 : speed + XFER_UDMA_0; + /* Check if drive supports dma - get highest supported mode */ + } else if ((speed = max_drive_dma_mode (drive)) != 0x0f) { + speed = speed > 2 ? 2 + XFER_MW_DMA_0 : speed + XFER_MW_DMA_0; + } + if(speed == 0x0f) { + /* Speed not set yet, get max pio mode supported + by the drive. */ + speed = max_drive_pio_mode (drive); + speed = speed > 4 ? 4 + XFER_PIO_0 : speed + XFER_PIO_0; + } + (void) x225_ide_tune_chipset(drive, speed); + + /* Set return code based on drive id information. */ + return (((id->dma_ultra & 0xff00 && id->field_valid & 0x04) || + (id->dma_mword & 0xff00 && id->field_valid & 0x02)) ? 1 : 0); +} + +static int config_drive_xfer_rate (ide_drive_t *drive) +{ + struct hd_driveid *id = drive->id; + int use_dma = 1; + ide_hwif_t *hwif = HWIF(drive); + +#ifdef DEBUG + printk("config_drive_xfer_rate for drive %s (%d)\n", + drive->name, drive->dn); +#endif + + if (id && (id->capability & 1) && HWIF(drive)->autodma) { + /* Consult the list of known "bad" drives */ + if (__ide_dma_bad_drive (drive)) + use_dma = 0; + /* Not in list of bad drives - check udma */ + else if (id->field_valid & 0x06) + /* Try to config ultra dma or multiword dma */ + use_dma = config_chipset_for_dma(drive); + + if (use_dma) + config_chipset_for_pio(drive); + + } + + return use_dma ? + hwif->ide_dma_on(drive) : hwif->ide_dma_off_quietly(drive); +} + +static void ide_outbsync_x22x (ide_drive_t *drive, u8 addr, + unsigned long port) +{ + outb(addr, port); + wmb(); +} + +/* + * Init the hwif structure for this controller + */ +static void __devinit ide_init_x225 (ide_hwif_t *hwif) +{ + if (!hwif->irq) { +#ifdef CONFIG_XILLEON_IDE_SEC + hwif->irq = X225_IDE_INT_SEC; +#else + hwif->irq = X225_IDE_INT; +#endif + } + hwif->tuneproc = x225_ide_tune_drive; + hwif->speedproc = x225_ide_tune_chipset; + hwif->serialized = 1; + + hwif->OUTBSYNC = ide_outbsync_x22x; + + + if (hwif->dma_base) { + if (!noautodma) + hwif->autodma = 1; + hwif->ide_dma_check = config_drive_xfer_rate; + hwif->udma_four = 1; + } else { + hwif->autodma = 0; + hwif->drives[0].autotune = 1; + hwif->drives[1].autotune = 1; + } + +#if defined(CONFIG_PROC_FS) + if (!xilleon_proc) { + ide_pci_create_host_proc("xilleon", xilleon_ide_get_info); + xilleon_proc = 1; + } +#endif /* CONFIG_PROC_FS */ + return; +} + +static void __devinit ide_dmacapable_x22x(ide_hwif_t *hwif, + unsigned long dmabase) +{ + hwif->dma_base = dmabase; + ide_setup_dma(hwif, dmabase, 8); +} + +static int __devinit init_setup_x22x (struct pci_dev *dev, + ide_pci_device_t *d) +{ + return ide_setup_pci_device(dev, d); +} + +static ide_pci_device_t xilleon_chipsets[] __devinitdata = { + { /* 0 */ + .name = "X225", + .init_setup = init_setup_x22x, + .init_chipset = pci_init_x225_ide, + .init_iops = NULL, + .init_hwif = ide_init_x225, + .init_dma = ide_dmacapable_x22x, + .channels = 1, + .autodma = AUTODMA, + .enablebits = {{0x00,0x00,0x00}, {0x00,0x00,0x00}}, + .bootable = ON_BOARD, + .extra = 0, + },{ /* 1 */ + .name = "X226", + .init_setup = init_setup_x22x, + .init_chipset = pci_init_x225_ide, + .init_iops = NULL, + .init_hwif = ide_init_x225, + .init_dma = ide_dmacapable_x22x, + .channels = 1, + .autodma = AUTODMA, + .enablebits = {{0x00,0x00,0x00}, {0x00,0x00,0x00}}, + .bootable = ON_BOARD, + .extra = 0, + },{ /* 2 */ + .name = "X210", + .init_setup = init_setup_x22x, + .init_chipset = pci_init_x225_ide, + .init_iops = NULL, + .init_hwif = ide_init_x225, + .init_dma = ide_dmacapable_x22x, + .channels = 1, + .autodma = AUTODMA, + .enablebits = {{0x00,0x00,0x00}, {0x00,0x00,0x00}}, + .bootable = ON_BOARD, + .extra = 0, + } +}; + +static struct pci_device_id xilleon_pci_tbl[] __devinitdata = { + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_X225_IDE, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_X226_IDE, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1}, + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_X210_IDE, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 2}, + { 0, }, +}; + + +static int __devinit xilleon_init_one(struct pci_dev *dev, + const struct pci_device_id *id) +{ + ide_pci_device_t *d = &xilleon_chipsets[id->driver_data]; + return ide_setup_pci_device(dev, d); +} + +static struct pci_driver driver = { + .name = "xilleon_ide", + .id_table = xilleon_pci_tbl, + .probe = xilleon_init_one, +}; + + +static int xilleon_ide_init(void) +{ + return ide_pci_register_driver(&driver); +} + +static void xilleon_ide_exit(void) +{ + ide_pci_unregister_driver(&driver); +} + +module_init(xilleon_ide_init); +module_exit(xilleon_ide_exit); + +MODULE_DESCRIPTION("PCI driver module for Xilleon IDE"); +MODULE_LICENSE("GPL");