158200af7SClément Léger // SPDX-License-Identifier: BSD-2-Clause 258200af7SClément Léger /* 358200af7SClément Léger * Copyright (c) 2015 Atmel Corporation, 458200af7SClément Léger * Nicolas Ferre <nicolas.ferre@atmel.com> 558200af7SClément Léger * Copyright (c) 2021, Microchip 658200af7SClément Léger */ 758200af7SClément Léger 858200af7SClément Léger #include <drivers/atmel_shdwc.h> 958200af7SClément Léger #include <drivers/sam/at91_ddr.h> 10d031d1ecSClément Léger #include <drivers/pm/sam/atmel_pm.h> 1158200af7SClément Léger #include <io.h> 1258200af7SClément Léger #include <kernel/dt.h> 139e3c57c8SEtienne Carriere #include <kernel/dt_driver.h> 1458200af7SClément Léger #include <kernel/thread.h> 1558200af7SClément Léger #include <libfdt.h> 16c2daaa37SClément Léger #include <matrix.h> 176370f75dSTony Han #include <platform_config.h> 1858200af7SClément Léger #include <stdbool.h> 1958200af7SClément Léger #include <tee_api_defines.h> 2058200af7SClément Léger #include <tee_api_types.h> 2158200af7SClément Léger #include <trace.h> 2258200af7SClément Léger #include <types_ext.h> 2358200af7SClément Léger #include <util.h> 2458200af7SClément Léger 2558200af7SClément Léger #include "at91_clk.h" 2658200af7SClément Léger 2758200af7SClément Léger #define SHDW_WK_PIN(reg, cfg) ((reg) & \ 2858200af7SClément Léger AT91_SHDW_WKUPIS((cfg)->wkup_pin_input)) 2958200af7SClément Léger #define SHDW_RTCWK(reg, cfg) (((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1) 3058200af7SClément Léger #define SHDW_RTTWK(reg, cfg) (((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1) 3158200af7SClément Léger #define SHDW_RTCWKEN(cfg) BIT((cfg)->mr_rtcwk_shift) 3258200af7SClément Léger #define SHDW_RTTWKEN(cfg) BIT((cfg)->mr_rttwk_shift) 3358200af7SClément Léger 3458200af7SClément Léger #define SLOW_CLK_FREQ 32768ULL 3558200af7SClément Léger #define DBC_PERIOD_US(x) DIV_ROUND_UP((1000000ULL * (x)), SLOW_CLK_FREQ) 3658200af7SClément Léger 37f527a3b7STony Han /* 38f527a3b7STony Han * @type_offset offset of Memory Device Register 39f527a3b7STony Han * @type_mask mask of Memory Device Type in Memory Device Register 40a991d533STony Han * @compatible the compatible string in the device tree 41f527a3b7STony Han */ 42f527a3b7STony Han struct ddrc_reg_config { 43f527a3b7STony Han uint32_t type_offset; 44f527a3b7STony Han uint32_t type_mask; 45a991d533STony Han const char *compatible; 46f527a3b7STony Han }; 47f527a3b7STony Han 48f527a3b7STony Han /* 49f527a3b7STony Han * @shdwc_always_secure Is peripheral SHDWC always secured? 50f527a3b7STony Han * @ddrc DDR controller configurations 51f527a3b7STony Han */ 52f527a3b7STony Han struct shdwc_compat { 53f527a3b7STony Han bool shdwc_always_secure; 54f527a3b7STony Han struct ddrc_reg_config ddrc; 55f527a3b7STony Han }; 56f527a3b7STony Han 5758200af7SClément Léger static vaddr_t shdwc_base; 5858200af7SClément Léger static vaddr_t mpddrc_base; 5958200af7SClément Léger 6058200af7SClément Léger bool atmel_shdwc_available(void) 6158200af7SClément Léger { 6258200af7SClément Léger return shdwc_base != 0; 6358200af7SClément Léger } 6458200af7SClément Léger 6558200af7SClément Léger void __noreturn atmel_shdwc_shutdown(void) 6658200af7SClément Léger { 6758200af7SClément Léger vaddr_t pmc_base = at91_pmc_get_base(); 6858200af7SClément Léger 6958200af7SClément Léger /* 7058200af7SClément Léger * Mask exception before entering assembly which does not expect to be 7158200af7SClément Léger * interrupted. 7258200af7SClément Léger */ 7358200af7SClément Léger thread_mask_exceptions(THREAD_EXCP_ALL); 7458200af7SClément Léger 7558200af7SClément Léger __atmel_shdwc_shutdown(mpddrc_base, shdwc_base, pmc_base); 7658200af7SClément Léger 7758200af7SClément Léger /* We are going to shutdown the CPU so we will never hit this loop */ 7858200af7SClément Léger while (true) 7958200af7SClément Léger ; 8058200af7SClément Léger } 8158200af7SClément Léger 8258200af7SClément Léger static const unsigned long long sdwc_dbc_period[] = { 8358200af7SClément Léger 0, 3, 32, 512, 4096, 32768, 8458200af7SClément Léger }; 8558200af7SClément Léger 8658200af7SClément Léger static uint32_t at91_shdwc_debouncer_value(uint32_t in_period_us) 8758200af7SClément Léger { 8858200af7SClément Léger int i = 0; 8958200af7SClément Léger int max_idx = ARRAY_SIZE(sdwc_dbc_period) - 1; 9058200af7SClément Léger uint64_t period_us = 0; 9158200af7SClément Léger uint64_t max_period_us = DBC_PERIOD_US(sdwc_dbc_period[max_idx]); 9258200af7SClément Léger 9358200af7SClément Léger if (in_period_us > max_period_us) { 9458200af7SClément Léger DMSG("debouncer period %"PRIu32" too big, using %"PRIu64" us", 9558200af7SClément Léger in_period_us, max_period_us); 9658200af7SClément Léger return max_idx; 9758200af7SClément Léger } 9858200af7SClément Léger 9958200af7SClément Léger for (i = max_idx - 1; i > 0; i--) { 10058200af7SClément Léger period_us = DBC_PERIOD_US(sdwc_dbc_period[i]); 10158200af7SClément Léger if (in_period_us > period_us) 10258200af7SClément Léger break; 10358200af7SClément Léger } 10458200af7SClément Léger 10558200af7SClément Léger return i + 1; 10658200af7SClément Léger } 10758200af7SClément Léger 10858200af7SClément Léger static uint32_t at91_shdwc_get_wakeup_input(const void *fdt, int np) 10958200af7SClément Léger { 11058200af7SClément Léger const uint32_t *prop = NULL; 11158200af7SClément Léger uint32_t wk_input_mask = 0; 11258200af7SClément Léger uint32_t wuir = 0; 11358200af7SClément Léger uint32_t wk_input = 0; 11458200af7SClément Léger int child = 0; 11558200af7SClément Léger int len = 0; 11658200af7SClément Léger 11758200af7SClément Léger fdt_for_each_subnode(child, fdt, np) { 11858200af7SClément Léger prop = fdt_getprop(fdt, child, "reg", &len); 11958200af7SClément Léger if (!prop || len != sizeof(uint32_t)) { 12058200af7SClément Léger DMSG("reg property is missing for node %s", 12158200af7SClément Léger fdt_get_name(fdt, child, NULL)); 12258200af7SClément Léger continue; 12358200af7SClément Léger } 12458200af7SClément Léger wk_input = fdt32_to_cpu(*prop); 12558200af7SClément Léger wk_input_mask = BIT32(wk_input); 12658200af7SClément Léger if (!(wk_input_mask & AT91_SHDW_WKUPEN_MASK)) { 12758200af7SClément Léger DMSG("wake-up input %"PRId32" out of bounds ignore", 12858200af7SClément Léger wk_input); 12958200af7SClément Léger continue; 13058200af7SClément Léger } 13158200af7SClément Léger wuir |= wk_input_mask; 13258200af7SClément Léger 13358200af7SClément Léger if (fdt_getprop(fdt, child, "atmel,wakeup-active-high", NULL)) 13458200af7SClément Léger wuir |= AT91_SHDW_WKUPT(wk_input); 13558200af7SClément Léger } 13658200af7SClément Léger 13758200af7SClément Léger return wuir; 13858200af7SClément Léger } 13958200af7SClément Léger 14058200af7SClément Léger static void at91_shdwc_dt_configure(const void *fdt, int np) 14158200af7SClément Léger { 14258200af7SClément Léger const uint32_t *prop = NULL; 14358200af7SClément Léger uint32_t mode = 0; 14458200af7SClément Léger uint32_t tmp = 0; 14558200af7SClément Léger uint32_t input = 0; 14658200af7SClément Léger int len = 0; 14758200af7SClément Léger 14858200af7SClément Léger prop = fdt_getprop(fdt, np, "debounce-delay-us", &len); 14958200af7SClément Léger if (prop && len == sizeof(uint32_t)) { 15058200af7SClément Léger tmp = fdt32_to_cpu(*prop); 15158200af7SClément Léger mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(tmp)); 15258200af7SClément Léger } 15358200af7SClément Léger 15458200af7SClément Léger if (fdt_getprop(fdt, np, "atmel,wakeup-rtc-timer", &len)) 15558200af7SClément Léger mode |= AT91_SHDW_RTCWKEN; 15658200af7SClément Léger 157*13d015f7STony Han if (fdt_getprop(fdt, np, "atmel,wakeup-rtt-timer", &len)) 158*13d015f7STony Han mode |= AT91_SHDW_RTTWKEN; 159*13d015f7STony Han 16058200af7SClément Léger io_write32(shdwc_base + AT91_SHDW_MR, mode); 16158200af7SClément Léger 16258200af7SClément Léger input = at91_shdwc_get_wakeup_input(fdt, np); 16358200af7SClément Léger io_write32(shdwc_base + AT91_SHDW_WUIR, input); 16458200af7SClément Léger } 16558200af7SClément Léger 16658200af7SClément Léger static TEE_Result atmel_shdwc_probe(const void *fdt, int node, 167f527a3b7STony Han const void *compat_data) 16858200af7SClément Léger { 16958200af7SClément Léger int ddr_node = 0; 17058200af7SClément Léger size_t size = 0; 17158200af7SClément Léger uint32_t ddr = AT91_DDRSDRC_MD_LPDDR2; 172f527a3b7STony Han struct shdwc_compat *compat = (struct shdwc_compat *)compat_data; 17358200af7SClément Léger 17458200af7SClément Léger /* 17558200af7SClément Léger * Assembly code relies on the fact that there is only one CPU to avoid 17658200af7SClément Léger * any other one to invalidate TLB/I-Cache. 17758200af7SClément Léger */ 17858200af7SClément Léger COMPILE_TIME_ASSERT(CFG_TEE_CORE_NB_CORE == 1); 17958200af7SClément Léger 180f354a5d8SGatien Chevallier if (fdt_get_status(fdt, node) != DT_STATUS_OK_SEC) 181c2daaa37SClément Léger return TEE_ERROR_BAD_PARAMETERS; 182c2daaa37SClément Léger 183f527a3b7STony Han if (!compat->shdwc_always_secure) 184c2daaa37SClément Léger matrix_configure_periph_secure(AT91C_ID_SYS); 185c2daaa37SClément Léger 186a5d5bbc8SVesa Jääskeläinen if (dt_map_dev(fdt, node, &shdwc_base, &size, DT_MAP_AUTO) < 0) 18758200af7SClément Léger return TEE_ERROR_GENERIC; 18858200af7SClément Léger 18958200af7SClément Léger ddr_node = fdt_node_offset_by_compatible(fdt, -1, 190a991d533STony Han compat->ddrc.compatible); 19158200af7SClément Léger if (ddr_node < 0) 19258200af7SClément Léger return TEE_ERROR_GENERIC; 19358200af7SClément Léger 194a5d5bbc8SVesa Jääskeläinen if (dt_map_dev(fdt, ddr_node, &mpddrc_base, &size, DT_MAP_AUTO) < 0) 19558200af7SClément Léger return TEE_ERROR_GENERIC; 19658200af7SClément Léger 197a991d533STony Han if (!compat->ddrc.type_mask) { 198f527a3b7STony Han ddr = io_read32(mpddrc_base + compat->ddrc.type_offset); 199f527a3b7STony Han ddr &= compat->ddrc.type_mask; 200a991d533STony Han if (ddr != AT91_DDRSDRC_MD_LPDDR2 && 201a991d533STony Han ddr != AT91_DDRSDRC_MD_LPDDR3) 20258200af7SClément Léger mpddrc_base = 0; 203a991d533STony Han } else { 204a991d533STony Han /* 205a991d533STony Han * Set the base to 0 as the code of DRAM controller for power 206a991d533STony Han * down is not implemented yet. 207a991d533STony Han */ 208a991d533STony Han mpddrc_base = 0; 209a991d533STony Han } 21058200af7SClément Léger 21158200af7SClément Léger at91_shdwc_dt_configure(fdt, node); 21258200af7SClément Léger 213d031d1ecSClément Léger return sama5d2_pm_init(fdt, shdwc_base); 21458200af7SClément Léger } 21558200af7SClément Léger 216f527a3b7STony Han static const struct shdwc_compat sama5d2_compat = { 217f527a3b7STony Han .shdwc_always_secure = false, 218f527a3b7STony Han .ddrc = { 219f527a3b7STony Han .type_offset = AT91_DDRSDRC_MDR, 220f527a3b7STony Han .type_mask = AT91_DDRSDRC_MD, 221a991d533STony Han .compatible = "atmel,sama5d3-ddramc", 222f527a3b7STony Han } 223f527a3b7STony Han }; 224f527a3b7STony Han 225f527a3b7STony Han static const struct shdwc_compat sama7g5_compat = { 226f527a3b7STony Han .shdwc_always_secure = true, 227a991d533STony Han .ddrc = { 228a991d533STony Han .compatible = "microchip,sama7g5-uddrc", 229a991d533STony Han } 230f527a3b7STony Han }; 231f527a3b7STony Han 23258200af7SClément Léger static const struct dt_device_match atmel_shdwc_match_table[] = { 233f527a3b7STony Han { 234f527a3b7STony Han .compatible = "atmel,sama5d2-shdwc", 235f527a3b7STony Han .compat_data = &sama5d2_compat 236f527a3b7STony Han }, 237f527a3b7STony Han { 238f527a3b7STony Han .compatible = "microchip,sama7g5-shdwc", 239f527a3b7STony Han .compat_data = &sama7g5_compat, 240f527a3b7STony Han }, 24158200af7SClément Léger { } 24258200af7SClément Léger }; 24358200af7SClément Léger 24461bdedeaSJerome Forissier DEFINE_DT_DRIVER(atmel_shdwc_dt_driver) = { 24558200af7SClément Léger .name = "atmel_shdwc", 24658200af7SClément Léger .type = DT_DRIVER_NOTYPE, 24758200af7SClément Léger .match_table = atmel_shdwc_match_table, 24858200af7SClément Léger .probe = atmel_shdwc_probe, 24958200af7SClément Léger }; 250