2469 lines
63 KiB
C
2469 lines
63 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
/*
|
||
|
* Copyright (C) 2022 MediaTek Inc.
|
||
|
*/
|
||
|
|
||
|
#include <linux/bitfield.h>
|
||
|
#include <linux/bits.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/completion.h>
|
||
|
#include <linux/cpuidle.h>
|
||
|
#include <linux/debugfs.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_runtime.h>
|
||
|
#include <linux/regulator/consumer.h>
|
||
|
#include <linux/reset.h>
|
||
|
#include <linux/seq_file.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <linux/thermal.h>
|
||
|
|
||
|
/* svs bank 1-line software 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 2-line type */
|
||
|
#define SVSB_LOW BIT(8)
|
||
|
#define SVSB_HIGH BIT(9)
|
||
|
|
||
|
/* svs bank mode support */
|
||
|
#define SVSB_MODE_ALL_DISABLE 0
|
||
|
#define SVSB_MODE_INIT01 BIT(1)
|
||
|
#define SVSB_MODE_INIT02 BIT(2)
|
||
|
#define SVSB_MODE_MON BIT(3)
|
||
|
|
||
|
/* 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)
|
||
|
#define SVSB_MON_VOLT_IGNORE BIT(16)
|
||
|
#define SVSB_REMOVE_DVTFIXED_VOLT BIT(24)
|
||
|
|
||
|
/* svs bank register fields and common configuration */
|
||
|
#define SVSB_PTPCONFIG_DETMAX GENMASK(15, 0)
|
||
|
#define SVSB_DET_MAX FIELD_PREP(SVSB_PTPCONFIG_DETMAX, 0xffff)
|
||
|
#define SVSB_DET_WINDOW 0xa28
|
||
|
|
||
|
/* DESCHAR */
|
||
|
#define SVSB_DESCHAR_FLD_MDES GENMASK(7, 0)
|
||
|
#define SVSB_DESCHAR_FLD_BDES GENMASK(15, 8)
|
||
|
|
||
|
/* TEMPCHAR */
|
||
|
#define SVSB_TEMPCHAR_FLD_DVT_FIXED GENMASK(7, 0)
|
||
|
#define SVSB_TEMPCHAR_FLD_MTDES GENMASK(15, 8)
|
||
|
#define SVSB_TEMPCHAR_FLD_VCO GENMASK(23, 16)
|
||
|
|
||
|
/* DETCHAR */
|
||
|
#define SVSB_DETCHAR_FLD_DCMDET GENMASK(7, 0)
|
||
|
#define SVSB_DETCHAR_FLD_DCBDET GENMASK(15, 8)
|
||
|
|
||
|
/* SVSEN (PTPEN) */
|
||
|
#define SVSB_PTPEN_INIT01 BIT(0)
|
||
|
#define SVSB_PTPEN_MON BIT(1)
|
||
|
#define SVSB_PTPEN_INIT02 (SVSB_PTPEN_INIT01 | BIT(2))
|
||
|
#define SVSB_PTPEN_OFF 0x0
|
||
|
|
||
|
/* FREQPCTS */
|
||
|
#define SVSB_FREQPCTS_FLD_PCT0_4 GENMASK(7, 0)
|
||
|
#define SVSB_FREQPCTS_FLD_PCT1_5 GENMASK(15, 8)
|
||
|
#define SVSB_FREQPCTS_FLD_PCT2_6 GENMASK(23, 16)
|
||
|
#define SVSB_FREQPCTS_FLD_PCT3_7 GENMASK(31, 24)
|
||
|
|
||
|
/* INTSTS */
|
||
|
#define SVSB_INTSTS_VAL_CLEAN 0x00ffffff
|
||
|
#define SVSB_INTSTS_F0_COMPLETE BIT(0)
|
||
|
#define SVSB_INTSTS_FLD_MONVOP GENMASK(23, 16)
|
||
|
#define SVSB_RUNCONFIG_DEFAULT 0x80000000
|
||
|
|
||
|
/* LIMITVALS */
|
||
|
#define SVSB_LIMITVALS_FLD_DTLO GENMASK(7, 0)
|
||
|
#define SVSB_LIMITVALS_FLD_DTHI GENMASK(15, 8)
|
||
|
#define SVSB_LIMITVALS_FLD_VMIN GENMASK(23, 16)
|
||
|
#define SVSB_LIMITVALS_FLD_VMAX GENMASK(31, 24)
|
||
|
#define SVSB_VAL_DTHI 0x1
|
||
|
#define SVSB_VAL_DTLO 0xfe
|
||
|
|
||
|
/* INTEN */
|
||
|
#define SVSB_INTEN_F0EN BIT(0)
|
||
|
#define SVSB_INTEN_DACK0UPEN BIT(8)
|
||
|
#define SVSB_INTEN_DC0EN BIT(9)
|
||
|
#define SVSB_INTEN_DC1EN BIT(10)
|
||
|
#define SVSB_INTEN_DACK0LOEN BIT(11)
|
||
|
#define SVSB_INTEN_INITPROD_OVF_EN BIT(12)
|
||
|
#define SVSB_INTEN_INITSUM_OVF_EN BIT(14)
|
||
|
#define SVSB_INTEN_MONVOPEN GENMASK(23, 16)
|
||
|
#define SVSB_INTEN_INIT0x (SVSB_INTEN_F0EN | SVSB_INTEN_DACK0UPEN | \
|
||
|
SVSB_INTEN_DC0EN | SVSB_INTEN_DC1EN | \
|
||
|
SVSB_INTEN_DACK0LOEN | \
|
||
|
SVSB_INTEN_INITPROD_OVF_EN | \
|
||
|
SVSB_INTEN_INITSUM_OVF_EN)
|
||
|
|
||
|
/* TSCALCS */
|
||
|
#define SVSB_TSCALCS_FLD_MTS GENMASK(11, 0)
|
||
|
#define SVSB_TSCALCS_FLD_BTS GENMASK(23, 12)
|
||
|
|
||
|
/* INIT2VALS */
|
||
|
#define SVSB_INIT2VALS_FLD_DCVOFFSETIN GENMASK(15, 0)
|
||
|
#define SVSB_INIT2VALS_FLD_AGEVOFFSETIN GENMASK(31, 16)
|
||
|
|
||
|
/* VOPS */
|
||
|
#define SVSB_VOPS_FLD_VOP0_4 GENMASK(7, 0)
|
||
|
#define SVSB_VOPS_FLD_VOP1_5 GENMASK(15, 8)
|
||
|
#define SVSB_VOPS_FLD_VOP2_6 GENMASK(23, 16)
|
||
|
#define SVSB_VOPS_FLD_VOP3_7 GENMASK(31, 24)
|
||
|
|
||
|
/* svs bank related setting */
|
||
|
#define BITS8 8
|
||
|
#define MAX_OPP_ENTRIES 16
|
||
|
#define REG_BYTES 4
|
||
|
#define SVSB_DC_SIGNED_BIT BIT(15)
|
||
|
#define SVSB_DET_CLK_EN BIT(31)
|
||
|
#define SVSB_TEMP_LOWER_BOUND 0xb2
|
||
|
#define SVSB_TEMP_UPPER_BOUND 0x64
|
||
|
|
||
|
static DEFINE_SPINLOCK(svs_lock);
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
#define debug_fops_ro(name) \
|
||
|
static int svs_##name##_debug_open(struct inode *inode, \
|
||
|
struct file *filp) \
|
||
|
{ \
|
||
|
return single_open(filp, svs_##name##_debug_show, \
|
||
|
inode->i_private); \
|
||
|
} \
|
||
|
static const struct file_operations svs_##name##_debug_fops = { \
|
||
|
.owner = THIS_MODULE, \
|
||
|
.open = svs_##name##_debug_open, \
|
||
|
.read = seq_read, \
|
||
|
.llseek = seq_lseek, \
|
||
|
.release = single_release, \
|
||
|
}
|
||
|
|
||
|
#define debug_fops_rw(name) \
|
||
|
static int svs_##name##_debug_open(struct inode *inode, \
|
||
|
struct file *filp) \
|
||
|
{ \
|
||
|
return single_open(filp, svs_##name##_debug_show, \
|
||
|
inode->i_private); \
|
||
|
} \
|
||
|
static const struct file_operations svs_##name##_debug_fops = { \
|
||
|
.owner = THIS_MODULE, \
|
||
|
.open = svs_##name##_debug_open, \
|
||
|
.read = seq_read, \
|
||
|
.write = svs_##name##_debug_write, \
|
||
|
.llseek = seq_lseek, \
|
||
|
.release = single_release, \
|
||
|
}
|
||
|
|
||
|
#define svs_dentry_data(name) {__stringify(name), &svs_##name##_debug_fops}
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
* 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_MON: svs bank can provide voltages with thermal effect
|
||
|
* @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 -> SVSB_PHASE_MON
|
||
|
*/
|
||
|
enum svsb_phase {
|
||
|
SVSB_PHASE_ERROR = 0,
|
||
|
SVSB_PHASE_INIT01,
|
||
|
SVSB_PHASE_INIT02,
|
||
|
SVSB_PHASE_MON,
|
||
|
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
|
||
|
* @base: svs platform register base
|
||
|
* @dev: svs platform device
|
||
|
* @main_clk: main clock for svs bank
|
||
|
* @pbank: svs bank pointer needing to be protected by spin_lock section
|
||
|
* @banks: svs banks that svs platform supports
|
||
|
* @rst: svs platform reset control
|
||
|
* @efuse_max: total number of svs efuse
|
||
|
* @tefuse_max: total number of thermal efuse
|
||
|
* @regs: svs platform registers map
|
||
|
* @bank_max: total number of svs banks
|
||
|
* @efuse: svs efuse data received from NVMEM framework
|
||
|
* @tefuse: thermal efuse data received from NVMEM framework
|
||
|
*/
|
||
|
struct svs_platform {
|
||
|
void __iomem *base;
|
||
|
struct device *dev;
|
||
|
struct clk *main_clk;
|
||
|
struct svs_bank *pbank;
|
||
|
struct svs_bank *banks;
|
||
|
struct reset_control *rst;
|
||
|
size_t efuse_max;
|
||
|
size_t tefuse_max;
|
||
|
const u32 *regs;
|
||
|
u32 bank_max;
|
||
|
u32 *efuse;
|
||
|
u32 *tefuse;
|
||
|
};
|
||
|
|
||
|
struct svs_platform_data {
|
||
|
char *name;
|
||
|
struct svs_bank *banks;
|
||
|
bool (*efuse_parsing)(struct svs_platform *svsp);
|
||
|
int (*probe)(struct svs_platform *svsp);
|
||
|
const u32 *regs;
|
||
|
u32 bank_max;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* struct svs_bank - svs bank representation
|
||
|
* @dev: bank device
|
||
|
* @opp_dev: device for opp table/buck control
|
||
|
* @init_completion: the timeout completion for bank init
|
||
|
* @buck: regulator used by opp_dev
|
||
|
* @tzd: thermal zone device for getting temperature
|
||
|
* @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
|
||
|
* @tzone_name: thermal zone name
|
||
|
* @phase: bank current phase
|
||
|
* @volt_od: bank voltage overdrive
|
||
|
* @reg_data: bank register data in different phase for debug purpose
|
||
|
* @pm_runtime_enabled_count: bank pm runtime enabled count
|
||
|
* @mode_support: bank mode support.
|
||
|
* @freq_base: reference frequency for bank init
|
||
|
* @turn_freq_base: refenrece frequency for 2-line turn point
|
||
|
* @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
|
||
|
* @temp: bank temperature
|
||
|
* @tzone_htemp: thermal zone high temperature threshold
|
||
|
* @tzone_htemp_voffset: thermal zone high temperature voltage offset
|
||
|
* @tzone_ltemp: thermal zone low temperature threshold
|
||
|
* @tzone_ltemp_voffset: thermal zone low temperature voltage offset
|
||
|
* @bts: svs efuse data
|
||
|
* @mts: svs efuse data
|
||
|
* @bdes: svs efuse data
|
||
|
* @mdes: svs efuse data
|
||
|
* @mtdes: svs efuse data
|
||
|
* @dcbdet: svs efuse data
|
||
|
* @dcmdet: svs efuse data
|
||
|
* @turn_pt: 2-line turn point tells which opp_volt calculated by high/low bank
|
||
|
* @type: bank type to represent it is 2-line (high/low) bank or 1-line bank
|
||
|
*
|
||
|
* Svs bank will generate suitalbe voltages by below general math equation
|
||
|
* and provide these voltages to opp voltage table.
|
||
|
*
|
||
|
* opp_volt[i] = (volt[i] * volt_step) + volt_base;
|
||
|
*/
|
||
|
struct svs_bank {
|
||
|
struct device *dev;
|
||
|
struct device *opp_dev;
|
||
|
struct completion init_completion;
|
||
|
struct regulator *buck;
|
||
|
struct thermal_zone_device *tzd;
|
||
|
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;
|
||
|
char *tzone_name;
|
||
|
enum svsb_phase phase;
|
||
|
s32 volt_od;
|
||
|
u32 reg_data[SVSB_PHASE_MAX][SVS_REG_MAX];
|
||
|
u32 pm_runtime_enabled_count;
|
||
|
u32 mode_support;
|
||
|
u32 freq_base;
|
||
|
u32 turn_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 temp;
|
||
|
u32 tzone_htemp;
|
||
|
u32 tzone_htemp_voffset;
|
||
|
u32 tzone_ltemp;
|
||
|
u32 tzone_ltemp_voffset;
|
||
|
u32 bts;
|
||
|
u32 mts;
|
||
|
u32 bdes;
|
||
|
u32 mdes;
|
||
|
u32 mtdes;
|
||
|
u32 dcbdet;
|
||
|
u32 dcmdet;
|
||
|
u32 turn_pt;
|
||
|
u32 type;
|
||
|
};
|
||
|
|
||
|
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 u32 svs_opp_volt_to_bank_volt(u32 opp_u_volt, u32 svsb_volt_step,
|
||
|
u32 svsb_volt_base)
|
||
|
{
|
||
|
return (opp_u_volt - svsb_volt_base) / svsb_volt_step;
|
||
|
}
|
||
|
|
||
|
static int svs_sync_bank_volts_from_opp(struct svs_bank *svsb)
|
||
|
{
|
||
|
struct dev_pm_opp *opp;
|
||
|
u32 i, opp_u_volt;
|
||
|
|
||
|
for (i = 0; i < svsb->opp_count; i++) {
|
||
|
opp = dev_pm_opp_find_freq_exact(svsb->opp_dev,
|
||
|
svsb->opp_dfreq[i],
|
||
|
true);
|
||
|
if (IS_ERR(opp)) {
|
||
|
dev_err(svsb->dev, "cannot find freq = %u (%ld)\n",
|
||
|
svsb->opp_dfreq[i], PTR_ERR(opp));
|
||
|
return PTR_ERR(opp);
|
||
|
}
|
||
|
|
||
|
opp_u_volt = dev_pm_opp_get_voltage(opp);
|
||
|
svsb->volt[i] = svs_opp_volt_to_bank_volt(opp_u_volt,
|
||
|
svsb->volt_step,
|
||
|
svsb->volt_base);
|
||
|
dev_pm_opp_put(opp);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int svs_adjust_pm_opp_volts(struct svs_bank *svsb)
|
||
|
{
|
||
|
int ret = -EPERM, tzone_temp = 0;
|
||
|
u32 i, svsb_volt, opp_volt, temp_voffset = 0, opp_start, opp_stop;
|
||
|
|
||
|
mutex_lock(&svsb->lock);
|
||
|
|
||
|
/*
|
||
|
* 2-line bank updates its corresponding opp volts.
|
||
|
* 1-line bank updates all opp volts.
|
||
|
*/
|
||
|
if (svsb->type == SVSB_HIGH) {
|
||
|
opp_start = 0;
|
||
|
opp_stop = svsb->turn_pt;
|
||
|
} else if (svsb->type == SVSB_LOW) {
|
||
|
opp_start = svsb->turn_pt;
|
||
|
opp_stop = svsb->opp_count;
|
||
|
} else {
|
||
|
opp_start = 0;
|
||
|
opp_stop = svsb->opp_count;
|
||
|
}
|
||
|
|
||
|
/* Get thermal effect */
|
||
|
if (svsb->phase == SVSB_PHASE_MON) {
|
||
|
ret = thermal_zone_get_temp(svsb->tzd, &tzone_temp);
|
||
|
if (ret || (svsb->temp > SVSB_TEMP_UPPER_BOUND &&
|
||
|
svsb->temp < SVSB_TEMP_LOWER_BOUND)) {
|
||
|
dev_err(svsb->dev, "%s: %d (0x%x), run default volts\n",
|
||
|
svsb->tzone_name, ret, svsb->temp);
|
||
|
svsb->phase = SVSB_PHASE_ERROR;
|
||
|
}
|
||
|
|
||
|
if (tzone_temp >= svsb->tzone_htemp)
|
||
|
temp_voffset += svsb->tzone_htemp_voffset;
|
||
|
else if (tzone_temp <= svsb->tzone_ltemp)
|
||
|
temp_voffset += svsb->tzone_ltemp_voffset;
|
||
|
|
||
|
/* 2-line bank update all opp volts when running mon mode */
|
||
|
if (svsb->type == SVSB_HIGH || svsb->type == SVSB_LOW) {
|
||
|
opp_start = 0;
|
||
|
opp_stop = svsb->opp_count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* vmin <= svsb_volt (opp_volt) <= default opp voltage */
|
||
|
for (i = opp_start; i < opp_stop; 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;
|
||
|
case SVSB_PHASE_MON:
|
||
|
svsb_volt = max(svsb->volt[i] + temp_voffset, 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;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
static int svs_dump_debug_show(struct seq_file *m, void *p)
|
||
|
{
|
||
|
struct svs_platform *svsp = (struct svs_platform *)m->private;
|
||
|
struct svs_bank *svsb;
|
||
|
unsigned long svs_reg_addr;
|
||
|
u32 idx, i, j, bank_id;
|
||
|
|
||
|
for (i = 0; i < svsp->efuse_max; i++)
|
||
|
if (svsp->efuse && svsp->efuse[i])
|
||
|
seq_printf(m, "M_HW_RES%d = 0x%08x\n",
|
||
|
i, svsp->efuse[i]);
|
||
|
|
||
|
for (i = 0; i < svsp->tefuse_max; i++)
|
||
|
if (svsp->tefuse)
|
||
|
seq_printf(m, "THERMAL_EFUSE%d = 0x%08x\n",
|
||
|
i, svsp->tefuse[i]);
|
||
|
|
||
|
for (bank_id = 0, idx = 0; idx < svsp->bank_max; idx++, bank_id++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
|
||
|
for (i = SVSB_PHASE_INIT01; i <= SVSB_PHASE_MON; i++) {
|
||
|
seq_printf(m, "Bank_number = %u\n", bank_id);
|
||
|
|
||
|
if (i == SVSB_PHASE_INIT01 || i == SVSB_PHASE_INIT02)
|
||
|
seq_printf(m, "mode = init%d\n", i);
|
||
|
else if (i == SVSB_PHASE_MON)
|
||
|
seq_puts(m, "mode = mon\n");
|
||
|
else
|
||
|
seq_puts(m, "mode = error\n");
|
||
|
|
||
|
for (j = DESCHAR; j < SVS_REG_MAX; j++) {
|
||
|
svs_reg_addr = (unsigned long)(svsp->base +
|
||
|
svsp->regs[j]);
|
||
|
seq_printf(m, "0x%08lx = 0x%08x\n",
|
||
|
svs_reg_addr, svsb->reg_data[i][j]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
debug_fops_ro(dump);
|
||
|
|
||
|
static int svs_enable_debug_show(struct seq_file *m, void *v)
|
||
|
{
|
||
|
struct svs_bank *svsb = (struct svs_bank *)m->private;
|
||
|
|
||
|
switch (svsb->phase) {
|
||
|
case SVSB_PHASE_ERROR:
|
||
|
seq_puts(m, "disabled\n");
|
||
|
break;
|
||
|
case SVSB_PHASE_INIT01:
|
||
|
seq_puts(m, "init1\n");
|
||
|
break;
|
||
|
case SVSB_PHASE_INIT02:
|
||
|
seq_puts(m, "init2\n");
|
||
|
break;
|
||
|
case SVSB_PHASE_MON:
|
||
|
seq_puts(m, "mon mode\n");
|
||
|
break;
|
||
|
default:
|
||
|
seq_puts(m, "unknown\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t svs_enable_debug_write(struct file *filp,
|
||
|
const char __user *buffer,
|
||
|
size_t count, loff_t *pos)
|
||
|
{
|
||
|
struct svs_bank *svsb = file_inode(filp)->i_private;
|
||
|
struct svs_platform *svsp = dev_get_drvdata(svsb->dev);
|
||
|
unsigned long flags;
|
||
|
int enabled, ret;
|
||
|
char *buf = NULL;
|
||
|
|
||
|
if (count >= PAGE_SIZE)
|
||
|
return -EINVAL;
|
||
|
|
||
|
buf = (char *)memdup_user_nul(buffer, count);
|
||
|
if (IS_ERR(buf))
|
||
|
return PTR_ERR(buf);
|
||
|
|
||
|
ret = kstrtoint(buf, 10, &enabled);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (!enabled) {
|
||
|
spin_lock_irqsave(&svs_lock, flags);
|
||
|
svsp->pbank = svsb;
|
||
|
svsb->mode_support = SVSB_MODE_ALL_DISABLE;
|
||
|
svs_switch_bank(svsp);
|
||
|
svs_writel_relaxed(svsp, SVSB_PTPEN_OFF, SVSEN);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTSTS_VAL_CLEAN, INTSTS);
|
||
|
spin_unlock_irqrestore(&svs_lock, flags);
|
||
|
|
||
|
svsb->phase = SVSB_PHASE_ERROR;
|
||
|
svs_adjust_pm_opp_volts(svsb);
|
||
|
}
|
||
|
|
||
|
kfree(buf);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
debug_fops_rw(enable);
|
||
|
|
||
|
static int svs_status_debug_show(struct seq_file *m, void *v)
|
||
|
{
|
||
|
struct svs_bank *svsb = (struct svs_bank *)m->private;
|
||
|
struct dev_pm_opp *opp;
|
||
|
int tzone_temp = 0, ret;
|
||
|
u32 i;
|
||
|
|
||
|
ret = thermal_zone_get_temp(svsb->tzd, &tzone_temp);
|
||
|
if (ret)
|
||
|
seq_printf(m, "%s: temperature ignore, turn_pt = %u\n",
|
||
|
svsb->name, svsb->turn_pt);
|
||
|
else
|
||
|
seq_printf(m, "%s: temperature = %d, turn_pt = %u\n",
|
||
|
svsb->name, tzone_temp, svsb->turn_pt);
|
||
|
|
||
|
for (i = 0; i < svsb->opp_count; i++) {
|
||
|
opp = dev_pm_opp_find_freq_exact(svsb->opp_dev,
|
||
|
svsb->opp_dfreq[i], true);
|
||
|
if (IS_ERR(opp)) {
|
||
|
seq_printf(m, "%s: cannot find freq = %u (%ld)\n",
|
||
|
svsb->name, svsb->opp_dfreq[i],
|
||
|
PTR_ERR(opp));
|
||
|
return PTR_ERR(opp);
|
||
|
}
|
||
|
|
||
|
seq_printf(m, "opp_freq[%02u]: %u, opp_volt[%02u]: %lu, ",
|
||
|
i, svsb->opp_dfreq[i], i,
|
||
|
dev_pm_opp_get_voltage(opp));
|
||
|
seq_printf(m, "svsb_volt[%02u]: 0x%x, freq_pct[%02u]: %u\n",
|
||
|
i, svsb->volt[i], i, svsb->freq_pct[i]);
|
||
|
dev_pm_opp_put(opp);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
debug_fops_ro(status);
|
||
|
|
||
|
static int svs_create_debug_cmds(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct svs_bank *svsb;
|
||
|
struct dentry *svs_dir, *svsb_dir, *file_entry;
|
||
|
const char *d = "/sys/kernel/debug/svs";
|
||
|
u32 i, idx;
|
||
|
|
||
|
struct svs_dentry {
|
||
|
const char *name;
|
||
|
const struct file_operations *fops;
|
||
|
};
|
||
|
|
||
|
struct svs_dentry svs_entries[] = {
|
||
|
svs_dentry_data(dump),
|
||
|
};
|
||
|
|
||
|
struct svs_dentry svsb_entries[] = {
|
||
|
svs_dentry_data(enable),
|
||
|
svs_dentry_data(status),
|
||
|
};
|
||
|
|
||
|
svs_dir = debugfs_create_dir("svs", NULL);
|
||
|
if (IS_ERR(svs_dir)) {
|
||
|
dev_err(svsp->dev, "cannot create %s: %ld\n",
|
||
|
d, PTR_ERR(svs_dir));
|
||
|
return PTR_ERR(svs_dir);
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(svs_entries); i++) {
|
||
|
file_entry = debugfs_create_file(svs_entries[i].name, 0664,
|
||
|
svs_dir, svsp,
|
||
|
svs_entries[i].fops);
|
||
|
if (IS_ERR(file_entry)) {
|
||
|
dev_err(svsp->dev, "cannot create %s/%s: %ld\n",
|
||
|
d, svs_entries[i].name, PTR_ERR(file_entry));
|
||
|
return PTR_ERR(file_entry);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
|
||
|
if (svsb->mode_support == SVSB_MODE_ALL_DISABLE)
|
||
|
continue;
|
||
|
|
||
|
svsb_dir = debugfs_create_dir(svsb->name, svs_dir);
|
||
|
if (IS_ERR(svsb_dir)) {
|
||
|
dev_err(svsp->dev, "cannot create %s/%s: %ld\n",
|
||
|
d, svsb->name, PTR_ERR(svsb_dir));
|
||
|
return PTR_ERR(svsb_dir);
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(svsb_entries); i++) {
|
||
|
file_entry = debugfs_create_file(svsb_entries[i].name,
|
||
|
0664, svsb_dir, svsb,
|
||
|
svsb_entries[i].fops);
|
||
|
if (IS_ERR(file_entry)) {
|
||
|
dev_err(svsp->dev, "no %s/%s/%s?: %ld\n",
|
||
|
d, svsb->name, svsb_entries[i].name,
|
||
|
PTR_ERR(file_entry));
|
||
|
return PTR_ERR(file_entry);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif /* CONFIG_DEBUG_FS */
|
||
|
|
||
|
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_v3(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct svs_bank *svsb = svsp->pbank;
|
||
|
u32 i, j, *vop, vop74, vop30, turn_pt = svsb->turn_pt;
|
||
|
u32 b_sft, shift_byte = 0, opp_start = 0, opp_stop = 0;
|
||
|
u32 middle_index = (svsb->opp_count / 2);
|
||
|
|
||
|
if (svsb->phase == SVSB_PHASE_MON &&
|
||
|
svsb->volt_flags & SVSB_MON_VOLT_IGNORE)
|
||
|
return;
|
||
|
|
||
|
vop74 = svs_readl_relaxed(svsp, VOP74);
|
||
|
vop30 = svs_readl_relaxed(svsp, VOP30);
|
||
|
|
||
|
/* Target is to set svsb->volt[] by algorithm */
|
||
|
if (turn_pt < middle_index) {
|
||
|
if (svsb->type == SVSB_HIGH) {
|
||
|
/* volt[0] ~ volt[turn_pt - 1] */
|
||
|
for (i = 0; i < turn_pt; i++) {
|
||
|
b_sft = BITS8 * (shift_byte % REG_BYTES);
|
||
|
vop = (shift_byte < REG_BYTES) ? &vop30 :
|
||
|
&vop74;
|
||
|
svsb->volt[i] = (*vop >> b_sft) & GENMASK(7, 0);
|
||
|
shift_byte++;
|
||
|
}
|
||
|
} else if (svsb->type == SVSB_LOW) {
|
||
|
/* volt[turn_pt] + volt[j] ~ volt[opp_count - 1] */
|
||
|
j = svsb->opp_count - 7;
|
||
|
svsb->volt[turn_pt] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, vop30);
|
||
|
shift_byte++;
|
||
|
for (i = j; i < svsb->opp_count; i++) {
|
||
|
b_sft = BITS8 * (shift_byte % REG_BYTES);
|
||
|
vop = (shift_byte < REG_BYTES) ? &vop30 :
|
||
|
&vop74;
|
||
|
svsb->volt[i] = (*vop >> b_sft) & GENMASK(7, 0);
|
||
|
shift_byte++;
|
||
|
}
|
||
|
|
||
|
/* volt[turn_pt + 1] ~ volt[j - 1] by interpolate */
|
||
|
for (i = turn_pt + 1; i < j; i++)
|
||
|
svsb->volt[i] = interpolate(svsb->freq_pct[turn_pt],
|
||
|
svsb->freq_pct[j],
|
||
|
svsb->volt[turn_pt],
|
||
|
svsb->volt[j],
|
||
|
svsb->freq_pct[i]);
|
||
|
}
|
||
|
} else {
|
||
|
if (svsb->type == SVSB_HIGH) {
|
||
|
/* volt[0] + volt[j] ~ volt[turn_pt - 1] */
|
||
|
j = turn_pt - 7;
|
||
|
svsb->volt[0] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, vop30);
|
||
|
shift_byte++;
|
||
|
for (i = j; i < turn_pt; i++) {
|
||
|
b_sft = BITS8 * (shift_byte % REG_BYTES);
|
||
|
vop = (shift_byte < REG_BYTES) ? &vop30 :
|
||
|
&vop74;
|
||
|
svsb->volt[i] = (*vop >> b_sft) & GENMASK(7, 0);
|
||
|
shift_byte++;
|
||
|
}
|
||
|
|
||
|
/* volt[1] ~ volt[j - 1] by interpolate */
|
||
|
for (i = 1; i < j; i++)
|
||
|
svsb->volt[i] = interpolate(svsb->freq_pct[0],
|
||
|
svsb->freq_pct[j],
|
||
|
svsb->volt[0],
|
||
|
svsb->volt[j],
|
||
|
svsb->freq_pct[i]);
|
||
|
} else if (svsb->type == SVSB_LOW) {
|
||
|
/* volt[turn_pt] ~ volt[opp_count - 1] */
|
||
|
for (i = turn_pt; i < svsb->opp_count; i++) {
|
||
|
b_sft = BITS8 * (shift_byte % REG_BYTES);
|
||
|
vop = (shift_byte < REG_BYTES) ? &vop30 :
|
||
|
&vop74;
|
||
|
svsb->volt[i] = (*vop >> b_sft) & GENMASK(7, 0);
|
||
|
shift_byte++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (svsb->type == SVSB_HIGH) {
|
||
|
opp_start = 0;
|
||
|
opp_stop = svsb->turn_pt;
|
||
|
} else if (svsb->type == SVSB_LOW) {
|
||
|
opp_start = svsb->turn_pt;
|
||
|
opp_stop = svsb->opp_count;
|
||
|
}
|
||
|
|
||
|
for (i = opp_start; i < opp_stop; i++)
|
||
|
if (svsb->volt_flags & SVSB_REMOVE_DVTFIXED_VOLT)
|
||
|
svsb->volt[i] -= svsb->dvt_fixed;
|
||
|
}
|
||
|
|
||
|
static void svs_set_bank_freq_pct_v3(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct svs_bank *svsb = svsp->pbank;
|
||
|
u32 i, j, *freq_pct, freq_pct74 = 0, freq_pct30 = 0;
|
||
|
u32 b_sft, shift_byte = 0, turn_pt;
|
||
|
u32 middle_index = (svsb->opp_count / 2);
|
||
|
|
||
|
for (i = 0; i < svsb->opp_count; i++) {
|
||
|
if (svsb->opp_dfreq[i] <= svsb->turn_freq_base) {
|
||
|
svsb->turn_pt = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
turn_pt = svsb->turn_pt;
|
||
|
|
||
|
/* Target is to fill out freq_pct74 / freq_pct30 by algorithm */
|
||
|
if (turn_pt < middle_index) {
|
||
|
if (svsb->type == SVSB_HIGH) {
|
||
|
/*
|
||
|
* If we don't handle this situation,
|
||
|
* SVSB_HIGH's FREQPCT74 / FREQPCT30 would keep "0"
|
||
|
* and this leads SVSB_LOW to work abnormally.
|
||
|
*/
|
||
|
if (turn_pt == 0)
|
||
|
freq_pct30 = svsb->freq_pct[0];
|
||
|
|
||
|
/* freq_pct[0] ~ freq_pct[turn_pt - 1] */
|
||
|
for (i = 0; i < turn_pt; i++) {
|
||
|
b_sft = BITS8 * (shift_byte % REG_BYTES);
|
||
|
freq_pct = (shift_byte < REG_BYTES) ?
|
||
|
&freq_pct30 : &freq_pct74;
|
||
|
*freq_pct |= (svsb->freq_pct[i] << b_sft);
|
||
|
shift_byte++;
|
||
|
}
|
||
|
} else if (svsb->type == SVSB_LOW) {
|
||
|
/*
|
||
|
* freq_pct[turn_pt] +
|
||
|
* freq_pct[opp_count - 7] ~ freq_pct[opp_count -1]
|
||
|
*/
|
||
|
freq_pct30 = svsb->freq_pct[turn_pt];
|
||
|
shift_byte++;
|
||
|
j = svsb->opp_count - 7;
|
||
|
for (i = j; i < svsb->opp_count; i++) {
|
||
|
b_sft = BITS8 * (shift_byte % REG_BYTES);
|
||
|
freq_pct = (shift_byte < REG_BYTES) ?
|
||
|
&freq_pct30 : &freq_pct74;
|
||
|
*freq_pct |= (svsb->freq_pct[i] << b_sft);
|
||
|
shift_byte++;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if (svsb->type == SVSB_HIGH) {
|
||
|
/*
|
||
|
* freq_pct[0] +
|
||
|
* freq_pct[turn_pt - 7] ~ freq_pct[turn_pt - 1]
|
||
|
*/
|
||
|
freq_pct30 = svsb->freq_pct[0];
|
||
|
shift_byte++;
|
||
|
j = turn_pt - 7;
|
||
|
for (i = j; i < turn_pt; i++) {
|
||
|
b_sft = BITS8 * (shift_byte % REG_BYTES);
|
||
|
freq_pct = (shift_byte < REG_BYTES) ?
|
||
|
&freq_pct30 : &freq_pct74;
|
||
|
*freq_pct |= (svsb->freq_pct[i] << b_sft);
|
||
|
shift_byte++;
|
||
|
}
|
||
|
} else if (svsb->type == SVSB_LOW) {
|
||
|
/* freq_pct[turn_pt] ~ freq_pct[opp_count - 1] */
|
||
|
for (i = turn_pt; i < svsb->opp_count; i++) {
|
||
|
b_sft = BITS8 * (shift_byte % REG_BYTES);
|
||
|
freq_pct = (shift_byte < REG_BYTES) ?
|
||
|
&freq_pct30 : &freq_pct74;
|
||
|
*freq_pct |= (svsb->freq_pct[i] << b_sft);
|
||
|
shift_byte++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
svs_writel_relaxed(svsp, freq_pct74, FREQPCT74);
|
||
|
svs_writel_relaxed(svsp, freq_pct30, FREQPCT30);
|
||
|
}
|
||
|
|
||
|
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] = FIELD_GET(SVSB_VOPS_FLD_VOP3_7, temp);
|
||
|
svsb->volt[12] = FIELD_GET(SVSB_VOPS_FLD_VOP2_6, temp);
|
||
|
svsb->volt[10] = FIELD_GET(SVSB_VOPS_FLD_VOP1_5, temp);
|
||
|
svsb->volt[8] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, temp);
|
||
|
|
||
|
temp = svs_readl_relaxed(svsp, VOP30);
|
||
|
svsb->volt[6] = FIELD_GET(SVSB_VOPS_FLD_VOP3_7, temp);
|
||
|
svsb->volt[4] = FIELD_GET(SVSB_VOPS_FLD_VOP2_6, temp);
|
||
|
svsb->volt[2] = FIELD_GET(SVSB_VOPS_FLD_VOP1_5, temp);
|
||
|
svsb->volt[0] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, temp);
|
||
|
|
||
|
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;
|
||
|
u32 freqpct74_val, freqpct30_val;
|
||
|
|
||
|
freqpct74_val = FIELD_PREP(SVSB_FREQPCTS_FLD_PCT0_4, svsb->freq_pct[8]) |
|
||
|
FIELD_PREP(SVSB_FREQPCTS_FLD_PCT1_5, svsb->freq_pct[10]) |
|
||
|
FIELD_PREP(SVSB_FREQPCTS_FLD_PCT2_6, svsb->freq_pct[12]) |
|
||
|
FIELD_PREP(SVSB_FREQPCTS_FLD_PCT3_7, svsb->freq_pct[14]);
|
||
|
|
||
|
freqpct30_val = FIELD_PREP(SVSB_FREQPCTS_FLD_PCT0_4, svsb->freq_pct[0]) |
|
||
|
FIELD_PREP(SVSB_FREQPCTS_FLD_PCT1_5, svsb->freq_pct[2]) |
|
||
|
FIELD_PREP(SVSB_FREQPCTS_FLD_PCT2_6, svsb->freq_pct[4]) |
|
||
|
FIELD_PREP(SVSB_FREQPCTS_FLD_PCT3_7, svsb->freq_pct[6]);
|
||
|
|
||
|
svs_writel_relaxed(svsp, freqpct74_val, FREQPCT74);
|
||
|
svs_writel_relaxed(svsp, freqpct30_val, 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, init2vals, ts_calcs;
|
||
|
|
||
|
svs_switch_bank(svsp);
|
||
|
|
||
|
des_char = FIELD_PREP(SVSB_DESCHAR_FLD_BDES, svsb->bdes) |
|
||
|
FIELD_PREP(SVSB_DESCHAR_FLD_MDES, svsb->mdes);
|
||
|
svs_writel_relaxed(svsp, des_char, DESCHAR);
|
||
|
|
||
|
temp_char = FIELD_PREP(SVSB_TEMPCHAR_FLD_VCO, svsb->vco) |
|
||
|
FIELD_PREP(SVSB_TEMPCHAR_FLD_MTDES, svsb->mtdes) |
|
||
|
FIELD_PREP(SVSB_TEMPCHAR_FLD_DVT_FIXED, svsb->dvt_fixed);
|
||
|
svs_writel_relaxed(svsp, temp_char, TEMPCHAR);
|
||
|
|
||
|
det_char = FIELD_PREP(SVSB_DETCHAR_FLD_DCBDET, svsb->dcbdet) |
|
||
|
FIELD_PREP(SVSB_DETCHAR_FLD_DCMDET, 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 = FIELD_PREP(SVSB_LIMITVALS_FLD_DTLO, SVSB_VAL_DTLO) |
|
||
|
FIELD_PREP(SVSB_LIMITVALS_FLD_DTHI, SVSB_VAL_DTHI) |
|
||
|
FIELD_PREP(SVSB_LIMITVALS_FLD_VMIN, svsb->vmin) |
|
||
|
FIELD_PREP(SVSB_LIMITVALS_FLD_VMAX, svsb->vmax);
|
||
|
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_VAL_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_PTPEN_INIT01, SVSEN);
|
||
|
break;
|
||
|
case SVSB_PHASE_INIT02:
|
||
|
init2vals = FIELD_PREP(SVSB_INIT2VALS_FLD_AGEVOFFSETIN, svsb->age_voffset_in) |
|
||
|
FIELD_PREP(SVSB_INIT2VALS_FLD_DCVOFFSETIN, svsb->dc_voffset_in);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTEN_INIT0x, INTEN);
|
||
|
svs_writel_relaxed(svsp, init2vals, INIT2VALS);
|
||
|
svs_writel_relaxed(svsp, SVSB_PTPEN_INIT02, SVSEN);
|
||
|
break;
|
||
|
case SVSB_PHASE_MON:
|
||
|
ts_calcs = FIELD_PREP(SVSB_TSCALCS_FLD_BTS, svsb->bts) |
|
||
|
FIELD_PREP(SVSB_TSCALCS_FLD_MTS, svsb->mts);
|
||
|
svs_writel_relaxed(svsp, ts_calcs, TSCALCS);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTEN_MONVOPEN, INTEN);
|
||
|
svs_writel_relaxed(svsp, SVSB_PTPEN_MON, SVSEN);
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(svsb->dev, "requested unknown target phase: %u\n",
|
||
|
target_phase);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void svs_save_bank_register_data(struct svs_platform *svsp,
|
||
|
enum svsb_phase phase)
|
||
|
{
|
||
|
struct svs_bank *svsb = svsp->pbank;
|
||
|
enum svs_reg_index rg_i;
|
||
|
|
||
|
for (rg_i = DESCHAR; rg_i < SVS_REG_MAX; rg_i++)
|
||
|
svsb->reg_data[phase][rg_i] = svs_readl_relaxed(svsp, rg_i);
|
||
|
}
|
||
|
|
||
|
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));
|
||
|
dev_err(svsb->dev, "TEMP = 0x%08x\n", svs_readl_relaxed(svsp, TEMP));
|
||
|
|
||
|
svs_save_bank_register_data(svsp, SVSB_PHASE_ERROR);
|
||
|
|
||
|
svsb->phase = SVSB_PHASE_ERROR;
|
||
|
svs_writel_relaxed(svsp, SVSB_PTPEN_OFF, SVSEN);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTSTS_VAL_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));
|
||
|
|
||
|
svs_save_bank_register_data(svsp, SVSB_PHASE_INIT01);
|
||
|
|
||
|
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_PTPEN_OFF, SVSEN);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTSTS_F0_COMPLETE, INTSTS);
|
||
|
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));
|
||
|
|
||
|
svs_save_bank_register_data(svsp, SVSB_PHASE_INIT02);
|
||
|
|
||
|
svsb->phase = SVSB_PHASE_INIT02;
|
||
|
svsb->get_volts(svsp);
|
||
|
|
||
|
svs_writel_relaxed(svsp, SVSB_PTPEN_OFF, SVSEN);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTSTS_F0_COMPLETE, INTSTS);
|
||
|
}
|
||
|
|
||
|
static inline void svs_mon_mode_isr_handler(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct svs_bank *svsb = svsp->pbank;
|
||
|
|
||
|
svs_save_bank_register_data(svsp, SVSB_PHASE_MON);
|
||
|
|
||
|
svsb->phase = SVSB_PHASE_MON;
|
||
|
svsb->get_volts(svsp);
|
||
|
|
||
|
svsb->temp = svs_readl_relaxed(svsp, TEMP) & GENMASK(7, 0);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTSTS_FLD_MONVOP, 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);
|
||
|
|
||
|
if (int_sts == SVSB_INTSTS_F0_COMPLETE &&
|
||
|
svs_en == SVSB_PTPEN_INIT01)
|
||
|
svs_init01_isr_handler(svsp);
|
||
|
else if (int_sts == SVSB_INTSTS_F0_COMPLETE &&
|
||
|
svs_en == SVSB_PTPEN_INIT02)
|
||
|
svs_init02_isr_handler(svsp);
|
||
|
else if (int_sts & SVSB_INTSTS_FLD_MONVOP)
|
||
|
svs_mon_mode_isr_handler(svsp);
|
||
|
else
|
||
|
svs_error_isr_handler(svsp);
|
||
|
|
||
|
spin_unlock_irqrestore(&svs_lock, flags);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
svs_adjust_pm_opp_volts(svsb);
|
||
|
|
||
|
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;
|
||
|
unsigned long flags, time_left;
|
||
|
bool search_done;
|
||
|
int ret = 0, r;
|
||
|
u32 opp_freq, opp_vboot, buck_volt, idx, i;
|
||
|
|
||
|
/* Keep CPUs' core power on for svs_init01 initialization */
|
||
|
cpuidle_pause_and_lock();
|
||
|
|
||
|
/* 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;
|
||
|
|
||
|
ret = regulator_enable(svsb->buck);
|
||
|
if (ret) {
|
||
|
dev_err(svsb->dev, "%s enable fail: %d\n",
|
||
|
svsb->buck_name, ret);
|
||
|
goto svs_init01_resume_cpuidle;
|
||
|
}
|
||
|
|
||
|
/* 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->opp_dev)) {
|
||
|
pm_runtime_enable(svsb->opp_dev);
|
||
|
svsb->pm_runtime_enabled_count++;
|
||
|
}
|
||
|
|
||
|
ret = pm_runtime_resume_and_get(svsb->opp_dev);
|
||
|
if (ret < 0) {
|
||
|
dev_err(svsb->dev, "mtcmos on fail: %d\n", ret);
|
||
|
goto svs_init01_resume_cpuidle;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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 svs_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 svs_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 svs_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 svs_init01_finish;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
svs_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->opp_dev);
|
||
|
if (r)
|
||
|
dev_err(svsb->dev, "mtcmos off fail: %d\n", r);
|
||
|
|
||
|
if (svsb->pm_runtime_enabled_count > 0) {
|
||
|
pm_runtime_disable(svsb->opp_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);
|
||
|
}
|
||
|
|
||
|
svs_init01_resume_cpuidle:
|
||
|
cpuidle_resume_and_unlock();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int svs_init02(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct svs_bank *svsb;
|
||
|
unsigned long flags, time_left;
|
||
|
int ret;
|
||
|
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");
|
||
|
ret = -EBUSY;
|
||
|
goto out_of_init02;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 2-line high/low bank update its corresponding opp voltages only.
|
||
|
* Therefore, we sync voltages from opp for high/low bank voltages
|
||
|
* consistency.
|
||
|
*/
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
|
||
|
if (!(svsb->mode_support & SVSB_MODE_INIT02))
|
||
|
continue;
|
||
|
|
||
|
if (svsb->type == SVSB_HIGH || svsb->type == SVSB_LOW) {
|
||
|
if (svs_sync_bank_volts_from_opp(svsb)) {
|
||
|
dev_err(svsb->dev, "sync volt fail\n");
|
||
|
ret = -EPERM;
|
||
|
goto out_of_init02;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
out_of_init02:
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
|
||
|
spin_lock_irqsave(&svs_lock, flags);
|
||
|
svsp->pbank = svsb;
|
||
|
svs_switch_bank(svsp);
|
||
|
svs_writel_relaxed(svsp, SVSB_PTPEN_OFF, SVSEN);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTSTS_VAL_CLEAN, INTSTS);
|
||
|
spin_unlock_irqrestore(&svs_lock, flags);
|
||
|
|
||
|
svsb->phase = SVSB_PHASE_ERROR;
|
||
|
svs_adjust_pm_opp_volts(svsb);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void svs_mon_mode(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct svs_bank *svsb;
|
||
|
unsigned long flags;
|
||
|
u32 idx;
|
||
|
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
|
||
|
if (!(svsb->mode_support & SVSB_MODE_MON))
|
||
|
continue;
|
||
|
|
||
|
spin_lock_irqsave(&svs_lock, flags);
|
||
|
svsp->pbank = svsb;
|
||
|
svs_set_bank_phase(svsp, SVSB_PHASE_MON);
|
||
|
spin_unlock_irqrestore(&svs_lock, flags);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
svs_mon_mode(svsp);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int svs_suspend(struct device *dev)
|
||
|
{
|
||
|
struct svs_platform *svsp = dev_get_drvdata(dev);
|
||
|
struct svs_bank *svsb;
|
||
|
unsigned long flags;
|
||
|
int ret;
|
||
|
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_PTPEN_OFF, SVSEN);
|
||
|
svs_writel_relaxed(svsp, SVSB_INTSTS_VAL_CLEAN, INTSTS);
|
||
|
spin_unlock_irqrestore(&svs_lock, flags);
|
||
|
|
||
|
svsb->phase = SVSB_PHASE_ERROR;
|
||
|
svs_adjust_pm_opp_volts(svsb);
|
||
|
}
|
||
|
|
||
|
ret = reset_control_assert(svsp->rst);
|
||
|
if (ret) {
|
||
|
dev_err(svsp->dev, "cannot assert reset %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
clk_disable_unprepare(svsp->main_clk);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int svs_resume(struct device *dev)
|
||
|
{
|
||
|
struct svs_platform *svsp = dev_get_drvdata(dev);
|
||
|
int ret;
|
||
|
|
||
|
ret = clk_prepare_enable(svsp->main_clk);
|
||
|
if (ret) {
|
||
|
dev_err(svsp->dev, "cannot enable main_clk, disable svs\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = reset_control_deassert(svsp->rst);
|
||
|
if (ret) {
|
||
|
dev_err(svsp->dev, "cannot deassert reset %d\n", ret);
|
||
|
goto out_of_resume;
|
||
|
}
|
||
|
|
||
|
ret = svs_init02(svsp);
|
||
|
if (ret)
|
||
|
goto svs_resume_reset_assert;
|
||
|
|
||
|
svs_mon_mode(svsp);
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
svs_resume_reset_assert:
|
||
|
dev_err(svsp->dev, "assert reset: %d\n",
|
||
|
reset_control_assert(svsp->rst));
|
||
|
|
||
|
out_of_resume:
|
||
|
clk_disable_unprepare(svsp->main_clk);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
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:
|
||
|
if (svsb->type == SVSB_HIGH)
|
||
|
svsb->name = "SVSB_GPU_HIGH";
|
||
|
else if (svsb->type == SVSB_LOW)
|
||
|
svsb->name = "SVSB_GPU_LOW";
|
||
|
else
|
||
|
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 = devm_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);
|
||
|
|
||
|
if (svsb->mode_support & SVSB_MODE_INIT01) {
|
||
|
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);
|
||
|
return PTR_ERR(svsb->buck);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (svsb->mode_support & SVSB_MODE_MON) {
|
||
|
svsb->tzd = thermal_zone_get_zone_by_name(svsb->tzone_name);
|
||
|
if (IS_ERR(svsb->tzd)) {
|
||
|
dev_err(svsb->dev, "cannot get \"%s\" thermal zone\n",
|
||
|
svsb->tzone_name);
|
||
|
return PTR_ERR(svsb->tzd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 int svs_thermal_efuse_get_data(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct nvmem_cell *cell;
|
||
|
|
||
|
/* Thermal efuse parsing */
|
||
|
cell = nvmem_cell_get(svsp->dev, "t-calibration-data");
|
||
|
if (IS_ERR_OR_NULL(cell)) {
|
||
|
dev_err(svsp->dev, "no \"t-calibration-data\"? %ld\n", PTR_ERR(cell));
|
||
|
return PTR_ERR(cell);
|
||
|
}
|
||
|
|
||
|
svsp->tefuse = nvmem_cell_read(cell, &svsp->tefuse_max);
|
||
|
if (IS_ERR(svsp->tefuse)) {
|
||
|
dev_err(svsp->dev, "cannot read thermal efuse: %ld\n",
|
||
|
PTR_ERR(svsp->tefuse));
|
||
|
nvmem_cell_put(cell);
|
||
|
return PTR_ERR(svsp->tefuse);
|
||
|
}
|
||
|
|
||
|
svsp->tefuse_max /= sizeof(u32);
|
||
|
nvmem_cell_put(cell);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static bool svs_mt8192_efuse_parsing(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct svs_bank *svsb;
|
||
|
u32 idx, i, vmin, golden_temp;
|
||
|
int ret;
|
||
|
|
||
|
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[9]) {
|
||
|
dev_notice(svsp->dev, "svs_efuse[9] = 0x0?\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Svs efuse parsing */
|
||
|
vmin = (svsp->efuse[19] >> 4) & GENMASK(1, 0);
|
||
|
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
|
||
|
if (vmin == 0x1)
|
||
|
svsb->vmin = 0x1e;
|
||
|
|
||
|
if (svsb->type == SVSB_LOW) {
|
||
|
svsb->mtdes = svsp->efuse[10] & GENMASK(7, 0);
|
||
|
svsb->bdes = (svsp->efuse[10] >> 16) & GENMASK(7, 0);
|
||
|
svsb->mdes = (svsp->efuse[10] >> 24) & GENMASK(7, 0);
|
||
|
svsb->dcbdet = (svsp->efuse[17]) & GENMASK(7, 0);
|
||
|
svsb->dcmdet = (svsp->efuse[17] >> 8) & GENMASK(7, 0);
|
||
|
} else if (svsb->type == SVSB_HIGH) {
|
||
|
svsb->mtdes = svsp->efuse[9] & GENMASK(7, 0);
|
||
|
svsb->bdes = (svsp->efuse[9] >> 16) & GENMASK(7, 0);
|
||
|
svsb->mdes = (svsp->efuse[9] >> 24) & GENMASK(7, 0);
|
||
|
svsb->dcbdet = (svsp->efuse[17] >> 16) & GENMASK(7, 0);
|
||
|
svsb->dcmdet = (svsp->efuse[17] >> 24) & GENMASK(7, 0);
|
||
|
}
|
||
|
|
||
|
svsb->vmax += svsb->dvt_fixed;
|
||
|
}
|
||
|
|
||
|
ret = svs_thermal_efuse_get_data(svsp);
|
||
|
if (ret)
|
||
|
return false;
|
||
|
|
||
|
for (i = 0; i < svsp->tefuse_max; i++)
|
||
|
if (svsp->tefuse[i] != 0)
|
||
|
break;
|
||
|
|
||
|
if (i == svsp->tefuse_max)
|
||
|
golden_temp = 50; /* All thermal efuse data are 0 */
|
||
|
else
|
||
|
golden_temp = (svsp->tefuse[0] >> 24) & GENMASK(7, 0);
|
||
|
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
svsb->mts = 500;
|
||
|
svsb->bts = (((500 * golden_temp + 250460) / 1000) - 25) * 4;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool svs_mt8183_efuse_parsing(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct svs_bank *svsb;
|
||
|
int format[6], x_roomt[6], o_vtsmcu[5], o_vtsabb, tb_roomt = 0;
|
||
|
int adc_ge_t, adc_oe_t, ge, oe, gain, degc_cali, adc_cali_en_t;
|
||
|
int o_slope, o_slope_sign, ts_id;
|
||
|
u32 idx, i, ft_pgm, mts, temp0, temp1, temp2;
|
||
|
int ret;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ret = svs_thermal_efuse_get_data(svsp);
|
||
|
if (ret)
|
||
|
return false;
|
||
|
|
||
|
/* Thermal efuse parsing */
|
||
|
adc_ge_t = (svsp->tefuse[1] >> 22) & GENMASK(9, 0);
|
||
|
adc_oe_t = (svsp->tefuse[1] >> 12) & GENMASK(9, 0);
|
||
|
|
||
|
o_vtsmcu[0] = (svsp->tefuse[0] >> 17) & GENMASK(8, 0);
|
||
|
o_vtsmcu[1] = (svsp->tefuse[0] >> 8) & GENMASK(8, 0);
|
||
|
o_vtsmcu[2] = svsp->tefuse[1] & GENMASK(8, 0);
|
||
|
o_vtsmcu[3] = (svsp->tefuse[2] >> 23) & GENMASK(8, 0);
|
||
|
o_vtsmcu[4] = (svsp->tefuse[2] >> 5) & GENMASK(8, 0);
|
||
|
o_vtsabb = (svsp->tefuse[2] >> 14) & GENMASK(8, 0);
|
||
|
|
||
|
degc_cali = (svsp->tefuse[0] >> 1) & GENMASK(5, 0);
|
||
|
adc_cali_en_t = svsp->tefuse[0] & BIT(0);
|
||
|
o_slope_sign = (svsp->tefuse[0] >> 7) & BIT(0);
|
||
|
|
||
|
ts_id = (svsp->tefuse[1] >> 9) & BIT(0);
|
||
|
if (!ts_id) {
|
||
|
o_slope = 1534;
|
||
|
} else {
|
||
|
o_slope = (svsp->tefuse[0] >> 26) & GENMASK(5, 0);
|
||
|
if (!o_slope_sign)
|
||
|
o_slope = 1534 + o_slope * 10;
|
||
|
else
|
||
|
o_slope = 1534 - o_slope * 10;
|
||
|
}
|
||
|
|
||
|
if (adc_cali_en_t == 0 ||
|
||
|
adc_ge_t < 265 || adc_ge_t > 758 ||
|
||
|
adc_oe_t < 265 || adc_oe_t > 758 ||
|
||
|
o_vtsmcu[0] < -8 || o_vtsmcu[0] > 484 ||
|
||
|
o_vtsmcu[1] < -8 || o_vtsmcu[1] > 484 ||
|
||
|
o_vtsmcu[2] < -8 || o_vtsmcu[2] > 484 ||
|
||
|
o_vtsmcu[3] < -8 || o_vtsmcu[3] > 484 ||
|
||
|
o_vtsmcu[4] < -8 || o_vtsmcu[4] > 484 ||
|
||
|
o_vtsabb < -8 || o_vtsabb > 484 ||
|
||
|
degc_cali < 1 || degc_cali > 63) {
|
||
|
dev_err(svsp->dev, "bad thermal efuse, no mon mode\n");
|
||
|
goto remove_mt8183_svsb_mon_mode;
|
||
|
}
|
||
|
|
||
|
ge = ((adc_ge_t - 512) * 10000) / 4096;
|
||
|
oe = (adc_oe_t - 512);
|
||
|
gain = (10000 + ge);
|
||
|
|
||
|
format[0] = (o_vtsmcu[0] + 3350 - oe);
|
||
|
format[1] = (o_vtsmcu[1] + 3350 - oe);
|
||
|
format[2] = (o_vtsmcu[2] + 3350 - oe);
|
||
|
format[3] = (o_vtsmcu[3] + 3350 - oe);
|
||
|
format[4] = (o_vtsmcu[4] + 3350 - oe);
|
||
|
format[5] = (o_vtsabb + 3350 - oe);
|
||
|
|
||
|
for (i = 0; i < 6; i++)
|
||
|
x_roomt[i] = (((format[i] * 10000) / 4096) * 10000) / gain;
|
||
|
|
||
|
temp0 = (10000 * 100000 / gain) * 15 / 18;
|
||
|
mts = (temp0 * 10) / o_slope;
|
||
|
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
svsb->mts = mts;
|
||
|
|
||
|
switch (svsb->sw_id) {
|
||
|
case SVSB_CPU_LITTLE:
|
||
|
tb_roomt = x_roomt[3];
|
||
|
break;
|
||
|
case SVSB_CPU_BIG:
|
||
|
tb_roomt = x_roomt[4];
|
||
|
break;
|
||
|
case SVSB_CCI:
|
||
|
tb_roomt = x_roomt[3];
|
||
|
break;
|
||
|
case SVSB_GPU:
|
||
|
tb_roomt = x_roomt[1];
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(svsb->dev, "unknown sw_id: %u\n", svsb->sw_id);
|
||
|
goto remove_mt8183_svsb_mon_mode;
|
||
|
}
|
||
|
|
||
|
temp0 = (degc_cali * 10 / 2);
|
||
|
temp1 = ((10000 * 100000 / 4096 / gain) *
|
||
|
oe + tb_roomt * 10) * 15 / 18;
|
||
|
temp2 = temp1 * 100 / o_slope;
|
||
|
|
||
|
svsb->bts = (temp0 + temp2 - 250) * 4 / 10;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
|
||
|
remove_mt8183_svsb_mon_mode:
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
svsb->mode_support &= ~SVSB_MODE_MON;
|
||
|
}
|
||
|
|
||
|
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));
|
||
|
nvmem_cell_put(cell);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
svsp->efuse_max /= sizeof(u32);
|
||
|
nvmem_cell_put(cell);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
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_mt8192_platform_probe(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct device *dev;
|
||
|
struct svs_bank *svsb;
|
||
|
u32 idx;
|
||
|
|
||
|
svsp->rst = devm_reset_control_get_optional(svsp->dev, "svs_rst");
|
||
|
if (IS_ERR(svsp->rst))
|
||
|
return dev_err_probe(svsp->dev, PTR_ERR(svsp->rst),
|
||
|
"cannot get svs reset control\n");
|
||
|
|
||
|
dev = svs_add_device_link(svsp, "lvts");
|
||
|
if (IS_ERR(dev))
|
||
|
return dev_err_probe(svsp->dev, PTR_ERR(dev),
|
||
|
"failed to get lvts device\n");
|
||
|
|
||
|
for (idx = 0; idx < svsp->bank_max; idx++) {
|
||
|
svsb = &svsp->banks[idx];
|
||
|
|
||
|
if (svsb->type == SVSB_HIGH)
|
||
|
svsb->opp_dev = svs_add_device_link(svsp, "mali");
|
||
|
else if (svsb->type == SVSB_LOW)
|
||
|
svsb->opp_dev = svs_get_subsys_device(svsp, "mali");
|
||
|
|
||
|
if (IS_ERR(svsb->opp_dev))
|
||
|
return dev_err_probe(svsp->dev, PTR_ERR(svsb->opp_dev),
|
||
|
"failed to get OPP device for bank %d\n",
|
||
|
idx);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int svs_mt8183_platform_probe(struct svs_platform *svsp)
|
||
|
{
|
||
|
struct device *dev;
|
||
|
struct svs_bank *svsb;
|
||
|
u32 idx;
|
||
|
|
||
|
dev = svs_add_device_link(svsp, "thermal");
|
||
|
if (IS_ERR(dev))
|
||
|
return dev_err_probe(svsp->dev, PTR_ERR(dev),
|
||
|
"failed to get thermal device\n");
|
||
|
|
||
|
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, "gpu");
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(svsb->dev, "unknown sw_id: %u\n", svsb->sw_id);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (IS_ERR(svsb->opp_dev))
|
||
|
return dev_err_probe(svsp->dev, PTR_ERR(svsb->opp_dev),
|
||
|
"failed to get OPP device for bank %d\n",
|
||
|
idx);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct svs_bank svs_mt8192_banks[] = {
|
||
|
{
|
||
|
.sw_id = SVSB_GPU,
|
||
|
.type = SVSB_LOW,
|
||
|
.set_freq_pct = svs_set_bank_freq_pct_v3,
|
||
|
.get_volts = svs_get_bank_volts_v3,
|
||
|
.volt_flags = SVSB_REMOVE_DVTFIXED_VOLT,
|
||
|
.mode_support = SVSB_MODE_INIT02,
|
||
|
.opp_count = MAX_OPP_ENTRIES,
|
||
|
.freq_base = 688000000,
|
||
|
.turn_freq_base = 688000000,
|
||
|
.volt_step = 6250,
|
||
|
.volt_base = 400000,
|
||
|
.vmax = 0x60,
|
||
|
.vmin = 0x1a,
|
||
|
.age_config = 0x555555,
|
||
|
.dc_config = 0x1,
|
||
|
.dvt_fixed = 0x1,
|
||
|
.vco = 0x18,
|
||
|
.chk_shift = 0x87,
|
||
|
.core_sel = 0x0fff0100,
|
||
|
.int_st = BIT(0),
|
||
|
.ctl0 = 0x00540003,
|
||
|
},
|
||
|
{
|
||
|
.sw_id = SVSB_GPU,
|
||
|
.type = SVSB_HIGH,
|
||
|
.set_freq_pct = svs_set_bank_freq_pct_v3,
|
||
|
.get_volts = svs_get_bank_volts_v3,
|
||
|
.tzone_name = "gpu1",
|
||
|
.volt_flags = SVSB_REMOVE_DVTFIXED_VOLT |
|
||
|
SVSB_MON_VOLT_IGNORE,
|
||
|
.mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON,
|
||
|
.opp_count = MAX_OPP_ENTRIES,
|
||
|
.freq_base = 902000000,
|
||
|
.turn_freq_base = 688000000,
|
||
|
.volt_step = 6250,
|
||
|
.volt_base = 400000,
|
||
|
.vmax = 0x60,
|
||
|
.vmin = 0x1a,
|
||
|
.age_config = 0x555555,
|
||
|
.dc_config = 0x1,
|
||
|
.dvt_fixed = 0x6,
|
||
|
.vco = 0x18,
|
||
|
.chk_shift = 0x87,
|
||
|
.core_sel = 0x0fff0101,
|
||
|
.int_st = BIT(1),
|
||
|
.ctl0 = 0x00540003,
|
||
|
.tzone_htemp = 85000,
|
||
|
.tzone_htemp_voffset = 0,
|
||
|
.tzone_ltemp = 25000,
|
||
|
.tzone_ltemp_voffset = 7,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
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",
|
||
|
.tzone_name = "tzts2",
|
||
|
.volt_flags = SVSB_INIT01_PD_REQ |
|
||
|
SVSB_INIT01_VOLT_INC_ONLY,
|
||
|
.mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02 |
|
||
|
SVSB_MODE_MON,
|
||
|
.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,
|
||
|
.tzone_htemp = 85000,
|
||
|
.tzone_htemp_voffset = 0,
|
||
|
.tzone_ltemp = 25000,
|
||
|
.tzone_ltemp_voffset = 3,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static const struct svs_platform_data svs_mt8192_platform_data = {
|
||
|
.name = "mt8192-svs",
|
||
|
.banks = svs_mt8192_banks,
|
||
|
.efuse_parsing = svs_mt8192_efuse_parsing,
|
||
|
.probe = svs_mt8192_platform_probe,
|
||
|
.regs = svs_regs_v2,
|
||
|
.bank_max = ARRAY_SIZE(svs_mt8192_banks),
|
||
|
};
|
||
|
|
||
|
static const struct svs_platform_data svs_mt8183_platform_data = {
|
||
|
.name = "mt8183-svs",
|
||
|
.banks = svs_mt8183_banks,
|
||
|
.efuse_parsing = svs_mt8183_efuse_parsing,
|
||
|
.probe = svs_mt8183_platform_probe,
|
||
|
.regs = svs_regs_v2,
|
||
|
.bank_max = ARRAY_SIZE(svs_mt8183_banks),
|
||
|
};
|
||
|
|
||
|
static const struct of_device_id svs_of_match[] = {
|
||
|
{
|
||
|
.compatible = "mediatek,mt8192-svs",
|
||
|
.data = &svs_mt8192_platform_data,
|
||
|
}, {
|
||
|
.compatible = "mediatek,mt8183-svs",
|
||
|
.data = &svs_mt8183_platform_data,
|
||
|
}, {
|
||
|
/* Sentinel */
|
||
|
},
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, svs_of_match);
|
||
|
|
||
|
static int svs_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct svs_platform *svsp;
|
||
|
const struct svs_platform_data *svsp_data;
|
||
|
int ret, svsp_irq;
|
||
|
|
||
|
svsp_data = of_device_get_match_data(&pdev->dev);
|
||
|
|
||
|
svsp = devm_kzalloc(&pdev->dev, sizeof(*svsp), GFP_KERNEL);
|
||
|
if (!svsp)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
svsp->dev = &pdev->dev;
|
||
|
svsp->banks = svsp_data->banks;
|
||
|
svsp->regs = svsp_data->regs;
|
||
|
svsp->bank_max = svsp_data->bank_max;
|
||
|
|
||
|
ret = svsp_data->probe(svsp);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (!svs_is_efuse_data_correct(svsp)) {
|
||
|
dev_notice(svsp->dev, "efuse data isn't correct\n");
|
||
|
ret = -EPERM;
|
||
|
goto svs_probe_free_efuse;
|
||
|
}
|
||
|
|
||
|
if (!svsp_data->efuse_parsing(svsp)) {
|
||
|
dev_err(svsp->dev, "efuse data parsing failed\n");
|
||
|
ret = -EPERM;
|
||
|
goto svs_probe_free_resource;
|
||
|
}
|
||
|
|
||
|
ret = svs_bank_resource_setup(svsp);
|
||
|
if (ret) {
|
||
|
dev_err(svsp->dev, "svs bank resource setup fail: %d\n", ret);
|
||
|
goto svs_probe_free_resource;
|
||
|
}
|
||
|
|
||
|
svsp_irq = platform_get_irq(pdev, 0);
|
||
|
if (svsp_irq < 0) {
|
||
|
ret = svsp_irq;
|
||
|
goto svs_probe_free_resource;
|
||
|
}
|
||
|
|
||
|
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));
|
||
|
ret = PTR_ERR(svsp->main_clk);
|
||
|
goto svs_probe_free_resource;
|
||
|
}
|
||
|
|
||
|
ret = clk_prepare_enable(svsp->main_clk);
|
||
|
if (ret) {
|
||
|
dev_err(svsp->dev, "cannot enable main clk: %d\n", ret);
|
||
|
goto svs_probe_free_resource;
|
||
|
}
|
||
|
|
||
|
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 = devm_request_threaded_irq(svsp->dev, svsp_irq, NULL, svs_isr,
|
||
|
IRQF_ONESHOT, svsp_data->name, svsp);
|
||
|
if (ret) {
|
||
|
dev_err(svsp->dev, "register irq(%d) failed: %d\n",
|
||
|
svsp_irq, ret);
|
||
|
goto svs_probe_iounmap;
|
||
|
}
|
||
|
|
||
|
ret = svs_start(svsp);
|
||
|
if (ret) {
|
||
|
dev_err(svsp->dev, "svs start fail: %d\n", ret);
|
||
|
goto svs_probe_iounmap;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
ret = svs_create_debug_cmds(svsp);
|
||
|
if (ret) {
|
||
|
dev_err(svsp->dev, "svs create debug cmds fail: %d\n", ret);
|
||
|
goto svs_probe_iounmap;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
svs_probe_iounmap:
|
||
|
iounmap(svsp->base);
|
||
|
|
||
|
svs_probe_clk_disable:
|
||
|
clk_disable_unprepare(svsp->main_clk);
|
||
|
|
||
|
svs_probe_free_resource:
|
||
|
if (!IS_ERR_OR_NULL(svsp->tefuse))
|
||
|
kfree(svsp->tefuse);
|
||
|
|
||
|
svs_probe_free_efuse:
|
||
|
if (!IS_ERR_OR_NULL(svsp->efuse))
|
||
|
kfree(svsp->efuse);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static DEFINE_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 = svs_of_match,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
module_platform_driver(svs_driver);
|
||
|
|
||
|
MODULE_AUTHOR("Roger Lu <roger.lu@mediatek.com>");
|
||
|
MODULE_DESCRIPTION("MediaTek SVS driver");
|
||
|
MODULE_LICENSE("GPL");
|