Merge tag 'nand/for-5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux...
authorRichard Weinberger <richard@nod.at>
Sat, 6 Jul 2019 20:51:56 +0000 (22:51 +0200)
committerRichard Weinberger <richard@nod.at>
Sat, 6 Jul 2019 20:51:56 +0000 (22:51 +0200)
NAND core changes:
- use longest matching pattern in ->exec_op() default parser
- export NAND operation tracer
- add flag to indicate panic_write in MTD
- use kzalloc() instead of kmalloc() and memset()

Raw NAND controller drivers changes:
- brcmnand:
  * fix BCH ECC layout for large page NAND parts
  * fallback to detected ecc-strength, ecc-step-size
  * when oops in progress use pio and interrupt polling
  * code refactor code to introduce helper functions
  * add support for v7.3 controller
- FSMC:
  * use nand_op_trace for operation tracing
- GPMI:
  * move all driver code into single file
  * various cleanups (including dmaengine changes)
  * use runtime PM to manage clocks
  * implement exec_op
- MTK:
  * correct low level time calculation of r/w cycle
  * improve data sampling timing for read cycle
  * add validity check for CE# pin setting
  * fix wrongly assigned OOB buffer pointer issue
  * re-license MTK NAND driver as Dual MIT/GPL
- STM32:
  * manage the get_irq error case
  * increase DMA completion timeouts

Raw NAND chips drivers changes:
- Macronix: add read-retry support

Onenand driver changes:
- add support for 8Gb datasize chips
- avoid fall-through warnings

SPI-NAND changes:
- define macros for page-read ops with three-byte addresses
- add support for two-byte device IDs and then for GigaDevice
  GD5F1GQ4UFxxG
- add initial support for Paragon PN26G0xA
- handle the case where the last page read has bitflips

12 files changed:
Documentation/devicetree/bindings/mtd/cypress,hyperflash.txt [new file with mode: 0644]
Documentation/devicetree/bindings/mtd/ti,am654-hbmc.txt [new file with mode: 0644]
MAINTAINERS
drivers/mtd/Kconfig
drivers/mtd/Makefile
drivers/mtd/chips/cfi_cmdset_0002.c
drivers/mtd/hyperbus/Kconfig [new file with mode: 0644]
drivers/mtd/hyperbus/Makefile [new file with mode: 0644]
drivers/mtd/hyperbus/hbmc-am654.c [new file with mode: 0644]
drivers/mtd/hyperbus/hyperbus-core.c [new file with mode: 0644]
include/linux/mtd/cfi.h
include/linux/mtd/hyperbus.h [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/mtd/cypress,hyperflash.txt b/Documentation/devicetree/bindings/mtd/cypress,hyperflash.txt
new file mode 100644 (file)
index 0000000..ad42f4d
--- /dev/null
@@ -0,0 +1,13 @@
+Bindings for HyperFlash NOR flash chips compliant with Cypress HyperBus
+specification and supports Cypress CFI specification 1.5 command set.
+
+Required properties:
+- compatible : "cypress,hyperflash", "cfi-flash" for HyperFlash NOR chips
+- reg : Address of flash's memory map
+
+Example:
+
+       flash@0 {
+               compatible = "cypress,hyperflash", "cfi-flash";
+               reg = <0x0 0x4000000>;
+       };
diff --git a/Documentation/devicetree/bindings/mtd/ti,am654-hbmc.txt b/Documentation/devicetree/bindings/mtd/ti,am654-hbmc.txt
new file mode 100644 (file)
index 0000000..faa81c2
--- /dev/null
@@ -0,0 +1,51 @@
+Bindings for HyperBus Memory Controller (HBMC) on TI's K3 family of SoCs
+
+Required properties:
+- compatible : "ti,am654-hbmc" for AM654 SoC
+- reg : Two entries:
+       First entry pointed to the register space of HBMC controller
+       Second entry pointing to the memory map region dedicated for
+       MMIO access to attached flash devices
+- ranges : Address translation from offset within CS to allocated MMIO
+          space in SoC
+
+Optional properties:
+- mux-controls : phandle to the multiplexer that controls selection of
+                HBMC vs OSPI inside Flash SubSystem (FSS). Default is OSPI,
+                if property is absent.
+                See Documentation/devicetree/bindings/mux/reg-mux.txt
+                for mmio-mux binding details
+
+Example:
+
+       system-controller@47000000 {
+               compatible = "syscon", "simple-mfd";
+               reg = <0x0 0x47000000 0x0 0x100>;
+               #address-cells = <2>;
+               #size-cells = <2>;
+               ranges;
+
+               hbmc_mux: multiplexer {
+                       compatible = "mmio-mux";
+                       #mux-control-cells = <1>;
+                       mux-reg-masks = <0x4 0x2>; /* 0: reg 0x4, bit 1 */
+               };
+       };
+
+       hbmc: hyperbus@47034000 {
+               compatible = "ti,am654-hbmc";
+               reg = <0x0 0x47034000 0x0 0x100>,
+                       <0x5 0x00000000 0x1 0x0000000>;
+               power-domains = <&k3_pds 55>;
+               #address-cells = <2>;
+               #size-cells = <1>;
+               ranges = <0x0 0x0 0x5 0x00000000 0x4000000>, /* CS0 - 64MB */
+                        <0x1 0x0 0x5 0x04000000 0x4000000>; /* CS1 - 64MB */
+               mux-controls = <&hbmc_mux 0>;
+
+               /* Slave flash node */
+               flash@0,0 {
+                       compatible = "cypress,hyperflash", "cfi-flash";
+                       reg = <0x0 0x0 0x4000000>;
+               };
+       };
index a6954776a37e70818fa8902f13986f86e735f243..e58f44e3f1037bd7dfaef3d4acd40e9c5d3657b0 100644 (file)
@@ -7317,6 +7317,14 @@ F:       include/uapi/linux/hyperv.h
 F:     tools/hv/
 F:     Documentation/ABI/stable/sysfs-bus-vmbus
 
+HYPERBUS SUPPORT
+M:     Vignesh Raghavendra <vigneshr@ti.com>
+S:     Supported
+F:     drivers/mtd/hyperbus/
+F:     include/linux/mtd/hyperbus.h
+F:     Documentation/devicetree/bindings/mtd/cypress,hyperflash.txt
+F:     Documentation/devicetree/bindings/mtd/ti,am654-hbmc.txt
+
 HYPERVISOR VIRTUAL CONSOLE DRIVER
 L:     linuxppc-dev@lists.ozlabs.org
 S:     Odd Fixes
index fb31a7f649a3d908989c69149bdaebca24f9f24f..80a6e2dcd085393f788acc823861a5e53d8d35dd 100644 (file)
@@ -274,4 +274,6 @@ source "drivers/mtd/spi-nor/Kconfig"
 
 source "drivers/mtd/ubi/Kconfig"
 
+source "drivers/mtd/hyperbus/Kconfig"
+
 endif # MTD
index 806287e80e842621f4560f09e611073bb380364e..62d649a959e28348f9b077ea871ae385cc259b49 100644 (file)
@@ -34,3 +34,4 @@ obj-y         += chips/ lpddr/ maps/ devices/ nand/ tests/
 
 obj-$(CONFIG_MTD_SPI_NOR)      += spi-nor/
 obj-$(CONFIG_MTD_UBI)          += ubi/
+obj-$(CONFIG_MTD_HYPERBUS)     += hyperbus/
index c8fa5906bdf9a2a3b9fb356032e59a7617cce165..f4da7bd552e93e1c1af534a6e705b799b348c4c9 100644 (file)
 #define SST49LF008A            0x005a
 #define AT49BV6416             0x00d6
 
+/*
+ * Status Register bit description. Used by flash devices that don't
+ * support DQ polling (e.g. HyperFlash)
+ */
+#define CFI_SR_DRB             BIT(7)
+#define CFI_SR_ESB             BIT(5)
+#define CFI_SR_PSB             BIT(4)
+#define CFI_SR_WBASB           BIT(3)
+#define CFI_SR_SLSB            BIT(1)
+
 static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
 static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
 static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
@@ -97,6 +107,50 @@ static struct mtd_chip_driver cfi_amdstd_chipdrv = {
        .module         = THIS_MODULE
 };
 
+/*
+ * Use status register to poll for Erase/write completion when DQ is not
+ * supported. This is indicated by Bit[1:0] of SoftwareFeatures field in
+ * CFI Primary Vendor-Specific Extended Query table 1.5
+ */
+static int cfi_use_status_reg(struct cfi_private *cfi)
+{
+       struct cfi_pri_amdstd *extp = cfi->cmdset_priv;
+       u8 poll_mask = CFI_POLL_STATUS_REG | CFI_POLL_DQ;
+
+       return extp->MinorVersion >= '5' &&
+               (extp->SoftwareFeatures & poll_mask) == CFI_POLL_STATUS_REG;
+}
+
+static void cfi_check_err_status(struct map_info *map, struct flchip *chip,
+                                unsigned long adr)
+{
+       struct cfi_private *cfi = map->fldrv_priv;
+       map_word status;
+
+       if (!cfi_use_status_reg(cfi))
+               return;
+
+       cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+       status = map_read(map, adr);
+
+       if (map_word_bitsset(map, status, CMD(0x3a))) {
+               unsigned long chipstatus = MERGESTATUS(status);
+
+               if (chipstatus & CFI_SR_ESB)
+                       pr_err("%s erase operation failed, status %lx\n",
+                              map->name, chipstatus);
+               if (chipstatus & CFI_SR_PSB)
+                       pr_err("%s program operation failed, status %lx\n",
+                              map->name, chipstatus);
+               if (chipstatus & CFI_SR_WBASB)
+                       pr_err("%s buffer program command aborted, status %lx\n",
+                              map->name, chipstatus);
+               if (chipstatus & CFI_SR_SLSB)
+                       pr_err("%s sector write protected, status %lx\n",
+                              map->name, chipstatus);
+       }
+}
 
 /* #define DEBUG_CFI_FEATURES */
 
@@ -742,10 +796,25 @@ static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd)
  * correctly and is therefore not done (particularly with interleaved chips
  * as each chip must be checked independently of the others).
  */
-static int __xipram chip_ready(struct map_info *map, unsigned long addr)
+static int __xipram chip_ready(struct map_info *map, struct flchip *chip,
+                              unsigned long addr)
 {
+       struct cfi_private *cfi = map->fldrv_priv;
        map_word d, t;
 
+       if (cfi_use_status_reg(cfi)) {
+               map_word ready = CMD(CFI_SR_DRB);
+               /*
+                * For chips that support status register, check device
+                * ready bit
+                */
+               cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi,
+                                cfi->device_type, NULL);
+               d = map_read(map, addr);
+
+               return map_word_andequal(map, d, ready, ready);
+       }
+
        d = map_read(map, addr);
        t = map_read(map, addr);
 
@@ -767,10 +836,30 @@ static int __xipram chip_ready(struct map_info *map, unsigned long addr)
  * as each chip must be checked independently of the others).
  *
  */
-static int __xipram chip_good(struct map_info *map, unsigned long addr, map_word expected)
+static int __xipram chip_good(struct map_info *map, struct flchip *chip,
+                             unsigned long addr, map_word expected)
 {
+       struct cfi_private *cfi = map->fldrv_priv;
        map_word oldd, curd;
 
+       if (cfi_use_status_reg(cfi)) {
+               map_word ready = CMD(CFI_SR_DRB);
+               map_word err = CMD(CFI_SR_PSB | CFI_SR_ESB);
+               /*
+                * For chips that support status register, check device
+                * ready bit and Erase/Program status bit to know if
+                * operation succeeded.
+                */
+               cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi,
+                                cfi->device_type, NULL);
+               curd = map_read(map, addr);
+
+               if (map_word_andequal(map, curd, ready, ready))
+                       return !map_word_bitsset(map, curd, err);
+
+               return 0;
+       }
+
        oldd = map_read(map, addr);
        curd = map_read(map, addr);
 
@@ -792,7 +881,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
 
        case FL_STATUS:
                for (;;) {
-                       if (chip_ready(map, adr))
+                       if (chip_ready(map, chip, adr))
                                break;
 
                        if (time_after(jiffies, timeo)) {
@@ -830,7 +919,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
                chip->state = FL_ERASE_SUSPENDING;
                chip->erase_suspended = 1;
                for (;;) {
-                       if (chip_ready(map, adr))
+                       if (chip_ready(map, chip, adr))
                                break;
 
                        if (time_after(jiffies, timeo)) {
@@ -1362,7 +1451,7 @@ static int do_otp_lock(struct map_info *map, struct flchip *chip, loff_t adr,
        /* wait for chip to become ready */
        timeo = jiffies + msecs_to_jiffies(2);
        for (;;) {
-               if (chip_ready(map, adr))
+               if (chip_ready(map, chip, adr))
                        break;
 
                if (time_after(jiffies, timeo)) {
@@ -1628,22 +1717,24 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
                        continue;
                }
 
-               if (time_after(jiffies, timeo) && !chip_ready(map, adr)){
+               if (time_after(jiffies, timeo) &&
+                   !chip_ready(map, chip, adr)) {
                        xip_enable(map, chip, adr);
                        printk(KERN_WARNING "MTD %s(): software timeout\n", __func__);
                        xip_disable(map, chip, adr);
                        break;
                }
 
-               if (chip_ready(map, adr))
+               if (chip_ready(map, chip, adr))
                        break;
 
                /* Latency issues. Drop the lock, wait a while and retry */
                UDELAY(map, chip, adr, 1);
        }
        /* Did we succeed? */
-       if (!chip_good(map, adr, datum)) {
+       if (!chip_good(map, chip, adr, datum)) {
                /* reset on all failures. */
+               cfi_check_err_status(map, chip, adr);
                map_write(map, CMD(0xF0), chip->start);
                /* FIXME - should have reset delay before continuing */
 
@@ -1881,10 +1972,11 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
                 * We check "time_after" and "!chip_good" before checking "chip_good" to avoid
                 * the failure due to scheduling.
                 */
-               if (time_after(jiffies, timeo) && !chip_good(map, adr, datum))
+               if (time_after(jiffies, timeo) &&
+                   !chip_good(map, chip, adr, datum))
                        break;
 
-               if (chip_good(map, adr, datum)) {
+               if (chip_good(map, chip, adr, datum)) {
                        xip_enable(map, chip, adr);
                        goto op_done;
                }
@@ -1901,6 +1993,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
         * See e.g.
         * http://www.spansion.com/Support/Application%20Notes/MirrorBit_Write_Buffer_Prog_Page_Buffer_Read_AN.pdf
         */
+       cfi_check_err_status(map, chip, adr);
        cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi,
                         cfi->device_type, NULL);
        cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi,
@@ -2018,7 +2111,7 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip,
         * If the driver thinks the chip is idle, and no toggle bits
         * are changing, then the chip is actually idle for sure.
         */
-       if (chip->state == FL_READY && chip_ready(map, adr))
+       if (chip->state == FL_READY && chip_ready(map, chip, adr))
                return 0;
 
        /*
@@ -2035,7 +2128,7 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip,
 
                /* wait for the chip to become ready */
                for (i = 0; i < jiffies_to_usecs(timeo); i++) {
-                       if (chip_ready(map, adr))
+                       if (chip_ready(map, chip, adr))
                                return 0;
 
                        udelay(1);
@@ -2099,14 +2192,15 @@ retry:
        map_write(map, datum, adr);
 
        for (i = 0; i < jiffies_to_usecs(uWriteTimeout); i++) {
-               if (chip_ready(map, adr))
+               if (chip_ready(map, chip, adr))
                        break;
 
                udelay(1);
        }
 
-       if (!chip_good(map, adr, datum)) {
+       if (!chip_good(map, chip, adr, datum)) {
                /* reset on all failures. */
+               cfi_check_err_status(map, chip, adr);
                map_write(map, CMD(0xF0), chip->start);
                /* FIXME - should have reset delay before continuing */
 
@@ -2300,7 +2394,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip)
                        chip->erase_suspended = 0;
                }
 
-               if (chip_good(map, adr, map_word_ff(map)))
+               if (chip_good(map, chip, adr, map_word_ff(map)))
                        break;
 
                if (time_after(jiffies, timeo)) {
@@ -2316,6 +2410,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip)
        /* Did we succeed? */
        if (ret) {
                /* reset on all failures. */
+               cfi_check_err_status(map, chip, adr);
                map_write(map, CMD(0xF0), chip->start);
                /* FIXME - should have reset delay before continuing */
 
@@ -2396,7 +2491,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
                        chip->erase_suspended = 0;
                }
 
-               if (chip_good(map, adr, map_word_ff(map)))
+               if (chip_good(map, chip, adr, map_word_ff(map)))
                        break;
 
                if (time_after(jiffies, timeo)) {
@@ -2412,6 +2507,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
        /* Did we succeed? */
        if (ret) {
                /* reset on all failures. */
+               cfi_check_err_status(map, chip, adr);
                map_write(map, CMD(0xF0), chip->start);
                /* FIXME - should have reset delay before continuing */
 
@@ -2533,8 +2629,6 @@ struct ppb_lock {
        int locked;
 };
 
-#define MAX_SECTORS                    512
-
 #define DO_XXLOCK_ONEBLOCK_LOCK                ((void *)1)
 #define DO_XXLOCK_ONEBLOCK_UNLOCK      ((void *)2)
 #define DO_XXLOCK_ONEBLOCK_GETLOCK     ((void *)3)
@@ -2589,7 +2683,7 @@ static int __maybe_unused do_ppb_xxlock(struct map_info *map,
         */
        timeo = jiffies + msecs_to_jiffies(2000);       /* 2s max (un)locking */
        for (;;) {
-               if (chip_ready(map, adr))
+               if (chip_ready(map, chip, adr))
                        break;
 
                if (time_after(jiffies, timeo)) {
@@ -2633,6 +2727,7 @@ static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs,
        int i;
        int sectors;
        int ret;
+       int max_sectors;
 
        /*
         * PPB unlocking always unlocks all sectors of the flash chip.
@@ -2640,7 +2735,11 @@ static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs,
         * first check the locking status of all sectors and save
         * it for future use.
         */
-       sect = kcalloc(MAX_SECTORS, sizeof(struct ppb_lock), GFP_KERNEL);
+       max_sectors = 0;
+       for (i = 0; i < mtd->numeraseregions; i++)
+               max_sectors += regions[i].numblocks;
+
+       sect = kcalloc(max_sectors, sizeof(struct ppb_lock), GFP_KERNEL);
        if (!sect)
                return -ENOMEM;
 
@@ -2689,9 +2788,9 @@ static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs,
                }
 
                sectors++;
-               if (sectors >= MAX_SECTORS) {
+               if (sectors >= max_sectors) {
                        printk(KERN_ERR "Only %d sectors for PPB locking supported!\n",
-                              MAX_SECTORS);
+                              max_sectors);
                        kfree(sect);
                        return -EINVAL;
                }
diff --git a/drivers/mtd/hyperbus/Kconfig b/drivers/mtd/hyperbus/Kconfig
new file mode 100644 (file)
index 0000000..cff6bbd
--- /dev/null
@@ -0,0 +1,23 @@
+menuconfig MTD_HYPERBUS
+       tristate "HyperBus support"
+       select MTD_CFI
+       select MTD_MAP_BANK_WIDTH_2
+       select MTD_CFI_AMDSTD
+       select MTD_COMPLEX_MAPPINGS
+       help
+         This is the framework for the HyperBus which can be used by
+         the HyperBus Controller driver to communicate with
+         HyperFlash. See Cypress HyperBus specification for more
+         details
+
+if MTD_HYPERBUS
+
+config HBMC_AM654
+       tristate "HyperBus controller driver for AM65x SoC"
+       select MULTIPLEXER
+       select MUX_MMIO
+       help
+        This is the driver for HyperBus controller on TI's AM65x and
+        other SoCs
+
+endif # MTD_HYPERBUS
diff --git a/drivers/mtd/hyperbus/Makefile b/drivers/mtd/hyperbus/Makefile
new file mode 100644 (file)
index 0000000..8a936e0
--- /dev/null
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_MTD_HYPERBUS)     += hyperbus-core.o
+obj-$(CONFIG_HBMC_AM654)       += hbmc-am654.o
diff --git a/drivers/mtd/hyperbus/hbmc-am654.c b/drivers/mtd/hyperbus/hbmc-am654.c
new file mode 100644 (file)
index 0000000..08d543b
--- /dev/null
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+// Author: Vignesh Raghavendra <vigneshr@ti.com>
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/hyperbus.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mux/consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/types.h>
+
+#define AM654_HBMC_CALIB_COUNT 25
+
+struct am654_hbmc_priv {
+       struct hyperbus_ctlr ctlr;
+       struct hyperbus_device hbdev;
+       struct mux_control *mux_ctrl;
+};
+
+static int am654_hbmc_calibrate(struct hyperbus_device *hbdev)
+{
+       struct map_info *map = &hbdev->map;
+       struct cfi_private cfi;
+       int count = AM654_HBMC_CALIB_COUNT;
+       int pass_count = 0;
+       int ret;
+
+       cfi.interleave = 1;
+       cfi.device_type = CFI_DEVICETYPE_X16;
+       cfi_send_gen_cmd(0xF0, 0, 0, map, &cfi, cfi.device_type, NULL);
+       cfi_send_gen_cmd(0x98, 0x55, 0, map, &cfi, cfi.device_type, NULL);
+
+       while (count--) {
+               ret = cfi_qry_present(map, 0, &cfi);
+               if (ret)
+                       pass_count++;
+               else
+                       pass_count = 0;
+               if (pass_count == 5)
+                       break;
+       }
+
+       cfi_qry_mode_off(0, map, &cfi);
+
+       return ret;
+}
+
+static const struct hyperbus_ops am654_hbmc_ops = {
+       .calibrate = am654_hbmc_calibrate,
+};
+
+static int am654_hbmc_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct am654_hbmc_priv *priv;
+       int ret;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, priv);
+
+       if (of_property_read_bool(dev->of_node, "mux-controls")) {
+               struct mux_control *control = devm_mux_control_get(dev, NULL);
+
+               if (IS_ERR(control))
+                       return PTR_ERR(control);
+
+               ret = mux_control_select(control, 1);
+               if (ret) {
+                       dev_err(dev, "Failed to select HBMC mux\n");
+                       return ret;
+               }
+               priv->mux_ctrl = control;
+       }
+
+       pm_runtime_enable(dev);
+       ret = pm_runtime_get_sync(dev);
+       if (ret < 0) {
+               pm_runtime_put_noidle(dev);
+               goto disable_pm;
+       }
+
+       priv->ctlr.dev = dev;
+       priv->ctlr.ops = &am654_hbmc_ops;
+       priv->hbdev.ctlr = &priv->ctlr;
+       priv->hbdev.np = of_get_next_child(dev->of_node, NULL);
+       ret = hyperbus_register_device(&priv->hbdev);
+       if (ret) {
+               dev_err(dev, "failed to register controller\n");
+               pm_runtime_put_sync(&pdev->dev);
+               goto disable_pm;
+       }
+
+       return 0;
+disable_pm:
+       pm_runtime_disable(dev);
+       if (priv->mux_ctrl)
+               mux_control_deselect(priv->mux_ctrl);
+       return ret;
+}
+
+static int am654_hbmc_remove(struct platform_device *pdev)
+{
+       struct am654_hbmc_priv *priv = platform_get_drvdata(pdev);
+       int ret;
+
+       ret = hyperbus_unregister_device(&priv->hbdev);
+       if (priv->mux_ctrl)
+               mux_control_deselect(priv->mux_ctrl);
+       pm_runtime_put_sync(&pdev->dev);
+       pm_runtime_disable(&pdev->dev);
+
+       return ret;
+}
+
+static const struct of_device_id am654_hbmc_dt_ids[] = {
+       {
+               .compatible = "ti,am654-hbmc",
+       },
+       { /* end of table */ }
+};
+
+MODULE_DEVICE_TABLE(of, am654_hbmc_dt_ids);
+
+static struct platform_driver am654_hbmc_platform_driver = {
+       .probe = am654_hbmc_probe,
+       .remove = am654_hbmc_remove,
+       .driver = {
+               .name = "hbmc-am654",
+               .of_match_table = am654_hbmc_dt_ids,
+       },
+};
+
+module_platform_driver(am654_hbmc_platform_driver);
+
+MODULE_DESCRIPTION("HBMC driver for AM654 SoC");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:hbmc-am654");
+MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
diff --git a/drivers/mtd/hyperbus/hyperbus-core.c b/drivers/mtd/hyperbus/hyperbus-core.c
new file mode 100644 (file)
index 0000000..6af9ea3
--- /dev/null
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+// Author: Vignesh Raghavendra <vigneshr@ti.com>
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/hyperbus.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/types.h>
+
+static struct hyperbus_device *map_to_hbdev(struct map_info *map)
+{
+       return container_of(map, struct hyperbus_device, map);
+}
+
+static map_word hyperbus_read16(struct map_info *map, unsigned long addr)
+{
+       struct hyperbus_device *hbdev = map_to_hbdev(map);
+       struct hyperbus_ctlr *ctlr = hbdev->ctlr;
+       map_word read_data;
+
+       read_data.x[0] = ctlr->ops->read16(hbdev, addr);
+
+       return read_data;
+}
+
+static void hyperbus_write16(struct map_info *map, map_word d,
+                            unsigned long addr)
+{
+       struct hyperbus_device *hbdev = map_to_hbdev(map);
+       struct hyperbus_ctlr *ctlr = hbdev->ctlr;
+
+       ctlr->ops->write16(hbdev, addr, d.x[0]);
+}
+
+static void hyperbus_copy_from(struct map_info *map, void *to,
+                              unsigned long from, ssize_t len)
+{
+       struct hyperbus_device *hbdev = map_to_hbdev(map);
+       struct hyperbus_ctlr *ctlr = hbdev->ctlr;
+
+       ctlr->ops->copy_from(hbdev, to, from, len);
+}
+
+static void hyperbus_copy_to(struct map_info *map, unsigned long to,
+                            const void *from, ssize_t len)
+{
+       struct hyperbus_device *hbdev = map_to_hbdev(map);
+       struct hyperbus_ctlr *ctlr = hbdev->ctlr;
+
+       ctlr->ops->copy_to(hbdev, to, from, len);
+}
+
+int hyperbus_register_device(struct hyperbus_device *hbdev)
+{
+       const struct hyperbus_ops *ops;
+       struct hyperbus_ctlr *ctlr;
+       struct device_node *np;
+       struct map_info *map;
+       struct resource res;
+       struct device *dev;
+       int ret;
+
+       if (!hbdev || !hbdev->np || !hbdev->ctlr || !hbdev->ctlr->dev) {
+               pr_err("hyperbus: please fill all the necessary fields!\n");
+               return -EINVAL;
+       }
+
+       np = hbdev->np;
+       ctlr = hbdev->ctlr;
+       if (!of_device_is_compatible(np, "cypress,hyperflash"))
+               return -ENODEV;
+
+       hbdev->memtype = HYPERFLASH;
+
+       ret = of_address_to_resource(np, 0, &res);
+       if (ret)
+               return ret;
+
+       dev = ctlr->dev;
+       map = &hbdev->map;
+       map->size = resource_size(&res);
+       map->virt = devm_ioremap_resource(dev, &res);
+       if (IS_ERR(map->virt))
+               return PTR_ERR(map->virt);
+
+       map->name = dev_name(dev);
+       map->bankwidth = 2;
+       map->device_node = np;
+
+       simple_map_init(map);
+       ops = ctlr->ops;
+       if (ops) {
+               if (ops->read16)
+                       map->read = hyperbus_read16;
+               if (ops->write16)
+                       map->write = hyperbus_write16;
+               if (ops->copy_to)
+                       map->copy_to = hyperbus_copy_to;
+               if (ops->copy_from)
+                       map->copy_from = hyperbus_copy_from;
+
+               if (ops->calibrate && !ctlr->calibrated) {
+                       ret = ops->calibrate(hbdev);
+                       if (!ret) {
+                               dev_err(dev, "Calibration failed\n");
+                               return -ENODEV;
+                       }
+                       ctlr->calibrated = true;
+               }
+       }
+
+       hbdev->mtd = do_map_probe("cfi_probe", map);
+       if (!hbdev->mtd) {
+               dev_err(dev, "probing of hyperbus device failed\n");
+               return -ENODEV;
+       }
+
+       hbdev->mtd->dev.parent = dev;
+       mtd_set_of_node(hbdev->mtd, np);
+
+       ret = mtd_device_register(hbdev->mtd, NULL, 0);
+       if (ret) {
+               dev_err(dev, "failed to register mtd device\n");
+               map_destroy(hbdev->mtd);
+               return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(hyperbus_register_device);
+
+int hyperbus_unregister_device(struct hyperbus_device *hbdev)
+{
+       int ret = 0;
+
+       if (hbdev && hbdev->mtd) {
+               ret = mtd_device_unregister(hbdev->mtd);
+               map_destroy(hbdev->mtd);
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(hyperbus_unregister_device);
+
+MODULE_DESCRIPTION("HyperBus Framework");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
index 208c87cf2e3e113ffcbb5bb7ed80922dab438bf6..c98a211086880a84c9c82abd5a6320ef363468fe 100644 (file)
@@ -219,6 +219,13 @@ struct cfi_pri_amdstd {
        uint8_t  VppMin;
        uint8_t  VppMax;
        uint8_t  TopBottom;
+       /* Below field are added from version 1.5 */
+       uint8_t  ProgramSuspend;
+       uint8_t  UnlockBypass;
+       uint8_t  SecureSiliconSector;
+       uint8_t  SoftwareFeatures;
+#define CFI_POLL_STATUS_REG    BIT(0)
+#define CFI_POLL_DQ            BIT(1)
 } __packed;
 
 /* Vendor-Specific PRI for Atmel chips (command set 0x0002) */
diff --git a/include/linux/mtd/hyperbus.h b/include/linux/mtd/hyperbus.h
new file mode 100644 (file)
index 0000000..2dfe659
--- /dev/null
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#ifndef __LINUX_MTD_HYPERBUS_H__
+#define __LINUX_MTD_HYPERBUS_H__
+
+#include <linux/mtd/map.h>
+
+enum hyperbus_memtype {
+       HYPERFLASH,
+       HYPERRAM,
+};
+
+/**
+ * struct hyperbus_device - struct representing HyperBus slave device
+ * @map: map_info struct for accessing MMIO HyperBus flash memory
+ * @np: pointer to HyperBus slave device node
+ * @mtd: pointer to MTD struct
+ * @ctlr: pointer to HyperBus controller struct
+ * @memtype: type of memory device: HyperFlash or HyperRAM
+ */
+
+struct hyperbus_device {
+       struct map_info map;
+       struct device_node *np;
+       struct mtd_info *mtd;
+       struct hyperbus_ctlr *ctlr;
+       enum hyperbus_memtype memtype;
+};
+
+/**
+ * struct hyperbus_ops - struct representing custom HyperBus operations
+ * @read16: read 16 bit of data from flash in a single burst. Used to read
+ *          from non default address space, such as ID/CFI space
+ * @write16: write 16 bit of data to flash in a single burst. Used to
+ *           send cmd to flash or write single 16 bit word at a time.
+ * @copy_from: copy data from flash memory
+ * @copy_to: copy data to flash memory
+ * @calibrate: calibrate HyperBus controller
+ */
+
+struct hyperbus_ops {
+       u16 (*read16)(struct hyperbus_device *hbdev, unsigned long addr);
+       void (*write16)(struct hyperbus_device *hbdev,
+                       unsigned long addr, u16 val);
+       void (*copy_from)(struct hyperbus_device *hbdev, void *to,
+                         unsigned long from, ssize_t len);
+       void (*copy_to)(struct hyperbus_device *dev, unsigned long to,
+                       const void *from, ssize_t len);
+       int (*calibrate)(struct hyperbus_device *dev);
+};
+
+/**
+ * struct hyperbus_ctlr - struct representing HyperBus controller
+ * @dev: pointer to HyperBus controller device
+ * @calibrated: flag to indicate ctlr calibration sequence is complete
+ * @ops: HyperBus controller ops
+ */
+struct hyperbus_ctlr {
+       struct device *dev;
+       bool calibrated;
+
+       const struct hyperbus_ops *ops;
+};
+
+/**
+ * hyperbus_register_device - probe and register a HyperBus slave memory device
+ * @hbdev: hyperbus_device struct with dev, np and ctlr field populated
+ *
+ * Return: 0 for success, others for failure.
+ */
+int hyperbus_register_device(struct hyperbus_device *hbdev);
+
+/**
+ * hyperbus_unregister_device - deregister HyperBus slave memory device
+ * @hbdev: hyperbus_device to be unregistered
+ *
+ * Return: 0 for success, others for failure.
+ */
+int hyperbus_unregister_device(struct hyperbus_device *hbdev);
+
+#endif /* __LINUX_MTD_HYPERBUS_H__ */