| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2020 MediaTek Inc. |
| */ |
| |
| #include <linux/bits.h> |
| #include <linux/clk.h> |
| #include <linux/completion.h> |
| #include <linux/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_qos.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 sw id */ |
| #define SVSB_CPU_LITTLE BIT(0) |
| #define SVSB_CPU_BIG BIT(1) |
| #define SVSB_CCI BIT(2) |
| #define SVSB_GPU BIT(3) |
| |
| /* svs bank 2-line type */ |
| #define SVSB_LOW BIT(4) |
| #define SVSB_HIGH BIT(5) |
| |
| /* 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 init01 condition */ |
| #define SVSB_INIT01_VOLT_IGNORE BIT(1) |
| #define SVSB_INIT01_VOLT_INC_ONLY BIT(2) |
| #define SVSB_INIT01_CLK_EN BIT(31) |
| |
| /* svs bank common setting */ |
| #define SVSB_TZONE_HIGH_TEMP_MAX U32_MAX |
| #define SVSB_RUNCONFIG_DEFAULT 0x80000000 |
| #define SVSB_DC_SIGNED_BIT 0x8000 |
| #define SVSB_INTEN_INIT0x 0x00005f01 |
| #define SVSB_INTEN_MONVOPEN 0x00ff0000 |
| #define SVSB_EN_OFF 0x0 |
| #define SVSB_EN_MASK 0x7 |
| #define SVSB_EN_INIT01 0x1 |
| #define SVSB_EN_INIT02 0x5 |
| #define SVSB_EN_MON 0x2 |
| #define SVSB_INTSTS_MONVOP 0x00ff0000 |
| #define SVSB_INTSTS_COMPLETE 0x1 |
| #define SVSB_INTSTS_CLEAN 0x00ffffff |
| |
| #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(name) {__stringify(name), &svs_##name##_debug_fops} |
| |
| static DEFINE_SPINLOCK(mtk_svs_lock); |
| |
| /* |
| * enum svsb_phase - svs bank phase enumeration |
| * @SVSB_PHASE_INIT01: basic init for svs bank |
| * @SVSB_PHASE_INIT02: svs bank can provide voltages |
| * @SVSB_PHASE_MON: svs bank can provide voltages with thermal effect |
| * @SVSB_PHASE_ERROR: svs bank encounters unexpected condition |
| * |
| * Each svs bank has its own independent phase. 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_INIT01 = 0, |
| SVSB_PHASE_INIT02, |
| SVSB_PHASE_MON, |
| SVSB_PHASE_ERROR, |
| }; |
| |
| 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_NUM, |
| }; |
| |
| 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 thermal_parameter - This is for storing thermal efuse data. |
| * We calculate thermal efuse data to produce "mts" and "bts" for |
| * svs bank mon mode. |
| */ |
| struct thermal_parameter { |
| int adc_ge_t; |
| int adc_oe_t; |
| int ge; |
| int oe; |
| int gain; |
| int o_vtsabb; |
| int o_vtsmcu1; |
| int o_vtsmcu2; |
| int o_vtsmcu3; |
| int o_vtsmcu4; |
| int o_vtsmcu5; |
| int degc_cali; |
| int adc_cali_en_t; |
| int o_slope; |
| int o_slope_sign; |
| int ts_id; |
| }; |
| |
| /* |
| * struct svs_bank - svs bank representation |
| * @dev: svs bank device |
| * @opp_dev: device for opp table/buck control |
| * @pd_dev: power domain device for SoC mtcmos control |
| * @init_completion: the timeout completion for bank init |
| * @buck: phandle of the regulator |
| * @lock: mutex lock to protect voltage update process |
| * @phase: bank current phase |
| * @name: bank name |
| * @tzone_name: thermal zone name |
| * @buck_name: regulator name |
| * @suspended: suspend flag of this bank |
| * @pd_req: bank's power-domain on request |
| * @volt_offset: bank voltage offset controlled by svs software |
| * @mode_support: bank mode support. |
| * @opp_freqs: signed-off frequencies from default opp table |
| * @opp_volts: signed-off voltages from default opp table |
| * @freqs_pct: percent of "opp_freqs / freq_base" for bank init |
| * @volts: bank voltages |
| * @reg_data: bank register data of each phase |
| * @freq_base: reference frequency for bank init |
| * @turn_freq_base: refenrece frequency for turn point |
| * @vboot: voltage request for bank init01 stage only |
| * @volt_step: bank voltage step |
| * @volt_base: bank voltage base |
| * @init01_volt_flag: bank init01 voltage flag |
| * @vmax: bank voltage maximum |
| * @vmin: bank voltage minimum |
| * @temp: bank temperature |
| * @temp_upper_bound: bank temperature upper bound |
| * @temp_lower_bound: bank temperature lower bound |
| * @tzone_high_temp: thermal zone high temperature threshold |
| * @tzone_high_temp_offset: thermal zone high temperature offset |
| * @tzone_low_temp: thermal zone low temperature threshold |
| * @tzone_low_temp_offset: thermal zone low temperature offset |
| * @core_sel: bank selection |
| * @opp_count: bank opp count |
| * @int_st: bank interrupt identification |
| * @sw_id: bank software identification |
| * @hw_id: bank hardware identification |
| * @ctl0: bank thermal sensor selection |
| * @cpu_id: cpu core id for SVS CPU only |
| * @turn_pt: turn point informs which opp_volt calculated by high/low bank. |
| * @type: bank type to represent it is 2-line (high/low) bank or 1-line bank. |
| * |
| * Other structure members which are not listed above are svs platform |
| * efuse data for bank init |
| */ |
| struct svs_bank { |
| struct device *dev; |
| struct device *opp_dev; |
| struct device *pd_dev; |
| struct completion init_completion; |
| struct regulator *buck; |
| struct mutex lock; /* lock to protect voltage update process */ |
| enum svsb_phase phase; |
| char *name; |
| char *tzone_name; |
| char *buck_name; |
| bool suspended; |
| bool pd_req; |
| s32 volt_offset; |
| u32 mode_support; |
| u32 opp_freqs[16]; |
| u32 opp_volts[16]; |
| u32 freqs_pct[16]; |
| u32 volts[16]; |
| u32 reg_data[3][SVS_REG_NUM]; |
| u32 freq_base; |
| u32 turn_freq_base; |
| u32 vboot; |
| u32 volt_step; |
| u32 volt_base; |
| u32 init01_volt_flag; |
| u32 vmax; |
| u32 vmin; |
| u32 bts; |
| u32 mts; |
| u32 bdes; |
| u32 mdes; |
| u32 mtdes; |
| u32 dcbdet; |
| u32 dcmdet; |
| u32 dthi; |
| u32 dtlo; |
| u32 det_window; |
| u32 det_max; |
| u32 age_config; |
| u32 age_voffset_in; |
| u32 agem; |
| u32 dc_config; |
| u32 dc_voffset_in; |
| u32 dvt_fixed; |
| u32 vco; |
| u32 chk_shift; |
| u32 temp; |
| u32 temp_upper_bound; |
| u32 temp_lower_bound; |
| u32 tzone_high_temp; |
| u32 tzone_high_temp_offset; |
| u32 tzone_low_temp; |
| u32 tzone_low_temp_offset; |
| u32 core_sel; |
| u32 opp_count; |
| u32 int_st; |
| u32 sw_id; |
| u32 hw_id; |
| u32 ctl0; |
| u32 cpu_id; |
| u32 turn_pt; |
| u32 type; |
| }; |
| |
| /* |
| * struct svs_platform - svs platform data |
| * @dev: svs platform device |
| * @base: svs platform register address base |
| * @main_clk: main clock for svs bank |
| * @pbank: phandle of svs bank and needs to be protected by spin_lock |
| * @banks: phandle of the banks that support |
| * @efuse_parsing: phandle of efuse parsing function |
| * @set_freqs_pct: phandle of set frequencies percent function |
| * @get_vops: phandle of get bank voltages function |
| * @irqflags: irq settings flags |
| * @rst: svs reset control |
| * @regs: phandle to the registers map |
| * @efuse_num: the total number of svs platform efuse |
| * @tefuse_num: the total number of thermal efuse |
| * @bank_num: the total number of banks |
| * @efuse_check: the svs efuse check index |
| * @efuse: svs platform efuse data received from NVMEM framework |
| * @tefuse: thermal efuse data received from NVMEM framework |
| * @name: svs platform name |
| */ |
| struct svs_platform { |
| struct device *dev; |
| void __iomem *base; |
| struct clk *main_clk; |
| struct svs_bank *pbank; |
| struct svs_bank *banks; |
| bool (*efuse_parsing)(struct svs_platform *svsp); |
| void (*set_freqs_pct)(struct svs_platform *svsp); |
| void (*get_vops)(struct svs_platform *svsp); |
| unsigned long irqflags; |
| struct reset_control *rst; |
| const u32 *regs; |
| char *name; |
| size_t efuse_num; |
| size_t tefuse_num; |
| u32 bank_num; |
| u32 efuse_check; |
| u32 *efuse; |
| u32 *tefuse; |
| }; |
| |
| 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(struct svs_platform *svsp, enum svs_reg_index rg_i) |
| { |
| return readl(svsp->base + svsp->regs[rg_i]); |
| } |
| |
| static void svs_writel(struct svs_platform *svsp, u32 val, |
| enum svs_reg_index rg_i) |
| { |
| writel(val, svsp->base + svsp->regs[rg_i]); |
| } |
| |
| static void svs_switch_bank(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| |
| svs_writel(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_freqs[i], |
| true); |
| if (IS_ERR(opp)) { |
| dev_err(svsb->dev, "cannot find freq = %u (%ld)\n", |
| svsb->opp_freqs[i], PTR_ERR(opp)); |
| return PTR_ERR(opp); |
| } |
| |
| opp_u_volt = dev_pm_opp_get_voltage(opp); |
| svsb->volts[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_get_bank_zone_temperature(const char *tzone_name, |
| int *tzone_temp) |
| { |
| struct thermal_zone_device *tzd; |
| |
| tzd = thermal_zone_get_zone_by_name(tzone_name); |
| if (IS_ERR(tzd)) |
| return PTR_ERR(tzd); |
| |
| return thermal_zone_get_temp(tzd, tzone_temp); |
| } |
| |
| static int svs_adjust_pm_opp_volts(struct svs_bank *svsb, bool force_update) |
| { |
| int tzone_temp, ret = -EPERM; |
| u32 i, svsb_volt, opp_volt, temp_offset = 0, opp_start, opp_stop; |
| |
| mutex_lock(&svsb->lock); |
| |
| /* |
| * If svs bank is suspended, it means signed-off voltages are applied. |
| * Don't need to update opp voltage anymore. |
| */ |
| if (svsb->suspended && !force_update) { |
| dev_notice(svsb->dev, "bank is suspended\n"); |
| ret = -EPERM; |
| goto unlock_mutex; |
| } |
| |
| /* |
| * 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) { |
| if (svsb->temp > svsb->temp_upper_bound && |
| svsb->temp < svsb->temp_lower_bound) { |
| dev_warn(svsb->dev, "svsb temp = 0x%x?\n", svsb->temp); |
| ret = -EINVAL; |
| goto unlock_mutex; |
| } |
| |
| ret = svs_get_bank_zone_temperature(svsb->tzone_name, |
| &tzone_temp); |
| if (ret) { |
| dev_err(svsb->dev, "no %s? (%d), run default volts\n", |
| svsb->tzone_name, ret); |
| svsb->phase = SVSB_PHASE_ERROR; |
| } |
| |
| if (tzone_temp >= svsb->tzone_high_temp) |
| temp_offset += svsb->tzone_high_temp_offset; |
| else if (tzone_temp <= svsb->tzone_low_temp) |
| temp_offset += svsb->tzone_low_temp_offset; |
| |
| /* 2-line bank takes thermal factor to update all opp volts */ |
| if (svsb->type == SVSB_HIGH || svsb->type == SVSB_LOW) { |
| opp_start = 0; |
| opp_stop = svsb->opp_count; |
| } |
| } |
| |
| /* vmin <= svsb_volt (opp_volt) <= signed-off (default) voltage */ |
| for (i = opp_start; i < opp_stop; i++) { |
| if (svsb->phase == SVSB_PHASE_MON) { |
| svsb_volt = max(svsb->volts[i] + svsb->volt_offset + |
| temp_offset, svsb->vmin); |
| opp_volt = svs_bank_volt_to_opp_volt(svsb_volt, |
| svsb->volt_step, |
| svsb->volt_base); |
| } else if (svsb->phase == SVSB_PHASE_INIT02) { |
| svsb_volt = max(svsb->volts[i] + svsb->volt_offset, |
| svsb->vmin); |
| opp_volt = svs_bank_volt_to_opp_volt(svsb_volt, |
| svsb->volt_step, |
| svsb->volt_base); |
| } else if (svsb->phase == SVSB_PHASE_ERROR) { |
| opp_volt = svsb->opp_volts[i]; |
| } else { |
| dev_err(svsb->dev, "unknown phase: %u?\n", svsb->phase); |
| ret = -EINVAL; |
| goto unlock_mutex; |
| } |
| |
| opp_volt = min(opp_volt, svsb->opp_volts[i]); |
| ret = dev_pm_opp_adjust_voltage(svsb->opp_dev, |
| svsb->opp_freqs[i], |
| opp_volt, opp_volt, |
| svsb->opp_volts[i]); |
| if (ret) { |
| dev_err(svsb->dev, "set voltage fail: %d\n", ret); |
| goto unlock_mutex; |
| } |
| } |
| |
| unlock_mutex: |
| mutex_unlock(&svsb->lock); |
| |
| return ret; |
| } |
| |
| static u32 interpolate(u32 f0, u32 f1, u32 v0, u32 v1, u32 fx) |
| { |
| u32 vy; |
| |
| if (v0 == v1 || f0 == f1) |
| return v0; |
| |
| /* *100 to have decimal fraction factor */ |
| vy = (v0 * 100) - ((((v0 - v1) * 100) / (f0 - f1)) * (f0 - fx)); |
| |
| return DIV_ROUND_UP(vy, 100); |
| } |
| |
| static void svs_get_vops_v3(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| u32 i, vop_i, *vop, vop74, vop30, mask7_0 = GENMASK(7, 0); |
| u32 b_sft, bits8 = 8, shift_byte = 0, reg_4bytes = 4; |
| u32 middle_index = (svsb->opp_count / 2); |
| u32 opp_start = 0, opp_stop = 0, turn_pt = svsb->turn_pt; |
| |
| /* get vops v3 doesn't use mon mode voltages */ |
| if (svsb->phase == SVSB_PHASE_MON) |
| return; |
| |
| vop74 = svs_readl(svsp, VOP74); |
| vop30 = svs_readl(svsp, VOP30); |
| |
| if (turn_pt < middle_index) { |
| if (svsb->type == SVSB_HIGH) { |
| /* We attain volts[0 ~ (turn_pt - 1)] */ |
| for (i = 0; i < turn_pt; i++) { |
| b_sft = bits8 * (shift_byte % reg_4bytes); |
| vop = (shift_byte < reg_4bytes) ? &vop30 : |
| &vop74; |
| svsb->volts[i] = (*vop >> b_sft) & mask7_0; |
| shift_byte++; |
| } |
| } else if (svsb->type == SVSB_LOW) { |
| /* |
| * We attain volts[turn_pt] + |
| * volts[vop_i ~ (opp_count - 1)] |
| */ |
| vop_i = svsb->opp_count - 7; |
| svsb->volts[turn_pt] = vop30 & mask7_0; |
| shift_byte++; |
| for (i = vop_i; i < svsb->opp_count; i++) { |
| b_sft = bits8 * (shift_byte % reg_4bytes); |
| vop = (shift_byte < reg_4bytes) ? &vop30 : |
| &vop74; |
| svsb->volts[i] = (*vop >> b_sft) & mask7_0; |
| shift_byte++; |
| } |
| |
| /* |
| * We attain volts[turn_pt + 1 ~ (vop_i - 1)] |
| * by interpolate |
| */ |
| for (i = turn_pt + 1; i < vop_i; i++) |
| svsb->volts[i] = |
| interpolate(svsb->freqs_pct[turn_pt], |
| svsb->freqs_pct[vop_i], |
| svsb->volts[turn_pt], |
| svsb->volts[vop_i], |
| svsb->freqs_pct[i]); |
| } |
| } else { |
| if (svsb->type == SVSB_HIGH) { |
| /* We attain volts[0] + volts[vop_i ~ (turn_pt - 1)] */ |
| vop_i = turn_pt - 7; |
| svsb->volts[0] = vop30 & mask7_0; |
| shift_byte++; |
| for (i = vop_i; i < turn_pt; i++) { |
| b_sft = bits8 * (shift_byte % reg_4bytes); |
| vop = (shift_byte < reg_4bytes) ? &vop30 : |
| &vop74; |
| svsb->volts[i] = (*vop >> b_sft) & mask7_0; |
| shift_byte++; |
| } |
| |
| /* We attain volts[1 ~ (vop_i - 1)] by interpolate */ |
| for (i = 1; i < vop_i; i++) |
| svsb->volts[i] = |
| interpolate(svsb->freqs_pct[0], |
| svsb->freqs_pct[vop_i], |
| svsb->volts[0], |
| svsb->volts[vop_i], |
| svsb->freqs_pct[i]); |
| } else if (svsb->type == SVSB_LOW) { |
| /* We attain volts[turn_pt ~ (opp_count - 1)] */ |
| for (i = turn_pt; i < svsb->opp_count; i++) { |
| b_sft = bits8 * (shift_byte % reg_4bytes); |
| vop = (shift_byte < reg_4bytes) ? &vop30 : |
| &vop74; |
| svsb->volts[i] = (*vop >> b_sft) & mask7_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++) |
| svsb->volts[i] -= svsb->dvt_fixed; |
| } |
| |
| static void svs_set_freqs_pct_v3(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| u32 i, freq_i, *freq_pct, freq_pct74 = 0, freq_pct30 = 0; |
| u32 b_sft, bits8 = 8, shift_byte = 0, reg_4bytes = 4; |
| u32 middle_index = (svsb->opp_count / 2), mask7_0 = GENMASK(7, 0); |
| u32 turn_pt = middle_index; |
| |
| for (i = 0; i < svsb->opp_count; i++) { |
| if (svsb->opp_freqs[i] <= svsb->turn_freq_base) { |
| svsb->turn_pt = i; |
| break; |
| } |
| } |
| |
| turn_pt = svsb->turn_pt; |
| |
| /* Target is to fill out freq_pct74 / freq_pct30 */ |
| if (turn_pt < middle_index) { |
| if (svsb->type == SVSB_HIGH) { |
| /* We select freqs_pct[0 ~ (turn_pt - 1)] */ |
| for (i = 0; i < turn_pt; i++) { |
| b_sft = bits8 * (shift_byte % reg_4bytes); |
| freq_pct = (shift_byte < reg_4bytes) ? |
| &freq_pct30 : &freq_pct74; |
| *freq_pct |= (svsb->freqs_pct[i] << b_sft); |
| shift_byte++; |
| } |
| } else if (svsb->type == SVSB_LOW) { |
| /* |
| * We select freqs_pct[turn_pt] + |
| * freqs_pct[(opp_count - 7) ~ (opp_count -1)] |
| */ |
| freq_pct30 = svsb->freqs_pct[turn_pt] & mask7_0; |
| shift_byte++; |
| freq_i = svsb->opp_count - 7; |
| for (i = freq_i; i < svsb->opp_count; i++) { |
| b_sft = bits8 * (shift_byte % reg_4bytes); |
| freq_pct = (shift_byte < reg_4bytes) ? |
| &freq_pct30 : &freq_pct74; |
| *freq_pct |= (svsb->freqs_pct[i] << b_sft); |
| shift_byte++; |
| } |
| } |
| } else { |
| if (svsb->type == SVSB_HIGH) { |
| /* |
| * We select freqs_pct[0] + |
| * freqs_pct[(turn_pt - 7) ~ (turn_pt - 1)] |
| */ |
| freq_pct30 = svsb->freqs_pct[0] & mask7_0; |
| shift_byte++; |
| freq_i = turn_pt - 7; |
| for (i = freq_i; i < turn_pt; i++) { |
| b_sft = bits8 * (shift_byte % reg_4bytes); |
| freq_pct = (shift_byte < reg_4bytes) ? |
| &freq_pct30 : &freq_pct74; |
| *freq_pct |= (svsb->freqs_pct[i] << b_sft); |
| shift_byte++; |
| } |
| } else if (svsb->type == SVSB_LOW) { |
| /* We select freqs_pct[turn_pt ~ (opp_count - 1)] */ |
| for (i = turn_pt; i < svsb->opp_count; i++) { |
| b_sft = bits8 * (shift_byte % reg_4bytes); |
| freq_pct = (shift_byte < reg_4bytes) ? |
| &freq_pct30 : &freq_pct74; |
| *freq_pct |= (svsb->freqs_pct[i] << b_sft); |
| shift_byte++; |
| } |
| } |
| } |
| |
| svs_writel(svsp, freq_pct74, FREQPCT74); |
| svs_writel(svsp, freq_pct30, FREQPCT30); |
| } |
| |
| static void svs_get_vops_v2(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| u32 temp, i; |
| |
| temp = svs_readl(svsp, VOP74); |
| svsb->volts[14] = (temp >> 24) & GENMASK(7, 0); |
| svsb->volts[12] = (temp >> 16) & GENMASK(7, 0); |
| svsb->volts[10] = (temp >> 8) & GENMASK(7, 0); |
| svsb->volts[8] = (temp & GENMASK(7, 0)); |
| |
| temp = svs_readl(svsp, VOP30); |
| svsb->volts[6] = (temp >> 24) & GENMASK(7, 0); |
| svsb->volts[4] = (temp >> 16) & GENMASK(7, 0); |
| svsb->volts[2] = (temp >> 8) & GENMASK(7, 0); |
| svsb->volts[0] = (temp & GENMASK(7, 0)); |
| |
| for (i = 0; i <= 12; i += 2) |
| svsb->volts[i + 1] = |
| interpolate(svsb->freqs_pct[i], |
| svsb->freqs_pct[i + 2], |
| svsb->volts[i], |
| svsb->volts[i + 2], |
| svsb->freqs_pct[i + 1]); |
| |
| svsb->volts[15] = |
| interpolate(svsb->freqs_pct[12], |
| svsb->freqs_pct[14], |
| svsb->volts[12], |
| svsb->volts[14], |
| svsb->freqs_pct[15]); |
| } |
| |
| static void svs_set_freqs_pct_v2(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| |
| svs_writel(svsp, |
| (svsb->freqs_pct[14] << 24) | |
| (svsb->freqs_pct[12] << 16) | |
| (svsb->freqs_pct[10] << 8) | |
| svsb->freqs_pct[8], |
| FREQPCT74); |
| |
| svs_writel(svsp, |
| (svsb->freqs_pct[6] << 24) | |
| (svsb->freqs_pct[4] << 16) | |
| (svsb->freqs_pct[2] << 8) | |
| svsb->freqs_pct[0], |
| FREQPCT30); |
| } |
| |
| static void svs_set_bank_phase(struct svs_platform *svsp, |
| enum svsb_phase target_phase) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| u32 des_char, temp_char, det_char, limit_vals; |
| u32 init2vals, ts_calcs, val, filter, i; |
| |
| svs_switch_bank(svsp); |
| |
| des_char = (svsb->bdes << 8) | svsb->mdes; |
| svs_writel(svsp, des_char, DESCHAR); |
| |
| temp_char = (svsb->vco << 16) | (svsb->mtdes << 8) | svsb->dvt_fixed; |
| svs_writel(svsp, temp_char, TEMPCHAR); |
| |
| det_char = (svsb->dcbdet << 8) | svsb->dcmdet; |
| svs_writel(svsp, det_char, DETCHAR); |
| |
| svs_writel(svsp, svsb->dc_config, DCCONFIG); |
| svs_writel(svsp, svsb->age_config, AGECONFIG); |
| |
| if (!svsb->agem) { |
| svs_writel(svsp, SVSB_RUNCONFIG_DEFAULT, RUNCONFIG); |
| } else { |
| val = 0x0; |
| |
| for (i = 0; i < 24; i += 2) { |
| filter = 0x3 << i; |
| |
| if (!(svsb->age_config & filter)) |
| val |= (0x1 << i); |
| else |
| val |= (svsb->age_config & filter); |
| } |
| svs_writel(svsp, val, RUNCONFIG); |
| } |
| |
| svsp->set_freqs_pct(svsp); |
| |
| limit_vals = (svsb->vmax << 24) | (svsb->vmin << 16) | |
| (svsb->dthi << 8) | svsb->dtlo; |
| svs_writel(svsp, limit_vals, LIMITVALS); |
| svs_writel(svsp, svsb->vboot, VBOOT); |
| svs_writel(svsp, svsb->det_window, DETWINDOW); |
| svs_writel(svsp, svsb->det_max, CONFIG); |
| |
| if (svsb->chk_shift) |
| svs_writel(svsp, svsb->chk_shift, CHKSHIFT); |
| |
| if (svsb->ctl0) |
| svs_writel(svsp, svsb->ctl0, CTL0); |
| |
| svs_writel(svsp, SVSB_INTSTS_CLEAN, INTSTS); |
| |
| switch (target_phase) { |
| case SVSB_PHASE_INIT01: |
| svs_writel(svsp, SVSB_INTEN_INIT0x, INTEN); |
| svs_writel(svsp, SVSB_EN_INIT01, SVSEN); |
| break; |
| case SVSB_PHASE_INIT02: |
| svs_writel(svsp, SVSB_INTEN_INIT0x, INTEN); |
| init2vals = (svsb->age_voffset_in << 16) | svsb->dc_voffset_in; |
| svs_writel(svsp, init2vals, INIT2VALS); |
| svs_writel(svsp, SVSB_EN_INIT02, SVSEN); |
| break; |
| case SVSB_PHASE_MON: |
| ts_calcs = (svsb->bts << 12) | svsb->mts; |
| svs_writel(svsp, ts_calcs, TSCALCS); |
| svs_writel(svsp, SVSB_INTEN_MONVOPEN, INTEN); |
| svs_writel(svsp, SVSB_EN_MON, SVSEN); |
| break; |
| default: |
| WARN_ON(1); |
| break; |
| } |
| } |
| |
| static inline void svs_init01_isr_handler(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| enum svs_reg_index rg_i; |
| |
| dev_info(svsb->dev, "%s: VDN74~30:0x%08x~0x%08x, DC:0x%08x\n", |
| __func__, svs_readl(svsp, VDESIGN74), |
| svs_readl(svsp, VDESIGN30), svs_readl(svsp, DCVALUES)); |
| |
| for (rg_i = DESCHAR; rg_i < SVS_REG_NUM; rg_i++) |
| svsb->reg_data[SVSB_PHASE_INIT01][rg_i] = svs_readl(svsp, rg_i); |
| |
| svsb->phase = SVSB_PHASE_INIT01; |
| svsb->dc_voffset_in = ~(svs_readl(svsp, DCVALUES) & GENMASK(15, 0)) + 1; |
| if (svsb->init01_volt_flag == SVSB_INIT01_VOLT_IGNORE || |
| ((svsb->dc_voffset_in & SVSB_DC_SIGNED_BIT) && |
| svsb->init01_volt_flag == SVSB_INIT01_VOLT_INC_ONLY)) |
| svsb->dc_voffset_in = 0; |
| |
| svsb->age_voffset_in = svs_readl(svsp, AGEVALUES) & GENMASK(15, 0); |
| |
| svs_writel(svsp, SVSB_EN_OFF, SVSEN); |
| svs_writel(svsp, SVSB_INTSTS_COMPLETE, INTSTS); |
| |
| /* svs init01 clock gating */ |
| svsb->core_sel &= ~SVSB_INIT01_CLK_EN; |
| } |
| |
| static inline void svs_init02_isr_handler(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| enum svs_reg_index rg_i; |
| |
| dev_info(svsb->dev, "%s: VOP74~30:0x%08x~0x%08x, DC:0x%08x\n", |
| __func__, svs_readl(svsp, VOP74), svs_readl(svsp, VOP30), |
| svs_readl(svsp, DCVALUES)); |
| |
| for (rg_i = DESCHAR; rg_i < SVS_REG_NUM; rg_i++) |
| svsb->reg_data[SVSB_PHASE_INIT02][rg_i] = svs_readl(svsp, rg_i); |
| |
| svsb->phase = SVSB_PHASE_INIT02; |
| svsp->get_vops(svsp); |
| |
| svs_writel(svsp, SVSB_EN_OFF, SVSEN); |
| svs_writel(svsp, SVSB_INTSTS_COMPLETE, INTSTS); |
| } |
| |
| static inline void svs_mon_mode_isr_handler(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| enum svs_reg_index rg_i; |
| |
| for (rg_i = DESCHAR; rg_i < SVS_REG_NUM; rg_i++) |
| svsb->reg_data[SVSB_PHASE_MON][rg_i] = svs_readl(svsp, rg_i); |
| |
| svsb->phase = SVSB_PHASE_MON; |
| svsb->temp = svs_readl(svsp, TEMP) & GENMASK(7, 0); |
| svsp->get_vops(svsp); |
| |
| svs_writel(svsp, SVSB_INTSTS_MONVOP, INTSTS); |
| } |
| |
| static inline void svs_error_isr_handler(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb = svsp->pbank; |
| enum svs_reg_index rg_i; |
| |
| dev_err(svsb->dev, "%s: CORESEL = 0x%08x\n", |
| __func__, svs_readl(svsp, CORESEL)); |
| dev_err(svsb->dev, "SVSEN = 0x%08x, INTSTS = 0x%08x\n", |
| svs_readl(svsp, SVSEN), svs_readl(svsp, INTSTS)); |
| dev_err(svsb->dev, "SMSTATE0 = 0x%08x, SMSTATE1 = 0x%08x\n", |
| svs_readl(svsp, SMSTATE0), svs_readl(svsp, SMSTATE1)); |
| dev_err(svsb->dev, "TEMP = 0x%08x\n", svs_readl(svsp, TEMP)); |
| |
| for (rg_i = DESCHAR; rg_i < SVS_REG_NUM; rg_i++) |
| svsb->reg_data[SVSB_PHASE_MON][rg_i] = svs_readl(svsp, rg_i); |
| |
| svsb->mode_support = SVSB_MODE_ALL_DISABLE; |
| svsb->phase = SVSB_PHASE_ERROR; |
| |
| svs_writel(svsp, SVSB_EN_OFF, SVSEN); |
| svs_writel(svsp, SVSB_INTSTS_CLEAN, 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_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| WARN_ON(!svsb); |
| |
| spin_lock_irqsave(&mtk_svs_lock, flags); |
| svsp->pbank = svsb; |
| |
| /* Find out which svs bank fires interrupt */ |
| if (svsb->int_st & svs_readl(svsp, INTST)) { |
| spin_unlock_irqrestore(&mtk_svs_lock, flags); |
| continue; |
| } |
| |
| if (!svsb->suspended) { |
| svs_switch_bank(svsp); |
| int_sts = svs_readl(svsp, INTSTS); |
| svs_en = svs_readl(svsp, SVSEN); |
| |
| if (int_sts == SVSB_INTSTS_COMPLETE && |
| ((svs_en & SVSB_EN_MASK) == SVSB_EN_INIT01)) |
| svs_init01_isr_handler(svsp); |
| else if ((int_sts == SVSB_INTSTS_COMPLETE) && |
| ((svs_en & SVSB_EN_MASK) == SVSB_EN_INIT02)) |
| svs_init02_isr_handler(svsp); |
| else if ((int_sts & SVSB_INTSTS_MONVOP)) |
| svs_mon_mode_isr_handler(svsp); |
| else |
| svs_error_isr_handler(svsp); |
| } |
| |
| spin_unlock_irqrestore(&mtk_svs_lock, flags); |
| break; |
| } |
| |
| if (svsb->phase != SVSB_PHASE_INIT01) |
| svs_adjust_pm_opp_volts(svsb, false); |
| |
| if (svsb->phase == SVSB_PHASE_INIT01 || |
| svsb->phase == SVSB_PHASE_INIT02) |
| complete(&svsb->init_completion); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void svs_mon_mode(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb; |
| unsigned long flags; |
| u32 idx; |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (!(svsb->mode_support & SVSB_MODE_MON)) |
| continue; |
| |
| spin_lock_irqsave(&mtk_svs_lock, flags); |
| svsp->pbank = svsb; |
| svs_set_bank_phase(svsp, SVSB_PHASE_MON); |
| spin_unlock_irqrestore(&mtk_svs_lock, flags); |
| } |
| } |
| |
| 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_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (!(svsb->mode_support & SVSB_MODE_INIT02)) |
| continue; |
| |
| reinit_completion(&svsb->init_completion); |
| spin_lock_irqsave(&mtk_svs_lock, flags); |
| svsp->pbank = svsb; |
| svs_set_bank_phase(svsp, SVSB_PHASE_INIT02); |
| spin_unlock_irqrestore(&mtk_svs_lock, flags); |
| |
| time_left = |
| wait_for_completion_timeout(&svsb->init_completion, |
| msecs_to_jiffies(2000)); |
| 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_num; 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 int svs_init01(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb; |
| struct pm_qos_request *qos_request; |
| unsigned long flags, time_left; |
| bool search_done, does_svsb_set_pd_on; |
| int ret = 0; |
| u32 opp_freqs, opp_vboot, buck_volt, idx, i; |
| |
| qos_request = kzalloc(sizeof(*qos_request), GFP_KERNEL); |
| if (!qos_request) |
| return -ENOMEM; |
| |
| /* Let CPUs leave idle-off state for initializing svs_init01. */ |
| cpu_latency_qos_add_request(qos_request, 0); |
| |
| /* |
| * Sometimes two svs banks use the same buck. |
| * Therefore, we set each svs bank to vboot voltage first. |
| */ |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (!(svsb->mode_support & SVSB_MODE_INIT01)) |
| continue; |
| |
| search_done = false; |
| does_svsb_set_pd_on = false; |
| |
| if (regulator_set_mode(svsb->buck, REGULATOR_MODE_FAST)) |
| dev_notice(svsb->dev, "set fast mode fail\n"); |
| |
| if (svsb->pd_req) { |
| ret = regulator_enable(svsb->buck); |
| if (ret) { |
| dev_err(svsb->dev, "\"%s\" enable fail: %d\n", |
| svsb->buck_name, ret); |
| goto init01_finish; |
| } |
| |
| if (!pm_runtime_enabled(svsb->pd_dev)) { |
| pm_runtime_enable(svsb->pd_dev); |
| does_svsb_set_pd_on = true; |
| } |
| |
| ret = pm_runtime_get_sync(svsb->pd_dev); |
| if (ret < 0) { |
| dev_err(svsb->dev, "mtcmos on fail: %d\n", |
| ret); |
| goto init01_finish; |
| } |
| } |
| |
| /* |
| * Find the fastest freq that can be run at vboot and |
| * fix to that freq until svs_init01 is done. |
| */ |
| 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_freqs = svsb->opp_freqs[i]; |
| if (!search_done && svsb->opp_volts[i] <= opp_vboot) { |
| ret = dev_pm_opp_adjust_voltage(svsb->opp_dev, |
| opp_freqs, |
| opp_vboot, |
| opp_vboot, |
| opp_vboot); |
| if (ret) { |
| dev_err(svsb->dev, |
| "set voltage fail: %d\n", ret); |
| goto init01_finish; |
| } |
| |
| search_done = true; |
| } else { |
| dev_pm_opp_disable(svsb->opp_dev, |
| svsb->opp_freqs[i]); |
| } |
| } |
| } |
| |
| /* svs bank init01 begins */ |
| for (idx = 0; idx < svsp->bank_num; 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: %u, expected vboot: %u\n", |
| buck_volt, opp_vboot); |
| ret = -EPERM; |
| goto init01_finish; |
| } |
| |
| spin_lock_irqsave(&mtk_svs_lock, flags); |
| svsp->pbank = svsb; |
| svs_set_bank_phase(svsp, SVSB_PHASE_INIT01); |
| spin_unlock_irqrestore(&mtk_svs_lock, flags); |
| |
| time_left = |
| wait_for_completion_timeout(&svsb->init_completion, |
| msecs_to_jiffies(2000)); |
| if (!time_left) { |
| dev_err(svsb->dev, "init01 completion timeout\n"); |
| ret = -EBUSY; |
| goto init01_finish; |
| } |
| } |
| |
| init01_finish: |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (!(svsb->mode_support & SVSB_MODE_INIT01)) |
| continue; |
| |
| for (i = 0; i < svsb->opp_count; i++) |
| dev_pm_opp_enable(svsb->opp_dev, svsb->opp_freqs[i]); |
| |
| if (regulator_set_mode(svsb->buck, REGULATOR_MODE_NORMAL)) |
| dev_notice(svsb->dev, "fail to set normal mode\n"); |
| |
| if (svsb->pd_req) { |
| if (pm_runtime_put_sync(svsb->pd_dev)) |
| dev_err(svsb->dev, "mtcmos off fail\n"); |
| |
| if (does_svsb_set_pd_on) { |
| pm_runtime_disable(svsb->pd_dev); |
| does_svsb_set_pd_on = false; |
| } |
| |
| if (regulator_disable(svsb->buck)) |
| dev_err(svsb->dev, "disable power fail\n"); |
| } |
| } |
| |
| cpu_latency_qos_remove_request(qos_request); |
| kfree(qos_request); |
| |
| return ret; |
| } |
| |
| 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 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->supplier->links.status != DL_DEV_DRIVER_BOUND) |
| return ERR_PTR(-EPROBE_DEFER); |
| |
| return dev; |
| } |
| |
| static int svs_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_num; 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: |
| WARN_ON(1); |
| return -EINVAL; |
| } |
| |
| svsb->dev = devm_kzalloc(svsp->dev, sizeof(*svsb->dev), |
| GFP_KERNEL); |
| if (!svsb->dev) |
| return -ENOMEM; |
| |
| ret = dev_set_name(svsb->dev, svsb->name); |
| if (ret) |
| return ret; |
| |
| dev_set_drvdata(svsb->dev, svsp); |
| |
| ret = dev_pm_opp_of_add_table(svsb->opp_dev); |
| if (ret) { |
| dev_err(svsb->dev, "add opp table fail: %d\n", ret); |
| return ret; |
| } |
| |
| mutex_init(&svsb->lock); |
| init_completion(&svsb->init_completion); |
| |
| 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); |
| } |
| |
| 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_freqs[i] = freq; |
| svsb->opp_volts[i] = dev_pm_opp_get_voltage(opp); |
| svsb->freqs_pct[i] = percent(svsb->opp_freqs[i], |
| svsb->freq_base); |
| dev_pm_opp_put(opp); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static bool svs_mt8192_efuse_parsing(struct svs_platform *svsp) |
| { |
| struct svs_bank *svsb; |
| struct nvmem_cell *cell; |
| u32 idx, i, ft_pgm, vmin, golden_temp; |
| |
| for (i = 0; i < svsp->efuse_num; i++) |
| if (svsp->efuse[i]) |
| dev_info(svsp->dev, "M_HW_RES%d: 0x%08x\n", |
| i, svsp->efuse[i]); |
| |
| /* Svs efuse parsing */ |
| ft_pgm = svsp->efuse[0] & GENMASK(7, 0); |
| vmin = (svsp->efuse[19] >> 4) & GENMASK(1, 0); |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (svsb->sw_id != SVSB_GPU) |
| return false; |
| |
| if (vmin == 0x1) |
| svsb->vmin = 0x1e; |
| |
| if (ft_pgm == 0) |
| svsb->init01_volt_flag = SVSB_INIT01_VOLT_IGNORE; |
| |
| 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); |
| svsb->vmax += svsb->dvt_fixed; |
| } 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; |
| } |
| } |
| |
| /* Thermal efuse parsing */ |
| cell = nvmem_cell_get(svsp->dev, "t-calibration-data"); |
| if (IS_ERR_OR_NULL(cell)) { |
| dev_err(svsp->dev, "no thermal cell, no mon mode\n"); |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svsb->mode_support &= ~SVSB_MODE_MON; |
| } |
| |
| return true; |
| } |
| |
| svsp->tefuse = nvmem_cell_read(cell, &svsp->tefuse_num); |
| svsp->tefuse_num /= sizeof(u32); |
| nvmem_cell_put(cell); |
| |
| for (i = 0; i < svsp->tefuse_num; i++) |
| if (svsp->tefuse[i] != 0) |
| break; |
| |
| if (i == svsp->tefuse_num) |
| 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_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (svsb->sw_id != SVSB_GPU) |
| return false; |
| |
| 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 thermal_parameter tp; |
| struct svs_bank *svsb; |
| bool mon_mode_support = true; |
| int format[6], x_roomt[6], tb_roomt = 0; |
| struct nvmem_cell *cell; |
| u32 idx, i, ft_pgm, mts, temp0, temp1, temp2; |
| |
| for (i = 0; i < svsp->efuse_num; i++) |
| if (svsp->efuse[i]) |
| dev_info(svsp->dev, "M_HW_RES%d: 0x%08x\n", |
| i, svsp->efuse[i]); |
| |
| /* Svs efuse parsing */ |
| ft_pgm = (svsp->efuse[0] >> 4) & GENMASK(3, 0); |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (ft_pgm <= 1) |
| svsb->init01_volt_flag = 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_offset += 10; |
| else |
| svsb->volt_offset += 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_offset += 15; |
| else |
| svsb->volt_offset += 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_offset += 10; |
| else |
| svsb->volt_offset += 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: |
| break; |
| } |
| } |
| |
| /* Get thermal efuse by nvmem */ |
| cell = nvmem_cell_get(svsp->dev, "t-calibration-data"); |
| if (IS_ERR_OR_NULL(cell)) { |
| dev_err(svsp->dev, "no thermal cell, no mon mode\n"); |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svsb->mode_support &= ~SVSB_MODE_MON; |
| } |
| |
| return true; |
| } |
| |
| svsp->tefuse = nvmem_cell_read(cell, &svsp->tefuse_num); |
| svsp->tefuse_num /= sizeof(u32); |
| nvmem_cell_put(cell); |
| |
| /* Thermal efuse parsing */ |
| tp.adc_ge_t = (svsp->tefuse[1] >> 22) & GENMASK(9, 0); |
| tp.adc_oe_t = (svsp->tefuse[1] >> 12) & GENMASK(9, 0); |
| |
| tp.o_vtsmcu1 = (svsp->tefuse[0] >> 17) & GENMASK(8, 0); |
| tp.o_vtsmcu2 = (svsp->tefuse[0] >> 8) & GENMASK(8, 0); |
| tp.o_vtsmcu3 = svsp->tefuse[1] & GENMASK(8, 0); |
| tp.o_vtsmcu4 = (svsp->tefuse[2] >> 23) & GENMASK(8, 0); |
| tp.o_vtsmcu5 = (svsp->tefuse[2] >> 5) & GENMASK(8, 0); |
| tp.o_vtsabb = (svsp->tefuse[2] >> 14) & GENMASK(8, 0); |
| |
| tp.degc_cali = (svsp->tefuse[0] >> 1) & GENMASK(5, 0); |
| tp.adc_cali_en_t = svsp->tefuse[0] & BIT(0); |
| tp.o_slope_sign = (svsp->tefuse[0] >> 7) & BIT(0); |
| |
| tp.ts_id = (svsp->tefuse[1] >> 9) & BIT(0); |
| tp.o_slope = (svsp->tefuse[0] >> 26) & GENMASK(5, 0); |
| |
| if (tp.adc_cali_en_t == 1) { |
| if (!tp.ts_id) |
| tp.o_slope = 0; |
| |
| if ((tp.adc_ge_t < 265 || tp.adc_ge_t > 758) || |
| (tp.adc_oe_t < 265 || tp.adc_oe_t > 758) || |
| (tp.o_vtsmcu1 < -8 || tp.o_vtsmcu1 > 484) || |
| (tp.o_vtsmcu2 < -8 || tp.o_vtsmcu2 > 484) || |
| (tp.o_vtsmcu3 < -8 || tp.o_vtsmcu3 > 484) || |
| (tp.o_vtsmcu4 < -8 || tp.o_vtsmcu4 > 484) || |
| (tp.o_vtsmcu5 < -8 || tp.o_vtsmcu5 > 484) || |
| (tp.o_vtsabb < -8 || tp.o_vtsabb > 484) || |
| (tp.degc_cali < 1 || tp.degc_cali > 63)) { |
| dev_err(svsp->dev, "bad thermal efuse, no mon mode\n"); |
| mon_mode_support = false; |
| } |
| } else { |
| dev_err(svsp->dev, "no thermal efuse, no mon mode\n"); |
| mon_mode_support = false; |
| } |
| |
| if (!mon_mode_support) { |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svsb->mode_support &= ~SVSB_MODE_MON; |
| } |
| |
| return true; |
| } |
| |
| tp.ge = ((tp.adc_ge_t - 512) * 10000) / 4096; |
| tp.oe = (tp.adc_oe_t - 512); |
| tp.gain = (10000 + tp.ge); |
| |
| format[0] = (tp.o_vtsmcu1 + 3350 - tp.oe); |
| format[1] = (tp.o_vtsmcu2 + 3350 - tp.oe); |
| format[2] = (tp.o_vtsmcu3 + 3350 - tp.oe); |
| format[3] = (tp.o_vtsmcu4 + 3350 - tp.oe); |
| format[4] = (tp.o_vtsmcu5 + 3350 - tp.oe); |
| format[5] = (tp.o_vtsabb + 3350 - tp.oe); |
| |
| for (i = 0; i < 6; i++) |
| x_roomt[i] = (((format[i] * 10000) / 4096) * 10000) / tp.gain; |
| |
| temp0 = (10000 * 100000 / tp.gain) * 15 / 18; |
| |
| if (!tp.o_slope_sign) |
| mts = (temp0 * 10) / (1534 + tp.o_slope * 10); |
| else |
| mts = (temp0 * 10) / (1534 - tp.o_slope * 10); |
| |
| for (idx = 0; idx < svsp->bank_num; 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: |
| break; |
| } |
| |
| temp0 = (tp.degc_cali * 10 / 2); |
| temp1 = ((10000 * 100000 / 4096 / tp.gain) * |
| tp.oe + tb_roomt * 10) * 15 / 18; |
| |
| if (!tp.o_slope_sign) |
| temp2 = temp1 * 100 / (1534 + tp.o_slope * 10); |
| else |
| temp2 = temp1 * 100 / (1534 - tp.o_slope * 10); |
| |
| svsb->bts = (temp0 + temp2 - 250) * 4 / 10; |
| } |
| |
| return true; |
| } |
| |
| static bool svs_is_supported(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_OR_NULL(cell)) { |
| dev_err(svsp->dev, |
| "no \"svs-calibration-data\" from dts? disable svs\n"); |
| return false; |
| } |
| |
| svsp->efuse = nvmem_cell_read(cell, &svsp->efuse_num); |
| svsp->efuse_num /= sizeof(u32); |
| nvmem_cell_put(cell); |
| |
| if (!svsp->efuse[svsp->efuse_check]) { |
| dev_err(svsp->dev, "svs_efuse[%u] = 0x%x?\n", |
| svsp->efuse_check, svsp->efuse[svsp->efuse_check]); |
| return false; |
| } |
| |
| return svsp->efuse_parsing(svsp); |
| } |
| |
| 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_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| /* Wait if svs_isr() is still in process. */ |
| spin_lock_irqsave(&mtk_svs_lock, flags); |
| svsp->pbank = svsb; |
| svs_switch_bank(svsp); |
| svs_writel(svsp, SVSB_EN_OFF, SVSEN); |
| svs_writel(svsp, SVSB_INTSTS_CLEAN, INTSTS); |
| spin_unlock_irqrestore(&mtk_svs_lock, flags); |
| |
| svsb->suspended = true; |
| if (svsb->phase != SVSB_PHASE_INIT01) { |
| svsb->phase = SVSB_PHASE_ERROR; |
| svs_adjust_pm_opp_volts(svsb, true); |
| } |
| } |
| |
| if (svsp->rst) { |
| 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); |
| struct svs_bank *svsb; |
| int ret; |
| u32 idx; |
| |
| ret = clk_prepare_enable(svsp->main_clk); |
| if (ret) { |
| dev_err(svsp->dev, "cannot enable main_clk, disable svs\n"); |
| return ret; |
| } |
| |
| if (svsp->rst) { |
| ret = reset_control_deassert(svsp->rst); |
| if (ret) { |
| dev_err(svsp->dev, "cannot deassert reset %d\n", ret); |
| return ret; |
| } |
| } |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svsb->suspended = false; |
| } |
| |
| ret = svs_init02(svsp); |
| if (ret) |
| return ret; |
| |
| svs_mon_mode(svsp); |
| |
| return 0; |
| } |
| |
| /* |
| * svs_dump_debug_show - dump svs/thermal efuse and svs banks' registers |
| */ |
| 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; |
| |
| for (i = 0; i < svsp->efuse_num; 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_num; i++) |
| if (svsp->tefuse) |
| seq_printf(m, "THERMAL_EFUSE%d = 0x%08x\n", |
| i, svsp->tefuse[i]); |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| for (i = SVSB_PHASE_INIT01; i <= SVSB_PHASE_MON; i++) { |
| seq_printf(m, "Bank_number = %u\n", svsb->hw_id); |
| |
| if (i < SVSB_PHASE_MON) |
| seq_printf(m, "mode = init%d\n", i + 1); |
| else |
| seq_puts(m, "mode = mon\n"); |
| |
| for (j = DESCHAR; j < SVS_REG_NUM; 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); |
| |
| /* |
| * svs_enable_debug_show - show svs bank current enable phase |
| */ |
| static int svs_enable_debug_show(struct seq_file *m, void *v) |
| { |
| struct svs_bank *svsb = (struct svs_bank *)m->private; |
| |
| if (svsb->phase == SVSB_PHASE_INIT01) |
| seq_puts(m, "init1\n"); |
| else if (svsb->phase == SVSB_PHASE_INIT02) |
| seq_puts(m, "init2\n"); |
| else if (svsb->phase == SVSB_PHASE_MON) |
| seq_puts(m, "mon mode\n"); |
| else if (svsb->phase == SVSB_PHASE_ERROR) |
| seq_puts(m, "disabled\n"); |
| else |
| seq_puts(m, "unknown\n"); |
| |
| return 0; |
| } |
| |
| /* |
| * svs_enable_debug_write - we only support svs bank disable control |
| */ |
| 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(&mtk_svs_lock, flags); |
| svsp->pbank = svsb; |
| svsb->mode_support = SVSB_MODE_ALL_DISABLE; |
| svs_switch_bank(svsp); |
| svs_writel(svsp, SVSB_EN_OFF, SVSEN); |
| svs_writel(svsp, SVSB_INTSTS_CLEAN, INTSTS); |
| spin_unlock_irqrestore(&mtk_svs_lock, flags); |
| |
| svsb->phase = SVSB_PHASE_ERROR; |
| svs_adjust_pm_opp_volts(svsb, true); |
| } |
| |
| kfree(buf); |
| |
| return count; |
| } |
| |
| debug_fops_rw(enable); |
| |
| /* |
| * svs_status_debug_show - show svs bank's tzone_temp/voltages/freqs_pct |
| * and its corresponding opp-table's opp_freqs/opp_volts |
| */ |
| 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, ret; |
| u32 i; |
| |
| ret = svs_get_bank_zone_temperature(svsb->tzone_name, &tzone_temp); |
| if (ret) |
| seq_printf(m, "%s: no \"%s\" zone? turn_pt = %u\n", |
| svsb->name, svsb->tzone_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_freqs[i], true); |
| if (IS_ERR(opp)) { |
| seq_printf(m, "%s: cannot find freq = %u (%ld)\n", |
| svsb->name, svsb->opp_freqs[i], |
| PTR_ERR(opp)); |
| return PTR_ERR(opp); |
| } |
| |
| seq_printf(m, "opp_freqs[%02u]: %u, opp_volts[%02u]: %lu, ", |
| i, svsb->opp_freqs[i], i, |
| dev_pm_opp_get_voltage(opp)); |
| seq_printf(m, "svsb_volts[%02u]: 0x%x, freqs_pct[%02u]: %u\n", |
| i, svsb->volts[i], i, svsb->freqs_pct[i]); |
| dev_pm_opp_put(opp); |
| } |
| |
| return 0; |
| } |
| |
| debug_fops_ro(status); |
| |
| /* |
| * svs_volt_offset_debug_show - show svs bank's voltage offset |
| */ |
| static int svs_volt_offset_debug_show(struct seq_file *m, void *v) |
| { |
| struct svs_bank *svsb = (struct svs_bank *)m->private; |
| |
| seq_printf(m, "%d\n", svsb->volt_offset); |
| |
| return 0; |
| } |
| |
| /* |
| * svs_volt_offset_debug_write - write svs bank's voltage offset |
| */ |
| static ssize_t svs_volt_offset_debug_write(struct file *filp, |
| const char __user *buffer, |
| size_t count, loff_t *pos) |
| { |
| struct svs_bank *svsb = file_inode(filp)->i_private; |
| char *buf = NULL; |
| s32 volt_offset; |
| |
| if (count >= PAGE_SIZE) |
| return -EINVAL; |
| |
| buf = (char *)memdup_user_nul(buffer, count); |
| if (IS_ERR(buf)) |
| return PTR_ERR(buf); |
| |
| if (!kstrtoint(buf, 10, &volt_offset)) { |
| svsb->volt_offset = volt_offset; |
| svs_adjust_pm_opp_volts(svsb, true); |
| } |
| |
| kfree(buf); |
| |
| return count; |
| } |
| |
| debug_fops_rw(volt_offset); |
| |
| static int svs_create_svs_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(dump), |
| }; |
| |
| struct svs_dentry svsb_entries[] = { |
| svs_dentry(enable), |
| svs_dentry(status), |
| svs_dentry(volt_offset), |
| }; |
| |
| 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 0; |
| } |
| |
| 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_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| 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 struct svs_bank svs_mt8192_banks[2] = { |
| { |
| .sw_id = SVSB_GPU, |
| .hw_id = 0, |
| .tzone_name = "gpu1", |
| .buck_name = "mali", |
| .mode_support = SVSB_MODE_INIT02, |
| .opp_count = 16, |
| .freq_base = 688000000, |
| .turn_freq_base = 688000000, |
| .vboot = 0x38, |
| .volt_step = 6250, |
| .volt_base = 400000, |
| .volt_offset = 0, |
| .vmax = 0x60, |
| .vmin = 0x1a, |
| .dthi = 0x1, |
| .dtlo = 0xfe, |
| .det_window = 0xa28, |
| .det_max = 0xffff, |
| .age_config = 0x555555, |
| .agem = 0, |
| .dc_config = 0x1, |
| .dvt_fixed = 0x1, |
| .vco = 0x18, |
| .chk_shift = 0x87, |
| .temp_upper_bound = 0x64, |
| .temp_lower_bound = 0xb2, |
| .tzone_high_temp = 85000, |
| .tzone_high_temp_offset = 0, |
| .tzone_low_temp = 25000, |
| .tzone_low_temp_offset = 7, |
| .core_sel = 0x0fff0100, |
| .int_st = BIT(0), |
| .ctl0 = 0x00540003, |
| .type = SVSB_LOW, |
| }, |
| { |
| .sw_id = SVSB_GPU, |
| .hw_id = 1, |
| .tzone_name = "gpu1", |
| .buck_name = "mali", |
| .pd_req = false, |
| .mode_support = SVSB_MODE_INIT02 | SVSB_MODE_MON, |
| .opp_count = 16, |
| .freq_base = 902000000, |
| .turn_freq_base = 688000000, |
| .vboot = 0x38, |
| .volt_step = 6250, |
| .volt_base = 400000, |
| .volt_offset = 0, |
| .vmax = 0x60, |
| .vmin = 0x1a, |
| .dthi = 0x1, |
| .dtlo = 0xfe, |
| .det_window = 0xa28, |
| .det_max = 0xffff, |
| .age_config = 0x555555, |
| .agem = 0, |
| .dc_config = 0x1, |
| .dvt_fixed = 0x6, |
| .vco = 0x18, |
| .chk_shift = 0x87, |
| .temp_upper_bound = 0x64, |
| .temp_lower_bound = 0xb2, |
| .tzone_high_temp = 85000, |
| .tzone_high_temp_offset = 0, |
| .tzone_low_temp = 25000, |
| .tzone_low_temp_offset = 7, |
| .core_sel = 0x0fff0101, |
| .int_st = BIT(1), |
| .ctl0 = 0x00540003, |
| .type = SVSB_HIGH, |
| }, |
| }; |
| |
| static struct svs_bank svs_mt8183_banks[4] = { |
| { |
| .sw_id = SVSB_CPU_LITTLE, |
| .hw_id = 0, |
| .cpu_id = 0, |
| .tzone_name = "tzts4", |
| .buck_name = "proc", |
| .pd_req = false, |
| .init01_volt_flag = SVSB_INIT01_VOLT_INC_ONLY, |
| .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02, |
| .opp_count = 16, |
| .freq_base = 1989000000, |
| .vboot = 0x30, |
| .volt_step = 6250, |
| .volt_base = 500000, |
| .volt_offset = 0, |
| .vmax = 0x64, |
| .vmin = 0x18, |
| .dthi = 0x1, |
| .dtlo = 0xfe, |
| .det_window = 0xa28, |
| .det_max = 0xffff, |
| .age_config = 0x555555, |
| .agem = 0, |
| .dc_config = 0x555555, |
| .dvt_fixed = 0x7, |
| .vco = 0x10, |
| .chk_shift = 0x77, |
| .temp_upper_bound = 0x64, |
| .temp_lower_bound = 0xb2, |
| .tzone_high_temp = SVSB_TZONE_HIGH_TEMP_MAX, |
| .tzone_low_temp = 25000, |
| .tzone_low_temp_offset = 0, |
| .core_sel = 0x8fff0000, |
| .int_st = BIT(0), |
| .ctl0 = 0x00010001, |
| }, |
| { |
| .sw_id = SVSB_CPU_BIG, |
| .hw_id = 1, |
| .cpu_id = 4, |
| .tzone_name = "tzts5", |
| .buck_name = "proc", |
| .pd_req = false, |
| .init01_volt_flag = SVSB_INIT01_VOLT_INC_ONLY, |
| .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02, |
| .opp_count = 16, |
| .freq_base = 1989000000, |
| .vboot = 0x30, |
| .volt_step = 6250, |
| .volt_base = 500000, |
| .volt_offset = 0, |
| .vmax = 0x58, |
| .vmin = 0x10, |
| .dthi = 0x1, |
| .dtlo = 0xfe, |
| .det_window = 0xa28, |
| .det_max = 0xffff, |
| .age_config = 0x555555, |
| .agem = 0, |
| .dc_config = 0x555555, |
| .dvt_fixed = 0x7, |
| .vco = 0x10, |
| .chk_shift = 0x77, |
| .temp_upper_bound = 0x64, |
| .temp_lower_bound = 0xb2, |
| .tzone_high_temp = SVSB_TZONE_HIGH_TEMP_MAX, |
| .tzone_low_temp = 25000, |
| .tzone_low_temp_offset = 0, |
| .core_sel = 0x8fff0001, |
| .int_st = BIT(1), |
| .ctl0 = 0x00000001, |
| }, |
| { |
| .sw_id = SVSB_CCI, |
| .hw_id = 2, |
| .tzone_name = "tzts4", |
| .buck_name = "proc", |
| .pd_req = false, |
| .init01_volt_flag = SVSB_INIT01_VOLT_INC_ONLY, |
| .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02, |
| .opp_count = 16, |
| .freq_base = 1196000000, |
| .vboot = 0x30, |
| .volt_step = 6250, |
| .volt_base = 500000, |
| .volt_offset = 0, |
| .vmax = 0x64, |
| .vmin = 0x18, |
| .dthi = 0x1, |
| .dtlo = 0xfe, |
| .det_window = 0xa28, |
| .det_max = 0xffff, |
| .age_config = 0x555555, |
| .agem = 0, |
| .dc_config = 0x555555, |
| .dvt_fixed = 0x7, |
| .vco = 0x10, |
| .chk_shift = 0x77, |
| .temp_upper_bound = 0x64, |
| .temp_lower_bound = 0xb2, |
| .tzone_high_temp = SVSB_TZONE_HIGH_TEMP_MAX, |
| .tzone_low_temp = 25000, |
| .tzone_low_temp_offset = 0, |
| .core_sel = 0x8fff0002, |
| .int_st = BIT(2), |
| .ctl0 = 0x00100003, |
| }, |
| { |
| .sw_id = SVSB_GPU, |
| .hw_id = 3, |
| .tzone_name = "tzts2", |
| .buck_name = "mali", |
| .pd_req = true, |
| .init01_volt_flag = SVSB_INIT01_VOLT_INC_ONLY, |
| .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02 | |
| SVSB_MODE_MON, |
| .opp_count = 16, |
| .freq_base = 900000000, |
| .vboot = 0x30, |
| .volt_step = 6250, |
| .volt_base = 500000, |
| .volt_offset = 0, |
| .vmax = 0x40, |
| .vmin = 0x14, |
| .dthi = 0x1, |
| .dtlo = 0xfe, |
| .det_window = 0xa28, |
| .det_max = 0xffff, |
| .age_config = 0x555555, |
| .agem = 0, |
| .dc_config = 0x555555, |
| .dvt_fixed = 0x3, |
| .vco = 0x10, |
| .chk_shift = 0x77, |
| .temp_upper_bound = 0x64, |
| .temp_lower_bound = 0xb2, |
| .tzone_high_temp = SVSB_TZONE_HIGH_TEMP_MAX, |
| .tzone_low_temp = 25000, |
| .tzone_low_temp_offset = 3, |
| .core_sel = 0x8fff0003, |
| .int_st = BIT(3), |
| .ctl0 = 0x00050001, |
| }, |
| }; |
| |
| static int svs_get_svs_mt8192_platform_data(struct svs_platform *svsp) |
| { |
| struct device *dev; |
| struct svs_bank *svsb; |
| u32 idx; |
| |
| svsp->name = "mt8192-svs", |
| svsp->banks = svs_mt8192_banks, |
| svsp->efuse_parsing = svs_mt8192_efuse_parsing, |
| svsp->set_freqs_pct = svs_set_freqs_pct_v3, |
| svsp->get_vops = svs_get_vops_v3, |
| svsp->regs = svs_regs_v2, |
| svsp->irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| svsp->bank_num = 2, |
| svsp->efuse_check = 9, |
| |
| svsp->rst = devm_reset_control_get_optional(svsp->dev, "svs_rst"); |
| if (IS_ERR(svsp->rst)) { |
| dev_err_probe(svsp->dev, PTR_ERR(svsp->rst), |
| "cannot get svs reset control\n"); |
| return PTR_ERR(svsp->rst); |
| } |
| |
| dev = svs_add_device_link(svsp, "lvts"); |
| if (IS_ERR(dev)) |
| return PTR_ERR(dev); |
| |
| for (idx = 0; idx < svsp->bank_num; 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 PTR_ERR(svsb->opp_dev); |
| } |
| |
| return 0; |
| } |
| |
| static int svs_get_svs_mt8183_platform_data(struct svs_platform *svsp) |
| { |
| struct device *dev; |
| struct svs_bank *svsb; |
| u32 idx; |
| |
| svsp->name = "mt8183-svs"; |
| svsp->banks = svs_mt8183_banks; |
| svsp->efuse_parsing = svs_mt8183_efuse_parsing; |
| svsp->set_freqs_pct = svs_set_freqs_pct_v2; |
| svsp->get_vops = svs_get_vops_v2; |
| svsp->regs = svs_regs_v2; |
| svsp->irqflags = IRQF_TRIGGER_LOW | IRQF_ONESHOT; |
| svsp->rst = NULL; |
| svsp->bank_num = 4; |
| svsp->efuse_check = 2; |
| |
| dev = svs_add_device_link(svsp, "thermal"); |
| if (IS_ERR(dev)) |
| return PTR_ERR(dev); |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| switch (svsb->sw_id) { |
| case SVSB_CPU_LITTLE: |
| case SVSB_CPU_BIG: |
| svsb->opp_dev = get_cpu_device(svsb->cpu_id); |
| break; |
| case SVSB_CCI: |
| svsb->opp_dev = svs_add_device_link(svsp, "cci"); |
| break; |
| case SVSB_GPU: |
| svsb->opp_dev = svs_add_device_link(svsp, "mali"); |
| svsb->pd_dev = svs_add_device_link(svsp, |
| "mali_gpu_core2"); |
| if (IS_ERR(svsb->pd_dev)) |
| return PTR_ERR(svsb->pd_dev); |
| break; |
| default: |
| WARN_ON(1); |
| return -EINVAL; |
| } |
| |
| if (IS_ERR(svsb->opp_dev)) |
| return PTR_ERR(svsb->opp_dev); |
| } |
| |
| return 0; |
| } |
| |
| static const struct of_device_id mtk_svs_of_match[] = { |
| { |
| .compatible = "mediatek,mt8183-svs", |
| .data = &svs_get_svs_mt8183_platform_data, |
| }, { |
| .compatible = "mediatek,mt8192-svs", |
| .data = &svs_get_svs_mt8192_platform_data, |
| }, { |
| /* Sentinel */ |
| }, |
| }; |
| |
| static int svs_probe(struct platform_device *pdev) |
| { |
| int (*svs_get_svs_platform_data)(struct svs_platform *svsp); |
| struct svs_platform *svsp; |
| unsigned int svsp_irq; |
| int ret; |
| |
| svsp = devm_kzalloc(&pdev->dev, sizeof(*svsp), GFP_KERNEL); |
| if (!svsp) |
| return -ENOMEM; |
| |
| svs_get_svs_platform_data = of_device_get_match_data(&pdev->dev); |
| if (!svs_get_svs_platform_data) { |
| dev_err(svsp->dev, "no svs platform data? why?\n"); |
| return -EPERM; |
| } |
| |
| svsp->dev = &pdev->dev; |
| ret = svs_get_svs_platform_data(svsp); |
| if (ret) { |
| dev_err_probe(svsp->dev, ret, "fail to get svsp data\n"); |
| return ret; |
| } |
| |
| if (!svs_is_supported(svsp)) { |
| dev_notice(svsp->dev, "svs is not supported\n"); |
| return -EPERM; |
| } |
| |
| ret = svs_resource_setup(svsp); |
| if (ret) { |
| dev_err(svsp->dev, "svs resource setup fail: %d\n", ret); |
| return ret; |
| } |
| |
| svsp_irq = irq_of_parse_and_map(svsp->dev->of_node, 0); |
| ret = devm_request_threaded_irq(svsp->dev, svsp_irq, NULL, svs_isr, |
| svsp->irqflags, svsp->name, svsp); |
| if (ret) { |
| dev_err(svsp->dev, "register irq(%d) failed: %d\n", |
| svsp_irq, ret); |
| return ret; |
| } |
| |
| svsp->main_clk = devm_clk_get(svsp->dev, "main"); |
| if (IS_ERR(svsp->main_clk)) { |
| dev_err(svsp->dev, "failed to get clock: %ld\n", |
| PTR_ERR(svsp->main_clk)); |
| return PTR_ERR(svsp->main_clk); |
| } |
| |
| ret = clk_prepare_enable(svsp->main_clk); |
| if (ret) { |
| dev_err(svsp->dev, "cannot enable main clk: %d\n", ret); |
| return ret; |
| } |
| |
| svsp->base = of_iomap(svsp->dev->of_node, 0); |
| if (IS_ERR_OR_NULL(svsp->base)) { |
| dev_err(svsp->dev, "cannot find svs register base\n"); |
| ret = -EINVAL; |
| goto svs_probe_clk_disable; |
| } |
| |
| ret = svs_start(svsp); |
| if (ret) { |
| dev_err(svsp->dev, "svs start fail: %d\n", ret); |
| goto svs_probe_iounmap; |
| } |
| |
| ret = svs_create_svs_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); |
| |
| return ret; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(svs_pm_ops, svs_suspend, svs_resume); |
| |
| static struct platform_driver svs_driver = { |
| .probe = svs_probe, |
| .driver = { |
| .name = "mtk-svs", |
| .pm = &svs_pm_ops, |
| .of_match_table = of_match_ptr(mtk_svs_of_match), |
| }, |
| }; |
| |
| module_platform_driver(svs_driver); |
| |
| MODULE_AUTHOR("Roger Lu <roger.lu@mediatek.com>"); |
| MODULE_DESCRIPTION("MediaTek SVS driver"); |
| MODULE_LICENSE("GPL v2"); |