// SPDX-License-Identifier: GPL-2.0+ /* * Texas Instruments power domain driver * * Copyright (C) 2020-2021 Texas Instruments Incorporated - http://www.ti.com/ * Tero Kristo */ #include #include #include #include #include #include #include #include #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 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, }, #elif CONFIG_SOC_K3_J721S2 { .family = "J721S2", .data = &j721s2_pd_platdata, }, #endif #ifdef CONFIG_SOC_K3_AM625 { .family = "AM62X", .data = &am62x_pd_platdata, }, #endif #ifdef CONFIG_SOC_K3_AM62A7 { .family = "AM62AX", .data = &am62ax_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; 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) 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) { 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); } 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 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, }; 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, };