mtd: nand: omap: Clean up device tree support
[cascardo/linux.git] / drivers / mtd / nand / omap2.c
index c553f78..35b8f33 100644 (file)
 #include <linux/slab.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/of_mtd.h>
 
 #include <linux/mtd/nand_bch.h>
 #include <linux/platform_data/elm.h>
 
+#include <linux/omap-gpmc.h>
 #include <linux/platform_data/mtd-nand-omap2.h>
 
 #define        DRIVER_NAME     "omap2-nand"
@@ -151,13 +153,17 @@ static struct nand_hw_control omap_gpmc_controller = {
 };
 
 struct omap_nand_info {
-       struct omap_nand_platform_data  *pdata;
        struct nand_chip                nand;
        struct platform_device          *pdev;
 
        int                             gpmc_cs;
-       unsigned long                   phys_base;
+       bool                            dev_ready;
+       enum nand_io                    xfer_type;
+       int                             devsize;
        enum omap_ecc                   ecc_opt;
+       struct device_node              *elm_of_node;
+
+       unsigned long                   phys_base;
        struct completion               comp;
        struct dma_chan                 *dma;
        int                             gpmc_irq_fifo;
@@ -168,12 +174,14 @@ struct omap_nand_info {
        } iomode;
        u_char                          *buf;
        int                                     buf_len;
+       /* Interface to GPMC */
        struct gpmc_nand_regs           reg;
+       struct gpmc_nand_ops            *ops;
+       bool                            flash_bbt;
        /* generated at runtime depending on ECC algorithm and layout selected */
        struct nand_ecclayout           oobinfo;
        /* fields specific for BCHx_HW ECC scheme */
        struct device                   *elm_dev;
-       struct device_node              *of_node;
 };
 
 static inline struct omap_nand_info *mtd_to_omap(struct mtd_info *mtd)
@@ -288,14 +296,13 @@ static void omap_write_buf8(struct mtd_info *mtd, const u_char *buf, int len)
 {
        struct omap_nand_info *info = mtd_to_omap(mtd);
        u_char *p = (u_char *)buf;
-       u32     status = 0;
+       bool status;
 
        while (len--) {
                iowrite8(*p++, info->nand.IO_ADDR_W);
                /* wait until buffer is available for write */
                do {
-                       status = readl(info->reg.gpmc_status) &
-                                       STATUS_BUFF_EMPTY;
+                       status = info->ops->nand_writebuffer_empty();
                } while (!status);
        }
 }
@@ -323,7 +330,7 @@ static void omap_write_buf16(struct mtd_info *mtd, const u_char * buf, int len)
 {
        struct omap_nand_info *info = mtd_to_omap(mtd);
        u16 *p = (u16 *) buf;
-       u32     status = 0;
+       bool status;
        /* FIXME try bursts of writesw() or DMA ... */
        len >>= 1;
 
@@ -331,8 +338,7 @@ static void omap_write_buf16(struct mtd_info *mtd, const u_char * buf, int len)
                iowrite16(*p++, info->nand.IO_ADDR_W);
                /* wait until buffer is available for write */
                do {
-                       status = readl(info->reg.gpmc_status) &
-                                       STATUS_BUFF_EMPTY;
+                       status = info->ops->nand_writebuffer_empty();
                } while (!status);
        }
 }
@@ -1630,7 +1636,7 @@ static bool omap2_nand_ecc_check(struct omap_nand_info *info,
                        "CONFIG_MTD_NAND_OMAP_BCH not enabled\n");
                return false;
        }
-       if (ecc_needs_elm && !is_elm_present(info, pdata->elm_of_node)) {
+       if (ecc_needs_elm && !is_elm_present(info, info->elm_of_node)) {
                dev_err(&info->pdev->dev, "ELM not available\n");
                return false;
        }
@@ -1638,10 +1644,86 @@ static bool omap2_nand_ecc_check(struct omap_nand_info *info,
        return true;
 }
 
+static const char * const nand_xfer_types[] = {
+       [NAND_OMAP_PREFETCH_POLLED] = "prefetch-polled",
+       [NAND_OMAP_POLLED] = "polled",
+       [NAND_OMAP_PREFETCH_DMA] = "prefetch-dma",
+       [NAND_OMAP_PREFETCH_IRQ] = "prefetch-irq",
+};
+
+static int omap_get_dt_info(struct device *dev, struct omap_nand_info *info)
+{
+       struct device_node *child = dev->of_node;
+       int i;
+       const char *s;
+       u32 cs;
+
+       if (of_property_read_u32(child, "reg", &cs) < 0) {
+               dev_err(dev, "reg not found in DT\n");
+               return -EINVAL;
+       }
+
+       info->gpmc_cs = cs;
+
+       /* detect availability of ELM module. Won't be present pre-OMAP4 */
+       info->elm_of_node = of_parse_phandle(child, "ti,elm-id", 0);
+       if (!info->elm_of_node)
+               dev_dbg(dev, "ti,elm-id not in DT\n");
+
+       /* select ecc-scheme for NAND */
+       if (of_property_read_string(child, "ti,nand-ecc-opt", &s)) {
+               dev_err(dev, "ti,nand-ecc-opt not found\n");
+               return -EINVAL;
+       }
+
+       if (!strcmp(s, "sw")) {
+               info->ecc_opt = OMAP_ECC_HAM1_CODE_SW;
+       } else if (!strcmp(s, "ham1") ||
+                  !strcmp(s, "hw") || !strcmp(s, "hw-romcode")) {
+               info->ecc_opt = OMAP_ECC_HAM1_CODE_HW;
+       } else if (!strcmp(s, "bch4")) {
+               if (info->elm_of_node)
+                       info->ecc_opt = OMAP_ECC_BCH4_CODE_HW;
+               else
+                       info->ecc_opt = OMAP_ECC_BCH4_CODE_HW_DETECTION_SW;
+       } else if (!strcmp(s, "bch8")) {
+               if (info->elm_of_node)
+                       info->ecc_opt = OMAP_ECC_BCH8_CODE_HW;
+               else
+                       info->ecc_opt = OMAP_ECC_BCH8_CODE_HW_DETECTION_SW;
+       } else if (!strcmp(s, "bch16")) {
+               info->ecc_opt = OMAP_ECC_BCH16_CODE_HW;
+       } else {
+               dev_err(dev, "unrecognized value for ti,nand-ecc-opt\n");
+               return -EINVAL;
+       }
+
+       /* select data transfer mode */
+       if (!of_property_read_string(child, "ti,nand-xfer-type", &s)) {
+               for (i = 0; i < ARRAY_SIZE(nand_xfer_types); i++) {
+                       if (!strcasecmp(s, nand_xfer_types[i])) {
+                               info->xfer_type = i;
+                               goto next;
+                       }
+               }
+
+               dev_err(dev, "unrecognized value for ti,nand-xfer-type\n");
+               return -EINVAL;
+       }
+
+next:
+       of_get_nand_on_flash_bbt(child);
+
+       if (of_get_nand_bus_width(child) == 16)
+               info->devsize = NAND_BUSWIDTH_16;
+
+       return 0;
+}
+
 static int omap_nand_probe(struct platform_device *pdev)
 {
        struct omap_nand_info           *info;
-       struct omap_nand_platform_data  *pdata;
+       struct omap_nand_platform_data  *pdata = NULL;
        struct mtd_info                 *mtd;
        struct nand_chip                *nand_chip;
        struct nand_ecclayout           *ecclayout;
@@ -1651,30 +1733,47 @@ static int omap_nand_probe(struct platform_device *pdev)
        unsigned                        sig;
        unsigned                        oob_index;
        struct resource                 *res;
-
-       pdata = dev_get_platdata(&pdev->dev);
-       if (pdata == NULL) {
-               dev_err(&pdev->dev, "platform data missing\n");
-               return -ENODEV;
-       }
+       struct device                   *dev = &pdev->dev;
 
        info = devm_kzalloc(&pdev->dev, sizeof(struct omap_nand_info),
                                GFP_KERNEL);
        if (!info)
                return -ENOMEM;
 
+       info->pdev = pdev;
+
+       if (dev->of_node) {
+               if (omap_get_dt_info(dev, info))
+                       return -EINVAL;
+       } else {
+               pdata = dev_get_platdata(&pdev->dev);
+               if (!pdata) {
+                       dev_err(&pdev->dev, "platform data missing\n");
+                       return -EINVAL;
+               }
+
+               info->gpmc_cs = pdata->cs;
+               info->reg = pdata->reg;
+               info->ecc_opt = pdata->ecc_opt;
+               info->dev_ready = pdata->dev_ready;
+               info->xfer_type = pdata->xfer_type;
+               info->devsize = pdata->devsize;
+               info->elm_of_node = pdata->elm_of_node;
+               info->flash_bbt = pdata->flash_bbt;
+       }
+
        platform_set_drvdata(pdev, info);
+       info->ops = gpmc_omap_get_nand_ops(&info->reg, info->gpmc_cs);
+       if (!info->ops) {
+               dev_err(&pdev->dev, "Failed to get GPMC->NAND interface\n");
+               return -ENODEV;
+       }
 
-       info->pdev              = pdev;
-       info->gpmc_cs           = pdata->cs;
-       info->reg               = pdata->reg;
-       info->of_node           = pdata->of_node;
-       info->ecc_opt           = pdata->ecc_opt;
        nand_chip               = &info->nand;
        mtd                     = nand_to_mtd(nand_chip);
        mtd->dev.parent         = &pdev->dev;
        nand_chip->ecc.priv     = NULL;
-       nand_set_flash_node(nand_chip, pdata->of_node);
+       nand_set_flash_node(nand_chip, dev->of_node);
 
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        nand_chip->IO_ADDR_R = devm_ioremap_resource(&pdev->dev, res);
@@ -1695,7 +1794,7 @@ static int omap_nand_probe(struct platform_device *pdev)
         * chip delay which is slightly more than tR (AC Timing) of the NAND
         * device and read status register until you get a failure or success
         */
-       if (pdata->dev_ready) {
+       if (info->dev_ready) {
                nand_chip->dev_ready = omap_dev_ready;
                nand_chip->chip_delay = 0;
        } else {
@@ -1703,21 +1802,22 @@ static int omap_nand_probe(struct platform_device *pdev)
                nand_chip->chip_delay = 50;
        }
 
-       if (pdata->flash_bbt)
+       if (info->flash_bbt)
                nand_chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
        else
                nand_chip->options |= NAND_SKIP_BBTSCAN;
 
        /* scan NAND device connected to chip controller */
-       nand_chip->options |= pdata->devsize & NAND_BUSWIDTH_16;
+       nand_chip->options |= info->devsize & NAND_BUSWIDTH_16;
        if (nand_scan_ident(mtd, 1, NULL)) {
-               dev_err(&info->pdev->dev, "scan failed, may be bus-width mismatch\n");
+               dev_err(&info->pdev->dev,
+                       "scan failed, may be bus-width mismatch\n");
                err = -ENXIO;
                goto return_error;
        }
 
        /* re-populate low-level callbacks based on xfer modes */
-       switch (pdata->xfer_type) {
+       switch (info->xfer_type) {
        case NAND_OMAP_PREFETCH_POLLED:
                nand_chip->read_buf   = omap_read_buf_pref;
                nand_chip->write_buf  = omap_write_buf_pref;
@@ -1797,7 +1897,7 @@ static int omap_nand_probe(struct platform_device *pdev)
 
        default:
                dev_err(&pdev->dev,
-                       "xfer_type(%d) not supported!\n", pdata->xfer_type);
+                       "xfer_type(%d) not supported!\n", info->xfer_type);
                err = -EINVAL;
                goto return_error;
        }
@@ -1807,13 +1907,19 @@ static int omap_nand_probe(struct platform_device *pdev)
                goto return_error;
        }
 
+       /*
+        * Bail out earlier to let NAND_ECC_SOFT code create its own
+        * ecclayout instead of using ours.
+        */
+       if (info->ecc_opt == OMAP_ECC_HAM1_CODE_SW) {
+               nand_chip->ecc.mode = NAND_ECC_SOFT;
+               goto scan_tail;
+       }
+
        /* populate MTD interface based on ECC scheme */
        ecclayout               = &info->oobinfo;
+       nand_chip->ecc.layout   = ecclayout;
        switch (info->ecc_opt) {
-       case OMAP_ECC_HAM1_CODE_SW:
-               nand_chip->ecc.mode = NAND_ECC_SOFT;
-               break;
-
        case OMAP_ECC_HAM1_CODE_HW:
                pr_info("nand: using OMAP_ECC_HAM1_CODE_HW\n");
                nand_chip->ecc.mode             = NAND_ECC_HW;
@@ -1861,10 +1967,7 @@ static int omap_nand_probe(struct platform_device *pdev)
                ecclayout->oobfree->offset      = 1 +
                                ecclayout->eccpos[ecclayout->eccbytes - 1] + 1;
                /* software bch library is used for locating errors */
-               nand_chip->ecc.priv             = nand_bch_init(mtd,
-                                                       nand_chip->ecc.size,
-                                                       nand_chip->ecc.bytes,
-                                                       &ecclayout);
+               nand_chip->ecc.priv             = nand_bch_init(mtd);
                if (!nand_chip->ecc.priv) {
                        dev_err(&info->pdev->dev, "unable to use BCH library\n");
                        err = -EINVAL;
@@ -1925,10 +2028,7 @@ static int omap_nand_probe(struct platform_device *pdev)
                ecclayout->oobfree->offset      = 1 +
                                ecclayout->eccpos[ecclayout->eccbytes - 1] + 1;
                /* software bch library is used for locating errors */
-               nand_chip->ecc.priv             = nand_bch_init(mtd,
-                                                       nand_chip->ecc.size,
-                                                       nand_chip->ecc.bytes,
-                                                       &ecclayout);
+               nand_chip->ecc.priv             = nand_bch_init(mtd);
                if (!nand_chip->ecc.priv) {
                        dev_err(&info->pdev->dev, "unable to use BCH library\n");
                        err = -EINVAL;
@@ -2002,9 +2102,6 @@ static int omap_nand_probe(struct platform_device *pdev)
                goto return_error;
        }
 
-       if (info->ecc_opt == OMAP_ECC_HAM1_CODE_SW)
-               goto scan_tail;
-
        /* all OOB bytes from oobfree->offset till end off OOB are free */
        ecclayout->oobfree->length = mtd->oobsize - ecclayout->oobfree->offset;
        /* check if NAND device's OOB is enough to store ECC signatures */
@@ -2015,7 +2112,6 @@ static int omap_nand_probe(struct platform_device *pdev)
                err = -EINVAL;
                goto return_error;
        }
-       nand_chip->ecc.layout = ecclayout;
 
 scan_tail:
        /* second phase scan */
@@ -2024,7 +2120,10 @@ scan_tail:
                goto return_error;
        }
 
-       mtd_device_register(mtd, pdata->parts, pdata->nr_parts);
+       if (dev->of_node)
+               mtd_device_register(mtd, NULL, 0);
+       else
+               mtd_device_register(mtd, pdata->parts, pdata->nr_parts);
 
        platform_set_drvdata(pdev, mtd);
 
@@ -2055,11 +2154,17 @@ static int omap_nand_remove(struct platform_device *pdev)
        return 0;
 }
 
+static const struct of_device_id omap_nand_ids[] = {
+       { .compatible = "ti,omap2-nand", },
+       {},
+};
+
 static struct platform_driver omap_nand_driver = {
        .probe          = omap_nand_probe,
        .remove         = omap_nand_remove,
        .driver         = {
                .name   = DRIVER_NAME,
+               .of_match_table = of_match_ptr(omap_nand_ids),
        },
 };