diff options
Diffstat (limited to 'arch/arm/mach-pxa/pxa27x.c')
-rw-r--r-- | arch/arm/mach-pxa/pxa27x.c | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/arch/arm/mach-pxa/pxa27x.c b/arch/arm/mach-pxa/pxa27x.c new file mode 100644 index 000000000..afbf6ace9 --- /dev/null +++ b/arch/arm/mach-pxa/pxa27x.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/arch/arm/mach-pxa/pxa27x.c + * + * Author: Nicolas Pitre + * Created: Nov 05, 2002 + * Copyright: MontaVista Software Inc. + * + * Code specific to PXA27x aka Bulverde. + */ +#include <linux/dmaengine.h> +#include <linux/dma/pxa-dma.h> +#include <linux/gpio.h> +#include <linux/gpio-pxa.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/irqchip.h> +#include <linux/suspend.h> +#include <linux/platform_device.h> +#include <linux/syscore_ops.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/platform_data/i2c-pxa.h> +#include <linux/platform_data/mmp_dma.h> +#include <linux/soc/pxa/cpu.h> + +#include <asm/mach/map.h> +#include <asm/irq.h> +#include <asm/suspend.h> +#include "irqs.h" +#include "pxa27x.h" +#include "reset.h" +#include <linux/platform_data/usb-ohci-pxa27x.h> +#include "pm.h" +#include "addr-map.h" +#include "smemc.h" + +#include "generic.h" +#include "devices.h" +#include <linux/clk-provider.h> +#include <linux/clkdev.h> + +void pxa27x_clear_otgph(void) +{ + if (cpu_is_pxa27x() && (PSSR & PSSR_OTGPH)) + PSSR |= PSSR_OTGPH; +} +EXPORT_SYMBOL(pxa27x_clear_otgph); + +static unsigned long ac97_reset_config[] = { + GPIO113_AC97_nRESET_GPIO_HIGH, + GPIO113_AC97_nRESET, + GPIO95_AC97_nRESET_GPIO_HIGH, + GPIO95_AC97_nRESET, +}; + +void pxa27x_configure_ac97reset(int reset_gpio, bool to_gpio) +{ + /* + * This helper function is used to work around a bug in the pxa27x's + * ac97 controller during a warm reset. The configuration of the + * reset_gpio is changed as follows: + * to_gpio == true: configured to generic output gpio and driven high + * to_gpio == false: configured to ac97 controller alt fn AC97_nRESET + */ + + if (reset_gpio == 113) + pxa2xx_mfp_config(to_gpio ? &ac97_reset_config[0] : + &ac97_reset_config[1], 1); + + if (reset_gpio == 95) + pxa2xx_mfp_config(to_gpio ? &ac97_reset_config[2] : + &ac97_reset_config[3], 1); +} +EXPORT_SYMBOL_GPL(pxa27x_configure_ac97reset); + +#ifdef CONFIG_PM + +#define SAVE(x) sleep_save[SLEEP_SAVE_##x] = x +#define RESTORE(x) x = sleep_save[SLEEP_SAVE_##x] + +/* + * allow platforms to override default PWRMODE setting used for PM_SUSPEND_MEM + */ +static unsigned int pwrmode = PWRMODE_SLEEP; + +int pxa27x_set_pwrmode(unsigned int mode) +{ + switch (mode) { + case PWRMODE_SLEEP: + case PWRMODE_DEEPSLEEP: + pwrmode = mode; + return 0; + } + + return -EINVAL; +} + +/* + * List of global PXA peripheral registers to preserve. + * More ones like CP and general purpose register values are preserved + * with the stack pointer in sleep.S. + */ +enum { + SLEEP_SAVE_PSTR, + SLEEP_SAVE_MDREFR, + SLEEP_SAVE_PCFR, + SLEEP_SAVE_COUNT +}; + +void pxa27x_cpu_pm_save(unsigned long *sleep_save) +{ + sleep_save[SLEEP_SAVE_MDREFR] = __raw_readl(MDREFR); + SAVE(PCFR); + + SAVE(PSTR); +} + +void pxa27x_cpu_pm_restore(unsigned long *sleep_save) +{ + __raw_writel(sleep_save[SLEEP_SAVE_MDREFR], MDREFR); + RESTORE(PCFR); + + PSSR = PSSR_RDH | PSSR_PH; + + RESTORE(PSTR); +} + +void pxa27x_cpu_pm_enter(suspend_state_t state) +{ + extern void pxa_cpu_standby(void); +#ifndef CONFIG_IWMMXT + u64 acc0; + + asm volatile(".arch_extension xscale\n\t" + "mra %Q0, %R0, acc0" : "=r" (acc0)); +#endif + + /* ensure voltage-change sequencer not initiated, which hangs */ + PCFR &= ~PCFR_FVC; + + /* Clear edge-detect status register. */ + PEDR = 0xDF12FE1B; + + /* Clear reset status */ + RCSR = RCSR_HWR | RCSR_WDR | RCSR_SMR | RCSR_GPR; + + switch (state) { + case PM_SUSPEND_STANDBY: + pxa_cpu_standby(); + break; + case PM_SUSPEND_MEM: + cpu_suspend(pwrmode, pxa27x_finish_suspend); +#ifndef CONFIG_IWMMXT + asm volatile(".arch_extension xscale\n\t" + "mar acc0, %Q0, %R0" : "=r" (acc0)); +#endif + break; + } +} + +static int pxa27x_cpu_pm_valid(suspend_state_t state) +{ + return state == PM_SUSPEND_MEM || state == PM_SUSPEND_STANDBY; +} + +static int pxa27x_cpu_pm_prepare(void) +{ + /* set resume return address */ + PSPR = __pa_symbol(cpu_resume); + return 0; +} + +static void pxa27x_cpu_pm_finish(void) +{ + /* ensure not to come back here if it wasn't intended */ + PSPR = 0; +} + +static struct pxa_cpu_pm_fns pxa27x_cpu_pm_fns = { + .save_count = SLEEP_SAVE_COUNT, + .save = pxa27x_cpu_pm_save, + .restore = pxa27x_cpu_pm_restore, + .valid = pxa27x_cpu_pm_valid, + .enter = pxa27x_cpu_pm_enter, + .prepare = pxa27x_cpu_pm_prepare, + .finish = pxa27x_cpu_pm_finish, +}; + +static void __init pxa27x_init_pm(void) +{ + pxa_cpu_pm_fns = &pxa27x_cpu_pm_fns; +} +#else +static inline void pxa27x_init_pm(void) {} +#endif + +/* PXA27x: Various gpios can issue wakeup events. This logic only + * handles the simple cases, not the WEMUX2 and WEMUX3 options + */ +static int pxa27x_set_wake(struct irq_data *d, unsigned int on) +{ + int gpio = pxa_irq_to_gpio(d->irq); + uint32_t mask; + + if (gpio >= 0 && gpio < 128) + return gpio_set_wake(gpio, on); + + if (d->irq == IRQ_KEYPAD) + return keypad_set_wake(on); + + switch (d->irq) { + case IRQ_RTCAlrm: + mask = PWER_RTC; + break; + case IRQ_USB: + mask = 1u << 26; + break; + default: + return -EINVAL; + } + + if (on) + PWER |= mask; + else + PWER &=~mask; + + return 0; +} + +void __init pxa27x_init_irq(void) +{ + pxa_init_irq(34, pxa27x_set_wake); +} + +static int __init +pxa27x_dt_init_irq(struct device_node *node, struct device_node *parent) +{ + pxa_dt_irq_init(pxa27x_set_wake); + set_handle_irq(ichp_handle_irq); + + return 0; +} +IRQCHIP_DECLARE(pxa27x_intc, "marvell,pxa-intc", pxa27x_dt_init_irq); + +static struct map_desc pxa27x_io_desc[] __initdata = { + { /* Mem Ctl */ + .virtual = (unsigned long)SMEMC_VIRT, + .pfn = __phys_to_pfn(PXA2XX_SMEMC_BASE), + .length = SMEMC_SIZE, + .type = MT_DEVICE + }, { /* UNCACHED_PHYS_0 */ + .virtual = UNCACHED_PHYS_0, + .pfn = __phys_to_pfn(0x00000000), + .length = UNCACHED_PHYS_0_SIZE, + .type = MT_DEVICE + }, +}; + +void __init pxa27x_map_io(void) +{ + pxa_map_io(); + iotable_init(ARRAY_AND_SIZE(pxa27x_io_desc)); + pxa27x_get_clk_frequency_khz(1); +} + +/* + * device registration specific to PXA27x. + */ +void __init pxa27x_set_i2c_power_info(struct i2c_pxa_platform_data *info) +{ + local_irq_disable(); + PCFR |= PCFR_PI2CEN; + local_irq_enable(); + pxa_register_device(&pxa27x_device_i2c_power, info); +} + +static struct pxa_gpio_platform_data pxa27x_gpio_info __initdata = { + .irq_base = PXA_GPIO_TO_IRQ(0), + .gpio_set_wake = gpio_set_wake, +}; + +static struct platform_device *devices[] __initdata = { + &pxa27x_device_udc, + &pxa_device_pmu, + &pxa_device_i2s, + &pxa_device_asoc_ssp1, + &pxa_device_asoc_ssp2, + &pxa_device_asoc_ssp3, + &pxa_device_asoc_platform, + &pxa_device_rtc, + &pxa27x_device_ssp1, + &pxa27x_device_ssp2, + &pxa27x_device_ssp3, + &pxa27x_device_pwm0, + &pxa27x_device_pwm1, +}; + +static const struct dma_slave_map pxa27x_slave_map[] = { + /* PXA25x, PXA27x and PXA3xx common entries */ + { "pxa2xx-ac97", "pcm_pcm_mic_mono", PDMA_FILTER_PARAM(LOWEST, 8) }, + { "pxa2xx-ac97", "pcm_pcm_aux_mono_in", PDMA_FILTER_PARAM(LOWEST, 9) }, + { "pxa2xx-ac97", "pcm_pcm_aux_mono_out", + PDMA_FILTER_PARAM(LOWEST, 10) }, + { "pxa2xx-ac97", "pcm_pcm_stereo_in", PDMA_FILTER_PARAM(LOWEST, 11) }, + { "pxa2xx-ac97", "pcm_pcm_stereo_out", PDMA_FILTER_PARAM(LOWEST, 12) }, + { "pxa-ssp-dai.0", "rx", PDMA_FILTER_PARAM(LOWEST, 13) }, + { "pxa-ssp-dai.0", "tx", PDMA_FILTER_PARAM(LOWEST, 14) }, + { "pxa-ssp-dai.1", "rx", PDMA_FILTER_PARAM(LOWEST, 15) }, + { "pxa-ssp-dai.1", "tx", PDMA_FILTER_PARAM(LOWEST, 16) }, + { "pxa2xx-ir", "rx", PDMA_FILTER_PARAM(LOWEST, 17) }, + { "pxa2xx-ir", "tx", PDMA_FILTER_PARAM(LOWEST, 18) }, + { "pxa2xx-mci.0", "rx", PDMA_FILTER_PARAM(LOWEST, 21) }, + { "pxa2xx-mci.0", "tx", PDMA_FILTER_PARAM(LOWEST, 22) }, + { "pxa-ssp-dai.2", "rx", PDMA_FILTER_PARAM(LOWEST, 66) }, + { "pxa-ssp-dai.2", "tx", PDMA_FILTER_PARAM(LOWEST, 67) }, + + /* PXA27x specific map */ + { "pxa2xx-i2s", "rx", PDMA_FILTER_PARAM(LOWEST, 2) }, + { "pxa2xx-i2s", "tx", PDMA_FILTER_PARAM(LOWEST, 3) }, + { "pxa27x-camera.0", "CI_Y", PDMA_FILTER_PARAM(HIGHEST, 68) }, + { "pxa27x-camera.0", "CI_U", PDMA_FILTER_PARAM(HIGHEST, 69) }, + { "pxa27x-camera.0", "CI_V", PDMA_FILTER_PARAM(HIGHEST, 70) }, +}; + +static struct mmp_dma_platdata pxa27x_dma_pdata = { + .dma_channels = 32, + .nb_requestors = 75, + .slave_map = pxa27x_slave_map, + .slave_map_cnt = ARRAY_SIZE(pxa27x_slave_map), +}; + +static int __init pxa27x_init(void) +{ + int ret = 0; + + if (cpu_is_pxa27x()) { + + pxa_register_wdt(RCSR); + + pxa27x_init_pm(); + + register_syscore_ops(&pxa_irq_syscore_ops); + register_syscore_ops(&pxa2xx_mfp_syscore_ops); + + if (!of_have_populated_dt()) { + pxa_register_device(&pxa27x_device_gpio, + &pxa27x_gpio_info); + pxa2xx_set_dmac_info(&pxa27x_dma_pdata); + ret = platform_add_devices(devices, + ARRAY_SIZE(devices)); + } + } + + return ret; +} + +postcore_initcall(pxa27x_init); |