418 lines
11 KiB
C
418 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2021 MediaTek Inc.
|
|
* Author: Yuan Jung Kuo <yuan-jung.kuo@mediatek.com>
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/irqflags.h>
|
|
|
|
#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 <yuan-jung.kuo@mediatek.com>");
|
|
MODULE_LICENSE("GPL v2");
|