[PATCH] S3C: ide: Add Samsung S3C IDE controller driver
Thomas Abraham
thomas.ab at samsung.com
Sun Nov 1 01:07:09 EDT 2009
This patch adds Samsung S3C IDE controller driver. This driver supports
PIO and UDMA modes of data transfer.
Note: This patch depends the following patch series.
[PATCH 0/7] S3C64XX: Add platform support for Samsung S3C IDE controller driver
Signed-off-by: Abhilash Kesavan <a.kesavan <at> samsung.com>
Signed-off-by: Thomas Abraham <thomas.ab <at> samsung.com>
---
drivers/ide/Kconfig | 9 +
drivers/ide/Makefile | 1 +
drivers/ide/ide-proc.c | 1 +
drivers/ide/s3c-ide.c | 822 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/ide.h | 2 +-
5 files changed, 834 insertions(+), 1 deletions(-)
create mode 100644 drivers/ide/s3c-ide.c
diff --git a/drivers/ide/Kconfig b/drivers/ide/Kconfig
index 9a5d0aa..35610bc 100644
--- a/drivers/ide/Kconfig
+++ b/drivers/ide/Kconfig
@@ -292,6 +292,15 @@ config BLK_DEV_IDEPNP
would like the kernel to automatically detect and activate
it, say Y here.
+config BLK_DEV_IDE_S3C
+ tristate "Samsung S3C IDE Controller"
+ depends on PLAT_S3C64XX
+ select BLK_DEV_IDEDMA
+ help
+ Say Y here if you want to support onchip CF/IDE controller
+ in Samsung SoC. It will be configured for True IDE mode with
+ support for PIO and UDMA mode of data transfer.
+
config BLK_DEV_IDEDMA_SFF
bool
diff --git a/drivers/ide/Makefile b/drivers/ide/Makefile
index 81df925..a64a723 100644
--- a/drivers/ide/Makefile
+++ b/drivers/ide/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_BLK_DEV_SIIMAGE) += siimage.o
obj-$(CONFIG_BLK_DEV_SIS5513) += sis5513.o
obj-$(CONFIG_BLK_DEV_SL82C105) += sl82c105.o
obj-$(CONFIG_BLK_DEV_SLC90E66) += slc90e66.o
+obj-$(CONFIG_BLK_DEV_IDE_S3C) += s3c-ide.o
obj-$(CONFIG_BLK_DEV_TC86C001) += tc86c001.o
obj-$(CONFIG_BLK_DEV_TRIFLEX) += triflex.o
obj-$(CONFIG_BLK_DEV_TRM290) += trm290.o
diff --git a/drivers/ide/ide-proc.c b/drivers/ide/ide-proc.c
index 3242698..d18478f 100644
--- a/drivers/ide/ide-proc.c
+++ b/drivers/ide/ide-proc.c
@@ -51,6 +51,7 @@ static int proc_ide_read_imodel
case ide_au1xxx: name = "au1xxx"; break;
case ide_palm3710: name = "palm3710"; break;
case ide_acorn: name = "acorn"; break;
+ case ide_s3c: name = "s3c-ide"; break;
default: name = "(unknown)"; break;
}
len = sprintf(page, "%s\n", name);
diff --git a/drivers/ide/s3c-ide.c b/drivers/ide/s3c-ide.c
new file mode 100644
index 0000000..027af17
--- /dev/null
+++ b/drivers/ide/s3c-ide.c
@@ -0,0 +1,822 @@
+/*
+ * 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 controller driver supports PIO and
+ * UDMA data transfer modes.
+ *
+ * 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>
+
+/*
+ * defines ide controller data transfer commands
+ */
+enum {
+ CMD_STOP,
+ CMD_START,
+ CMD_ABORT,
+ CMD_CONTINUE
+};
+
+/*
+ * defines the transfer class
+ */
+enum {
+ XFER_MODE_PIO_CPU,
+ XFER_MODE_PIO_DMA,
+ XFER_MODE_UDMA
+};
+
+/*
+ * defines the bus state
+ */
+enum {
+ BS_IDLE,
+ BS_BUSYW,
+ BS_PREP,
+ BS_BUSYR,
+ BS_PAUSER,
+ BS_PAUSEW,
+ BS_PAUSER2
+};
+
+struct dma_queue_t {
+ ulong addr;
+ ulong len;
+};
+
+/*
+ * struct s3c_ide_device - instance of ide controller device.
+ */
+struct s3c_ide_device {
+ struct platform_device *pdev;
+ struct clk *ide_clk;
+ ide_hwif_t *hwif;
+ int irq;
+ ulong piotime[5];
+ ulong udmatime[5];
+ void __iomem *regbase;
+ u32 index; /* current queue index */
+ u32 queue_size; /* total queue size requested */
+ struct dma_queue_t table[PRD_ENTRIES];
+ u32 dma_mode; /* in DMA session */
+};
+
+static inline void ide_writel(struct s3c_ide_device *dev, u32 value, u32 reg)
+{
+ writel(value, dev->regbase + reg);
+}
+
+static inline u32 ide_readl(struct s3c_ide_device *dev, u32 reg)
+{
+ return readl(dev->regbase + reg);
+}
+
+/*
+ * waits until the IDE controller is able to perform next read/write
+ * operation to the disk.
+ */
+static int wait_for_host_ready(struct s3c_ide_device *ide_dev)
+{
+ ulong timeout;
+
+ /* wait for maximum of 20 msec */
+ timeout = jiffies + msecs_to_jiffies(20);
+ while (time_before(jiffies, timeout)) {
+ if ((ide_readl(ide_dev, S3C_ATA_FIFO_STATUS) >> 28) == 0)
+ return 0;
+ }
+ dev_err(&ide_dev->pdev->dev,
+ "ide controller not ready for next taskfile operation");
+ return -1;
+}
+
+/*
+ * writes to one of the task file registers.
+ */
+static void ide_outb(ide_hwif_t *hwif, u8 addr, ulong reg)
+{
+ struct s3c_ide_device *ide_dev = hwif->hwif_data;
+
+ wait_for_host_ready(ide_dev);
+ __raw_writeb(addr, reg);
+}
+
+/*
+ * reads from one of the task file registers.
+ */
+static u8 ide_inb(ide_hwif_t *hwif, ulong reg)
+{
+ struct s3c_ide_device *ide_dev = 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;
+}
+
+/*
+ * following are ide_tp_ops functions implemented by the IDE
+ * cotnroller driver.
+ */
+static void s3c_ide_exec_command(ide_hwif_t *hwif, u8 cmd)
+{
+ ide_outb(hwif, cmd, hwif->io_ports.command_addr);
+}
+
+static u8 s3c_ide_read_status(ide_hwif_t *hwif)
+{
+ return ide_inb(hwif, hwif->io_ports.status_addr);
+}
+
+static u8 s3c_ide_read_altstatus(ide_hwif_t *hwif)
+{
+ return ide_inb(hwif, hwif->io_ports.ctl_addr);
+}
+
+static void s3c_ide_write_devctl(ide_hwif_t *hwif, u8 ctl)
+{
+ 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;
+ 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 = 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 = 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)
+ ide_outb(hwif, tf->feature, io_ports->feature_addr);
+ if (valid & IDE_VALID_NSECT)
+ ide_outb(hwif, tf->nsect, io_ports->nsect_addr);
+ if (valid & IDE_VALID_LBAL)
+ ide_outb(hwif, tf->lbal, io_ports->lbal_addr);
+ if (valid & IDE_VALID_LBAM)
+ ide_outb(hwif, tf->lbam, io_ports->lbam_addr);
+ if (valid & IDE_VALID_LBAH)
+ ide_outb(hwif, tf->lbah, io_ports->lbah_addr);
+ if (valid & IDE_VALID_DEVICE)
+ ide_outb(hwif, tf->device, io_ports->device_addr);
+}
+
+static 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 = ide_inb(hwif, io_ports->feature_addr);
+ if (valid & IDE_VALID_NSECT)
+ tf->nsect = ide_inb(hwif, io_ports->nsect_addr);
+ if (valid & IDE_VALID_LBAL)
+ tf->lbal = ide_inb(hwif, io_ports->lbal_addr);
+ if (valid & IDE_VALID_LBAM)
+ tf->lbam = ide_inb(hwif, io_ports->lbam_addr);
+ if (valid & IDE_VALID_LBAH)
+ tf->lbah = ide_inb(hwif, io_ports->lbah_addr);
+ if (valid & IDE_VALID_DEVICE)
+ tf->device = ide_inb(hwif, io_ports->device_addr);
+}
+
+/*
+ * wait for a specified ide bus state
+ */
+static int wait_for_bus_state(struct s3c_ide_device *ide_dev, u8 state)
+{
+ u32 status, current_state;
+ ulong timeout;
+
+ timeout = jiffies + msecs_to_jiffies(100);
+ while (time_before(jiffies, timeout)) {
+ status = ide_readl(ide_dev, S3C_BUS_FIFO_STATUS);
+ current_state = (status >> 16) & 0x7;
+ if (current_state == state) {
+ if ((state == BS_PAUSER2) || (state == BS_IDLE)) {
+ if (status & 0xFFFF)
+ continue;
+ }
+ }
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * reads a ide disk device register
+ */
+static u8 read_dev_reg(struct s3c_ide_device *ide_dev, u32 reg)
+{
+ u8 temp;
+
+ wait_for_host_ready(ide_dev);
+ temp = __raw_readb(ide_dev->regbase + reg);
+ wait_for_host_ready(ide_dev);
+ temp = __raw_readb(ide_dev->regbase + S3C_ATA_PIO_RDATA);
+ return temp;
+}
+
+/*
+ * sets the data transfer mode
+ */
+static void set_xfer_mode(struct s3c_ide_device *ide_dev, u8 mode, int rw)
+{
+ u32 reg = ide_readl(ide_dev, S3C_ATA_CFG) & ~(0x39c);
+
+ reg |= mode << 2;
+ if (mode && rw)
+ reg |= 0x10;
+ if (mode == XFER_MODE_UDMA)
+ reg |= 0x380;
+ ide_writel(ide_dev, reg, S3C_ATA_CFG);
+}
+
+static void set_xfer_command(struct s3c_ide_device *ide_dev, u8 cmd)
+{
+ wait_for_host_ready(ide_dev);
+ ide_writel(ide_dev, cmd, S3C_ATA_CMD);
+}
+
+/* Building the Scatter Gather Table */
+static int ide_build_dmatable(ide_drive_t *drive, struct ide_cmd *cmd)
+{
+ int i, count = 0, nents = cmd->sg_nents;
+ ide_hwif_t *hwif = drive->hwif;
+ struct request *rq = hwif->rq;
+ struct scatterlist *sg;
+ u32 addr_reg, size_reg;
+ struct s3c_ide_device *ide_dev = hwif->hwif_data;
+
+ if (rq_data_dir(rq) == WRITE) {
+ addr_reg = S3C_ATA_SBUF_START;
+ size_reg = S3C_ATA_SBUF_SIZE;
+ } else {
+ addr_reg = S3C_ATA_TBUF_START;
+ size_reg = S3C_ATA_TBUF_SIZE;
+ }
+
+ /* save information for interrupt context */
+ if (nents > 1)
+ ide_dev->dma_mode = 1;
+ if (!nents)
+ return 0;
+
+ /* fill the descriptors */
+ sg = hwif->sg_table;
+ for (i = 0, sg = hwif->sg_table; i < nents && sg_dma_len(sg);
+ i++, sg++) {
+ ide_dev->table[i].addr = sg_dma_address(sg);
+ ide_dev->table[i].len = sg_dma_len(sg);
+ count += ide_dev->table[i].len;
+ }
+ ide_dev->table[i].addr = 0;
+ ide_dev->table[i].len = 0;
+ ide_dev->queue_size = i;
+
+ ide_writel(ide_dev, ide_dev->table[0].len - 0x1, size_reg);
+ ide_writel(ide_dev, ide_dev->table[0].addr, addr_reg);
+
+ ide_dev->index = 1;
+ ide_writel(ide_dev, count, S3C_ATA_XFR_NUM);
+ return 1;
+}
+
+/*
+ * wait for a specified status of the ide disk status
+ */
+static int wait_for_disk_status(ide_drive_t *drive, u8 status)
+{
+ u8 csd;
+ ulong timeout;
+ struct s3c_ide_device *ide_dev = drive->hwif->hwif_data;
+
+ timeout = jiffies + msecs_to_jiffies(1000);
+ while (time_before(jiffies, timeout)) {
+ csd = read_dev_reg(ide_dev, S3C_ATA_PIO_CSD);
+ if ((csd == status) || (csd & ATA_ERR))
+ return 0;
+ }
+
+ dev_err(&ide_dev->pdev->dev,
+ "timeout occured while waiting for disk status");
+ return -1;
+}
+
+/*
+ * following are the ide_dma_ops functions implemented by the ide driver
+ */
+static int s3c_ide_dma_init(ide_hwif_t *hwif, const struct ide_port_info *d)
+{
+ return 0;
+}
+
+static void s3c_ide_dma_host_set(ide_drive_t *drive, int on)
+{
+ return;
+}
+
+static int s3c_ide_dma_setup(ide_drive_t *drive, struct ide_cmd *cmd)
+{
+ if (!ide_build_dmatable(drive, cmd))
+ return 1;
+
+ drive->waiting_for_dma = 1;
+ return 0;
+}
+
+static void s3c_ide_dma_start(ide_drive_t *drive)
+{
+ struct request *rq = drive->hwif->rq;
+ uint rw = (rq_data_dir(rq) == WRITE);
+ struct s3c_ide_device *ide_dev = drive->hwif->hwif_data;
+
+ wait_for_disk_status(drive, DRIVE_READY|ATA_DRQ);
+ ide_writel(ide_dev, 0x3, S3C_ATA_IRQ_MSK);
+ set_xfer_mode(ide_dev, XFER_MODE_UDMA, rw);
+ set_xfer_command(ide_dev, CMD_START);
+ return;
+}
+
+static int s3c_ide_dma_end(ide_drive_t *drive)
+{
+ struct s3c_ide_device *ide_dev = drive->hwif->hwif_data;
+
+ if (wait_for_host_ready(ide_dev))
+ return 1;
+
+ if ((ide_readl(ide_dev, S3C_BUS_FIFO_STATUS) >> 16) == BS_PAUSEW)
+ ide_writel(ide_dev, CMD_CONTINUE, S3C_ATA_CMD);
+
+ if (wait_for_bus_state(ide_dev, BS_IDLE))
+ return 1;
+
+ ide_writel(ide_dev, 0x3, S3C_ATA_IRQ_MSK);
+ set_xfer_mode(ide_dev, XFER_MODE_PIO_CPU, 0);
+ if (wait_for_disk_status(drive, DRIVE_READY))
+ return 1;
+ drive->waiting_for_dma = 0;
+ return 0;
+}
+
+static void s3c_ide_dma_lostirq(ide_drive_t *drive)
+{
+ struct s3c_ide_device *ide_dev = drive->hwif->hwif_data;
+ dev_err(&ide_dev->pdev->dev, "irq lost");
+}
+
+
+static int s3c_ide_dma_test_irq(ide_drive_t *drive)
+{
+ return 1;
+}
+
+static void set_ata_enable(struct s3c_ide_device *ide_dev, u8 state)
+{
+ u32 temp = ide_readl(ide_dev, S3C_ATA_CTRL);
+ temp = state ? temp | 1 : temp & ~1;
+ ide_writel(ide_dev, temp , S3C_ATA_CTRL);
+}
+
+static void set_endian_mode(struct s3c_ide_device *ide_dev, u8 mode)
+{
+ u32 reg = ide_readl(ide_dev, S3C_ATA_CFG);
+ reg = mode ? (reg & ~S3C_ATA_CFG_SWAP) : (reg | S3C_ATA_CFG_SWAP);
+ ide_writel(ide_dev, reg, S3C_ATA_CFG);
+}
+
+/*
+ * This function selects the maximum possible transfer speed.
+ */
+static u8 ide_ratefilter(ide_drive_t *drive, u8 speed)
+{
+ if (drive->media != ide_disk)
+ return min(speed, (u8) XFER_PIO_4);
+
+ switch (speed) {
+ case XFER_UDMA_6:
+ case XFER_UDMA_5:
+ speed = XFER_UDMA_4;
+ break;
+ case XFER_UDMA_4:
+ case XFER_UDMA_3:
+ case XFER_UDMA_2:
+ case XFER_UDMA_1:
+ case XFER_UDMA_0:
+ break;
+ default:
+ speed = min(speed, (u8) XFER_PIO_4);
+ break;
+ }
+ return speed;
+}
+
+/*
+ * 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 = hwif->hwif_data;
+ u8 speed = ide_ratefilter(drive, xferspeed);
+ u32 ata_cfg;
+
+ /* IORDY is enabled for modes > PIO2 */
+ if (XFER_PIO_0 >= speed && speed <= XFER_PIO_4) {
+ ata_cfg = ide_readl(ide_dev, 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(ide_dev, ata_cfg, S3C_ATA_CFG);
+ ide_writel(ide_dev, ide_dev->piotime[speed - XFER_PIO_0],
+ S3C_ATA_PIO_TIME);
+ } else {
+ ide_writel(ide_dev, ide_dev->piotime[0], S3C_ATA_PIO_TIME);
+ ide_writel(ide_dev, ide_dev->udmatime[speed - XFER_UDMA_0],
+ S3C_ATA_UDMA_TIME);
+ set_endian_mode(ide_dev, 1);
+ }
+ 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));
+}
+
+static 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 = hwif->hwif_data;
+ u32 reg = ide_readl(ide_dev, S3C_ATA_IRQ);
+ u32 len, addr;
+ u32 stat;
+
+ ide_writel(ide_dev, reg, S3C_ATA_IRQ);
+ if (ide_dev->dma_mode) {
+ len = ide_dev->table[ide_dev->index].len - 1;
+ addr = ide_dev->table[ide_dev->index].addr;
+ if (reg & 0x10)
+ wait_for_bus_state(ide_dev, BS_PAUSER2);
+ else if (reg & 0x08) {
+ wait_for_bus_state(ide_dev, BS_PAUSEW);
+ ide_writel(ide_dev, len, S3C_ATA_TBUF_SIZE);
+ ide_writel(ide_dev, addr, S3C_ATA_TBUF_START);
+ } else
+ return 1;
+
+ ide_writel(ide_dev, len, S3C_ATA_SBUF_SIZE);
+ ide_writel(ide_dev, addr, S3C_ATA_SBUF_START);
+
+ if (ide_dev->queue_size == ++ide_dev->index)
+ ide_dev->dma_mode = 0;
+
+ ide_writel(ide_dev, CMD_CONTINUE, S3C_ATA_CMD);
+ return 1;
+ }
+
+ stat = ide_readl(ide_dev, S3C_BUS_FIFO_STATUS) >> 16;
+ if (stat == BS_PAUSER2)
+ ide_writel(ide_dev, CMD_CONTINUE, S3C_ATA_CMD);
+ else if (stat == BS_PAUSEW)
+ ide_writel(ide_dev, CMD_CONTINUE, S3C_ATA_CMD);
+
+ return ide_intr(irq, dev_id);
+}
+
+static void setup_timing_value(struct s3c_ide_device *ide_dev, u32 clk_rate)
+{
+ u32 t1, t2, teoc, i;
+ u32 uTdvh1, uTdvs, uTrp, uTss, uTackenv;
+ ulong cycle_time = (uint)(1000000000 / clk_rate);
+
+ /* transfer timing for PIO mode */
+ 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 };
+
+ /* transfer timing for UDMA mode */
+ uint uUdmaTdvh[5] = { 50, 32, 29, 25, 24 };
+ uint uUdmaTdvs[5] = { 70, 48, 31, 20, 7 };
+ uint uUdmaTrp[5] = { 160, 125, 100, 100, 100 };
+ uint uUdmaTss[5] = { 50, 50, 50, 50, 50 };
+ uint uUdmaTackenvMin[5] = { 20, 20, 20, 20, 20 };
+
+ /* calculate pio_time register value for all PIO modes */
+ 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;
+ }
+
+ /* calculate udma_time register value for all udma modes */
+ for (i = 0; i < 5; i++) {
+ uTdvh1 = (uUdmaTdvh[i] / cycle_time) & 0x0f;
+ uTdvs = (uUdmaTdvs[i] / cycle_time) & 0xff;
+ uTrp = (uUdmaTrp[i] / cycle_time) & 0xff;
+ uTss = (uUdmaTss[i] / cycle_time) & 0x0f;
+ uTackenv = (uUdmaTackenvMin[i] / cycle_time) & 0x0f;
+ ide_dev->udmatime[i] = (uTdvh1 << 24) | (uTdvs << 16) |
+ (uTrp << 8) | (uTss << 4) | uTackenv;
+ }
+}
+
+static void change_mode_to_ata(struct s3c_ide_device *ide_dev)
+{
+ ide_writel(ide_dev, ide_readl(ide_dev,
+ 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 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_PATA80;
+}
+
+static const struct ide_port_ops s3c_ide_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_dma_ops s3c_ide_dma_ops = {
+ .dma_host_set = s3c_ide_dma_host_set,
+ .dma_setup = s3c_ide_dma_setup,
+ .dma_start = s3c_ide_dma_start,
+ .dma_end = s3c_ide_dma_end,
+ .dma_test_irq = s3c_ide_dma_test_irq,
+ .dma_lost_irq = s3c_ide_dma_lostirq,
+};
+
+static const struct ide_port_info s3c_port_info = {
+ .name = "s3c-ide",
+ .init_dma = s3c_ide_dma_init,
+ .dma_ops = &s3c_ide_dma_ops,
+ .port_ops = &s3c_ide_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,
+ .udma_mask = ATA_UDMA4
+};
+
+static int __devinit s3c_ide_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct s3c_ide_device *ide_dev;
+ struct s3c_ide_platdata *pdata = pdev->dev.platform_data;
+ struct ide_host *host;
+ int ret = 0;
+ struct ide_hw hw, *hws[] = { &hw };
+
+ ide_dev = kzalloc(sizeof(struct s3c_ide_device), GFP_KERNEL);
+ if (!ide_dev) {
+ dev_err(&pdev->dev, "no memory for s3c device instance\n");
+ return -ENOMEM;
+ }
+ ide_dev->pdev = pdev;
+
+ ide_dev->irq = platform_get_irq(pdev, 0);
+ if (ide_dev->irq < 0) {
+ dev_err(&pdev->dev, "could not obtain irq number\n");
+ ret = -ENODEV;
+ goto release_device_mem;
+ }
+
+ 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 release_device_mem;
+ }
+
+ 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 release_device_mem;
+ }
+
+ 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 release_mem;
+ }
+
+ ide_dev->ide_clk = clk_get(&pdev->dev, "cfcon");
+ if (IS_ERR(ide_dev->ide_clk)) {
+ dev_err(&pdev->dev, "failed to find clock source\n");
+ ret = PTR_ERR(ide_dev->ide_clk);
+ ide_dev->ide_clk = NULL;
+ goto unmap;
+ }
+
+ if (clk_enable(ide_dev->ide_clk)) {
+ dev_err(&pdev->dev, "failed to enable clock source.\n");
+ ret = -ENODEV;
+ goto clkerr;
+ }
+
+ setup_timing_value(ide_dev, clk_get_rate(ide_dev->ide_clk));
+ if (pdata && (pdata->setup_gpio))
+ pdata->setup_gpio();
+ init_ide_device(ide_dev);
+
+ ide_writel(ide_dev, 0x1f, S3C_ATA_IRQ);
+ ide_writel(ide_dev, 0x1b, S3C_ATA_IRQ_MSK);
+
+ memset(&hw, 0, sizeof(hw));
+ 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");
+ ret = -ENOMEM;
+ goto stop_clk;
+ }
+
+ host->irq_handler = s3c_irq_handler;
+ host->ports[0]->hwif_data = (void *)ide_dev;
+ ide_dev->hwif = host->ports[0];
+ platform_set_drvdata(pdev, host);
+
+ ret = ide_host_register(host, &s3c_port_info, hws);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ide host\n");
+ goto dealloc_ide_host;
+ }
+
+ return 0;
+
+dealloc_ide_host:
+ ide_host_free(host);
+stop_clk:
+ clk_disable(ide_dev->ide_clk);
+clkerr:
+ clk_put(ide_dev->ide_clk);
+unmap:
+ iounmap(ide_dev->regbase);
+release_mem:
+ release_mem_region(res->start, res->end - res->start + 1);
+release_device_mem:
+ kfree(ide_dev);
+ return ret;
+}
+
+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);
+ clk_disable(ide_dev->ide_clk);
+ clk_put(ide_dev->ide_clk);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, res->end - res->start + 1);
+ kfree(ide_dev);
+ 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
More information about the linux-arm-kernel
mailing list