// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2021 MediaTek Inc. * Author: Yuan Jung Kuo */ #include #include #include #include #include #include #include #define ISPDVFS_DBG #ifdef ISPDVFS_DBG #define ISP_LOGD(fmt, args...) \ do { \ if (mtk_ispdvfs_dbg_level) \ pr_notice("[ISPDVFS] %s(): " fmt "\n",\ __func__, ##args); \ } while (0) #else #define ISPDVFS_DBG(fmt, args...) #endif #define ISP_LOGI(fmt, args...) \ pr_notice("[ISPDVFS] %s(): " fmt "\n", \ __func__, ##args) #define ISP_LOGE(fmt, args...) \ pr_notice("[ISPDVFS] error %s(),%d: " fmt "\n", \ __func__, __LINE__, ##args) #define ISP_LOGF(fmt, args...) \ pr_notice("[ISPDVFS] fatal %s(),%d: " fmt "\n", \ __func__, __LINE__, ##args) #define SPM_VMM_HW_SEM_REG_OFST 0x6A8 #define SPM_VMM_ISO_REG_OFST 0xF30 #define SPM_VMM_ISO_REG_OFST_MT6886 0xF74 #define SPM_VMM_EXT_BUCK_ISO_BIT 16 #define SPM_AOC_VMM_SRAM_ISO_DIN_BIT 17 #define SPM_AOC_VMM_SRAM_LATCH_ENB 18 #define SPM_HW_SEM_TIMEOUT_RUN 5000 #define SPM_HW_SEM_SET_DELAY_US 10 #define SPM_VMM_HW_SEM_REG_OFST_M0 0x69C #define SPM_VMM_HW_SEM_REG_OFST_M1 0x6A0 #define SPM_VMM_HW_SEM_REG_OFST_M2 0x6A4 #define SPM_VMM_HW_SEM_REG_OFST_M3 0x6A8 #define SPM_VMM_HW_SEM_REG_OFST_M4 0x6AC #define SPM_VMM_HW_SEM_REG_OFST_M5 0x6B0 #define SPM_VMM_HW_SEM_REG_OFST_M6 0x6B4 #define SPM_VMM_HW_SEM_REG_OFST_M7 0x6B8 /* Buck ISO control for 7s */ #define SPM_VMM_EXT_BUCK_ISO_BIT_7S 20 #define SPM_AOC_VMM_SRAM_ISO_DIN_BIT_7S 21 #define SPM_AOC_VMM_SRAM_LATCH_ENB_7S 22 #define SOC_BUCK_ISO_CON_SET 0xF7C #define SOC_BUCK_ISO_CON_CLR 0xF80 struct vmm_spm_drv_data { void __iomem *spm_reg; struct notifier_block nb; }; static struct vmm_spm_drv_data g_drv_data; static void vmm_dump_spm_sem_reg(void __iomem *base) { ISP_LOGE("SPM_VMM_HW_SEM_REG_OFST_M0 0x(%x)\n", readl_relaxed(base + SPM_VMM_HW_SEM_REG_OFST_M0)); ISP_LOGE("SPM_VMM_HW_SEM_REG_OFST_M1 0x(%x)\n", readl_relaxed(base + SPM_VMM_HW_SEM_REG_OFST_M1)); ISP_LOGE("SPM_VMM_HW_SEM_REG_OFST_M2 0x(%x)\n", readl_relaxed(base + SPM_VMM_HW_SEM_REG_OFST_M2)); ISP_LOGE("SPM_VMM_HW_SEM_REG_OFST_M3 0x(%x)\n", readl_relaxed(base + SPM_VMM_HW_SEM_REG_OFST_M3)); ISP_LOGE("SPM_VMM_HW_SEM_REG_OFST_M4 0x(%x)\n", readl_relaxed(base + SPM_VMM_HW_SEM_REG_OFST_M4)); ISP_LOGE("SPM_VMM_HW_SEM_REG_OFST_M5 0x(%x)\n", readl_relaxed(base + SPM_VMM_HW_SEM_REG_OFST_M5)); ISP_LOGE("SPM_VMM_HW_SEM_REG_OFST_M6 0x(%x)\n", readl_relaxed(base + SPM_VMM_HW_SEM_REG_OFST_M6)); ISP_LOGE("SPM_VMM_HW_SEM_REG_OFST_M7 0x(%x)\n", readl_relaxed(base + SPM_VMM_HW_SEM_REG_OFST_M7)); } static int acquire_hw_semaphore(void __iomem *hw_sem_addr, unsigned long *pFlags) { u32 hw_sem; int i = 1; unsigned long flags; ISP_LOGI("VMM will try to acquire hw sem"); for (;;) { local_irq_save(flags); hw_sem = readl_relaxed(hw_sem_addr); hw_sem |= 0x1; writel_relaxed(hw_sem, hw_sem_addr); udelay(SPM_HW_SEM_SET_DELAY_US); /* *hw_sem_addr == 0x1 means acquire succeed */ if (readl_relaxed(hw_sem_addr) & 0x1) break; local_irq_restore(flags); if (i++ > SPM_HW_SEM_TIMEOUT_RUN) { ISP_LOGE("Acquire HW SEM timeout"); return -ETIMEDOUT; } } *pFlags = flags; return 0; } static void release_hw_semaphore(void __iomem *hw_sem_addr, unsigned long *pFlags) { u32 hw_sem; hw_sem = readl_relaxed(hw_sem_addr); hw_sem |= 0x1; writel_relaxed(hw_sem, hw_sem_addr); udelay(SPM_HW_SEM_SET_DELAY_US); if (readl_relaxed(hw_sem_addr) & 0x1) goto error_out; local_irq_restore(*pFlags); return; error_out: local_irq_restore(*pFlags); ISP_LOGE("Release HW SEM FAIL"); BUG_ON(1); } static void vmm_buck_isolation_off(void __iomem *base) { void __iomem *reg_buck_iso_addr = base + SPM_VMM_ISO_REG_OFST; void __iomem *hw_sem_addr = base + SPM_VMM_HW_SEM_REG_OFST; u32 reg_buck_iso_val; unsigned long flags; if (acquire_hw_semaphore(hw_sem_addr, &flags)) { vmm_dump_spm_sem_reg(base); BUG_ON(1); return; } reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val &= ~(1UL << SPM_VMM_EXT_BUCK_ISO_BIT); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val &= ~(1UL << SPM_AOC_VMM_SRAM_ISO_DIN_BIT); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val &= ~(1UL << SPM_AOC_VMM_SRAM_LATCH_ENB); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); release_hw_semaphore(hw_sem_addr, &flags); } static void vmm_buck_isolation_on(void __iomem *base) { void __iomem *reg_buck_iso_addr = base + SPM_VMM_ISO_REG_OFST; void __iomem *hw_sem_addr = base + SPM_VMM_HW_SEM_REG_OFST; u32 reg_buck_iso_val; unsigned long flags; if (acquire_hw_semaphore(hw_sem_addr, &flags)) { vmm_dump_spm_sem_reg(base); BUG_ON(1); return; } reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val |= (1UL << SPM_AOC_VMM_SRAM_LATCH_ENB); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val |= (1UL << SPM_AOC_VMM_SRAM_ISO_DIN_BIT); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val |= (1UL << SPM_VMM_EXT_BUCK_ISO_BIT); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); release_hw_semaphore(hw_sem_addr, &flags); } static int regulator_event_notify(struct notifier_block *nb, unsigned long event, void *data) { struct vmm_spm_drv_data *drv_data = &g_drv_data; if (!drv_data->spm_reg) { ISP_LOGE("SPM_BASE is NULL"); return -EINVAL; } if (event == REGULATOR_EVENT_ENABLE) { vmm_buck_isolation_off(drv_data->spm_reg); ISP_LOGI("VMM regulator enable done"); } else if (event == REGULATOR_EVENT_PRE_DISABLE) { vmm_buck_isolation_on(drv_data->spm_reg); ISP_LOGI("VMM regulator before disable"); } return 0; } static void vmm_buck_isolation_off_mt6886(void __iomem *base) { void __iomem *reg_buck_iso_addr = base + SPM_VMM_ISO_REG_OFST_MT6886; void __iomem *hw_sem_addr = base + SPM_VMM_HW_SEM_REG_OFST; u32 reg_buck_iso_val; unsigned long flags; if (acquire_hw_semaphore(hw_sem_addr, &flags)) { vmm_dump_spm_sem_reg(base); BUG_ON(1); return; } reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val &= ~(1UL << SPM_VMM_EXT_BUCK_ISO_BIT_7S); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val &= ~(1UL << SPM_AOC_VMM_SRAM_ISO_DIN_BIT_7S); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val &= ~(1UL << SPM_AOC_VMM_SRAM_LATCH_ENB_7S); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); release_hw_semaphore(hw_sem_addr, &flags); } static void vmm_buck_isolation_on_mt6886(void __iomem *base) { void __iomem *reg_buck_iso_addr = base + SPM_VMM_ISO_REG_OFST_MT6886; void __iomem *hw_sem_addr = base + SPM_VMM_HW_SEM_REG_OFST; u32 reg_buck_iso_val; unsigned long flags; if (acquire_hw_semaphore(hw_sem_addr, &flags)) { vmm_dump_spm_sem_reg(base); BUG_ON(1); return; } reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val |= (1UL << SPM_AOC_VMM_SRAM_LATCH_ENB_7S); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val |= (1UL << SPM_AOC_VMM_SRAM_ISO_DIN_BIT_7S); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); reg_buck_iso_val = readl_relaxed(reg_buck_iso_addr); reg_buck_iso_val |= (1UL << SPM_VMM_EXT_BUCK_ISO_BIT_7S); writel_relaxed(reg_buck_iso_val, reg_buck_iso_addr); release_hw_semaphore(hw_sem_addr, &flags); } static int regulator_event_notify_mt6886(struct notifier_block *nb, unsigned long event, void *data) { struct vmm_spm_drv_data *drv_data = &g_drv_data; if (!drv_data->spm_reg) { ISP_LOGE("SPM_BASE is NULL"); return -EINVAL; } if (event == REGULATOR_EVENT_ENABLE) { vmm_buck_isolation_off_mt6886(drv_data->spm_reg); ISP_LOGI("VMM regulator enable done"); } else if (event == REGULATOR_EVENT_PRE_DISABLE) { vmm_buck_isolation_on_mt6886(drv_data->spm_reg); ISP_LOGI("VMM regulator before disable"); } return 0; } static void vmm_buck_isolation_off_7s(void __iomem *base) { void __iomem *addr = base + SOC_BUCK_ISO_CON_CLR; writel_relaxed((1 << SPM_VMM_EXT_BUCK_ISO_BIT_7S), addr); writel_relaxed((1 << SPM_AOC_VMM_SRAM_ISO_DIN_BIT_7S), addr); writel_relaxed((1 << SPM_AOC_VMM_SRAM_LATCH_ENB_7S), addr); } static void vmm_buck_isolation_on_7s(void __iomem *base) { void __iomem *addr = base + SOC_BUCK_ISO_CON_SET; writel_relaxed((1 << SPM_AOC_VMM_SRAM_LATCH_ENB_7S), addr); writel_relaxed((1 << SPM_AOC_VMM_SRAM_ISO_DIN_BIT_7S), addr); writel_relaxed((1 << SPM_VMM_EXT_BUCK_ISO_BIT_7S), addr); } static int regulator_event_notify_7s(struct notifier_block *nb, unsigned long event, void *data) { struct vmm_spm_drv_data *drv_data = &g_drv_data; if (!drv_data->spm_reg) { ISP_LOGE("SPM_BASE is NULL"); return -EINVAL; } if (event == REGULATOR_EVENT_ENABLE) { vmm_buck_isolation_off_7s(drv_data->spm_reg); ISP_LOGI("VMM 7s regulator enable done"); } else if (event == REGULATOR_EVENT_PRE_DISABLE) { vmm_buck_isolation_on_7s(drv_data->spm_reg); ISP_LOGI("VMM 7s regulator before disable"); } return 0; } static int vmm_spm_probe(struct platform_device *pdev) { struct vmm_spm_drv_data *drv_data = &g_drv_data; struct device *dev = &pdev->dev; struct resource *res; struct regulator *reg; s32 ret; /* SPM registers */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ISP_LOGE("fail to get resource SPM_BASE"); return -EINVAL; } drv_data->spm_reg = devm_ioremap(dev, res->start, resource_size(res)); if (!(drv_data->spm_reg)) { ISP_LOGE("fail to ioremap SPM_BASE: 0x%llx", res->start); return -EINVAL; } reg = devm_regulator_get(dev, "vmm-pmic"); if (IS_ERR(reg)) { ISP_LOGE("devm_regulator_get vmm-pmic fail"); return PTR_ERR(reg); } if (of_device_is_compatible(dev->of_node, "mediatek,vmm_spm_7s")) drv_data->nb.notifier_call = regulator_event_notify_7s; else if (of_device_is_compatible(dev->of_node, "mediatek,vmm_spm_mt6886")) drv_data->nb.notifier_call = regulator_event_notify_mt6886; else drv_data->nb.notifier_call = regulator_event_notify; ret = devm_regulator_register_notifier(reg, &drv_data->nb); if (ret) ISP_LOGE("Failed to register notifier: %d\n", ret); return ret; } static const struct of_device_id of_vmm_spm_match_tbl[] = { { .compatible = "mediatek,vmm_spm", }, { .compatible = "mediatek,vmm_spm_7s", }, { .compatible = "mediatek,vmm_spm_mt6886", }, {} }; static struct platform_driver drv_vmm_spm = { .probe = vmm_spm_probe, .driver = { .name = "mtk-vmm-spm", .of_match_table = of_vmm_spm_match_tbl, }, }; static int __init mtk_vmm_spm_init(void) { s32 status; status = platform_driver_register(&drv_vmm_spm); if (status) { pr_notice("Failed to register VMM SPM driver(%d)\n", status); return -ENODEV; } return 0; } static void __exit mtk_vmm_spm_exit(void) { platform_driver_unregister(&drv_vmm_spm); } module_init(mtk_vmm_spm_init); module_exit(mtk_vmm_spm_exit); MODULE_DESCRIPTION("MTK VMM SPM driver"); MODULE_AUTHOR("Yuan Jung Kuo "); MODULE_LICENSE("GPL v2");