lkml.org 
[lkml]   [2022]   [Jan]   [7]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v21 3/8] soc: mediatek: SVS: introduce MTK SVS engine
    Date
    The Smart Voltage Scaling(SVS) engine is a piece of hardware
    which calculates suitable SVS bank voltages to OPP voltage table.
    Then, DVFS driver could apply those SVS bank voltages to PMIC/Buck
    when receiving OPP_EVENT_ADJUST_VOLTAGE.

    Signed-off-by: Roger Lu <roger.lu@mediatek.com>
    ---
    drivers/soc/mediatek/Kconfig | 10 +
    drivers/soc/mediatek/Makefile | 1 +
    drivers/soc/mediatek/mtk-svs.c | 1446 ++++++++++++++++++++++++++++++++
    3 files changed, 1457 insertions(+)
    create mode 100644 drivers/soc/mediatek/mtk-svs.c

    diff --git a/drivers/soc/mediatek/Kconfig b/drivers/soc/mediatek/Kconfig
    index fdd8bc08569e..3c3eedea35f7 100644
    --- a/drivers/soc/mediatek/Kconfig
    +++ b/drivers/soc/mediatek/Kconfig
    @@ -73,4 +73,14 @@ config MTK_MMSYS
    Say yes here to add support for the MediaTek Multimedia
    Subsystem (MMSYS).

    +config MTK_SVS
    + tristate "MediaTek Smart Voltage Scaling(SVS)"
    + depends on MTK_EFUSE && NVMEM
    + help
    + The Smart Voltage Scaling(SVS) engine is a piece of hardware
    + which has several controllers(banks) for calculating suitable
    + voltage to different power domains(CPU/GPU/CCI) according to
    + chip process corner, temperatures and other factors. Then DVFS
    + driver could apply SVS bank voltage to PMIC/Buck.
    +
    endmenu
    diff --git a/drivers/soc/mediatek/Makefile b/drivers/soc/mediatek/Makefile
    index 90270f8114ed..0e9e703c931a 100644
    --- a/drivers/soc/mediatek/Makefile
    +++ b/drivers/soc/mediatek/Makefile
    @@ -7,3 +7,4 @@ obj-$(CONFIG_MTK_SCPSYS) += mtk-scpsys.o
    obj-$(CONFIG_MTK_SCPSYS_PM_DOMAINS) += mtk-pm-domains.o
    obj-$(CONFIG_MTK_MMSYS) += mtk-mmsys.o
    obj-$(CONFIG_MTK_MMSYS) += mtk-mutex.o
    +obj-$(CONFIG_MTK_SVS) += mtk-svs.o
    diff --git a/drivers/soc/mediatek/mtk-svs.c b/drivers/soc/mediatek/mtk-svs.c
    new file mode 100644
    index 000000000000..fc7e2ee44a92
    --- /dev/null
    +++ b/drivers/soc/mediatek/mtk-svs.c
    @@ -0,0 +1,1446 @@
    +// SPDX-License-Identifier: GPL-2.0-only
    +/*
    + * Copyright (C) 2020 MediaTek Inc.
    + */
    +
    +#include <linux/bits.h>
    +#include <linux/clk.h>
    +#include <linux/completion.h>
    +#include <linux/device.h>
    +#include <linux/init.h>
    +#include <linux/interrupt.h>
    +#include <linux/kernel.h>
    +#include <linux/kthread.h>
    +#include <linux/module.h>
    +#include <linux/mutex.h>
    +#include <linux/nvmem-consumer.h>
    +#include <linux/of_address.h>
    +#include <linux/of_irq.h>
    +#include <linux/of_platform.h>
    +#include <linux/platform_device.h>
    +#include <linux/pm_domain.h>
    +#include <linux/pm_opp.h>
    +#include <linux/pm_qos.h>
    +#include <linux/pm_runtime.h>
    +#include <linux/regulator/consumer.h>
    +#include <linux/slab.h>
    +#include <linux/spinlock.h>
    +
    +/* svs bank 1-line sw id */
    +#define SVSB_CPU_LITTLE BIT(0)
    +#define SVSB_CPU_BIG BIT(1)
    +#define SVSB_CCI BIT(2)
    +#define SVSB_GPU BIT(3)
    +
    +/* svs bank mode support */
    +#define SVSB_MODE_ALL_DISABLE 0
    +#define SVSB_MODE_INIT01 BIT(1)
    +#define SVSB_MODE_INIT02 BIT(2)
    +
    +/* svs bank volt flags */
    +#define SVSB_INIT01_PD_REQ BIT(0)
    +#define SVSB_INIT01_VOLT_IGNORE BIT(1)
    +#define SVSB_INIT01_VOLT_INC_ONLY BIT(2)
    +
    +/* svs bank common setting */
    +#define MAX_OPP_ENTRIES 16
    +#define SVSB_DC_SIGNED_BIT BIT(15)
    +#define SVSB_DET_CLK_EN BIT(31)
    +#define SVSB_DET_MAX 0xffff
    +#define SVSB_DET_WINDOW 0xa28
    +#define SVSB_DTHI 0x1
    +#define SVSB_DTLO 0xfe
    +#define SVSB_EN_INIT01 0x1
    +#define SVSB_EN_INIT02 0x5
    +#define SVSB_EN_MASK 0x7
    +#define SVSB_EN_OFF 0x0
    +#define SVSB_INTEN_INIT0x 0x00005f01
    +#define SVSB_INTSTS_CLEAN 0x00ffffff
    +#define SVSB_INTSTS_COMPLETE 0x1
    +#define SVSB_RUNCONFIG_DEFAULT 0x80000000
    +
    +static DEFINE_SPINLOCK(svs_lock);
    +
    +/**
    + * enum svsb_phase - svs bank phase enumeration
    + * @SVSB_PHASE_ERROR: svs bank encounters unexpected condition
    + * @SVSB_PHASE_INIT01: svs bank basic init for data calibration
    + * @SVSB_PHASE_INIT02: svs bank can provide voltages to opp table
    + * @SVSB_PHASE_MAX: total number of svs bank phase (debug purpose)
    + *
    + * Each svs bank has its own independent phase and we enable each svs bank by
    + * running their phase orderly. However, when svs bank encounters unexpected
    + * condition, it will fire an irq (PHASE_ERROR) to inform svs software.
    + *
    + * svs bank general phase-enabled order:
    + * SVSB_PHASE_INIT01 -> SVSB_PHASE_INIT02
    + */
    +enum svsb_phase {
    + SVSB_PHASE_ERROR = 0,
    + SVSB_PHASE_INIT01,
    + SVSB_PHASE_INIT02,
    + SVSB_PHASE_MAX,
    +};
    +
    +enum svs_reg_index {
    + DESCHAR = 0,
    + TEMPCHAR,
    + DETCHAR,
    + AGECHAR,
    + DCCONFIG,
    + AGECONFIG,
    + FREQPCT30,
    + FREQPCT74,
    + LIMITVALS,
    + VBOOT,
    + DETWINDOW,
    + CONFIG,
    + TSCALCS,
    + RUNCONFIG,
    + SVSEN,
    + INIT2VALS,
    + DCVALUES,
    + AGEVALUES,
    + VOP30,
    + VOP74,
    + TEMP,
    + INTSTS,
    + INTSTSRAW,
    + INTEN,
    + CHKINT,
    + CHKSHIFT,
    + STATUS,
    + VDESIGN30,
    + VDESIGN74,
    + DVT30,
    + DVT74,
    + AGECOUNT,
    + SMSTATE0,
    + SMSTATE1,
    + CTL0,
    + DESDETSEC,
    + TEMPAGESEC,
    + CTRLSPARE0,
    + CTRLSPARE1,
    + CTRLSPARE2,
    + CTRLSPARE3,
    + CORESEL,
    + THERMINTST,
    + INTST,
    + THSTAGE0ST,
    + THSTAGE1ST,
    + THSTAGE2ST,
    + THAHBST0,
    + THAHBST1,
    + SPARE0,
    + SPARE1,
    + SPARE2,
    + SPARE3,
    + THSLPEVEB,
    + SVS_REG_MAX,
    +};
    +
    +static const u32 svs_regs_v2[] = {
    + [DESCHAR] = 0xc00,
    + [TEMPCHAR] = 0xc04,
    + [DETCHAR] = 0xc08,
    + [AGECHAR] = 0xc0c,
    + [DCCONFIG] = 0xc10,
    + [AGECONFIG] = 0xc14,
    + [FREQPCT30] = 0xc18,
    + [FREQPCT74] = 0xc1c,
    + [LIMITVALS] = 0xc20,
    + [VBOOT] = 0xc24,
    + [DETWINDOW] = 0xc28,
    + [CONFIG] = 0xc2c,
    + [TSCALCS] = 0xc30,
    + [RUNCONFIG] = 0xc34,
    + [SVSEN] = 0xc38,
    + [INIT2VALS] = 0xc3c,
    + [DCVALUES] = 0xc40,
    + [AGEVALUES] = 0xc44,
    + [VOP30] = 0xc48,
    + [VOP74] = 0xc4c,
    + [TEMP] = 0xc50,
    + [INTSTS] = 0xc54,
    + [INTSTSRAW] = 0xc58,
    + [INTEN] = 0xc5c,
    + [CHKINT] = 0xc60,
    + [CHKSHIFT] = 0xc64,
    + [STATUS] = 0xc68,
    + [VDESIGN30] = 0xc6c,
    + [VDESIGN74] = 0xc70,
    + [DVT30] = 0xc74,
    + [DVT74] = 0xc78,
    + [AGECOUNT] = 0xc7c,
    + [SMSTATE0] = 0xc80,
    + [SMSTATE1] = 0xc84,
    + [CTL0] = 0xc88,
    + [DESDETSEC] = 0xce0,
    + [TEMPAGESEC] = 0xce4,
    + [CTRLSPARE0] = 0xcf0,
    + [CTRLSPARE1] = 0xcf4,
    + [CTRLSPARE2] = 0xcf8,
    + [CTRLSPARE3] = 0xcfc,
    + [CORESEL] = 0xf00,
    + [THERMINTST] = 0xf04,
    + [INTST] = 0xf08,
    + [THSTAGE0ST] = 0xf0c,
    + [THSTAGE1ST] = 0xf10,
    + [THSTAGE2ST] = 0xf14,
    + [THAHBST0] = 0xf18,
    + [THAHBST1] = 0xf1c,
    + [SPARE0] = 0xf20,
    + [SPARE1] = 0xf24,
    + [SPARE2] = 0xf28,
    + [SPARE3] = 0xf2c,
    + [THSLPEVEB] = 0xf30,
    +};
    +
    +/**
    + * struct svs_platform - svs platform control
    + * @name: svs platform name
    + * @base: svs platform register base
    + * @dev: svs platform device
    + * @main_clk: main clock for svs bank
    + * @pbank: svs bank pointer protected by spin_lock section
    + * @banks: svs banks that svs platform supports
    + * @efuse_parsing: svs platform efuse parsing function pointer
    + * @probe: svs platform probe function pointer
    + * @irqflags: svs platform irq settings flags
    + * @efuse_max: total number of svs efuse
    + * @regs: svs platform registers map
    + * @bank_max: total number of svs banks
    + * @efuse: svs efuse data received from NVMEM framework
    + */
    +struct svs_platform {
    + char *name;
    + void __iomem *base;
    + struct device *dev;
    + struct clk *main_clk;
    + struct svs_bank *pbank;
    + struct svs_bank *banks;
    + bool (*efuse_parsing)(struct svs_platform *svsp);
    + int (*probe)(struct svs_platform *svsp);
    + unsigned long irqflags;
    + size_t efuse_max;
    + const u32 *regs;
    + u32 bank_max;
    + u32 *efuse;
    +};
    +
    +/**
    + * struct svs_bank - svs bank representation
    + * @dev: bank device
    + * @opp_dev: device for opp table/buck control
    + * @pd_dev: power domain device for SoC mtcmos control
    + * @init_completion: the timeout completion for bank init
    + * @buck: regulator used by opp_dev
    + * @lock: mutex lock to protect voltage update process
    + * @set_freq_pct: function pointer to set bank frequency percent table
    + * @get_volts: function pointer to get bank voltages
    + * @name: bank name
    + * @buck_name: regulator name
    + * @suspended: suspend flag of this bank
    + * @phase: bank current phase
    + * @volt_od: bank voltage overdrive
    + * @pm_runtime_enabled_count: bank pm runtime enabled count
    + * @mode_support: bank mode support.
    + * @freq_base: reference frequency for bank init
    + * @vboot: voltage request for bank init01 only
    + * @opp_dfreq: default opp frequency table
    + * @opp_dvolt: default opp voltage table
    + * @freq_pct: frequency percent table for bank init
    + * @volt: bank voltage table
    + * @volt_step: bank voltage step
    + * @volt_base: bank voltage base
    + * @volt_flags: bank voltage flags
    + * @vmax: bank voltage maximum
    + * @vmin: bank voltage minimum
    + * @age_config: bank age configuration
    + * @age_voffset_in: bank age voltage offset
    + * @dc_config: bank dc configuration
    + * @dc_voffset_in: bank dc voltage offset
    + * @dvt_fixed: bank dvt fixed value
    + * @vco: bank VCO value
    + * @chk_shift: bank chicken shift
    + * @core_sel: bank selection
    + * @opp_count: bank opp count
    + * @int_st: bank interrupt identification
    + * @sw_id: bank software identification
    + * @cpu_id: cpu core id for SVS CPU bank use only
    + * @ctl0: TS-x selection
    + * @bdes: svs efuse data
    + * @mdes: svs efuse data
    + * @mtdes: svs efuse data
    + * @dcbdet: svs efuse data
    + * @dcmdet: svs efuse data
    + *
    + * Svs bank will generate suitalbe voltages by below general math equation
    + * and provide these voltages to opp voltage table.
    + *
    + * opp volt[i] = (volt_step * volt[i]) + volt_base;
    + */
    +struct svs_bank {
    + struct device *dev;
    + struct device *opp_dev;
    + struct device *pd_dev;
    + struct completion init_completion;
    + struct regulator *buck;
    + struct mutex lock; /* lock to protect voltage update process */
    + void (*set_freq_pct)(struct svs_platform *svsp);
    + void (*get_volts)(struct svs_platform *svsp);
    + char *name;
    + char *buck_name;
    + bool suspended;
    + enum svsb_phase phase;
    + s32 volt_od;
    + u32 pm_runtime_enabled_count;
    + u32 mode_support;
    + u32 freq_base;
    + u32 vboot;
    + u32 opp_dfreq[MAX_OPP_ENTRIES];
    + u32 opp_dvolt[MAX_OPP_ENTRIES];
    + u32 freq_pct[MAX_OPP_ENTRIES];
    + u32 volt[MAX_OPP_ENTRIES];
    + u32 volt_step;
    + u32 volt_base;
    + u32 volt_flags;
    + u32 vmax;
    + u32 vmin;
    + u32 age_config;
    + u32 age_voffset_in;
    + u32 dc_config;
    + u32 dc_voffset_in;
    + u32 dvt_fixed;
    + u32 vco;
    + u32 chk_shift;
    + u32 core_sel;
    + u32 opp_count;
    + u32 int_st;
    + u32 sw_id;
    + u32 cpu_id;
    + u32 ctl0;
    + u32 bdes;
    + u32 mdes;
    + u32 mtdes;
    + u32 dcbdet;
    + u32 dcmdet;
    +};
    +
    +static u32 percent(u32 numerator, u32 denominator)
    +{
    + /* If not divide 1000, "numerator * 100" will have data overflow. */
    + numerator /= 1000;
    + denominator /= 1000;
    +
    + return DIV_ROUND_UP(numerator * 100, denominator);
    +}
    +
    +static u32 svs_readl_relaxed(struct svs_platform *svsp, enum svs_reg_index rg_i)
    +{
    + return readl_relaxed(svsp->base + svsp->regs[rg_i]);
    +}
    +
    +static void svs_writel_relaxed(struct svs_platform *svsp, u32 val,
    + enum svs_reg_index rg_i)
    +{
    + writel_relaxed(val, svsp->base + svsp->regs[rg_i]);
    +}
    +
    +static void svs_switch_bank(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb = svsp->pbank;
    +
    + svs_writel_relaxed(svsp, svsb->core_sel, CORESEL);
    +}
    +
    +static u32 svs_bank_volt_to_opp_volt(u32 svsb_volt, u32 svsb_volt_step,
    + u32 svsb_volt_base)
    +{
    + return (svsb_volt * svsb_volt_step) + svsb_volt_base;
    +}
    +
    +static int svs_adjust_pm_opp_volts(struct svs_bank *svsb, bool force_update)
    +{
    + int ret = -EPERM;
    + u32 i, svsb_volt, opp_volt;
    +
    + mutex_lock(&svsb->lock);
    +
    + /*
    + * If svs bank is suspended, it means default opp voltages are applied.
    + * We don't need to update opp voltage anymore.
    + */
    + if (svsb->suspended && !force_update) {
    + dev_notice(svsb->dev, "bank is suspended\n");
    + ret = -EPERM;
    + goto unlock_mutex;
    + }
    +
    + /* vmin <= svsb_volt (opp_volt) <= default opp voltage */
    + for (i = 0; i < svsb->opp_count; i++) {
    + switch (svsb->phase) {
    + case SVSB_PHASE_ERROR:
    + opp_volt = svsb->opp_dvolt[i];
    + break;
    + case SVSB_PHASE_INIT01:
    + /* do nothing */
    + goto unlock_mutex;
    + case SVSB_PHASE_INIT02:
    + svsb_volt = max(svsb->volt[i], svsb->vmin);
    + opp_volt = svs_bank_volt_to_opp_volt(svsb_volt,
    + svsb->volt_step,
    + svsb->volt_base);
    + break;
    + default:
    + dev_err(svsb->dev, "unknown phase: %u\n", svsb->phase);
    + ret = -EINVAL;
    + goto unlock_mutex;
    + }
    +
    + opp_volt = min(opp_volt, svsb->opp_dvolt[i]);
    + ret = dev_pm_opp_adjust_voltage(svsb->opp_dev,
    + svsb->opp_dfreq[i],
    + opp_volt, opp_volt,
    + svsb->opp_dvolt[i]);
    + if (ret) {
    + dev_err(svsb->dev, "set %uuV fail: %d\n",
    + opp_volt, ret);
    + goto unlock_mutex;
    + }
    + }
    +
    +unlock_mutex:
    + mutex_unlock(&svsb->lock);
    +
    + return ret;
    +}
    +
    +static u32 interpolate(u32 f0, u32 f1, u32 v0, u32 v1, u32 fx)
    +{
    + u32 vx;
    +
    + if (v0 == v1 || f0 == f1)
    + return v0;
    +
    + /* *100 to have decimal fraction factor */
    + vx = (v0 * 100) - ((((v0 - v1) * 100) / (f0 - f1)) * (f0 - fx));
    +
    + return DIV_ROUND_UP(vx, 100);
    +}
    +
    +static void svs_get_bank_volts_v2(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb = svsp->pbank;
    + u32 temp, i;
    +
    + temp = svs_readl_relaxed(svsp, VOP74);
    + svsb->volt[14] = (temp >> 24) & GENMASK(7, 0);
    + svsb->volt[12] = (temp >> 16) & GENMASK(7, 0);
    + svsb->volt[10] = (temp >> 8) & GENMASK(7, 0);
    + svsb->volt[8] = (temp & GENMASK(7, 0));
    +
    + temp = svs_readl_relaxed(svsp, VOP30);
    + svsb->volt[6] = (temp >> 24) & GENMASK(7, 0);
    + svsb->volt[4] = (temp >> 16) & GENMASK(7, 0);
    + svsb->volt[2] = (temp >> 8) & GENMASK(7, 0);
    + svsb->volt[0] = (temp & GENMASK(7, 0));
    +
    + for (i = 0; i <= 12; i += 2)
    + svsb->volt[i + 1] = interpolate(svsb->freq_pct[i],
    + svsb->freq_pct[i + 2],
    + svsb->volt[i],
    + svsb->volt[i + 2],
    + svsb->freq_pct[i + 1]);
    +
    + svsb->volt[15] = interpolate(svsb->freq_pct[12],
    + svsb->freq_pct[14],
    + svsb->volt[12],
    + svsb->volt[14],
    + svsb->freq_pct[15]);
    +
    + for (i = 0; i < svsb->opp_count; i++)
    + svsb->volt[i] += svsb->volt_od;
    +}
    +
    +static void svs_set_bank_freq_pct_v2(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb = svsp->pbank;
    +
    + svs_writel_relaxed(svsp,
    + (svsb->freq_pct[14] << 24) |
    + (svsb->freq_pct[12] << 16) |
    + (svsb->freq_pct[10] << 8) |
    + svsb->freq_pct[8],
    + FREQPCT74);
    +
    + svs_writel_relaxed(svsp,
    + (svsb->freq_pct[6] << 24) |
    + (svsb->freq_pct[4] << 16) |
    + (svsb->freq_pct[2] << 8) |
    + svsb->freq_pct[0],
    + FREQPCT30);
    +}
    +
    +static void svs_set_bank_phase(struct svs_platform *svsp,
    + enum svsb_phase target_phase)
    +{
    + struct svs_bank *svsb = svsp->pbank;
    + u32 des_char, temp_char, det_char, limit_vals;
    + u32 init2vals;
    +
    + svs_switch_bank(svsp);
    +
    + des_char = (svsb->bdes << 8) | svsb->mdes;
    + svs_writel_relaxed(svsp, des_char, DESCHAR);
    +
    + temp_char = (svsb->vco << 16) | (svsb->mtdes << 8) | svsb->dvt_fixed;
    + svs_writel_relaxed(svsp, temp_char, TEMPCHAR);
    +
    + det_char = (svsb->dcbdet << 8) | svsb->dcmdet;
    + svs_writel_relaxed(svsp, det_char, DETCHAR);
    +
    + svs_writel_relaxed(svsp, svsb->dc_config, DCCONFIG);
    + svs_writel_relaxed(svsp, svsb->age_config, AGECONFIG);
    + svs_writel_relaxed(svsp, SVSB_RUNCONFIG_DEFAULT, RUNCONFIG);
    +
    + svsb->set_freq_pct(svsp);
    +
    + limit_vals = (svsb->vmax << 24) | (svsb->vmin << 16) |
    + (SVSB_DTHI << 8) | SVSB_DTLO;
    + svs_writel_relaxed(svsp, limit_vals, LIMITVALS);
    +
    + svs_writel_relaxed(svsp, SVSB_DET_WINDOW, DETWINDOW);
    + svs_writel_relaxed(svsp, SVSB_DET_MAX, CONFIG);
    + svs_writel_relaxed(svsp, svsb->chk_shift, CHKSHIFT);
    + svs_writel_relaxed(svsp, svsb->ctl0, CTL0);
    + svs_writel_relaxed(svsp, SVSB_INTSTS_CLEAN, INTSTS);
    +
    + switch (target_phase) {
    + case SVSB_PHASE_INIT01:
    + svs_writel_relaxed(svsp, svsb->vboot, VBOOT);
    + svs_writel_relaxed(svsp, SVSB_INTEN_INIT0x, INTEN);
    + svs_writel_relaxed(svsp, SVSB_EN_INIT01, SVSEN);
    + break;
    + case SVSB_PHASE_INIT02:
    + svs_writel_relaxed(svsp, SVSB_INTEN_INIT0x, INTEN);
    + init2vals = (svsb->age_voffset_in << 16) | svsb->dc_voffset_in;
    + svs_writel_relaxed(svsp, init2vals, INIT2VALS);
    + svs_writel_relaxed(svsp, SVSB_EN_INIT02, SVSEN);
    + break;
    + default:
    + dev_err(svsb->dev, "requested unknown target phase: %u\n",
    + target_phase);
    + break;
    + }
    +}
    +
    +static inline void svs_error_isr_handler(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb = svsp->pbank;
    +
    + dev_err(svsb->dev, "%s: CORESEL = 0x%08x\n",
    + __func__, svs_readl_relaxed(svsp, CORESEL));
    + dev_err(svsb->dev, "SVSEN = 0x%08x, INTSTS = 0x%08x\n",
    + svs_readl_relaxed(svsp, SVSEN),
    + svs_readl_relaxed(svsp, INTSTS));
    + dev_err(svsb->dev, "SMSTATE0 = 0x%08x, SMSTATE1 = 0x%08x\n",
    + svs_readl_relaxed(svsp, SMSTATE0),
    + svs_readl_relaxed(svsp, SMSTATE1));
    +
    + svsb->mode_support = SVSB_MODE_ALL_DISABLE;
    + svsb->phase = SVSB_PHASE_ERROR;
    +
    + svs_writel_relaxed(svsp, SVSB_EN_OFF, SVSEN);
    + svs_writel_relaxed(svsp, SVSB_INTSTS_CLEAN, INTSTS);
    +}
    +
    +static inline void svs_init01_isr_handler(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb = svsp->pbank;
    +
    + dev_info(svsb->dev, "%s: VDN74~30:0x%08x~0x%08x, DC:0x%08x\n",
    + __func__, svs_readl_relaxed(svsp, VDESIGN74),
    + svs_readl_relaxed(svsp, VDESIGN30),
    + svs_readl_relaxed(svsp, DCVALUES));
    +
    + svsb->phase = SVSB_PHASE_INIT01;
    + svsb->dc_voffset_in = ~(svs_readl_relaxed(svsp, DCVALUES) &
    + GENMASK(15, 0)) + 1;
    + if (svsb->volt_flags & SVSB_INIT01_VOLT_IGNORE ||
    + (svsb->dc_voffset_in & SVSB_DC_SIGNED_BIT &&
    + svsb->volt_flags & SVSB_INIT01_VOLT_INC_ONLY))
    + svsb->dc_voffset_in = 0;
    +
    + svsb->age_voffset_in = svs_readl_relaxed(svsp, AGEVALUES) &
    + GENMASK(15, 0);
    +
    + svs_writel_relaxed(svsp, SVSB_EN_OFF, SVSEN);
    + svs_writel_relaxed(svsp, SVSB_INTSTS_COMPLETE, INTSTS);
    +
    + /* svs init01 clock gating */
    + svsb->core_sel &= ~SVSB_DET_CLK_EN;
    +}
    +
    +static inline void svs_init02_isr_handler(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb = svsp->pbank;
    +
    + dev_info(svsb->dev, "%s: VOP74~30:0x%08x~0x%08x, DC:0x%08x\n",
    + __func__, svs_readl_relaxed(svsp, VOP74),
    + svs_readl_relaxed(svsp, VOP30),
    + svs_readl_relaxed(svsp, DCVALUES));
    +
    + svsb->phase = SVSB_PHASE_INIT02;
    + svsb->get_volts(svsp);
    +
    + svs_writel_relaxed(svsp, SVSB_EN_OFF, SVSEN);
    + svs_writel_relaxed(svsp, SVSB_INTSTS_COMPLETE, INTSTS);
    +}
    +
    +static irqreturn_t svs_isr(int irq, void *data)
    +{
    + struct svs_platform *svsp = data;
    + struct svs_bank *svsb = NULL;
    + unsigned long flags;
    + u32 idx, int_sts, svs_en;
    +
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    + WARN(!svsb, "%s: svsb(%s) is null", __func__, svsb->name);
    +
    + spin_lock_irqsave(&svs_lock, flags);
    + svsp->pbank = svsb;
    +
    + /* Find out which svs bank fires interrupt */
    + if (svsb->int_st & svs_readl_relaxed(svsp, INTST)) {
    + spin_unlock_irqrestore(&svs_lock, flags);
    + continue;
    + }
    +
    + svs_switch_bank(svsp);
    + int_sts = svs_readl_relaxed(svsp, INTSTS);
    + svs_en = svs_readl_relaxed(svsp, SVSEN) & SVSB_EN_MASK;
    +
    + if (int_sts == SVSB_INTSTS_COMPLETE &&
    + svs_en == SVSB_EN_INIT01)
    + svs_init01_isr_handler(svsp);
    + else if (int_sts == SVSB_INTSTS_COMPLETE &&
    + svs_en == SVSB_EN_INIT02)
    + svs_init02_isr_handler(svsp);
    + else
    + svs_error_isr_handler(svsp);
    +
    + spin_unlock_irqrestore(&svs_lock, flags);
    + break;
    + }
    +
    + svs_adjust_pm_opp_volts(svsb, false);
    +
    + if (svsb->phase == SVSB_PHASE_INIT01 ||
    + svsb->phase == SVSB_PHASE_INIT02)
    + complete(&svsb->init_completion);
    +
    + return IRQ_HANDLED;
    +}
    +
    +static int svs_init01(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb;
    + struct pm_qos_request *cpu_qos_request;
    + unsigned long flags, time_left;
    + bool search_done;
    + int ret = 0, r;
    + u32 opp_freq, opp_vboot, buck_volt, idx, i;
    +
    + cpu_qos_request = kzalloc(sizeof(*cpu_qos_request), GFP_KERNEL);
    + if (!cpu_qos_request)
    + return -ENOMEM;
    +
    + /* Let CPUs leave idle-off state for initializing svs_init01. */
    + cpu_latency_qos_add_request(cpu_qos_request, 0);
    +
    + /* Svs bank init01 preparation - power enable */
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + if (!(svsb->mode_support & SVSB_MODE_INIT01))
    + continue;
    +
    + svsb->buck = devm_regulator_get_optional(svsb->opp_dev,
    + svsb->buck_name);
    + if (IS_ERR(svsb->buck)) {
    + dev_err(svsb->dev, "cannot get \"%s-supply\"\n",
    + svsb->buck_name);
    + ret = PTR_ERR(svsb->buck);
    + goto init01_remove_cpu_qos_request;
    + }
    +
    + ret = regulator_enable(svsb->buck);
    + if (ret) {
    + dev_err(svsb->dev, "%s enable fail: %d\n",
    + svsb->buck_name, ret);
    + goto init01_remove_cpu_qos_request;
    + }
    +
    + /* Some buck doesn't support mode change. Show fail msg only */
    + ret = regulator_set_mode(svsb->buck, REGULATOR_MODE_FAST);
    + if (ret)
    + dev_notice(svsb->dev, "set fast mode fail: %d\n", ret);
    +
    + if (svsb->volt_flags & SVSB_INIT01_PD_REQ) {
    + if (!pm_runtime_enabled(svsb->pd_dev)) {
    + pm_runtime_enable(svsb->pd_dev);
    + svsb->pm_runtime_enabled_count++;
    + }
    +
    + ret = pm_runtime_get_sync(svsb->pd_dev);
    + if (ret < 0) {
    + dev_err(svsb->dev, "mtcmos on fail: %d\n", ret);
    + goto init01_remove_cpu_qos_request;
    + }
    + }
    + }
    +
    + /*
    + * Svs bank init01 preparation - vboot voltage adjustment
    + * Sometimes two svs banks use the same buck. Therefore,
    + * we have to set each svs bank to target voltage(vboot) first.
    + */
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + if (!(svsb->mode_support & SVSB_MODE_INIT01))
    + continue;
    +
    + /*
    + * Find the fastest freq that can be run at vboot and
    + * fix to that freq until svs_init01 is done.
    + */
    + search_done = false;
    + opp_vboot = svs_bank_volt_to_opp_volt(svsb->vboot,
    + svsb->volt_step,
    + svsb->volt_base);
    +
    + for (i = 0; i < svsb->opp_count; i++) {
    + opp_freq = svsb->opp_dfreq[i];
    + if (!search_done && svsb->opp_dvolt[i] <= opp_vboot) {
    + ret = dev_pm_opp_adjust_voltage(svsb->opp_dev,
    + opp_freq,
    + opp_vboot,
    + opp_vboot,
    + opp_vboot);
    + if (ret) {
    + dev_err(svsb->dev,
    + "set opp %uuV vboot fail: %d\n",
    + opp_vboot, ret);
    + goto init01_finish;
    + }
    +
    + search_done = true;
    + } else {
    + ret = dev_pm_opp_disable(svsb->opp_dev,
    + svsb->opp_dfreq[i]);
    + if (ret) {
    + dev_err(svsb->dev,
    + "opp %uHz disable fail: %d\n",
    + svsb->opp_dfreq[i], ret);
    + goto init01_finish;
    + }
    + }
    + }
    + }
    +
    + /* Svs bank init01 begins */
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + if (!(svsb->mode_support & SVSB_MODE_INIT01))
    + continue;
    +
    + opp_vboot = svs_bank_volt_to_opp_volt(svsb->vboot,
    + svsb->volt_step,
    + svsb->volt_base);
    +
    + buck_volt = regulator_get_voltage(svsb->buck);
    + if (buck_volt != opp_vboot) {
    + dev_err(svsb->dev,
    + "buck voltage: %uuV, expected vboot: %uuV\n",
    + buck_volt, opp_vboot);
    + ret = -EPERM;
    + goto init01_finish;
    + }
    +
    + spin_lock_irqsave(&svs_lock, flags);
    + svsp->pbank = svsb;
    + svs_set_bank_phase(svsp, SVSB_PHASE_INIT01);
    + spin_unlock_irqrestore(&svs_lock, flags);
    +
    + time_left = wait_for_completion_timeout(&svsb->init_completion,
    + msecs_to_jiffies(5000));
    + if (!time_left) {
    + dev_err(svsb->dev, "init01 completion timeout\n");
    + ret = -EBUSY;
    + goto init01_finish;
    + }
    + }
    +
    +init01_finish:
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + if (!(svsb->mode_support & SVSB_MODE_INIT01))
    + continue;
    +
    + for (i = 0; i < svsb->opp_count; i++) {
    + r = dev_pm_opp_enable(svsb->opp_dev,
    + svsb->opp_dfreq[i]);
    + if (r)
    + dev_err(svsb->dev, "opp %uHz enable fail: %d\n",
    + svsb->opp_dfreq[i], r);
    + }
    +
    + if (svsb->volt_flags & SVSB_INIT01_PD_REQ) {
    + r = pm_runtime_put_sync(svsb->pd_dev);
    + if (r)
    + dev_err(svsb->dev, "mtcmos off fail: %d\n", r);
    +
    + if (svsb->pm_runtime_enabled_count > 0) {
    + pm_runtime_disable(svsb->pd_dev);
    + svsb->pm_runtime_enabled_count--;
    + }
    + }
    +
    + r = regulator_set_mode(svsb->buck, REGULATOR_MODE_NORMAL);
    + if (r)
    + dev_notice(svsb->dev, "set normal mode fail: %d\n", r);
    +
    + r = regulator_disable(svsb->buck);
    + if (r)
    + dev_err(svsb->dev, "%s disable fail: %d\n",
    + svsb->buck_name, r);
    + }
    +
    +init01_remove_cpu_qos_request:
    + cpu_latency_qos_remove_request(cpu_qos_request);
    + kfree(cpu_qos_request);
    +
    + return ret;
    +}
    +
    +static int svs_init02(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb;
    + unsigned long flags, time_left;
    + u32 idx;
    +
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + if (!(svsb->mode_support & SVSB_MODE_INIT02))
    + continue;
    +
    + reinit_completion(&svsb->init_completion);
    + spin_lock_irqsave(&svs_lock, flags);
    + svsp->pbank = svsb;
    + svs_set_bank_phase(svsp, SVSB_PHASE_INIT02);
    + spin_unlock_irqrestore(&svs_lock, flags);
    +
    + time_left = wait_for_completion_timeout(&svsb->init_completion,
    + msecs_to_jiffies(5000));
    + if (!time_left) {
    + dev_err(svsb->dev, "init02 completion timeout\n");
    + return -EBUSY;
    + }
    + }
    +
    + return 0;
    +}
    +
    +static int svs_start(struct svs_platform *svsp)
    +{
    + int ret;
    +
    + ret = svs_init01(svsp);
    + if (ret)
    + return ret;
    +
    + ret = svs_init02(svsp);
    + if (ret)
    + return ret;
    +
    + return 0;
    +}
    +
    +static int svs_suspend(struct device *dev)
    +{
    + struct svs_platform *svsp = dev_get_drvdata(dev);
    + struct svs_bank *svsb;
    + unsigned long flags;
    + u32 idx;
    +
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + /* This might wait for svs_isr() process */
    + spin_lock_irqsave(&svs_lock, flags);
    + svsp->pbank = svsb;
    + svs_switch_bank(svsp);
    + svs_writel_relaxed(svsp, SVSB_EN_OFF, SVSEN);
    + svs_writel_relaxed(svsp, SVSB_INTSTS_CLEAN, INTSTS);
    + spin_unlock_irqrestore(&svs_lock, flags);
    +
    + svsb->suspended = true;
    + svsb->phase = SVSB_PHASE_ERROR;
    + svs_adjust_pm_opp_volts(svsb, true);
    + }
    +
    + clk_disable_unprepare(svsp->main_clk);
    +
    + return 0;
    +}
    +
    +static int svs_resume(struct device *dev)
    +{
    + struct svs_platform *svsp = dev_get_drvdata(dev);
    + struct svs_bank *svsb;
    + int ret;
    + u32 idx;
    +
    + ret = clk_prepare_enable(svsp->main_clk);
    + if (ret) {
    + dev_err(svsp->dev, "cannot enable main_clk, disable svs\n");
    + return ret;
    + }
    +
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    + svsb->suspended = false;
    + }
    +
    + ret = svs_init02(svsp);
    + if (ret)
    + return ret;
    +
    + return 0;
    +}
    +
    +static int svs_bank_resource_setup(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb;
    + struct dev_pm_opp *opp;
    + unsigned long freq;
    + int count, ret;
    + u32 idx, i;
    +
    + dev_set_drvdata(svsp->dev, svsp);
    +
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + switch (svsb->sw_id) {
    + case SVSB_CPU_LITTLE:
    + svsb->name = "SVSB_CPU_LITTLE";
    + break;
    + case SVSB_CPU_BIG:
    + svsb->name = "SVSB_CPU_BIG";
    + break;
    + case SVSB_CCI:
    + svsb->name = "SVSB_CCI";
    + break;
    + case SVSB_GPU:
    + svsb->name = "SVSB_GPU";
    + break;
    + default:
    + dev_err(svsb->dev, "unknown sw_id: %u\n", svsb->sw_id);
    + return -EINVAL;
    + }
    +
    + svsb->dev = devm_kzalloc(svsp->dev, sizeof(*svsb->dev),
    + GFP_KERNEL);
    + if (!svsb->dev)
    + return -ENOMEM;
    +
    + ret = dev_set_name(svsb->dev, "%s", svsb->name);
    + if (ret)
    + return ret;
    +
    + dev_set_drvdata(svsb->dev, svsp);
    +
    + ret = dev_pm_opp_of_add_table(svsb->opp_dev);
    + if (ret) {
    + dev_err(svsb->dev, "add opp table fail: %d\n", ret);
    + return ret;
    + }
    +
    + mutex_init(&svsb->lock);
    + init_completion(&svsb->init_completion);
    +
    + count = dev_pm_opp_get_opp_count(svsb->opp_dev);
    + if (svsb->opp_count != count) {
    + dev_err(svsb->dev,
    + "opp_count not \"%u\" but get \"%d\"?\n",
    + svsb->opp_count, count);
    + return count;
    + }
    +
    + for (i = 0, freq = U32_MAX; i < svsb->opp_count; i++, freq--) {
    + opp = dev_pm_opp_find_freq_floor(svsb->opp_dev, &freq);
    + if (IS_ERR(opp)) {
    + dev_err(svsb->dev, "cannot find freq = %ld\n",
    + PTR_ERR(opp));
    + return PTR_ERR(opp);
    + }
    +
    + svsb->opp_dfreq[i] = freq;
    + svsb->opp_dvolt[i] = dev_pm_opp_get_voltage(opp);
    + svsb->freq_pct[i] = percent(svsb->opp_dfreq[i],
    + svsb->freq_base);
    + dev_pm_opp_put(opp);
    + }
    + }
    +
    + return 0;
    +}
    +
    +static bool svs_mt8183_efuse_parsing(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb;
    + u32 idx, i, ft_pgm;
    +
    + for (i = 0; i < svsp->efuse_max; i++)
    + if (svsp->efuse[i])
    + dev_info(svsp->dev, "M_HW_RES%d: 0x%08x\n",
    + i, svsp->efuse[i]);
    +
    + if (!svsp->efuse[2]) {
    + dev_notice(svsp->dev, "svs_efuse[2] = 0x0?\n");
    + return false;
    + }
    +
    + /* Svs efuse parsing */
    + ft_pgm = (svsp->efuse[0] >> 4) & GENMASK(3, 0);
    +
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + if (ft_pgm <= 1)
    + svsb->volt_flags |= SVSB_INIT01_VOLT_IGNORE;
    +
    + switch (svsb->sw_id) {
    + case SVSB_CPU_LITTLE:
    + svsb->bdes = svsp->efuse[16] & GENMASK(7, 0);
    + svsb->mdes = (svsp->efuse[16] >> 8) & GENMASK(7, 0);
    + svsb->dcbdet = (svsp->efuse[16] >> 16) & GENMASK(7, 0);
    + svsb->dcmdet = (svsp->efuse[16] >> 24) & GENMASK(7, 0);
    + svsb->mtdes = (svsp->efuse[17] >> 16) & GENMASK(7, 0);
    +
    + if (ft_pgm <= 3)
    + svsb->volt_od += 10;
    + else
    + svsb->volt_od += 2;
    + break;
    + case SVSB_CPU_BIG:
    + svsb->bdes = svsp->efuse[18] & GENMASK(7, 0);
    + svsb->mdes = (svsp->efuse[18] >> 8) & GENMASK(7, 0);
    + svsb->dcbdet = (svsp->efuse[18] >> 16) & GENMASK(7, 0);
    + svsb->dcmdet = (svsp->efuse[18] >> 24) & GENMASK(7, 0);
    + svsb->mtdes = svsp->efuse[17] & GENMASK(7, 0);
    +
    + if (ft_pgm <= 3)
    + svsb->volt_od += 15;
    + else
    + svsb->volt_od += 12;
    + break;
    + case SVSB_CCI:
    + svsb->bdes = svsp->efuse[4] & GENMASK(7, 0);
    + svsb->mdes = (svsp->efuse[4] >> 8) & GENMASK(7, 0);
    + svsb->dcbdet = (svsp->efuse[4] >> 16) & GENMASK(7, 0);
    + svsb->dcmdet = (svsp->efuse[4] >> 24) & GENMASK(7, 0);
    + svsb->mtdes = (svsp->efuse[5] >> 16) & GENMASK(7, 0);
    +
    + if (ft_pgm <= 3)
    + svsb->volt_od += 10;
    + else
    + svsb->volt_od += 2;
    + break;
    + case SVSB_GPU:
    + svsb->bdes = svsp->efuse[6] & GENMASK(7, 0);
    + svsb->mdes = (svsp->efuse[6] >> 8) & GENMASK(7, 0);
    + svsb->dcbdet = (svsp->efuse[6] >> 16) & GENMASK(7, 0);
    + svsb->dcmdet = (svsp->efuse[6] >> 24) & GENMASK(7, 0);
    + svsb->mtdes = svsp->efuse[5] & GENMASK(7, 0);
    +
    + if (ft_pgm >= 2) {
    + svsb->freq_base = 800000000; /* 800MHz */
    + svsb->dvt_fixed = 2;
    + }
    + break;
    + default:
    + dev_err(svsb->dev, "unknown sw_id: %u\n", svsb->sw_id);
    + return false;
    + }
    + }
    +
    + return true;
    +}
    +
    +static bool svs_is_efuse_data_correct(struct svs_platform *svsp)
    +{
    + struct nvmem_cell *cell;
    +
    + /* Get svs efuse by nvmem */
    + cell = nvmem_cell_get(svsp->dev, "svs-calibration-data");
    + if (IS_ERR(cell)) {
    + dev_err(svsp->dev, "no \"svs-calibration-data\"? %ld\n",
    + PTR_ERR(cell));
    + return false;
    + }
    +
    + svsp->efuse = nvmem_cell_read(cell, &svsp->efuse_max);
    + if (IS_ERR(svsp->efuse)) {
    + dev_err(svsp->dev, "cannot read svs efuse: %ld\n",
    + PTR_ERR(svsp->efuse));
    + return false;
    + }
    +
    + svsp->efuse_max /= sizeof(u32);
    + nvmem_cell_put(cell);
    +
    + return svsp->efuse_parsing(svsp);
    +}
    +
    +static struct device *svs_get_subsys_device(struct svs_platform *svsp,
    + const char *node_name)
    +{
    + struct platform_device *pdev;
    + struct device_node *np;
    +
    + np = of_find_node_by_name(NULL, node_name);
    + if (!np) {
    + dev_err(svsp->dev, "cannot find %s node\n", node_name);
    + return ERR_PTR(-ENODEV);
    + }
    +
    + pdev = of_find_device_by_node(np);
    + if (!pdev) {
    + of_node_put(np);
    + dev_err(svsp->dev, "cannot find pdev by %s\n", node_name);
    + return ERR_PTR(-ENXIO);
    + }
    +
    + of_node_put(np);
    +
    + return &pdev->dev;
    +}
    +
    +static struct device *svs_add_device_link(struct svs_platform *svsp,
    + const char *node_name)
    +{
    + struct device *dev;
    + struct device_link *sup_link;
    +
    + if (!node_name) {
    + dev_err(svsp->dev, "node name cannot be null\n");
    + return ERR_PTR(-EINVAL);
    + }
    +
    + dev = svs_get_subsys_device(svsp, node_name);
    + if (IS_ERR(dev))
    + return dev;
    +
    + sup_link = device_link_add(svsp->dev, dev,
    + DL_FLAG_AUTOREMOVE_CONSUMER);
    + if (!sup_link) {
    + dev_err(svsp->dev, "sup_link is NULL\n");
    + return ERR_PTR(-EINVAL);
    + }
    +
    + if (sup_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
    + return ERR_PTR(-EPROBE_DEFER);
    +
    + return dev;
    +}
    +
    +static int svs_mt8183_platform_probe(struct svs_platform *svsp)
    +{
    + struct svs_bank *svsb;
    + u32 idx;
    +
    + for (idx = 0; idx < svsp->bank_max; idx++) {
    + svsb = &svsp->banks[idx];
    +
    + switch (svsb->sw_id) {
    + case SVSB_CPU_LITTLE:
    + case SVSB_CPU_BIG:
    + svsb->opp_dev = get_cpu_device(svsb->cpu_id);
    + break;
    + case SVSB_CCI:
    + svsb->opp_dev = svs_add_device_link(svsp, "cci");
    + break;
    + case SVSB_GPU:
    + svsb->opp_dev = svs_add_device_link(svsp, "mali");
    + svsb->pd_dev = svs_add_device_link(svsp,
    + "mali_gpu_core2");
    + if (IS_ERR(svsb->pd_dev))
    + return PTR_ERR(svsb->pd_dev);
    + break;
    + default:
    + dev_err(svsb->dev, "unknown sw_id: %u\n", svsb->sw_id);
    + return -EINVAL;
    + }
    +
    + if (IS_ERR(svsb->opp_dev))
    + return PTR_ERR(svsb->opp_dev);
    + }
    +
    + return 0;
    +}
    +
    +static struct svs_bank svs_mt8183_banks[] = {
    + {
    + .sw_id = SVSB_CPU_LITTLE,
    + .set_freq_pct = svs_set_bank_freq_pct_v2,
    + .get_volts = svs_get_bank_volts_v2,
    + .cpu_id = 0,
    + .buck_name = "proc",
    + .volt_flags = SVSB_INIT01_VOLT_INC_ONLY,
    + .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
    + .opp_count = MAX_OPP_ENTRIES,
    + .freq_base = 1989000000,
    + .vboot = 0x30,
    + .volt_step = 6250,
    + .volt_base = 500000,
    + .vmax = 0x64,
    + .vmin = 0x18,
    + .age_config = 0x555555,
    + .dc_config = 0x555555,
    + .dvt_fixed = 0x7,
    + .vco = 0x10,
    + .chk_shift = 0x77,
    + .core_sel = 0x8fff0000,
    + .int_st = BIT(0),
    + .ctl0 = 0x00010001,
    + },
    + {
    + .sw_id = SVSB_CPU_BIG,
    + .set_freq_pct = svs_set_bank_freq_pct_v2,
    + .get_volts = svs_get_bank_volts_v2,
    + .cpu_id = 4,
    + .buck_name = "proc",
    + .volt_flags = SVSB_INIT01_VOLT_INC_ONLY,
    + .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
    + .opp_count = MAX_OPP_ENTRIES,
    + .freq_base = 1989000000,
    + .vboot = 0x30,
    + .volt_step = 6250,
    + .volt_base = 500000,
    + .vmax = 0x58,
    + .vmin = 0x10,
    + .age_config = 0x555555,
    + .dc_config = 0x555555,
    + .dvt_fixed = 0x7,
    + .vco = 0x10,
    + .chk_shift = 0x77,
    + .core_sel = 0x8fff0001,
    + .int_st = BIT(1),
    + .ctl0 = 0x00000001,
    + },
    + {
    + .sw_id = SVSB_CCI,
    + .set_freq_pct = svs_set_bank_freq_pct_v2,
    + .get_volts = svs_get_bank_volts_v2,
    + .buck_name = "proc",
    + .volt_flags = SVSB_INIT01_VOLT_INC_ONLY,
    + .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
    + .opp_count = MAX_OPP_ENTRIES,
    + .freq_base = 1196000000,
    + .vboot = 0x30,
    + .volt_step = 6250,
    + .volt_base = 500000,
    + .vmax = 0x64,
    + .vmin = 0x18,
    + .age_config = 0x555555,
    + .dc_config = 0x555555,
    + .dvt_fixed = 0x7,
    + .vco = 0x10,
    + .chk_shift = 0x77,
    + .core_sel = 0x8fff0002,
    + .int_st = BIT(2),
    + .ctl0 = 0x00100003,
    + },
    + {
    + .sw_id = SVSB_GPU,
    + .set_freq_pct = svs_set_bank_freq_pct_v2,
    + .get_volts = svs_get_bank_volts_v2,
    + .buck_name = "mali",
    + .volt_flags = SVSB_INIT01_PD_REQ |
    + SVSB_INIT01_VOLT_INC_ONLY,
    + .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
    + .opp_count = MAX_OPP_ENTRIES,
    + .freq_base = 900000000,
    + .vboot = 0x30,
    + .volt_step = 6250,
    + .volt_base = 500000,
    + .vmax = 0x40,
    + .vmin = 0x14,
    + .age_config = 0x555555,
    + .dc_config = 0x555555,
    + .dvt_fixed = 0x3,
    + .vco = 0x10,
    + .chk_shift = 0x77,
    + .core_sel = 0x8fff0003,
    + .int_st = BIT(3),
    + .ctl0 = 0x00050001,
    + },
    +};
    +
    +struct svs_platform_data {
    + char *name;
    + struct svs_bank *banks;
    + bool (*efuse_parsing)(struct svs_platform *svsp);
    + unsigned long irqflags;
    + const u32 *regs;
    + u32 bank_max;
    + int (*probe)(struct svs_platform *svsp);
    +};
    +
    +static const struct svs_platform_data svs_mt8183_platform_data = {
    + .name = "mt8183-svs",
    + .banks = svs_mt8183_banks,
    + .efuse_parsing = svs_mt8183_efuse_parsing,
    + .irqflags = IRQF_TRIGGER_LOW,
    + .regs = svs_regs_v2,
    + .bank_max = ARRAY_SIZE(svs_mt8183_banks),
    + .probe = svs_mt8183_platform_probe,
    +};
    +
    +static const struct of_device_id svs_of_match[] = {
    + {
    + .compatible = "mediatek,mt8183-svs",
    + .data = &svs_mt8183_platform_data,
    + }, {
    + /* Sentinel */
    + },
    +};
    +
    +static struct svs_platform *svs_platform_probe(struct platform_device *pdev)
    +{
    + struct svs_platform *svsp;
    + const struct svs_platform_data *svsp_data;
    + int ret;
    +
    + svsp_data = of_device_get_match_data(&pdev->dev);
    + if (!svsp_data) {
    + dev_err(&pdev->dev, "no svs platform data?\n");
    + return ERR_PTR(-EPERM);
    + }
    +
    + svsp = devm_kzalloc(&pdev->dev, sizeof(*svsp), GFP_KERNEL);
    + if (!svsp)
    + return ERR_PTR(-ENOMEM);
    +
    + svsp->dev = &pdev->dev;
    + svsp->name = svsp_data->name;
    + svsp->banks = svsp_data->banks;
    + svsp->efuse_parsing = svsp_data->efuse_parsing;
    + svsp->regs = svsp_data->regs;
    + svsp->irqflags = svsp_data->irqflags;
    + svsp->bank_max = svsp_data->bank_max;
    + svsp->probe = svsp_data->probe;
    +
    + ret = svsp->probe(svsp);
    + if (ret)
    + return ERR_PTR(ret);
    +
    + return svsp;
    +}
    +
    +static int svs_probe(struct platform_device *pdev)
    +{
    + struct svs_platform *svsp;
    + unsigned int svsp_irq;
    + int ret;
    +
    + svsp = svs_platform_probe(pdev);
    + if (IS_ERR(svsp)) {
    + dev_err_probe(&pdev->dev, PTR_ERR(svsp),
    + "svs platform probe fail\n");
    + return PTR_ERR(svsp);
    + }
    +
    + if (!svs_is_efuse_data_correct(svsp)) {
    + dev_notice(svsp->dev, "efuse data isn't correct\n");
    + return -EPERM;
    + }
    +
    + ret = svs_bank_resource_setup(svsp);
    + if (ret) {
    + dev_err(svsp->dev, "svs bank resource setup fail: %d\n", ret);
    + return ret;
    + }
    +
    + svsp_irq = irq_of_parse_and_map(svsp->dev->of_node, 0);
    + ret = devm_request_threaded_irq(svsp->dev, svsp_irq, NULL, svs_isr,
    + svsp->irqflags | IRQF_ONESHOT,
    + svsp->name, svsp);
    + if (ret) {
    + dev_err(svsp->dev, "register irq(%d) failed: %d\n",
    + svsp_irq, ret);
    + return ret;
    + }
    +
    + svsp->main_clk = devm_clk_get(svsp->dev, "main");
    + if (IS_ERR(svsp->main_clk)) {
    + dev_err(svsp->dev, "failed to get clock: %ld\n",
    + PTR_ERR(svsp->main_clk));
    + return PTR_ERR(svsp->main_clk);
    + }
    +
    + ret = clk_prepare_enable(svsp->main_clk);
    + if (ret) {
    + dev_err(svsp->dev, "cannot enable main clk: %d\n", ret);
    + return ret;
    + }
    +
    + svsp->base = of_iomap(svsp->dev->of_node, 0);
    + if (IS_ERR_OR_NULL(svsp->base)) {
    + dev_err(svsp->dev, "cannot find svs register base\n");
    + ret = -EINVAL;
    + goto svs_probe_clk_disable;
    + }
    +
    + ret = svs_start(svsp);
    + if (ret) {
    + dev_err(svsp->dev, "svs start fail: %d\n", ret);
    + goto svs_probe_iounmap;
    + }
    +
    + return 0;
    +
    +svs_probe_iounmap:
    + iounmap(svsp->base);
    +
    +svs_probe_clk_disable:
    + clk_disable_unprepare(svsp->main_clk);
    +
    + return ret;
    +}
    +
    +static SIMPLE_DEV_PM_OPS(svs_pm_ops, svs_suspend, svs_resume);
    +
    +static struct platform_driver svs_driver = {
    + .probe = svs_probe,
    + .driver = {
    + .name = "mtk-svs",
    + .pm = &svs_pm_ops,
    + .of_match_table = of_match_ptr(svs_of_match),
    + },
    +};
    +
    +module_platform_driver(svs_driver);
    +
    +MODULE_AUTHOR("Roger Lu <roger.lu@mediatek.com>");
    +MODULE_DESCRIPTION("MediaTek SVS driver");
    +MODULE_LICENSE("GPL v2");
    --
    2.18.0
    \
     
     \ /
      Last update: 2022-01-07 10:52    [W:3.811 / U:0.760 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site