diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /arch/arm/mach-at91 | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/arm/mach-at91')
-rw-r--r-- | arch/arm/mach-at91/.gitignore | 2 | ||||
-rw-r--r-- | arch/arm/mach-at91/Kconfig | 241 | ||||
-rw-r--r-- | arch/arm/mach-at91/Makefile | 30 | ||||
-rw-r--r-- | arch/arm/mach-at91/at91rm9200.c | 32 | ||||
-rw-r--r-- | arch/arm/mach-at91/at91sam9.c | 33 | ||||
-rw-r--r-- | arch/arm/mach-at91/generic.h | 27 | ||||
-rw-r--r-- | arch/arm/mach-at91/pm.c | 1668 | ||||
-rw-r--r-- | arch/arm/mach-at91/pm.h | 45 | ||||
-rw-r--r-- | arch/arm/mach-at91/pm_data-offsets.c | 23 | ||||
-rw-r--r-- | arch/arm/mach-at91/pm_suspend.S | 1157 | ||||
-rw-r--r-- | arch/arm/mach-at91/sam9x60.c | 34 | ||||
-rw-r--r-- | arch/arm/mach-at91/sam_secure.c | 52 | ||||
-rw-r--r-- | arch/arm/mach-at91/sam_secure.h | 19 | ||||
-rw-r--r-- | arch/arm/mach-at91/sama5.c | 79 | ||||
-rw-r--r-- | arch/arm/mach-at91/sama7.c | 33 | ||||
-rw-r--r-- | arch/arm/mach-at91/samv7.c | 24 |
16 files changed, 3499 insertions, 0 deletions
diff --git a/arch/arm/mach-at91/.gitignore b/arch/arm/mach-at91/.gitignore new file mode 100644 index 000000000..f6d473896 --- /dev/null +++ b/arch/arm/mach-at91/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +pm_data-offsets.h diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig new file mode 100644 index 000000000..3dd9e7186 --- /dev/null +++ b/arch/arm/mach-at91/Kconfig @@ -0,0 +1,241 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig ARCH_AT91 + bool "AT91/Microchip SoCs" + depends on (CPU_LITTLE_ENDIAN && (ARCH_MULTI_V4T || ARCH_MULTI_V5)) || \ + ARCH_MULTI_V7 || ARM_SINGLE_ARMV7M + select ARM_CPU_SUSPEND if PM && ARCH_MULTI_V7 + select COMMON_CLK_AT91 + select GPIOLIB + select PINCTRL + select SOC_BUS + +if ARCH_AT91 +config SOC_SAMV7 + bool "SAM Cortex-M7 family" if ARM_SINGLE_ARMV7M + select COMMON_CLK_AT91 + select PINCTRL_AT91 + help + Select this if you are using an SoC from Microchip's SAME7, SAMS7 or SAMV7 + families. + +config SOC_SAMA5D2 + bool "SAMA5D2 family" + depends on ARCH_MULTI_V7 + select SOC_SAMA5 + select CACHE_L2X0 + select HAVE_AT91_UTMI + select HAVE_AT91_USB_CLK + select HAVE_AT91_H32MX + select HAVE_AT91_GENERATED_CLK + select HAVE_AT91_AUDIO_PLL + select HAVE_AT91_I2S_MUX_CLK + select PINCTRL_AT91PIO4 + help + Select this if ou are using one of Microchip's SAMA5D2 family SoC. + +config SOC_SAMA5D3 + bool "SAMA5D3 family" + depends on ARCH_MULTI_V7 + select SOC_SAMA5 + select HAVE_AT91_UTMI + select HAVE_AT91_SMD + select HAVE_AT91_USB_CLK + select PINCTRL_AT91 + help + Select this if you are using one of Microchip's SAMA5D3 family SoC. + This support covers SAMA5D31, SAMA5D33, SAMA5D34, SAMA5D35, SAMA5D36. + +config SOC_SAMA5D4 + bool "SAMA5D4 family" + depends on ARCH_MULTI_V7 + select SOC_SAMA5 + select CACHE_L2X0 + select HAVE_AT91_UTMI + select HAVE_AT91_SMD + select HAVE_AT91_USB_CLK + select HAVE_AT91_H32MX + select PINCTRL_AT91 + help + Select this if you are using one of Microchip's SAMA5D4 family SoC. + +config SOC_SAMA7G5 + bool "SAMA7G5 family" + depends on ARCH_MULTI_V7 + select HAVE_AT91_GENERATED_CLK + select HAVE_AT91_SAM9X60_PLL + select HAVE_AT91_UTMI + select PM_OPP + select SOC_SAMA7 + help + Select this if you are using one of Microchip's SAMA7G5 family SoC. + +config SOC_LAN966 + bool "ARMv7 based Microchip LAN966 SoC family" + depends on ARCH_MULTI_V7 + select DW_APB_TIMER_OF + select ARM_GIC + select MEMORY + help + This enables support for ARMv7 based Microchip LAN966 SoC family. + +config SOC_AT91RM9200 + bool "AT91RM9200" + depends on ARCH_MULTI_V4T + select ATMEL_AIC_IRQ + select ATMEL_PM if PM + select ATMEL_ST + select CPU_ARM920T + select HAVE_AT91_USB_CLK + select PINCTRL_AT91 + select SOC_SAM_V4_V5 + select SRAM if PM + help + Select this if you are using Microchip's AT91RM9200 SoC. + +config SOC_AT91SAM9 + bool "AT91SAM9" + depends on ARCH_MULTI_V5 + select ATMEL_AIC_IRQ + select ATMEL_PM if PM + select ATMEL_SDRAMC + select CPU_ARM926T + select HAVE_AT91_SMD + select HAVE_AT91_USB_CLK + select HAVE_AT91_UTMI + select HAVE_FB_ATMEL + select MEMORY + select PINCTRL_AT91 + select SOC_SAM_V4_V5 + select SRAM if PM + help + Select this if you are using one of those Microchip SoC: + AT91SAM9260 + AT91SAM9261 + AT91SAM9263 + AT91SAM9G15 + AT91SAM9G20 + AT91SAM9G25 + AT91SAM9G35 + AT91SAM9G45 + AT91SAM9G46 + AT91SAM9M10 + AT91SAM9M11 + AT91SAM9N12 + AT91SAM9RL + AT91SAM9X25 + AT91SAM9X35 + AT91SAM9XE + +config SOC_SAM9X60 + bool "SAM9X60" + depends on ARCH_MULTI_V5 + select ATMEL_AIC5_IRQ + select ATMEL_PM if PM + select ATMEL_SDRAMC + select CPU_ARM926T + select HAVE_AT91_USB_CLK + select HAVE_AT91_GENERATED_CLK + select HAVE_AT91_SAM9X60_PLL + select MEMORY + select PINCTRL_AT91 + select SOC_SAM_V4_V5 + select SRAM if PM + help + Select this if you are using Microchip's SAM9X60 SoC + +comment "Clocksource driver selection" + +config ATMEL_CLOCKSOURCE_PIT + bool "Periodic Interval Timer (PIT) support" + depends on SOC_AT91SAM9 || SOC_SAM9X60 || SOC_SAMA5 + default SOC_AT91SAM9 || SOC_SAMA5 + select ATMEL_PIT + help + Select this to get a clocksource based on the Atmel Periodic Interval + Timer. It has a relatively low resolution and the TC Block clocksource + should be preferred. + +config ATMEL_CLOCKSOURCE_TCB + bool "Timer Counter Blocks (TCB) support" + default SOC_AT91RM9200 || SOC_AT91SAM9 || SOC_SAM9X60 || SOC_SAMA5 + select ATMEL_TCB_CLKSRC + help + Select this to get a high precision clocksource based on a + TC block with a 5+ MHz base clock rate. + On platforms with 16-bit counters, two timer channels are combined + to make a single 32-bit timer. + It can also be used as a clock event device supporting oneshot mode. + +config MICROCHIP_CLOCKSOURCE_PIT64B + bool "64-bit Periodic Interval Timer (PIT64B) support" + default SOC_SAM9X60 || SOC_SAMA7 + select MICROCHIP_PIT64B + help + Select this to get a high resolution clockevent (SAM9X60) or + clocksource and clockevent (SAMA7G5) based on Microchip 64-bit + Periodic Interval Timer. + +config HAVE_AT91_UTMI + bool + +config HAVE_AT91_USB_CLK + bool + +config COMMON_CLK_AT91 + bool + select MFD_SYSCON + +config HAVE_AT91_SMD + bool + +config HAVE_AT91_H32MX + bool + +config HAVE_AT91_GENERATED_CLK + bool + +config HAVE_AT91_AUDIO_PLL + bool + +config HAVE_AT91_I2S_MUX_CLK + bool + +config HAVE_AT91_SAM9X60_PLL + bool + +config SOC_SAM_V4_V5 + bool + +config SOC_SAM_V7 + bool + +config SOC_SAMA5 + bool + select ATMEL_AIC5_IRQ + select ATMEL_PM if PM + select ATMEL_SDRAMC + select MEMORY + select SOC_SAM_V7 + select SRAM if PM + +config ATMEL_PM + bool + +config ATMEL_SECURE_PM + bool "Atmel Secure PM support" + depends on SOC_SAMA5D2 && ATMEL_PM + select ARM_PSCI + help + When running under a TEE, the suspend mode must be requested to be set + at TEE level. When enable, this option will use secure monitor calls + to set the suspend level. PSCI is then used to enter suspend. + +config SOC_SAMA7 + bool + select ARM_GIC + select ATMEL_PM if PM + select ATMEL_SDRAMC + select MEMORY + select SOC_SAM_V7 + select SRAM if PM +endif diff --git a/arch/arm/mach-at91/Makefile b/arch/arm/mach-at91/Makefile new file mode 100644 index 000000000..0dcc37180 --- /dev/null +++ b/arch/arm/mach-at91/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the linux kernel. +# + +# CPU-specific support +obj-$(CONFIG_SOC_AT91RM9200) += at91rm9200.o +obj-$(CONFIG_SOC_AT91SAM9) += at91sam9.o +obj-$(CONFIG_SOC_SAM9X60) += sam9x60.o +obj-$(CONFIG_SOC_SAMA5) += sama5.o sam_secure.o +obj-$(CONFIG_SOC_SAMA7) += sama7.o +obj-$(CONFIG_SOC_SAMV7) += samv7.o + +# Power Management +obj-$(CONFIG_ATMEL_PM) += pm.o pm_suspend.o + +ifeq ($(CONFIG_CPU_V7),y) +AFLAGS_pm_suspend.o := -march=armv7-a +endif +ifeq ($(CONFIG_PM_DEBUG),y) +CFLAGS_pm.o += -DDEBUG +endif + +$(obj)/pm_data-offsets.h: $(obj)/pm_data-offsets.s FORCE + $(call filechk,offsets,__PM_DATA_OFFSETS_H__) + +$(obj)/pm_suspend.o: $(obj)/pm_data-offsets.h + +targets += pm_data-offsets.s +clean-files += pm_data-offsets.h diff --git a/arch/arm/mach-at91/at91rm9200.c b/arch/arm/mach-at91/at91rm9200.c new file mode 100644 index 000000000..4f8186211 --- /dev/null +++ b/arch/arm/mach-at91/at91rm9200.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Setup code for AT91RM9200 + * + * Copyright (C) 2011 Atmel, + * 2011 Nicolas Ferre <nicolas.ferre@atmel.com> + * 2012 Joachim Eastwood <manabian@gmail.com> + */ + +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <asm/mach/arch.h> + +#include "generic.h" + +static void __init at91rm9200_dt_device_init(void) +{ + of_platform_default_populate(NULL, NULL, NULL); + + at91rm9200_pm_init(); +} + +static const char *const at91rm9200_dt_board_compat[] __initconst = { + "atmel,at91rm9200", + NULL +}; + +DT_MACHINE_START(at91rm9200_dt, "Atmel AT91RM9200") + .init_machine = at91rm9200_dt_device_init, + .dt_compat = at91rm9200_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-at91/at91sam9.c b/arch/arm/mach-at91/at91sam9.c new file mode 100644 index 000000000..7e572189a --- /dev/null +++ b/arch/arm/mach-at91/at91sam9.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Setup code for AT91SAM9 + * + * Copyright (C) 2011 Atmel, + * 2011 Nicolas Ferre <nicolas.ferre@atmel.com> + */ + +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <asm/mach/arch.h> +#include <asm/system_misc.h> + +#include "generic.h" + +static void __init at91sam9_init(void) +{ + of_platform_default_populate(NULL, NULL, NULL); + + at91sam9_pm_init(); +} + +static const char *const at91_dt_board_compat[] __initconst = { + "atmel,at91sam9", + NULL +}; + +DT_MACHINE_START(at91sam_dt, "Atmel AT91SAM9") + /* Maintainer: Atmel */ + .init_machine = at91sam9_init, + .dt_compat = at91_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-at91/generic.h b/arch/arm/mach-at91/generic.h new file mode 100644 index 000000000..0c3960a8b --- /dev/null +++ b/arch/arm/mach-at91/generic.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * linux/arch/arm/mach-at91/generic.h + * + * Copyright (C) 2005 David Brownell + */ + +#ifndef _AT91_GENERIC_H +#define _AT91_GENERIC_H + +#ifdef CONFIG_PM +extern void __init at91rm9200_pm_init(void); +extern void __init at91sam9_pm_init(void); +extern void __init sam9x60_pm_init(void); +extern void __init sama5_pm_init(void); +extern void __init sama5d2_pm_init(void); +extern void __init sama7_pm_init(void); +#else +static inline void __init at91rm9200_pm_init(void) { } +static inline void __init at91sam9_pm_init(void) { } +static inline void __init sam9x60_pm_init(void) { } +static inline void __init sama5_pm_init(void) { } +static inline void __init sama5d2_pm_init(void) { } +static inline void __init sama7_pm_init(void) { } +#endif + +#endif /* _AT91_GENERIC_H */ diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c new file mode 100644 index 000000000..437dd0352 --- /dev/null +++ b/arch/arm/mach-at91/pm.c @@ -0,0 +1,1668 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * arch/arm/mach-at91/pm.c + * AT91 Power Management + * + * Copyright (C) 2005 David Brownell + */ + +#include <linux/genalloc.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/of_platform.h> +#include <linux/parser.h> +#include <linux/suspend.h> + +#include <linux/clk.h> +#include <linux/clk/at91_pmc.h> +#include <linux/platform_data/atmel.h> + +#include <asm/cacheflush.h> +#include <asm/fncpy.h> +#include <asm/system_misc.h> +#include <asm/suspend.h> + +#include "generic.h" +#include "pm.h" +#include "sam_secure.h" + +#define BACKUP_DDR_PHY_CALIBRATION (9) + +/** + * struct at91_pm_bu - AT91 power management backup unit data structure + * @suspended: true if suspended to backup mode + * @reserved: reserved + * @canary: canary data for memory checking after exit from backup mode + * @resume: resume API + * @ddr_phy_calibration: DDR PHY calibration data: ZQ0CR0, first 8 words + * of the memory + */ +struct at91_pm_bu { + int suspended; + unsigned long reserved; + phys_addr_t canary; + phys_addr_t resume; + unsigned long ddr_phy_calibration[BACKUP_DDR_PHY_CALIBRATION]; +}; + +/** + * struct at91_pm_sfrbu_regs - registers mapping for SFRBU + * @pswbu: power switch BU control registers + */ +struct at91_pm_sfrbu_regs { + struct { + u32 key; + u32 ctrl; + u32 state; + u32 softsw; + } pswbu; +}; + +/** + * enum at91_pm_eth_clk - Ethernet clock indexes + * @AT91_PM_ETH_PCLK: pclk index + * @AT91_PM_ETH_HCLK: hclk index + * @AT91_PM_ETH_MAX_CLK: max index + */ +enum at91_pm_eth_clk { + AT91_PM_ETH_PCLK, + AT91_PM_ETH_HCLK, + AT91_PM_ETH_MAX_CLK, +}; + +/** + * enum at91_pm_eth - Ethernet controller indexes + * @AT91_PM_G_ETH: gigabit Ethernet controller index + * @AT91_PM_E_ETH: megabit Ethernet controller index + * @AT91_PM_MAX_ETH: max index + */ +enum at91_pm_eth { + AT91_PM_G_ETH, + AT91_PM_E_ETH, + AT91_PM_MAX_ETH, +}; + +/** + * struct at91_pm_quirk_eth - AT91 PM Ethernet quirks + * @dev: Ethernet device + * @np: Ethernet device node + * @clks: Ethernet clocks + * @modes: power management mode that this quirk applies to + * @dns_modes: do not suspend modes: stop suspending if Ethernet is configured + * as wakeup source but buggy and no other wakeup source is + * available + */ +struct at91_pm_quirk_eth { + struct device *dev; + struct device_node *np; + struct clk_bulk_data clks[AT91_PM_ETH_MAX_CLK]; + u32 modes; + u32 dns_modes; +}; + +/** + * struct at91_pm_quirks - AT91 PM quirks + * @eth: Ethernet quirks + */ +struct at91_pm_quirks { + struct at91_pm_quirk_eth eth[AT91_PM_MAX_ETH]; +}; + +/** + * struct at91_soc_pm - AT91 SoC power management data structure + * @config_shdwc_ws: wakeup sources configuration function for SHDWC + * @config_pmc_ws: wakeup srouces configuration function for PMC + * @ws_ids: wakup sources of_device_id array + * @bu: backup unit mapped data (for backup mode) + * @quirks: PM quirks + * @data: PM data to be used on last phase of suspend + * @sfrbu_regs: SFRBU registers mapping + * @memcs: memory chip select + */ +struct at91_soc_pm { + int (*config_shdwc_ws)(void __iomem *shdwc, u32 *mode, u32 *polarity); + int (*config_pmc_ws)(void __iomem *pmc, u32 mode, u32 polarity); + const struct of_device_id *ws_ids; + struct at91_pm_bu *bu; + struct at91_pm_quirks quirks; + struct at91_pm_data data; + struct at91_pm_sfrbu_regs sfrbu_regs; + void *memcs; +}; + +/** + * enum at91_pm_iomaps - IOs that needs to be mapped for different PM modes + * @AT91_PM_IOMAP_SHDWC: SHDWC controller + * @AT91_PM_IOMAP_SFRBU: SFRBU controller + * @AT91_PM_IOMAP_ETHC: Ethernet controller + */ +enum at91_pm_iomaps { + AT91_PM_IOMAP_SHDWC, + AT91_PM_IOMAP_SFRBU, + AT91_PM_IOMAP_ETHC, +}; + +#define AT91_PM_IOMAP(name) BIT(AT91_PM_IOMAP_##name) + +static struct at91_soc_pm soc_pm = { + .data = { + .standby_mode = AT91_PM_STANDBY, + .suspend_mode = AT91_PM_ULP0, + }, +}; + +static const match_table_t pm_modes __initconst = { + { AT91_PM_STANDBY, "standby" }, + { AT91_PM_ULP0, "ulp0" }, + { AT91_PM_ULP0_FAST, "ulp0-fast" }, + { AT91_PM_ULP1, "ulp1" }, + { AT91_PM_BACKUP, "backup" }, + { -1, NULL }, +}; + +#define at91_ramc_read(id, field) \ + __raw_readl(soc_pm.data.ramc[id] + field) + +#define at91_ramc_write(id, field, value) \ + __raw_writel(value, soc_pm.data.ramc[id] + field) + +static int at91_pm_valid_state(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_ON: + case PM_SUSPEND_STANDBY: + case PM_SUSPEND_MEM: + return 1; + + default: + return 0; + } +} + +static int canary = 0xA5A5A5A5; + +struct wakeup_source_info { + unsigned int pmc_fsmr_bit; + unsigned int shdwc_mr_bit; + bool set_polarity; +}; + +static const struct wakeup_source_info ws_info[] = { + { .pmc_fsmr_bit = AT91_PMC_FSTT(10), .set_polarity = true }, + { .pmc_fsmr_bit = AT91_PMC_RTCAL, .shdwc_mr_bit = BIT(17) }, + { .pmc_fsmr_bit = AT91_PMC_USBAL }, + { .pmc_fsmr_bit = AT91_PMC_SDMMC_CD }, + { .pmc_fsmr_bit = AT91_PMC_RTTAL }, + { .pmc_fsmr_bit = AT91_PMC_RXLP_MCE }, +}; + +static const struct of_device_id sama5d2_ws_ids[] = { + { .compatible = "atmel,sama5d2-gem", .data = &ws_info[0] }, + { .compatible = "atmel,sama5d2-rtc", .data = &ws_info[1] }, + { .compatible = "atmel,sama5d3-udc", .data = &ws_info[2] }, + { .compatible = "atmel,at91rm9200-ohci", .data = &ws_info[2] }, + { .compatible = "usb-ohci", .data = &ws_info[2] }, + { .compatible = "atmel,at91sam9g45-ehci", .data = &ws_info[2] }, + { .compatible = "usb-ehci", .data = &ws_info[2] }, + { .compatible = "atmel,sama5d2-sdhci", .data = &ws_info[3] }, + { /* sentinel */ } +}; + +static const struct of_device_id sam9x60_ws_ids[] = { + { .compatible = "microchip,sam9x60-rtc", .data = &ws_info[1] }, + { .compatible = "atmel,at91rm9200-ohci", .data = &ws_info[2] }, + { .compatible = "usb-ohci", .data = &ws_info[2] }, + { .compatible = "atmel,at91sam9g45-ehci", .data = &ws_info[2] }, + { .compatible = "usb-ehci", .data = &ws_info[2] }, + { .compatible = "microchip,sam9x60-rtt", .data = &ws_info[4] }, + { .compatible = "cdns,sam9x60-macb", .data = &ws_info[5] }, + { /* sentinel */ } +}; + +static const struct of_device_id sama7g5_ws_ids[] = { + { .compatible = "microchip,sama7g5-rtc", .data = &ws_info[1] }, + { .compatible = "microchip,sama7g5-ohci", .data = &ws_info[2] }, + { .compatible = "usb-ohci", .data = &ws_info[2] }, + { .compatible = "atmel,at91sam9g45-ehci", .data = &ws_info[2] }, + { .compatible = "usb-ehci", .data = &ws_info[2] }, + { .compatible = "microchip,sama7g5-sdhci", .data = &ws_info[3] }, + { .compatible = "microchip,sama7g5-rtt", .data = &ws_info[4] }, + { /* sentinel */ } +}; + +static int at91_pm_config_ws(unsigned int pm_mode, bool set) +{ + const struct wakeup_source_info *wsi; + const struct of_device_id *match; + struct platform_device *pdev; + struct device_node *np; + unsigned int mode = 0, polarity = 0, val = 0; + + if (pm_mode != AT91_PM_ULP1) + return 0; + + if (!soc_pm.data.pmc || !soc_pm.data.shdwc || !soc_pm.ws_ids) + return -EPERM; + + if (!set) { + writel(mode, soc_pm.data.pmc + AT91_PMC_FSMR); + return 0; + } + + if (soc_pm.config_shdwc_ws) + soc_pm.config_shdwc_ws(soc_pm.data.shdwc, &mode, &polarity); + + /* SHDWC.MR */ + val = readl(soc_pm.data.shdwc + 0x04); + + /* Loop through defined wakeup sources. */ + for_each_matching_node_and_match(np, soc_pm.ws_ids, &match) { + pdev = of_find_device_by_node(np); + if (!pdev) + continue; + + if (device_may_wakeup(&pdev->dev)) { + wsi = match->data; + + /* Check if enabled on SHDWC. */ + if (wsi->shdwc_mr_bit && !(val & wsi->shdwc_mr_bit)) + goto put_device; + + mode |= wsi->pmc_fsmr_bit; + if (wsi->set_polarity) + polarity |= wsi->pmc_fsmr_bit; + } + +put_device: + put_device(&pdev->dev); + } + + if (mode) { + if (soc_pm.config_pmc_ws) + soc_pm.config_pmc_ws(soc_pm.data.pmc, mode, polarity); + } else { + pr_err("AT91: PM: no ULP1 wakeup sources found!"); + } + + return mode ? 0 : -EPERM; +} + +static int at91_sama5d2_config_shdwc_ws(void __iomem *shdwc, u32 *mode, + u32 *polarity) +{ + u32 val; + + /* SHDWC.WUIR */ + val = readl(shdwc + 0x0c); + *mode |= (val & 0x3ff); + *polarity |= ((val >> 16) & 0x3ff); + + return 0; +} + +static int at91_sama5d2_config_pmc_ws(void __iomem *pmc, u32 mode, u32 polarity) +{ + writel(mode, pmc + AT91_PMC_FSMR); + writel(polarity, pmc + AT91_PMC_FSPR); + + return 0; +} + +static int at91_sam9x60_config_pmc_ws(void __iomem *pmc, u32 mode, u32 polarity) +{ + writel(mode, pmc + AT91_PMC_FSMR); + + return 0; +} + +static bool at91_pm_eth_quirk_is_valid(struct at91_pm_quirk_eth *eth) +{ + struct platform_device *pdev; + + /* Interface NA in DT. */ + if (!eth->np) + return false; + + /* No quirks for this interface and current suspend mode. */ + if (!(eth->modes & BIT(soc_pm.data.mode))) + return false; + + if (!eth->dev) { + /* Driver not probed. */ + pdev = of_find_device_by_node(eth->np); + if (!pdev) + return false; + /* put_device(eth->dev) is called at the end of suspend. */ + eth->dev = &pdev->dev; + } + + /* No quirks if device isn't a wakeup source. */ + if (!device_may_wakeup(eth->dev)) + return false; + + return true; +} + +static int at91_pm_config_quirks(bool suspend) +{ + struct at91_pm_quirk_eth *eth; + int i, j, ret, tmp; + + /* + * Ethernet IPs who's device_node pointers are stored into + * soc_pm.quirks.eth[].np cannot handle WoL packets while in ULP0, ULP1 + * or both due to a hardware bug. If they receive WoL packets while in + * ULP0 or ULP1 IPs could stop working or the whole system could stop + * working. We cannot handle this scenario in the ethernet driver itself + * as the driver is common to multiple vendors and also we only know + * here, in this file, if we suspend to ULP0 or ULP1 mode. Thus handle + * these scenarios here, as quirks. + */ + for (i = 0; i < AT91_PM_MAX_ETH; i++) { + eth = &soc_pm.quirks.eth[i]; + + if (!at91_pm_eth_quirk_is_valid(eth)) + continue; + + /* + * For modes in dns_modes mask the system blocks if quirk is not + * applied but if applied the interface doesn't act at WoL + * events. Thus take care to avoid suspending if this interface + * is the only configured wakeup source. + */ + if (suspend && eth->dns_modes & BIT(soc_pm.data.mode)) { + int ws_count = 0; +#ifdef CONFIG_PM_SLEEP + struct wakeup_source *ws; + + for_each_wakeup_source(ws) { + if (ws->dev == eth->dev) + continue; + + ws_count++; + break; + } +#endif + + /* + * Checking !ws is good for all platforms with issues + * even when both G_ETH and E_ETH are available as dns_modes + * is populated only on G_ETH interface. + */ + if (!ws_count) { + pr_err("AT91: PM: Ethernet cannot resume from WoL!"); + ret = -EPERM; + put_device(eth->dev); + eth->dev = NULL; + /* No need to revert clock settings for this eth. */ + i--; + goto clk_unconfigure; + } + } + + if (suspend) { + clk_bulk_disable_unprepare(AT91_PM_ETH_MAX_CLK, eth->clks); + } else { + ret = clk_bulk_prepare_enable(AT91_PM_ETH_MAX_CLK, + eth->clks); + if (ret) + goto clk_unconfigure; + /* + * Release the reference to eth->dev taken in + * at91_pm_eth_quirk_is_valid(). + */ + put_device(eth->dev); + eth->dev = NULL; + } + } + + return 0; + +clk_unconfigure: + /* + * In case of resume we reach this point if clk_prepare_enable() failed. + * we don't want to revert the previous clk_prepare_enable() for the + * other IP. + */ + for (j = i; j >= 0; j--) { + eth = &soc_pm.quirks.eth[j]; + if (suspend) { + if (!at91_pm_eth_quirk_is_valid(eth)) + continue; + + tmp = clk_bulk_prepare_enable(AT91_PM_ETH_MAX_CLK, eth->clks); + if (tmp) { + pr_err("AT91: PM: failed to enable %s clocks\n", + j == AT91_PM_G_ETH ? "geth" : "eth"); + } + } + + /* + * Release the reference to eth->dev taken in + * at91_pm_eth_quirk_is_valid(). + */ + put_device(eth->dev); + eth->dev = NULL; + } + + return ret; +} + +/* + * Called after processes are frozen, but before we shutdown devices. + */ +static int at91_pm_begin(suspend_state_t state) +{ + int ret; + + switch (state) { + case PM_SUSPEND_MEM: + soc_pm.data.mode = soc_pm.data.suspend_mode; + break; + + case PM_SUSPEND_STANDBY: + soc_pm.data.mode = soc_pm.data.standby_mode; + break; + + default: + soc_pm.data.mode = -1; + } + + ret = at91_pm_config_ws(soc_pm.data.mode, true); + if (ret) + return ret; + + if (soc_pm.data.mode == AT91_PM_BACKUP) + soc_pm.bu->suspended = 1; + else if (soc_pm.bu) + soc_pm.bu->suspended = 0; + + return 0; +} + +/* + * Verify that all the clocks are correct before entering + * slow-clock mode. + */ +static int at91_pm_verify_clocks(void) +{ + unsigned long scsr; + int i; + + scsr = readl(soc_pm.data.pmc + AT91_PMC_SCSR); + + /* USB must not be using PLLB */ + if ((scsr & soc_pm.data.uhp_udp_mask) != 0) { + pr_err("AT91: PM - Suspend-to-RAM with USB still active\n"); + return 0; + } + + /* PCK0..PCK3 must be disabled, or configured to use clk32k */ + for (i = 0; i < 4; i++) { + u32 css; + + if ((scsr & (AT91_PMC_PCK0 << i)) == 0) + continue; + css = readl(soc_pm.data.pmc + AT91_PMC_PCKR(i)) & AT91_PMC_CSS; + if (css != AT91_PMC_CSS_SLOW) { + pr_err("AT91: PM - Suspend-to-RAM with PCK%d src %d\n", i, css); + return 0; + } + } + + return 1; +} + +/* + * Call this from platform driver suspend() to see how deeply to suspend. + * For example, some controllers (like OHCI) need one of the PLL clocks + * in order to act as a wakeup source, and those are not available when + * going into slow clock mode. + * + * REVISIT: generalize as clk_will_be_available(clk)? Other platforms have + * the very same problem (but not using at91 main_clk), and it'd be better + * to add one generic API rather than lots of platform-specific ones. + */ +int at91_suspend_entering_slow_clock(void) +{ + return (soc_pm.data.mode >= AT91_PM_ULP0); +} +EXPORT_SYMBOL(at91_suspend_entering_slow_clock); + +static void (*at91_suspend_sram_fn)(struct at91_pm_data *); +extern void at91_pm_suspend_in_sram(struct at91_pm_data *pm_data); +extern u32 at91_pm_suspend_in_sram_sz; + +static int at91_suspend_finish(unsigned long val) +{ + unsigned char modified_gray_code[] = { + 0x00, 0x01, 0x02, 0x03, 0x06, 0x07, 0x04, 0x05, 0x0c, 0x0d, + 0x0e, 0x0f, 0x0a, 0x0b, 0x08, 0x09, 0x18, 0x19, 0x1a, 0x1b, + 0x1e, 0x1f, 0x1c, 0x1d, 0x14, 0x15, 0x16, 0x17, 0x12, 0x13, + 0x10, 0x11, + }; + unsigned int tmp, index; + int i; + + if (soc_pm.data.mode == AT91_PM_BACKUP && soc_pm.data.ramc_phy) { + /* + * Bootloader will perform DDR recalibration and will try to + * restore the ZQ0SR0 with the value saved here. But the + * calibration is buggy and restoring some values from ZQ0SR0 + * is forbidden and risky thus we need to provide processed + * values for these (modified gray code values). + */ + tmp = readl(soc_pm.data.ramc_phy + DDR3PHY_ZQ0SR0); + + /* Store pull-down output impedance select. */ + index = (tmp >> DDR3PHY_ZQ0SR0_PDO_OFF) & 0x1f; + soc_pm.bu->ddr_phy_calibration[0] = modified_gray_code[index]; + + /* Store pull-up output impedance select. */ + index = (tmp >> DDR3PHY_ZQ0SR0_PUO_OFF) & 0x1f; + soc_pm.bu->ddr_phy_calibration[0] |= modified_gray_code[index]; + + /* Store pull-down on-die termination impedance select. */ + index = (tmp >> DDR3PHY_ZQ0SR0_PDODT_OFF) & 0x1f; + soc_pm.bu->ddr_phy_calibration[0] |= modified_gray_code[index]; + + /* Store pull-up on-die termination impedance select. */ + index = (tmp >> DDR3PHY_ZQ0SRO_PUODT_OFF) & 0x1f; + soc_pm.bu->ddr_phy_calibration[0] |= modified_gray_code[index]; + + /* + * The 1st 8 words of memory might get corrupted in the process + * of DDR PHY recalibration; it is saved here in securam and it + * will be restored later, after recalibration, by bootloader + */ + for (i = 1; i < BACKUP_DDR_PHY_CALIBRATION; i++) + soc_pm.bu->ddr_phy_calibration[i] = + *((unsigned int *)soc_pm.memcs + (i - 1)); + } + + flush_cache_all(); + outer_disable(); + + at91_suspend_sram_fn(&soc_pm.data); + + return 0; +} + +static void at91_pm_switch_ba_to_vbat(void) +{ + unsigned int offset = offsetof(struct at91_pm_sfrbu_regs, pswbu); + unsigned int val; + + /* Just for safety. */ + if (!soc_pm.data.sfrbu) + return; + + val = readl(soc_pm.data.sfrbu + offset); + + /* Already on VBAT. */ + if (!(val & soc_pm.sfrbu_regs.pswbu.state)) + return; + + val &= ~soc_pm.sfrbu_regs.pswbu.softsw; + val |= soc_pm.sfrbu_regs.pswbu.key | soc_pm.sfrbu_regs.pswbu.ctrl; + writel(val, soc_pm.data.sfrbu + offset); + + /* Wait for update. */ + val = readl(soc_pm.data.sfrbu + offset); + while (val & soc_pm.sfrbu_regs.pswbu.state) + val = readl(soc_pm.data.sfrbu + offset); +} + +static void at91_pm_suspend(suspend_state_t state) +{ + if (soc_pm.data.mode == AT91_PM_BACKUP) { + at91_pm_switch_ba_to_vbat(); + + cpu_suspend(0, at91_suspend_finish); + + /* The SRAM is lost between suspend cycles */ + at91_suspend_sram_fn = fncpy(at91_suspend_sram_fn, + &at91_pm_suspend_in_sram, + at91_pm_suspend_in_sram_sz); + } else { + at91_suspend_finish(0); + } + + outer_resume(); +} + +/* + * STANDBY mode has *all* drivers suspended; ignores irqs not marked as 'wakeup' + * event sources; and reduces DRAM power. But otherwise it's identical to + * PM_SUSPEND_ON: cpu idle, and nothing fancy done with main or cpu clocks. + * + * AT91_PM_ULP0 is like STANDBY plus slow clock mode, so drivers must + * suspend more deeply, the master clock switches to the clk32k and turns off + * the main oscillator + * + * AT91_PM_BACKUP turns off the whole SoC after placing the DDR in self refresh + */ +static int at91_pm_enter(suspend_state_t state) +{ + int ret; + + ret = at91_pm_config_quirks(true); + if (ret) + return ret; + + switch (state) { + case PM_SUSPEND_MEM: + case PM_SUSPEND_STANDBY: + /* + * Ensure that clocks are in a valid state. + */ + if (soc_pm.data.mode >= AT91_PM_ULP0 && + !at91_pm_verify_clocks()) + goto error; + + at91_pm_suspend(state); + + break; + + case PM_SUSPEND_ON: + cpu_do_idle(); + break; + + default: + pr_debug("AT91: PM - bogus suspend state %d\n", state); + goto error; + } + +error: + at91_pm_config_quirks(false); + return 0; +} + +/* + * Called right prior to thawing processes. + */ +static void at91_pm_end(void) +{ + at91_pm_config_ws(soc_pm.data.mode, false); +} + + +static const struct platform_suspend_ops at91_pm_ops = { + .valid = at91_pm_valid_state, + .begin = at91_pm_begin, + .enter = at91_pm_enter, + .end = at91_pm_end, +}; + +static struct platform_device at91_cpuidle_device = { + .name = "cpuidle-at91", +}; + +/* + * The AT91RM9200 goes into self-refresh mode with this command, and will + * terminate self-refresh automatically on the next SDRAM access. + * + * Self-refresh mode is exited as soon as a memory access is made, but we don't + * know for sure when that happens. However, we need to restore the low-power + * mode if it was enabled before going idle. Restoring low-power mode while + * still in self-refresh is "not recommended", but seems to work. + */ +static void at91rm9200_standby(void) +{ + asm volatile( + "b 1f\n\t" + ".align 5\n\t" + "1: mcr p15, 0, %0, c7, c10, 4\n\t" + " str %2, [%1, %3]\n\t" + " mcr p15, 0, %0, c7, c0, 4\n\t" + : + : "r" (0), "r" (soc_pm.data.ramc[0]), + "r" (1), "r" (AT91_MC_SDRAMC_SRR)); +} + +/* We manage both DDRAM/SDRAM controllers, we need more than one value to + * remember. + */ +static void at91_ddr_standby(void) +{ + /* Those two values allow us to delay self-refresh activation + * to the maximum. */ + u32 lpr0, lpr1 = 0; + u32 mdr, saved_mdr0, saved_mdr1 = 0; + u32 saved_lpr0, saved_lpr1 = 0; + + /* LPDDR1 --> force DDR2 mode during self-refresh */ + saved_mdr0 = at91_ramc_read(0, AT91_DDRSDRC_MDR); + if ((saved_mdr0 & AT91_DDRSDRC_MD) == AT91_DDRSDRC_MD_LOW_POWER_DDR) { + mdr = saved_mdr0 & ~AT91_DDRSDRC_MD; + mdr |= AT91_DDRSDRC_MD_DDR2; + at91_ramc_write(0, AT91_DDRSDRC_MDR, mdr); + } + + if (soc_pm.data.ramc[1]) { + saved_lpr1 = at91_ramc_read(1, AT91_DDRSDRC_LPR); + lpr1 = saved_lpr1 & ~AT91_DDRSDRC_LPCB; + lpr1 |= AT91_DDRSDRC_LPCB_SELF_REFRESH; + saved_mdr1 = at91_ramc_read(1, AT91_DDRSDRC_MDR); + if ((saved_mdr1 & AT91_DDRSDRC_MD) == AT91_DDRSDRC_MD_LOW_POWER_DDR) { + mdr = saved_mdr1 & ~AT91_DDRSDRC_MD; + mdr |= AT91_DDRSDRC_MD_DDR2; + at91_ramc_write(1, AT91_DDRSDRC_MDR, mdr); + } + } + + saved_lpr0 = at91_ramc_read(0, AT91_DDRSDRC_LPR); + lpr0 = saved_lpr0 & ~AT91_DDRSDRC_LPCB; + lpr0 |= AT91_DDRSDRC_LPCB_SELF_REFRESH; + + /* self-refresh mode now */ + at91_ramc_write(0, AT91_DDRSDRC_LPR, lpr0); + if (soc_pm.data.ramc[1]) + at91_ramc_write(1, AT91_DDRSDRC_LPR, lpr1); + + cpu_do_idle(); + + at91_ramc_write(0, AT91_DDRSDRC_MDR, saved_mdr0); + at91_ramc_write(0, AT91_DDRSDRC_LPR, saved_lpr0); + if (soc_pm.data.ramc[1]) { + at91_ramc_write(0, AT91_DDRSDRC_MDR, saved_mdr1); + at91_ramc_write(1, AT91_DDRSDRC_LPR, saved_lpr1); + } +} + +static void sama5d3_ddr_standby(void) +{ + u32 lpr0; + u32 saved_lpr0; + + saved_lpr0 = at91_ramc_read(0, AT91_DDRSDRC_LPR); + lpr0 = saved_lpr0 & ~AT91_DDRSDRC_LPCB; + lpr0 |= AT91_DDRSDRC_LPCB_POWER_DOWN; + + at91_ramc_write(0, AT91_DDRSDRC_LPR, lpr0); + + cpu_do_idle(); + + at91_ramc_write(0, AT91_DDRSDRC_LPR, saved_lpr0); +} + +/* We manage both DDRAM/SDRAM controllers, we need more than one value to + * remember. + */ +static void at91sam9_sdram_standby(void) +{ + u32 lpr0, lpr1 = 0; + u32 saved_lpr0, saved_lpr1 = 0; + + if (soc_pm.data.ramc[1]) { + saved_lpr1 = at91_ramc_read(1, AT91_SDRAMC_LPR); + lpr1 = saved_lpr1 & ~AT91_SDRAMC_LPCB; + lpr1 |= AT91_SDRAMC_LPCB_SELF_REFRESH; + } + + saved_lpr0 = at91_ramc_read(0, AT91_SDRAMC_LPR); + lpr0 = saved_lpr0 & ~AT91_SDRAMC_LPCB; + lpr0 |= AT91_SDRAMC_LPCB_SELF_REFRESH; + + /* self-refresh mode now */ + at91_ramc_write(0, AT91_SDRAMC_LPR, lpr0); + if (soc_pm.data.ramc[1]) + at91_ramc_write(1, AT91_SDRAMC_LPR, lpr1); + + cpu_do_idle(); + + at91_ramc_write(0, AT91_SDRAMC_LPR, saved_lpr0); + if (soc_pm.data.ramc[1]) + at91_ramc_write(1, AT91_SDRAMC_LPR, saved_lpr1); +} + +static void sama7g5_standby(void) +{ + int pwrtmg, ratio; + + pwrtmg = readl(soc_pm.data.ramc[0] + UDDRC_PWRCTL); + ratio = readl(soc_pm.data.pmc + AT91_PMC_RATIO); + + /* + * Place RAM into self-refresh after a maximum idle clocks. The maximum + * idle clocks is configured by bootloader in + * UDDRC_PWRMGT.SELFREF_TO_X32. + */ + writel(pwrtmg | UDDRC_PWRCTL_SELFREF_EN, + soc_pm.data.ramc[0] + UDDRC_PWRCTL); + /* Divide CPU clock by 16. */ + writel(ratio & ~AT91_PMC_RATIO_RATIO, soc_pm.data.pmc + AT91_PMC_RATIO); + + cpu_do_idle(); + + /* Restore previous configuration. */ + writel(ratio, soc_pm.data.pmc + AT91_PMC_RATIO); + writel(pwrtmg, soc_pm.data.ramc[0] + UDDRC_PWRCTL); +} + +struct ramc_info { + void (*idle)(void); + unsigned int memctrl; +}; + +static const struct ramc_info ramc_infos[] __initconst = { + { .idle = at91rm9200_standby, .memctrl = AT91_MEMCTRL_MC}, + { .idle = at91sam9_sdram_standby, .memctrl = AT91_MEMCTRL_SDRAMC}, + { .idle = at91_ddr_standby, .memctrl = AT91_MEMCTRL_DDRSDR}, + { .idle = sama5d3_ddr_standby, .memctrl = AT91_MEMCTRL_DDRSDR}, + { .idle = sama7g5_standby, }, +}; + +static const struct of_device_id ramc_ids[] __initconst = { + { .compatible = "atmel,at91rm9200-sdramc", .data = &ramc_infos[0] }, + { .compatible = "atmel,at91sam9260-sdramc", .data = &ramc_infos[1] }, + { .compatible = "atmel,at91sam9g45-ddramc", .data = &ramc_infos[2] }, + { .compatible = "atmel,sama5d3-ddramc", .data = &ramc_infos[3] }, + { .compatible = "microchip,sama7g5-uddrc", .data = &ramc_infos[4], }, + { /*sentinel*/ } +}; + +static const struct of_device_id ramc_phy_ids[] __initconst = { + { .compatible = "microchip,sama7g5-ddr3phy", }, + { /* Sentinel. */ }, +}; + +static __init int at91_dt_ramc(bool phy_mandatory) +{ + struct device_node *np; + const struct of_device_id *of_id; + int idx = 0; + void *standby = NULL; + const struct ramc_info *ramc; + int ret; + + for_each_matching_node_and_match(np, ramc_ids, &of_id) { + soc_pm.data.ramc[idx] = of_iomap(np, 0); + if (!soc_pm.data.ramc[idx]) { + pr_err("unable to map ramc[%d] cpu registers\n", idx); + ret = -ENOMEM; + of_node_put(np); + goto unmap_ramc; + } + + ramc = of_id->data; + if (ramc) { + if (!standby) + standby = ramc->idle; + soc_pm.data.memctrl = ramc->memctrl; + } + + idx++; + } + + if (!idx) { + pr_err("unable to find compatible ram controller node in dtb\n"); + ret = -ENODEV; + goto unmap_ramc; + } + + /* Lookup for DDR PHY node, if any. */ + for_each_matching_node_and_match(np, ramc_phy_ids, &of_id) { + soc_pm.data.ramc_phy = of_iomap(np, 0); + if (!soc_pm.data.ramc_phy) { + pr_err("unable to map ramc phy cpu registers\n"); + ret = -ENOMEM; + of_node_put(np); + goto unmap_ramc; + } + } + + if (phy_mandatory && !soc_pm.data.ramc_phy) { + pr_err("DDR PHY is mandatory!\n"); + ret = -ENODEV; + goto unmap_ramc; + } + + if (!standby) { + pr_warn("ramc no standby function available\n"); + return 0; + } + + at91_cpuidle_device.dev.platform_data = standby; + + return 0; + +unmap_ramc: + while (idx) + iounmap(soc_pm.data.ramc[--idx]); + + return ret; +} + +static void at91rm9200_idle(void) +{ + /* + * Disable the processor clock. The processor will be automatically + * re-enabled by an interrupt or by a reset. + */ + writel(AT91_PMC_PCK, soc_pm.data.pmc + AT91_PMC_SCDR); +} + +static void at91sam9_idle(void) +{ + writel(AT91_PMC_PCK, soc_pm.data.pmc + AT91_PMC_SCDR); + cpu_do_idle(); +} + +static void __init at91_pm_sram_init(void) +{ + struct gen_pool *sram_pool; + phys_addr_t sram_pbase; + unsigned long sram_base; + struct device_node *node; + struct platform_device *pdev = NULL; + + for_each_compatible_node(node, NULL, "mmio-sram") { + pdev = of_find_device_by_node(node); + if (pdev) { + of_node_put(node); + break; + } + } + + if (!pdev) { + pr_warn("%s: failed to find sram device!\n", __func__); + return; + } + + sram_pool = gen_pool_get(&pdev->dev, NULL); + if (!sram_pool) { + pr_warn("%s: sram pool unavailable!\n", __func__); + goto out_put_device; + } + + sram_base = gen_pool_alloc(sram_pool, at91_pm_suspend_in_sram_sz); + if (!sram_base) { + pr_warn("%s: unable to alloc sram!\n", __func__); + goto out_put_device; + } + + sram_pbase = gen_pool_virt_to_phys(sram_pool, sram_base); + at91_suspend_sram_fn = __arm_ioremap_exec(sram_pbase, + at91_pm_suspend_in_sram_sz, false); + if (!at91_suspend_sram_fn) { + pr_warn("SRAM: Could not map\n"); + goto out_put_device; + } + + /* Copy the pm suspend handler to SRAM */ + at91_suspend_sram_fn = fncpy(at91_suspend_sram_fn, + &at91_pm_suspend_in_sram, at91_pm_suspend_in_sram_sz); + return; + +out_put_device: + put_device(&pdev->dev); + return; +} + +static bool __init at91_is_pm_mode_active(int pm_mode) +{ + return (soc_pm.data.standby_mode == pm_mode || + soc_pm.data.suspend_mode == pm_mode); +} + +static int __init at91_pm_backup_scan_memcs(unsigned long node, + const char *uname, int depth, + void *data) +{ + const char *type; + const __be32 *reg; + int *located = data; + int size; + + /* Memory node already located. */ + if (*located) + return 0; + + type = of_get_flat_dt_prop(node, "device_type", NULL); + + /* We are scanning "memory" nodes only. */ + if (!type || strcmp(type, "memory")) + return 0; + + reg = of_get_flat_dt_prop(node, "reg", &size); + if (reg) { + soc_pm.memcs = __va((phys_addr_t)be32_to_cpu(*reg)); + *located = 1; + } + + return 0; +} + +static int __init at91_pm_backup_init(void) +{ + struct gen_pool *sram_pool; + struct device_node *np; + struct platform_device *pdev; + int ret = -ENODEV, located = 0; + + if (!IS_ENABLED(CONFIG_SOC_SAMA5D2) && + !IS_ENABLED(CONFIG_SOC_SAMA7G5)) + return -EPERM; + + if (!at91_is_pm_mode_active(AT91_PM_BACKUP)) + return 0; + + np = of_find_compatible_node(NULL, NULL, "atmel,sama5d2-securam"); + if (!np) + return ret; + + pdev = of_find_device_by_node(np); + of_node_put(np); + if (!pdev) { + pr_warn("%s: failed to find securam device!\n", __func__); + return ret; + } + + sram_pool = gen_pool_get(&pdev->dev, NULL); + if (!sram_pool) { + pr_warn("%s: securam pool unavailable!\n", __func__); + goto securam_fail; + } + + soc_pm.bu = (void *)gen_pool_alloc(sram_pool, sizeof(struct at91_pm_bu)); + if (!soc_pm.bu) { + pr_warn("%s: unable to alloc securam!\n", __func__); + ret = -ENOMEM; + goto securam_fail; + } + + soc_pm.bu->suspended = 0; + soc_pm.bu->canary = __pa_symbol(&canary); + soc_pm.bu->resume = __pa_symbol(cpu_resume); + if (soc_pm.data.ramc_phy) { + of_scan_flat_dt(at91_pm_backup_scan_memcs, &located); + if (!located) + goto securam_fail; + } + + return 0; + +securam_fail: + put_device(&pdev->dev); + return ret; +} + +static void __init at91_pm_secure_init(void) +{ + int suspend_mode; + struct arm_smccc_res res; + + suspend_mode = soc_pm.data.suspend_mode; + + res = sam_smccc_call(SAMA5_SMC_SIP_SET_SUSPEND_MODE, + suspend_mode, 0); + if (res.a0 == 0) { + pr_info("AT91: Secure PM: suspend mode set to %s\n", + pm_modes[suspend_mode].pattern); + return; + } + + pr_warn("AT91: Secure PM: %s mode not supported !\n", + pm_modes[suspend_mode].pattern); + + res = sam_smccc_call(SAMA5_SMC_SIP_GET_SUSPEND_MODE, 0, 0); + if (res.a0 == 0) { + pr_warn("AT91: Secure PM: failed to get default mode\n"); + return; + } + + pr_info("AT91: Secure PM: using default suspend mode %s\n", + pm_modes[suspend_mode].pattern); + + soc_pm.data.suspend_mode = res.a1; +} +static const struct of_device_id atmel_shdwc_ids[] = { + { .compatible = "atmel,sama5d2-shdwc" }, + { .compatible = "microchip,sam9x60-shdwc" }, + { .compatible = "microchip,sama7g5-shdwc" }, + { /* sentinel. */ } +}; + +static const struct of_device_id gmac_ids[] __initconst = { + { .compatible = "atmel,sama5d3-gem" }, + { .compatible = "atmel,sama5d2-gem" }, + { .compatible = "atmel,sama5d29-gem" }, + { .compatible = "microchip,sama7g5-gem" }, + { }, +}; + +static const struct of_device_id emac_ids[] __initconst = { + { .compatible = "atmel,sama5d3-macb" }, + { .compatible = "microchip,sama7g5-emac" }, + { }, +}; + +/* + * Replaces _mode_to_replace with a supported mode that doesn't depend + * on controller pointed by _map_bitmask + * @_maps: u32 array containing AT91_PM_IOMAP() flags and indexed by AT91 + * PM mode + * @_map_bitmask: AT91_PM_IOMAP() bitmask; if _mode_to_replace depends on + * controller represented by _map_bitmask, _mode_to_replace needs to be + * updated + * @_mode_to_replace: standby_mode or suspend_mode that need to be + * updated + * @_mode_to_check: standby_mode or suspend_mode; this is needed here + * to avoid having standby_mode and suspend_mode set with the same AT91 + * PM mode + */ +#define AT91_PM_REPLACE_MODE(_maps, _map_bitmask, _mode_to_replace, \ + _mode_to_check) \ + do { \ + if (((_maps)[(_mode_to_replace)]) & (_map_bitmask)) { \ + int _mode_to_use, _mode_complementary; \ + /* Use ULP0 if it doesn't need _map_bitmask. */ \ + if (!((_maps)[AT91_PM_ULP0] & (_map_bitmask))) {\ + _mode_to_use = AT91_PM_ULP0; \ + _mode_complementary = AT91_PM_STANDBY; \ + } else { \ + _mode_to_use = AT91_PM_STANDBY; \ + _mode_complementary = AT91_PM_STANDBY; \ + } \ + \ + if ((_mode_to_check) != _mode_to_use) \ + (_mode_to_replace) = _mode_to_use; \ + else \ + (_mode_to_replace) = _mode_complementary;\ + } \ + } while (0) + +/* + * Replaces standby and suspend modes with default supported modes: + * ULP0 and STANDBY. + * @_maps: u32 array indexed by AT91 PM mode containing AT91_PM_IOMAP() + * flags + * @_map: controller specific name; standby and suspend mode need to be + * replaced in order to not depend on this controller + */ +#define AT91_PM_REPLACE_MODES(_maps, _map) \ + do { \ + AT91_PM_REPLACE_MODE((_maps), BIT(AT91_PM_IOMAP_##_map),\ + (soc_pm.data.standby_mode), \ + (soc_pm.data.suspend_mode)); \ + AT91_PM_REPLACE_MODE((_maps), BIT(AT91_PM_IOMAP_##_map),\ + (soc_pm.data.suspend_mode), \ + (soc_pm.data.standby_mode)); \ + } while (0) + +static int __init at91_pm_get_eth_clks(struct device_node *np, + struct clk_bulk_data *clks) +{ + clks[AT91_PM_ETH_PCLK].clk = of_clk_get_by_name(np, "pclk"); + if (IS_ERR(clks[AT91_PM_ETH_PCLK].clk)) + return PTR_ERR(clks[AT91_PM_ETH_PCLK].clk); + + clks[AT91_PM_ETH_HCLK].clk = of_clk_get_by_name(np, "hclk"); + if (IS_ERR(clks[AT91_PM_ETH_HCLK].clk)) + return PTR_ERR(clks[AT91_PM_ETH_HCLK].clk); + + return 0; +} + +static int __init at91_pm_eth_clks_empty(struct clk_bulk_data *clks) +{ + return IS_ERR(clks[AT91_PM_ETH_PCLK].clk) || + IS_ERR(clks[AT91_PM_ETH_HCLK].clk); +} + +static void __init at91_pm_modes_init(const u32 *maps, int len) +{ + struct at91_pm_quirk_eth *gmac = &soc_pm.quirks.eth[AT91_PM_G_ETH]; + struct at91_pm_quirk_eth *emac = &soc_pm.quirks.eth[AT91_PM_E_ETH]; + struct device_node *np; + int ret; + + ret = at91_pm_backup_init(); + if (ret) { + if (soc_pm.data.standby_mode == AT91_PM_BACKUP) + soc_pm.data.standby_mode = AT91_PM_ULP0; + if (soc_pm.data.suspend_mode == AT91_PM_BACKUP) + soc_pm.data.suspend_mode = AT91_PM_ULP0; + } + + if (maps[soc_pm.data.standby_mode] & AT91_PM_IOMAP(SHDWC) || + maps[soc_pm.data.suspend_mode] & AT91_PM_IOMAP(SHDWC)) { + np = of_find_matching_node(NULL, atmel_shdwc_ids); + if (!np) { + pr_warn("%s: failed to find shdwc!\n", __func__); + AT91_PM_REPLACE_MODES(maps, SHDWC); + } else { + soc_pm.data.shdwc = of_iomap(np, 0); + of_node_put(np); + } + } + + if (maps[soc_pm.data.standby_mode] & AT91_PM_IOMAP(SFRBU) || + maps[soc_pm.data.suspend_mode] & AT91_PM_IOMAP(SFRBU)) { + np = of_find_compatible_node(NULL, NULL, "atmel,sama5d2-sfrbu"); + if (!np) { + pr_warn("%s: failed to find sfrbu!\n", __func__); + AT91_PM_REPLACE_MODES(maps, SFRBU); + } else { + soc_pm.data.sfrbu = of_iomap(np, 0); + of_node_put(np); + } + } + + if ((at91_is_pm_mode_active(AT91_PM_ULP1) || + at91_is_pm_mode_active(AT91_PM_ULP0) || + at91_is_pm_mode_active(AT91_PM_ULP0_FAST)) && + (maps[soc_pm.data.standby_mode] & AT91_PM_IOMAP(ETHC) || + maps[soc_pm.data.suspend_mode] & AT91_PM_IOMAP(ETHC))) { + np = of_find_matching_node(NULL, gmac_ids); + if (!np) { + np = of_find_matching_node(NULL, emac_ids); + if (np) + goto get_emac_clks; + AT91_PM_REPLACE_MODES(maps, ETHC); + goto unmap_unused_nodes; + } else { + gmac->np = np; + at91_pm_get_eth_clks(np, gmac->clks); + } + + np = of_find_matching_node(NULL, emac_ids); + if (!np) { + if (at91_pm_eth_clks_empty(gmac->clks)) + AT91_PM_REPLACE_MODES(maps, ETHC); + } else { +get_emac_clks: + emac->np = np; + ret = at91_pm_get_eth_clks(np, emac->clks); + if (ret && at91_pm_eth_clks_empty(gmac->clks)) { + of_node_put(gmac->np); + of_node_put(emac->np); + gmac->np = NULL; + emac->np = NULL; + } + } + } + +unmap_unused_nodes: + /* Unmap all unnecessary. */ + if (soc_pm.data.shdwc && + !(maps[soc_pm.data.standby_mode] & AT91_PM_IOMAP(SHDWC) || + maps[soc_pm.data.suspend_mode] & AT91_PM_IOMAP(SHDWC))) { + iounmap(soc_pm.data.shdwc); + soc_pm.data.shdwc = NULL; + } + + if (soc_pm.data.sfrbu && + !(maps[soc_pm.data.standby_mode] & AT91_PM_IOMAP(SFRBU) || + maps[soc_pm.data.suspend_mode] & AT91_PM_IOMAP(SFRBU))) { + iounmap(soc_pm.data.sfrbu); + soc_pm.data.sfrbu = NULL; + } + + return; +} + +struct pmc_info { + unsigned long uhp_udp_mask; + unsigned long mckr; + unsigned long version; +}; + +static const struct pmc_info pmc_infos[] __initconst = { + { + .uhp_udp_mask = AT91RM9200_PMC_UHP | AT91RM9200_PMC_UDP, + .mckr = 0x30, + .version = AT91_PMC_V1, + }, + + { + .uhp_udp_mask = AT91SAM926x_PMC_UHP | AT91SAM926x_PMC_UDP, + .mckr = 0x30, + .version = AT91_PMC_V1, + }, + { + .uhp_udp_mask = AT91SAM926x_PMC_UHP, + .mckr = 0x30, + .version = AT91_PMC_V1, + }, + { .uhp_udp_mask = 0, + .mckr = 0x30, + .version = AT91_PMC_V1, + }, + { + .uhp_udp_mask = AT91SAM926x_PMC_UHP | AT91SAM926x_PMC_UDP, + .mckr = 0x28, + .version = AT91_PMC_V2, + }, + { + .mckr = 0x28, + .version = AT91_PMC_V2, + }, + +}; + +static const struct of_device_id atmel_pmc_ids[] __initconst = { + { .compatible = "atmel,at91rm9200-pmc", .data = &pmc_infos[0] }, + { .compatible = "atmel,at91sam9260-pmc", .data = &pmc_infos[1] }, + { .compatible = "atmel,at91sam9261-pmc", .data = &pmc_infos[1] }, + { .compatible = "atmel,at91sam9263-pmc", .data = &pmc_infos[1] }, + { .compatible = "atmel,at91sam9g45-pmc", .data = &pmc_infos[2] }, + { .compatible = "atmel,at91sam9n12-pmc", .data = &pmc_infos[1] }, + { .compatible = "atmel,at91sam9rl-pmc", .data = &pmc_infos[3] }, + { .compatible = "atmel,at91sam9x5-pmc", .data = &pmc_infos[1] }, + { .compatible = "atmel,sama5d3-pmc", .data = &pmc_infos[1] }, + { .compatible = "atmel,sama5d4-pmc", .data = &pmc_infos[1] }, + { .compatible = "atmel,sama5d2-pmc", .data = &pmc_infos[1] }, + { .compatible = "microchip,sam9x60-pmc", .data = &pmc_infos[4] }, + { .compatible = "microchip,sama7g5-pmc", .data = &pmc_infos[5] }, + { /* sentinel */ }, +}; + +static void __init at91_pm_modes_validate(const int *modes, int len) +{ + u8 i, standby = 0, suspend = 0; + int mode; + + for (i = 0; i < len; i++) { + if (standby && suspend) + break; + + if (modes[i] == soc_pm.data.standby_mode && !standby) { + standby = 1; + continue; + } + + if (modes[i] == soc_pm.data.suspend_mode && !suspend) { + suspend = 1; + continue; + } + } + + if (!standby) { + if (soc_pm.data.suspend_mode == AT91_PM_STANDBY) + mode = AT91_PM_ULP0; + else + mode = AT91_PM_STANDBY; + + pr_warn("AT91: PM: %s mode not supported! Using %s.\n", + pm_modes[soc_pm.data.standby_mode].pattern, + pm_modes[mode].pattern); + soc_pm.data.standby_mode = mode; + } + + if (!suspend) { + if (soc_pm.data.standby_mode == AT91_PM_ULP0) + mode = AT91_PM_STANDBY; + else + mode = AT91_PM_ULP0; + + pr_warn("AT91: PM: %s mode not supported! Using %s.\n", + pm_modes[soc_pm.data.suspend_mode].pattern, + pm_modes[mode].pattern); + soc_pm.data.suspend_mode = mode; + } +} + +static void __init at91_pm_init(void (*pm_idle)(void)) +{ + struct device_node *pmc_np; + const struct of_device_id *of_id; + const struct pmc_info *pmc; + + if (at91_cpuidle_device.dev.platform_data) + platform_device_register(&at91_cpuidle_device); + + pmc_np = of_find_matching_node_and_match(NULL, atmel_pmc_ids, &of_id); + soc_pm.data.pmc = of_iomap(pmc_np, 0); + of_node_put(pmc_np); + if (!soc_pm.data.pmc) { + pr_err("AT91: PM not supported, PMC not found\n"); + return; + } + + pmc = of_id->data; + soc_pm.data.uhp_udp_mask = pmc->uhp_udp_mask; + soc_pm.data.pmc_mckr_offset = pmc->mckr; + soc_pm.data.pmc_version = pmc->version; + + if (pm_idle) + arm_pm_idle = pm_idle; + + at91_pm_sram_init(); + + if (at91_suspend_sram_fn) { + suspend_set_ops(&at91_pm_ops); + pr_info("AT91: PM: standby: %s, suspend: %s\n", + pm_modes[soc_pm.data.standby_mode].pattern, + pm_modes[soc_pm.data.suspend_mode].pattern); + } else { + pr_info("AT91: PM not supported, due to no SRAM allocated\n"); + } +} + +void __init at91rm9200_pm_init(void) +{ + int ret; + + if (!IS_ENABLED(CONFIG_SOC_AT91RM9200)) + return; + + /* + * Force STANDBY and ULP0 mode to avoid calling + * at91_pm_modes_validate() which may increase booting time. + * Platform supports anyway only STANDBY and ULP0 modes. + */ + soc_pm.data.standby_mode = AT91_PM_STANDBY; + soc_pm.data.suspend_mode = AT91_PM_ULP0; + + ret = at91_dt_ramc(false); + if (ret) + return; + + /* + * AT91RM9200 SDRAM low-power mode cannot be used with self-refresh. + */ + at91_ramc_write(0, AT91_MC_SDRAMC_LPR, 0); + + at91_pm_init(at91rm9200_idle); +} + +void __init sam9x60_pm_init(void) +{ + static const int modes[] __initconst = { + AT91_PM_STANDBY, AT91_PM_ULP0, AT91_PM_ULP0_FAST, AT91_PM_ULP1, + }; + static const int iomaps[] __initconst = { + [AT91_PM_ULP1] = AT91_PM_IOMAP(SHDWC), + }; + int ret; + + if (!IS_ENABLED(CONFIG_SOC_SAM9X60)) + return; + + at91_pm_modes_validate(modes, ARRAY_SIZE(modes)); + at91_pm_modes_init(iomaps, ARRAY_SIZE(iomaps)); + ret = at91_dt_ramc(false); + if (ret) + return; + + at91_pm_init(NULL); + + soc_pm.ws_ids = sam9x60_ws_ids; + soc_pm.config_pmc_ws = at91_sam9x60_config_pmc_ws; +} + +void __init at91sam9_pm_init(void) +{ + int ret; + + if (!IS_ENABLED(CONFIG_SOC_AT91SAM9)) + return; + + /* + * Force STANDBY and ULP0 mode to avoid calling + * at91_pm_modes_validate() which may increase booting time. + * Platform supports anyway only STANDBY and ULP0 modes. + */ + soc_pm.data.standby_mode = AT91_PM_STANDBY; + soc_pm.data.suspend_mode = AT91_PM_ULP0; + + ret = at91_dt_ramc(false); + if (ret) + return; + + at91_pm_init(at91sam9_idle); +} + +void __init sama5_pm_init(void) +{ + static const int modes[] __initconst = { + AT91_PM_STANDBY, AT91_PM_ULP0, AT91_PM_ULP0_FAST, + }; + static const u32 iomaps[] __initconst = { + [AT91_PM_ULP0] = AT91_PM_IOMAP(ETHC), + [AT91_PM_ULP0_FAST] = AT91_PM_IOMAP(ETHC), + }; + int ret; + + if (!IS_ENABLED(CONFIG_SOC_SAMA5)) + return; + + at91_pm_modes_validate(modes, ARRAY_SIZE(modes)); + at91_pm_modes_init(iomaps, ARRAY_SIZE(iomaps)); + ret = at91_dt_ramc(false); + if (ret) + return; + + at91_pm_init(NULL); + + /* Quirks applies to ULP0, ULP0 fast and ULP1 modes. */ + soc_pm.quirks.eth[AT91_PM_G_ETH].modes = BIT(AT91_PM_ULP0) | + BIT(AT91_PM_ULP0_FAST) | + BIT(AT91_PM_ULP1); + /* Do not suspend in ULP0, ULP0 fast if GETH is the only wakeup source. */ + soc_pm.quirks.eth[AT91_PM_G_ETH].dns_modes = BIT(AT91_PM_ULP0) | + BIT(AT91_PM_ULP0_FAST); +} + +void __init sama5d2_pm_init(void) +{ + static const int modes[] __initconst = { + AT91_PM_STANDBY, AT91_PM_ULP0, AT91_PM_ULP0_FAST, AT91_PM_ULP1, + AT91_PM_BACKUP, + }; + static const u32 iomaps[] __initconst = { + [AT91_PM_ULP0] = AT91_PM_IOMAP(ETHC), + [AT91_PM_ULP0_FAST] = AT91_PM_IOMAP(ETHC), + [AT91_PM_ULP1] = AT91_PM_IOMAP(SHDWC) | + AT91_PM_IOMAP(ETHC), + [AT91_PM_BACKUP] = AT91_PM_IOMAP(SHDWC) | + AT91_PM_IOMAP(SFRBU), + }; + int ret; + + if (!IS_ENABLED(CONFIG_SOC_SAMA5D2)) + return; + + if (IS_ENABLED(CONFIG_ATMEL_SECURE_PM)) { + pr_warn("AT91: Secure PM: ignoring standby mode\n"); + at91_pm_secure_init(); + return; + } + + at91_pm_modes_validate(modes, ARRAY_SIZE(modes)); + at91_pm_modes_init(iomaps, ARRAY_SIZE(iomaps)); + ret = at91_dt_ramc(false); + if (ret) + return; + + at91_pm_init(NULL); + + soc_pm.ws_ids = sama5d2_ws_ids; + soc_pm.config_shdwc_ws = at91_sama5d2_config_shdwc_ws; + soc_pm.config_pmc_ws = at91_sama5d2_config_pmc_ws; + + soc_pm.sfrbu_regs.pswbu.key = (0x4BD20C << 8); + soc_pm.sfrbu_regs.pswbu.ctrl = BIT(0); + soc_pm.sfrbu_regs.pswbu.softsw = BIT(1); + soc_pm.sfrbu_regs.pswbu.state = BIT(3); + + /* Quirk applies to ULP0, ULP0 fast and ULP1 modes. */ + soc_pm.quirks.eth[AT91_PM_G_ETH].modes = BIT(AT91_PM_ULP0) | + BIT(AT91_PM_ULP0_FAST) | + BIT(AT91_PM_ULP1); + /* + * Do not suspend in ULP0, ULP0 fast if GETH is the only wakeup + * source. + */ + soc_pm.quirks.eth[AT91_PM_G_ETH].dns_modes = BIT(AT91_PM_ULP0) | + BIT(AT91_PM_ULP0_FAST); +} + +void __init sama7_pm_init(void) +{ + static const int modes[] __initconst = { + AT91_PM_STANDBY, AT91_PM_ULP0, AT91_PM_ULP1, AT91_PM_BACKUP, + }; + static const u32 iomaps[] __initconst = { + [AT91_PM_ULP0] = AT91_PM_IOMAP(SFRBU), + [AT91_PM_ULP1] = AT91_PM_IOMAP(SFRBU) | + AT91_PM_IOMAP(SHDWC) | + AT91_PM_IOMAP(ETHC), + [AT91_PM_BACKUP] = AT91_PM_IOMAP(SFRBU) | + AT91_PM_IOMAP(SHDWC), + }; + int ret; + + if (!IS_ENABLED(CONFIG_SOC_SAMA7)) + return; + + at91_pm_modes_validate(modes, ARRAY_SIZE(modes)); + + ret = at91_dt_ramc(true); + if (ret) + return; + + at91_pm_modes_init(iomaps, ARRAY_SIZE(iomaps)); + at91_pm_init(NULL); + + soc_pm.ws_ids = sama7g5_ws_ids; + soc_pm.config_pmc_ws = at91_sam9x60_config_pmc_ws; + + soc_pm.sfrbu_regs.pswbu.key = (0x4BD20C << 8); + soc_pm.sfrbu_regs.pswbu.ctrl = BIT(0); + soc_pm.sfrbu_regs.pswbu.softsw = BIT(1); + soc_pm.sfrbu_regs.pswbu.state = BIT(2); + + /* Quirks applies to ULP1 for both Ethernet interfaces. */ + soc_pm.quirks.eth[AT91_PM_E_ETH].modes = BIT(AT91_PM_ULP1); + soc_pm.quirks.eth[AT91_PM_G_ETH].modes = BIT(AT91_PM_ULP1); +} + +static int __init at91_pm_modes_select(char *str) +{ + char *s; + substring_t args[MAX_OPT_ARGS]; + int standby, suspend; + + if (!str) + return 0; + + s = strsep(&str, ","); + standby = match_token(s, pm_modes, args); + if (standby < 0) + return 0; + + suspend = match_token(str, pm_modes, args); + if (suspend < 0) + return 0; + + soc_pm.data.standby_mode = standby; + soc_pm.data.suspend_mode = suspend; + + return 0; +} +early_param("atmel.pm_modes", at91_pm_modes_select); diff --git a/arch/arm/mach-at91/pm.h b/arch/arm/mach-at91/pm.h new file mode 100644 index 000000000..53bdc9000 --- /dev/null +++ b/arch/arm/mach-at91/pm.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * AT91 Power Management + * + * Copyright (C) 2005 David Brownell + */ +#ifndef __ARCH_ARM_MACH_AT91_PM +#define __ARCH_ARM_MACH_AT91_PM + +#include <asm/proc-fns.h> + +#include <linux/mfd/syscon/atmel-mc.h> +#include <soc/at91/at91sam9_ddrsdr.h> +#include <soc/at91/at91sam9_sdramc.h> +#include <soc/at91/sama7-ddr.h> +#include <soc/at91/sama7-sfrbu.h> + +#define AT91_MEMCTRL_MC 0 +#define AT91_MEMCTRL_SDRAMC 1 +#define AT91_MEMCTRL_DDRSDR 2 + +#define AT91_PM_STANDBY 0x00 +#define AT91_PM_ULP0 0x01 +#define AT91_PM_ULP0_FAST 0x02 +#define AT91_PM_ULP1 0x03 +#define AT91_PM_BACKUP 0x04 + +#ifndef __ASSEMBLY__ +struct at91_pm_data { + void __iomem *pmc; + void __iomem *ramc[2]; + void __iomem *ramc_phy; + unsigned long uhp_udp_mask; + unsigned int memctrl; + unsigned int mode; + void __iomem *shdwc; + void __iomem *sfrbu; + unsigned int standby_mode; + unsigned int suspend_mode; + unsigned int pmc_mckr_offset; + unsigned int pmc_version; +}; +#endif + +#endif diff --git a/arch/arm/mach-at91/pm_data-offsets.c b/arch/arm/mach-at91/pm_data-offsets.c new file mode 100644 index 000000000..40bd4e8fe --- /dev/null +++ b/arch/arm/mach-at91/pm_data-offsets.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/stddef.h> +#include <linux/kbuild.h> +#include "pm.h" + +int main(void) +{ + DEFINE(PM_DATA_PMC, offsetof(struct at91_pm_data, pmc)); + DEFINE(PM_DATA_RAMC0, offsetof(struct at91_pm_data, ramc[0])); + DEFINE(PM_DATA_RAMC1, offsetof(struct at91_pm_data, ramc[1])); + DEFINE(PM_DATA_RAMC_PHY, offsetof(struct at91_pm_data, + ramc_phy)); + DEFINE(PM_DATA_MEMCTRL, offsetof(struct at91_pm_data, memctrl)); + DEFINE(PM_DATA_MODE, offsetof(struct at91_pm_data, mode)); + DEFINE(PM_DATA_SHDWC, offsetof(struct at91_pm_data, shdwc)); + DEFINE(PM_DATA_SFRBU, offsetof(struct at91_pm_data, sfrbu)); + DEFINE(PM_DATA_PMC_MCKR_OFFSET, offsetof(struct at91_pm_data, + pmc_mckr_offset)); + DEFINE(PM_DATA_PMC_VERSION, offsetof(struct at91_pm_data, + pmc_version)); + + return 0; +} diff --git a/arch/arm/mach-at91/pm_suspend.S b/arch/arm/mach-at91/pm_suspend.S new file mode 100644 index 000000000..e4904faf1 --- /dev/null +++ b/arch/arm/mach-at91/pm_suspend.S @@ -0,0 +1,1157 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * arch/arm/mach-at91/pm_slow_clock.S + * + * Copyright (C) 2006 Savin Zlobec + * + * AT91SAM9 support: + * Copyright (C) 2007 Anti Sullin <anti.sullin@artecdesign.ee> + */ +#include <linux/linkage.h> +#include <linux/clk/at91_pmc.h> +#include "pm.h" +#include "pm_data-offsets.h" + +#define SRAMC_SELF_FRESH_ACTIVE 0x01 +#define SRAMC_SELF_FRESH_EXIT 0x00 + +pmc .req r0 +tmp1 .req r4 +tmp2 .req r5 +tmp3 .req r6 + +/* + * Wait until master clock is ready (after switching master clock source) + * + * @r_mckid: register holding master clock identifier + * + * Side effects: overwrites r7, r8 + */ + .macro wait_mckrdy r_mckid +#ifdef CONFIG_SOC_SAMA7 + cmp \r_mckid, #0 + beq 1f + mov r7, #AT91_PMC_MCKXRDY + b 2f +#endif +1: mov r7, #AT91_PMC_MCKRDY +2: ldr r8, [pmc, #AT91_PMC_SR] + and r8, r7 + cmp r8, r7 + bne 2b + .endm + +/* + * Wait until master oscillator has stabilized. + * + * Side effects: overwrites r7 + */ + .macro wait_moscrdy +1: ldr r7, [pmc, #AT91_PMC_SR] + tst r7, #AT91_PMC_MOSCS + beq 1b + .endm + +/* + * Wait for main oscillator selection is done + * + * Side effects: overwrites r7 + */ + .macro wait_moscsels +1: ldr r7, [pmc, #AT91_PMC_SR] + tst r7, #AT91_PMC_MOSCSELS + beq 1b + .endm + +/* + * Put the processor to enter the idle state + * + * Side effects: overwrites r7 + */ + .macro at91_cpu_idle + +#if defined(CONFIG_CPU_V7) + mov r7, #AT91_PMC_PCK + str r7, [pmc, #AT91_PMC_SCDR] + + dsb + + wfi @ Wait For Interrupt +#else + mcr p15, 0, tmp1, c7, c0, 4 +#endif + + .endm + +/** + * Set state for 2.5V low power regulator + * @ena: 0 - disable regulator + * 1 - enable regulator + * + * Side effects: overwrites r7, r8, r9, r10 + */ + .macro at91_2_5V_reg_set_low_power ena +#ifdef CONFIG_SOC_SAMA7 + ldr r7, .sfrbu + mov r8, #\ena + ldr r9, [r7, #AT91_SFRBU_25LDOCR] + orr r9, r9, #AT91_SFRBU_25LDOCR_LP + cmp r8, #1 + beq lp_done_\ena + bic r9, r9, #AT91_SFRBU_25LDOCR_LP +lp_done_\ena: + ldr r10, =AT91_SFRBU_25LDOCR_LDOANAKEY + orr r9, r9, r10 + str r9, [r7, #AT91_SFRBU_25LDOCR] +#endif + .endm + + .macro at91_backup_set_lpm reg +#ifdef CONFIG_SOC_SAMA7 + orr \reg, \reg, #0x200000 +#endif + .endm + + .text + + .arm + +#ifdef CONFIG_SOC_SAMA7 +/** + * Enable self-refresh + * + * Side effects: overwrites r2, r3, tmp1, tmp2, tmp3, r7 + */ +.macro at91_sramc_self_refresh_ena + ldr r2, .sramc_base + ldr r3, .sramc_phy_base + ldr r7, .pm_mode + + dsb + + /* Disable all AXI ports. */ + ldr tmp1, [r2, #UDDRC_PCTRL_0] + bic tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_0] + + ldr tmp1, [r2, #UDDRC_PCTRL_1] + bic tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_1] + + ldr tmp1, [r2, #UDDRC_PCTRL_2] + bic tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_2] + + ldr tmp1, [r2, #UDDRC_PCTRL_3] + bic tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_3] + + ldr tmp1, [r2, #UDDRC_PCTRL_4] + bic tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_4] + +sr_ena_1: + /* Wait for all ports to disable. */ + ldr tmp1, [r2, #UDDRC_PSTAT] + ldr tmp2, =UDDRC_PSTAT_ALL_PORTS + tst tmp1, tmp2 + bne sr_ena_1 + + /* Switch to self-refresh. */ + ldr tmp1, [r2, #UDDRC_PWRCTL] + orr tmp1, tmp1, #UDDRC_PWRCTL_SELFREF_SW + str tmp1, [r2, #UDDRC_PWRCTL] + +sr_ena_2: + /* Wait for self-refresh enter. */ + ldr tmp1, [r2, #UDDRC_STAT] + bic tmp1, tmp1, #~UDDRC_STAT_SELFREF_TYPE_MSK + cmp tmp1, #UDDRC_STAT_SELFREF_TYPE_SW + bne sr_ena_2 + + /* Disable DX DLLs for non-backup modes. */ + cmp r7, #AT91_PM_BACKUP + beq sr_ena_3 + + /* Do not soft reset the AC DLL. */ + ldr tmp1, [r3, DDR3PHY_ACDLLCR] + bic tmp1, tmp1, DDR3PHY_ACDLLCR_DLLSRST + str tmp1, [r3, DDR3PHY_ACDLLCR] + + /* Disable DX DLLs. */ + ldr tmp1, [r3, #DDR3PHY_DX0DLLCR] + orr tmp1, tmp1, #DDR3PHY_DXDLLCR_DLLDIS + str tmp1, [r3, #DDR3PHY_DX0DLLCR] + + ldr tmp1, [r3, #DDR3PHY_DX1DLLCR] + orr tmp1, tmp1, #DDR3PHY_DXDLLCR_DLLDIS + str tmp1, [r3, #DDR3PHY_DX1DLLCR] + +sr_ena_3: + /* Power down DDR PHY data receivers. */ + ldr tmp1, [r3, #DDR3PHY_DXCCR] + orr tmp1, tmp1, #DDR3PHY_DXCCR_DXPDR + str tmp1, [r3, #DDR3PHY_DXCCR] + + /* Power down ADDR/CMD IO. */ + ldr tmp1, [r3, #DDR3PHY_ACIOCR] + orr tmp1, tmp1, #DDR3PHY_ACIORC_ACPDD + orr tmp1, tmp1, #DDR3PHY_ACIOCR_CKPDD_CK0 + orr tmp1, tmp1, #DDR3PHY_ACIOCR_CSPDD_CS0 + str tmp1, [r3, #DDR3PHY_ACIOCR] + + /* Power down ODT. */ + ldr tmp1, [r3, #DDR3PHY_DSGCR] + orr tmp1, tmp1, #DDR3PHY_DSGCR_ODTPDD_ODT0 + str tmp1, [r3, #DDR3PHY_DSGCR] +.endm + +/** + * Disable self-refresh + * + * Side effects: overwrites r2, r3, tmp1, tmp2, tmp3 + */ +.macro at91_sramc_self_refresh_dis + ldr r2, .sramc_base + ldr r3, .sramc_phy_base + + /* Power up DDR PHY data receivers. */ + ldr tmp1, [r3, #DDR3PHY_DXCCR] + bic tmp1, tmp1, #DDR3PHY_DXCCR_DXPDR + str tmp1, [r3, #DDR3PHY_DXCCR] + + /* Power up the output of CK and CS pins. */ + ldr tmp1, [r3, #DDR3PHY_ACIOCR] + bic tmp1, tmp1, #DDR3PHY_ACIORC_ACPDD + bic tmp1, tmp1, #DDR3PHY_ACIOCR_CKPDD_CK0 + bic tmp1, tmp1, #DDR3PHY_ACIOCR_CSPDD_CS0 + str tmp1, [r3, #DDR3PHY_ACIOCR] + + /* Power up ODT. */ + ldr tmp1, [r3, #DDR3PHY_DSGCR] + bic tmp1, tmp1, #DDR3PHY_DSGCR_ODTPDD_ODT0 + str tmp1, [r3, #DDR3PHY_DSGCR] + + /* Enable DX DLLs. */ + ldr tmp1, [r3, #DDR3PHY_DX0DLLCR] + bic tmp1, tmp1, #DDR3PHY_DXDLLCR_DLLDIS + str tmp1, [r3, #DDR3PHY_DX0DLLCR] + + ldr tmp1, [r3, #DDR3PHY_DX1DLLCR] + bic tmp1, tmp1, #DDR3PHY_DXDLLCR_DLLDIS + str tmp1, [r3, #DDR3PHY_DX1DLLCR] + + /* Enable quasi-dynamic programming. */ + mov tmp1, #0 + str tmp1, [r2, #UDDRC_SWCTRL] + + /* De-assert SDRAM initialization. */ + ldr tmp1, [r2, #UDDRC_DFIMISC] + bic tmp1, tmp1, #UDDRC_DFIMISC_DFI_INIT_COMPLETE_EN + str tmp1, [r2, #UDDRC_DFIMISC] + + /* Quasi-dynamic programming done. */ + mov tmp1, #UDDRC_SWCTRL_SW_DONE + str tmp1, [r2, #UDDRC_SWCTRL] + +sr_dis_1: + ldr tmp1, [r2, #UDDRC_SWSTAT] + tst tmp1, #UDDRC_SWSTAT_SW_DONE_ACK + beq sr_dis_1 + + /* DLL soft-reset + DLL lock wait + ITM reset */ + mov tmp1, #(DDR3PHY_PIR_INIT | DDR3PHY_PIR_DLLSRST | \ + DDR3PHY_PIR_DLLLOCK | DDR3PHY_PIR_ITMSRST) + str tmp1, [r3, #DDR3PHY_PIR] + +sr_dis_4: + /* Wait for it. */ + ldr tmp1, [r3, #DDR3PHY_PGSR] + tst tmp1, #DDR3PHY_PGSR_IDONE + beq sr_dis_4 + + /* Enable quasi-dynamic programming. */ + mov tmp1, #0 + str tmp1, [r2, #UDDRC_SWCTRL] + + /* Assert PHY init complete enable signal. */ + ldr tmp1, [r2, #UDDRC_DFIMISC] + orr tmp1, tmp1, #UDDRC_DFIMISC_DFI_INIT_COMPLETE_EN + str tmp1, [r2, #UDDRC_DFIMISC] + + /* Programming is done. Set sw_done. */ + mov tmp1, #UDDRC_SWCTRL_SW_DONE + str tmp1, [r2, #UDDRC_SWCTRL] + +sr_dis_5: + /* Wait for it. */ + ldr tmp1, [r2, #UDDRC_SWSTAT] + tst tmp1, #UDDRC_SWSTAT_SW_DONE_ACK + beq sr_dis_5 + + /* Trigger self-refresh exit. */ + ldr tmp1, [r2, #UDDRC_PWRCTL] + bic tmp1, tmp1, #UDDRC_PWRCTL_SELFREF_SW + str tmp1, [r2, #UDDRC_PWRCTL] + +sr_dis_6: + /* Wait for self-refresh exit done. */ + ldr tmp1, [r2, #UDDRC_STAT] + bic tmp1, tmp1, #~UDDRC_STAT_OPMODE_MSK + cmp tmp1, #UDDRC_STAT_OPMODE_NORMAL + bne sr_dis_6 + + /* Enable all AXI ports. */ + ldr tmp1, [r2, #UDDRC_PCTRL_0] + orr tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_0] + + ldr tmp1, [r2, #UDDRC_PCTRL_1] + orr tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_1] + + ldr tmp1, [r2, #UDDRC_PCTRL_2] + orr tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_2] + + ldr tmp1, [r2, #UDDRC_PCTRL_3] + orr tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_3] + + ldr tmp1, [r2, #UDDRC_PCTRL_4] + orr tmp1, tmp1, #0x1 + str tmp1, [r2, #UDDRC_PCTRL_4] + + dsb +.endm +#else +/** + * Enable self-refresh + * + * register usage: + * @r1: memory type + * @r2: base address of the sram controller + * @r3: temporary + */ +.macro at91_sramc_self_refresh_ena + ldr r1, .memtype + ldr r2, .sramc_base + + cmp r1, #AT91_MEMCTRL_MC + bne sr_ena_ddrc_sf + + /* Active SDRAM self-refresh mode */ + mov r3, #1 + str r3, [r2, #AT91_MC_SDRAMC_SRR] + b sr_ena_exit + +sr_ena_ddrc_sf: + cmp r1, #AT91_MEMCTRL_DDRSDR + bne sr_ena_sdramc_sf + + /* + * DDR Memory controller + */ + + /* LPDDR1 --> force DDR2 mode during self-refresh */ + ldr r3, [r2, #AT91_DDRSDRC_MDR] + str r3, .saved_sam9_mdr + bic r3, r3, #~AT91_DDRSDRC_MD + cmp r3, #AT91_DDRSDRC_MD_LOW_POWER_DDR + ldreq r3, [r2, #AT91_DDRSDRC_MDR] + biceq r3, r3, #AT91_DDRSDRC_MD + orreq r3, r3, #AT91_DDRSDRC_MD_DDR2 + streq r3, [r2, #AT91_DDRSDRC_MDR] + + /* Active DDRC self-refresh mode */ + ldr r3, [r2, #AT91_DDRSDRC_LPR] + str r3, .saved_sam9_lpr + bic r3, r3, #AT91_DDRSDRC_LPCB + orr r3, r3, #AT91_DDRSDRC_LPCB_SELF_REFRESH + str r3, [r2, #AT91_DDRSDRC_LPR] + + /* If using the 2nd ddr controller */ + ldr r2, .sramc1_base + cmp r2, #0 + beq sr_ena_no_2nd_ddrc + + ldr r3, [r2, #AT91_DDRSDRC_MDR] + str r3, .saved_sam9_mdr1 + bic r3, r3, #~AT91_DDRSDRC_MD + cmp r3, #AT91_DDRSDRC_MD_LOW_POWER_DDR + ldreq r3, [r2, #AT91_DDRSDRC_MDR] + biceq r3, r3, #AT91_DDRSDRC_MD + orreq r3, r3, #AT91_DDRSDRC_MD_DDR2 + streq r3, [r2, #AT91_DDRSDRC_MDR] + + /* Active DDRC self-refresh mode */ + ldr r3, [r2, #AT91_DDRSDRC_LPR] + str r3, .saved_sam9_lpr1 + bic r3, r3, #AT91_DDRSDRC_LPCB + orr r3, r3, #AT91_DDRSDRC_LPCB_SELF_REFRESH + str r3, [r2, #AT91_DDRSDRC_LPR] + +sr_ena_no_2nd_ddrc: + b sr_ena_exit + + /* + * SDRAMC Memory controller + */ +sr_ena_sdramc_sf: + /* Active SDRAMC self-refresh mode */ + ldr r3, [r2, #AT91_SDRAMC_LPR] + str r3, .saved_sam9_lpr + bic r3, r3, #AT91_SDRAMC_LPCB + orr r3, r3, #AT91_SDRAMC_LPCB_SELF_REFRESH + str r3, [r2, #AT91_SDRAMC_LPR] + + ldr r3, .saved_sam9_lpr + str r3, [r2, #AT91_SDRAMC_LPR] + +sr_ena_exit: +.endm + +/** + * Disable self-refresh + * + * register usage: + * @r1: memory type + * @r2: base address of the sram controller + * @r3: temporary + */ +.macro at91_sramc_self_refresh_dis + ldr r1, .memtype + ldr r2, .sramc_base + + cmp r1, #AT91_MEMCTRL_MC + bne sr_dis_ddrc_exit_sf + + /* + * at91rm9200 Memory controller + */ + + /* + * For exiting the self-refresh mode, do nothing, + * automatically exit the self-refresh mode. + */ + b sr_dis_exit + +sr_dis_ddrc_exit_sf: + cmp r1, #AT91_MEMCTRL_DDRSDR + bne sdramc_exit_sf + + /* DDR Memory controller */ + + /* Restore MDR in case of LPDDR1 */ + ldr r3, .saved_sam9_mdr + str r3, [r2, #AT91_DDRSDRC_MDR] + /* Restore LPR on AT91 with DDRAM */ + ldr r3, .saved_sam9_lpr + str r3, [r2, #AT91_DDRSDRC_LPR] + + /* If using the 2nd ddr controller */ + ldr r2, .sramc1_base + cmp r2, #0 + ldrne r3, .saved_sam9_mdr1 + strne r3, [r2, #AT91_DDRSDRC_MDR] + ldrne r3, .saved_sam9_lpr1 + strne r3, [r2, #AT91_DDRSDRC_LPR] + + b sr_dis_exit + +sdramc_exit_sf: + /* SDRAMC Memory controller */ + ldr r3, .saved_sam9_lpr + str r3, [r2, #AT91_SDRAMC_LPR] + +sr_dis_exit: +.endm +#endif + +.macro at91_pm_ulp0_mode + ldr pmc, .pmc_base + ldr tmp2, .pm_mode + ldr tmp3, .mckr_offset + + /* Check if ULP0 fast variant has been requested. */ + cmp tmp2, #AT91_PM_ULP0_FAST + bne 0f + + /* Set highest prescaler for power saving */ + ldr tmp1, [pmc, tmp3] + bic tmp1, tmp1, #AT91_PMC_PRES + orr tmp1, tmp1, #AT91_PMC_PRES_64 + str tmp1, [pmc, tmp3] + + mov tmp3, #0 + wait_mckrdy tmp3 + b 1f + +0: + /* Turn off the crystal oscillator */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + bic tmp1, tmp1, #AT91_PMC_MOSCEN + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + /* Save RC oscillator state */ + ldr tmp1, [pmc, #AT91_PMC_SR] + str tmp1, .saved_osc_status + tst tmp1, #AT91_PMC_MOSCRCS + bne 1f + + /* Turn off RC oscillator */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + bic tmp1, tmp1, #AT91_PMC_MOSCRCEN + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + /* Wait main RC disabled done */ +2: ldr tmp1, [pmc, #AT91_PMC_SR] + tst tmp1, #AT91_PMC_MOSCRCS + bne 2b + + /* Wait for interrupt */ +1: at91_cpu_idle + + /* Check if ULP0 fast variant has been requested. */ + cmp tmp2, #AT91_PM_ULP0_FAST + bne 5f + + /* Set lowest prescaler for fast resume. */ + ldr tmp3, .mckr_offset + ldr tmp1, [pmc, tmp3] + bic tmp1, tmp1, #AT91_PMC_PRES + str tmp1, [pmc, tmp3] + + mov tmp3, #0 + wait_mckrdy tmp3 + b 6f + +5: /* Restore RC oscillator state */ + ldr tmp1, .saved_osc_status + tst tmp1, #AT91_PMC_MOSCRCS + beq 4f + + /* Turn on RC oscillator */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + orr tmp1, tmp1, #AT91_PMC_MOSCRCEN + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + /* Wait main RC stabilization */ +3: ldr tmp1, [pmc, #AT91_PMC_SR] + tst tmp1, #AT91_PMC_MOSCRCS + beq 3b + + /* Turn on the crystal oscillator */ +4: ldr tmp1, [pmc, #AT91_CKGR_MOR] + orr tmp1, tmp1, #AT91_PMC_MOSCEN + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + wait_moscrdy +6: +.endm + +/** + * Note: This procedure only applies on the platform which uses + * the external crystal oscillator as a main clock source. + */ +.macro at91_pm_ulp1_mode + ldr pmc, .pmc_base + ldr tmp2, .mckr_offset + mov tmp3, #0 + + /* Save RC oscillator state and check if it is enabled. */ + ldr tmp1, [pmc, #AT91_PMC_SR] + str tmp1, .saved_osc_status + tst tmp1, #AT91_PMC_MOSCRCS + bne 2f + + /* Enable RC oscillator */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + orr tmp1, tmp1, #AT91_PMC_MOSCRCEN + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + /* Wait main RC stabilization */ +1: ldr tmp1, [pmc, #AT91_PMC_SR] + tst tmp1, #AT91_PMC_MOSCRCS + beq 1b + + /* Switch the main clock source to 12-MHz RC oscillator */ +2: ldr tmp1, [pmc, #AT91_CKGR_MOR] + bic tmp1, tmp1, #AT91_PMC_MOSCSEL + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + wait_moscsels + + /* Disable the crystal oscillator */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + bic tmp1, tmp1, #AT91_PMC_MOSCEN + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + /* Switch the master clock source to main clock */ + ldr tmp1, [pmc, tmp2] + bic tmp1, tmp1, #AT91_PMC_CSS + orr tmp1, tmp1, #AT91_PMC_CSS_MAIN + str tmp1, [pmc, tmp2] + + wait_mckrdy tmp3 + + /* Enter the ULP1 mode by set WAITMODE bit in CKGR_MOR */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + orr tmp1, tmp1, #AT91_PMC_WAITMODE + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + /* Quirk for SAM9X60's PMC */ + nop + nop + + wait_mckrdy tmp3 + + /* Enable the crystal oscillator */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + orr tmp1, tmp1, #AT91_PMC_MOSCEN + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + wait_moscrdy + + /* Switch the master clock source to slow clock */ + ldr tmp1, [pmc, tmp2] + bic tmp1, tmp1, #AT91_PMC_CSS + str tmp1, [pmc, tmp2] + + wait_mckrdy tmp3 + + /* Switch main clock source to crystal oscillator */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + orr tmp1, tmp1, #AT91_PMC_MOSCSEL + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + wait_moscsels + + /* Switch the master clock source to main clock */ + ldr tmp1, [pmc, tmp2] + bic tmp1, tmp1, #AT91_PMC_CSS + orr tmp1, tmp1, #AT91_PMC_CSS_MAIN + str tmp1, [pmc, tmp2] + + wait_mckrdy tmp3 + + /* Restore RC oscillator state */ + ldr tmp1, .saved_osc_status + tst tmp1, #AT91_PMC_MOSCRCS + bne 3f + + /* Disable RC oscillator */ + ldr tmp1, [pmc, #AT91_CKGR_MOR] + bic tmp1, tmp1, #AT91_PMC_MOSCRCEN + bic tmp1, tmp1, #AT91_PMC_KEY_MASK + orr tmp1, tmp1, #AT91_PMC_KEY + str tmp1, [pmc, #AT91_CKGR_MOR] + + /* Wait RC oscillator disable done */ +4: ldr tmp1, [pmc, #AT91_PMC_SR] + tst tmp1, #AT91_PMC_MOSCRCS + bne 4b + +3: +.endm + +.macro at91_plla_disable + /* Save PLLA setting and disable it */ + ldr tmp1, .pmc_version + cmp tmp1, #AT91_PMC_V1 + beq 1f + +#ifdef CONFIG_HAVE_AT91_SAM9X60_PLL + /* Save PLLA settings. */ + ldr tmp2, [pmc, #AT91_PMC_PLL_UPDT] + bic tmp2, tmp2, #AT91_PMC_PLL_UPDT_ID + str tmp2, [pmc, #AT91_PMC_PLL_UPDT] + + /* save div. */ + mov tmp1, #0 + ldr tmp2, [pmc, #AT91_PMC_PLL_CTRL0] + bic tmp2, tmp2, #0xffffff00 + orr tmp1, tmp1, tmp2 + + /* save mul. */ + ldr tmp2, [pmc, #AT91_PMC_PLL_CTRL1] + bic tmp2, tmp2, #0xffffff + orr tmp1, tmp1, tmp2 + str tmp1, .saved_pllar + + /* step 2. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_UPDT] + bic tmp1, tmp1, #AT91_PMC_PLL_UPDT_UPDATE + bic tmp1, tmp1, #AT91_PMC_PLL_UPDT_ID + str tmp1, [pmc, #AT91_PMC_PLL_UPDT] + + /* step 3. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_CTRL0] + bic tmp1, tmp1, #AT91_PMC_PLL_CTRL0_ENPLLCK + orr tmp1, tmp1, #AT91_PMC_PLL_CTRL0_ENPLL + str tmp1, [pmc, #AT91_PMC_PLL_CTRL0] + + /* step 4. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_UPDT] + orr tmp1, tmp1, #AT91_PMC_PLL_UPDT_UPDATE + bic tmp1, tmp1, #AT91_PMC_PLL_UPDT_ID + str tmp1, [pmc, #AT91_PMC_PLL_UPDT] + + /* step 5. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_CTRL0] + bic tmp1, tmp1, #AT91_PMC_PLL_CTRL0_ENPLL + str tmp1, [pmc, #AT91_PMC_PLL_CTRL0] + + /* step 7. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_UPDT] + orr tmp1, tmp1, #AT91_PMC_PLL_UPDT_UPDATE + bic tmp1, tmp1, #AT91_PMC_PLL_UPDT_ID + str tmp1, [pmc, #AT91_PMC_PLL_UPDT] + + b 2f +#endif + +1: /* Save PLLA setting and disable it */ + ldr tmp1, [pmc, #AT91_CKGR_PLLAR] + str tmp1, .saved_pllar + + /* Disable PLLA. */ + mov tmp1, #AT91_PMC_PLLCOUNT + orr tmp1, tmp1, #(1 << 29) /* bit 29 always set */ + str tmp1, [pmc, #AT91_CKGR_PLLAR] +2: +.endm + +.macro at91_plla_enable + ldr tmp2, .saved_pllar + ldr tmp3, .pmc_version + cmp tmp3, #AT91_PMC_V1 + beq 4f + +#ifdef CONFIG_HAVE_AT91_SAM9X60_PLL + /* step 1. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_UPDT] + bic tmp1, tmp1, #AT91_PMC_PLL_UPDT_ID + bic tmp1, tmp1, #AT91_PMC_PLL_UPDT_UPDATE + str tmp1, [pmc, #AT91_PMC_PLL_UPDT] + + /* step 2. */ + ldr tmp1, =AT91_PMC_PLL_ACR_DEFAULT_PLLA + str tmp1, [pmc, #AT91_PMC_PLL_ACR] + + /* step 3. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_CTRL1] + mov tmp3, tmp2 + bic tmp3, tmp3, #0xffffff + orr tmp1, tmp1, tmp3 + str tmp1, [pmc, #AT91_PMC_PLL_CTRL1] + + /* step 8. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_UPDT] + bic tmp1, tmp1, #AT91_PMC_PLL_UPDT_ID + orr tmp1, tmp1, #AT91_PMC_PLL_UPDT_UPDATE + str tmp1, [pmc, #AT91_PMC_PLL_UPDT] + + /* step 9. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_CTRL0] + orr tmp1, tmp1, #AT91_PMC_PLL_CTRL0_ENLOCK + orr tmp1, tmp1, #AT91_PMC_PLL_CTRL0_ENPLL + orr tmp1, tmp1, #AT91_PMC_PLL_CTRL0_ENPLLCK + bic tmp1, tmp1, #0xff + mov tmp3, tmp2 + bic tmp3, tmp3, #0xffffff00 + orr tmp1, tmp1, tmp3 + str tmp1, [pmc, #AT91_PMC_PLL_CTRL0] + + /* step 10. */ + ldr tmp1, [pmc, #AT91_PMC_PLL_UPDT] + orr tmp1, tmp1, #AT91_PMC_PLL_UPDT_UPDATE + bic tmp1, tmp1, #AT91_PMC_PLL_UPDT_ID + str tmp1, [pmc, #AT91_PMC_PLL_UPDT] + + /* step 11. */ +3: ldr tmp1, [pmc, #AT91_PMC_PLL_ISR0] + tst tmp1, #0x1 + beq 3b + b 2f +#endif + + /* Restore PLLA setting */ +4: str tmp2, [pmc, #AT91_CKGR_PLLAR] + + /* Enable PLLA. */ + tst tmp2, #(AT91_PMC_MUL & 0xff0000) + bne 1f + tst tmp2, #(AT91_PMC_MUL & ~0xff0000) + beq 2f + +1: ldr tmp1, [pmc, #AT91_PMC_SR] + tst tmp1, #AT91_PMC_LOCKA + beq 1b +2: +.endm + +/** + * at91_mckx_ps_enable: save MCK1..4 settings and switch it to main clock + * + * Side effects: overwrites tmp1, tmp2 + */ +.macro at91_mckx_ps_enable +#ifdef CONFIG_SOC_SAMA7 + ldr pmc, .pmc_base + + /* There are 4 MCKs we need to handle: MCK1..4 */ + mov tmp1, #1 +e_loop: cmp tmp1, #5 + beq e_done + + /* Write MCK ID to retrieve the settings. */ + str tmp1, [pmc, #AT91_PMC_MCR_V2] + ldr tmp2, [pmc, #AT91_PMC_MCR_V2] + +e_save_mck1: + cmp tmp1, #1 + bne e_save_mck2 + str tmp2, .saved_mck1 + b e_ps + +e_save_mck2: + cmp tmp1, #2 + bne e_save_mck3 + str tmp2, .saved_mck2 + b e_ps + +e_save_mck3: + cmp tmp1, #3 + bne e_save_mck4 + str tmp2, .saved_mck3 + b e_ps + +e_save_mck4: + str tmp2, .saved_mck4 + +e_ps: + /* Use CSS=MAINCK and DIV=1. */ + bic tmp2, tmp2, #AT91_PMC_MCR_V2_CSS + bic tmp2, tmp2, #AT91_PMC_MCR_V2_DIV + orr tmp2, tmp2, #AT91_PMC_MCR_V2_CSS_MAINCK + orr tmp2, tmp2, #AT91_PMC_MCR_V2_DIV1 + str tmp2, [pmc, #AT91_PMC_MCR_V2] + + wait_mckrdy tmp1 + + add tmp1, tmp1, #1 + b e_loop + +e_done: +#endif +.endm + +/** + * at91_mckx_ps_restore: restore MCK1..4 settings + * + * Side effects: overwrites tmp1, tmp2 + */ +.macro at91_mckx_ps_restore +#ifdef CONFIG_SOC_SAMA7 + ldr pmc, .pmc_base + + /* There are 4 MCKs we need to handle: MCK1..4 */ + mov tmp1, #1 +r_loop: cmp tmp1, #5 + beq r_done + +r_save_mck1: + cmp tmp1, #1 + bne r_save_mck2 + ldr tmp2, .saved_mck1 + b r_ps + +r_save_mck2: + cmp tmp1, #2 + bne r_save_mck3 + ldr tmp2, .saved_mck2 + b r_ps + +r_save_mck3: + cmp tmp1, #3 + bne r_save_mck4 + ldr tmp2, .saved_mck3 + b r_ps + +r_save_mck4: + ldr tmp2, .saved_mck4 + +r_ps: + /* Write MCK ID to retrieve the settings. */ + str tmp1, [pmc, #AT91_PMC_MCR_V2] + ldr tmp3, [pmc, #AT91_PMC_MCR_V2] + + /* We need to restore CSS and DIV. */ + bic tmp3, tmp3, #AT91_PMC_MCR_V2_CSS + bic tmp3, tmp3, #AT91_PMC_MCR_V2_DIV + orr tmp3, tmp3, tmp2 + bic tmp3, tmp3, #AT91_PMC_MCR_V2_ID_MSK + orr tmp3, tmp3, tmp1 + orr tmp3, tmp3, #AT91_PMC_MCR_V2_CMD + str tmp2, [pmc, #AT91_PMC_MCR_V2] + + wait_mckrdy tmp1 + + add tmp1, tmp1, #1 + b r_loop +r_done: +#endif +.endm + +.macro at91_ulp_mode + at91_mckx_ps_enable + + ldr pmc, .pmc_base + ldr tmp2, .mckr_offset + ldr tmp3, .pm_mode + + /* Save Master clock setting */ + ldr tmp1, [pmc, tmp2] + str tmp1, .saved_mckr + + /* + * Set master clock source to: + * - MAINCK if using ULP0 fast variant + * - slow clock, otherwise + */ + bic tmp1, tmp1, #AT91_PMC_CSS + cmp tmp3, #AT91_PM_ULP0_FAST + bne save_mck + orr tmp1, tmp1, #AT91_PMC_CSS_MAIN +save_mck: + str tmp1, [pmc, tmp2] + + mov tmp3, #0 + wait_mckrdy tmp3 + + at91_plla_disable + + /* Enable low power mode for 2.5V regulator. */ + at91_2_5V_reg_set_low_power 1 + + ldr tmp3, .pm_mode + cmp tmp3, #AT91_PM_ULP1 + beq ulp1_mode + + at91_pm_ulp0_mode + b ulp_exit + +ulp1_mode: + at91_pm_ulp1_mode + b ulp_exit + +ulp_exit: + /* Disable low power mode for 2.5V regulator. */ + at91_2_5V_reg_set_low_power 0 + + ldr pmc, .pmc_base + + at91_plla_enable + + /* + * Restore master clock setting + */ + ldr tmp1, .mckr_offset + ldr tmp2, .saved_mckr + str tmp2, [pmc, tmp1] + + mov tmp3, #0 + wait_mckrdy tmp3 + + at91_mckx_ps_restore +.endm + +.macro at91_backup_mode + /* Switch the master clock source to slow clock. */ + ldr pmc, .pmc_base + ldr tmp2, .mckr_offset + ldr tmp1, [pmc, tmp2] + bic tmp1, tmp1, #AT91_PMC_CSS + str tmp1, [pmc, tmp2] + + mov tmp3, #0 + wait_mckrdy tmp3 + + /*BUMEN*/ + ldr r0, .sfrbu + mov tmp1, #0x1 + str tmp1, [r0, #0x10] + + /* Wait for it. */ +1: ldr tmp1, [r0, #0x10] + tst tmp1, #0x1 + beq 1b + + /* Shutdown */ + ldr r0, .shdwc + mov tmp1, #0xA5000000 + add tmp1, tmp1, #0x1 + at91_backup_set_lpm tmp1 + str tmp1, [r0, #0] +.endm + +/* + * void at91_suspend_sram_fn(struct at91_pm_data*) + * @input param: + * @r0: base address of struct at91_pm_data + */ +/* at91_pm_suspend_in_sram must be 8-byte aligned per the requirements of fncpy() */ + .align 3 +ENTRY(at91_pm_suspend_in_sram) + /* Save registers on stack */ + stmfd sp!, {r4 - r12, lr} + + /* Drain write buffer */ + mov tmp1, #0 + mcr p15, 0, tmp1, c7, c10, 4 + + /* Flush tlb. */ + mov r4, #0 + mcr p15, 0, r4, c8, c7, 0 + + ldr tmp1, [r0, #PM_DATA_PMC_MCKR_OFFSET] + str tmp1, .mckr_offset + ldr tmp1, [r0, #PM_DATA_PMC_VERSION] + str tmp1, .pmc_version + ldr tmp1, [r0, #PM_DATA_MEMCTRL] + str tmp1, .memtype + ldr tmp1, [r0, #PM_DATA_MODE] + str tmp1, .pm_mode + + /* + * ldrne below are here to preload their address in the TLB as access + * to RAM may be limited while in self-refresh. + */ + ldr tmp1, [r0, #PM_DATA_PMC] + str tmp1, .pmc_base + cmp tmp1, #0 + ldrne tmp2, [tmp1, #0] + + ldr tmp1, [r0, #PM_DATA_RAMC0] + str tmp1, .sramc_base + cmp tmp1, #0 + ldrne tmp2, [tmp1, #0] + + ldr tmp1, [r0, #PM_DATA_RAMC1] + str tmp1, .sramc1_base + cmp tmp1, #0 + ldrne tmp2, [tmp1, #0] + +#ifndef CONFIG_SOC_SAM_V4_V5 + /* ldrne below are here to preload their address in the TLB */ + ldr tmp1, [r0, #PM_DATA_RAMC_PHY] + str tmp1, .sramc_phy_base + cmp tmp1, #0 + ldrne tmp2, [tmp1, #0] + + ldr tmp1, [r0, #PM_DATA_SHDWC] + str tmp1, .shdwc + cmp tmp1, #0 + ldrne tmp2, [tmp1, #0] + + ldr tmp1, [r0, #PM_DATA_SFRBU] + str tmp1, .sfrbu + cmp tmp1, #0 + ldrne tmp2, [tmp1, #0x10] +#endif + + /* Active the self-refresh mode */ + at91_sramc_self_refresh_ena + + ldr r0, .pm_mode + cmp r0, #AT91_PM_STANDBY + beq standby + cmp r0, #AT91_PM_BACKUP + beq backup_mode + + at91_ulp_mode + b exit_suspend + +standby: + /* Wait for interrupt */ + ldr pmc, .pmc_base + at91_cpu_idle + b exit_suspend + +backup_mode: + at91_backup_mode + +exit_suspend: + /* Exit the self-refresh mode */ + at91_sramc_self_refresh_dis + + /* Restore registers, and return */ + ldmfd sp!, {r4 - r12, pc} +ENDPROC(at91_pm_suspend_in_sram) + +.pmc_base: + .word 0 +.sramc_base: + .word 0 +.sramc1_base: + .word 0 +.sramc_phy_base: + .word 0 +.shdwc: + .word 0 +.sfrbu: + .word 0 +.memtype: + .word 0 +.pm_mode: + .word 0 +.mckr_offset: + .word 0 +.pmc_version: + .word 0 +.saved_mckr: + .word 0 +.saved_pllar: + .word 0 +.saved_sam9_lpr: + .word 0 +.saved_sam9_lpr1: + .word 0 +.saved_sam9_mdr: + .word 0 +.saved_sam9_mdr1: + .word 0 +.saved_osc_status: + .word 0 +#ifdef CONFIG_SOC_SAMA7 +.saved_mck1: + .word 0 +.saved_mck2: + .word 0 +.saved_mck3: + .word 0 +.saved_mck4: + .word 0 +#endif + +ENTRY(at91_pm_suspend_in_sram_sz) + .word .-at91_pm_suspend_in_sram diff --git a/arch/arm/mach-at91/sam9x60.c b/arch/arm/mach-at91/sam9x60.c new file mode 100644 index 000000000..d8c739d25 --- /dev/null +++ b/arch/arm/mach-at91/sam9x60.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Setup code for SAM9X60. + * + * Copyright (C) 2019 Microchip Technology Inc. and its subsidiaries + * + * Author: Claudiu Beznea <claudiu.beznea@microchip.com> + */ + +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <asm/mach/arch.h> +#include <asm/system_misc.h> + +#include "generic.h" + +static void __init sam9x60_init(void) +{ + of_platform_default_populate(NULL, NULL, NULL); + + sam9x60_pm_init(); +} + +static const char *const sam9x60_dt_board_compat[] __initconst = { + "microchip,sam9x60", + NULL +}; + +DT_MACHINE_START(sam9x60_dt, "Microchip SAM9X60") + /* Maintainer: Microchip */ + .init_machine = sam9x60_init, + .dt_compat = sam9x60_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-at91/sam_secure.c b/arch/arm/mach-at91/sam_secure.c new file mode 100644 index 000000000..f7789cbe2 --- /dev/null +++ b/arch/arm/mach-at91/sam_secure.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022, Microchip + */ + +#include <linux/arm-smccc.h> +#include <linux/of.h> + +#include "sam_secure.h" + +static bool optee_available; + +#define SAM_SIP_SMC_STD_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL, ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_SIP, (func_num)) + +struct arm_smccc_res sam_smccc_call(u32 fn, u32 arg0, u32 arg1) +{ + struct arm_smccc_res res = {.a0 = -1}; + + if (WARN_ON(!optee_available)) + return res; + + arm_smccc_smc(SAM_SIP_SMC_STD_CALL_VAL(fn), arg0, arg1, 0, 0, 0, 0, 0, + &res); + + return res; +} + +bool sam_linux_is_optee_available(void) +{ + /* If optee has been detected, then we are running in normal world */ + return optee_available; +} + +void __init sam_secure_init(void) +{ + struct device_node *np; + + /* + * We only check that the OP-TEE node is present and available. The + * OP-TEE kernel driver is not needed for the type of interaction made + * with OP-TEE here so the driver's status is not checked. + */ + np = of_find_node_by_path("/firmware/optee"); + if (np && of_device_is_available(np)) + optee_available = true; + of_node_put(np); + + if (optee_available) + pr_info("Running under OP-TEE firmware\n"); +} diff --git a/arch/arm/mach-at91/sam_secure.h b/arch/arm/mach-at91/sam_secure.h new file mode 100644 index 000000000..1a0b5ebbf --- /dev/null +++ b/arch/arm/mach-at91/sam_secure.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2022, Microchip + */ + +#ifndef SAM_SECURE_H +#define SAM_SECURE_H + +#include <linux/arm-smccc.h> + +/* Secure Monitor mode APIs */ +#define SAMA5_SMC_SIP_SET_SUSPEND_MODE 0x400 +#define SAMA5_SMC_SIP_GET_SUSPEND_MODE 0x401 + +void __init sam_secure_init(void); +struct arm_smccc_res sam_smccc_call(u32 fn, u32 arg0, u32 arg1); +bool sam_linux_is_optee_available(void); + +#endif /* SAM_SECURE_H */ diff --git a/arch/arm/mach-at91/sama5.c b/arch/arm/mach-at91/sama5.c new file mode 100644 index 000000000..bf2b5c6a1 --- /dev/null +++ b/arch/arm/mach-at91/sama5.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Setup code for SAMA5 + * + * Copyright (C) 2013 Atmel, + * 2013 Ludovic Desroches <ludovic.desroches@atmel.com> + */ + +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <asm/hardware/cache-l2x0.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> +#include <asm/outercache.h> +#include <asm/system_misc.h> + +#include "generic.h" +#include "sam_secure.h" + +static void sama5_l2c310_write_sec(unsigned long val, unsigned reg) +{ + /* OP-TEE configures the L2 cache and does not allow modifying it yet */ +} + +static void __init sama5_secure_cache_init(void) +{ + sam_secure_init(); + if (IS_ENABLED(CONFIG_OUTER_CACHE) && sam_linux_is_optee_available()) + outer_cache.write_sec = sama5_l2c310_write_sec; +} + +static void __init sama5_dt_device_init(void) +{ + of_platform_default_populate(NULL, NULL, NULL); + sama5_pm_init(); +} + +static const char *const sama5_dt_board_compat[] __initconst = { + "atmel,sama5", + NULL +}; + +DT_MACHINE_START(sama5_dt, "Atmel SAMA5") + /* Maintainer: Atmel */ + .init_machine = sama5_dt_device_init, + .dt_compat = sama5_dt_board_compat, +MACHINE_END + +static const char *const sama5_alt_dt_board_compat[] __initconst = { + "atmel,sama5d4", + NULL +}; + +DT_MACHINE_START(sama5_alt_dt, "Atmel SAMA5") + /* Maintainer: Atmel */ + .init_machine = sama5_dt_device_init, + .dt_compat = sama5_alt_dt_board_compat, + .l2c_aux_mask = ~0UL, +MACHINE_END + +static void __init sama5d2_init(void) +{ + of_platform_default_populate(NULL, NULL, NULL); + sama5d2_pm_init(); +} + +static const char *const sama5d2_compat[] __initconst = { + "atmel,sama5d2", + NULL +}; + +DT_MACHINE_START(sama5d2, "Atmel SAMA5") + /* Maintainer: Atmel */ + .init_machine = sama5d2_init, + .init_early = sama5_secure_cache_init, + .dt_compat = sama5d2_compat, + .l2c_aux_mask = ~0UL, +MACHINE_END diff --git a/arch/arm/mach-at91/sama7.c b/arch/arm/mach-at91/sama7.c new file mode 100644 index 000000000..bd43733ed --- /dev/null +++ b/arch/arm/mach-at91/sama7.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Setup code for SAMA7 + * + * Copyright (C) 2021 Microchip Technology, Inc. and its subsidiaries + * + */ + +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <asm/mach/arch.h> +#include <asm/system_misc.h> + +#include "generic.h" + +static void __init sama7_dt_device_init(void) +{ + of_platform_default_populate(NULL, NULL, NULL); + sama7_pm_init(); +} + +static const char *const sama7_dt_board_compat[] __initconst = { + "microchip,sama7", + NULL +}; + +DT_MACHINE_START(sama7_dt, "Microchip SAMA7") + /* Maintainer: Microchip */ + .init_machine = sama7_dt_device_init, + .dt_compat = sama7_dt_board_compat, +MACHINE_END + diff --git a/arch/arm/mach-at91/samv7.c b/arch/arm/mach-at91/samv7.c new file mode 100644 index 000000000..28f998f0f --- /dev/null +++ b/arch/arm/mach-at91/samv7.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Setup code for SAMv7x + * + * Copyright (C) 2013 Atmel, + * 2016 Andras Szemzo <szemzo.andras@gmail.com> + */ +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> +#include <asm/system_misc.h> +#include "generic.h" + +static const char *const samv7_dt_board_compat[] __initconst = { + "atmel,samv7", + NULL +}; + +DT_MACHINE_START(samv7_dt, "Atmel SAMV7") + .dt_compat = samv7_dt_board_compat, +MACHINE_END |