aboutsummaryrefslogtreecommitdiff
path: root/src/soc/mediatek/mt8183/spm.c
diff options
context:
space:
mode:
authorDawei Chien <dawei.chien@mediatek.com>2019-05-30 11:55:10 +0800
committerPatrick Georgi <pgeorgi@google.com>2019-09-30 11:38:42 +0000
commit2422f8c21e4590dcf75239583099a467f79f9878 (patch)
tree4034a79d2b5a531e89106c30a4420f6d69bf4fa6 /src/soc/mediatek/mt8183/spm.c
parent45ffee83464156a58a57570b22ba35ab2859e532 (diff)
mediatek/mt8183: Init SPM driver
To support mt8183 power saving during suspend to RAM, this patch loads SPM firmware to support SPM suspend. SPM needs its own firmware to do these power saving in the right timing under correct conditions. After linux PM suspends, SPM is able to turn off power for the last CPU and do more power saving for the SoC such as DRAM self-refresh mode and turning off 26M crystal. BUG=none BRANCH=none TEST=suspend/resume passes for LPDDR4 3200 Change-Id: I3393a772f025b0912a5a25a63a87512454fbc86e Signed-off-by: Dawei Chien <dawei.chien@mediatek.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/34545 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Yu-Ping Wu <yupingso@google.com> Reviewed-by: Hung-Te Lin <hungte@chromium.org>
Diffstat (limited to 'src/soc/mediatek/mt8183/spm.c')
-rw-r--r--src/soc/mediatek/mt8183/spm.c351
1 files changed, 351 insertions, 0 deletions
diff --git a/src/soc/mediatek/mt8183/spm.c b/src/soc/mediatek/mt8183/spm.c
new file mode 100644
index 0000000000..669970fb2f
--- /dev/null
+++ b/src/soc/mediatek/mt8183/spm.c
@@ -0,0 +1,351 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2019 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <assert.h>
+#include <cbfs.h>
+#include <delay.h>
+#include <device/mmio.h>
+#include <endian.h>
+#include <soc/emi.h>
+#include <soc/spm.h>
+#include <timer.h>
+
+#define BUF_SIZE (16 * KiB)
+static uint8_t spm_bin[BUF_SIZE] __aligned(8);
+
+static int spm_register_init(void)
+{
+ u32 pcm_fsm_sta;
+
+ write32(&mtk_spm->poweron_config_set,
+ SPM_REGWR_CFG_KEY | BCLK_CG_EN_LSB | MD_BCLK_CG_EN_LSB);
+
+ write32(&mtk_spm->spm_power_on_val1, POWER_ON_VAL1_DEF);
+ write32(&mtk_spm->pcm_pwr_io_en, 0);
+
+ write32(&mtk_spm->pcm_con0, SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB |
+ PCM_SW_RESET_LSB);
+ write32(&mtk_spm->pcm_con0, SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB);
+
+ pcm_fsm_sta = read32(&mtk_spm->pcm_fsm_sta);
+
+ if ((pcm_fsm_sta & PCM_FSM_STA_MASK) != PCM_FSM_STA_DEF) {
+ printk(BIOS_ERR, "PCM reset failed\n");
+ return -1;
+ }
+
+ write32(&mtk_spm->pcm_con0, SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB |
+ EN_IM_SLEEP_DVS_LSB);
+ write32(&mtk_spm->pcm_con1, SPM_REGWR_CFG_KEY | EVENT_LOCK_EN_LSB |
+ SPM_SRAM_ISOINT_B_LSB | MIF_APBEN_LSB |
+ SCP_APB_INTERNAL_EN_LSB);
+ write32(&mtk_spm->pcm_im_ptr, 0);
+ write32(&mtk_spm->pcm_im_len, 0);
+
+ write32(&mtk_spm->spm_clk_con,
+ read32(&mtk_spm->spm_clk_con) | SYSCLK1_EN_CTRL |
+ SPM_LOCK_INFRA_DCM_LSB | EXT_SRCCLKEN_MASK |
+ CXO32K_REMOVE_EN_MD1_LSB |
+ CLKSQ1_SEL_CTRL_LSB | SRCLKEN0_EN_LSB | SYSCLK1_SRC_MASK_B);
+
+ write32(&mtk_spm->spm_wakeup_event_mask, SPM_WAKEUP_EVENT_MASK_DEF);
+
+ write32(&mtk_spm->spm_irq_mask, ISRM_ALL);
+ write32(&mtk_spm->spm_irq_sta, ISRC_ALL);
+ write32(&mtk_spm->spm_swint_clr, PCM_SW_INT_ALL);
+
+ write32(&mtk_spm->pcm_reg_data_ini,
+ read32(&mtk_spm->spm_power_on_val1));
+ write32(&mtk_spm->pcm_pwr_io_en, PCM_RF_SYNC_R7);
+ write32(&mtk_spm->pcm_pwr_io_en, 0);
+
+ write32(&mtk_spm->ddr_en_dbc_len,
+ MD_DDR_EN_0_DBC_LEN |
+ MD_DDR_EN_1_DBC_LEN |
+ CONN_DDR_EN_DBC_LEN);
+
+ clrsetbits_le32(&mtk_spm->spare_ack_mask,
+ SPARE_ACK_MASK_B_BIT1,
+ SPARE_ACK_MASK_B_BIT0);
+
+ write32(&mtk_spm->sysrom_con, IFR_SRAMROM_ROM_PDN);
+ write32(&mtk_spm->spm_pc_trace_con,
+ SPM_PC_TRACE_OFFSET |
+ SPM_PC_TRACE_HW_EN_LSB);
+
+ setbits_le32(&mtk_spm->spare_src_req_mask, SPARE1_DDREN_MASK_B_LSB);
+
+ return 0;
+}
+
+static int spm_code_swapping(void)
+{
+ u32 con1;
+
+ con1 = read32(&mtk_spm->spm_wakeup_event_mask);
+
+ write32(&mtk_spm->spm_wakeup_event_mask,
+ con1 & ~WAKEUP_EVENT_MASK_B_BIT0);
+ write32(&mtk_spm->spm_cpu_wakeup_event, 1);
+
+ if (!wait_us(SPM_CORE_TIMEOUT,
+ read32(&mtk_spm->spm_irq_sta) & PCM_IRQ_ROOT_MASK_LSB)) {
+ printk(BIOS_ERR,
+ "timeout: r15=%#x, pcmsta=%#x, irqsta=%#x [%d]\n",
+ read32(&mtk_spm->pcm_reg15_data),
+ read32(&mtk_spm->pcm_fsm_sta),
+ read32(&mtk_spm->spm_irq_sta),
+ SPM_CORE_TIMEOUT);
+ return -1;
+ }
+
+ write32(&mtk_spm->spm_cpu_wakeup_event, 0);
+ write32(&mtk_spm->spm_wakeup_event_mask, con1);
+ return 0;
+}
+
+static int spm_reset_and_init_pcm(const struct pcm_desc *pcmdesc)
+{
+ u32 con1, pcm_fsm_sta;
+
+ if (read32(&mtk_spm->pcm_reg1_data) == SPM_PCM_REG1_DATA_CHECK &&
+ read32(&mtk_spm->pcm_reg15_data) != SPM_PCM_REG15_DATA_CHECK) {
+ if (spm_code_swapping())
+ return -1;
+ write32(&mtk_spm->spm_power_on_val0,
+ read32(&mtk_spm->pcm_reg0_data));
+ }
+
+ write32(&mtk_spm->pcm_pwr_io_en, 0);
+
+ clrsetbits_le32(&mtk_spm->pcm_con1,
+ PCM_TIMER_EN_LSB,
+ SPM_REGWR_CFG_KEY);
+
+ write32(&mtk_spm->pcm_con0, SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB |
+ PCM_SW_RESET_LSB);
+ write32(&mtk_spm->pcm_con0, SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB);
+
+ pcm_fsm_sta = read32(&mtk_spm->pcm_fsm_sta);
+
+ if ((pcm_fsm_sta & PCM_FSM_STA_MASK) != PCM_FSM_STA_DEF) {
+ printk(BIOS_ERR, "reset pcm(PCM_FSM_STA=%#x)\n",
+ read32(&mtk_spm->pcm_fsm_sta));
+ return -1;
+ }
+
+ write32(&mtk_spm->pcm_con0, SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB |
+ EN_IM_SLEEP_DVS_LSB);
+
+ con1 = read32(&mtk_spm->pcm_con1) & PCM_WDT_WAKE_MODE_LSB;
+ write32(&mtk_spm->pcm_con1, con1 | SPM_REGWR_CFG_KEY |
+ EVENT_LOCK_EN_LSB | SPM_SRAM_ISOINT_B_LSB |
+ (pcmdesc->replace ? 0 : IM_NONRP_EN_LSB) |
+ MIF_APBEN_LSB | SCP_APB_INTERNAL_EN_LSB);
+
+ return 0;
+}
+
+static void spm_load_pcm_code(const struct dyna_load_pcm *pcm)
+{
+ int i;
+
+ write32(&mtk_spm->pcm_con1, read32(&mtk_spm->pcm_con1) |
+ SPM_REGWR_CFG_KEY | IM_SLAVE_LSB);
+
+ for (i = 0; i < pcm->desc.size; i++) {
+ write32(&mtk_spm->pcm_im_host_rw_ptr,
+ PCM_IM_HOST_EN_LSB | PCM_IM_HOST_W_EN_LSB | i);
+ write32(&mtk_spm->pcm_im_host_rw_dat,
+ (u32) *(pcm->buf + i));
+ }
+ write32(&mtk_spm->pcm_im_host_rw_ptr, 0);
+}
+
+static void spm_check_pcm_code(const struct dyna_load_pcm *pcm)
+{
+ int i;
+
+ for (i = 0; i < pcm->desc.size; i++) {
+ write32(&mtk_spm->pcm_im_host_rw_ptr, PCM_IM_HOST_EN_LSB | i);
+ if ((read32(&mtk_spm->pcm_im_host_rw_dat)) !=
+ (u32) *(pcm->buf + i))
+ spm_load_pcm_code(pcm);
+ }
+ write32(&mtk_spm->pcm_im_host_rw_ptr, 0);
+}
+
+static void spm_kick_im_to_fetch(const struct dyna_load_pcm *pcm)
+{
+ u32 con0;
+
+ spm_load_pcm_code(pcm);
+ spm_check_pcm_code(pcm);
+
+ printk(BIOS_DEBUG, "%s: ptr = %p\n", __func__, pcm->buf);
+ printk(BIOS_DEBUG, "%s: len = %d\n", __func__, pcm->desc.size);
+
+ con0 = read32(&mtk_spm->pcm_con0) & ~(IM_KICK_L_LSB | PCM_KICK_L_LSB);
+ write32(&mtk_spm->pcm_con0, con0 | SPM_REGWR_CFG_KEY |
+ PCM_CK_EN_LSB | IM_KICK_L_LSB);
+ write32(&mtk_spm->pcm_con0, con0 | SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB);
+}
+
+static void spm_init_pcm_register(void)
+{
+ write32(&mtk_spm->pcm_reg_data_ini,
+ read32(&mtk_spm->spm_power_on_val0));
+ write32(&mtk_spm->pcm_pwr_io_en, PCM_RF_SYNC_R0);
+ write32(&mtk_spm->pcm_pwr_io_en, 0);
+
+ write32(&mtk_spm->pcm_reg_data_ini,
+ read32(&mtk_spm->spm_power_on_val1));
+ write32(&mtk_spm->pcm_pwr_io_en, PCM_RF_SYNC_R7);
+ write32(&mtk_spm->pcm_pwr_io_en, 0);
+}
+
+static void spm_init_event_vector(const struct pcm_desc *pcmdesc)
+{
+ for (int i = 0; i < PCM_EVENT_VECTOR_NUM; i++)
+ write32(&mtk_spm->pcm_event_vector[i], pcmdesc->vector[i]);
+}
+
+static const char * const dyna_load_pcm_path[] = {
+ [DYNA_LOAD_PCM_SUSPEND_LP4_3733] = "pcm_allinone_lp4_3733.bin",
+ [DYNA_LOAD_PCM_SUSPEND_LP4_3200] = "pcm_allinone_lp4_3200.bin",
+};
+
+static int spm_load_firmware(enum dyna_load_pcm_index index,
+ struct dyna_load_pcm *pcm)
+{
+ /*
+ * Layout:
+ * u16 firmware_size
+ * u32 binary[firmware_size]
+ * struct pcm_desc descriptor
+ * char *version
+ */
+ u16 firmware_size;
+ int copy_size;
+ const char *file_name = dyna_load_pcm_path[index];
+ struct stopwatch sw;
+
+ stopwatch_init(&sw);
+
+ size_t file_size = cbfs_boot_load_file(file_name, spm_bin,
+ sizeof(spm_bin), CBFS_TYPE_RAW);
+
+ if (file_size == 0) {
+ printk(BIOS_ERR, "SPM binary %s not found\n", file_name);
+ return -1;
+ }
+
+ int offset = 0;
+
+ /* firmware size */
+ copy_size = sizeof(firmware_size);
+ memcpy(&firmware_size, spm_bin + offset, copy_size);
+ printk(BIOS_DEBUG, "SPM: binary array size = %d\n", firmware_size);
+ offset += copy_size;
+
+ /* binary */
+ assert(offset < file_size);
+ copy_size = firmware_size * 4;
+ pcm->buf = (u32 *)(spm_bin + offset);
+ offset += copy_size;
+
+ /* descriptor */
+ assert(offset < file_size);
+ copy_size = sizeof(struct pcm_desc);
+ memcpy((void *)&(pcm->desc.size), spm_bin + offset, copy_size);
+ offset += copy_size;
+
+ /* version */
+ /* The termintating character should be contained in the spm binary */
+ assert(spm_bin[file_size - 1] == '\0');
+ assert(offset < file_size);
+ printk(BIOS_DEBUG, "SPM: version = %s\n", spm_bin + offset);
+
+ printk(BIOS_INFO, "SPM binary loaded in %ld msecs\n",
+ stopwatch_duration_msecs(&sw));
+
+ return 0;
+}
+
+static void spm_kick_pcm_to_run(void)
+{
+ uint32_t con0;
+
+ write32(&mtk_spm->spm_mas_pause_mask_b, SPM_MAS_PAUSE_MASK_B_VAL);
+ write32(&mtk_spm->spm_mas_pause2_mask_b, SPM_MAS_PAUSE2_MASK_B_VAL);
+ write32(&mtk_spm->pcm_reg_data_ini, 0);
+
+ write32(&mtk_spm->pcm_pwr_io_en, PCM_PWRIO_EN_R0 | PCM_PWRIO_EN_R7);
+
+ printk(BIOS_DEBUG, "SPM: %s\n", __func__);
+
+ /* check IM ready */
+ while ((read32(&mtk_spm->pcm_fsm_sta) & IM_STATE_MASK) != IM_STATE)
+ ;
+
+ /* kick PCM to run, and toggle PCM_KICK */
+ con0 = read32(&mtk_spm->pcm_con0) & ~(IM_KICK_L_LSB | PCM_KICK_L_LSB);
+ write32(&mtk_spm->pcm_con0, con0 | SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB |
+ PCM_KICK_L_LSB);
+ write32(&mtk_spm->pcm_con0, con0 | SPM_REGWR_CFG_KEY | PCM_CK_EN_LSB);
+
+ printk(BIOS_DEBUG, "SPM: %s done\n", __func__);
+}
+
+int spm_init(void)
+{
+ struct pcm_desc *pcmdesc;
+ enum dyna_load_pcm_index index;
+ struct stopwatch sw;
+
+ stopwatch_init(&sw);
+
+ if (CONFIG(MT8183_DRAM_EMCP))
+ index = DYNA_LOAD_PCM_SUSPEND_LP4_3733;
+ else
+ index = DYNA_LOAD_PCM_SUSPEND_LP4_3200;
+
+ printk(BIOS_DEBUG, "SPM: pcm index = %d\n", index);
+
+ struct dyna_load_pcm pcm;
+ if (spm_load_firmware(index, &pcm)) {
+ printk(BIOS_ERR, "SPM: firmware is not ready\n");
+ printk(BIOS_ERR, "SPM: check dram type and firmware version\n");
+ return -1;
+ }
+
+ pcmdesc = &pcm.desc;
+
+ if (spm_register_init())
+ return -1;
+
+ if (spm_reset_and_init_pcm(pcmdesc))
+ return -1;
+
+ spm_kick_im_to_fetch(&pcm);
+ spm_init_pcm_register();
+ spm_init_event_vector(pcmdesc);
+ spm_kick_pcm_to_run();
+
+ printk(BIOS_INFO, "SPM: %s done in %ld msecs\n", __func__,
+ stopwatch_duration_msecs(&sw));
+
+ return 0;
+}