/* * drivers/ata/pata_mpc52xx.c * * libata driver for the Freescale MPC52xx on-chip IDE interface * * Copyright (C) 2006 Sylvain Munaut <tnt@246tNt.com> * Copyright (C) 2003 Mipsys - Benjamin Herrenschmidt * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/libata.h> #include <linux/of_platform.h> #include <asm/types.h> #include <asm/prom.h> #include <asm/mpc52xx.h> #define DRV_NAME "mpc52xx_ata" #define DRV_VERSION "0.1.2" /* Private structures used by the driver */ struct mpc52xx_ata_timings { u32 pio1; u32 pio2; }; struct mpc52xx_ata_priv { unsigned int ipb_period; struct mpc52xx_ata __iomem * ata_regs; int ata_irq; struct mpc52xx_ata_timings timings[2]; int csel; }; /* ATAPI-4 PIO specs (in ns) */ static const int ataspec_t0[5] = {600, 383, 240, 180, 120}; static const int ataspec_t1[5] = { 70, 50, 30, 30, 25}; static const int ataspec_t2_8[5] = {290, 290, 290, 80, 70}; static const int ataspec_t2_16[5] = {165, 125, 100, 80, 70}; static const int ataspec_t2i[5] = { 0, 0, 0, 70, 25}; static const int ataspec_t4[5] = { 30, 20, 15, 10, 10}; static const int ataspec_ta[5] = { 35, 35, 35, 35, 35}; #define CALC_CLKCYC(c,v) ((((v)+(c)-1)/(c))) /* Bit definitions inside the registers */ #define MPC52xx_ATA_HOSTCONF_SMR 0x80000000UL /* State machine reset */ #define MPC52xx_ATA_HOSTCONF_FR 0x40000000UL /* FIFO Reset */ #define MPC52xx_ATA_HOSTCONF_IE 0x02000000UL /* Enable interrupt in PIO */ #define MPC52xx_ATA_HOSTCONF_IORDY 0x01000000UL /* Drive supports IORDY protocol */ #define MPC52xx_ATA_HOSTSTAT_TIP 0x80000000UL /* Transaction in progress */ #define MPC52xx_ATA_HOSTSTAT_UREP 0x40000000UL /* UDMA Read Extended Pause */ #define MPC52xx_ATA_HOSTSTAT_RERR 0x02000000UL /* Read Error */ #define MPC52xx_ATA_HOSTSTAT_WERR 0x01000000UL /* Write Error */ #define MPC52xx_ATA_FIFOSTAT_EMPTY 0x01 /* FIFO Empty */ #define MPC52xx_ATA_DMAMODE_WRITE 0x01 /* Write DMA */ #define MPC52xx_ATA_DMAMODE_READ 0x02 /* Read DMA */ #define MPC52xx_ATA_DMAMODE_UDMA 0x04 /* UDMA enabled */ #define MPC52xx_ATA_DMAMODE_IE 0x08 /* Enable drive interrupt to CPU in DMA mode */ #define MPC52xx_ATA_DMAMODE_FE 0x10 /* FIFO Flush enable in Rx mode */ #define MPC52xx_ATA_DMAMODE_FR 0x20 /* FIFO Reset */ #define MPC52xx_ATA_DMAMODE_HUT 0x40 /* Host UDMA burst terminate */ /* Structure of the hardware registers */ struct mpc52xx_ata { /* Host interface registers */ u32 config; /* ATA + 0x00 Host configuration */ u32 host_status; /* ATA + 0x04 Host controller status */ u32 pio1; /* ATA + 0x08 PIO Timing 1 */ u32 pio2; /* ATA + 0x0c PIO Timing 2 */ u32 mdma1; /* ATA + 0x10 MDMA Timing 1 */ u32 mdma2; /* ATA + 0x14 MDMA Timing 2 */ u32 udma1; /* ATA + 0x18 UDMA Timing 1 */ u32 udma2; /* ATA + 0x1c UDMA Timing 2 */ u32 udma3; /* ATA + 0x20 UDMA Timing 3 */ u32 udma4; /* ATA + 0x24 UDMA Timing 4 */ u32 udma5; /* ATA + 0x28 UDMA Timing 5 */ u32 share_cnt; /* ATA + 0x2c ATA share counter */ u32 reserved0[3]; /* FIFO registers */ u32 fifo_data; /* ATA + 0x3c */ u8 fifo_status_frame; /* ATA + 0x40 */ u8 fifo_status; /* ATA + 0x41 */ u16 reserved7[1]; u8 fifo_control; /* ATA + 0x44 */ u8 reserved8[5]; u16 fifo_alarm; /* ATA + 0x4a */ u16 reserved9; u16 fifo_rdp; /* ATA + 0x4e */ u16 reserved10; u16 fifo_wrp; /* ATA + 0x52 */ u16 reserved11; u16 fifo_lfrdp; /* ATA + 0x56 */ u16 reserved12; u16 fifo_lfwrp; /* ATA + 0x5a */ /* Drive TaskFile registers */ u8 tf_control; /* ATA + 0x5c TASKFILE Control/Alt Status */ u8 reserved13[3]; u16 tf_data; /* ATA + 0x60 TASKFILE Data */ u16 reserved14; u8 tf_features; /* ATA + 0x64 TASKFILE Features/Error */ u8 reserved15[3]; u8 tf_sec_count; /* ATA + 0x68 TASKFILE Sector Count */ u8 reserved16[3]; u8 tf_sec_num; /* ATA + 0x6c TASKFILE Sector Number */ u8 reserved17[3]; u8 tf_cyl_low; /* ATA + 0x70 TASKFILE Cylinder Low */ u8 reserved18[3]; u8 tf_cyl_high; /* ATA + 0x74 TASKFILE Cylinder High */ u8 reserved19[3]; u8 tf_dev_head; /* ATA + 0x78 TASKFILE Device/Head */ u8 reserved20[3]; u8 tf_command; /* ATA + 0x7c TASKFILE Command/Status */ u8 dma_mode; /* ATA + 0x7d ATA Host DMA Mode configuration */ u8 reserved21[2]; }; /* ======================================================================== */ /* Aux fns */ /* ======================================================================== */ /* MPC52xx low level hw control */ static int mpc52xx_ata_compute_pio_timings(struct mpc52xx_ata_priv *priv, int dev, int pio) { struct mpc52xx_ata_timings *timing = &priv->timings[dev]; unsigned int ipb_period = priv->ipb_period; unsigned int t0, t1, t2_8, t2_16, t2i, t4, ta; if ((pio<0) || (pio>4)) return -EINVAL; t0 = CALC_CLKCYC(ipb_period, 1000 * ataspec_t0[pio]); t1 = CALC_CLKCYC(ipb_period, 1000 * ataspec_t1[pio]); t2_8 = CALC_CLKCYC(ipb_period, 1000 * ataspec_t2_8[pio]); t2_16 = CALC_CLKCYC(ipb_period, 1000 * ataspec_t2_16[pio]); t2i = CALC_CLKCYC(ipb_period, 1000 * ataspec_t2i[pio]); t4 = CALC_CLKCYC(ipb_period, 1000 * ataspec_t4[pio]); ta = CALC_CLKCYC(ipb_period, 1000 * ataspec_ta[pio]); timing->pio1 = (t0 << 24) | (t2_8 << 16) | (t2_16 << 8) | (t2i); timing->pio2 = (t4 << 24) | (t1 << 16) | (ta << 8); return 0; } static void mpc52xx_ata_apply_timings(struct mpc52xx_ata_priv *priv, int device) { struct mpc52xx_ata __iomem *regs = priv->ata_regs; struct mpc52xx_ata_timings *timing = &priv->timings[device]; out_be32(®s->pio1, timing->pio1); out_be32(®s->pio2, timing->pio2); out_be32(®s->mdma1, 0); out_be32(®s->mdma2, 0); out_be32(®s->udma1, 0); out_be32(®s->udma2, 0); out_be32(®s->udma3, 0); out_be32(®s->udma4, 0); out_be32(®s->udma5, 0); priv->csel = device; } static int mpc52xx_ata_hw_init(struct mpc52xx_ata_priv *priv) { struct mpc52xx_ata __iomem *regs = priv->ata_regs; int tslot; /* Clear share_cnt (all sample code do this ...) */ out_be32(®s->share_cnt, 0); /* Configure and reset host */ out_be32(®s->config, MPC52xx_ATA_HOSTCONF_IE | MPC52xx_ATA_HOSTCONF_IORDY | MPC52xx_ATA_HOSTCONF_SMR | MPC52xx_ATA_HOSTCONF_FR); udelay(10); out_be32(®s->config, MPC52xx_ATA_HOSTCONF_IE | MPC52xx_ATA_HOSTCONF_IORDY); /* Set the time slot to 1us */ tslot = CALC_CLKCYC(priv->ipb_period, 1000000); out_be32(®s->share_cnt, tslot << 16 ); /* Init timings to PIO0 */ memset(priv->timings, 0x00, 2*sizeof(struct mpc52xx_ata_timings)); mpc52xx_ata_compute_pio_timings(priv, 0, 0); mpc52xx_ata_compute_pio_timings(priv, 1, 0); mpc52xx_ata_apply_timings(priv, 0); return 0; } /* ======================================================================== */ /* libata driver */ /* ======================================================================== */ static void mpc52xx_ata_set_piomode(struct ata_port *ap, struct ata_device *adev) { struct mpc52xx_ata_priv *priv = ap->host->private_data; int pio, rv; pio = adev->pio_mode - XFER_PIO_0; rv = mpc52xx_ata_compute_pio_timings(priv, adev->devno, pio); if (rv) { printk(KERN_ERR DRV_NAME ": Trying to select invalid PIO mode %d\n", pio); return; } mpc52xx_ata_apply_timings(priv, adev->devno); } static void mpc52xx_ata_dev_select(struct ata_port *ap, unsigned int device) { struct mpc52xx_ata_priv *priv = ap->host->private_data; if (device != priv->csel) mpc52xx_ata_apply_timings(priv, device); ata_sff_dev_select(ap,device); } static struct scsi_host_template mpc52xx_ata_sht = { ATA_PIO_SHT(DRV_NAME), }; static struct ata_port_operations mpc52xx_ata_port_ops = { .inherits = &ata_sff_port_ops, .sff_dev_select = mpc52xx_ata_dev_select, .cable_detect = ata_cable_40wire, .set_piomode = mpc52xx_ata_set_piomode, .post_internal_cmd = ATA_OP_NULL, }; static int __devinit mpc52xx_ata_init_one(struct device *dev, struct mpc52xx_ata_priv *priv, unsigned long raw_ata_regs) { struct ata_host *host; struct ata_port *ap; struct ata_ioports *aio; host = ata_host_alloc(dev, 1); if (!host) return -ENOMEM; ap = host->ports[0]; ap->flags |= ATA_FLAG_SLAVE_POSS; ap->pio_mask = 0x1f; /* Up to PIO4 */ ap->mwdma_mask = 0x00; /* No MWDMA */ ap->udma_mask = 0x00; /* No UDMA */ ap->ops = &mpc52xx_ata_port_ops; host->private_data = priv; aio = &ap->ioaddr; aio->cmd_addr = NULL; /* Don't have a classic reg block */ aio->altstatus_addr = &priv->ata_regs->tf_control; aio->ctl_addr = &priv->ata_regs->tf_control; aio->data_addr = &priv->ata_regs->tf_data; aio->error_addr = &priv->ata_regs->tf_features; aio->feature_addr = &priv->ata_regs->tf_features; aio->nsect_addr = &priv->ata_regs->tf_sec_count; aio->lbal_addr = &priv->ata_regs->tf_sec_num; aio->lbam_addr = &priv->ata_regs->tf_cyl_low; aio->lbah_addr = &priv->ata_regs->tf_cyl_high; aio->device_addr = &priv->ata_regs->tf_dev_head; aio->status_addr = &priv->ata_regs->tf_command; aio->command_addr = &priv->ata_regs->tf_command; ata_port_desc(ap, "ata_regs 0x%lx", raw_ata_regs); /* activate host */ return ata_host_activate(host, priv->ata_irq, ata_sff_interrupt, 0, &mpc52xx_ata_sht); } static struct mpc52xx_ata_priv * mpc52xx_ata_remove_one(struct device *dev) { struct ata_host *host = dev_get_drvdata(dev); struct mpc52xx_ata_priv *priv = host->private_data; ata_host_detach(host); return priv; } /* ======================================================================== */ /* OF Platform driver */ /* ======================================================================== */ static int __devinit mpc52xx_ata_probe(struct of_device *op, const struct of_device_id *match) { unsigned int ipb_freq; struct resource res_mem; int ata_irq; struct mpc52xx_ata __iomem *ata_regs; struct mpc52xx_ata_priv *priv; int rv; /* Get ipb frequency */ ipb_freq = mpc52xx_find_ipb_freq(op->node); if (!ipb_freq) { printk(KERN_ERR DRV_NAME ": " "Unable to find IPB Bus frequency\n" ); return -ENODEV; } /* Get IRQ and register */ rv = of_address_to_resource(op->node, 0, &res_mem); if (rv) { printk(KERN_ERR DRV_NAME ": " "Error while parsing device node resource\n" ); return rv; } ata_irq = irq_of_parse_and_map(op->node, 0); if (ata_irq == NO_IRQ) { printk(KERN_ERR DRV_NAME ": " "Error while mapping the irq\n"); return -EINVAL; } /* Request mem region */ if (!devm_request_mem_region(&op->dev, res_mem.start, sizeof(struct mpc52xx_ata), DRV_NAME)) { printk(KERN_ERR DRV_NAME ": " "Error while requesting mem region\n"); rv = -EBUSY; goto err; } /* Remap registers */ ata_regs = devm_ioremap(&op->dev, res_mem.start, sizeof(struct mpc52xx_ata)); if (!ata_regs) { printk(KERN_ERR DRV_NAME ": " "Error while mapping register set\n"); rv = -ENOMEM; goto err; } /* Prepare our private structure */ priv = devm_kzalloc(&op->dev, sizeof(struct mpc52xx_ata_priv), GFP_ATOMIC); if (!priv) { printk(KERN_ERR DRV_NAME ": " "Error while allocating private structure\n"); rv = -ENOMEM; goto err; } priv->ipb_period = 1000000000 / (ipb_freq / 1000); priv->ata_regs = ata_regs; priv->ata_irq = ata_irq; priv->csel = -1; /* Init the hw */ rv = mpc52xx_ata_hw_init(priv); if (rv) { printk(KERN_ERR DRV_NAME ": Error during HW init\n"); goto err; } /* Register ourselves to libata */ rv = mpc52xx_ata_init_one(&op->dev, priv, res_mem.start); if (rv) { printk(KERN_ERR DRV_NAME ": " "Error while registering to ATA layer\n"); return rv; } /* Done */ return 0; /* Error path */ err: irq_dispose_mapping(ata_irq); return rv; } static int mpc52xx_ata_remove(struct of_device *op) { struct mpc52xx_ata_priv *priv; priv = mpc52xx_ata_remove_one(&op->dev); irq_dispose_mapping(priv->ata_irq); return 0; } #ifdef CONFIG_PM static int mpc52xx_ata_suspend(struct of_device *op, pm_message_t state) { struct ata_host *host = dev_get_drvdata(&op->dev); return ata_host_suspend(host, state); } static int mpc52xx_ata_resume(struct of_device *op) { struct ata_host *host = dev_get_drvdata(&op->dev); struct mpc52xx_ata_priv *priv = host->private_data; int rv; rv = mpc52xx_ata_hw_init(priv); if (rv) { printk(KERN_ERR DRV_NAME ": Error during HW init\n"); return rv; } ata_host_resume(host); return 0; } #endif static struct of_device_id mpc52xx_ata_of_match[] = { { .compatible = "fsl,mpc5200-ata", }, { .compatible = "mpc5200-ata", }, {}, }; static struct of_platform_driver mpc52xx_ata_of_platform_driver = { .owner = THIS_MODULE, .name = DRV_NAME, .match_table = mpc52xx_ata_of_match, .probe = mpc52xx_ata_probe, .remove = mpc52xx_ata_remove, #ifdef CONFIG_PM .suspend = mpc52xx_ata_suspend, .resume = mpc52xx_ata_resume, #endif .driver = { .name = DRV_NAME, .owner = THIS_MODULE, }, }; /* ======================================================================== */ /* Module */ /* ======================================================================== */ static int __init mpc52xx_ata_init(void) { printk(KERN_INFO "ata: MPC52xx IDE/ATA libata driver\n"); return of_register_platform_driver(&mpc52xx_ata_of_platform_driver); } static void __exit mpc52xx_ata_exit(void) { of_unregister_platform_driver(&mpc52xx_ata_of_platform_driver); } module_init(mpc52xx_ata_init); module_exit(mpc52xx_ata_exit); MODULE_AUTHOR("Sylvain Munaut <tnt@246tNt.com>"); MODULE_DESCRIPTION("Freescale MPC52xx IDE/ATA libata driver"); MODULE_LICENSE("GPL"); MODULE_DEVICE_TABLE(of, mpc52xx_ata_of_match); MODULE_VERSION(DRV_VERSION);