This patch provides support for the Power/Sleep controller found on some SoCs. The PSC controls two basic things: power domains and peripheral clock gating and resets. The device tree is parsed at setup_arch tree to find any PSC node. The number and controllability of the power domains and peripheral reset/gating varies quite a bit from SoC to SoC. This driver tries to encapsulate the common bits and provide an API for the SoC specific code to use. Signed-off-by: Mark Salter <msalter@xxxxxxxxxx> --- arch/c6x/platforms/psc.c | 181 ++++++++++++++++++++++++++++++++++++++++++++++ arch/c6x/platforms/psc.h | 25 +++++++ 2 files changed, 206 insertions(+), 0 deletions(-) create mode 100644 arch/c6x/platforms/psc.c create mode 100644 arch/c6x/platforms/psc.h diff --git a/arch/c6x/platforms/psc.c b/arch/c6x/platforms/psc.c new file mode 100644 index 0000000..c3fd52c --- /dev/null +++ b/arch/c6x/platforms/psc.c @@ -0,0 +1,181 @@ +/* + * Power/Sleep Controller + * + * Copyright (C) 2011 Texas Instruments Incorporated + * Author: Mark Salter <msalter@xxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <asm/soc.h> +#include "psc.h" + +#define PSC_PTCMD 0x0120 +#define PSC_PTSTAT 0x0128 + +#define PSC_PDSTAT0 0x0200 +#define PSC_PDCTL0 0x0300 + +#define PSC_MDSTAT0 0x0800 +#define PSC_MDCTL0 0x0a00 + +#define PD_NEXT_DISABLE 2 +#define PD_NEXT_ENABLE 3 + +#define MD_NEXT_SWRST_DISABLE 0 +#define MD_NEXT_SYNCRST 1 +#define MD_NEXT_DISABLE 2 +#define MD_NEXT_ENABLE 3 +#define MD_NEXT_MASK 0x1f + +#define LRSTZ 0x100 + +struct psc_data { + u32 phys_base; + void __iomem *base; + int num_domains; + int num_modules; + u8 module_domains[MAX_PSC_MODULES]; +}; + +static struct psc_data psc; + +static inline void psc_write(int offset, unsigned int val) +{ + if (psc.base) + soc_writel(val, psc.base + offset); +} + +static inline unsigned int psc_read(int offset) +{ + if (!psc.base) + return 0; + return soc_readl(psc.base + offset); +} + +static void __wait_for_gostat(int domain_mask) +{ + while (psc_read(PSC_PTSTAT) & domain_mask) + ; +} + +void psc_domain_on(int domain) +{ + int reg = PSC_PDCTL0 + (domain * 4); + + if (domain < 0 || domain >= psc.num_domains) + return; + + __wait_for_gostat(1 << domain); + psc_write(reg, psc_read(reg) | 1); +} + +void psc_domain_off(int domain) +{ + int reg = PSC_PDCTL0 + (domain * 4); + + if (domain < 0 || domain >= psc.num_domains) + return; + + __wait_for_gostat(1 << domain); + psc_write(reg, psc_read(reg) & ~1); +} + +void psc_transition_domains(int domain_mask) +{ + domain_mask &= (1 << psc.num_domains) - 1; + if (domain_mask == 0) + return; + + __wait_for_gostat(domain_mask); + psc_write(PSC_PTCMD, domain_mask); +} + +void psc_module_on(int module) +{ + u32 reg, val; + + if (module < 0 || module >= psc.num_modules) + return; + + __wait_for_gostat(1 << psc.module_domains[module]); + + reg = PSC_MDCTL0 + (module * 4); + val = psc_read(reg) & ~MD_NEXT_MASK; + psc_write(reg, val | MD_NEXT_ENABLE); +} + +void psc_module_off(int module) +{ + u32 reg, val; + + if (module < 0 || module >= psc.num_modules) + return; + + __wait_for_gostat(1 << psc.module_domains[module]); + + reg = PSC_MDCTL0 + (module * 4); + val = psc_read(reg) & ~MD_NEXT_MASK; + psc_write(reg, val | MD_NEXT_DISABLE); +} + +void psc_module_reset(int module) +{ + int reg = PSC_MDCTL0 + module * 4; + + if (module < 0 || module >= psc.num_modules) + return; + + psc_write(reg, psc_read(reg) & ~LRSTZ); +} + +void psc_module_reset_release(int module) +{ + int reg = PSC_MDCTL0 + module * 4; + + if (module < 0 || module >= psc.num_modules) + return; + + psc_write(reg, psc_read(reg) | LRSTZ); +} + +void __init psc_init(void) +{ + struct device_node *node; + const __be32 *p; + int i, len; + + node = of_find_compatible_node(NULL, NULL, "ti,c64x+psc"); + if (!node) + return; + + psc.base = of_iomap(node, 0); + if (!psc.base) { + of_node_put(node); + return; + } + + p = of_get_property(node, "ti,number-psc-domains", &len); + if (p && len == sizeof(u32)) + psc.num_domains = be32_to_cpup(p); + + p = of_get_property(node, "ti,number-psc-modules", &len); + if (p && len == sizeof(u32)) + psc.num_modules = be32_to_cpup(p); + + p = of_get_property(node, "ti,module-domain-map", &len); + if (p) { + len /= sizeof(u32); + if (len > MAX_PSC_MODULES) + len = MAX_PSC_MODULES; + for (i = 0; i <= len; i++) + psc.module_domains[i] = be32_to_cpup(&p[i]); + } + + of_node_put(node); +} diff --git a/arch/c6x/platforms/psc.h b/arch/c6x/platforms/psc.h new file mode 100644 index 0000000..7f4bd1d --- /dev/null +++ b/arch/c6x/platforms/psc.h @@ -0,0 +1,25 @@ +/* + * C6X Power/Sleep Controller + * + * Copyright (C) 2011 Texas Instruments Incorporated + * Author: Mark Salter <msalter@xxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _C6X_PSC_H +#define _C6X_PSC_H + +#define MAX_PSC_MODULES 16 + +extern void psc_init(void); +extern void psc_domain_on(int domain); +extern void psc_domain_off(int domain); +extern void psc_transition_domains(int domain_mask); +extern void psc_module_on(int module); +extern void psc_module_off(int module); +extern void psc_module_reset(int module); +extern void psc_module_reset_release(int module); + +#endif /* _C6X_PSC_H */ -- 1.7.6 -- To unsubscribe from this list: send the line "unsubscribe linux-arch" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html