power domain: MediaTek: add power domain driver for MT7629 SoC

This adds a power domain driver for the Mediatek SCPSYS unit.

The System Control Processor System (SCPSYS) has several power
management related tasks in the system. The tasks include thermal
measurement, dynamic voltage frequency scaling (DVFS), interrupt
filter and lowlevel sleep control. The System Power Manager (SPM)
inside the SCPSYS is for the MTCMOS power domain control.

For now this driver only adds power domain support.

Signed-off-by: Ryder Lee <ryder.lee@mediatek.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
diff --git a/drivers/power/domain/Kconfig b/drivers/power/domain/Kconfig
index a08b428..93deaef 100644
--- a/drivers/power/domain/Kconfig
+++ b/drivers/power/domain/Kconfig
@@ -23,6 +23,13 @@
           Enable support for manipulating NXP i.MX8 on-SoC power domains via IPC
           requests to the SCU.
 
+config MTK_POWER_DOMAIN
+	bool "Enable the MediaTek power domain driver"
+	depends on POWER_DOMAIN && ARCH_MEDIATEK
+	help
+	  Enable support for manipulating MediaTek power domains via MMIO
+	  mapped registers.
+
 config MESON_GX_VPU_POWER_DOMAIN
 	bool "Enable Amlogic Meson GX VPU power domain driver"
 	depends on ARCH_MESON
diff --git a/drivers/power/domain/Makefile b/drivers/power/domain/Makefile
index b08d18f..695aafe 100644
--- a/drivers/power/domain/Makefile
+++ b/drivers/power/domain/Makefile
@@ -6,6 +6,7 @@
 obj-$(CONFIG_$(SPL_)POWER_DOMAIN) += power-domain-uclass.o
 obj-$(CONFIG_BCM6328_POWER_DOMAIN) += bcm6328-power-domain.o
 obj-$(CONFIG_IMX8_POWER_DOMAIN) += imx8-power-domain.o
+obj-$(CONFIG_MTK_POWER_DOMAIN) += mtk-power-domain.o
 obj-$(CONFIG_MESON_GX_VPU_POWER_DOMAIN) += meson-gx-pwrc-vpu.o
 obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain.o
 obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain-test.o
diff --git a/drivers/power/domain/mtk-power-domain.c b/drivers/power/domain/mtk-power-domain.c
new file mode 100644
index 0000000..ed4a718
--- /dev/null
+++ b/drivers/power/domain/mtk-power-domain.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 MediaTek Inc.
+ * Author: Ryder Lee <ryder.lee@mediatek.com>
+ */
+
+#include <clk.h>
+#include <common.h>
+#include <dm.h>
+#include <power-domain-uclass.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <asm/processor.h>
+#include <linux/iopoll.h>
+
+#include <dt-bindings/power/mt7629-power.h>
+
+#define SPM_EN			(0xb16 << 16 | 0x1)
+#define SPM_ETHSYS_PWR_CON	0x2e0
+#define SPM_HIF0_PWR_CON	0x2e4
+#define SPM_HIF1_PWR_CON	0x2e8
+#define SPM_PWR_STATUS		0x60c
+#define SPM_PWR_STATUS_2ND	0x610
+
+#define PWR_RST_B_BIT		BIT(0)
+#define PWR_ISO_BIT		BIT(1)
+#define PWR_ON_BIT		BIT(2)
+#define PWR_ON_2ND_BIT		BIT(3)
+#define PWR_CLK_DIS_BIT		BIT(4)
+
+#define PWR_STATUS_ETHSYS	BIT(24)
+#define PWR_STATUS_HIF0		BIT(25)
+#define PWR_STATUS_HIF1		BIT(26)
+
+/* Infrasys configuration */
+#define INFRA_TOPDCM_CTRL	0x10
+#define INFRA_TOPAXI_PROT_EN	0x220
+#define INFRA_TOPAXI_PROT_STA1	0x228
+
+#define DCM_TOP_EN		BIT(0)
+
+enum scp_domain_type {
+	SCPSYS_MT7629,
+};
+
+struct scp_domain;
+
+struct scp_domain_data {
+	struct scp_domain *scpd;
+	u32 sta_mask;
+	int ctl_offs;
+	u32 sram_pdn_bits;
+	u32 sram_pdn_ack_bits;
+	u32 bus_prot_mask;
+};
+
+struct scp_domain {
+	void __iomem *base;
+	void __iomem *infracfg;
+	enum scp_domain_type type;
+	struct scp_domain_data *data;
+};
+
+static struct scp_domain_data scp_domain_mt7629[] = {
+	[MT7629_POWER_DOMAIN_ETHSYS] = {
+		.sta_mask = PWR_STATUS_ETHSYS,
+		.ctl_offs = SPM_ETHSYS_PWR_CON,
+		.sram_pdn_bits = GENMASK(11, 8),
+		.sram_pdn_ack_bits = GENMASK(15, 12),
+		.bus_prot_mask = (BIT(3) | BIT(17)),
+	},
+	[MT7629_POWER_DOMAIN_HIF0] = {
+		.sta_mask = PWR_STATUS_HIF0,
+		.ctl_offs = SPM_HIF0_PWR_CON,
+		.sram_pdn_bits = GENMASK(11, 8),
+		.sram_pdn_ack_bits = GENMASK(15, 12),
+		.bus_prot_mask = GENMASK(25, 24),
+	},
+	[MT7629_POWER_DOMAIN_HIF1] = {
+		.sta_mask = PWR_STATUS_HIF1,
+		.ctl_offs = SPM_HIF1_PWR_CON,
+		.sram_pdn_bits = GENMASK(11, 8),
+		.sram_pdn_ack_bits = GENMASK(15, 12),
+		.bus_prot_mask = GENMASK(28, 26),
+	},
+};
+
+/**
+ * This function enables the bus protection bits for disabled power
+ * domains so that the system does not hang when some unit accesses the
+ * bus while in power down.
+ */
+static int mtk_infracfg_set_bus_protection(void __iomem *infracfg,
+					   u32 mask)
+{
+	u32 val;
+
+	clrsetbits_le32(infracfg + INFRA_TOPAXI_PROT_EN, mask, mask);
+
+	return readl_poll_timeout(infracfg + INFRA_TOPAXI_PROT_STA1, val,
+				  (val & mask) == mask, 100);
+}
+
+static int mtk_infracfg_clear_bus_protection(void __iomem *infracfg,
+					     u32 mask)
+{
+	u32 val;
+
+	clrbits_le32(infracfg + INFRA_TOPAXI_PROT_EN, mask);
+
+	return readl_poll_timeout(infracfg + INFRA_TOPAXI_PROT_STA1, val,
+				  !(val & mask), 100);
+}
+
+static int scpsys_domain_is_on(struct scp_domain_data *data)
+{
+	struct scp_domain *scpd = data->scpd;
+	u32 sta = readl(scpd->base + SPM_PWR_STATUS) &
+			data->sta_mask;
+	u32 sta2 = readl(scpd->base + SPM_PWR_STATUS_2ND) &
+			 data->sta_mask;
+
+	/*
+	 * A domain is on when both status bits are set. If only one is set
+	 * return an error. This happens while powering up a domain
+	 */
+	if (sta && sta2)
+		return true;
+	if (!sta && !sta2)
+		return false;
+
+	return -EINVAL;
+}
+
+static int scpsys_power_on(struct power_domain *power_domain)
+{
+	struct scp_domain *scpd = dev_get_priv(power_domain->dev);
+	struct scp_domain_data *data = &scpd->data[power_domain->id];
+	void __iomem *ctl_addr = scpd->base + data->ctl_offs;
+	u32 pdn_ack = data->sram_pdn_ack_bits;
+	u32 val;
+	int ret, tmp;
+
+	writel(SPM_EN, scpd->base);
+
+	val = readl(ctl_addr);
+	val |= PWR_ON_BIT;
+	writel(val, ctl_addr);
+
+	val |= PWR_ON_2ND_BIT;
+	writel(val, ctl_addr);
+
+	ret = readx_poll_timeout(scpsys_domain_is_on, data, tmp, tmp > 0,
+				 100);
+	if (ret < 0)
+		return ret;
+
+	val &= ~PWR_CLK_DIS_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~PWR_ISO_BIT;
+	writel(val, ctl_addr);
+
+	val |= PWR_RST_B_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~data->sram_pdn_bits;
+	writel(val, ctl_addr);
+
+	ret = readl_poll_timeout(ctl_addr, tmp, !(tmp & pdn_ack), 100);
+	if (ret < 0)
+		return ret;
+
+	if (data->bus_prot_mask) {
+		ret = mtk_infracfg_clear_bus_protection(scpd->infracfg,
+							data->bus_prot_mask);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int scpsys_power_off(struct power_domain *power_domain)
+{
+	struct scp_domain *scpd = dev_get_priv(power_domain->dev);
+	struct scp_domain_data *data = &scpd->data[power_domain->id];
+	void __iomem *ctl_addr = scpd->base + data->ctl_offs;
+	u32 pdn_ack = data->sram_pdn_ack_bits;
+	u32 val;
+	int ret, tmp;
+
+	if (data->bus_prot_mask) {
+		ret = mtk_infracfg_set_bus_protection(scpd->infracfg,
+						      data->bus_prot_mask);
+		if (ret)
+			return ret;
+	}
+
+	val = readl(ctl_addr);
+	val |= data->sram_pdn_bits;
+	writel(val, ctl_addr);
+
+	ret = readl_poll_timeout(ctl_addr, tmp, (tmp & pdn_ack) == pdn_ack,
+				 100);
+	if (ret < 0)
+		return ret;
+
+	val |= PWR_ISO_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~PWR_RST_B_BIT;
+	writel(val, ctl_addr);
+
+	val |= PWR_CLK_DIS_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~PWR_ON_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~PWR_ON_2ND_BIT;
+	writel(val, ctl_addr);
+
+	ret = readx_poll_timeout(scpsys_domain_is_on, data, tmp, !tmp, 100);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int scpsys_power_request(struct power_domain *power_domain)
+{
+	struct scp_domain *scpd = dev_get_priv(power_domain->dev);
+	struct scp_domain_data *data;
+
+	data = &scpd->data[power_domain->id];
+	data->scpd = scpd;
+
+	return 0;
+}
+
+static int scpsys_power_free(struct power_domain *power_domain)
+{
+	return 0;
+}
+
+static int mtk_power_domain_hook(struct udevice *dev)
+{
+	struct scp_domain *scpd = dev_get_priv(dev);
+
+	scpd->type = (enum scp_domain_type)dev_get_driver_data(dev);
+
+	switch (scpd->type) {
+	case SCPSYS_MT7629:
+		scpd->data = scp_domain_mt7629;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mtk_power_domain_probe(struct udevice *dev)
+{
+	struct ofnode_phandle_args args;
+	struct scp_domain *scpd = dev_get_priv(dev);
+	struct regmap *regmap;
+	struct clk_bulk bulk;
+	int err;
+
+	scpd->base = dev_read_addr_ptr(dev);
+	if (!scpd->base)
+		return -ENOENT;
+
+	err = mtk_power_domain_hook(dev);
+	if (err)
+		return err;
+
+	/* get corresponding syscon phandle */
+	err = dev_read_phandle_with_args(dev, "infracfg", NULL, 0, 0, &args);
+	if (err)
+		return err;
+
+	regmap = syscon_node_to_regmap(args.node);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	scpd->infracfg = regmap_get_range(regmap, 0);
+	if (!scpd->infracfg)
+		return -ENOENT;
+
+	/* enable Infra DCM */
+	setbits_le32(scpd->infracfg + INFRA_TOPDCM_CTRL, DCM_TOP_EN);
+
+	err = clk_get_bulk(dev, &bulk);
+	if (err)
+		return err;
+
+	return clk_enable_bulk(&bulk);
+}
+
+static const struct udevice_id mtk_power_domain_ids[] = {
+	{
+		.compatible = "mediatek,mt7629-scpsys",
+		.data = SCPSYS_MT7629,
+	},
+	{ /* sentinel */ }
+};
+
+struct power_domain_ops mtk_power_domain_ops = {
+	.free = scpsys_power_free,
+	.off = scpsys_power_off,
+	.on = scpsys_power_on,
+	.request = scpsys_power_request,
+};
+
+U_BOOT_DRIVER(mtk_power_domain) = {
+	.name = "mtk_power_domain",
+	.id = UCLASS_POWER_DOMAIN,
+	.ops = &mtk_power_domain_ops,
+	.probe = mtk_power_domain_probe,
+	.of_match = mtk_power_domain_ids,
+	.priv_auto_alloc_size = sizeof(struct scp_domain),
+};