Skip to content

Commit

Permalink
drivers: atmel-shdwc: add atmel shdwc driver
Browse files Browse the repository at this point in the history
Add atmel shdwc driver for sama5d2. This driver uses assembly code
which expects to run from a single cache line. For the time being,
building this code is restricted to single core system since it rely
on the fact that no other cores can invalidate the TLB or the
I-cache. This driver will be used by PSCI to shutdown the SoC.

Acked-by: Etienne Carriere <etienne.carriere@linaro.org>
Acked-by: Jerome Forissier <jerome@forissier.org>
Acked-by: Jens Wiklander <jens.wiklander@linaro.org>
Signed-off-by: Clément Léger <clement.leger@bootlin.com>
  • Loading branch information
clementleger authored and jforissier committed Dec 15, 2021
1 parent aa161c1 commit 58200af
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 0 deletions.
182 changes: 182 additions & 0 deletions core/drivers/atmel_shdwc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2015 Atmel Corporation,
* Nicolas Ferre <nicolas.ferre@atmel.com>
* Copyright (c) 2021, Microchip
*/

#include <drivers/atmel_shdwc.h>
#include <drivers/sam/at91_ddr.h>
#include <io.h>
#include <kernel/dt.h>
#include <kernel/thread.h>
#include <libfdt.h>
#include <stdbool.h>
#include <tee_api_defines.h>
#include <tee_api_types.h>
#include <trace.h>
#include <types_ext.h>
#include <util.h>

#include "at91_clk.h"

#define SHDW_WK_PIN(reg, cfg) ((reg) & \
AT91_SHDW_WKUPIS((cfg)->wkup_pin_input))
#define SHDW_RTCWK(reg, cfg) (((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1)
#define SHDW_RTTWK(reg, cfg) (((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1)
#define SHDW_RTCWKEN(cfg) BIT((cfg)->mr_rtcwk_shift)
#define SHDW_RTTWKEN(cfg) BIT((cfg)->mr_rttwk_shift)

#define SLOW_CLK_FREQ 32768ULL
#define DBC_PERIOD_US(x) DIV_ROUND_UP((1000000ULL * (x)), SLOW_CLK_FREQ)

static vaddr_t shdwc_base;
static vaddr_t mpddrc_base;

bool atmel_shdwc_available(void)
{
return shdwc_base != 0;
}

void __noreturn atmel_shdwc_shutdown(void)
{
vaddr_t pmc_base = at91_pmc_get_base();

/*
* Mask exception before entering assembly which does not expect to be
* interrupted.
*/
thread_mask_exceptions(THREAD_EXCP_ALL);

__atmel_shdwc_shutdown(mpddrc_base, shdwc_base, pmc_base);

/* We are going to shutdown the CPU so we will never hit this loop */
while (true)
;
}

static const unsigned long long sdwc_dbc_period[] = {
0, 3, 32, 512, 4096, 32768,
};

static uint32_t at91_shdwc_debouncer_value(uint32_t in_period_us)
{
int i = 0;
int max_idx = ARRAY_SIZE(sdwc_dbc_period) - 1;
uint64_t period_us = 0;
uint64_t max_period_us = DBC_PERIOD_US(sdwc_dbc_period[max_idx]);

if (in_period_us > max_period_us) {
DMSG("debouncer period %"PRIu32" too big, using %"PRIu64" us",
in_period_us, max_period_us);
return max_idx;
}

for (i = max_idx - 1; i > 0; i--) {
period_us = DBC_PERIOD_US(sdwc_dbc_period[i]);
if (in_period_us > period_us)
break;
}

return i + 1;
}

static uint32_t at91_shdwc_get_wakeup_input(const void *fdt, int np)
{
const uint32_t *prop = NULL;
uint32_t wk_input_mask = 0;
uint32_t wuir = 0;
uint32_t wk_input = 0;
int child = 0;
int len = 0;

fdt_for_each_subnode(child, fdt, np) {
prop = fdt_getprop(fdt, child, "reg", &len);
if (!prop || len != sizeof(uint32_t)) {
DMSG("reg property is missing for node %s",
fdt_get_name(fdt, child, NULL));
continue;
}
wk_input = fdt32_to_cpu(*prop);
wk_input_mask = BIT32(wk_input);
if (!(wk_input_mask & AT91_SHDW_WKUPEN_MASK)) {
DMSG("wake-up input %"PRId32" out of bounds ignore",
wk_input);
continue;
}
wuir |= wk_input_mask;

if (fdt_getprop(fdt, child, "atmel,wakeup-active-high", NULL))
wuir |= AT91_SHDW_WKUPT(wk_input);
}

return wuir;
}

static void at91_shdwc_dt_configure(const void *fdt, int np)
{
const uint32_t *prop = NULL;
uint32_t mode = 0;
uint32_t tmp = 0;
uint32_t input = 0;
int len = 0;

prop = fdt_getprop(fdt, np, "debounce-delay-us", &len);
if (prop && len == sizeof(uint32_t)) {
tmp = fdt32_to_cpu(*prop);
mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(tmp));
}

if (fdt_getprop(fdt, np, "atmel,wakeup-rtc-timer", &len))
mode |= AT91_SHDW_RTCWKEN;

io_write32(shdwc_base + AT91_SHDW_MR, mode);

input = at91_shdwc_get_wakeup_input(fdt, np);
io_write32(shdwc_base + AT91_SHDW_WUIR, input);
}

static TEE_Result atmel_shdwc_probe(const void *fdt, int node,
const void *compat_data __unused)
{
int ddr_node = 0;
size_t size = 0;
uint32_t ddr = AT91_DDRSDRC_MD_LPDDR2;

/*
* Assembly code relies on the fact that there is only one CPU to avoid
* any other one to invalidate TLB/I-Cache.
*/
COMPILE_TIME_ASSERT(CFG_TEE_CORE_NB_CORE == 1);

if (dt_map_dev(fdt, node, &shdwc_base, &size) < 0)
return TEE_ERROR_GENERIC;

ddr_node = fdt_node_offset_by_compatible(fdt, -1,
"atmel,sama5d3-ddramc");
if (ddr_node < 0)
return TEE_ERROR_GENERIC;

if (dt_map_dev(fdt, ddr_node, &mpddrc_base, &size) < 0)
return TEE_ERROR_GENERIC;

ddr = io_read32(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD;
if (ddr != AT91_DDRSDRC_MD_LPDDR2 && ddr != AT91_DDRSDRC_MD_LPDDR3)
mpddrc_base = 0;

at91_shdwc_dt_configure(fdt, node);

return TEE_SUCCESS;
}

static const struct dt_device_match atmel_shdwc_match_table[] = {
{ .compatible = "atmel,sama5d2-shdwc" },
{ }
};

const struct dt_driver atmel_shdwc_dt_driver __dt_driver = {
.name = "atmel_shdwc",
.type = DT_DRIVER_NOTYPE,
.match_table = atmel_shdwc_match_table,
.probe = atmel_shdwc_probe,
};
79 changes: 79 additions & 0 deletions core/drivers/atmel_shdwc_a32.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (c) 2015 Atmel Corporation,
* Nicolas Ferre <nicolas.ferre@atmel.com>
* Copyright (c) 2021, Microchip
*/


#include <arm.h>
#include <arm32_macros.S>
#include <asm.S>
#include <drivers/atmel_shdwc.h>
#include <drivers/sam/at91_ddr.h>

#include "at91_pmc.h"

/*
* Code size of shutdown assembly must fit in a single Cortex-A5 cache
* line which is 8 words long (32 bytes) since SDRAM might be disabled and thus
* not accessible to fetch code or data from it.
*/
.macro check_fit_in_cacheline since
.if (. - \since) > 32
.error "Shutdown assembly code exceeds cache line size"
.endif
.endm

/**
* Shutdown the CPU
*
* This function is in assembly to be sure the code fits in a single cache line.
* We are going to power down the SDRAM and thus we can't fetch code from it
* once powered down.
*
* r0 = mpddrc_base
* r1 = shdwc_base
* r2 = pmc_base
*/
FUNC __atmel_shdwc_shutdown , :

mov_imm r3, AT91_DDRSDRC_LPDDR2_PWOFF
mov_imm r4, (AT91_SHDW_KEY | AT91_SHDW_SHDW)

/*
* Read values from both shutdown controller and PMC to ensure the
* translations will be in the TLB.
*/
ldr r6, [r1, #AT91_SHDW_CR]
ldr r6, [r2, #AT91_PMC_MCKR]

/* Power down SDRAM0 if mpddrc_base is set */
tst r0, #0
beq 1f

/* Align to cache line to ensure the rest of code fits in a single line */
.balign 32
__atmel_shdwc_shutdown_sdram_disabled:
str r3, [r0, #AT91_DDRSDRC_LPR]

/* Switch the master clock source to slow clock. */
1:
ldr r6, [r2, #AT91_PMC_MCKR]
bic r6, r6, #AT91_PMC_CSS
str r6, [r2, #AT91_PMC_MCKR]

/* Wait for clock switch. */
2:
ldr r6, [r2, #AT91_PMC_SR]
tst r6, #AT91_PMC_MCKRDY
beq 2b

/* Shutdown CPU */
str r4, [r1, #AT91_SHDW_CR]

check_fit_in_cacheline __atmel_shdwc_shutdown_sdram_disabled

/* We should never reach this since we shut down the CPU */
b .
END_FUNC __atmel_shdwc_shutdown
1 change: 1 addition & 0 deletions core/drivers/sub.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ srcs-$(CFG_STIH_UART) += stih_asc.c
srcs-$(CFG_ATMEL_UART) += atmel_uart.c
srcs-$(CFG_ATMEL_TRNG) += atmel_trng.c
srcs-$(CFG_ATMEL_RSTC) += atmel_rstc.c
srcs-$(CFG_ATMEL_SHDWC) += atmel_shdwc.c atmel_shdwc_a32.S
srcs-$(CFG_AMLOGIC_UART) += amlogic_uart.c
srcs-$(CFG_MVEBU_UART) += mvebu_uart.c
srcs-$(CFG_STM32_BSEC) += stm32_bsec.c
Expand Down
64 changes: 64 additions & 0 deletions core/include/drivers/atmel_shdwc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (c) 2015 Atmel Corporation,
* Nicolas Ferre <nicolas.ferre@atmel.com>
* Copyright (c) 2021, Microchip
*/

#ifndef __DRIVERS_ATMEL_SHDWC_H
#define __DRIVERS_ATMEL_SHDWC_H

#include <compiler.h>
#include <stdbool.h>
#include <stdint.h>
#include <util.h>

/* Shut Down Control Register */
#define AT91_SHDW_CR 0x00
/* Shut Down command */
#define AT91_SHDW_SHDW BIT(0)
/* KEY Password */
#define AT91_SHDW_KEY SHIFT_U32(0xa5UL, 24)

/* Shut Down Mode Register */
#define AT91_SHDW_MR 0x04
#define AT91_SHDW_WKUPDBC_SHIFT 24
#define AT91_SHDW_WKUPDBC_MASK GENMASK_32(26, 24)
#define AT91_SHDW_WKUPDBC(x) (SHIFT_U32((x), AT91_SHDW_WKUPDBC_SHIFT) & \
AT91_SHDW_WKUPDBC_MASK)
#define AT91_SHDW_RTCWKEN BIT32(17)

/* Shut Down Status Register */
#define AT91_SHDW_SR 0x08
#define AT91_SHDW_WKUPIS_SHIFT 16
#define AT91_SHDW_WKUPIS_MASK GENMASK_32(31, 16)
#define AT91_SHDW_WKUPIS(x) (BIT32((x) + AT91_SHDW_WKUPIS_SHIFT))

/* Shutdown Wake-up Inputs Register */
#define AT91_SHDW_WUIR 0x0c
#define AT91_SHDW_WKUPEN_MASK GENMASK_32(15, 0)
#define AT91_SHDW_WKUPEN(x) (BIT32(x) & AT91_SHDW_WKUPEN_MASK)
#define AT91_SHDW_WKUPT_SHIFT 16
#define AT91_SHDW_WKUPT_MASK GENMASK_32(31, 16)
#define AT91_SHDW_WKUPT(x) (BIT32((x) + AT91_SHDW_WKUPT_SHIFT))

#ifndef __ASSEMBLER__
#if defined(CFG_ATMEL_SHDWC)

void __atmel_shdwc_shutdown(uint32_t mpddrc_base, uint32_t shdwc_base,
uint32_t pmc_base);

bool atmel_shdwc_available(void);

void __noreturn atmel_shdwc_shutdown(void);
#else
static inline bool atmel_shdwc_available(void)
{
return false;
}

static inline void atmel_shdwc_shutdown(void) {}
#endif /* defined(CFG_ATMEL_SHDWC) */
#endif /* __ASSEMBLER__*/

#endif /* __DRIVERS_ATMEL_SHDWC_H */

0 comments on commit 58200af

Please sign in to comment.