| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2022 MediaTek Inc. | |
| * Copyright (C) 2022 Collabora Ltd. | |
| * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> | |
| */ | |
| #include <linux/bitfield.h> | |
| #include <linux/bits.h> | |
| #include <linux/clk.h> | |
| #include <linux/completion.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/cpu.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 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 Thermal Coefficients */ | |
| #define SVSB_TS_COEFF_MT8195 250460 | |
| #define SVSB_TS_COEFF_MT8186 204650 | |
| /* Algo helpers */ | |
| #define FUSE_DATA_NOT_VALID U32_MAX | |
| /* 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_sw_id - SVS Bank Software ID | |
| * @SVSB_SWID_CPU_LITTLE: CPU little cluster Bank | |
| * @SVSB_SWID_CPU_BIG: CPU big cluster Bank | |
| * @SVSB_SWID_CCI: Cache Coherent Interconnect Bank | |
| * @SVSB_SWID_GPU: GPU Bank | |
| * @SVSB_SWID_MAX: Total number of Banks | |
| */ | |
| enum svsb_sw_id { | |
| SVSB_SWID_CPU_LITTLE, | |
| SVSB_SWID_CPU_BIG, | |
| SVSB_SWID_CCI, | |
| SVSB_SWID_GPU, | |
| SVSB_SWID_MAX | |
| }; | |
| /** | |
| * enum svsb_type - SVS Bank 2-line: Type and Role | |
| * @SVSB_TYPE_NONE: One-line type Bank - Global role | |
| * @SVSB_TYPE_LOW: Two-line type Bank - Low bank role | |
| * @SVSB_TYPE_HIGH: Two-line type Bank - High bank role | |
| * @SVSB_TYPE_MAX: Total number of bank types | |
| */ | |
| enum svsb_type { | |
| SVSB_TYPE_NONE, | |
| SVSB_TYPE_LOW, | |
| SVSB_TYPE_HIGH, | |
| SVSB_TYPE_MAX | |
| }; | |
| /** | |
| * 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] = 0x00, | |
| [TEMPCHAR] = 0x04, | |
| [DETCHAR] = 0x08, | |
| [AGECHAR] = 0x0c, | |
| [DCCONFIG] = 0x10, | |
| [AGECONFIG] = 0x14, | |
| [FREQPCT30] = 0x18, | |
| [FREQPCT74] = 0x1c, | |
| [LIMITVALS] = 0x20, | |
| [VBOOT] = 0x24, | |
| [DETWINDOW] = 0x28, | |
| [CONFIG] = 0x2c, | |
| [TSCALCS] = 0x30, | |
| [RUNCONFIG] = 0x34, | |
| [SVSEN] = 0x38, | |
| [INIT2VALS] = 0x3c, | |
| [DCVALUES] = 0x40, | |
| [AGEVALUES] = 0x44, | |
| [VOP30] = 0x48, | |
| [VOP74] = 0x4c, | |
| [TEMP] = 0x50, | |
| [INTSTS] = 0x54, | |
| [INTSTSRAW] = 0x58, | |
| [INTEN] = 0x5c, | |
| [CHKINT] = 0x60, | |
| [CHKSHIFT] = 0x64, | |
| [STATUS] = 0x68, | |
| [VDESIGN30] = 0x6c, | |
| [VDESIGN74] = 0x70, | |
| [DVT30] = 0x74, | |
| [DVT74] = 0x78, | |
| [AGECOUNT] = 0x7c, | |
| [SMSTATE0] = 0x80, | |
| [SMSTATE1] = 0x84, | |
| [CTL0] = 0x88, | |
| [DESDETSEC] = 0xe0, | |
| [TEMPAGESEC] = 0xe4, | |
| [CTRLSPARE0] = 0xf0, | |
| [CTRLSPARE1] = 0xf4, | |
| [CTRLSPARE2] = 0xf8, | |
| [CTRLSPARE3] = 0xfc, | |
| [CORESEL] = 0x300, | |
| [THERMINTST] = 0x304, | |
| [INTST] = 0x308, | |
| [THSTAGE0ST] = 0x30c, | |
| [THSTAGE1ST] = 0x310, | |
| [THSTAGE2ST] = 0x314, | |
| [THAHBST0] = 0x318, | |
| [THAHBST1] = 0x31c, | |
| [SPARE0] = 0x320, | |
| [SPARE1] = 0x324, | |
| [SPARE2] = 0x328, | |
| [SPARE3] = 0x32c, | |
| [THSLPEVEB] = 0x330, | |
| }; | |
| static const char * const svs_swid_names[SVSB_SWID_MAX] = { | |
| "SVSB_CPU_LITTLE", "SVSB_CPU_BIG", "SVSB_CCI", "SVSB_GPU" | |
| }; | |
| static const char * const svs_type_names[SVSB_TYPE_MAX] = { | |
| "", "_LOW", "_HIGH" | |
| }; | |
| enum svs_fusemap_dev { | |
| BDEV_BDES, | |
| BDEV_MDES, | |
| BDEV_MTDES, | |
| BDEV_DCBDET, | |
| BDEV_DCMDET, | |
| BDEV_MAX | |
| }; | |
| enum svs_fusemap_glb { | |
| GLB_FT_PGM, | |
| GLB_VMIN, | |
| GLB_MAX | |
| }; | |
| struct svs_fusemap { | |
| s8 index; | |
| u8 ofst; | |
| }; | |
| /** | |
| * struct svs_platform - svs platform control | |
| * @base: svs platform register base | |
| * @dev: svs platform device | |
| * @main_clk: main clock for svs bank | |
| * @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 | |
| * @efuse: svs efuse data received from NVMEM framework | |
| * @tefuse: thermal efuse data received from NVMEM framework | |
| * @ts_coeff: thermal sensors coefficient | |
| * @bank_max: total number of svs banks | |
| */ | |
| struct svs_platform { | |
| void __iomem *base; | |
| struct device *dev; | |
| struct clk *main_clk; | |
| struct svs_bank *banks; | |
| struct reset_control *rst; | |
| size_t efuse_max; | |
| size_t tefuse_max; | |
| const u32 *regs; | |
| u32 *efuse; | |
| u32 *tefuse; | |
| u32 ts_coeff; | |
| u16 bank_max; | |
| }; | |
| struct svs_platform_data { | |
| char *name; | |
| struct svs_bank *banks; | |
| bool (*efuse_parsing)(struct svs_platform *svsp, const struct svs_platform_data *pdata); | |
| int (*probe)(struct svs_platform *svsp); | |
| const struct svs_fusemap *glb_fuse_map; | |
| const u32 *regs; | |
| u32 ts_coeff; | |
| u16 bank_max; | |
| }; | |
| /** | |
| * struct svs_bank_pdata - SVS Bank immutable config parameters | |
| * @dev_fuse_map: Bank fuse map data | |
| * @buck_name: Regulator name | |
| * @tzone_name: Thermal zone name | |
| * @age_config: Bank age configuration | |
| * @ctl0: TS-x selection | |
| * @dc_config: Bank dc configuration | |
| * @int_st: Bank interrupt identification | |
| * @turn_freq_base: Reference frequency for 2-line turn point | |
| * @tzone_htemp: Thermal zone high temperature threshold | |
| * @tzone_ltemp: Thermal zone low temperature threshold | |
| * @volt_step: Bank voltage step | |
| * @volt_base: Bank voltage base | |
| * @tzone_htemp_voffset: Thermal zone high temperature voltage offset | |
| * @tzone_ltemp_voffset: Thermal zone low temperature voltage offset | |
| * @chk_shift: Bank chicken shift | |
| * @cpu_id: CPU core ID for SVS CPU bank use only | |
| * @opp_count: Bank opp count | |
| * @vboot: Voltage request for bank init01 only | |
| * @vco: Bank VCO value | |
| * @sw_id: Bank software identification | |
| * @type: SVS Bank Type (1 or 2-line) and Role (high/low) | |
| * @set_freq_pct: function pointer to set bank frequency percent table | |
| * @get_volts: function pointer to get bank voltages | |
| */ | |
| struct svs_bank_pdata { | |
| const struct svs_fusemap *dev_fuse_map; | |
| char *buck_name; | |
| char *tzone_name; | |
| u32 age_config; | |
| u32 ctl0; | |
| u32 dc_config; | |
| u32 int_st; | |
| u32 turn_freq_base; | |
| u32 tzone_htemp; | |
| u32 tzone_ltemp; | |
| u32 volt_step; | |
| u32 volt_base; | |
| u16 tzone_htemp_voffset; | |
| u16 tzone_ltemp_voffset; | |
| u8 chk_shift; | |
| u8 cpu_id; | |
| u8 opp_count; | |
| u8 vboot; | |
| u8 vco; | |
| u8 sw_id; | |
| u8 type; | |
| /* Callbacks */ | |
| void (*set_freq_pct)(struct svs_platform *svsp, struct svs_bank *svsb); | |
| void (*get_volts)(struct svs_platform *svsp, struct svs_bank *svsb); | |
| }; | |
| /** | |
| * struct svs_bank - svs bank representation | |
| * @pdata: SVS Bank immutable config parameters | |
| * @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 | |
| * @name: bank 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 | |
| * @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_flags: bank voltage flags | |
| * @vmax: bank voltage maximum | |
| * @vmin: bank voltage minimum | |
| * @age_voffset_in: bank age voltage offset | |
| * @dc_voffset_in: bank dc voltage offset | |
| * @dvt_fixed: bank dvt fixed value | |
| * @core_sel: bank selection | |
| * @temp: bank temperature | |
| * @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 | |
| * @vbin_turn_pt: voltage bin turn point helps know which svsb_volt should be overridden | |
| * | |
| * Svs bank will generate suitable 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 { | |
| const struct svs_bank_pdata pdata; | |
| struct device *dev; | |
| struct device *opp_dev; | |
| struct completion init_completion; | |
| struct regulator *buck; | |
| struct thermal_zone_device *tzd; | |
| struct mutex lock; | |
| int pm_runtime_enabled_count; | |
| short int volt_od; | |
| char *name; | |
| enum svsb_phase phase; | |
| u32 reg_data[SVSB_PHASE_MAX][SVS_REG_MAX]; | |
| u8 mode_support; | |
| 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_flags; | |
| u32 freq_base; | |
| u32 turn_pt; | |
| u32 vbin_turn_pt; | |
| u32 core_sel; | |
| u32 temp; | |
| u16 age_voffset_in; | |
| u16 dc_voffset_in; | |
| u8 dvt_fixed; | |
| u8 vmax; | |
| u8 vmin; | |
| u16 bts; | |
| u16 mts; | |
| u16 bdes; | |
| u16 mdes; | |
| u8 mtdes; | |
| u8 dcbdet; | |
| u8 dcmdet; | |
| }; | |
| static u32 percent(u32 numerator, u32 denominator) | |
| { | |
| /* If not divide 1000, "numerator * 100" will have data overflow. */ | |
| numerator /= 1000; | |
| denominator /= 1000; | |
| return DIV_ROUND_UP(numerator * 100, denominator); | |
| } | |
| static u32 svs_readl_relaxed(struct svs_platform *svsp, enum svs_reg_index rg_i) | |
| { | |
| return readl_relaxed(svsp->base + svsp->regs[rg_i]); | |
| } | |
| static void svs_writel_relaxed(struct svs_platform *svsp, u32 val, | |
| enum svs_reg_index rg_i) | |
| { | |
| writel_relaxed(val, svsp->base + svsp->regs[rg_i]); | |
| } | |
| static void svs_switch_bank(struct svs_platform *svsp, struct svs_bank *svsb) | |
| { | |
| 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) | |
| { | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| struct dev_pm_opp *opp; | |
| u32 i, opp_u_volt; | |
| for (i = 0; i < bdata->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, | |
| bdata->volt_step, | |
| bdata->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; | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| 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 (bdata->type == SVSB_TYPE_HIGH) { | |
| opp_start = 0; | |
| opp_stop = svsb->turn_pt; | |
| } else if (bdata->type == SVSB_TYPE_LOW) { | |
| opp_start = svsb->turn_pt; | |
| opp_stop = bdata->opp_count; | |
| } else { | |
| opp_start = 0; | |
| opp_stop = bdata->opp_count; | |
| } | |
| /* Get thermal effect */ | |
| if (!IS_ERR_OR_NULL(svsb->tzd)) { | |
| 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", | |
| bdata->tzone_name, ret, svsb->temp); | |
| svsb->phase = SVSB_PHASE_ERROR; | |
| } | |
| if (tzone_temp >= bdata->tzone_htemp) | |
| temp_voffset += bdata->tzone_htemp_voffset; | |
| else if (tzone_temp <= bdata->tzone_ltemp) | |
| temp_voffset += bdata->tzone_ltemp_voffset; | |
| /* 2-line bank update all opp volts when running mon mode */ | |
| if (svsb->phase == SVSB_PHASE_MON && (bdata->type == SVSB_TYPE_HIGH || | |
| bdata->type == SVSB_TYPE_LOW)) { | |
| opp_start = 0; | |
| opp_stop = bdata->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: | |
| case SVSB_PHASE_MON: | |
| svsb_volt = max(svsb->volt[i] + temp_voffset, svsb->vmin); | |
| opp_volt = svs_bank_volt_to_opp_volt(svsb_volt, | |
| bdata->volt_step, | |
| bdata->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 void svs_bank_disable_and_restore_default_volts(struct svs_platform *svsp, | |
| struct svs_bank *svsb) | |
| { | |
| unsigned long flags; | |
| if (svsb->mode_support == SVSB_MODE_ALL_DISABLE) | |
| return; | |
| spin_lock_irqsave(&svs_lock, flags); | |
| svs_switch_bank(svsp, svsb); | |
| 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); | |
| } | |
| #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); | |
| int enabled, ret; | |
| char *buf __free(kfree) = 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) { | |
| svs_bank_disable_and_restore_default_volts(svsp, svsb); | |
| svsb->mode_support = SVSB_MODE_ALL_DISABLE; | |
| } | |
| 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, vbin_turn_pt = %u, turn_pt = %u\n", | |
| svsb->name, svsb->vbin_turn_pt, svsb->turn_pt); | |
| else | |
| seq_printf(m, "%s: temperature = %d, vbin_turn_pt = %u, turn_pt = %u\n", | |
| svsb->name, tzone_temp, svsb->vbin_turn_pt, | |
| svsb->turn_pt); | |
| for (i = 0; i < svsb->pdata.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) | |
| { | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| 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 = (bdata->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 (bdata->type == SVSB_TYPE_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 (bdata->type == SVSB_TYPE_LOW) { | |
| /* volt[turn_pt] + volt[j] ~ volt[opp_count - 1] */ | |
| j = bdata->opp_count - 7; | |
| svsb->volt[turn_pt] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, vop30); | |
| shift_byte++; | |
| for (i = j; i < bdata->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 (bdata->type == SVSB_TYPE_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 (bdata->type == SVSB_TYPE_LOW) { | |
| /* volt[turn_pt] ~ volt[opp_count - 1] */ | |
| for (i = turn_pt; i < bdata->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 (bdata->type == SVSB_TYPE_HIGH) { | |
| opp_start = 0; | |
| opp_stop = svsb->turn_pt; | |
| } else if (bdata->type == SVSB_TYPE_LOW) { | |
| opp_start = svsb->turn_pt; | |
| opp_stop = bdata->opp_count; | |
| } | |
| for (i = opp_start; i < opp_stop; i++) | |
| if (svsb->volt_flags & SVSB_REMOVE_DVTFIXED_VOLT) | |
| svsb->volt[i] -= svsb->dvt_fixed; | |
| /* For voltage bin support */ | |
| if (svsb->opp_dfreq[0] > svsb->freq_base) { | |
| svsb->volt[0] = svs_opp_volt_to_bank_volt(svsb->opp_dvolt[0], | |
| bdata->volt_step, | |
| bdata->volt_base); | |
| /* Find voltage bin turn point */ | |
| for (i = 0; i < bdata->opp_count; i++) { | |
| if (svsb->opp_dfreq[i] <= svsb->freq_base) { | |
| svsb->vbin_turn_pt = i; | |
| break; | |
| } | |
| } | |
| /* Override svs bank voltages */ | |
| for (i = 1; i < svsb->vbin_turn_pt; i++) | |
| svsb->volt[i] = interpolate(svsb->freq_pct[0], | |
| svsb->freq_pct[svsb->vbin_turn_pt], | |
| svsb->volt[0], | |
| svsb->volt[svsb->vbin_turn_pt], | |
| svsb->freq_pct[i]); | |
| } | |
| } | |
| static void svs_set_bank_freq_pct_v3(struct svs_platform *svsp, struct svs_bank *svsb) | |
| { | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| u32 i, j, *freq_pct, freq_pct74 = 0, freq_pct30 = 0; | |
| u32 b_sft, shift_byte = 0, turn_pt; | |
| u32 middle_index = (bdata->opp_count / 2); | |
| for (i = 0; i < bdata->opp_count; i++) { | |
| if (svsb->opp_dfreq[i] <= bdata->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 (bdata->type == SVSB_TYPE_HIGH) { | |
| /* | |
| * If we don't handle this situation, | |
| * SVSB_TYPE_HIGH's FREQPCT74 / FREQPCT30 would keep "0" | |
| * and this leads SVSB_TYPE_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 (bdata->type == SVSB_TYPE_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 = bdata->opp_count - 7; | |
| for (i = j; i < bdata->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 (bdata->type == SVSB_TYPE_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 (bdata->type == SVSB_TYPE_LOW) { | |
| /* freq_pct[turn_pt] ~ freq_pct[opp_count - 1] */ | |
| for (i = turn_pt; i < bdata->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) | |
| { | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| 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 < bdata->opp_count; i++) | |
| svsb->volt[i] += svsb->volt_od; | |
| /* For voltage bin support */ | |
| if (svsb->opp_dfreq[0] > svsb->freq_base) { | |
| svsb->volt[0] = svs_opp_volt_to_bank_volt(svsb->opp_dvolt[0], | |
| bdata->volt_step, | |
| bdata->volt_base); | |
| /* Find voltage bin turn point */ | |
| for (i = 0; i < bdata->opp_count; i++) { | |
| if (svsb->opp_dfreq[i] <= svsb->freq_base) { | |
| svsb->vbin_turn_pt = i; | |
| break; | |
| } | |
| } | |
| /* Override svs bank voltages */ | |
| for (i = 1; i < svsb->vbin_turn_pt; i++) | |
| svsb->volt[i] = interpolate(svsb->freq_pct[0], | |
| svsb->freq_pct[svsb->vbin_turn_pt], | |
| svsb->volt[0], | |
| svsb->volt[svsb->vbin_turn_pt], | |
| svsb->freq_pct[i]); | |
| } | |
| } | |
| static void svs_set_bank_freq_pct_v2(struct svs_platform *svsp, struct svs_bank *svsb) | |
| { | |
| 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, | |
| unsigned int bank_idx, | |
| enum svsb_phase target_phase) | |
| { | |
| struct svs_bank *svsb = &svsp->banks[bank_idx]; | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| u32 des_char, temp_char, det_char, limit_vals, init2vals, ts_calcs; | |
| svs_switch_bank(svsp, svsb); | |
| 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, bdata->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, bdata->dc_config, DCCONFIG); | |
| svs_writel_relaxed(svsp, bdata->age_config, AGECONFIG); | |
| svs_writel_relaxed(svsp, SVSB_RUNCONFIG_DEFAULT, RUNCONFIG); | |
| bdata->set_freq_pct(svsp, svsb); | |
| 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, bdata->chk_shift, CHKSHIFT); | |
| svs_writel_relaxed(svsp, bdata->ctl0, CTL0); | |
| svs_writel_relaxed(svsp, SVSB_INTSTS_VAL_CLEAN, INTSTS); | |
| switch (target_phase) { | |
| case SVSB_PHASE_INIT01: | |
| svs_writel_relaxed(svsp, bdata->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, | |
| unsigned short bank_idx, | |
| enum svsb_phase phase) | |
| { | |
| struct svs_bank *svsb = &svsp->banks[bank_idx]; | |
| 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, | |
| unsigned short bank_idx) | |
| { | |
| struct svs_bank *svsb = &svsp->banks[bank_idx]; | |
| 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, bank_idx, 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, | |
| unsigned short bank_idx) | |
| { | |
| struct svs_bank *svsb = &svsp->banks[bank_idx]; | |
| u32 val; | |
| 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, bank_idx, SVSB_PHASE_INIT01); | |
| svsb->phase = SVSB_PHASE_INIT01; | |
| val = ~(svs_readl_relaxed(svsp, DCVALUES) & GENMASK(15, 0)) + 1; | |
| svsb->dc_voffset_in = val & GENMASK(15, 0); | |
| 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, | |
| unsigned short bank_idx) | |
| { | |
| struct svs_bank *svsb = &svsp->banks[bank_idx]; | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| 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, bank_idx, SVSB_PHASE_INIT02); | |
| svsb->phase = SVSB_PHASE_INIT02; | |
| bdata->get_volts(svsp, svsb); | |
| 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, | |
| unsigned short bank_idx) | |
| { | |
| struct svs_bank *svsb = &svsp->banks[bank_idx]; | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| svs_save_bank_register_data(svsp, bank_idx, SVSB_PHASE_MON); | |
| svsb->phase = SVSB_PHASE_MON; | |
| bdata->get_volts(svsp, svsb); | |
| 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; | |
| const struct svs_bank_pdata *bdata; | |
| 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]; | |
| bdata = &svsb->pdata; | |
| WARN(!svsb, "%s: svsb(%s) is null", __func__, svsb->name); | |
| spin_lock_irqsave(&svs_lock, flags); | |
| /* Find out which svs bank fires interrupt */ | |
| if (bdata->int_st & svs_readl_relaxed(svsp, INTST)) { | |
| spin_unlock_irqrestore(&svs_lock, flags); | |
| continue; | |
| } | |
| svs_switch_bank(svsp, svsb); | |
| 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, idx); | |
| else if (int_sts == SVSB_INTSTS_F0_COMPLETE && | |
| svs_en == SVSB_PTPEN_INIT02) | |
| svs_init02_isr_handler(svsp, idx); | |
| else if (int_sts & SVSB_INTSTS_FLD_MONVOP) | |
| svs_mon_mode_isr_handler(svsp, idx); | |
| else | |
| svs_error_isr_handler(svsp, idx); | |
| 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 bool svs_mode_available(struct svs_platform *svsp, u8 mode) | |
| { | |
| int i; | |
| for (i = 0; i < svsp->bank_max; i++) | |
| if (svsp->banks[i].mode_support & mode) | |
| return true; | |
| return false; | |
| } | |
| static int svs_init01(struct svs_platform *svsp) | |
| { | |
| const struct svs_bank_pdata *bdata; | |
| 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; | |
| if (!svs_mode_available(svsp, SVSB_MODE_INIT01)) | |
| return 0; | |
| /* 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]; | |
| bdata = &svsb->pdata; | |
| if (!(svsb->mode_support & SVSB_MODE_INIT01)) | |
| continue; | |
| ret = regulator_enable(svsb->buck); | |
| if (ret) { | |
| dev_err(svsb->dev, "%s enable fail: %d\n", | |
| bdata->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]; | |
| bdata = &svsb->pdata; | |
| 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(bdata->vboot, | |
| bdata->volt_step, | |
| bdata->volt_base); | |
| for (i = 0; i < bdata->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]; | |
| bdata = &svsb->pdata; | |
| if (!(svsb->mode_support & SVSB_MODE_INIT01)) | |
| continue; | |
| opp_vboot = svs_bank_volt_to_opp_volt(bdata->vboot, | |
| bdata->volt_step, | |
| bdata->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); | |
| svs_set_bank_phase(svsp, idx, 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]; | |
| bdata = &svsb->pdata; | |
| if (!(svsb->mode_support & SVSB_MODE_INIT01)) | |
| continue; | |
| for (i = 0; i < bdata->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", | |
| bdata->buck_name, r); | |
| } | |
| svs_init01_resume_cpuidle: | |
| cpuidle_resume_and_unlock(); | |
| return ret; | |
| } | |
| static int svs_init02(struct svs_platform *svsp) | |
| { | |
| const struct svs_bank_pdata *bdata; | |
| struct svs_bank *svsb; | |
| unsigned long flags, time_left; | |
| int ret; | |
| u32 idx; | |
| if (!svs_mode_available(svsp, SVSB_MODE_INIT02)) | |
| return 0; | |
| 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); | |
| svs_set_bank_phase(svsp, idx, 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]; | |
| bdata = &svsb->pdata; | |
| if (!(svsb->mode_support & SVSB_MODE_INIT02)) | |
| continue; | |
| if (bdata->type == SVSB_TYPE_HIGH || bdata->type == SVSB_TYPE_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]; | |
| svs_bank_disable_and_restore_default_volts(svsp, 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); | |
| svs_set_bank_phase(svsp, idx, 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); | |
| int ret; | |
| u32 idx; | |
| for (idx = 0; idx < svsp->bank_max; idx++) { | |
| struct svs_bank *svsb = &svsp->banks[idx]; | |
| svs_bank_disable_and_restore_default_volts(svsp, 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) | |
| { | |
| const struct svs_bank_pdata *bdata; | |
| struct svs_bank *svsb; | |
| struct dev_pm_opp *opp; | |
| char tz_name_buf[20]; | |
| 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]; | |
| bdata = &svsb->pdata; | |
| if (bdata->sw_id >= SVSB_SWID_MAX || bdata->type >= SVSB_TYPE_MAX) { | |
| dev_err(svsb->dev, "unknown bank sw_id or type\n"); | |
| return -EINVAL; | |
| } | |
| svsb->dev = devm_kzalloc(svsp->dev, sizeof(*svsb->dev), GFP_KERNEL); | |
| if (!svsb->dev) | |
| return -ENOMEM; | |
| svsb->name = devm_kasprintf(svsp->dev, GFP_KERNEL, "%s%s", | |
| svs_swid_names[bdata->sw_id], | |
| svs_type_names[bdata->type]); | |
| if (!svsb->name) | |
| 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, | |
| bdata->buck_name); | |
| if (IS_ERR(svsb->buck)) { | |
| dev_err(svsb->dev, "cannot get \"%s-supply\"\n", | |
| bdata->buck_name); | |
| return PTR_ERR(svsb->buck); | |
| } | |
| } | |
| if (!IS_ERR_OR_NULL(bdata->tzone_name)) { | |
| snprintf(tz_name_buf, ARRAY_SIZE(tz_name_buf), | |
| "%s-thermal", bdata->tzone_name); | |
| svsb->tzd = thermal_zone_get_zone_by_name(tz_name_buf); | |
| if (IS_ERR(svsb->tzd)) { | |
| dev_err(svsb->dev, "cannot get \"%s\" thermal zone\n", | |
| tz_name_buf); | |
| return PTR_ERR(svsb->tzd); | |
| } | |
| } | |
| count = dev_pm_opp_get_opp_count(svsb->opp_dev); | |
| if (bdata->opp_count != count) { | |
| dev_err(svsb->dev, | |
| "opp_count not \"%u\" but get \"%d\"?\n", | |
| bdata->opp_count, count); | |
| return count; | |
| } | |
| for (i = 0, freq = ULONG_MAX; i < bdata->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_get_efuse_data(struct svs_platform *svsp, | |
| const char *nvmem_cell_name, | |
| u32 **svsp_efuse, size_t *svsp_efuse_max) | |
| { | |
| struct nvmem_cell *cell; | |
| cell = nvmem_cell_get(svsp->dev, nvmem_cell_name); | |
| if (IS_ERR(cell)) { | |
| dev_err(svsp->dev, "no \"%s\"? %ld\n", | |
| nvmem_cell_name, PTR_ERR(cell)); | |
| return PTR_ERR(cell); | |
| } | |
| *svsp_efuse = nvmem_cell_read(cell, svsp_efuse_max); | |
| if (IS_ERR(*svsp_efuse)) { | |
| nvmem_cell_put(cell); | |
| return PTR_ERR(*svsp_efuse); | |
| } | |
| *svsp_efuse_max /= sizeof(u32); | |
| nvmem_cell_put(cell); | |
| return 0; | |
| } | |
| static u32 svs_get_fuse_val(u32 *fuse_array, const struct svs_fusemap *fmap, u8 nbits) | |
| { | |
| u32 val; | |
| if (fmap->index < 0) | |
| return FUSE_DATA_NOT_VALID; | |
| val = fuse_array[fmap->index] >> fmap->ofst; | |
| val &= GENMASK(nbits - 1, 0); | |
| return val; | |
| } | |
| static bool svs_is_available(struct svs_platform *svsp) | |
| { | |
| int i, num_populated = 0; | |
| /* If at least two fuse arrays are populated, SVS is calibrated */ | |
| for (i = 0; i < svsp->efuse_max; i++) { | |
| if (svsp->efuse[i]) | |
| num_populated++; | |
| if (num_populated > 1) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool svs_common_parse_efuse(struct svs_platform *svsp, | |
| const struct svs_platform_data *pdata) | |
| { | |
| const struct svs_fusemap *gfmap = pdata->glb_fuse_map; | |
| struct svs_fusemap tfm = { 0, 24 }; | |
| u32 golden_temp, val; | |
| u8 ft_pgm, vmin; | |
| int i; | |
| if (!svs_is_available(svsp)) | |
| return false; | |
| /* Get golden temperature from SVS-Thermal calibration */ | |
| val = svs_get_fuse_val(svsp->tefuse, &tfm, 8); | |
| /* If golden temp is not programmed, use the default of 50 */ | |
| golden_temp = val ? val : 50; | |
| /* Parse fused SVS calibration */ | |
| ft_pgm = svs_get_fuse_val(svsp->efuse, &gfmap[GLB_FT_PGM], 8); | |
| vmin = svs_get_fuse_val(svsp->efuse, &gfmap[GLB_VMIN], 2); | |
| for (i = 0; i < svsp->bank_max; i++) { | |
| struct svs_bank *svsb = &svsp->banks[i]; | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| const struct svs_fusemap *dfmap = bdata->dev_fuse_map; | |
| if (vmin == 1) | |
| svsb->vmin = 0x1e; | |
| if (ft_pgm == 0) | |
| svsb->volt_flags |= SVSB_INIT01_VOLT_IGNORE; | |
| svsb->mtdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_MTDES], 8); | |
| svsb->bdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_BDES], 8); | |
| svsb->mdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_MDES], 8); | |
| svsb->dcbdet = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_DCBDET], 8); | |
| svsb->dcmdet = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_DCMDET], 8); | |
| svsb->vmax += svsb->dvt_fixed; | |
| svsb->mts = (svsp->ts_coeff * 2) / 1000; | |
| svsb->bts = (((500 * golden_temp + svsp->ts_coeff) / 1000) - 25) * 4; | |
| } | |
| return true; | |
| } | |
| static bool svs_mt8183_efuse_parsing(struct svs_platform *svsp, | |
| const struct svs_platform_data *pdata) | |
| { | |
| struct svs_bank *svsb; | |
| const struct svs_bank_pdata *bdata; | |
| 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; | |
| 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 = svs_get_fuse_val(svsp->efuse, &pdata->glb_fuse_map[GLB_FT_PGM], 4); | |
| for (idx = 0; idx < svsp->bank_max; idx++) { | |
| svsb = &svsp->banks[idx]; | |
| bdata = &svsb->pdata; | |
| const struct svs_fusemap *dfmap = bdata->dev_fuse_map; | |
| if (ft_pgm <= 1) | |
| svsb->volt_flags |= SVSB_INIT01_VOLT_IGNORE; | |
| svsb->mtdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_MTDES], 8); | |
| svsb->bdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_BDES], 8); | |
| svsb->mdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_MDES], 8); | |
| svsb->dcbdet = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_DCBDET], 8); | |
| svsb->dcmdet = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_DCMDET], 8); | |
| switch (bdata->sw_id) { | |
| case SVSB_SWID_CPU_LITTLE: | |
| case SVSB_SWID_CCI: | |
| if (ft_pgm <= 3) | |
| svsb->volt_od += 10; | |
| else | |
| svsb->volt_od += 2; | |
| break; | |
| case SVSB_SWID_CPU_BIG: | |
| if (ft_pgm <= 3) | |
| svsb->volt_od += 15; | |
| else | |
| svsb->volt_od += 12; | |
| break; | |
| case SVSB_SWID_GPU: | |
| if (ft_pgm != FUSE_DATA_NOT_VALID && ft_pgm >= 2) { | |
| svsb->freq_base = 800000000; /* 800MHz */ | |
| svsb->dvt_fixed = 2; | |
| } | |
| break; | |
| default: | |
| dev_err(svsb->dev, "unknown sw_id: %u\n", bdata->sw_id); | |
| 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]; | |
| bdata = &svsb->pdata; | |
| svsb->mts = mts; | |
| switch (bdata->sw_id) { | |
| case SVSB_SWID_CPU_LITTLE: | |
| tb_roomt = x_roomt[3]; | |
| break; | |
| case SVSB_SWID_CPU_BIG: | |
| tb_roomt = x_roomt[4]; | |
| break; | |
| case SVSB_SWID_CCI: | |
| tb_roomt = x_roomt[3]; | |
| break; | |
| case SVSB_SWID_GPU: | |
| tb_roomt = x_roomt[1]; | |
| break; | |
| default: | |
| dev_err(svsb->dev, "unknown sw_id: %u\n", bdata->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 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); | |
| of_node_put(np); | |
| if (!pdev) { | |
| dev_err(svsp->dev, "cannot find pdev by %s\n", node_name); | |
| return ERR_PTR(-ENXIO); | |
| } | |
| 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; | |
| 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 void svs_put_device(void *_dev) | |
| { | |
| struct device *dev = _dev; | |
| put_device(dev); | |
| } | |
| static int svs_mt8192_platform_probe(struct svs_platform *svsp) | |
| { | |
| struct device *dev; | |
| u32 idx; | |
| int ret; | |
| 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, "thermal-sensor"); | |
| if (IS_ERR(dev)) | |
| return dev_err_probe(svsp->dev, PTR_ERR(dev), | |
| "failed to get lvts device\n"); | |
| put_device(dev); | |
| for (idx = 0; idx < svsp->bank_max; idx++) { | |
| struct svs_bank *svsb = &svsp->banks[idx]; | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| switch (bdata->sw_id) { | |
| case SVSB_SWID_CPU_LITTLE: | |
| case SVSB_SWID_CPU_BIG: | |
| svsb->opp_dev = get_cpu_device(bdata->cpu_id); | |
| get_device(svsb->opp_dev); | |
| break; | |
| case SVSB_SWID_CCI: | |
| svsb->opp_dev = svs_add_device_link(svsp, "cci"); | |
| break; | |
| case SVSB_SWID_GPU: | |
| if (bdata->type == SVSB_TYPE_LOW) | |
| svsb->opp_dev = svs_get_subsys_device(svsp, "gpu"); | |
| else | |
| svsb->opp_dev = svs_add_device_link(svsp, "gpu"); | |
| break; | |
| default: | |
| dev_err(svsb->dev, "unknown sw_id: %u\n", bdata->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); | |
| ret = devm_add_action_or_reset(svsp->dev, svs_put_device, | |
| svsb->opp_dev); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int svs_mt8183_platform_probe(struct svs_platform *svsp) | |
| { | |
| struct device *dev; | |
| u32 idx; | |
| int ret; | |
| dev = svs_add_device_link(svsp, "thermal-sensor"); | |
| if (IS_ERR(dev)) | |
| return dev_err_probe(svsp->dev, PTR_ERR(dev), | |
| "failed to get thermal device\n"); | |
| put_device(dev); | |
| for (idx = 0; idx < svsp->bank_max; idx++) { | |
| struct svs_bank *svsb = &svsp->banks[idx]; | |
| const struct svs_bank_pdata *bdata = &svsb->pdata; | |
| switch (bdata->sw_id) { | |
| case SVSB_SWID_CPU_LITTLE: | |
| case SVSB_SWID_CPU_BIG: | |
| svsb->opp_dev = get_cpu_device(bdata->cpu_id); | |
| get_device(svsb->opp_dev); | |
| break; | |
| case SVSB_SWID_CCI: | |
| svsb->opp_dev = svs_add_device_link(svsp, "cci"); | |
| break; | |
| case SVSB_SWID_GPU: | |
| svsb->opp_dev = svs_add_device_link(svsp, "gpu"); | |
| break; | |
| default: | |
| dev_err(svsb->dev, "unknown sw_id: %u\n", bdata->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); | |
| ret = devm_add_action_or_reset(svsp->dev, svs_put_device, | |
| svsb->opp_dev); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static struct svs_bank svs_mt8195_banks[] = { | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_GPU, | |
| .type = SVSB_TYPE_LOW, | |
| .set_freq_pct = svs_set_bank_freq_pct_v3, | |
| .get_volts = svs_get_bank_volts_v3, | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .turn_freq_base = 640000000, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x1, | |
| .vco = 0x18, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(0), | |
| .ctl0 = 0x00540003, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 10, 16 }, { 10, 24 }, { 10, 0 }, { 8, 0 }, { 8, 8 } | |
| } | |
| }, | |
| .mode_support = SVSB_MODE_INIT02, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT, | |
| .freq_base = 640000000, | |
| .core_sel = 0x0fff0100, | |
| .dvt_fixed = 0x1, | |
| .vmax = 0x38, | |
| .vmin = 0x14, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_GPU, | |
| .type = SVSB_TYPE_HIGH, | |
| .set_freq_pct = svs_set_bank_freq_pct_v3, | |
| .get_volts = svs_get_bank_volts_v3, | |
| .tzone_name = "gpu", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .turn_freq_base = 640000000, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x1, | |
| .vco = 0x18, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(1), | |
| .ctl0 = 0x00540003, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 0, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 7, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 9, 16 }, { 9, 24 }, { 9, 0 }, { 8, 0 }, { 8, 8 } | |
| }, | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE, | |
| .mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON, | |
| .freq_base = 880000000, | |
| .core_sel = 0x0fff0101, | |
| .dvt_fixed = 0x6, | |
| .vmax = 0x38, | |
| .vmin = 0x14, | |
| }, | |
| }; | |
| static struct svs_bank svs_mt8192_banks[] = { | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_GPU, | |
| .type = SVSB_TYPE_LOW, | |
| .set_freq_pct = svs_set_bank_freq_pct_v3, | |
| .get_volts = svs_get_bank_volts_v3, | |
| .tzone_name = "gpu", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .turn_freq_base = 688000000, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x1, | |
| .vco = 0x18, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(0), | |
| .ctl0 = 0x00540003, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 0, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 7, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 10, 16 }, { 10, 24 }, { 10, 0 }, { 17, 0 }, { 17, 8 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT, | |
| .mode_support = SVSB_MODE_INIT02, | |
| .freq_base = 688000000, | |
| .core_sel = 0x0fff0100, | |
| .dvt_fixed = 0x1, | |
| .vmax = 0x60, | |
| .vmin = 0x1a, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_GPU, | |
| .type = SVSB_TYPE_HIGH, | |
| .set_freq_pct = svs_set_bank_freq_pct_v3, | |
| .get_volts = svs_get_bank_volts_v3, | |
| .tzone_name = "gpu", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .turn_freq_base = 688000000, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x1, | |
| .vco = 0x18, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(1), | |
| .ctl0 = 0x00540003, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 0, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 7, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 9, 16 }, { 9, 24 }, { 17, 0 }, { 17, 16 }, { 17, 24 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE, | |
| .mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON, | |
| .freq_base = 902000000, | |
| .core_sel = 0x0fff0101, | |
| .dvt_fixed = 0x6, | |
| .vmax = 0x60, | |
| .vmin = 0x1a, | |
| }, | |
| }; | |
| static struct svs_bank svs_mt8188_banks[] = { | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_GPU, | |
| .type = SVSB_TYPE_LOW, | |
| .set_freq_pct = svs_set_bank_freq_pct_v3, | |
| .get_volts = svs_get_bank_volts_v3, | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .turn_freq_base = 640000000, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x555555, | |
| .vco = 0x10, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(0), | |
| .ctl0 = 0x00100003, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 5, 16 }, { 5, 24 }, { 5, 0 }, { 15, 16 }, { 15, 24 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT, | |
| .mode_support = SVSB_MODE_INIT02, | |
| .freq_base = 640000000, | |
| .core_sel = 0x0fff0000, | |
| .dvt_fixed = 0x1, | |
| .vmax = 0x38, | |
| .vmin = 0x1c, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_GPU, | |
| .type = SVSB_TYPE_HIGH, | |
| .set_freq_pct = svs_set_bank_freq_pct_v3, | |
| .get_volts = svs_get_bank_volts_v3, | |
| .tzone_name = "gpu", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .turn_freq_base = 640000000, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x555555, | |
| .vco = 0x10, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(1), | |
| .ctl0 = 0x00100003, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 0, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 7, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 4, 16 }, { 4, 24 }, { 4, 0 }, { 14, 0 }, { 14, 8 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE, | |
| .mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON, | |
| .freq_base = 880000000, | |
| .core_sel = 0x0fff0001, | |
| .dvt_fixed = 0x4, | |
| .vmax = 0x38, | |
| .vmin = 0x1c, | |
| }, | |
| }; | |
| static struct svs_bank svs_mt8186_banks[] = { | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_CPU_BIG, | |
| .type = SVSB_TYPE_LOW, | |
| .set_freq_pct = svs_set_bank_freq_pct_v3, | |
| .get_volts = svs_get_bank_volts_v3, | |
| .cpu_id = 6, | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .turn_freq_base = 1670000000, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x1, | |
| .dc_config = 0x1, | |
| .vco = 0x10, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(0), | |
| .ctl0 = 0x00540003, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 3, 16 }, { 3, 24 }, { 3, 0 }, { 14, 16 }, { 14, 24 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT, | |
| .volt_od = 4, | |
| .mode_support = SVSB_MODE_INIT02, | |
| .freq_base = 1670000000, | |
| .core_sel = 0x0fff0100, | |
| .dvt_fixed = 0x3, | |
| .vmax = 0x59, | |
| .vmin = 0x20, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_CPU_BIG, | |
| .type = SVSB_TYPE_HIGH, | |
| .set_freq_pct = svs_set_bank_freq_pct_v3, | |
| .get_volts = svs_get_bank_volts_v3, | |
| .cpu_id = 6, | |
| .tzone_name = "cpu-big", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .turn_freq_base = 1670000000, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x1, | |
| .dc_config = 0x1, | |
| .vco = 0x10, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(1), | |
| .ctl0 = 0x00540003, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 8, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 8, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 2, 16 }, { 2, 24 }, { 2, 0 }, { 13, 0 }, { 13, 8 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE, | |
| .volt_od = 4, | |
| .mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON, | |
| .freq_base = 2050000000, | |
| .core_sel = 0x0fff0101, | |
| .dvt_fixed = 0x6, | |
| .vmax = 0x73, | |
| .vmin = 0x20, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_CPU_LITTLE, | |
| .set_freq_pct = svs_set_bank_freq_pct_v2, | |
| .get_volts = svs_get_bank_volts_v2, | |
| .cpu_id = 0, | |
| .tzone_name = "cpu-little", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x1, | |
| .dc_config = 0x1, | |
| .vco = 0x10, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(2), | |
| .ctl0 = 0x3210000f, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 8, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 8, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 4, 16 }, { 4, 24 }, { 4, 0 }, { 14, 0 }, { 14, 8 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE, | |
| .volt_od = 3, | |
| .mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON, | |
| .freq_base = 2000000000, | |
| .core_sel = 0x0fff0102, | |
| .dvt_fixed = 0x6, | |
| .vmax = 0x65, | |
| .vmin = 0x20, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_CCI, | |
| .set_freq_pct = svs_set_bank_freq_pct_v2, | |
| .get_volts = svs_get_bank_volts_v2, | |
| .tzone_name = "cci", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x1, | |
| .dc_config = 0x1, | |
| .vco = 0x10, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(3), | |
| .ctl0 = 0x3210000f, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 8, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 8, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 5, 16 }, { 5, 24 }, { 5, 0 }, { 15, 16 }, { 15, 24 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE, | |
| .volt_od = 3, | |
| .mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON, | |
| .freq_base = 1400000000, | |
| .core_sel = 0x0fff0103, | |
| .dvt_fixed = 0x6, | |
| .vmax = 0x65, | |
| .vmin = 0x20, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_GPU, | |
| .set_freq_pct = svs_set_bank_freq_pct_v2, | |
| .get_volts = svs_get_bank_volts_v2, | |
| .tzone_name = "gpu", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .volt_step = 6250, | |
| .volt_base = 400000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x1, | |
| .vco = 0x10, | |
| .chk_shift = 0x87, | |
| .int_st = BIT(4), | |
| .ctl0 = 0x00100003, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 8, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 7, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 6, 16 }, { 6, 24 }, { 6, 0 }, { 15, 8 }, { 15, 0 } | |
| } | |
| }, | |
| .volt_flags = SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE, | |
| .mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON, | |
| .freq_base = 850000000, | |
| .core_sel = 0x0fff0104, | |
| .dvt_fixed = 0x4, | |
| .vmax = 0x58, | |
| .vmin = 0x20, | |
| }, | |
| }; | |
| static struct svs_bank svs_mt8183_banks[] = { | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_CPU_LITTLE, | |
| .set_freq_pct = svs_set_bank_freq_pct_v2, | |
| .get_volts = svs_get_bank_volts_v2, | |
| .cpu_id = 0, | |
| .buck_name = "proc", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .vboot = 0x30, | |
| .volt_step = 6250, | |
| .volt_base = 500000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x555555, | |
| .vco = 0x10, | |
| .chk_shift = 0x77, | |
| .int_st = BIT(0), | |
| .ctl0 = 0x00010001, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 16, 0 }, { 16, 8 }, { 17, 16 }, { 16, 16 }, { 16, 24 } | |
| } | |
| }, | |
| .volt_flags = SVSB_INIT01_VOLT_INC_ONLY, | |
| .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02, | |
| .freq_base = 1989000000, | |
| .core_sel = 0x8fff0000, | |
| .dvt_fixed = 0x7, | |
| .vmax = 0x64, | |
| .vmin = 0x18, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_CPU_BIG, | |
| .set_freq_pct = svs_set_bank_freq_pct_v2, | |
| .get_volts = svs_get_bank_volts_v2, | |
| .cpu_id = 4, | |
| .buck_name = "proc", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .vboot = 0x30, | |
| .volt_step = 6250, | |
| .volt_base = 500000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x555555, | |
| .vco = 0x10, | |
| .chk_shift = 0x77, | |
| .int_st = BIT(1), | |
| .ctl0 = 0x00000001, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 18, 0 }, { 18, 8 }, { 17, 0 }, { 18, 16 }, { 18, 24 } | |
| } | |
| }, | |
| .volt_flags = SVSB_INIT01_VOLT_INC_ONLY, | |
| .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02, | |
| .freq_base = 1989000000, | |
| .core_sel = 0x8fff0001, | |
| .dvt_fixed = 0x7, | |
| .vmax = 0x58, | |
| .vmin = 0x10, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_CCI, | |
| .set_freq_pct = svs_set_bank_freq_pct_v2, | |
| .get_volts = svs_get_bank_volts_v2, | |
| .buck_name = "proc", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .vboot = 0x30, | |
| .volt_step = 6250, | |
| .volt_base = 500000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x555555, | |
| .vco = 0x10, | |
| .chk_shift = 0x77, | |
| .int_st = BIT(2), | |
| .ctl0 = 0x00100003, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 4, 0 }, { 4, 8 }, { 5, 16 }, { 4, 16 }, { 4, 24 } | |
| } | |
| }, | |
| .volt_flags = SVSB_INIT01_VOLT_INC_ONLY, | |
| .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02, | |
| .freq_base = 1196000000, | |
| .core_sel = 0x8fff0002, | |
| .dvt_fixed = 0x7, | |
| .vmax = 0x64, | |
| .vmin = 0x18, | |
| }, | |
| { | |
| .pdata = (const struct svs_bank_pdata) { | |
| .sw_id = SVSB_SWID_GPU, | |
| .set_freq_pct = svs_set_bank_freq_pct_v2, | |
| .get_volts = svs_get_bank_volts_v2, | |
| .buck_name = "mali", | |
| .tzone_name = "gpu", | |
| .opp_count = MAX_OPP_ENTRIES, | |
| .vboot = 0x30, | |
| .volt_step = 6250, | |
| .volt_base = 500000, | |
| .age_config = 0x555555, | |
| .dc_config = 0x555555, | |
| .vco = 0x10, | |
| .chk_shift = 0x77, | |
| .int_st = BIT(3), | |
| .ctl0 = 0x00050001, | |
| .tzone_htemp = 85000, | |
| .tzone_htemp_voffset = 0, | |
| .tzone_ltemp = 25000, | |
| .tzone_ltemp_voffset = 3, | |
| .dev_fuse_map = (const struct svs_fusemap[BDEV_MAX]) { | |
| { 6, 0 }, { 6, 8 }, { 5, 0 }, { 6, 16 }, { 6, 24 } | |
| } | |
| }, | |
| .volt_flags = SVSB_INIT01_PD_REQ | SVSB_INIT01_VOLT_INC_ONLY, | |
| .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02 | SVSB_MODE_MON, | |
| .freq_base = 900000000, | |
| .core_sel = 0x8fff0003, | |
| .dvt_fixed = 0x3, | |
| .vmax = 0x40, | |
| .vmin = 0x14, | |
| }, | |
| }; | |
| static const struct svs_platform_data svs_mt8195_platform_data = { | |
| .name = "mt8195-svs", | |
| .banks = svs_mt8195_banks, | |
| .efuse_parsing = svs_common_parse_efuse, | |
| .probe = svs_mt8192_platform_probe, | |
| .regs = svs_regs_v2, | |
| .bank_max = ARRAY_SIZE(svs_mt8195_banks), | |
| .ts_coeff = SVSB_TS_COEFF_MT8195, | |
| .glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) { | |
| { 0, 0 }, { 19, 4 } | |
| } | |
| }; | |
| static const struct svs_platform_data svs_mt8192_platform_data = { | |
| .name = "mt8192-svs", | |
| .banks = svs_mt8192_banks, | |
| .efuse_parsing = svs_common_parse_efuse, | |
| .probe = svs_mt8192_platform_probe, | |
| .regs = svs_regs_v2, | |
| .bank_max = ARRAY_SIZE(svs_mt8192_banks), | |
| .ts_coeff = SVSB_TS_COEFF_MT8195, | |
| .glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) { | |
| /* FT_PGM not present */ | |
| { -1, 0 }, { 19, 4 } | |
| } | |
| }; | |
| static const struct svs_platform_data svs_mt8188_platform_data = { | |
| .name = "mt8188-svs", | |
| .banks = svs_mt8188_banks, | |
| .efuse_parsing = svs_common_parse_efuse, | |
| .probe = svs_mt8192_platform_probe, | |
| .regs = svs_regs_v2, | |
| .bank_max = ARRAY_SIZE(svs_mt8188_banks), | |
| .ts_coeff = SVSB_TS_COEFF_MT8195, | |
| .glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) { | |
| /* FT_PGM and VMIN not present */ | |
| { -1, 0 }, { -1, 0 } | |
| } | |
| }; | |
| static const struct svs_platform_data svs_mt8186_platform_data = { | |
| .name = "mt8186-svs", | |
| .banks = svs_mt8186_banks, | |
| .efuse_parsing = svs_common_parse_efuse, | |
| .probe = svs_mt8192_platform_probe, | |
| .regs = svs_regs_v2, | |
| .bank_max = ARRAY_SIZE(svs_mt8186_banks), | |
| .ts_coeff = SVSB_TS_COEFF_MT8186, | |
| .glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) { | |
| /* FT_PGM and VMIN not present */ | |
| { -1, 0 }, { -1, 0 } | |
| } | |
| }; | |
| 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), | |
| .glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) { | |
| /* VMIN not present */ | |
| { 0, 4 }, { -1, 0 } | |
| } | |
| }; | |
| static const struct of_device_id svs_of_match[] = { | |
| { .compatible = "mediatek,mt8195-svs", .data = &svs_mt8195_platform_data }, | |
| { .compatible = "mediatek,mt8192-svs", .data = &svs_mt8192_platform_data }, | |
| { .compatible = "mediatek,mt8188-svs", .data = &svs_mt8188_platform_data }, | |
| { .compatible = "mediatek,mt8186-svs", .data = &svs_mt8186_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; | |
| svsp->ts_coeff = svsp_data->ts_coeff; | |
| ret = svsp_data->probe(svsp); | |
| if (ret) | |
| return ret; | |
| ret = svs_get_efuse_data(svsp, "svs-calibration-data", | |
| &svsp->efuse, &svsp->efuse_max); | |
| if (ret) | |
| return dev_err_probe(&pdev->dev, ret, "Cannot read SVS calibration\n"); | |
| ret = svs_get_efuse_data(svsp, "t-calibration-data", | |
| &svsp->tefuse, &svsp->tefuse_max); | |
| if (ret) { | |
| dev_err_probe(&pdev->dev, ret, "Cannot read SVS-Thermal calibration\n"); | |
| goto svs_probe_free_efuse; | |
| } | |
| if (!svsp_data->efuse_parsing(svsp, svsp_data)) { | |
| ret = dev_err_probe(svsp->dev, -EINVAL, "efuse data parsing failed\n"); | |
| goto svs_probe_free_tefuse; | |
| } | |
| ret = svs_bank_resource_setup(svsp); | |
| if (ret) { | |
| dev_err_probe(svsp->dev, ret, "svs bank resource setup fail\n"); | |
| goto svs_probe_free_tefuse; | |
| } | |
| svsp_irq = platform_get_irq(pdev, 0); | |
| if (svsp_irq < 0) { | |
| ret = svsp_irq; | |
| goto svs_probe_free_tefuse; | |
| } | |
| svsp->main_clk = devm_clk_get(svsp->dev, "main"); | |
| if (IS_ERR(svsp->main_clk)) { | |
| ret = dev_err_probe(svsp->dev, PTR_ERR(svsp->main_clk), | |
| "failed to get clock\n"); | |
| goto svs_probe_free_tefuse; | |
| } | |
| ret = clk_prepare_enable(svsp->main_clk); | |
| if (ret) { | |
| dev_err_probe(svsp->dev, ret, "cannot enable main clk\n"); | |
| goto svs_probe_free_tefuse; | |
| } | |
| svsp->base = of_iomap(svsp->dev->of_node, 0); | |
| if (IS_ERR_OR_NULL(svsp->base)) { | |
| ret = dev_err_probe(svsp->dev, -EINVAL, "cannot find svs register base\n"); | |
| 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_probe(svsp->dev, ret, "register irq(%d) failed\n", svsp_irq); | |
| goto svs_probe_iounmap; | |
| } | |
| ret = svs_start(svsp); | |
| if (ret) { | |
| dev_err_probe(svsp->dev, ret, "svs start fail\n"); | |
| goto svs_probe_iounmap; | |
| } | |
| #ifdef CONFIG_DEBUG_FS | |
| ret = svs_create_debug_cmds(svsp); | |
| if (ret) { | |
| dev_err_probe(svsp->dev, ret, "svs create debug cmds fail\n"); | |
| 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_tefuse: | |
| kfree(svsp->tefuse); | |
| svs_probe_free_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_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); | |
| MODULE_DESCRIPTION("MediaTek SVS driver"); | |
| MODULE_LICENSE("GPL"); |