Re: [PATCH] S3C: ide: Add Samsung S3C IDE controller driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Oct 8, 2009 at 3:59 PM,  <thomas.ab@xxxxxxxxxxx> wrote:
> From: Thomas Abraham <thomas.ab@xxxxxxxxxxx>
> diff --git a/drivers/ide/s3c-ide.c b/drivers/ide/s3c-ide.c
> new file mode 100644
> index 0000000..5ebfe98
> --- /dev/null
> +++ b/drivers/ide/s3c-ide.c
> @@ -0,0 +1,486 @@
> +/*
> + * s3c-ide.c - Samsung S3C IDE controller Driver
> + *
> + * Copyright (C) 2009 Samsung Electronics
> + *      http://samsungsemi.com/
> + *
> + * The Samsung S3C IDE controller driver provides low-level support for
> + * interfacing with IDE disks. This supports only PIO mode of operation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +
> +#include <linux/types.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/init.h>
> +#include <linux/ide.h>
> +#include <linux/sysdev.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <plat/regs-ide.h>
> +#include <plat/ide.h>
> +
> +/*
> + * This structure represents one instance of the IDE controller driver.
> + */
> +struct s3c_ide_device {
> +       struct platform_device  *pdev;
> +       ide_hwif_t              *hwif;
> +       int                     irq;
> +       ulong                   piotime[5];
> +       void __iomem            *regbase;
> +};
> +
> +/* Global Declarations */
> +static struct s3c_ide_device s3c_ide_dev;
> +
> +#define ide_writel(value, reg) writel(value, ide_dev->regbase + (reg))
> +#define ide_readl(reg) readl(ide_dev->regbase + (reg))
> +
> +/*
> + * This function waits until the IDE controller is able to perform next
> + * read/write operation to the disk.
> + */
> +static void wait_for_host_ready(struct s3c_ide_device *ide_dev)
> +{
> +       u32 count = 1000000;
> +       while (ide_readl(S3C_ATA_FIFO_STATUS) >> 28) {
> +               if (!(count--)) {
> +                       dev_err(&ide_dev->pdev->dev,
> +                       "ide controller not ready for next taskfile operation");
> +                       return;
> +               }
> +       }
> +}

 I think instead of using a magic number for loop counter, it will be
better to use some timed wait using a counter as in
http://www.spinics.net/lists/arm-kernel/msg73904.html


> +
> +/*
> + * This function writes to one of the rask file registers.
> + */
> +static void s3c_ide_OUTB(ide_hwif_t *hwif, u8 addr, ulong reg)
> +{
> +       struct s3c_ide_device *ide_dev =
> +                       (struct s3c_ide_device *)hwif->hwif_data;
> +
> +       wait_for_host_ready(ide_dev);
> +       __raw_writeb(addr, reg);
> +}
> +
> +/*
> + * This function reads from one of the task file registers.
> + */
> +static u8 s3c_ide_INB(ide_hwif_t *hwif, ulong reg)
> +{
> +       struct s3c_ide_device *ide_dev =
> +                       (struct s3c_ide_device *)hwif->hwif_data;
> +       u8 temp;
> +
> +       wait_for_host_ready(ide_dev);
> +       temp = __raw_readb(reg);
> +       wait_for_host_ready(ide_dev);
> +       temp = __raw_readb(ide_dev->regbase + S3C_ATA_PIO_RDATA);
> +       return temp;
> +}

any namespace reason for the caps  _OUTB and _INB ?


> +
> +/*
> + * The following are ide_tp_ops functions implemented by the IDE
> + * cotnroller driver.
> + */
> +static void s3c_ide_exec_command(ide_hwif_t *hwif, u8 cmd)
> +{
> +       s3c_ide_OUTB(hwif, cmd, hwif->io_ports.command_addr);
> +}
> +
> +static u8 s3c_ide_read_status(ide_hwif_t *hwif)
> +{
> +       return s3c_ide_INB(hwif, hwif->io_ports.status_addr);
> +}
> +
> +static u8 s3c_ide_read_altstatus(ide_hwif_t *hwif)
> +{
> +       return s3c_ide_INB(hwif, hwif->io_ports.ctl_addr);
> +}
> +
> +static void s3c_ide_write_devctl(ide_hwif_t *hwif, u8 ctl)
> +{
> +       s3c_ide_OUTB(hwif, ctl, hwif->io_ports.ctl_addr);
> +}
> +
> +static void s3c_ide_dev_select(ide_drive_t *drive)
> +{
> +       ide_hwif_t *hwif = drive->hwif;
> +       u8 select = drive->select | ATA_DEVICE_OBS;
> +       s3c_ide_OUTB(hwif, select, hwif->io_ports.device_addr);
> +}
> +
> +static void s3c_ide_input_data(ide_drive_t *drive, struct ide_cmd *cmd,
> +                                       void *buf, unsigned int len)
> +{
> +       ide_hwif_t *hwif = drive->hwif;
> +       struct s3c_ide_device *ide_dev =
> +                       (struct s3c_ide_device *)hwif->hwif_data;
> +       struct ide_io_ports *io_ports = &hwif->io_ports;
> +       unsigned long data_addr = io_ports->data_addr;
> +       unsigned int words = (len + 1) >> 1, i;
> +       u16 *temp_addr = (u16 *)buf;
> +
> +       for (i = 0; i < words; i++, temp_addr++) {
> +               wait_for_host_ready(ide_dev);
> +               *temp_addr = __raw_readw(data_addr);
> +               wait_for_host_ready(ide_dev);
> +               *temp_addr = __raw_readw(ide_dev->regbase + S3C_ATA_PIO_RDATA);
> +       }
> +}
> +
> +static void s3c_ide_output_data(ide_drive_t *drive, struct ide_cmd *cmd,
> +                                       void *buf, unsigned int len)
> +{
> +       ide_hwif_t *hwif = drive->hwif;
> +       struct s3c_ide_device *ide_dev =
> +                       (struct s3c_ide_device *)hwif->hwif_data;
> +       struct ide_io_ports *io_ports = &hwif->io_ports;
> +       unsigned long data_addr = io_ports->data_addr;
> +       unsigned int words = (len + 1) >> 1, i;
> +       u16 *temp_addr = (u16 *)buf;
> +
> +       for (i = 0; i < words; i++, temp_addr++) {
> +               wait_for_host_ready(ide_dev);
> +               writel(*temp_addr, data_addr);
> +       }
> +}
> +
> +static void s3c_ide_tf_load(ide_drive_t *drive, struct ide_taskfile *tf,
> +                               u8 valid)
> +{
> +       ide_hwif_t *hwif = drive->hwif;
> +       struct ide_io_ports *io_ports = &hwif->io_ports;
> +
> +       if (valid & IDE_VALID_FEATURE)
> +               s3c_ide_OUTB(hwif, tf->feature, io_ports->feature_addr);
> +       if (valid & IDE_VALID_NSECT)
> +               s3c_ide_OUTB(hwif, tf->nsect, io_ports->nsect_addr);
> +       if (valid & IDE_VALID_LBAL)
> +               s3c_ide_OUTB(hwif, tf->lbal, io_ports->lbal_addr);
> +       if (valid & IDE_VALID_LBAM)
> +               s3c_ide_OUTB(hwif, tf->lbam, io_ports->lbam_addr);
> +       if (valid & IDE_VALID_LBAH)
> +               s3c_ide_OUTB(hwif, tf->lbah, io_ports->lbah_addr);
> +       if (valid & IDE_VALID_DEVICE)
> +               s3c_ide_OUTB(hwif, tf->device, io_ports->device_addr);
> +}
> +
> +void s3c_ide_tf_read(ide_drive_t *drive, struct ide_taskfile *tf, u8 valid)
> +{
> +       ide_hwif_t *hwif = drive->hwif;
> +       struct ide_io_ports *io_ports = &hwif->io_ports;
> +
> +       if (valid & IDE_VALID_ERROR)
> +               tf->error  = s3c_ide_INB(hwif, io_ports->feature_addr);
> +       if (valid & IDE_VALID_NSECT)
> +               tf->nsect  = s3c_ide_INB(hwif, io_ports->nsect_addr);
> +       if (valid & IDE_VALID_LBAL)
> +               tf->lbal   = s3c_ide_INB(hwif, io_ports->lbal_addr);
> +       if (valid & IDE_VALID_LBAM)
> +               tf->lbam   = s3c_ide_INB(hwif, io_ports->lbam_addr);
> +       if (valid & IDE_VALID_LBAH)
> +               tf->lbah   = s3c_ide_INB(hwif, io_ports->lbah_addr);
> +       if (valid & IDE_VALID_DEVICE)
> +               tf->device = s3c_ide_INB(hwif, io_ports->device_addr);
> +}
> +
> +static void set_ata_enable(struct s3c_ide_device *ide_dev, u8 state)
> +{
> +       u32 temp = ide_readl(S3C_ATA_CTRL);
> +       temp = state ? temp | 1 : temp & ~1;
> +       ide_writel(temp , S3C_ATA_CTRL);
> +}
> +
> +static void set_endian_mode(struct s3c_ide_device *ide_dev, u8 mode)
> +{
> +       u32 reg = ide_readl(S3C_ATA_CFG);
> +       reg = mode ? (reg & ~S3C_ATA_CFG_SWAP) : (reg | S3C_ATA_CFG_SWAP);
> +       ide_writel(reg, S3C_ATA_CFG);
> +}
> +
> +/*
> + * This function selects the maximum possible transfer speed.
> + */
> +static u8 s3c_ide_ratefilter(u8 speed)
> +{
> +       return min(speed, (u8)XFER_PIO_4);
> +}
> +
> +/*
> + * This function selects the best possible transfer speed.
> + */
> +static void s3c_ide_tune_chipset(ide_drive_t *drive, u8 xferspeed)
> +{
> +       ide_hwif_t *hwif = (ide_hwif_t *)drive->hwif;
> +       struct s3c_ide_device *ide_dev =
> +                       (struct s3c_ide_device *)hwif->hwif_data;
> +       u8 speed = s3c_ide_ratefilter(xferspeed);
> +
> +       /* IORDY is enabled for modes > PIO2 */
> +       if (XFER_PIO_0 >= speed && speed <= XFER_PIO_4) {
> +               ulong ata_cfg = ide_readl(S3C_ATA_CFG);
> +
> +               switch (speed) {
> +               case XFER_PIO_0:
> +               case XFER_PIO_1:
> +               case XFER_PIO_2:
> +                       ata_cfg &= (~S3C_ATA_CFG_IORDYEN);
> +                       break;
> +               case XFER_PIO_3:
> +               case XFER_PIO_4:
> +                       ata_cfg |= S3C_ATA_CFG_IORDYEN;
> +                       break;
> +               }
> +
> +               ide_writel(ata_cfg, S3C_ATA_CFG);
> +               ide_writel(ide_dev->piotime[speed - XFER_PIO_0],
> +                               S3C_ATA_PIO_TIME);
> +       }
> +       ide_config_drive_speed(drive, speed);
> +}
> +
> +static void s3c_ide_tune_drive(ide_drive_t *drive, u8 pio)
> +{
> +       pio = ide_get_best_pio_mode(drive, 255, pio);
> +       (void)s3c_ide_tune_chipset(drive, (XFER_PIO_0 + pio));
> +}
> +
> +irqreturn_t s3c_irq_handler(int irq, void *dev_id)
> +{
> +       ide_hwif_t *hwif = (ide_hwif_t *)dev_id;
> +       struct s3c_ide_device *ide_dev =
> +                       (struct s3c_ide_device *)hwif->hwif_data;
> +       u32 reg = ide_readl(S3C_ATA_IRQ);
> +
> +       ide_writel(reg, S3C_ATA_IRQ);
> +       return ide_intr(irq, dev_id);
> +}
> +
> +static void s3c_ide_setup_timing_value(struct s3c_ide_device *ide_dev,
> +                                               u32 clk_rate)
> +{
> +       uint t1, t2, teoc, i;
> +       uint pio_t1[5] = { 70, 50, 30, 30, 30 };
> +       uint pio_t2[5] = { 290, 290, 290, 80, 70 };
> +       uint pio_teoc[5] = { 20, 20, 10, 10, 10 };
> +       ulong cycle_time = (uint) (1000000000 / clk_rate);
> +
> +       for (i = 0; i < 5; i++) {
> +               t1 = (pio_t1[i] / cycle_time) & 0x0f;
> +               t2 = (pio_t2[i] / cycle_time) & 0xff;
> +               teoc = (pio_teoc[i] / cycle_time) & 0xff;
> +               ide_dev->piotime[i] = (teoc << 12) | (t2 << 4) | t1;
> +       }
> +}
> +
> +static void change_mode_to_ata(struct s3c_ide_device *ide_dev)
> +{
> +       ide_writel(ide_readl(S3C_CFATA_MUX) | S3C_CFATA_MUX_TRUEIDE,
> +                       S3C_CFATA_MUX);
> +}
> +
> +static void init_ide_device(struct s3c_ide_device *ide_dev)
> +{
> +       change_mode_to_ata(ide_dev);
> +       set_endian_mode(ide_dev, 1);
> +       set_ata_enable(ide_dev, 1);
> +}
> +
> +static void s3c_ide_setup_ports(struct ide_hw *hw,
> +                               struct s3c_ide_device *ide_dev)
> +{
> +       int i;
> +       unsigned long *ata_regs = hw->io_ports_array;
> +
> +       /* S3C IDE controller does not include irq_addr port */
> +       for (i = 0; i < IDE_NR_PORTS-1; i++)
> +               *ata_regs++ = (ulong)ide_dev->regbase +
> +                               S3C_ATA_PIO_DTR + (i << 2);
> +}
> +
> +static u8 s3c_cable_detect(ide_hwif_t *hwif)
> +{
> +       return ATA_CBL_PATA40;
> +}
> +
> +static const struct ide_port_ops s3c_port_ops = {
> +       .set_pio_mode = s3c_ide_tune_drive,
> +       .set_dma_mode = s3c_ide_tune_chipset,
> +       .cable_detect = s3c_cable_detect,
> +};
> +
> +static const struct ide_tp_ops s3c_ide_tp_ops = {
> +       .exec_command   = s3c_ide_exec_command,
> +       .read_status    = s3c_ide_read_status,
> +       .read_altstatus = s3c_ide_read_altstatus,
> +       .write_devctl   = s3c_ide_write_devctl,
> +       .dev_select     = s3c_ide_dev_select,
> +       .tf_load        = s3c_ide_tf_load,
> +       .tf_read        = s3c_ide_tf_read,
> +       .input_data     = s3c_ide_input_data,
> +       .output_data    = s3c_ide_output_data,
> +};
> +
> +
> +static const struct ide_port_info s3c_port_info = {
> +       .name           = "s3c-ide",
> +       .port_ops       = &s3c_port_ops,
> +       .tp_ops         = &s3c_ide_tp_ops,
> +       .chipset        = ide_s3c,
> +       .host_flags     = IDE_HFLAG_MMIO | IDE_HFLAG_NO_IO_32BIT |
> +                               IDE_HFLAG_UNMASK_IRQS,
> +       .pio_mask       = ATA_PIO4,
> +};
> +
> +static int __devinit s3c_ide_probe(struct platform_device *pdev)
> +{
> +       struct resource *res;
> +       struct clk *ide_clk;
> +       struct s3c_ide_device *ide_dev = &s3c_ide_dev;
> +       struct s3c_ide_platdata *pdata;
> +       struct ide_host *host;
> +       int ret = 0;
> +       struct ide_hw hw, *hws[] = { &hw };
> +
> +       memset(&s3c_ide_dev, 0, sizeof(s3c_ide_dev));
> +       ide_dev->pdev = pdev;
> +       pdata = pdev->dev.platform_data;
> +       ide_dev->irq = platform_get_irq(pdev, 0);
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (res == NULL) {
> +               dev_err(&pdev->dev, "could not obtain base address \
> +                                       of controller\n");
> +               ret = -ENODEV;
> +               goto out;
> +       }
> +
> +       if (ide_dev->irq < 0) {
> +               dev_err(&pdev->dev, "could not obtain irq number\n");
> +               ret = -ENODEV;
> +               goto out;
> +       }
             check for valid irq shud be done immidiately after trying to get it
and before acquiring any other resource


> +       if (!request_mem_region(res->start, res->end - res->start + 1,
> +               pdev->name)) {
> +               dev_err(&pdev->dev, "could not obtain i/o address\n");
> +               ret = -EBUSY;
> +               goto out;
> +       }
> +
> +       ide_dev->regbase = ioremap(res->start, res->end - res->start + 1);
> +       if (ide_dev->regbase == 0) {
> +               dev_err(&pdev->dev, "could not remap i/o address\n");
> +               ret = -ENOMEM;
> +               goto out;
> +       }
> +
> +       ide_clk = clk_get(&pdev->dev, "cfcon");
> +       if (IS_ERR(ide_clk)) {
> +               dev_err(&pdev->dev, "failed to find clock source\n");
> +               ret = PTR_ERR(ide_clk);
> +               ide_clk = NULL;
> +               goto out;
> +       }
> +
> +       if (clk_enable(ide_clk)) {
> +               dev_err(&pdev->dev, "failed to enable clock source.\n");
> +               goto out;
> +       }
> +
> +       s3c_ide_setup_timing_value(ide_dev, clk_get_rate(ide_clk));
> +       if (pdata->setup_gpio)
> +               pdata->setup_gpio();
> +       init_ide_device(ide_dev);
> +
> +       ide_writel(0x1f, S3C_ATA_IRQ);
> +       ide_writel(0x1b, S3C_ATA_IRQ_MSK);
> +
> +       memset(&hw, 0, sizeof(hw));
> +       s3c_ide_setup_ports(&hw, ide_dev);
> +       hw.irq = ide_dev->irq;
> +       hw.dev = &pdev->dev;
> +
> +       host = ide_host_alloc(&s3c_port_info, hws, 1);
> +       if (!host) {
> +               dev_err(&pdev->dev, "failed to allocate ide host\n");
> +               goto out;
> +       }
> +
> +       host->irq_handler = s3c_irq_handler;
> +       host->ports[0]->hwif_data = (void *)ide_dev;
> +
> +       ret = ide_host_register(host, &s3c_port_info, hws);
> +       if (ret) {
> +               dev_err(&pdev->dev, "failed to register ide host\n");
> +               ide_host_free(host);
> +               goto out;
> +       }
> +
> +       s3c_ide_dev.hwif = host->ports[0];
> +       platform_set_drvdata(pdev, host);
> +       printk(KERN_INFO "S3C IDE driver configured for PIO_ONLY mode\n");
> +
> +out:
> +       return ret;
> +}
Since you call 'goto out' upon some call failure, the label 'out' shud
 undo all the successful operations before hitting the failure.
It shud rather be out1, out2 etc jumped at from various locations during
initialization.


> +static int __devexit s3c_ide_remove(struct platform_device *pdev)
> +{
> +       struct ide_host *host = platform_get_drvdata(pdev);
> +       struct resource *res;
> +       struct s3c_ide_device *ide_dev = host->ports[0]->hwif_data;
> +
> +       ide_host_remove(host);
> +       iounmap(ide_dev->regbase);
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       release_resource(res);

          It had rather be release_mem_region ?

> +       kfree(res);
> +
> +       return 0;
> +}
> +
> +static struct platform_driver s3c_ide_driver = {
> +       .probe          = s3c_ide_probe,
> +       .remove         = __devexit_p(s3c_ide_remove),
> +       .driver = {
> +               .name   = "s3c-ide",
> +               .owner  = THIS_MODULE,
> +       },
> +};
> +
> +static int __init s3c_ide_init(void)
> +{
> +       return platform_driver_register(&s3c_ide_driver);
> +}
> +
> +static void __exit s3c_ide_exit(void)
> +{
> +       platform_driver_unregister(&s3c_ide_driver);
> +}
> +
> +module_init(s3c_ide_init);
> +module_exit(s3c_ide_exit);
> +
> +MODULE_DESCRIPTION("Samsung S3C IDE Controller Driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:s3c-cfcon");
> diff --git a/include/linux/ide.h b/include/linux/ide.h
> index edc93a6..ff9eb0e 100644
> --- a/include/linux/ide.h
> +++ b/include/linux/ide.h
> @@ -164,7 +164,7 @@ enum {              ide_unknown,    ide_generic,    ide_pci,
>                ide_cmd640,     ide_dtc2278,    ide_ali14xx,
>                ide_qd65xx,     ide_umc8672,    ide_ht6560b,
>                ide_4drives,    ide_pmac,       ide_acorn,
> -               ide_au1xxx,     ide_palm3710
> +               ide_au1xxx,     ide_palm3710,   ide_s3c
>  };
>
>  typedef u8 hwif_chipset_t;
> --
> 1.5.3.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
--
To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux SoC Development]     [Linux Rockchip Development]     [Linux USB Development]     [Video for Linux]     [Linux Audio Users]     [Linux SCSI]     [Yosemite News]

  Powered by Linux