mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2461 lines
63 KiB
2461 lines
63 KiB
// 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); |
|
|
|
#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} |
|
|
|
/** |
|
* 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 |
|
* @name: svs platform name |
|
* @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_parsing: svs platform efuse parsing function pointer |
|
* @probe: svs platform probe function pointer |
|
* @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 { |
|
char *name; |
|
void __iomem *base; |
|
struct device *dev; |
|
struct clk *main_clk; |
|
struct svs_bank *pbank; |
|
struct svs_bank *banks; |
|
struct reset_control *rst; |
|
bool (*efuse_parsing)(struct svs_platform *svsp); |
|
int (*probe)(struct svs_platform *svsp); |
|
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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
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_get_sync(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; |
|
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; |
|
} |
|
} |
|
|
|
/* |
|
* 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"); |
|
return -EPERM; |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
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 out_of_resume; |
|
|
|
svs_mon_mode(svsp); |
|
|
|
return 0; |
|
|
|
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); |
|
o_slope = (svsp->tefuse[0] >> 26) & GENMASK(5, 0); |
|
|
|
if (adc_cali_en_t == 1) { |
|
if (!ts_id) |
|
o_slope = 0; |
|
|
|
if (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; |
|
} |
|
} else { |
|
dev_err(svsp->dev, "no 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; |
|
|
|
if (!o_slope_sign) |
|
mts = (temp0 * 10) / (1534 + o_slope * 10); |
|
else |
|
mts = (temp0 * 10) / (1534 - o_slope * 10); |
|
|
|
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; |
|
|
|
if (!o_slope_sign) |
|
temp2 = temp1 * 100 / (1534 + o_slope * 10); |
|
else |
|
temp2 = temp1 * 100 / (1534 - o_slope * 10); |
|
|
|
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 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_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 */ |
|
}, |
|
}; |
|
|
|
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->probe = svsp_data->probe; |
|
svsp->regs = svsp_data->regs; |
|
svsp->bank_max = svsp_data->bank_max; |
|
|
|
ret = svsp->probe(svsp); |
|
if (ret) |
|
return ERR_PTR(ret); |
|
|
|
return svsp; |
|
} |
|
|
|
static int svs_probe(struct platform_device *pdev) |
|
{ |
|
struct svs_platform *svsp; |
|
int svsp_irq, ret; |
|
|
|
svsp = svs_platform_probe(pdev); |
|
if (IS_ERR(svsp)) |
|
return PTR_ERR(svsp); |
|
|
|
if (!svs_is_efuse_data_correct(svsp)) { |
|
dev_notice(svsp->dev, "efuse data isn't correct\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; |
|
} |
|
|
|
ret = devm_request_threaded_irq(svsp->dev, svsp_irq, NULL, svs_isr, |
|
IRQF_ONESHOT, svsp->name, svsp); |
|
if (ret) { |
|
dev_err(svsp->dev, "register irq(%d) failed: %d\n", |
|
svsp_irq, ret); |
|
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 = svs_start(svsp); |
|
if (ret) { |
|
dev_err(svsp->dev, "svs start fail: %d\n", ret); |
|
goto svs_probe_iounmap; |
|
} |
|
|
|
ret = svs_create_debug_cmds(svsp); |
|
if (ret) { |
|
dev_err(svsp->dev, "svs create debug cmds 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); |
|
|
|
svs_probe_free_resource: |
|
if (!IS_ERR_OR_NULL(svsp->efuse)) |
|
kfree(svsp->efuse); |
|
if (!IS_ERR_OR_NULL(svsp->tefuse)) |
|
kfree(svsp->tefuse); |
|
|
|
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 <[email protected]>"); |
|
MODULE_DESCRIPTION("MediaTek SVS driver"); |
|
MODULE_LICENSE("GPL");
|
|
|