f79753c3de
Add support command for debugging K3 power domains. This is useful with the HSM rearch setup, where power domains are directly controlled by SPL instead of going through the TI SCI layer. The debugging support is only available in the u-boot codebase though, so the raw register access power domain layer must be enabled on u-boot side for this to work. By default, u-boot side uses the TI SCI layer, and R5 SPL only uses the direct access methods. 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>
369 lines
7.2 KiB
C
369 lines
7.2 KiB
C
// 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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
u8 lpsc_get_state(struct ti_lpsc *lpsc)
|
|
{
|
|
return lpsc_read(lpsc, PSC_MDCTL) & MDSTAT_STATE_MASK;
|
|
}
|
|
|
|
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,
|
|
};
|