2021-06-11 08:45:15 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2021-06-11 08:45:16 +00:00
|
|
|
u8 ti_pd_state(struct ti_pd *pd)
|
2021-06-11 08:45:15 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-06-11 08:45:16 +00:00
|
|
|
u8 lpsc_get_state(struct ti_lpsc *lpsc)
|
2021-06-11 08:45:15 +00:00
|
|
|
{
|
|
|
|
return lpsc_read(lpsc, PSC_MDCTL) & MDSTAT_STATE_MASK;
|
|
|
|
}
|
|
|
|
|
2021-06-11 08:45:16 +00:00
|
|
|
int ti_lpsc_transition(struct ti_lpsc *lpsc, u8 state)
|
2021-06-11 08:45:15 +00:00
|
|
|
{
|
|
|
|
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,
|
|
|
|
};
|