[PATCH 09/20] pmdomain: add K3 driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The power domain support for K3 SoCs is implemented on the Cortex-R5
boot processor by the ti-dm firmware binary. The A53 cores access the
power domains via mailboxes. However, during early boot we are running
barebox on the Cortex-R5 processor and the ti-dm firmware is not yet
running, so we must implement our own driver for the power domains.
Code is based on U-Boot-2025.01-rc1.

Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx>
---
 drivers/pmdomain/ti/Kconfig  |   4 +
 drivers/pmdomain/ti/Makefile |   1 +
 drivers/pmdomain/ti/ti-k3.c  | 479 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 484 insertions(+)

diff --git a/drivers/pmdomain/ti/Kconfig b/drivers/pmdomain/ti/Kconfig
index f34a5146c1..fc65e34118 100644
--- a/drivers/pmdomain/ti/Kconfig
+++ b/drivers/pmdomain/ti/Kconfig
@@ -6,3 +6,7 @@ config TI_SCI_PM_DOMAINS
 	help
 	  Generic power domain implementation for TI device implementing
 	  the TI SCI protocol.
+
+config TI_K3_PM_DOMAINS
+	bool "TI K3 PM Domains Driver"
+	depends on MACH_K3_CORTEX_R5 || COMPILE_TEST
diff --git a/drivers/pmdomain/ti/Makefile b/drivers/pmdomain/ti/Makefile
index ab582e04a8..e5d56da052 100644
--- a/drivers/pmdomain/ti/Makefile
+++ b/drivers/pmdomain/ti/Makefile
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_TI_SCI_PM_DOMAINS) += ti_sci_pm_domains.o
+obj-$(CONFIG_TI_K3_PM_DOMAINS) += ti-k3.o
diff --git a/drivers/pmdomain/ti/ti-k3.c b/drivers/pmdomain/ti/ti-k3.c
new file mode 100644
index 0000000000..33bffeaca0
--- /dev/null
+++ b/drivers/pmdomain/ti/ti-k3.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Texas Instruments power domain driver
+ *
+ * Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com/
+ *	Tero Kristo <t-kristo@xxxxxx>
+ */
+
+#define pr_fmt(fmt)     "ti-k3-pm-domain: " fmt
+
+#include <io.h>
+#include <of_device.h>
+#include <malloc.h>
+#include <init.h>
+#include <pm_domain.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#define PSC_PTCMD		0x120
+#define PSC_PTCMD_H		0x124
+#define PSC_PTSTAT		0x128
+#define PSC_PTSTAT_H		0x12C
+#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
+
+#define LPSC_MODULE_EXISTS      BIT(0)
+#define LPSC_NO_CLOCK_GATING    BIT(1)
+#define LPSC_DEPENDS            BIT(2)
+#define LPSC_HAS_RESET_ISO      BIT(3)
+#define LPSC_HAS_LOCAL_RESET    BIT(4)
+#define LPSC_NO_MODULE_RESET    BIT(5)
+
+#define PSC_PD_EXISTS           BIT(0)
+#define PSC_PD_ALWAYSON         BIT(1)
+#define PSC_PD_DEPENDS          BIT(2)
+
+#define MDSTAT_STATE_MASK		0x3f
+#define MDSTAT_BUSY_MASK		0x30
+#define MDSTAT_STATE_SWRSTDISABLE	0x0
+#define MDSTAT_STATE_ENABLE		0x3
+
+struct ti_psc {
+	int id;
+	void __iomem *base;
+};
+
+struct ti_pd;
+
+struct ti_pd {
+	int id;
+	int usecount;
+	struct ti_psc *psc;
+	struct ti_pd *depend;
+};
+
+struct ti_lpsc;
+
+struct ti_lpsc {
+	int id;
+	int usecount;
+	struct ti_psc *psc;
+	struct ti_pd *pd;
+	struct ti_lpsc *depend;
+};
+
+struct ti_dev {
+	int lpsc;
+	int id;
+};
+
+/**
+ * struct ti_k3_pd_platdata - pm domain controller information structure
+ */
+struct ti_k3_pd_platdata {
+	struct ti_psc *psc;
+	struct ti_pd *pd;
+	struct ti_lpsc *lpsc;
+	struct ti_dev *devs;
+	int num_psc;
+	int num_pd;
+	int num_lpsc;
+	int num_devs;
+};
+
+struct ti_k3_pm_domain {
+	struct generic_pm_domain genpd;
+	struct ti_lpsc *lpsc;
+};
+
+struct ti_k3_priv {
+	const struct ti_k3_pd_platdata *data;
+	struct genpd_onecell_data pd_data;
+	struct ti_k3_pm_domain *pd;
+};
+
+static u32 psc_read(struct ti_psc *psc, u32 reg)
+{
+	return readl(psc->base + reg);
+}
+
+static void psc_write(u32 val, struct ti_psc *psc, u32 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 int ti_pd_wait(struct ti_pd *pd)
+{
+	u32 ptstat;
+	u32 pdoffset = 0;
+	u32 ptstatreg = PSC_PTSTAT;
+	int ret;
+
+	if (pd->id > 31) {
+		pdoffset = 32;
+		ptstatreg = PSC_PTSTAT_H;
+	}
+
+	ret = readl_poll_timeout(pd->psc->base + ptstatreg, ptstat,
+				 !(ptstat & BIT(pd->id - pdoffset)), PD_TIMEOUT);
+
+	if (ret)
+		pr_err("%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)
+{
+	u32 pdoffset = 0;
+	u32 ptcmdreg = PSC_PTCMD;
+
+	if (pd->id > 31) {
+		pdoffset = 32;
+		ptcmdreg = PSC_PTCMD_H;
+	}
+
+	psc_write(BIT(pd->id - pdoffset), pd->psc, ptcmdreg);
+}
+
+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;
+
+	pr_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;
+
+	pr_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)
+		pr_err("%s: module %d failed to transition.\n", __func__,
+		       lpsc->id);
+
+	return ret;
+}
+
+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;
+	}
+
+	pr_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 inline struct ti_k3_pm_domain *to_ti_k3_pd(struct generic_pm_domain *gpd)
+{
+	return container_of(gpd, struct ti_k3_pm_domain, genpd);
+}
+
+static int ti_k3_pm_domain_on(struct generic_pm_domain *domain)
+{
+	struct ti_k3_pm_domain *pd = to_ti_k3_pd(domain);
+
+	return ti_lpsc_transition(pd->lpsc, MDSTAT_STATE_ENABLE);
+}       
+        
+static int ti_k3_pm_domain_off(struct generic_pm_domain *domain)
+{       
+	struct ti_k3_pm_domain *pd = to_ti_k3_pd(domain);
+
+	return ti_lpsc_transition(pd->lpsc, MDSTAT_STATE_SWRSTDISABLE);
+}       
+
+static struct ti_psc am625_psc[] = {
+	[0] = { .id = 0, .base = (void *)0x04000000 },
+	[1] = { .id = 1, .base = (void *)0x00400000 },
+};
+
+static struct ti_pd am625_pd[] = {
+	[0] = { .id = 0, .psc = &am625_psc[1], },
+	[1] = { .id = 2, .psc = &am625_psc[1], .depend = &am625_pd[0] },
+	[2] = { .id = 3, .psc = &am625_psc[1], .depend = &am625_pd[0] },
+	[3] = { .id = 4, .psc = &am625_psc[1], .depend = &am625_pd[2] },
+	[4] = { .id = 5, .psc = &am625_psc[1], .depend = &am625_pd[2] },
+};
+
+static struct ti_lpsc am625_lpsc[] = {
+	[0] = { .id = 0, .psc = &am625_psc[1], .pd = &am625_pd[0], },
+	[1] = { .id = 9, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[11] },
+	[2] = { .id = 10, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[1] },
+	[3] = { .id = 11, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[2] },
+	[4] = { .id = 12, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[8] },
+	[5] = { .id = 13, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[9] },
+	[6] = { .id = 20, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[11] },
+	[7] = { .id = 21, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[11] },
+	[8] = { .id = 23, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[11] },
+	[9] = { .id = 24, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[11] },
+	[10] = { .id = 28, .psc = &am625_psc[1], .pd = &am625_pd[0], .depend = &am625_lpsc[11] },
+	[11] = { .id = 34, .psc = &am625_psc[1], .pd = &am625_pd[0], },
+	[12] = { .id = 41, .psc = &am625_psc[1], .pd = &am625_pd[1], .depend = &am625_lpsc[11] },
+	[13] = { .id = 42, .psc = &am625_psc[1], .pd = &am625_pd[2], .depend = &am625_lpsc[11] },
+	[14] = { .id = 45, .psc = &am625_psc[1], .pd = &am625_pd[3], .depend = &am625_lpsc[13] },
+	[15] = { .id = 46, .psc = &am625_psc[1], .pd = &am625_pd[4], .depend = &am625_lpsc[13] },
+};
+
+static struct ti_dev am625_dev[] = {
+	{ .id = 16,  .lpsc = 0 },
+	{ .id = 77,  .lpsc = 0 },
+	{ .id = 61,  .lpsc = 0 },
+	{ .id = 95,  .lpsc = 0 },
+	{ .id = 107, .lpsc = 0 },
+	{ .id = 170, .lpsc = 1 },
+	{ .id = 177, .lpsc = 2 },
+	{ .id = 55,  .lpsc = 3 },
+	{ .id = 178, .lpsc = 4 },
+	{ .id = 179, .lpsc = 5 },
+	{ .id = 57,  .lpsc = 6 },
+	{ .id = 58,  .lpsc = 7 },
+	{ .id = 161, .lpsc = 8 },
+	{ .id = 162, .lpsc = 9 },
+	{ .id = 75,  .lpsc = 10 },
+	{ .id = 36,  .lpsc = 11 },
+	{ .id = 102, .lpsc = 11 },
+	{ .id = 146, .lpsc = 11 },
+	{ .id = 13,  .lpsc = 12 },
+	{ .id = 166, .lpsc = 13 },
+	{ .id = 135, .lpsc = 14 },
+	{ .id = 136, .lpsc = 15 },
+};
+
+const struct ti_k3_pd_platdata am62x_pd_platdata = {
+	.psc = am625_psc,
+	.pd = am625_pd,
+	.lpsc = am625_lpsc,
+	.devs = am625_dev,
+	.num_psc = ARRAY_SIZE(am625_psc),
+	.num_pd = ARRAY_SIZE(am625_pd),
+	.num_lpsc = ARRAY_SIZE(am625_lpsc),
+	.num_devs = ARRAY_SIZE(am625_dev),
+};
+
+static struct generic_pm_domain *ti_k3_pd_xlate(struct of_phandle_args *args,
+						void *data)
+{
+	struct genpd_onecell_data *genpd_data = data;
+	struct ti_k3_priv *priv = container_of(genpd_data, struct ti_k3_priv, pd_data);
+	unsigned int idx = args->args[0];
+	int i;
+
+	for (i = 0; i < priv->data->num_devs; i++)
+		if (priv->data->devs[i].id == idx) {
+			unsigned int lpsc = priv->data->devs[i].lpsc;
+			pr_debug("Translate: %d -> %d\n", idx, lpsc);
+			return &priv->pd[lpsc].genpd;
+		}
+
+	return 0;
+}
+
+static int ti_k3_pm_domain_probe(struct device *dev)
+{
+	struct ti_k3_priv *priv;
+	const struct ti_k3_pd_platdata *data;
+	struct ti_k3_pm_domain *pd;
+	struct generic_pm_domain **domains;
+	struct genpd_onecell_data *pd_data;
+	int num_domains;
+	int i;
+
+	priv = xzalloc(sizeof(*priv));
+
+	if (of_machine_is_compatible("ti,am625"))
+		data = &am62x_pd_platdata;
+	else
+		return dev_err_probe(dev, -EINVAL, "Unknown SoC\n");
+
+	priv->data = data;
+
+	num_domains = data->num_lpsc;
+
+	pd = devm_kcalloc(dev, num_domains, sizeof(*pd), GFP_KERNEL);
+	if (!pd)
+		return -ENOMEM;
+
+	domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
+	if (!domains)
+		return -ENOMEM;
+
+	priv->pd = pd;
+
+	for (i = 0; i < num_domains; i++, pd++) {
+		pd->genpd.name = basprintf("pd:%d", i);
+		pd->lpsc = &data->lpsc[i];
+		pd->genpd.power_off = ti_k3_pm_domain_off;
+		pd->genpd.power_on = ti_k3_pm_domain_on;
+
+		pm_genpd_init(&pd->genpd, NULL, true);
+
+		domains[i] = &pd->genpd;
+	}
+
+	pd_data = &priv->pd_data;
+
+	pd_data->domains = domains;
+	pd_data->num_domains = num_domains;
+	pd_data->xlate = ti_k3_pd_xlate;
+
+	return of_genpd_add_provider_onecell(dev->of_node, pd_data);
+}
+
+static const struct of_device_id ti_k3_power_domain_of_match[] = {
+	{ .compatible = "ti,sci-pm-domain" },
+	{ /* sentinel */ }
+};
+
+static struct driver ti_k3_pm_domains_driver = {
+        .probe = ti_k3_pm_domain_probe,
+        .name = "ti_k3_pm_domains",
+        .of_match_table = ti_k3_power_domain_of_match,
+};
+core_platform_driver(ti_k3_pm_domains_driver);

-- 
2.39.5





[Index of Archives]     [Linux Embedded]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux