power: domain: Introduce driver for raw TI K3 PDs

Normally, power domains are handled via TI-SCI in K3 SoCs. However,
SPL is not going to have access to sysfw resources, so it must control
them directly. Add driver for supporting this.

Signed-off-by: Tero Kristo <t-kristo@ti.com>
Signed-off-by: Tero Kristo <kristo@kernel.org>
Reviewed-by: Jaehoon Chung <jh80.chung@samsung.com>
diff --git a/drivers/power/domain/ti-power-domain.c b/drivers/power/domain/ti-power-domain.c
new file mode 100644
index 0000000..56bc6fc
--- /dev/null
+++ b/drivers/power/domain/ti-power-domain.c
@@ -0,0 +1,368 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Texas Instruments power domain driver
+ *
+ * Copyright (C) 2020-2021 Texas Instruments Incorporated - http://www.ti.com/
+ *	Tero Kristo <t-kristo@ti.com>
+ */
+
+#include <asm/io.h>
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <power-domain-uclass.h>
+#include <soc.h>
+#include <k3-dev.h>
+#include <linux/iopoll.h>
+
+#define PSC_PTCMD		0x120
+#define PSC_PTSTAT		0x128
+#define PSC_PDSTAT		0x200
+#define PSC_PDCTL		0x300
+#define PSC_MDSTAT		0x800
+#define PSC_MDCTL		0xa00
+
+#define PDCTL_STATE_MASK		0x1
+#define PDCTL_STATE_OFF			0x0
+#define PDCTL_STATE_ON			0x1
+
+#define MDSTAT_STATE_MASK		0x3f
+#define MDSTAT_BUSY_MASK		0x30
+#define MDSTAT_STATE_SWRSTDISABLE	0x0
+#define MDSTAT_STATE_ENABLE		0x3
+
+#define LPSC_TIMEOUT		1000
+#define PD_TIMEOUT		1000
+
+static u32 psc_read(struct ti_psc *psc, u32 reg)
+{
+	u32 val;
+
+	val = readl(psc->base + reg);
+	debug("%s: 0x%x from %p\n", __func__, val, psc->base + reg);
+	return val;
+}
+
+static void psc_write(u32 val, struct ti_psc *psc, u32 reg)
+{
+	debug("%s: 0x%x to %p\n", __func__, val, psc->base + reg);
+	writel(val, psc->base + reg);
+}
+
+static u32 pd_read(struct ti_pd *pd, u32 reg)
+{
+	return psc_read(pd->psc, reg + 4 * pd->id);
+}
+
+static void pd_write(u32 val, struct ti_pd *pd, u32 reg)
+{
+	psc_write(val, pd->psc, reg + 4 * pd->id);
+}
+
+static u32 lpsc_read(struct ti_lpsc *lpsc, u32 reg)
+{
+	return psc_read(lpsc->psc, reg + 4 * lpsc->id);
+}
+
+static void lpsc_write(u32 val, struct ti_lpsc *lpsc, u32 reg)
+{
+	psc_write(val, lpsc->psc, reg + 4 * lpsc->id);
+}
+
+static const struct soc_attr ti_k3_soc_pd_data[] = {
+#if IS_ENABLED(CONFIG_SOC_K3_J721E)
+	{
+		.family = "J721E",
+		.data = &j721e_pd_platdata,
+	},
+	{
+		.family = "J7200",
+		.data = &j7200_pd_platdata,
+	},
+#endif
+	{ /* sentinel */ }
+};
+
+static int ti_power_domain_probe(struct udevice *dev)
+{
+	struct ti_k3_pd_platdata *data = dev_get_priv(dev);
+	const struct soc_attr *soc_match_data;
+	const struct ti_k3_pd_platdata *pdata;
+
+	printf("%s(dev=%p)\n", __func__, dev);
+
+	if (!data)
+		return -ENOMEM;
+
+	soc_match_data = soc_device_match(ti_k3_soc_pd_data);
+	if (!soc_match_data)
+		return -ENODEV;
+
+	pdata = (const struct ti_k3_pd_platdata *)soc_match_data->data;
+
+	data->psc = pdata->psc;
+	data->pd = pdata->pd;
+	data->lpsc = pdata->lpsc;
+	data->devs = pdata->devs;
+	data->num_psc = pdata->num_psc;
+	data->num_pd = pdata->num_pd;
+	data->num_lpsc = pdata->num_lpsc;
+	data->num_devs = pdata->num_devs;
+
+	return 0;
+}
+
+static int ti_pd_wait(struct ti_pd *pd)
+{
+	u32 ptstat;
+	int ret;
+
+	ret = readl_poll_timeout(pd->psc->base + PSC_PTSTAT, ptstat,
+				 !(ptstat & BIT(pd->id)), PD_TIMEOUT);
+
+	if (ret)
+		printf("%s: psc%d, pd%d failed to transition.\n", __func__,
+		       pd->psc->id, pd->id);
+
+	return ret;
+}
+
+static void ti_pd_transition(struct ti_pd *pd)
+{
+	psc_write(BIT(pd->id), pd->psc, PSC_PTCMD);
+}
+
+static u8 ti_pd_state(struct ti_pd *pd)
+{
+	return pd_read(pd, PSC_PDCTL) & PDCTL_STATE_MASK;
+}
+
+static int ti_pd_get(struct ti_pd *pd)
+{
+	u32 pdctl;
+	int ret;
+
+	pd->usecount++;
+
+	if (pd->usecount > 1)
+		return 0;
+
+	if (pd->depend) {
+		ret = ti_pd_get(pd->depend);
+		if (ret)
+			return ret;
+		ti_pd_transition(pd->depend);
+		ret = ti_pd_wait(pd->depend);
+		if (ret)
+			return ret;
+	}
+
+	pdctl = pd_read(pd, PSC_PDCTL);
+
+	if ((pdctl & PDCTL_STATE_MASK) == PDCTL_STATE_ON)
+		return 0;
+
+	debug("%s: enabling psc:%d, pd:%d\n", __func__, pd->psc->id, pd->id);
+
+	pdctl &= ~PDCTL_STATE_MASK;
+	pdctl |= PDCTL_STATE_ON;
+
+	pd_write(pdctl, pd, PSC_PDCTL);
+
+	return 0;
+}
+
+static int ti_pd_put(struct ti_pd *pd)
+{
+	u32 pdctl;
+	int ret;
+
+	pd->usecount--;
+
+	if (pd->usecount > 0)
+		return 0;
+
+	pdctl = pd_read(pd, PSC_PDCTL);
+	if ((pdctl & PDCTL_STATE_MASK) == PDCTL_STATE_OFF)
+		return 0;
+
+	pdctl &= ~PDCTL_STATE_MASK;
+	pdctl |= PDCTL_STATE_OFF;
+
+	debug("%s: disabling psc:%d, pd:%d\n", __func__, pd->psc->id, pd->id);
+
+	pd_write(pdctl, pd, PSC_PDCTL);
+
+	if (pd->depend) {
+		ti_pd_transition(pd);
+		ret = ti_pd_wait(pd);
+		if (ret)
+			return ret;
+
+		ret = ti_pd_put(pd->depend);
+		if (ret)
+			return ret;
+		ti_pd_transition(pd->depend);
+		ret = ti_pd_wait(pd->depend);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int ti_lpsc_wait(struct ti_lpsc *lpsc)
+{
+	u32 mdstat;
+	int ret;
+
+	ret = readl_poll_timeout(lpsc->psc->base + PSC_MDSTAT + lpsc->id * 4,
+				 mdstat,
+				 !(mdstat & MDSTAT_BUSY_MASK), LPSC_TIMEOUT);
+
+	if (ret)
+		printf("%s: module %d failed to transition.\n", __func__,
+		       lpsc->id);
+
+	return ret;
+}
+
+static u8 lpsc_get_state(struct ti_lpsc *lpsc)
+{
+	return lpsc_read(lpsc, PSC_MDCTL) & MDSTAT_STATE_MASK;
+}
+
+static int ti_lpsc_transition(struct ti_lpsc *lpsc, u8 state)
+{
+	struct ti_pd *psc_pd;
+	int ret;
+	u32 mdctl;
+
+	psc_pd = lpsc->pd;
+
+	if (state == MDSTAT_STATE_ENABLE) {
+		lpsc->usecount++;
+		if (lpsc->usecount > 1)
+			return 0;
+	} else {
+		lpsc->usecount--;
+		if (lpsc->usecount >= 1)
+			return 0;
+	}
+
+	debug("%s: transitioning psc:%d, lpsc:%d to %x\n", __func__,
+	      lpsc->psc->id, lpsc->id, state);
+
+	if (lpsc->depend)
+		ti_lpsc_transition(lpsc->depend, state);
+
+	mdctl = lpsc_read(lpsc, PSC_MDCTL);
+	if ((mdctl & MDSTAT_STATE_MASK) == state)
+		return 0;
+
+	if (state == MDSTAT_STATE_ENABLE)
+		ti_pd_get(psc_pd);
+	else
+		ti_pd_put(psc_pd);
+
+	mdctl &= ~MDSTAT_STATE_MASK;
+	mdctl |= state;
+
+	lpsc_write(mdctl, lpsc, PSC_MDCTL);
+
+	ti_pd_transition(psc_pd);
+	ret = ti_pd_wait(psc_pd);
+	if (ret)
+		return ret;
+
+	return ti_lpsc_wait(lpsc);
+}
+
+static int ti_power_domain_transition(struct power_domain *pd, u8 state)
+{
+	struct ti_lpsc *lpsc = pd->priv;
+
+	return ti_lpsc_transition(lpsc, state);
+}
+
+static int ti_power_domain_on(struct power_domain *pd)
+{
+	debug("%s(pd=%p, id=%lu)\n", __func__, pd, pd->id);
+
+	return ti_power_domain_transition(pd, MDSTAT_STATE_ENABLE);
+}
+
+static int ti_power_domain_off(struct power_domain *pd)
+{
+	debug("%s(pd=%p, id=%lu)\n", __func__, pd, pd->id);
+
+	return ti_power_domain_transition(pd, MDSTAT_STATE_SWRSTDISABLE);
+}
+
+static struct ti_lpsc *lpsc_lookup(struct ti_k3_pd_platdata *data, int id)
+{
+	int idx;
+
+	for (idx = 0; idx < data->num_devs; idx++)
+		if (data->devs[idx].id == id)
+			return data->devs[idx].lpsc;
+
+	return NULL;
+}
+
+static int ti_power_domain_of_xlate(struct power_domain *pd,
+				    struct ofnode_phandle_args *args)
+{
+	struct ti_k3_pd_platdata *data = dev_get_priv(pd->dev);
+	struct ti_lpsc *lpsc;
+
+	debug("%s(power_domain=%p, id=%d)\n", __func__, pd, args->args[0]);
+
+	if (args->args_count < 1) {
+		printf("Invalid args_count: %d\n", args->args_count);
+		return -EINVAL;
+	}
+
+	lpsc = lpsc_lookup(data, args->args[0]);
+	if (!lpsc) {
+		printf("%s: invalid dev-id: %d\n", __func__, args->args[0]);
+		return -ENOENT;
+	}
+
+	pd->id = lpsc->id;
+	pd->priv = lpsc;
+
+	return 0;
+}
+
+static int ti_power_domain_request(struct power_domain *pd)
+{
+	return 0;
+}
+
+static int ti_power_domain_free(struct power_domain *pd)
+{
+	return 0;
+}
+
+static const struct udevice_id ti_power_domain_of_match[] = {
+	{ .compatible = "ti,sci-pm-domain" },
+	{ /* sentinel */ }
+};
+
+static struct power_domain_ops ti_power_domain_ops = {
+	.on = ti_power_domain_on,
+	.off = ti_power_domain_off,
+	.of_xlate = ti_power_domain_of_xlate,
+	.request = ti_power_domain_request,
+	.rfree = ti_power_domain_free,
+};
+
+U_BOOT_DRIVER(ti_pm_domains) = {
+	.name = "ti-pm-domains",
+	.id = UCLASS_POWER_DOMAIN,
+	.of_match = ti_power_domain_of_match,
+	.probe = ti_power_domain_probe,
+	.priv_auto = sizeof(struct ti_k3_pd_platdata),
+	.ops = &ti_power_domain_ops,
+};