| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2018 MediaTek Inc. |
| */ |
| |
| #define pr_fmt(fmt) "[mtk_svs] " fmt |
| |
| #include <linux/clk.h> |
| #include <linux/completion.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.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/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/power/mtk_svs.h> |
| #include <linux/proc_fs.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/seq_file.h> |
| #include <linux/spinlock.h> |
| #include <linux/thermal.h> |
| #include <linux/uaccess.h> |
| |
| #define SVS_INIT01_VOLT_IGNORE 1 |
| #define SVS_INIT01_VOLT_INC_ONLY 2 |
| |
| #define SVS_PHASE_INIT01 0 |
| #define SVS_PHASE_INIT02 1 |
| #define SVS_PHASE_MON 2 |
| #define SVS_PHASE_ERROR 3 |
| |
| #define SVS_CPU_LITTLE 1 |
| #define SVS_CPU_BIG 2 |
| #define SVS_CCI 3 |
| #define SVS_GPU 4 |
| |
| #define proc_fops_rw(name) \ |
| static int name ## _proc_open(struct inode *inode, \ |
| struct file *file) \ |
| { \ |
| return single_open(file, name ## _proc_show, \ |
| PDE_DATA(inode)); \ |
| } \ |
| static const struct file_operations name ## _proc_fops = { \ |
| .owner = THIS_MODULE, \ |
| .open = name ## _proc_open, \ |
| .read = seq_read, \ |
| .llseek = seq_lseek, \ |
| .release = single_release, \ |
| .write = name ## _proc_write, \ |
| } |
| |
| #define proc_fops_ro(name) \ |
| static int name ## _proc_open(struct inode *inode, \ |
| struct file *file) \ |
| { \ |
| return single_open(file, name ## _proc_show, \ |
| PDE_DATA(inode)); \ |
| } \ |
| static const struct file_operations name ## _proc_fops = { \ |
| .owner = THIS_MODULE, \ |
| .open = name ## _proc_open, \ |
| .read = seq_read, \ |
| .llseek = seq_lseek, \ |
| .release = single_release, \ |
| } |
| |
| #define proc_entry(name) {__stringify(name), &name ## _proc_fops} |
| |
| static DEFINE_SPINLOCK(mtk_svs_lock); |
| struct mtk_svs; |
| |
| enum reg_index { |
| TEMPMONCTL0 = 0, |
| TEMPMONCTL1, |
| TEMPMONCTL2, |
| TEMPMONINT, |
| TEMPMONINTSTS, |
| TEMPMONIDET0, |
| TEMPMONIDET1, |
| TEMPMONIDET2, |
| TEMPH2NTHRE, |
| TEMPHTHRE, |
| TEMPCTHRE, |
| TEMPOFFSETH, |
| TEMPOFFSETL, |
| TEMPMSRCTL0, |
| TEMPMSRCTL1, |
| TEMPAHBPOLL, |
| TEMPAHBTO, |
| TEMPADCPNP0, |
| TEMPADCPNP1, |
| TEMPADCPNP2, |
| TEMPADCMUX, |
| TEMPADCEXT, |
| TEMPADCEXT1, |
| TEMPADCEN, |
| TEMPPNPMUXADDR, |
| TEMPADCMUXADDR, |
| TEMPADCEXTADDR, |
| TEMPADCEXT1ADDR, |
| TEMPADCENADDR, |
| TEMPADCVALIDADDR, |
| TEMPADCVOLTADDR, |
| TEMPRDCTRL, |
| TEMPADCVALIDMASK, |
| TEMPADCVOLTAGESHIFT, |
| TEMPADCWRITECTRL, |
| TEMPMSR0, |
| TEMPMSR1, |
| TEMPMSR2, |
| TEMPADCHADDR, |
| TEMPIMMD0, |
| TEMPIMMD1, |
| TEMPIMMD2, |
| TEMPMONIDET3, |
| TEMPADCPNP3, |
| TEMPMSR3, |
| TEMPIMMD3, |
| TEMPPROTCTL, |
| TEMPPROTTA, |
| TEMPPROTTB, |
| TEMPPROTTC, |
| TEMPSPARE0, |
| TEMPSPARE1, |
| TEMPSPARE2, |
| TEMPSPARE3, |
| TEMPMSR0_1, |
| TEMPMSR1_1, |
| TEMPMSR2_1, |
| TEMPMSR3_1, |
| DESCHAR, |
| 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, |
| reg_num, |
| }; |
| |
| static const u32 svs_regs_v2[] = { |
| [TEMPMONCTL0] = 0x000, |
| [TEMPMONCTL1] = 0x004, |
| [TEMPMONCTL2] = 0x008, |
| [TEMPMONINT] = 0x00c, |
| [TEMPMONINTSTS] = 0x010, |
| [TEMPMONIDET0] = 0x014, |
| [TEMPMONIDET1] = 0x018, |
| [TEMPMONIDET2] = 0x01c, |
| [TEMPH2NTHRE] = 0x024, |
| [TEMPHTHRE] = 0x028, |
| [TEMPCTHRE] = 0x02c, |
| [TEMPOFFSETH] = 0x030, |
| [TEMPOFFSETL] = 0x034, |
| [TEMPMSRCTL0] = 0x038, |
| [TEMPMSRCTL1] = 0x03c, |
| [TEMPAHBPOLL] = 0x040, |
| [TEMPAHBTO] = 0x044, |
| [TEMPADCPNP0] = 0x048, |
| [TEMPADCPNP1] = 0x04c, |
| [TEMPADCPNP2] = 0x050, |
| [TEMPADCMUX] = 0x054, |
| [TEMPADCEXT] = 0x058, |
| [TEMPADCEXT1] = 0x05c, |
| [TEMPADCEN] = 0x060, |
| [TEMPPNPMUXADDR] = 0x064, |
| [TEMPADCMUXADDR] = 0x068, |
| [TEMPADCEXTADDR] = 0x06c, |
| [TEMPADCEXT1ADDR] = 0x070, |
| [TEMPADCENADDR] = 0x074, |
| [TEMPADCVALIDADDR] = 0x078, |
| [TEMPADCVOLTADDR] = 0x07c, |
| [TEMPRDCTRL] = 0x080, |
| [TEMPADCVALIDMASK] = 0x084, |
| [TEMPADCVOLTAGESHIFT] = 0x088, |
| [TEMPADCWRITECTRL] = 0x08c, |
| [TEMPMSR0] = 0x090, |
| [TEMPMSR1] = 0x094, |
| [TEMPMSR2] = 0x098, |
| [TEMPADCHADDR] = 0x09c, |
| [TEMPIMMD0] = 0x0a0, |
| [TEMPIMMD1] = 0x0a4, |
| [TEMPIMMD2] = 0x0a8, |
| [TEMPMONIDET3] = 0x0b0, |
| [TEMPADCPNP3] = 0x0b4, |
| [TEMPMSR3] = 0x0b8, |
| [TEMPIMMD3] = 0x0bc, |
| [TEMPPROTCTL] = 0x0c0, |
| [TEMPPROTTA] = 0x0c4, |
| [TEMPPROTTB] = 0x0c8, |
| [TEMPPROTTC] = 0x0cc, |
| [TEMPSPARE0] = 0x0f0, |
| [TEMPSPARE1] = 0x0f4, |
| [TEMPSPARE2] = 0x0f8, |
| [TEMPSPARE3] = 0x0fc, |
| [TEMPMSR0_1] = 0x190, |
| [TEMPMSR1_1] = 0x194, |
| [TEMPMSR2_1] = 0x198, |
| [TEMPMSR3_1] = 0x1b8, |
| [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 { |
| 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_ops { |
| void (*set_freqs_pct)(struct mtk_svs *svs); |
| void (*get_vops)(struct mtk_svs *svs); |
| }; |
| |
| struct svs_bank { |
| struct svs_bank_ops *ops; |
| struct completion init_completion; |
| struct device *dev; |
| struct regulator *buck; |
| struct mutex lock; /* lock to protect update voltage process */ |
| bool suspended; |
| bool mtcmos_request; |
| bool init01_support; |
| bool init02_support; |
| bool mon_mode_support; |
| s32 volt_offset; |
| u32 *opp_freqs; |
| u32 *freqs_pct; |
| u32 *opp_volts; |
| u32 *init02_volts; |
| u32 *volts; |
| u32 reg_data[3][reg_num]; |
| u32 freq_base; |
| u32 vboot; |
| u32 volt_step; |
| u32 volt_base; |
| u32 init01_volt_flag; |
| u32 phase; |
| 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 chkshift; |
| u32 svs_temp; |
| u32 upper_temp_bound; |
| u32 lower_temp_bound; |
| u32 low_temp_threashold; |
| u32 low_temp_offset; |
| u32 coresel; |
| u32 opp_count; |
| u32 intst; |
| u32 systemclk_en; |
| u32 sw_id; |
| u32 hw_id; |
| u32 ctl0; |
| u8 *of_compatible; |
| u8 *name; |
| u8 *zone_name; |
| u8 *buck_name; |
| }; |
| |
| struct svs_platform { |
| struct svs_bank *banks; |
| int (*efuse_parsing)(struct mtk_svs *svs); |
| bool fake_efuse; |
| const u32 *regs; |
| u32 bank_num; |
| u32 efuse_num; |
| u32 efuse_check; |
| u32 thermal_efuse_num; |
| u8 *name; |
| }; |
| |
| struct mtk_svs { |
| const struct svs_platform *platform; |
| struct svs_bank *bank; |
| struct device *dev; |
| void __iomem *base; |
| struct clk *main_clk; |
| u32 *efuse; |
| u32 *thermal_efuse; |
| }; |
| |
| unsigned long claim_mtk_svs_lock(void) |
| __acquires(&mtk_svs_lock) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&mtk_svs_lock, flags); |
| |
| return flags; |
| } |
| EXPORT_SYMBOL_GPL(claim_mtk_svs_lock); |
| |
| void release_mtk_svs_lock(unsigned long flags) |
| __releases(&mtk_svs_lock) |
| { |
| spin_unlock_irqrestore(&mtk_svs_lock, flags); |
| } |
| EXPORT_SYMBOL_GPL(release_mtk_svs_lock); |
| |
| static u32 percent(u32 numerator, u32 denominator) |
| { |
| u32 percent; |
| |
| /* If not divide 1000, "numerator * 100" would be data overflow. */ |
| numerator /= 1000; |
| denominator /= 1000; |
| percent = ((numerator * 100) + denominator - 1) / denominator; |
| |
| return percent; |
| } |
| |
| static u32 svs_readl(struct mtk_svs *svs, enum reg_index i) |
| { |
| return readl(svs->base + svs->platform->regs[i]); |
| } |
| |
| static void svs_writel(struct mtk_svs *svs, u32 val, enum reg_index i) |
| { |
| writel(val, svs->base + svs->platform->regs[i]); |
| } |
| |
| static void svs_switch_bank(struct mtk_svs *svs) |
| { |
| struct svs_bank *svsb = svs->bank; |
| |
| svs_writel(svs, svsb->coresel, CORESEL); |
| } |
| |
| static u32 svs_volt_to_opp_volt(u32 svsb_volt, |
| u32 svsb_volt_step, u32 svsb_volt_base) |
| { |
| u32 u_volt; |
| |
| u_volt = (svsb_volt * svsb_volt_step) + svsb_volt_base; |
| |
| return u_volt; |
| } |
| |
| static int svs_get_zone_temperature(struct svs_bank *svsb, int *zone_temp) |
| { |
| struct thermal_zone_device *tzd; |
| int ret; |
| |
| tzd = thermal_zone_get_zone_by_name(svsb->zone_name); |
| ret = thermal_zone_get_temp(tzd, zone_temp); |
| |
| return ret; |
| } |
| |
| static int svs_set_volts(struct svs_bank *svsb, bool force_update) |
| { |
| u32 i, svsb_volt, opp_volt, low_temp_offset = 0; |
| int zone_temp, ret; |
| |
| mutex_lock(&svsb->lock); |
| |
| /* If bank is suspended, it means init02 voltage is applied. |
| * Don't need to update opp voltage anymore. |
| */ |
| if (svsb->suspended && !force_update) { |
| pr_notice("%s: bank is suspended\n", svsb->name); |
| mutex_unlock(&svsb->lock); |
| return -EPERM; |
| } |
| |
| /* get thermal effect */ |
| if (svsb->phase == SVS_PHASE_MON) { |
| if (svsb->svs_temp > svsb->upper_temp_bound && |
| svsb->svs_temp < svsb->lower_temp_bound) { |
| pr_err("%s: svs_temp is abnormal (0x%x)?\n", |
| svsb->name, svsb->svs_temp); |
| mutex_unlock(&svsb->lock); |
| return -EINVAL; |
| } |
| |
| ret = svs_get_zone_temperature(svsb, &zone_temp); |
| if (ret) { |
| pr_err("%s: cannot get zone \"%s\" temperature\n", |
| svsb->name, svsb->zone_name); |
| pr_err("%s: add low_temp_offset = %u\n", |
| svsb->name, svsb->low_temp_offset); |
| zone_temp = svsb->low_temp_threashold; |
| } |
| |
| if (zone_temp <= svsb->low_temp_threashold) |
| low_temp_offset = svsb->low_temp_offset; |
| } |
| |
| /* vmin <= svsb_volt (opp_volt) <= signed-off voltage */ |
| for (i = 0; i < svsb->opp_count; i++) { |
| if (svsb->phase == SVS_PHASE_MON) { |
| svsb_volt = max((svsb->volts[i] + svsb->volt_offset + |
| low_temp_offset), svsb->vmin); |
| opp_volt = svs_volt_to_opp_volt(svsb_volt, |
| svsb->volt_step, |
| svsb->volt_base); |
| } else if (svsb->phase == SVS_PHASE_INIT02) { |
| svsb_volt = max((svsb->init02_volts[i] + |
| svsb->volt_offset), svsb->vmin); |
| opp_volt = svs_volt_to_opp_volt(svsb_volt, |
| svsb->volt_step, |
| svsb->volt_base); |
| } else if (svsb->phase == SVS_PHASE_ERROR) { |
| opp_volt = svsb->opp_volts[i]; |
| } else { |
| pr_err("%s: unknown phase: %u?\n", |
| svsb->name, svsb->phase); |
| mutex_unlock(&svsb->lock); |
| return -EINVAL; |
| } |
| |
| opp_volt = min(opp_volt, svsb->opp_volts[i]); |
| ret = dev_pm_opp_adjust_voltage(svsb->dev, svsb->opp_freqs[i], |
| opp_volt); |
| if (ret) { |
| pr_err("%s: set voltage failed: %d\n", svsb->name, ret); |
| mutex_unlock(&svsb->lock); |
| return ret; |
| } |
| } |
| |
| mutex_unlock(&svsb->lock); |
| |
| return 0; |
| } |
| |
| 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, +99 for rounding up. */ |
| vy = (v0 * 100) - ((((v0 - v1) * 100) / (f0 - f1)) * (f0 - fx)); |
| vy = (vy + 99) / 100; |
| |
| return vy; |
| } |
| |
| static void svs_get_vops_v2(struct mtk_svs *svs) |
| { |
| struct svs_bank *svsb = svs->bank; |
| u32 temp, i; |
| |
| temp = svs_readl(svs, VOP30); |
| svsb->volts[6] = (temp >> 24) & 0xff; |
| svsb->volts[4] = (temp >> 16) & 0xff; |
| svsb->volts[2] = (temp >> 8) & 0xff; |
| svsb->volts[0] = (temp & 0xff); |
| |
| temp = svs_readl(svs, VOP74); |
| svsb->volts[14] = (temp >> 24) & 0xff; |
| svsb->volts[12] = (temp >> 16) & 0xff; |
| svsb->volts[10] = (temp >> 8) & 0xff; |
| svsb->volts[8] = (temp & 0xff); |
| |
| for (i = 0; i <= 7; i++) { |
| if (i < 7) { |
| svsb->volts[(i * 2) + 1] = |
| interpolate(svsb->freqs_pct[i * 2], |
| svsb->freqs_pct[(i + 1) * 2], |
| svsb->volts[i * 2], |
| svsb->volts[(i + 1) * 2], |
| svsb->freqs_pct[(i * 2) + 1]); |
| } else if (i == 7) { |
| svsb->volts[(i * 2) + 1] = |
| interpolate(svsb->freqs_pct[(i - 1) * 2], |
| svsb->freqs_pct[i * 2], |
| svsb->volts[(i - 1) * 2], |
| svsb->volts[i * 2], |
| svsb->freqs_pct[(i * 2) + 1]); |
| } |
| } |
| } |
| |
| static void svs_set_freqs_pct_v2(struct mtk_svs *svs) |
| { |
| struct svs_bank *svsb = svs->bank; |
| |
| svs_writel(svs, |
| ((svsb->freqs_pct[6] << 24) & 0xff000000) | |
| ((svsb->freqs_pct[4] << 16) & 0xff0000) | |
| ((svsb->freqs_pct[2] << 8) & 0xff00) | |
| (svsb->freqs_pct[0] & 0xff), |
| FREQPCT30); |
| svs_writel(svs, |
| ((svsb->freqs_pct[14] << 24) & 0xff000000) | |
| ((svsb->freqs_pct[12] << 16) & 0xff0000) | |
| ((svsb->freqs_pct[10] << 8) & 0xff00) | |
| ((svsb->freqs_pct[8]) & 0xff), |
| FREQPCT74); |
| } |
| |
| static void svs_set_phase(struct mtk_svs *svs, u32 target_phase) |
| { |
| struct svs_bank *svsb = svs->bank; |
| u32 des_char, temp_char, det_char, limit_vals; |
| u32 init2vals, ts_calcs, val, filter, i; |
| |
| svs_switch_bank(svs); |
| |
| des_char = ((svsb->bdes << 8) & 0xff00) | (svsb->mdes & 0xff); |
| svs_writel(svs, des_char, DESCHAR); |
| |
| temp_char = ((svsb->vco << 16) & 0xff0000) | |
| ((svsb->mtdes << 8) & 0xff00) | |
| (svsb->dvt_fixed & 0xff); |
| svs_writel(svs, temp_char, TEMPCHAR); |
| |
| det_char = ((svsb->dcbdet << 8) & 0xff00) | (svsb->dcmdet & 0xff); |
| svs_writel(svs, det_char, DETCHAR); |
| |
| svs_writel(svs, svsb->dc_config, DCCONFIG); |
| svs_writel(svs, svsb->age_config, AGECONFIG); |
| |
| if (svsb->agem == 0x0) { |
| svs_writel(svs, 0x80000000, RUNCONFIG); |
| } else { |
| val = 0x0; |
| |
| for (i = 0; i < 24; i += 2) { |
| filter = 0x3 << i; |
| |
| if ((svsb->age_config & filter) == 0x0) |
| val |= (0x1 << i); |
| else |
| val |= (svsb->age_config & filter); |
| } |
| svs_writel(svs, val, RUNCONFIG); |
| } |
| |
| svsb->ops->set_freqs_pct(svs); |
| |
| limit_vals = ((svsb->vmax << 24) & 0xff000000) | |
| ((svsb->vmin << 16) & 0xff0000) | |
| ((svsb->dthi << 8) & 0xff00) | |
| (svsb->dtlo & 0xff); |
| svs_writel(svs, limit_vals, LIMITVALS); |
| svs_writel(svs, (svsb->vboot & 0xff), VBOOT); |
| svs_writel(svs, (svsb->det_window & 0xffff), DETWINDOW); |
| svs_writel(svs, (svsb->det_max & 0xffff), CONFIG); |
| |
| if (svsb->chkshift != 0) |
| svs_writel(svs, (svsb->chkshift & 0xff), CHKSHIFT); |
| |
| if (svsb->ctl0 != 0) |
| svs_writel(svs, svsb->ctl0, CTL0); |
| |
| svs_writel(svs, 0x00ffffff, INTSTS); |
| |
| switch (target_phase) { |
| case SVS_PHASE_INIT01: |
| svs_writel(svs, 0x00005f01, INTEN); |
| svs_writel(svs, 0x00000001, SVSEN); |
| break; |
| case SVS_PHASE_INIT02: |
| svs_writel(svs, 0x00005f01, INTEN); |
| init2vals = ((svsb->age_voffset_in << 16) & 0xffff0000) | |
| (svsb->dc_voffset_in & 0xffff); |
| svs_writel(svs, init2vals, INIT2VALS); |
| svs_writel(svs, 0x00000005, SVSEN); |
| break; |
| case SVS_PHASE_MON: |
| ts_calcs = ((svsb->bts << 12) & 0xfff000) | (svsb->mts & 0xfff); |
| svs_writel(svs, ts_calcs, TSCALCS); |
| svs_writel(svs, 0x00FF0000, INTEN); |
| svs_writel(svs, 0x00000002, SVSEN); |
| break; |
| default: |
| WARN_ON(1); |
| break; |
| } |
| } |
| |
| static inline void svs_init01_isr_handler(struct mtk_svs *svs) |
| { |
| struct svs_bank *svsb = svs->bank; |
| enum reg_index rg_i; |
| |
| pr_notice("%s: %s: VDN74:0x%08x, VDN30:0x%08x, DCVALUES:0x%08x\n", |
| svsb->name, __func__, svs_readl(svs, VDESIGN74), |
| svs_readl(svs, VDESIGN30), svs_readl(svs, DCVALUES)); |
| |
| for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++) |
| svsb->reg_data[SVS_PHASE_INIT01][rg_i] = svs_readl(svs, rg_i); |
| |
| svsb->dc_voffset_in = ~(svs_readl(svs, DCVALUES) & 0xffff) + 1; |
| if (svsb->init01_volt_flag == SVS_INIT01_VOLT_IGNORE) |
| svsb->dc_voffset_in = 0; |
| else if ((svsb->dc_voffset_in & 0x8000) && |
| (svsb->init01_volt_flag == SVS_INIT01_VOLT_INC_ONLY)) |
| svsb->dc_voffset_in = 0; |
| |
| svsb->age_voffset_in = svs_readl(svs, AGEVALUES) & 0xffff; |
| |
| svs_writel(svs, 0x0, SVSEN); |
| svs_writel(svs, 0x1, INTSTS); |
| |
| /* svs init01 clock gating */ |
| svsb->coresel &= ~svsb->systemclk_en; |
| |
| svsb->phase = SVS_PHASE_INIT01; |
| complete(&svsb->init_completion); |
| } |
| |
| static inline void svs_init02_isr_handler(struct mtk_svs *svs) |
| { |
| struct svs_bank *svsb = svs->bank; |
| enum reg_index rg_i; |
| |
| pr_notice("%s: %s: VOP74:0x%08x, VOP30:0x%08x, DCVALUES:0x%08x\n", |
| svsb->name, __func__, svs_readl(svs, VOP74), |
| svs_readl(svs, VOP30), svs_readl(svs, DCVALUES)); |
| |
| for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++) |
| svsb->reg_data[SVS_PHASE_INIT02][rg_i] = svs_readl(svs, rg_i); |
| |
| svsb->ops->get_vops(svs); |
| memcpy(svsb->init02_volts, svsb->volts, 4 * svsb->opp_count); |
| svsb->phase = SVS_PHASE_INIT02; |
| |
| svs_writel(svs, 0x0, SVSEN); |
| svs_writel(svs, 0x1, INTSTS); |
| |
| complete(&svsb->init_completion); |
| } |
| |
| static inline void svs_mon_mode_isr_handler(struct mtk_svs *svs) |
| { |
| struct svs_bank *svsb = svs->bank; |
| enum reg_index rg_i; |
| |
| for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++) |
| svsb->reg_data[SVS_PHASE_MON][rg_i] = svs_readl(svs, rg_i); |
| |
| svsb->svs_temp = svs_readl(svs, TEMP) & 0xff; |
| |
| svsb->ops->get_vops(svs); |
| svsb->phase = SVS_PHASE_MON; |
| |
| svs_writel(svs, 0x00ff0000, INTSTS); |
| } |
| |
| static inline void svs_error_isr_handler(struct mtk_svs *svs) |
| { |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb = svs->bank; |
| enum reg_index rg_i; |
| |
| pr_err("%s(): %s(%s)", __func__, svsp->name, svsb->name); |
| pr_err("CORESEL(0x%x) = 0x%08x\n", |
| svsp->regs[CORESEL], svs_readl(svs, CORESEL)), |
| pr_err("SVSEN(0x%x) = 0x%08x, INTSTS(0x%x) = 0x%08x\n", |
| svsp->regs[SVSEN], svs_readl(svs, SVSEN), |
| svsp->regs[INTSTS], svs_readl(svs, INTSTS)); |
| pr_err("SMSTATE0(0x%x) = 0x%08x, SMSTATE1(0x%x) = 0x%08x\n", |
| svsp->regs[SMSTATE0], svs_readl(svs, SMSTATE0), |
| svsp->regs[SMSTATE1], svs_readl(svs, SMSTATE1)); |
| |
| for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++) |
| svsb->reg_data[SVS_PHASE_MON][rg_i] = svs_readl(svs, rg_i); |
| |
| svsb->init01_support = false; |
| svsb->init02_support = false; |
| svsb->mon_mode_support = false; |
| |
| if (svsb->phase == SVS_PHASE_MON) |
| svsb->phase = SVS_PHASE_INIT02; |
| |
| svs_writel(svs, 0x0, SVSEN); |
| svs_writel(svs, 0x00ffffff, INTSTS); |
| } |
| |
| static inline void svs_isr_handler(struct mtk_svs *svs) |
| { |
| u32 intsts, svsen; |
| |
| svs_switch_bank(svs); |
| |
| intsts = svs_readl(svs, INTSTS); |
| svsen = svs_readl(svs, SVSEN); |
| |
| if (intsts == 0x1 && ((svsen & 0x7) == 0x1)) |
| svs_init01_isr_handler(svs); |
| else if ((intsts == 0x1) && ((svsen & 0x7) == 0x5)) |
| svs_init02_isr_handler(svs); |
| else if ((intsts & 0x00ff0000) != 0x0) |
| svs_mon_mode_isr_handler(svs); |
| else |
| svs_error_isr_handler(svs); |
| } |
| |
| static irqreturn_t svs_isr(int irq, void *data) |
| { |
| struct mtk_svs *svs = (struct mtk_svs *)data; |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb = NULL; |
| unsigned long flags; |
| u32 idx; |
| |
| flags = claim_mtk_svs_lock(); |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svs->bank = svsb; |
| |
| if (svsb->suspended) |
| continue; |
| else if (svsb->intst & svs_readl(svs, INTST)) |
| continue; |
| |
| svs_isr_handler(svs); |
| break; |
| } |
| release_mtk_svs_lock(flags); |
| |
| if (svsb->phase != SVS_PHASE_INIT01) |
| svs_set_volts(svsb, false); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void svs_mon_mode(struct mtk_svs *svs) |
| { |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| unsigned long flags; |
| u32 idx; |
| |
| flags = claim_mtk_svs_lock(); |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svs->bank = svsb; |
| |
| if (!svsb->mon_mode_support) |
| continue; |
| |
| svs_set_phase(svs, SVS_PHASE_MON); |
| } |
| release_mtk_svs_lock(flags); |
| } |
| |
| static int svs_init02(struct mtk_svs *svs) |
| { |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| unsigned long flags, time_left; |
| u32 idx; |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svs->bank = svsb; |
| |
| if (!svsb->init02_support) |
| continue; |
| |
| reinit_completion(&svsb->init_completion); |
| flags = claim_mtk_svs_lock(); |
| svs_set_phase(svs, SVS_PHASE_INIT02); |
| release_mtk_svs_lock(flags); |
| time_left = |
| wait_for_completion_timeout(&svsb->init_completion, |
| msecs_to_jiffies(2000)); |
| if (time_left == 0) { |
| pr_err("%s: init02 completion timeout\n", svsb->name); |
| return -EBUSY; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int svs_init01(struct mtk_svs *svs) |
| { |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| struct pm_qos_request qos_request = { {0} }; |
| unsigned long flags, time_left; |
| bool search_done; |
| int ret = -EINVAL; |
| u32 opp_freqs, opp_vboot, buck_volt, idx, i; |
| |
| /* Let CPUs leave idle-off state for initializing svs_init01. */ |
| pm_qos_add_request(&qos_request, PM_QOS_CPU_DMA_LATENCY, 0); |
| |
| /* Sometimes two svs_bank 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]; |
| search_done = false; |
| |
| if (!svsb->init01_support) |
| continue; |
| |
| ret = regulator_set_mode(svsb->buck, REGULATOR_MODE_FAST); |
| if (ret) |
| pr_notice("%s: fail to set fast mode: %d\n", |
| svsb->name, ret); |
| |
| if (svsb->mtcmos_request) { |
| ret = regulator_enable(svsb->buck); |
| if (ret) { |
| pr_err("%s: fail to enable %s power: %d\n", |
| svsb->name, svsb->buck_name, ret); |
| goto init01_finish; |
| } |
| |
| ret = dev_pm_domain_attach(svsb->dev, false); |
| if (ret) { |
| pr_err("%s: attach pm domain fail: %d\n", |
| svsb->name, ret); |
| goto init01_finish; |
| } |
| |
| pm_runtime_enable(svsb->dev); |
| ret = pm_runtime_get_sync(svsb->dev); |
| if (ret < 0) { |
| pr_err("%s: turn mtcmos on fail: %d\n", |
| svsb->name, 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_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->dev, |
| opp_freqs, |
| opp_vboot); |
| if (ret) { |
| pr_err("%s: set voltage failed: %d\n", |
| svsb->name, ret); |
| goto init01_finish; |
| } |
| |
| search_done = true; |
| } else { |
| dev_pm_opp_disable(svsb->dev, |
| svsb->opp_freqs[i]); |
| } |
| } |
| } |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svs->bank = svsb; |
| |
| if (!svsb->init01_support) |
| continue; |
| |
| opp_vboot = svs_volt_to_opp_volt(svsb->vboot, |
| svsb->volt_step, |
| svsb->volt_base); |
| |
| buck_volt = regulator_get_voltage(svsb->buck); |
| if (buck_volt != opp_vboot) { |
| pr_err("%s: buck voltage: %u, expected vboot: %u\n", |
| svsb->name, buck_volt, opp_vboot); |
| ret = -EPERM; |
| goto init01_finish; |
| } |
| |
| init_completion(&svsb->init_completion); |
| flags = claim_mtk_svs_lock(); |
| svs_set_phase(svs, SVS_PHASE_INIT01); |
| release_mtk_svs_lock(flags); |
| time_left = |
| wait_for_completion_timeout(&svsb->init_completion, |
| msecs_to_jiffies(2000)); |
| if (time_left == 0) { |
| pr_err("%s: init01 completion timeout\n", svsb->name); |
| ret = -EBUSY; |
| goto init01_finish; |
| } |
| } |
| |
| init01_finish: |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (!svsb->init01_support) |
| continue; |
| |
| for (i = 0; i < svsb->opp_count; i++) |
| dev_pm_opp_enable(svsb->dev, svsb->opp_freqs[i]); |
| |
| if (regulator_set_mode(svsb->buck, REGULATOR_MODE_NORMAL)) |
| pr_notice("%s: fail to set normal mode: %d\n", |
| svsb->name, ret); |
| |
| if (svsb->mtcmos_request) { |
| if (pm_runtime_put_sync(svsb->dev)) |
| pr_err("%s: turn mtcmos off fail: %d\n", |
| svsb->name, ret); |
| pm_runtime_disable(svsb->dev); |
| dev_pm_domain_detach(svsb->dev, 0); |
| if (regulator_disable(svsb->buck)) |
| pr_err("%s: fail to disable %s power: %d\n", |
| svsb->name, svsb->buck_name, ret); |
| } |
| } |
| |
| pm_qos_remove_request(&qos_request); |
| |
| return ret; |
| } |
| |
| static int svs_start(struct mtk_svs *svs) |
| { |
| int ret; |
| |
| ret = svs_init01(svs); |
| if (ret) |
| return ret; |
| |
| ret = svs_init02(svs); |
| if (ret) |
| return ret; |
| |
| svs_mon_mode(svs); |
| |
| return ret; |
| } |
| |
| static int svs_mt8183_efuse_parsing(struct mtk_svs *svs) |
| { |
| const struct svs_platform *svsp = svs->platform; |
| struct thermal_parameter tp; |
| struct svs_bank *svsb; |
| bool mon_mode_support = true; |
| int format[6], x_roomt[6], tb_roomt; |
| u32 idx, i, ft_pgm, mts, temp0, temp1, temp2; |
| |
| if (svsp->fake_efuse) { |
| pr_notice("fake efuse\n"); |
| svs->efuse[0] = 0x00310080; |
| svs->efuse[1] = 0xabfbf757; |
| svs->efuse[2] = 0x47c747c7; |
| svs->efuse[3] = 0xabfbf757; |
| svs->efuse[4] = 0xe7fca0ec; |
| svs->efuse[5] = 0x47bf4b88; |
| svs->efuse[6] = 0xabfb8fa5; |
| svs->efuse[7] = 0xabfb217b; |
| svs->efuse[8] = 0x4bf34be1; |
| svs->efuse[9] = 0xabfb670d; |
| svs->efuse[16] = 0xabfbc653; |
| svs->efuse[17] = 0x47f347e1; |
| svs->efuse[18] = 0xabfbd848; |
| |
| svs->thermal_efuse[0] = 0x02873f69; |
| svs->thermal_efuse[1] = 0xa11d9142; |
| svs->thermal_efuse[2] = 0xa2526900; |
| } |
| |
| /* svs efuse parsing */ |
| ft_pgm = (svs->efuse[0] >> 4) & 0xf; |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| if (ft_pgm <= 1) |
| svsb->init01_volt_flag = SVS_INIT01_VOLT_IGNORE; |
| |
| switch (svsb->sw_id) { |
| case SVS_CPU_LITTLE: |
| svsb->bdes = svs->efuse[16] & 0xff; |
| svsb->mdes = (svs->efuse[16] >> 8) & 0xff; |
| svsb->dcbdet = (svs->efuse[16] >> 16) & 0xff; |
| svsb->dcmdet = (svs->efuse[16] >> 24) & 0xff; |
| svsb->mtdes = (svs->efuse[17] >> 16) & 0xff; |
| |
| if (ft_pgm <= 3) |
| svsb->volt_offset += 10; |
| else |
| svsb->volt_offset += 2; |
| break; |
| case SVS_CPU_BIG: |
| svsb->bdes = svs->efuse[18] & 0xff; |
| svsb->mdes = (svs->efuse[18] >> 8) & 0xff; |
| svsb->dcbdet = (svs->efuse[18] >> 16) & 0xff; |
| svsb->dcmdet = (svs->efuse[18] >> 24) & 0xff; |
| svsb->mtdes = svs->efuse[17] & 0xff; |
| |
| if (ft_pgm <= 3) |
| svsb->volt_offset += 15; |
| else |
| svsb->volt_offset += 12; |
| break; |
| case SVS_CCI: |
| svsb->bdes = svs->efuse[4] & 0xff; |
| svsb->mdes = (svs->efuse[4] >> 8) & 0xff; |
| svsb->dcbdet = (svs->efuse[4] >> 16) & 0xff; |
| svsb->dcmdet = (svs->efuse[4] >> 24) & 0xff; |
| svsb->mtdes = (svs->efuse[5] >> 16) & 0xff; |
| |
| if (ft_pgm <= 3) |
| svsb->volt_offset += 10; |
| else |
| svsb->volt_offset += 2; |
| break; |
| case SVS_GPU: |
| svsb->bdes = svs->efuse[6] & 0xff; |
| svsb->mdes = (svs->efuse[6] >> 8) & 0xff; |
| svsb->dcbdet = (svs->efuse[6] >> 16) & 0xff; |
| svsb->dcmdet = (svs->efuse[6] >> 24) & 0xff; |
| svsb->mtdes = svs->efuse[5] & 0xff; |
| |
| if (ft_pgm >= 2) { |
| svsb->freq_base = 800000000; /* 800MHz */ |
| svsb->dvt_fixed = 2; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| for (i = 0; i < svsp->efuse_num; i++) { |
| if (svs->efuse[i]) |
| pr_notice("M_HW_RES%d: 0x%08x\n", i, svs->efuse[i]); |
| } |
| |
| /* thermal efuse parsing */ |
| if (!svs->thermal_efuse) |
| return 0; |
| |
| tp.adc_ge_t = (svs->thermal_efuse[1] >> 22) & 0x3ff; |
| tp.adc_oe_t = (svs->thermal_efuse[1] >> 12) & 0x3ff; |
| |
| tp.o_vtsmcu1 = (svs->thermal_efuse[0] >> 17) & 0x1ff; |
| tp.o_vtsmcu2 = (svs->thermal_efuse[0] >> 8) & 0x1ff; |
| tp.o_vtsmcu3 = svs->thermal_efuse[1] & 0x1ff; |
| tp.o_vtsmcu4 = (svs->thermal_efuse[2] >> 23) & 0x1ff; |
| tp.o_vtsmcu5 = (svs->thermal_efuse[2] >> 5) & 0x1ff; |
| tp.o_vtsabb = (svs->thermal_efuse[2] >> 14) & 0x1ff; |
| |
| tp.degc_cali = (svs->thermal_efuse[0] >> 1) & 0x3f; |
| tp.adc_cali_en_t = svs->thermal_efuse[0] & BIT(0); |
| tp.o_slope_sign = (svs->thermal_efuse[0] >> 7) & BIT(0); |
| |
| tp.ts_id = (svs->thermal_efuse[1] >> 9) & BIT(0); |
| tp.o_slope = (svs->thermal_efuse[0] >> 26) & 0x3f; |
| |
| if (tp.adc_cali_en_t == 1) { |
| if (tp.ts_id == 0) |
| 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)) { |
| pr_err("bad thermal efuse data. disable mon mode\n"); |
| mon_mode_support = false; |
| } |
| } else { |
| pr_err("no thermal efuse data. disable mon mode\n"); |
| mon_mode_support = false; |
| } |
| |
| if (!mon_mode_support) { |
| for (i = 0; i < svsp->thermal_efuse_num; i++) |
| pr_err("thermal_efuse[%u] = 0x%08x\n", |
| i, svs->thermal_efuse[i]); |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svsb->mon_mode_support = false; |
| } |
| |
| return 0; |
| } |
| |
| 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 == 0) |
| 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 SVS_CPU_LITTLE: |
| tb_roomt = x_roomt[3]; |
| break; |
| case SVS_CPU_BIG: |
| tb_roomt = x_roomt[4]; |
| break; |
| case SVS_CCI: |
| tb_roomt = x_roomt[3]; |
| break; |
| case SVS_GPU: |
| tb_roomt = x_roomt[1]; |
| break; |
| default: |
| pr_err("unknown svsb_id = %u? disable svs\n", |
| svsb->sw_id); |
| return -EINVAL; |
| } |
| |
| temp0 = (tp.degc_cali * 10 / 2); |
| temp1 = ((10000 * 100000 / 4096 / tp.gain) * |
| tp.oe + tb_roomt * 10) * 15 / 18; |
| |
| if (tp.o_slope_sign == 0) |
| 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 0; |
| } |
| |
| static int svs_is_support(struct mtk_svs *svs) |
| { |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| struct nvmem_cell *cell; |
| size_t len; |
| int ret; |
| u32 idx, i; |
| |
| if (svsp->fake_efuse) { |
| len = svsp->efuse_num * 4; |
| svs->efuse = kzalloc(len, GFP_KERNEL); |
| if (!svs->efuse) |
| return -ENOMEM; |
| |
| len = svsp->thermal_efuse_num * 4; |
| svs->thermal_efuse = kzalloc(len, GFP_KERNEL); |
| if (!svs->thermal_efuse) |
| return -ENOMEM; |
| |
| goto svsp_efuse_parsing; |
| } |
| |
| /* get svs efuse by nvmem */ |
| cell = nvmem_cell_get(svs->dev, "svs-calibration-data"); |
| if (IS_ERR(cell)) { |
| pr_err("no \"svs-calibration-data\" from dts? disable svs\n"); |
| return PTR_ERR(cell); |
| } |
| |
| svs->efuse = (u32 *)nvmem_cell_read(cell, &len); |
| nvmem_cell_put(cell); |
| |
| ret = (svs->efuse[svsp->efuse_check] == 0) ? -EPERM : 0; |
| if (ret) { |
| pr_err("no svs efuse. disable svs.\n"); |
| for (i = 0; i < svsp->efuse_num; i++) |
| pr_err("M_HW_RES%d: 0x%08x\n", i, svs->efuse[i]); |
| return ret; |
| } |
| |
| /* get thermal efuse by nvmem */ |
| cell = nvmem_cell_get(svs->dev, "calibration-data"); |
| if (IS_ERR(cell)) { |
| pr_err("no \"calibration-data\" from dts? disable mon mode\n"); |
| svs->thermal_efuse = NULL; |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svsb->mon_mode_support = false; |
| } |
| goto svsp_efuse_parsing; |
| } |
| |
| svs->thermal_efuse = (u32 *)nvmem_cell_read(cell, &len); |
| nvmem_cell_put(cell); |
| |
| svsp_efuse_parsing: |
| ret = svsp->efuse_parsing(svs); |
| |
| return ret; |
| } |
| |
| static int svs_resource_setup(struct mtk_svs *svs) |
| { |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| struct platform_device *pdev; |
| struct device_node *np = NULL; |
| struct dev_pm_opp *opp; |
| unsigned long freq; |
| size_t opp_size; |
| int count, ret; |
| u32 idx, i; |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (!svsb->init01_support) |
| continue; |
| |
| switch (svsb->sw_id) { |
| case SVS_CPU_LITTLE: |
| svsb->name = "SVS_CPU_LITTLE"; |
| break; |
| case SVS_CPU_BIG: |
| svsb->name = "SVS_CPU_BIG"; |
| break; |
| case SVS_CCI: |
| svsb->name = "SVS_CCI"; |
| break; |
| case SVS_GPU: |
| svsb->name = "SVS_GPU"; |
| break; |
| default: |
| WARN_ON(1); |
| return -EINVAL; |
| } |
| |
| /* Add svs_bank device for opp-table/mtcmos/buck control */ |
| pdev = platform_device_alloc(svsb->name, 0); |
| if (!pdev) { |
| pr_err("%s: fail to alloc pdev for svs_bank\n", |
| svsb->name); |
| return -ENOMEM; |
| } |
| |
| for_each_child_of_node(svs->dev->of_node, np) { |
| if (of_device_is_compatible(np, svsb->of_compatible)) { |
| pdev->dev.of_node = np; |
| break; |
| } |
| } |
| |
| ret = platform_device_add(pdev); |
| if (ret) { |
| pr_err("%s: fail to add svs_bank device: %d\n", |
| svsb->name, ret); |
| return ret; |
| } |
| |
| svsb->dev = &pdev->dev; |
| dev_set_drvdata(svsb->dev, svs); |
| ret = dev_pm_opp_of_add_table(svsb->dev); |
| if (ret) { |
| pr_err("%s: fail to add opp table: %d\n", |
| svsb->name, ret); |
| return ret; |
| } |
| |
| mutex_init(&svsb->lock); |
| |
| svsb->buck = devm_regulator_get_optional(svsb->dev, |
| svsb->buck_name); |
| if (IS_ERR(svsb->buck)) { |
| pr_err("%s: cannot get regulator \"%s-supply\"\n", |
| svsb->name, svsb->buck_name); |
| return PTR_ERR(svsb->buck); |
| } |
| |
| count = dev_pm_opp_get_opp_count(svsb->dev); |
| if (svsb->opp_count != count) { |
| pr_err("%s: opp_count not \"%u\" but get \"%d\"?\n", |
| svsb->name, svsb->opp_count, count); |
| return count; |
| } |
| |
| opp_size = 4 * svsb->opp_count; |
| svsb->opp_volts = kmalloc(opp_size, GFP_KERNEL); |
| if (!svsb->opp_volts) |
| return -ENOMEM; |
| |
| svsb->init02_volts = kmalloc(opp_size, GFP_KERNEL); |
| if (!svsb->init02_volts) |
| return -ENOMEM; |
| |
| svsb->volts = kmalloc(opp_size, GFP_KERNEL); |
| if (!svsb->volts) |
| return -ENOMEM; |
| |
| svsb->opp_freqs = kmalloc(opp_size, GFP_KERNEL); |
| if (!svsb->opp_freqs) |
| return -ENOMEM; |
| |
| svsb->freqs_pct = kmalloc(opp_size, GFP_KERNEL); |
| if (!svsb->freqs_pct) |
| return -ENOMEM; |
| |
| for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) { |
| opp = dev_pm_opp_find_freq_floor(svsb->dev, &freq); |
| if (IS_ERR(opp)) { |
| pr_err("%s: error opp entry!!, err = %ld\n", |
| svsb->name, 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) & 0xff; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int svs_suspend(struct device *dev) |
| { |
| struct mtk_svs *svs = dev_get_drvdata(dev); |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| unsigned long flags; |
| u32 idx; |
| |
| /* Wait if there is processing svs_isr(). Suspend all banks. */ |
| flags = claim_mtk_svs_lock(); |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svs->bank = svsb; |
| svs_switch_bank(svs); |
| svs_writel(svs, 0x0, SVSEN); |
| svs_writel(svs, 0x00ffffff, INTSTS); |
| svsb->suspended = true; |
| } |
| release_mtk_svs_lock(flags); |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| if (svsb->phase == SVS_PHASE_MON) { |
| svsb->phase = SVS_PHASE_INIT02; |
| svs_set_volts(svsb, true); |
| } |
| } |
| |
| clk_disable_unprepare(svs->main_clk); |
| |
| return 0; |
| } |
| |
| static int svs_resume(struct device *dev) |
| { |
| struct mtk_svs *svs = dev_get_drvdata(dev); |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| int ret; |
| u32 idx; |
| |
| ret = clk_prepare_enable(svs->main_clk); |
| if (ret) |
| pr_err("%s(): cannot enable main_clk\n", __func__); |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| svsb->suspended = false; |
| } |
| |
| ret = svs_init02(svs); |
| if (ret) |
| return ret; |
| |
| svs_mon_mode(svs); |
| |
| return 0; |
| } |
| |
| static int svs_debug_proc_show(struct seq_file *m, void *v) |
| { |
| struct svs_bank *svsb = (struct svs_bank *)m->private; |
| |
| if (svsb->phase == SVS_PHASE_INIT01) |
| seq_puts(m, "init1\n"); |
| else if (svsb->phase == SVS_PHASE_INIT02) |
| seq_puts(m, "init2\n"); |
| else if (svsb->phase == SVS_PHASE_MON) |
| seq_puts(m, "mon mode\n"); |
| else if (svsb->phase == SVS_PHASE_ERROR) |
| seq_puts(m, "disabled\n"); |
| else |
| seq_puts(m, "unknown\n"); |
| |
| return 0; |
| } |
| |
| static ssize_t svs_debug_proc_write(struct file *file, |
| const char __user *buffer, |
| size_t count, loff_t *pos) |
| { |
| struct svs_bank *svsb = (struct svs_bank *)PDE_DATA(file_inode(file)); |
| struct mtk_svs *svs = dev_get_drvdata(svsb->dev); |
| char *buf = (char *)__get_free_page(GFP_USER); |
| unsigned long flags; |
| int enabled, ret; |
| |
| if (svsb->phase == SVS_PHASE_ERROR) |
| return count; |
| |
| if (!buf) |
| return -ENOMEM; |
| |
| if (count >= PAGE_SIZE) { |
| free_page((unsigned long)buf); |
| return -EINVAL; |
| } |
| |
| if (copy_from_user(buf, buffer, count)) { |
| free_page((unsigned long)buf); |
| return -EFAULT; |
| } |
| |
| buf[count] = '\0'; |
| |
| ret = kstrtoint(buf, 10, &enabled); |
| if (ret) |
| return ret; |
| |
| if (!enabled) { |
| flags = claim_mtk_svs_lock(); |
| svs->bank = svsb; |
| |
| svsb->init01_support = false; |
| svsb->init02_support = false; |
| svsb->mon_mode_support = false; |
| |
| svs_switch_bank(svs); |
| svs_writel(svs, 0x0, SVSEN); |
| svs_writel(svs, 0x00ffffff, INTSTS); |
| release_mtk_svs_lock(flags); |
| } |
| |
| svsb->phase = SVS_PHASE_ERROR; |
| svs_set_volts(svsb, true); |
| |
| return count; |
| } |
| |
| proc_fops_rw(svs_debug); |
| |
| static int svs_dump_proc_show(struct seq_file *m, void *v) |
| { |
| struct mtk_svs *svs = (struct mtk_svs *)m->private; |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| unsigned long svs_reg_addr; |
| u32 idx, i, j; |
| |
| for (i = 0; i < svsp->efuse_num; i++) { |
| if (svs->efuse[i]) |
| seq_printf(m, "M_HW_RES%d = 0x%08x\n", |
| i, svs->efuse[i]); |
| } |
| |
| for (i = 0; i < svsp->thermal_efuse_num; i++) { |
| if (svs->thermal_efuse && svs->thermal_efuse[i]) |
| seq_printf(m, "THERMAL_EFUSE%d = 0x%08x\n", |
| i, svs->thermal_efuse[i]); |
| } |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (!svsb->init01_support) |
| continue; |
| |
| for (i = SVS_PHASE_INIT01; i <= SVS_PHASE_MON; i++) { |
| seq_printf(m, "Bank_number = %u\n", svsb->hw_id); |
| |
| if (i < SVS_PHASE_MON) |
| seq_printf(m, "mode = init%d\n", i + 1); |
| else |
| seq_puts(m, "mode = mon\n"); |
| |
| for (j = TEMPMONCTL0; j < reg_num; j++) { |
| svs_reg_addr = (unsigned long)(svs->base + |
| svsp->regs[j]); |
| seq_printf(m, "0x%08lx = 0x%08x\n", |
| svs_reg_addr, svsb->reg_data[i][j]); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| proc_fops_ro(svs_dump); |
| |
| static int svs_status_proc_show(struct seq_file *m, void *v) |
| { |
| struct svs_bank *svsb = (struct svs_bank *)m->private; |
| struct dev_pm_opp *opp; |
| unsigned long freq; |
| int zone_temp, ret; |
| u32 i; |
| |
| ret = svs_get_zone_temperature(svsb, &zone_temp); |
| if (ret) |
| seq_printf(m, "%s: cannot get zone \"%s\" temperature\n", |
| svsb->name, svsb->zone_name); |
| else |
| seq_printf(m, "%s: temperature = %d\n", svsb->name, zone_temp); |
| |
| for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) { |
| opp = dev_pm_opp_find_freq_floor(svsb->dev, &freq); |
| if (IS_ERR(opp)) { |
| seq_printf(m, "%s: error opp entry!!, err = %ld\n", |
| svsb->name, PTR_ERR(opp)); |
| return PTR_ERR(opp); |
| } |
| |
| seq_printf(m, "opp_freqs[%02u]: %lu, volts[%02u]: %lu, ", |
| i, freq, 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]); |
| } |
| |
| return 0; |
| } |
| |
| proc_fops_ro(svs_status); |
| |
| static int svs_volt_offset_proc_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; |
| } |
| |
| static ssize_t svs_volt_offset_proc_write(struct file *file, |
| const char __user *buffer, |
| size_t count, loff_t *pos) |
| { |
| struct svs_bank *svsb = (struct svs_bank *)PDE_DATA(file_inode(file)); |
| char *buf = (char *)__get_free_page(GFP_USER); |
| int ret, volt_offset; |
| |
| if (!buf) |
| return -ENOMEM; |
| |
| if (count >= PAGE_SIZE) { |
| free_page((unsigned long)buf); |
| return -EINVAL; |
| } |
| |
| if (copy_from_user(buf, buffer, count)) { |
| free_page((unsigned long)buf); |
| return -EFAULT; |
| } |
| |
| buf[count] = '\0'; |
| |
| if (!kstrtoint(buf, 10, &volt_offset)) { |
| svsb->volt_offset = volt_offset; |
| ret = svs_set_volts(svsb, true); |
| if (ret) |
| return ret; |
| } |
| |
| return count; |
| } |
| |
| proc_fops_rw(svs_volt_offset); |
| |
| static int svs_create_svs_procfs(struct mtk_svs *svs) |
| { |
| const struct svs_platform *svsp = svs->platform; |
| struct svs_bank *svsb; |
| struct proc_dir_entry *svs_dir, *bank_dir; |
| u32 idx, i; |
| |
| struct pentry { |
| const char *name; |
| const struct file_operations *fops; |
| }; |
| |
| struct pentry svs_entries[] = { |
| proc_entry(svs_dump), |
| }; |
| |
| struct pentry bank_entries[] = { |
| proc_entry(svs_debug), |
| proc_entry(svs_status), |
| proc_entry(svs_volt_offset), |
| }; |
| |
| svs_dir = proc_mkdir("svs", NULL); |
| if (!svs_dir) { |
| pr_err("mkdir /proc/svs failed\n"); |
| return -EPERM; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(svs_entries); i++) { |
| if (!proc_create_data(svs_entries[i].name, 0664, |
| svs_dir, svs_entries[i].fops, svs)) { |
| pr_err("create /proc/svs/%s failed\n", |
| svs_entries[i].name); |
| return -EPERM; |
| } |
| } |
| |
| for (idx = 0; idx < svsp->bank_num; idx++) { |
| svsb = &svsp->banks[idx]; |
| |
| if (!svsb->init01_support) |
| continue; |
| |
| bank_dir = proc_mkdir(svsb->name, svs_dir); |
| if (!bank_dir) { |
| pr_err("mkdir /proc/svs/%s failed\n", svsb->name); |
| return -EPERM; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(bank_entries); i++) { |
| if (!proc_create_data(bank_entries[i].name, 0664, |
| bank_dir, bank_entries[i].fops, |
| svsb)) { |
| pr_err("create /proc/svs/%s/%s failed\n", |
| svsb->name, bank_entries[i].name); |
| return -EPERM; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static struct svs_bank_ops svs_mt8183_banks_ops = { |
| .set_freqs_pct = svs_set_freqs_pct_v2, |
| .get_vops = svs_get_vops_v2, |
| }; |
| |
| static struct svs_bank svs_mt8183_banks[4] = { |
| { |
| .of_compatible = "mediatek,mt8183-svs-cpu-little", |
| .sw_id = SVS_CPU_LITTLE, |
| .hw_id = 0, |
| .ops = &svs_mt8183_banks_ops, |
| .zone_name = "tzts4", |
| .buck_name = "vcpu-little", |
| .mtcmos_request = false, |
| .init01_volt_flag = SVS_INIT01_VOLT_INC_ONLY, |
| .init01_support = true, |
| .init02_support = true, |
| .mon_mode_support = false, |
| .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 = 0x0, |
| .dc_config = 0x555555, |
| .dvt_fixed = 0x7, |
| .vco = 0x10, |
| .chkshift = 0x77, |
| .upper_temp_bound = 0x64, |
| .lower_temp_bound = 0xb2, |
| .low_temp_threashold = 25000, |
| .low_temp_offset = 0, |
| .coresel = 0x8fff0000, |
| .systemclk_en = BIT(31), |
| .intst = BIT(0), |
| .ctl0 = 0x00010001, |
| }, |
| { |
| .of_compatible = "mediatek,mt8183-svs-cpu-big", |
| .sw_id = SVS_CPU_BIG, |
| .hw_id = 1, |
| .ops = &svs_mt8183_banks_ops, |
| .zone_name = "tzts5", |
| .buck_name = "vcpu-big", |
| .mtcmos_request = false, |
| .init01_volt_flag = SVS_INIT01_VOLT_INC_ONLY, |
| .init01_support = true, |
| .init02_support = true, |
| .mon_mode_support = false, |
| .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 = 0x0, |
| .dc_config = 0x555555, |
| .dvt_fixed = 0x7, |
| .vco = 0x10, |
| .chkshift = 0x77, |
| .upper_temp_bound = 0x64, |
| .lower_temp_bound = 0xb2, |
| .low_temp_threashold = 25000, |
| .low_temp_offset = 0, |
| .coresel = 0x8fff0001, |
| .systemclk_en = BIT(31), |
| .intst = BIT(1), |
| .ctl0 = 0x00000001, |
| }, |
| { |
| .of_compatible = "mediatek,mt8183-svs-cci", |
| .sw_id = SVS_CCI, |
| .hw_id = 2, |
| .ops = &svs_mt8183_banks_ops, |
| .zone_name = "tzts4", |
| .buck_name = "vcci", |
| .mtcmos_request = false, |
| .init01_volt_flag = SVS_INIT01_VOLT_INC_ONLY, |
| .init01_support = true, |
| .init02_support = true, |
| .mon_mode_support = false, |
| .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 = 0x0, |
| .dc_config = 0x555555, |
| .dvt_fixed = 0x7, |
| .vco = 0x10, |
| .chkshift = 0x77, |
| .upper_temp_bound = 0x64, |
| .lower_temp_bound = 0xb2, |
| .low_temp_threashold = 25000, |
| .low_temp_offset = 0, |
| .coresel = 0x8fff0002, |
| .systemclk_en = BIT(31), |
| .intst = BIT(2), |
| .ctl0 = 0x00100003, |
| }, |
| { |
| .of_compatible = "mediatek,mt8183-svs-gpu", |
| .sw_id = SVS_GPU, |
| .hw_id = 3, |
| .ops = &svs_mt8183_banks_ops, |
| .zone_name = "tzts2", |
| .buck_name = "vgpu", |
| .mtcmos_request = true, |
| .init01_volt_flag = SVS_INIT01_VOLT_INC_ONLY, |
| .init01_support = true, |
| .init02_support = true, |
| .mon_mode_support = true, |
| .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 = 0x0, |
| .dc_config = 0x555555, |
| .dvt_fixed = 0x3, |
| .vco = 0x10, |
| .chkshift = 0x77, |
| .upper_temp_bound = 0x64, |
| .lower_temp_bound = 0xb2, |
| .low_temp_threashold = 25000, |
| .low_temp_offset = 3, |
| .coresel = 0x8fff0003, |
| .systemclk_en = BIT(31), |
| .intst = BIT(3), |
| .ctl0 = 0x00050001, |
| }, |
| }; |
| |
| static const struct svs_platform svs_mt8183_platform = { |
| .name = "mt8183-svs", |
| .banks = svs_mt8183_banks, |
| .efuse_parsing = svs_mt8183_efuse_parsing, |
| .regs = svs_regs_v2, |
| .fake_efuse = false, |
| .bank_num = 4, |
| .efuse_num = 25, |
| .efuse_check = 2, |
| .thermal_efuse_num = 3, |
| }; |
| |
| static const struct of_device_id mtk_svs_of_match[] = { |
| { |
| .compatible = "mediatek,mt8183-svs", |
| .data = &svs_mt8183_platform, |
| }, { |
| /* sentinel */ |
| }, |
| }; |
| |
| static int svs_probe(struct platform_device *pdev) |
| { |
| const struct of_device_id *of_dev_id; |
| struct mtk_svs *svs; |
| int ret; |
| u32 svs_irq; |
| |
| svs = devm_kzalloc(&pdev->dev, sizeof(*svs), GFP_KERNEL); |
| if (!svs) |
| return -ENOMEM; |
| |
| svs->dev = &pdev->dev; |
| if (!svs->dev->of_node) { |
| pr_err("cannot find device node\n"); |
| return -ENODEV; |
| } |
| |
| svs->base = of_iomap(svs->dev->of_node, 0); |
| if (IS_ERR(svs->base)) { |
| pr_err("cannot find svs register base\n"); |
| return PTR_ERR(svs->base); |
| } |
| |
| svs_irq = irq_of_parse_and_map(svs->dev->of_node, 0); |
| ret = devm_request_threaded_irq(svs->dev, svs_irq, NULL, svs_isr, |
| IRQF_TRIGGER_LOW | IRQF_ONESHOT, |
| "mtk-svs", svs); |
| if (ret) { |
| pr_err("register irq(%d) failed: %d\n", svs_irq, ret); |
| return ret; |
| } |
| |
| of_dev_id = of_match_node(mtk_svs_of_match, svs->dev->of_node); |
| if (!of_dev_id || !of_dev_id->data) |
| return -EINVAL; |
| |
| svs->platform = of_dev_id->data; |
| dev_set_drvdata(svs->dev, svs); |
| |
| svs->main_clk = devm_clk_get(svs->dev, "main_clk"); |
| if (IS_ERR(svs->main_clk)) { |
| pr_err("failed to get clock: %ld\n", PTR_ERR(svs->main_clk)); |
| return PTR_ERR(svs->main_clk); |
| } |
| |
| ret = clk_prepare_enable(svs->main_clk); |
| if (ret) { |
| pr_err("cannot enable main_clk: %d\n", ret); |
| return ret; |
| } |
| |
| ret = svs_is_support(svs); |
| if (ret) |
| goto svs_probe_fail; |
| |
| ret = svs_resource_setup(svs); |
| if (ret) |
| goto svs_probe_fail; |
| |
| ret = svs_start(svs); |
| if (ret) |
| goto svs_probe_fail; |
| |
| ret = svs_create_svs_procfs(svs); |
| if (ret) |
| goto svs_probe_fail; |
| |
| return 0; |
| |
| svs_probe_fail: |
| clk_disable_unprepare(svs->main_clk); |
| |
| return ret; |
| } |
| |
| static const struct dev_pm_ops svs_pm_ops = { |
| .suspend = svs_suspend, |
| .resume = 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), |
| }, |
| }; |
| |
| static int __init svs_init(void) |
| { |
| int ret; |
| |
| ret = platform_driver_register(&svs_driver); |
| if (ret) { |
| pr_err("svs platform driver register failed: %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| late_initcall_sync(svs_init); |
| |
| MODULE_DESCRIPTION("MediaTek SVS Driver v1.0"); |
| MODULE_LICENSE("GPL"); |