mtd: Add TI HyperBus Memory Controller driver

AM654/J721e has HyperBus Memory Controller that supports HyperFlash and
HyperRAM devices. It provides a memory mapped interface to interact with
these devices. Add a driver to support the same.
Driver calibrates the controller, setups up for MMIO access and probes
HyperFlash child node.

Signed-off-by: Vignesh Raghavendra <vigneshr@ti.com>
Reviewed-by: Stefan Roese <sr@denx.de>
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 0050fb2..37f379d 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -94,6 +94,13 @@
 	  This enables access to Hyperflash memory through the Renesas
 	  RCar Gen3 RPC controller.
 
+config HBMC_AM654
+	bool "HyperBus controller driver for AM65x SoC"
+	depends on SYSCON
+	help
+	 This is the driver for HyperBus controller on TI's AM65x and
+	 other SoCs
+
 source "drivers/mtd/nand/Kconfig"
 
 source "drivers/mtd/spi/Kconfig"
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 22ceda9..293079d 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -18,5 +18,6 @@
 obj-$(CONFIG_ST_SMI) += st_smi.o
 obj-$(CONFIG_STM32_FLASH) += stm32_flash.o
 obj-$(CONFIG_RENESAS_RPC_HF) += renesas_rpc_hf.o
+obj-$(CONFIG_HBMC_AM654) += hbmc-am654.o
 
 obj-y += nand/
diff --git a/drivers/mtd/hbmc-am654.c b/drivers/mtd/hbmc-am654.c
new file mode 100644
index 0000000..5a560f1
--- /dev/null
+++ b/drivers/mtd/hbmc-am654.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+// Author: Vignesh Raghavendra <vigneshr@ti.com>
+
+#include <common.h>
+#include <asm/io.h>
+#include <dm.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#define FSS_SYSC_REG	0x4
+
+#define HYPERBUS_CALIB_COUNT 25
+
+struct am654_hbmc_priv {
+	void __iomem *mmiobase;
+	bool calibrated;
+};
+
+/* Calibrate by looking for "QRY" string within the CFI space */
+static int am654_hyperbus_calibrate(struct udevice *dev)
+{
+	struct am654_hbmc_priv *priv = dev_get_priv(dev);
+	int count = HYPERBUS_CALIB_COUNT;
+	int pass_count = 0;
+	u16 qry[3];
+
+	if (priv->calibrated)
+		return 0;
+
+	writew(0xF0, priv->mmiobase);
+	writew(0x98, priv->mmiobase + 0xaa);
+
+	while (count--) {
+		qry[0] = readw(priv->mmiobase + 0x20);
+		qry[1] = readw(priv->mmiobase + 0x22);
+		qry[2] = readw(priv->mmiobase + 0x24);
+
+		if (qry[0] == 'Q' && qry[1] == 'R' && qry[2] == 'Y')
+			pass_count++;
+		else
+			pass_count = 0;
+		if (pass_count == 5)
+			break;
+	}
+	writew(0xF0, priv->mmiobase);
+	writew(0xFF, priv->mmiobase);
+
+	return pass_count == 5;
+}
+
+static int am654_select_hbmc(struct udevice *dev)
+{
+	struct regmap *regmap = syscon_get_regmap(dev_get_parent(dev));
+
+	return regmap_update_bits(regmap, FSS_SYSC_REG, 0x2, 0x2);
+}
+
+static int am654_hbmc_bind(struct udevice *dev)
+{
+	return dm_scan_fdt_dev(dev);
+}
+
+static int am654_hbmc_probe(struct udevice *dev)
+{
+	struct am654_hbmc_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	priv->mmiobase = devfdt_remap_addr_index(dev, 1);
+	if (dev_read_bool(dev, "mux-controls")) {
+		ret = am654_select_hbmc(dev);
+		if (ret) {
+			dev_err(dev, "Failed to select HBMC mux\n");
+			return ret;
+		}
+	}
+
+	if (!priv->calibrated) {
+		ret = am654_hyperbus_calibrate(dev);
+		if (!ret) {
+			dev_err(dev, "Calibration Failed\n");
+			return -EIO;
+		}
+	}
+	priv->calibrated = true;
+
+	return 0;
+}
+
+static const struct udevice_id am654_hbmc_dt_ids[] = {
+	{
+		.compatible = "ti,am654-hbmc",
+	},
+	{ /* end of table */ }
+};
+
+U_BOOT_DRIVER(hbmc_am654) = {
+	.name	= "hbmc-am654",
+	.id	= UCLASS_MTD,
+	.of_match = am654_hbmc_dt_ids,
+	.probe = am654_hbmc_probe,
+	.bind = am654_hbmc_bind,
+	.priv_auto_alloc_size = sizeof(struct am654_hbmc_priv),
+};