// 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_platform.h>
#include <linux/parser.h>
#include <linux/suspend.h>
#include <linux/clk/at91_pmc.h>
#include <asm/cacheflush.h>
#include <asm/fncpy.h>
#include <asm/system_misc.h>
#include <asm/suspend.h>
#include "generic.h"
#include "pm.h"
/*
* FIXME: this is needed to communicate between the pinctrl driver and
* the PM implementation in the machine. Possibly part of the PM
* implementation should be moved down into the pinctrl driver and get
* called as part of the generic suspend/resume path.
*/
#ifdef CONFIG_PINCTRL_AT91
extern void at91_pinctrl_gpio_suspend(void);
extern void at91_pinctrl_gpio_resume(void);
#endif
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_data data;
};
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_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;
static struct at91_pm_bu {
int suspended;
unsigned long reserved;
phys_addr_t canary;
phys_addr_t resume;
} *pm_bu;
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,at91rm9200-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 = "atmel,at91sam9x5-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 = "atmel,at91sam9260-rtt", .data = &ws_info[4] },
{ .compatible = "cdns,sam9x60-macb", .data = &ws_info[5] },
{ /* 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