// 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/platform_device.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 = 0 xA5A5A5A5;
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 sama7_ws_ids[] = {
{ .compatible = "microchip,sama7d65-rtc" , .data = &ws_info[1 ] },
{ .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,sama7d65-sdhci" , .data = &ws_info[3 ] },
{ .compatible = "microchip,sama7g5-sdhci" , .data = &ws_info[3 ] },
{ .compatible = "microchip,sama7d65-rtt" , .data = &ws_info[4 ] },
{ .compatible = "microchip,sama7g5-rtt" , .data = &ws_info[4 ] },
{ /* sentinel */ }
};
static const struct of_device_id sam9x7_ws_ids[] = {
{ .compatible = "microchip,sam9x7-rtc" , .data = &ws_info[1 ] },
{ .compatible = "microchip,sam9x7-rtt" , .data = &ws_info[4 ] },
{ .compatible = "microchip,sam9x7-gem" , .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.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 + 0 x04);
/* 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 + 0 x0c);
*mode |= (val & 0 x3ff);
*polarity |= ((val >> 16 ) & 0 x3ff);
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)
{
/* SYNOPSYS workaround to fix a bug in the calibration logic */
unsigned char modified_fix_code[] = {
0 x00, 0 x01, 0 x01, 0 x06, 0 x07, 0 x0c, 0 x06, 0 x07, 0 x0b, 0 x18,
0 x0a, 0 x0b, 0 x0c, 0 x0d, 0 x0d, 0 x0a, 0 x13, 0 x13, 0 x12, 0 x13,
0 x14, 0 x15, 0 x15, 0 x12, 0 x18, 0 x19, 0 x19, 0 x1e, 0 x1f, 0 x14,
0 x1e, 0 x1f,
};
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 .
*/
tmp = readl(soc_pm.data.ramc_phy + DDR3PHY_ZQ0SR0);
/* Store pull-down output impedance select. */
index = (tmp >> DDR3PHY_ZQ0SR0_PDO_OFF) & 0 x1f;
soc_pm.bu->ddr_phy_calibration[0 ] = modified_fix_code[index] << DDR3PHY_ZQ0SR0_PDO_OFF;
/* Store pull-up output impedance select. */
index = (tmp >> DDR3PHY_ZQ0SR0_PUO_OFF) & 0 x1f;
soc_pm.bu->ddr_phy_calibration[0 ] |= modified_fix_code[index] << DDR3PHY_ZQ0SR0_PUO_OFF;
/* Store pull-down on-die termination impedance select. */
index = (tmp >> DDR3PHY_ZQ0SR0_PDODT_OFF) & 0 x1f;
soc_pm.bu->ddr_phy_calibration[0 ] |= modified_fix_code[index] << DDR3PHY_ZQ0SR0_PDODT_OFF;
/* Store pull-up on-die termination impedance select. */
index = (tmp >> DDR3PHY_ZQ0SRO_PUODT_OFF) & 0 x1f;
soc_pm.bu->ddr_phy_calibration[0 ] |= modified_fix_code[index] << DDR3PHY_ZQ0SRO_PUODT_OFF;
/*
* The 1 st 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 ;
}
/**
* at91_pm_switch_ba_to_auto ( ) - Configure Backup Unit Power Switch
* to automatic / hardware mode .
*
* The Backup Unit Power Switch can be managed either by software or hardware .
* Enabling hardware mode allows the automatic transition of power between
* VDDANA ( or VDDIN33 ) and VDDBU ( or VBAT , respectively ) , based on the
* availability of these power sources .
*
* If the Backup Unit Power Switch is already in automatic mode , no action is
* required . If it is in software - controlled mode , it is switched to automatic
* mode to enhance safety and eliminate the need for toggling between power
* sources .
*/
static void at91_pm_switch_ba_to_auto(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 auto/hardware. */
if (!(val & soc_pm.sfrbu_regs.pswbu.ctrl))
return ;
val &= ~soc_pm.sfrbu_regs.pswbu.ctrl;
val |= soc_pm.sfrbu_regs.pswbu.key;
writel(val, 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_auto();
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);
if (IS_ENABLED(CONFIG_SOC_SAMA7D65)) {
/* SHDWC.SR */
readl(soc_pm.data.shdwc + 0 x08);
}
} 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) &&
!IS_ENABLED(CONFIG_SOC_SAMA7D65))
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);
soc_pm.data.mode = suspend_mode;
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" );
soc_pm.data.mode = -1 ;
return ;
}
pr_info("AT91: Secure PM: using default suspend mode %s\n" ,
pm_modes[suspend_mode].pattern);
soc_pm.data.suspend_mode = res.a1;
soc_pm.data.mode = soc_pm.data.suspend_mode;
}
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;
unsigned long mcks;
};
static const struct pmc_info pmc_infos[] __initconst = {
{
.uhp_udp_mask = AT91RM9200_PMC_UHP | AT91RM9200_PMC_UDP,
.mckr = 0 x30,
.version = AT91_PMC_V1,
},
{
.uhp_udp_mask = AT91SAM926x_PMC_UHP | AT91SAM926x_PMC_UDP,
.mckr = 0 x30,
.version = AT91_PMC_V1,
},
{
.uhp_udp_mask = AT91SAM926x_PMC_UHP,
.mckr = 0 x30,
.version = AT91_PMC_V1,
},
{ .uhp_udp_mask = 0 ,
.mckr = 0 x30,
.version = AT91_PMC_V1,
},
{
.uhp_udp_mask = AT91SAM926x_PMC_UHP | AT91SAM926x_PMC_UDP,
.mckr = 0 x28,
.version = AT91_PMC_V2,
},
{
.mckr = 0 x28,
.version = AT91_PMC_V2,
.mcks = 4 ,
},
{
.uhp_udp_mask = AT91SAM926x_PMC_UHP,
.mckr = 0 x28,
.version = AT91_PMC_V2,
.mcks = 9 ,
},
};
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,sam9x7-pmc" , .data = &pmc_infos[4 ] },
{ .compatible = "microchip,sama7d65-pmc" , .data = &pmc_infos[6 ] },
{ .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;
soc_pm.data.pmc_mcks = pmc->mcks;
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 sam9x7_pm_init(void )
{
static const int modes[] __initconst = {
AT91_PM_STANDBY, AT91_PM_ULP0,
};
int ret;
if (!IS_ENABLED(CONFIG_SOC_SAM9X7))
return ;
at91_pm_modes_validate(modes, ARRAY_SIZE(modes));
ret = at91_dt_ramc(false );
if (ret)
return ;
at91_pm_init(NULL);
soc_pm.ws_ids = sam9x7_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 = (0 x4BD20C << 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 = sama7_ws_ids;
soc_pm.config_pmc_ws = at91_sam9x60_config_pmc_ws;
soc_pm.sfrbu_regs.pswbu.key = (0 x4BD20C << 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);
Messung V0.5 in Prozent C=96 H=90 G=93
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.45Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-28)
¤
*Eine klare Vorstellung vom Zielzustand