/* * Copyright (C) 2013 Boris BREZILLON * * 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. * */ #include #include #include #include #include #include #include #include #include #include "pmc.h" #define PMC_MAX_IDS 128 int of_at91_get_clk_range(struct device_node *np, const char *propname, struct clk_range *range) { u32 min, max; int ret; ret = of_property_read_u32_index(np, propname, 0, &min); if (ret) return ret; ret = of_property_read_u32_index(np, propname, 1, &max); if (ret) return ret; if (range) { range->min = min; range->max = max; } return 0; } EXPORT_SYMBOL_GPL(of_at91_get_clk_range); #ifdef CONFIG_PM static struct regmap *pmcreg; static u8 registered_ids[PMC_MAX_IDS]; static struct { u32 scsr; u32 pcsr0; u32 uckr; u32 mor; u32 mcfr; u32 pllar; u32 mckr; u32 usb; u32 imr; u32 pcsr1; u32 pcr[PMC_MAX_IDS]; u32 audio_pll0; u32 audio_pll1; } pmc_cache; void pmc_register_id(u8 id) { int i; for (i = 0; i < PMC_MAX_IDS; i++) { if (registered_ids[i] == 0) { registered_ids[i] = id; break; } if (registered_ids[i] == id) break; } } static int pmc_suspend(void) { int i; regmap_read(pmcreg, AT91_PMC_IMR, &pmc_cache.scsr); regmap_read(pmcreg, AT91_PMC_PCSR, &pmc_cache.pcsr0); regmap_read(pmcreg, AT91_CKGR_UCKR, &pmc_cache.uckr); regmap_read(pmcreg, AT91_CKGR_MOR, &pmc_cache.mor); regmap_read(pmcreg, AT91_CKGR_MCFR, &pmc_cache.mcfr); regmap_read(pmcreg, AT91_CKGR_PLLAR, &pmc_cache.pllar); regmap_read(pmcreg, AT91_PMC_MCKR, &pmc_cache.mckr); regmap_read(pmcreg, AT91_PMC_USB, &pmc_cache.usb); regmap_read(pmcreg, AT91_PMC_IMR, &pmc_cache.imr); regmap_read(pmcreg, AT91_PMC_PCSR1, &pmc_cache.pcsr1); for (i = 0; registered_ids[i]; i++) { regmap_write(pmcreg, AT91_PMC_PCR, (registered_ids[i] & AT91_PMC_PCR_PID_MASK)); regmap_read(pmcreg, AT91_PMC_PCR, &pmc_cache.pcr[registered_ids[i]]); } return 0; } static void pmc_resume(void) { int i, ret = 0; u32 tmp; regmap_read(pmcreg, AT91_PMC_MCKR, &tmp); if (pmc_cache.mckr != tmp) pr_warn("MCKR was not configured properly by the firmware\n"); regmap_read(pmcreg, AT91_CKGR_PLLAR, &tmp); if (pmc_cache.pllar != tmp) pr_warn("PLLAR was not configured properly by the firmware\n"); regmap_write(pmcreg, AT91_PMC_IMR, pmc_cache.scsr); regmap_write(pmcreg, AT91_PMC_PCER, pmc_cache.pcsr0); regmap_write(pmcreg, AT91_CKGR_UCKR, pmc_cache.uckr); regmap_write(pmcreg, AT91_CKGR_MOR, pmc_cache.mor); regmap_write(pmcreg, AT91_CKGR_MCFR, pmc_cache.mcfr); regmap_write(pmcreg, AT91_PMC_USB, pmc_cache.usb); regmap_write(pmcreg, AT91_PMC_IMR, pmc_cache.imr); regmap_write(pmcreg, AT91_PMC_PCER1, pmc_cache.pcsr1); for (i = 0; registered_ids[i]; i++) { regmap_write(pmcreg, AT91_PMC_PCR, pmc_cache.pcr[registered_ids[i]] | AT91_PMC_PCR_CMD); } if (pmc_cache.uckr & AT91_PMC_UPLLEN) { ret = regmap_read_poll_timeout(pmcreg, AT91_PMC_SR, tmp, !(tmp & AT91_PMC_LOCKU), 10, 5000); if (ret) pr_crit("USB PLL didn't lock when resuming\n"); } } static struct syscore_ops pmc_syscore_ops = { .suspend = pmc_suspend, .resume = pmc_resume, }; static const struct of_device_id sama5d2_pmc_dt_ids[] = { { .compatible = "atmel,sama5d2-pmc" }, { /* sentinel */ } }; static int __init pmc_register_ops(void) { struct device_node *np; np = of_find_matching_node(NULL, sama5d2_pmc_dt_ids); pmcreg = syscon_node_to_regmap(np); if (IS_ERR(pmcreg)) return PTR_ERR(pmcreg); register_syscore_ops(&pmc_syscore_ops); return 0; } /* This has to happen before arch_initcall because of the tcb_clksrc driver */ postcore_initcall(pmc_register_ops); #endif