aboutsummaryrefslogtreecommitdiff
path: root/arch/powerpc/sysdev/ppc4xx_cpm.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/powerpc/sysdev/ppc4xx_cpm.c')
-rw-r--r--arch/powerpc/sysdev/ppc4xx_cpm.c636
1 files changed, 636 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/ppc4xx_cpm.c b/arch/powerpc/sysdev/ppc4xx_cpm.c
new file mode 100644
index 00000000000..d0804f0d316
--- /dev/null
+++ b/arch/powerpc/sysdev/ppc4xx_cpm.c
@@ -0,0 +1,636 @@
+/*
+ * arch/powerpc/sysdev/ppc4xx_cpm.c
+ *
+ * PowerPC 4xx Clock and Power Management
+ *
+ * (C) Copyright 2009, Applied Micro Circuits Corporation
+ * Victor Gallardo (vgallardo@amcc.com)
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/proc_fs.h>
+#include <linux/dma-mapping.h>
+#include <asm/uaccess.h>
+#include <asm/prom.h>
+#include <asm/io.h>
+#include <asm/dcr.h>
+#include <asm/dcr-regs.h>
+#include <asm/mmio-regs.h>
+#include <asm/ppc4xx_cpm.h>
+#include <asm/ppc4xx_ocm.h>
+
+#define CPM_PM_MEM_DATA_SIZE (256*sizeof(unsigned long))
+#define CPM_PM_MEM_ALIGN 16
+
+#define CPM_ER 0x0
+#define CPM_FR 0x1
+#define CPM_SR 0x2
+
+#define CPM_PCIE_SLEEP 0x00040000
+#define CPM_SATA0_SLEEP 0x00001000
+
+extern void cpm_suspend_mem(int pm_mode, unsigned long *data, void *resume);
+extern const long cpm_suspend_mem_size;
+
+extern void cpm_resume_mem(void);
+extern const long cpm_resume_mem_size;
+
+struct cpm_pm_mem {
+ /* functions in OCM */
+ void (*suspend)(int pm_mode, unsigned long *data, void *resume);
+ void (*resume )(void);
+ /* data OCM */
+ unsigned long *data;
+};
+
+struct cpm_pm_iic {
+ phys_addr_t phys;
+ size_t size;
+};
+
+struct cpm_pm_emac {
+ phys_addr_t phys;
+ size_t size;
+};
+
+struct cpm {
+ unsigned int index;
+ unsigned int dcrbase;
+ unsigned int ready;
+ unsigned int save_er;
+ unsigned int save_fr;
+ unsigned int pm_cpu;
+ unsigned int pm_off;
+ unsigned int pm_doze;
+ unsigned int pm_nap;
+ unsigned int pm_deepsleep;
+ struct cpm_pm_iic pm_iic;
+ struct cpm_pm_emac pm_emac;
+};
+
+extern int pcie_used;
+
+//static struct proc_dir_entry * cpm_proc_entry;
+
+static struct cpm *cpm_nodes = NULL;
+static int cpm_count = 0;
+
+static struct cpm_pm_mem cpm_pm_mem;
+
+static u32 dcrbase_l2c = 0;
+
+const char *cpm_mode_name(int mode)
+{
+ switch (mode) {
+ case CPM_PM_DOZE:
+ return "doze";
+ case CPM_PM_NAP:
+ return "nap";
+ case CPM_PM_DEEPSLEEP:
+ return "deepsleep";
+ default:
+ BUG();
+ }
+}
+
+static struct cpm * get_cpm_node(unsigned int index)
+{
+ BUG_ON(index >= cpm_count);
+
+ return &cpm_nodes[index];
+}
+
+static int cpm_enable(unsigned int index, unsigned int val)
+{
+ struct cpm *cpm = get_cpm_node(index);
+
+ mtdcr(cpm->dcrbase + CPM_ER,
+ mfdcr(cpm->dcrbase + CPM_ER) | val);
+
+ return 0;
+}
+
+static int cpm_force_enable(unsigned int index, unsigned int val)
+{
+ struct cpm *cpm = get_cpm_node(index);
+
+ mtdcr(cpm->dcrbase + CPM_FR,
+ mfdcr(cpm->dcrbase + CPM_FR) | val);
+
+ while ((mfdcr(cpm->dcrbase + CPM_SR) & val) != val);
+
+ return 0;
+}
+
+static int cpm_save(unsigned int index)
+{
+ struct cpm *cpm = get_cpm_node(index);
+
+ cpm->save_er = mfdcr(cpm->dcrbase + CPM_ER);
+ cpm->save_fr = mfdcr(cpm->dcrbase + CPM_FR);
+
+ return 0;
+}
+
+static int cpm_restore(unsigned int index)
+{
+ struct cpm *cpm = get_cpm_node(index);
+
+ mtdcr(cpm->dcrbase + CPM_ER, cpm->save_er);
+ mtdcr(cpm->dcrbase + CPM_FR, cpm->save_fr);
+
+ while ((mfdcr(cpm->dcrbase + CPM_SR) & cpm->save_fr) != cpm->save_fr);
+
+ return 0;
+}
+
+static int cpm_pm_enable(int pm_mode)
+{
+ unsigned int i;
+ unsigned int pm_val;
+
+ for (i=0; i < cpm_count; i++) {
+ struct cpm *cpm = get_cpm_node(i);
+
+ switch(pm_mode) {
+ case CPM_PM_DOZE:
+ pm_val = cpm->pm_doze;
+ break;
+ case CPM_PM_NAP:
+ pm_val = cpm->pm_nap;
+ break;
+ case CPM_PM_DEEPSLEEP:
+ pm_val = cpm->pm_deepsleep;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ cpm_save(i);
+ cpm_enable(i, pm_val);
+ cpm_force_enable(i, pm_val);
+ }
+
+ return 0;
+}
+
+static int cpm_pm_disable(int pm_mode)
+{
+ unsigned int i;
+
+ for (i=0; i < cpm_count; i++) {
+ switch(pm_mode) {
+ case CPM_PM_DOZE:
+ case CPM_PM_NAP:
+ case CPM_PM_DEEPSLEEP:
+ cpm_restore(i);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static void cpm_flush_caches(void)
+{
+ struct page *page;
+ size_t size = PAGE_ALIGN(0x8000);
+ unsigned long order = get_order(size);
+
+ /* Flush and invalidate L1 cache */
+ page = alloc_pages(GFP_NOWAIT, order);
+ if (page) {
+ unsigned long kaddr = (unsigned long)page_address(page);
+ unsigned long endaddr = kaddr + size;
+ unsigned long addr;
+
+ for (addr = kaddr; addr < endaddr; addr += L1_CACHE_BYTES)
+ asm ("dcbt 0,%0": :"r" (addr));
+ asm ("sync");
+
+ for (addr = kaddr; addr < endaddr; addr += L1_CACHE_BYTES)
+ asm ("dcbf 0,%0": :"r" (addr));
+ asm ("sync");
+ asm ("isync");
+
+ __free_pages(page, order);
+ }
+
+ if (dcrbase_l2c != 0) {
+ /* Invalidate L2 cache using the Hardware Clear Command */
+ mtdcr(dcrbase_l2c + DCRN_L2C0_ADDR, 0);
+ mtdcr(dcrbase_l2c + DCRN_L2C0_CMD, L2C_CMD_HCC);
+
+ /* Wait for Command Complete to set */
+ while (!(mfdcr(dcrbase_l2c + DCRN_L2C0_SR) & L2C_SR_CC))
+ ;
+ //printk(KERN_INFO "%s\n", __func__);
+ }
+}
+
+static int cpm_pm_mem_idle(int pm_mode)
+{
+ void __iomem *iicp = NULL;
+
+ if (pm_mode < CPM_PM_NAP)
+ return -EINVAL;
+
+ if ( !cpm_pm_mem.suspend || !cpm_pm_mem.resume ||!cpm_pm_mem.data)
+ return -ENOMEM;
+
+ printk(KERN_INFO "%s\n", __func__);
+
+ if (pm_mode == CPM_PM_DEEPSLEEP) {
+ /* FIXME: Disable I2C interrupbecause it causes interrupt that wake up the system */
+ struct cpm *cpm = get_cpm_node(0);
+ iicp = ioremap(cpm->pm_iic.phys, cpm->pm_iic.size);
+ cpm_pm_mem.data[CPM_PM_DATA_IIC_PTR] = (unsigned long)iicp;
+ }
+
+ if (!iicp) {
+ printk(KERN_INFO "No iic\n");
+ pm_mode = CPM_PM_NAP;
+ } else {
+ printk(KERN_INFO "%s: iic address = 0x%p\n",
+ __func__, iicp);
+ printk(KERN_INFO "%s: iic status = 0x%02x\n",
+ __func__, in_8(iicp+IIC_STS));
+ }
+ /* call OCM code */
+ cpm_pm_mem.suspend(pm_mode, cpm_pm_mem.data, cpm_pm_mem.resume);
+ printk(KERN_INFO "%s: wakeup\n", __func__);
+
+ if (iicp)
+ iounmap(iicp);
+
+ return 0;
+}
+
+int cpm_pm_idle(int pm_mode)
+{
+ unsigned long tcr_save = 0;
+ unsigned long ccr0_save = 0;
+ int emac0_mr0;
+ void __iomem *emacp = NULL;
+ struct cpm *cpm;
+
+ printk(KERN_INFO "%s\n", __func__);
+
+
+ cpm = get_cpm_node(0);
+ emacp = ioremap(cpm->pm_emac.phys, cpm->pm_emac.size);
+ if (emacp) {
+ emac0_mr0 = in_be32(emacp + 0);
+ /*printk(KERN_INFO "EMAC0_MR0 value before set WOL:0x%x\n", emac0_mr0);*/
+ emac0_mr0 &= CPM_PM_DISABLE_EMAC0_MR0_RXE;
+ out_be32(emacp + 0, emac0_mr0);
+ emac0_mr0 = in_be32(emacp + 0);
+ while (!(emac0_mr0 & CPM_PM_EMAC0_MR0_RXI
+ && !(emac0_mr0 & CPM_PM_ENABLE_EMAC0_MR0_RXE))) {
+ emac0_mr0 = in_be32(emacp + 0);
+ }
+ emac0_mr0 |= CPM_PM_ENABLE_EMAC0_MR0_WKE;
+ out_be32(emacp + 0, emac0_mr0);
+ emac0_mr0 = in_be32(emacp + 0);
+ emac0_mr0 |= CPM_PM_ENABLE_EMAC0_MR0_RXE;
+ out_be32(emacp + 0, emac0_mr0);
+ /*printk(KERN_INFO "EMAC0_MR0 value after set WOL:0x%x\n", emac0_mr0);*/
+ }
+
+ ccr0_save = mfspr(SPRN_CCR0);
+ tcr_save = mfspr(SPRN_TCR);
+ mtspr(SPRN_TCR, tcr_save & ~TCR_DIE);
+ mtspr(SPRN_CCR0, ccr0_save | CCR0_DTB | CCR0_GDCBT);
+
+ cpm_flush_caches();
+ cpm_pm_enable(pm_mode);
+
+
+ if (cpm_pm_mem_idle(pm_mode) < 0) {
+ unsigned long msr_save;
+
+ /* set wait state MSR */
+ msr_save = mfmsr();
+ mtmsr(msr_save|MSR_WE|MSR_EE|MSR_CE|MSR_DE);
+ isync();
+ /* return to initial state */
+ mtmsr(msr_save);
+ isync();
+ }
+
+ cpm_pm_disable(pm_mode);
+ mtspr(SPRN_CCR0, ccr0_save);
+ mtspr(SPRN_TCR, tcr_save);
+
+ if (emacp) {
+ emac0_mr0 = in_be32(emacp + 0);
+ emac0_mr0 &= CPM_PM_DISABLE_EMAC0_MR0_RXE;
+ out_be32(emacp + 0, emac0_mr0);
+ emac0_mr0 = in_be32(emacp + 0);
+ while (!(emac0_mr0 & CPM_PM_EMAC0_MR0_RXI
+ && !(emac0_mr0 & CPM_PM_ENABLE_EMAC0_MR0_RXE))) {
+ emac0_mr0 = in_be32(emacp + 0);
+ }
+ emac0_mr0 &= CPM_PM_DISABLE_EMAC0_MR0_WKE;
+ out_be32(emacp + 0, emac0_mr0);
+ emac0_mr0 = in_be32(emacp + 0);
+ emac0_mr0 |= CPM_PM_ENABLE_EMAC0_MR0_RXE;
+ out_be32(emacp + 0, emac0_mr0);
+ /*printk(KERN_INFO "EMAC0_MR0 value after disable WOL:0x%x\n", emac0_mr0);*/
+ }
+
+ printk(KERN_INFO "%s: wakeup\n", __func__);
+
+ return 0;
+}
+
+
+int cpm_pm_suspend(suspend_state_t state, int suspend_mode)
+{
+ int pm_mode;
+
+ switch (state) {
+ case PM_SUSPEND_STANDBY:
+ /* standby only support DOZE */
+ pm_mode = CPM_PM_DOZE;
+ break;
+ case PM_SUSPEND_MEM:
+ if (suspend_mode < CPM_PM_NAP)
+ suspend_mode = CPM_PM_DOZE;
+ pm_mode = suspend_mode;
+ break;
+ default:
+ printk(KERN_INFO "%s: -EINVAL\n", __func__);
+ return -EINVAL;
+ }
+
+ cpm_pm_idle(pm_mode);
+
+ return 0;
+}
+
+static void __init cpm_get_pm_emac(struct cpm *cpm, struct device_node *node)
+{
+ const phandle *phandle;
+ struct device_node *dev_node;
+ struct resource rsrc;
+
+ phandle = of_get_property(node, "pm-emac-device", NULL);
+ if (!phandle) {
+ printk(KERN_INFO "CPM%d: pm-memory property not defined\n",
+ cpm->index);
+ return;
+ }
+
+ dev_node = of_find_node_by_phandle(*phandle);
+ if (!dev_node) {
+ printk(KERN_ERR "CPM%d: Can't find pm-emac-device node\n",
+ cpm->index);
+ return;
+ }
+
+ printk(KERN_INFO "CPM%d: pm-emac-device resource %s\n",
+ cpm->index, dev_node->full_name);
+
+ if (of_address_to_resource(dev_node, 0, &rsrc)) {
+ printk(KERN_ERR "CPM%d: Can't get address to %s resource\n",
+ cpm->index, dev_node->full_name);
+ return;
+ }
+
+ cpm->pm_emac.phys = rsrc.start;
+ cpm->pm_emac.size = rsrc.end - rsrc.start + 1;
+}
+
+static void __init cpm_get_pm_iic(struct cpm *cpm, struct device_node *node)
+{
+ const phandle *phandle;
+ struct device_node *dev_node;
+ struct resource rsrc;
+
+ phandle = of_get_property(node, "pm-iic-device", NULL);
+ if (!phandle) {
+ printk(KERN_INFO "CPM%d: pm-memory property not defined\n",
+ cpm->index);
+ return;
+ }
+
+ dev_node = of_find_node_by_phandle(*phandle);
+ if (!dev_node) {
+ printk(KERN_ERR "CPM%d: Can't find pm-iic-device node\n",
+ cpm->index);
+ return;
+ }
+
+ printk(KERN_INFO "CPM%d: pm-iic-device resource %s\n",
+ cpm->index, dev_node->full_name);
+
+ if (of_address_to_resource(dev_node, 0, &rsrc)) {
+ printk(KERN_ERR "CPM%d: Can't get address to %s resource\n",
+ cpm->index, dev_node->full_name);
+ return;
+ }
+
+ cpm->pm_iic.phys = rsrc.start;
+ cpm->pm_iic.size = rsrc.end - rsrc.start + 1;
+
+ //printk(KERN_INFO "CPM%d: pm-iic-device address 0x%llx\n",
+ //cpm->index, cpm->pm_iic.phys);
+}
+
+static void __init cpm_init_node(struct device_node *node)
+{
+ struct cpm *cpm;
+ const unsigned int *cell_index;
+ const unsigned int *dcr_reg;
+ const unsigned int *pm_cpu;
+ const unsigned int *pm_off;
+ const unsigned int *pm_doze;
+ const unsigned int *pm_nap;
+ const unsigned int *pm_deepsleep;
+ int len;
+
+ cell_index = of_get_property(node, "cell-index", &len);
+ if (!cell_index)
+ BUG_ON("cpm: missing cell-index property");
+ else if ((len != sizeof(unsigned int)) || *cell_index >= cpm_count)
+ BUG_ON("cpm: invalid cell-index property");
+ else if (cpm_nodes[*cell_index].ready)
+ BUG_ON("cpm: duplicate cell-index property");
+
+ cpm = &cpm_nodes[*cell_index];
+ cpm->index = *cell_index;
+
+ dcr_reg = of_get_property(node, "dcr-reg", &len);
+ if (!dcr_reg || (len != 2*sizeof(unsigned int)))
+ BUG_ON("cpm: missing or invalid dcr-reg property");
+
+ cpm->dcrbase = *dcr_reg;
+
+ pm_cpu = of_get_property(node, "pm-cpu", &len);
+ if (pm_cpu && (len == sizeof(unsigned int)))
+ cpm->pm_cpu = *pm_cpu;
+
+ pm_off = of_get_property(node, "pm-off", &len);
+ if (pm_off && (len == sizeof(unsigned int)))
+ cpm->pm_off = *pm_off;
+
+ pm_doze = of_get_property(node, "pm-doze", &len);
+ if (pm_doze && (len == sizeof(unsigned int)))
+ cpm->pm_doze = *pm_doze;
+
+ pm_nap = of_get_property(node, "pm-nap", &len);
+ if (pm_nap && (len == sizeof(unsigned int)))
+ cpm->pm_nap = *pm_nap;
+
+ pm_deepsleep = of_get_property(node, "pm-deepsleep", &len);
+ if (pm_deepsleep && (len == sizeof(unsigned int)))
+ cpm->pm_deepsleep = *pm_deepsleep;
+
+ cpm_get_pm_iic(cpm, node);
+ cpm_get_pm_emac(cpm, node);
+
+ printk (KERN_INFO "CPM%d: DCR at 0x%x\n", cpm->index, cpm->dcrbase);
+
+ cpm->ready = 1;
+
+ mtdcr(cpm->dcrbase + CPM_ER,
+ mfdcr(cpm->dcrbase + CPM_ER) | cpm->pm_off | cpm->pm_cpu);
+ mtdcr(cpm->dcrbase + CPM_FR,
+ mfdcr(cpm->dcrbase + CPM_FR) | cpm->pm_off);
+
+ /* put unused IP into sleep */
+ if (pcie_used == 0){
+ mtdcr(cpm->dcrbase + CPM_ER,
+ mfdcr(cpm->dcrbase + CPM_ER) | CPM_PCIE_SLEEP);
+ mtdcr(cpm->dcrbase + CPM_FR,
+ mfdcr(cpm->dcrbase + CPM_FR) | CPM_PCIE_SLEEP);
+ }
+ else if (pcie_used == 1){
+ mtdcr(cpm->dcrbase + CPM_ER,
+ mfdcr(cpm->dcrbase + CPM_ER) | CPM_SATA0_SLEEP);
+ mtdcr(cpm->dcrbase + CPM_FR,
+ mfdcr(cpm->dcrbase + CPM_FR) | CPM_SATA0_SLEEP);
+ }
+}
+
+
+static int __init cpm_pm_mem_init(void)
+{
+ struct cpm_pm_mem *mem;
+ phys_addr_t p;
+
+ mem = &cpm_pm_mem;
+
+ memset(mem,0,sizeof(struct cpm_pm_mem));
+
+ mem->suspend = ocm_alloc(&p,cpm_suspend_mem_size, CPM_PM_MEM_ALIGN,
+
+ OCM_NON_CACHED, "cpm_suspend_mem");
+ if (!mem->suspend) {
+ printk(KERN_ERR "CPM: failed to allocate suspend memory!\n");
+ return -ENOMEM;
+ }
+
+ mem->resume = ocm_alloc(&p,cpm_resume_mem_size, CPM_PM_MEM_ALIGN,
+ OCM_NON_CACHED, "cpm_resume_mem");
+ if (!mem->resume) {
+ printk(KERN_ERR "CPM: failed to allocate resume memory!\n");
+ ocm_free(mem->suspend);
+ mem->suspend = NULL;
+ return -ENOMEM;
+ }
+
+ mem->data = ocm_alloc(&p,CPM_PM_MEM_DATA_SIZE, CPM_PM_MEM_ALIGN,
+ OCM_NON_CACHED, "cpm_data_mem");
+ if (!mem->data) {
+ printk(KERN_ERR "CPM: failed to allocate data memory!\n");
+ ocm_free(mem->suspend);
+ ocm_free(mem->resume);
+ mem->suspend = NULL;
+ mem->resume = NULL;
+ return -ENOMEM;
+ }
+
+ printk(KERN_INFO "CPM: ocm suspend address 0x%p\n",mem->suspend);
+ printk(KERN_INFO "CPM: ocm resume address 0x%p\n",mem->resume);
+ printk(KERN_INFO "CPM: ocm data address 0x%p\n",mem->data);
+
+ memcpy(mem->suspend, cpm_suspend_mem, cpm_suspend_mem_size);
+ memcpy(mem->resume, cpm_resume_mem, cpm_resume_mem_size);
+ memset(mem->data,0,CPM_PM_MEM_DATA_SIZE);
+
+ return 0;
+}
+
+static int __init cpm_init(void)
+{
+ struct device_node *np;
+ int count;
+ const u32 *dcrreg;
+
+ count = 0;
+ for_each_compatible_node(np, NULL, "ibm,cpm") {
+ count++;
+ }
+// printk(KERN_ERR "CPM: cpm_init %d!\n", count);
+ if (!count) {
+ return 0;
+ }
+
+ cpm_nodes = kzalloc((count*sizeof(struct cpm)), GFP_KERNEL);
+ if (!cpm_nodes) {
+ printk(KERN_ERR "CPM: out of memory allocating CPM!\n");
+ return -ENOMEM;
+ }
+
+ cpm_count = count;
+
+ for_each_compatible_node(np, NULL, "ibm,cpm") {
+ cpm_init_node(np);
+ }
+
+ cpm_pm_mem_init();
+
+// cpm_proc_init();
+
+ np = of_find_compatible_node(NULL, NULL, "ibm,l2-cache");
+ if (!np) {
+ printk(KERN_ERR "Can't get l2-cache node\n");
+ return 0;
+ }
+
+ dcrreg = of_get_property(np, "dcr-reg", &count);
+ if (!dcrreg || (count != 4 * sizeof(u32))) {
+ printk(KERN_ERR "%s: Can't get DCR register base !",
+ np->full_name);
+ of_node_put(np);
+ return -ENODEV;
+ }
+ dcrbase_l2c = dcrreg[2];
+
+ return 0;
+}
+
+subsys_initcall(cpm_init);