// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "GFX_LDO: %s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LDO_ATEST_REG 0x0 #define LDO_CFG0_REG 0x4 #define LDO_CFG1_REG 0x8 #define LDO_CFG2_REG 0xC #define LDO_LD_DATA_REG 0x10 #define LDO_VREF_TEST_CFG 0x14 #define ENABLE_LDO_STATUS_BIT (BIT(8) | BIT(12)) #define LDO_AUTOBYPASS_BIT BIT(20) #define LDO_VREF_SET_REG 0x18 #define UPDATE_VREF_BIT BIT(31) #define SEL_RST_BIT BIT(16) #define VREF_VAL_MASK GENMASK(6, 0) #define PWRSWITCH_CTRL_REG 0x1C #define LDO_CLAMP_IO_BIT BIT(31) #define CPR_BYPASS_IN_LDO_MODE_BIT BIT(30) #define EN_LDOAP_CTRL_CPR_BIT BIT(29) #define CX_CPR_BYPASS_IN_LDO_MODE_BIT BIT(10) #define PWR_SRC_SEL_BIT BIT(9) #define ACK_SW_OVR_BIT BIT(8) #define LDO_PREON_SW_OVR_BIT BIT(7) #define LDO_BYPASS_BIT BIT(6) #define LDO_PDN_BIT BIT(5) #define LDO_UNDER_SW_CTRL_BIT BIT(4) #define BHS_EN_REST_BIT BIT(2) #define BHS_EN_FEW_BIT BIT(1) #define BHS_UNDER_SW_CTL BIT(0) #define LDO_STATUS1_REG 0x24 #define PWRSWITCH_STATUS_REG 0x28 #define LDO_VREF_SETTLED_BIT BIT(4) #define LDO_READY_BIT BIT(2) #define BHS_EN_REST_ACK_BIT BIT(1) #define REF_CURRENT_X1_REG 0x2C #define REF_CURRENT_X2_REG 0x30 #define ADC_CTL_REG 0x34 #define MIN_LDO_VOLTAGE 375000 #define MAX_LDO_VOLTAGE 980000 #define LDO_STEP_VOLATGE 5000 #define MAX_LDO_REGS 11 #define BYTES_PER_FUSE_ROW 8 #define MAX_FUSE_ROW_BIT 63 #define MIN_CORNER_OFFSET 1 #define GFX_LDO_FUSE_STEP_VOLT 10000 #define GFX_LDO_FUSE_SIZE 5 enum direction { NO_CHANGE, UP, DOWN, }; enum voltage_handling { VOLTAGE, CORNER, }; struct fuse_param { unsigned int row; unsigned int bit_start; unsigned int bit_end; }; struct ldo_config { u32 offset; u32 value; }; struct msm_gfx_ldo { struct device *dev; struct regulator_desc rdesc; struct regulator_dev *rdev; struct regulator *vdd_cx; struct regulator *mem_acc_vreg; struct dentry *debugfs; u32 num_corners; u32 num_ldo_corners; u32 *open_loop_volt; u32 *ceiling_volt; u32 *floor_volt; u32 *ldo_corner_en_map; u32 *vdd_cx_corner_map; u32 *mem_acc_corner_map; const int *ref_volt; const struct fuse_param *ldo_enable_param; const struct fuse_param **init_volt_param; bool ldo_fuse_enable; bool ldo_mode_disable; struct ldo_config *ldo_init_config; void __iomem *efuse_base; phys_addr_t efuse_addr; void __iomem *ldo_base; phys_addr_t ldo_addr; bool vreg_enabled; enum msm_ldo_supply_mode mode; u32 corner; int ldo_voltage_uv; struct mutex ldo_mutex; enum voltage_handling ops_type; }; #define MSM8953_LDO_FUSE_CORNERS 3 #define LDO_MAX_OFFSET 0xFFFF static struct ldo_config msm8953_ldo_config[] = { {LDO_ATEST_REG, 0x00000203}, {LDO_CFG0_REG, 0x05008600}, {LDO_CFG1_REG, 0x0}, {LDO_CFG2_REG, 0x0000C3FC}, {LDO_VREF_TEST_CFG, 0x004B1102}, {LDO_MAX_OFFSET, LDO_MAX_OFFSET}, }; static struct ldo_config sdm660_ldo_config[] = { {LDO_ATEST_REG, 0x00000080}, {LDO_CFG0_REG, 0x0100A600}, {LDO_CFG1_REG, 0x000000A0}, {LDO_CFG2_REG, 0x0000C3FE}, {LDO_LD_DATA_REG, 0x00000000}, {LDO_VREF_TEST_CFG, 0x00401100}, {REF_CURRENT_X1_REG, 0x00000230}, {REF_CURRENT_X2_REG, 0x00000048}, {ADC_CTL_REG, 0x00000000}, {LDO_MAX_OFFSET, LDO_MAX_OFFSET}, }; static struct fuse_param msm8953_ldo_enable_param[] = { {65, 11, 11}, {}, }; static const struct fuse_param msm8953_init_voltage_param[MSM8953_LDO_FUSE_CORNERS][2] = { { {73, 42, 46}, {} }, { {73, 37, 41}, {} }, { {73, 32, 36}, {} }, }; static const int msm8953_fuse_ref_volt[MSM8953_LDO_FUSE_CORNERS] = { 580000, 650000, 720000, }; enum { MSM8953_SOC_ID, SDM660_SOC_ID, }; static int convert_open_loop_voltage_fuse(int ref_volt, int step_volt, u32 fuse, int fuse_len) { int sign, steps; sign = (fuse & (1 << (fuse_len - 1))) ? -1 : 1; steps = fuse & ((1 << (fuse_len - 1)) - 1); return ref_volt + sign * steps * step_volt; } static int read_fuse_param(void __iomem *fuse_base_addr, const struct fuse_param *param, u64 *param_value) { u64 fuse_val, val; int bits; int bits_total = 0; *param_value = 0; while (param->row || param->bit_start || param->bit_end) { if (param->bit_start > param->bit_end || param->bit_end > MAX_FUSE_ROW_BIT) { pr_err("Invalid fuse parameter segment: row=%u, start=%u, end=%u\n", param->row, param->bit_start, param->bit_end); return -EINVAL; } bits = param->bit_end - param->bit_start + 1; if (bits_total + bits > 64) { pr_err("Invalid fuse parameter segments; total bits = %d\n", bits_total + bits); return -EINVAL; } fuse_val = readq_relaxed(fuse_base_addr + param->row * BYTES_PER_FUSE_ROW); val = (fuse_val >> param->bit_start) & ((1ULL << bits) - 1); *param_value |= val << bits_total; bits_total += bits; param++; } return 0; } static enum msm_ldo_supply_mode get_operating_mode(struct msm_gfx_ldo *ldo_vreg, int corner) { if (!ldo_vreg->ldo_mode_disable && ldo_vreg->ldo_fuse_enable && ldo_vreg->ldo_corner_en_map[corner]) return LDO_MODE; return BHS_MODE; } static char *register_str[] = { "LDO_ATEST", "LDO_CFG0", "LDO_CFG1", "LDO_CFG2", "LDO_LD_DATA", "LDO_VREF_TEST_CFG", "LDO_VREF_SET", "PWRSWITCH_CTL", "LDO_STATUS0", "LDO_STATUS1", "PWRSWITCH_STATUS", }; static void dump_registers(struct msm_gfx_ldo *ldo_vreg, char *func) { u32 reg[MAX_LDO_REGS]; int i; for (i = 0; i < MAX_LDO_REGS; i++) { reg[i] = 0; reg[i] = readl_relaxed(ldo_vreg->ldo_base + (i * 4)); pr_debug("%s -- %s = 0x%x\n", func, register_str[i], reg[i]); } } #define GET_VREF(a) DIV_ROUND_UP(a - MIN_LDO_VOLTAGE, LDO_STEP_VOLATGE) static void configure_ldo_voltage(struct msm_gfx_ldo *ldo_vreg, int new_uv) { int val = 0; u32 reg = 0; val = GET_VREF(new_uv); reg = readl_relaxed(ldo_vreg->ldo_base + LDO_VREF_SET_REG); /* set the new voltage */ reg &= ~VREF_VAL_MASK; reg |= val & VREF_VAL_MASK; writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG); /* Initiate VREF update */ reg |= UPDATE_VREF_BIT; writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG); /* complete the writes */ mb(); reg &= ~UPDATE_VREF_BIT; writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG); ldo_vreg->ldo_voltage_uv = new_uv; /* complete the write sequence */ mb(); } static int ldo_update_voltage(struct msm_gfx_ldo *ldo_vreg, int new_uv) { int timeout = 50; u32 reg = 0; configure_ldo_voltage(ldo_vreg, new_uv); while (--timeout) { reg = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_STATUS_REG); if (reg & (LDO_VREF_SETTLED_BIT | LDO_READY_BIT)) break; udelay(10); } if (!timeout) { pr_err("LDO_VREF_SETTLED not set PWRSWITCH_STATUS = 0x%x\n", reg); return -EBUSY; } pr_debug("LDO voltage set to=%d uV VREF_REG=%x\n", ldo_vreg->ldo_voltage_uv, readl_relaxed(ldo_vreg->ldo_base + LDO_VREF_SET_REG)); return 0; } static int enable_ldo_mode(struct msm_gfx_ldo *ldo_vreg, int new_uv) { u32 ctl = 0; /* set the ldo-vref */ configure_ldo_voltage(ldo_vreg, new_uv); /* configure the LDO for power-up */ ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* Move BHS under SW control */ ctl |= BHS_UNDER_SW_CTL; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* Set LDO under gdsc control */ ctl &= ~LDO_UNDER_SW_CTRL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* enable hw_pre-on to gdsc */ ctl |= LDO_PREON_SW_OVR_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* remove LDO bypass */ ctl &= ~LDO_BYPASS_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* set power-source as LDO */ ctl |= PWR_SRC_SEL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* clear fake-sw ack to gdsc */ ctl &= ~ACK_SW_OVR_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* put CPR in bypass mode */ if (ldo_vreg->ops_type == CORNER) { ctl |= CPR_BYPASS_IN_LDO_MODE_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); } /* complete all writes */ mb(); dump_registers(ldo_vreg, "enable_ldo_mode"); return 0; } static int enable_bhs_mode(struct msm_gfx_ldo *ldo_vreg) { u32 ctl = 0; ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* Put LDO under SW control */ ctl |= LDO_UNDER_SW_CTRL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* set power-source as BHS */ ctl &= ~PWR_SRC_SEL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); if (ldo_vreg->ops_type == CORNER) { /* clear GFX CPR in by-pass mode */ ctl &= ~CPR_BYPASS_IN_LDO_MODE_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); } /* Enable the BHS control signals to gdsc */ ctl &= ~BHS_EN_FEW_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); ctl &= ~BHS_EN_REST_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* Put BHS under GDSC control */ ctl &= ~BHS_UNDER_SW_CTL; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); dump_registers(ldo_vreg, "enable_bhs_mode"); return 0; } static int msm_gfx_ldo_corner_enable(struct regulator_dev *rdev) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); int rc = 0, new_uv; enum msm_ldo_supply_mode enable_mode; mutex_lock(&ldo_vreg->ldo_mutex); pr_debug("regulator_enable requested. corner=%d\n", ldo_vreg->corner + MIN_CORNER_OFFSET); if (ldo_vreg->vdd_cx) { rc = regulator_set_voltage(ldo_vreg->vdd_cx, ldo_vreg->vdd_cx_corner_map[ldo_vreg->corner], INT_MAX); if (rc) { pr_err("Unable to set CX for corner %d rc=%d\n", ldo_vreg->corner + MIN_CORNER_OFFSET, rc); goto fail; } rc = regulator_enable(ldo_vreg->vdd_cx); if (rc) { pr_err("regulator_enable: vdd_cx: failed rc=%d\n", rc); goto fail; } } enable_mode = get_operating_mode(ldo_vreg, ldo_vreg->corner); if (enable_mode == LDO_MODE) { new_uv = ldo_vreg->open_loop_volt[ldo_vreg->corner]; rc = enable_ldo_mode(ldo_vreg, new_uv); pr_debug("LDO voltage configured =%d uV corner=%d\n", ldo_vreg->ldo_voltage_uv, ldo_vreg->corner + MIN_CORNER_OFFSET); } else { rc = enable_bhs_mode(ldo_vreg); } if (rc) { pr_err("Failed to enable regulator in %s mode rc=%d\n", (enable_mode == LDO_MODE) ? "LDO" : "BHS", rc); goto disable_cx; } pr_debug("regulator_enable complete. mode=%s, corner=%d\n", (enable_mode == LDO_MODE) ? "LDO" : "BHS", ldo_vreg->corner + MIN_CORNER_OFFSET); ldo_vreg->mode = enable_mode; ldo_vreg->vreg_enabled = true; disable_cx: if (rc && ldo_vreg->vdd_cx) { rc = regulator_disable(ldo_vreg->vdd_cx); if (rc) pr_err("regulator_enable: vdd_cx: failed rc=%d\n", rc); } fail: mutex_unlock(&ldo_vreg->ldo_mutex); return rc; } static int msm_gfx_ldo_disable(struct regulator_dev *rdev) { int rc = 0; struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); mutex_lock(&ldo_vreg->ldo_mutex); if (ldo_vreg->vdd_cx) { rc = regulator_disable(ldo_vreg->vdd_cx); if (rc) { pr_err("regulator_disable: vdd_cx: failed rc=%d\n", rc); goto done; } rc = regulator_set_voltage(ldo_vreg->vdd_cx, 0, INT_MAX); if (rc) pr_err("failed to set voltage on CX rc=%d\n", rc); } /* No additional configuration for LDO/BHS - taken care by gsdc */ ldo_vreg->vreg_enabled = false; pr_debug("regulator_disabled complete\n"); done: mutex_unlock(&ldo_vreg->ldo_mutex); return rc; } static int switch_mode_to_ldo(struct msm_gfx_ldo *ldo_vreg, int new_uv) { u32 ctl = 0, status = 0, timeout = 50; ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); if (ldo_vreg->ops_type == CORNER) { /* enable CPR bypass mode for LDO */ ctl |= CPR_BYPASS_IN_LDO_MODE_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); } /* fake ack to GDSC */ ctl |= ACK_SW_OVR_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* set power-source as LDO */ ctl |= PWR_SRC_SEL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* Make sure BHS continues to power the rail */ ctl |= BHS_EN_FEW_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); ctl |= BHS_EN_REST_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* move BHS to SW control */ ctl |= BHS_UNDER_SW_CTL; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* set LDO under SW control */ ctl |= LDO_UNDER_SW_CTRL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* bypass LDO */ ctl |= LDO_BYPASS_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* power-on LDO */ ctl &= ~LDO_PDN_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* set the new LDO voltage */ ldo_update_voltage(ldo_vreg, new_uv); pr_debug("LDO voltage =%d uV\n", ldo_vreg->ldo_voltage_uv); /* make sure that the configuration is complete */ mb(); /* power down BHS */ ctl &= ~BHS_EN_FEW_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); ctl &= ~BHS_EN_REST_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* make sure that the configuration is complete */ mb(); /* wait for BHS to turn-off */ while (--timeout) { status = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_STATUS_REG); if (!(status & BHS_EN_REST_ACK_BIT)) break; udelay(10); } if (!timeout) pr_err("BHS_EN_RESET_ACK not clear PWRSWITCH_STATUS = 0x%x\n", status); /* remove LDO bypass */ ctl &= ~LDO_BYPASS_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* expose LDO to gdsc */ ctl &= ~ACK_SW_OVR_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); ctl &= ~LDO_UNDER_SW_CTRL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); dump_registers(ldo_vreg, "switch_mode_to_ldo"); return 0; } static int switch_mode_to_bhs(struct msm_gfx_ldo *ldo_vreg) { u32 ctl = 0, status = 0, timeout = 50; ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* fake ack to gdsc */ ctl |= ACK_SW_OVR_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* select BHS as power source */ ctl &= ~PWR_SRC_SEL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* LDO stays ON */ ctl &= ~LDO_PDN_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* Move LDO to SW control */ ctl |= LDO_UNDER_SW_CTRL_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* Power-up BHS */ ctl |= BHS_EN_FEW_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); ctl |= BHS_EN_REST_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* make sure that the configuration is complete */ mb(); /* wait for BHS to power-up */ while (--timeout) { status = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_STATUS_REG); if (status & BHS_EN_REST_ACK_BIT) break; udelay(10); } if (!timeout) pr_err("BHS_EN_RESET_ACK not set PWRSWITCH_STATUS = 0x%x\n", status); /* bypass LDO */ ctl |= LDO_BYPASS_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* pull-down LDO */ ctl |= LDO_PDN_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); /* Expose BHS to gdsc */ ctl &= ~ACK_SW_OVR_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); ctl &= ~BHS_UNDER_SW_CTL; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); if (ldo_vreg->ops_type == CORNER) { /* Enable CPR in BHS mode */ ctl &= ~CPR_BYPASS_IN_LDO_MODE_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); } /* make sure that all configuration is complete */ mb(); dump_registers(ldo_vreg, "switch_mode_to_bhs"); return 0; } static int msm_gfx_ldo_set_corner(struct regulator_dev *rdev, int corner, int corner_max, unsigned int *selector) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); int rc = 0, mem_acc_corner, new_uv; enum msm_ldo_supply_mode new_mode; enum direction dir = NO_CHANGE; corner -= MIN_CORNER_OFFSET; corner_max -= MIN_CORNER_OFFSET; mutex_lock(&ldo_vreg->ldo_mutex); if (corner == ldo_vreg->corner) goto done; pr_debug("set-voltage requested: old_mode=%s old_corner=%d new_corner=%d vreg_enabled=%d\n", ldo_vreg->mode == BHS_MODE ? "BHS" : "LDO", ldo_vreg->corner + MIN_CORNER_OFFSET, corner + MIN_CORNER_OFFSET, ldo_vreg->vreg_enabled); if (corner > ldo_vreg->corner) dir = UP; else if (corner < ldo_vreg->corner) dir = DOWN; if (ldo_vreg->mem_acc_vreg && dir == DOWN) { mem_acc_corner = ldo_vreg->mem_acc_corner_map[corner]; rc = regulator_set_voltage(ldo_vreg->mem_acc_vreg, mem_acc_corner, mem_acc_corner); } if (!ldo_vreg->vreg_enabled) { ldo_vreg->corner = corner; goto done; } if (ldo_vreg->vdd_cx) { rc = regulator_set_voltage(ldo_vreg->vdd_cx, ldo_vreg->vdd_cx_corner_map[corner], INT_MAX); if (rc) { pr_err("Unable to set CX for corner %d rc=%d\n", corner + MIN_CORNER_OFFSET, rc); goto done; } } new_mode = get_operating_mode(ldo_vreg, corner); if (new_mode == BHS_MODE) { if (ldo_vreg->mode == LDO_MODE) { rc = switch_mode_to_bhs(ldo_vreg); if (rc) pr_err("Switch to BHS corner=%d failed rc=%d\n", corner + MIN_CORNER_OFFSET, rc); } } else { /* new mode - LDO */ new_uv = ldo_vreg->open_loop_volt[ldo_vreg->corner]; if (ldo_vreg->mode == BHS_MODE) { rc = switch_mode_to_ldo(ldo_vreg, new_uv); if (rc) pr_err("Switch to LDO failed corner=%d rc=%d\n", corner + MIN_CORNER_OFFSET, rc); } else { rc = ldo_update_voltage(ldo_vreg, new_uv); if (rc) pr_err("Update voltage failed corner=%d rc=%d\n", corner + MIN_CORNER_OFFSET, rc); } } if (!rc) { pr_debug("set-voltage complete. old_mode=%s new_mode=%s old_corner=%d new_corner=%d\n", ldo_vreg->mode == BHS_MODE ? "BHS" : "LDO", new_mode == BHS_MODE ? "BHS" : "LDO", ldo_vreg->corner + MIN_CORNER_OFFSET, corner + MIN_CORNER_OFFSET); ldo_vreg->mode = new_mode; ldo_vreg->corner = corner; } done: if (!rc && ldo_vreg->mem_acc_vreg && dir == UP) { mem_acc_corner = ldo_vreg->mem_acc_corner_map[corner]; rc = regulator_set_voltage(ldo_vreg->mem_acc_vreg, mem_acc_corner, mem_acc_corner); } mutex_unlock(&ldo_vreg->ldo_mutex); return rc; } static int msm_gfx_ldo_get_corner(struct regulator_dev *rdev) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); return ldo_vreg->corner + MIN_CORNER_OFFSET; } static int msm_gfx_ldo_is_enabled(struct regulator_dev *rdev) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); return ldo_vreg->vreg_enabled; } static struct regulator_ops msm_gfx_ldo_corner_ops = { .enable = msm_gfx_ldo_corner_enable, .disable = msm_gfx_ldo_disable, .is_enabled = msm_gfx_ldo_is_enabled, .set_voltage = msm_gfx_ldo_set_corner, .get_voltage = msm_gfx_ldo_get_corner, }; static int msm_gfx_ldo_get_bypass(struct regulator_dev *rdev, bool *enable) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); *enable = ldo_vreg->mode; return 0; } static int msm_gfx_ldo_set_bypass(struct regulator_dev *rdev, bool mode) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); int rc = 0; mutex_lock(&ldo_vreg->ldo_mutex); if (ldo_vreg->mode == mode || !ldo_vreg->vreg_enabled) goto done; if (mode == LDO_MODE) rc = switch_mode_to_ldo(ldo_vreg, ldo_vreg->ldo_voltage_uv); else rc = switch_mode_to_bhs(ldo_vreg); if (rc) { pr_err("Failed to configure regulator in %s mode rc=%d\n", (mode == LDO_MODE) ? "LDO" : "BHS", rc); goto done; } pr_debug("regulator_set_bypass complete. mode=%s, voltage = %d uV\n", (mode == LDO_MODE) ? "LDO" : "BHS", (mode == LDO_MODE) ? ldo_vreg->ldo_voltage_uv : 0); ldo_vreg->mode = mode; done: mutex_unlock(&ldo_vreg->ldo_mutex); return rc; } static int msm_gfx_ldo_voltage_enable(struct regulator_dev *rdev) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); int rc = 0; enum msm_ldo_supply_mode enable_mode; mutex_lock(&ldo_vreg->ldo_mutex); pr_debug("regulator_enable requested. voltage=%d\n", ldo_vreg->ldo_voltage_uv); enable_mode = ldo_vreg->mode; if (enable_mode == LDO_MODE) rc = enable_ldo_mode(ldo_vreg, ldo_vreg->ldo_voltage_uv); else rc = enable_bhs_mode(ldo_vreg); if (rc) { pr_err("Failed to enable regulator in %s mode rc=%d\n", (enable_mode == LDO_MODE) ? "LDO" : "BHS", rc); goto fail; } pr_debug("regulator_enable complete. mode=%s, voltage = %d uV\n", (enable_mode == LDO_MODE) ? "LDO" : "BHS", (enable_mode == LDO_MODE) ? ldo_vreg->ldo_voltage_uv : 0); ldo_vreg->vreg_enabled = true; fail: mutex_unlock(&ldo_vreg->ldo_mutex); return rc; } static int msm_gfx_ldo_set_voltage(struct regulator_dev *rdev, int new_uv, int max_uv, unsigned int *selector) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); int rc = 0; mutex_lock(&ldo_vreg->ldo_mutex); if (new_uv == ldo_vreg->ldo_voltage_uv) goto done; if (!ldo_vreg->vreg_enabled || ldo_vreg->mode != LDO_MODE) { ldo_vreg->ldo_voltage_uv = new_uv; goto done; } /* update LDO voltage */ rc = ldo_update_voltage(ldo_vreg, new_uv); if (rc) pr_err("Update voltage failed for [%d, %d], rc=%d\n", new_uv, max_uv, rc); done: mutex_unlock(&ldo_vreg->ldo_mutex); return rc; } static int msm_gfx_ldo_get_voltage(struct regulator_dev *rdev) { struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev); return ldo_vreg->ldo_voltage_uv; } static struct regulator_ops msm_gfx_ldo_voltage_ops = { .enable = msm_gfx_ldo_voltage_enable, .disable = msm_gfx_ldo_disable, .is_enabled = msm_gfx_ldo_is_enabled, .set_voltage = msm_gfx_ldo_set_voltage, .get_voltage = msm_gfx_ldo_get_voltage, .set_bypass = msm_gfx_ldo_set_bypass, .get_bypass = msm_gfx_ldo_get_bypass, }; static int msm_gfx_ldo_adjust_init_voltage(struct msm_gfx_ldo *ldo_vreg) { int rc, len, size, i; u32 *volt_adjust; struct device_node *of_node = ldo_vreg->dev->of_node; char *prop_name = "qcom,ldo-init-voltage-adjustment"; if (!of_find_property(of_node, prop_name, &len)) { /* No initial voltage adjustment needed. */ return 0; } size = len / sizeof(u32); if (size != ldo_vreg->num_ldo_corners) { pr_err("%s length=%d is invalid: required:%d\n", prop_name, size, ldo_vreg->num_ldo_corners); return -EINVAL; } volt_adjust = devm_kcalloc(ldo_vreg->dev, size, sizeof(*volt_adjust), GFP_KERNEL); if (!volt_adjust) return -ENOMEM; rc = of_property_read_u32_array(of_node, prop_name, volt_adjust, size); if (rc) { pr_err("failed to read %s property rc=%d\n", prop_name, rc); return rc; } for (i = 0; i < ldo_vreg->num_corners; i++) { if (volt_adjust[i]) { ldo_vreg->open_loop_volt[i] += volt_adjust[i]; pr_info("adjusted the open-loop voltage[%d] %d -> %d\n", i + MIN_CORNER_OFFSET, ldo_vreg->open_loop_volt[i] - volt_adjust[i], ldo_vreg->open_loop_volt[i]); } } return 0; } static int msm_gfx_ldo_voltage_init(struct msm_gfx_ldo *ldo_vreg) { struct device_node *of_node = ldo_vreg->dev->of_node; int i, rc, len; u64 efuse_bits; len = ldo_vreg->num_ldo_corners; ldo_vreg->open_loop_volt = devm_kcalloc(ldo_vreg->dev, len, sizeof(*ldo_vreg->open_loop_volt), GFP_KERNEL); ldo_vreg->ceiling_volt = devm_kcalloc(ldo_vreg->dev, len, sizeof(*ldo_vreg->ceiling_volt), GFP_KERNEL); ldo_vreg->floor_volt = devm_kcalloc(ldo_vreg->dev, len, sizeof(*ldo_vreg->floor_volt), GFP_KERNEL); if (!ldo_vreg->open_loop_volt || !ldo_vreg->ceiling_volt || !ldo_vreg->floor_volt) return -ENOMEM; rc = of_property_read_u32_array(of_node, "qcom,ldo-voltage-ceiling", ldo_vreg->ceiling_volt, len); if (rc) { pr_err("Unable to read qcom,ldo-voltage-ceiling rc=%d\n", rc); return rc; } rc = of_property_read_u32_array(of_node, "qcom,ldo-voltage-floor", ldo_vreg->floor_volt, len); if (rc) { pr_err("Unable to read qcom,ldo-voltage-floor rc=%d\n", rc); return rc; } for (i = 0; i < ldo_vreg->num_ldo_corners; i++) { rc = read_fuse_param(ldo_vreg->efuse_base, ldo_vreg->init_volt_param[i], &efuse_bits); if (rc) { pr_err("Unable to read init-voltage rc=%d\n", rc); return rc; } ldo_vreg->open_loop_volt[i] = convert_open_loop_voltage_fuse( ldo_vreg->ref_volt[i], GFX_LDO_FUSE_STEP_VOLT, efuse_bits, GFX_LDO_FUSE_SIZE); pr_info("LDO corner %d: target-volt = %d uV\n", i + MIN_CORNER_OFFSET, ldo_vreg->open_loop_volt[i]); } rc = msm_gfx_ldo_adjust_init_voltage(ldo_vreg); if (rc) { pr_err("Unable to adjust init voltages rc=%d\n", rc); return rc; } for (i = 0; i < ldo_vreg->num_ldo_corners; i++) { if (ldo_vreg->open_loop_volt[i] > ldo_vreg->ceiling_volt[i]) { pr_info("Warning: initial voltage[%d] %d above ceiling %d\n", i + MIN_CORNER_OFFSET, ldo_vreg->open_loop_volt[i], ldo_vreg->ceiling_volt[i]); ldo_vreg->open_loop_volt[i] = ldo_vreg->ceiling_volt[i]; } else if (ldo_vreg->open_loop_volt[i] < ldo_vreg->floor_volt[i]) { pr_info("Warning: initial voltage[%d] %d below floor %d\n", i + MIN_CORNER_OFFSET, ldo_vreg->open_loop_volt[i], ldo_vreg->floor_volt[i]); ldo_vreg->open_loop_volt[i] = ldo_vreg->floor_volt[i]; } } efuse_bits = 0; rc = read_fuse_param(ldo_vreg->efuse_base, ldo_vreg->ldo_enable_param, &efuse_bits); if (rc) { pr_err("Unable to read ldo_enable_param rc=%d\n", rc); return rc; } ldo_vreg->ldo_fuse_enable = !!efuse_bits; pr_info("LDO-mode fuse %s by default\n", ldo_vreg->ldo_fuse_enable ? "enabled" : "disabled"); return rc; } static int msm_gfx_ldo_efuse_init(struct platform_device *pdev, struct msm_gfx_ldo *ldo_vreg) { struct resource *res; u32 len; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse_addr"); if (!res || !res->start) { pr_err("efuse_addr missing: res=%p\n", res); return -EINVAL; } ldo_vreg->efuse_addr = res->start; len = resource_size(res); ldo_vreg->efuse_base = devm_ioremap(&pdev->dev, ldo_vreg->efuse_addr, len); if (!ldo_vreg->efuse_base) { pr_err("Unable to map efuse_addr %pa\n", &ldo_vreg->efuse_addr); return -EINVAL; } return 0; } static int msm_gfx_ldo_mem_acc_init(struct msm_gfx_ldo *ldo_vreg) { int rc; u32 len, size; struct device_node *of_node = ldo_vreg->dev->of_node; if (of_find_property(ldo_vreg->dev->of_node, "mem-acc-supply", NULL)) { ldo_vreg->mem_acc_vreg = devm_regulator_get(ldo_vreg->dev, "mem-acc"); if (IS_ERR_OR_NULL(ldo_vreg->mem_acc_vreg)) { rc = PTR_RET(ldo_vreg->mem_acc_vreg); if (rc != -EPROBE_DEFER) pr_err("devm_regulator_get: mem-acc: rc=%d\n", rc); return rc; } } else { pr_debug("mem-acc-supply not specified\n"); return 0; } if (!of_find_property(of_node, "qcom,mem-acc-corner-map", &len)) { pr_err("qcom,mem-acc-corner-map missing\n"); return -EINVAL; } size = len / sizeof(u32); if (size != ldo_vreg->num_corners) { pr_err("qcom,mem-acc-corner-map length=%d is invalid: required:%u\n", size, ldo_vreg->num_corners); return -EINVAL; } ldo_vreg->mem_acc_corner_map = devm_kcalloc(ldo_vreg->dev, size, sizeof(*ldo_vreg->mem_acc_corner_map), GFP_KERNEL); if (!ldo_vreg->mem_acc_corner_map) return -ENOMEM; rc = of_property_read_u32_array(of_node, "qcom,mem-acc-corner-map", ldo_vreg->mem_acc_corner_map, size); if (rc) pr_err("Unable to read qcom,mem-acc-corner-map rc=%d\n", rc); return rc; } static int msm_gfx_ldo_init(struct platform_device *pdev, struct msm_gfx_ldo *ldo_vreg) { struct resource *res; u32 len, ctl; int i = 0; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ldo_addr"); if (!res || !res->start) { pr_err("ldo_addr missing: res=%p\n", res); return -EINVAL; } ldo_vreg->ldo_addr = res->start; len = resource_size(res); ldo_vreg->ldo_base = devm_ioremap(ldo_vreg->dev, ldo_vreg->ldo_addr, len); if (!ldo_vreg->ldo_base) { pr_err("Unable to map efuse_addr %pa\n", &ldo_vreg->ldo_addr); return -EINVAL; } /* HW initialization */ /* clear clamp_io, enable CPR in auto-bypass*/ ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); ctl &= ~LDO_CLAMP_IO_BIT; ctl |= EN_LDOAP_CTRL_CPR_BIT; writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG); i = 0; while (ldo_vreg->ldo_init_config && ldo_vreg->ldo_init_config[i].offset != LDO_MAX_OFFSET) { writel_relaxed(ldo_vreg->ldo_init_config[i].value, ldo_vreg->ldo_base + ldo_vreg->ldo_init_config[i].offset); i++; } /* complete the writes */ mb(); return 0; } static int ldo_parse_cx_parameters(struct msm_gfx_ldo *ldo_vreg) { struct device_node *of_node = ldo_vreg->dev->of_node; int rc, len, size; if (of_find_property(of_node, "vdd-cx-supply", NULL)) { ldo_vreg->vdd_cx = devm_regulator_get(ldo_vreg->dev, "vdd-cx"); if (IS_ERR_OR_NULL(ldo_vreg->vdd_cx)) { rc = PTR_RET(ldo_vreg->vdd_cx); if (rc != -EPROBE_DEFER) pr_err("devm_regulator_get: vdd-cx: rc=%d\n", rc); return rc; } } else { pr_debug("vdd-cx-supply not specified\n"); return 0; } if (!of_find_property(of_node, "qcom,vdd-cx-corner-map", &len)) { pr_err("qcom,vdd-cx-corner-map missing\n"); return -EINVAL; } size = len / sizeof(u32); if (size != ldo_vreg->num_corners) { pr_err("qcom,vdd-cx-corner-map length=%d is invalid: required:%u\n", size, ldo_vreg->num_corners); return -EINVAL; } ldo_vreg->vdd_cx_corner_map = devm_kcalloc(ldo_vreg->dev, size, sizeof(*ldo_vreg->vdd_cx_corner_map), GFP_KERNEL); if (!ldo_vreg->vdd_cx_corner_map) return -ENOMEM; rc = of_property_read_u32_array(of_node, "qcom,vdd-cx-corner-map", ldo_vreg->vdd_cx_corner_map, size); if (rc) pr_err("Unable to read qcom,vdd-cx-corner-map rc=%d\n", rc); return rc; } static int msm_gfx_ldo_parse_dt(struct msm_gfx_ldo *ldo_vreg) { struct device_node *of_node = ldo_vreg->dev->of_node; int rc, size, len; rc = of_property_read_u32(of_node, "qcom,num-corners", &ldo_vreg->num_corners); if (rc < 0) { pr_err("Unable to read qcom,num-corners rc=%d\n", rc); return rc; } rc = of_property_read_u32(of_node, "qcom,num-ldo-corners", &ldo_vreg->num_ldo_corners); if (rc) { pr_err("Unable to read qcom,num-ldo-corners rc=%d\n", rc); return rc; } rc = of_property_read_u32(of_node, "qcom,init-corner", &ldo_vreg->corner); if (rc) { pr_err("Unable to read qcom,init-corner rc=%d\n", rc); return rc; } if (!of_find_property(of_node, "qcom,ldo-enable-corner-map", &len)) { pr_err("qcom,ldo-enable-corner-map missing\n"); return -EINVAL; } size = len / sizeof(u32); if (size != ldo_vreg->num_corners) { pr_err("qcom,ldo-enable-corner-map length=%d is invalid: required:%u\n", size, ldo_vreg->num_corners); return -EINVAL; } ldo_vreg->ldo_corner_en_map = devm_kcalloc(ldo_vreg->dev, size, sizeof(*ldo_vreg->ldo_corner_en_map), GFP_KERNEL); if (!ldo_vreg->ldo_corner_en_map) return -ENOMEM; rc = of_property_read_u32_array(of_node, "qcom,ldo-enable-corner-map", ldo_vreg->ldo_corner_en_map, size); if (rc) { pr_err("Unable to read qcom,ldo-enable-corner-map rc=%d\n", rc); return rc; } rc = ldo_parse_cx_parameters(ldo_vreg); if (rc) { pr_err("Unable to parse CX parameters rc=%d\n", rc); return rc; } return 0; } static int msm_gfx_ldo_target_init(struct msm_gfx_ldo *ldo_vreg) { int i; /* MSM8953 */ ldo_vreg->init_volt_param = devm_kzalloc(ldo_vreg->dev, (MSM8953_LDO_FUSE_CORNERS * sizeof(struct fuse_param *)), GFP_KERNEL); if (!ldo_vreg->init_volt_param) return -ENOMEM; for (i = 0; i < MSM8953_LDO_FUSE_CORNERS; i++) ldo_vreg->init_volt_param[i] = msm8953_init_voltage_param[i]; ldo_vreg->ref_volt = msm8953_fuse_ref_volt; ldo_vreg->ldo_enable_param = msm8953_ldo_enable_param; return 0; } static int debugfs_ldo_mode_disable_set(void *data, u64 val) { struct msm_gfx_ldo *ldo_vreg = data; ldo_vreg->ldo_mode_disable = !!val; pr_debug("LDO-mode %s\n", ldo_vreg->ldo_mode_disable ? "disabled" : "enabled"); return 0; } static int debugfs_ldo_mode_disable_get(void *data, u64 *val) { struct msm_gfx_ldo *ldo_vreg = data; *val = ldo_vreg->ldo_mode_disable; return 0; } DEFINE_DEBUGFS_ATTRIBUTE(ldo_mode_disable_fops, debugfs_ldo_mode_disable_get, debugfs_ldo_mode_disable_set, "%llu\n"); static int debugfs_ldo_set_voltage(void *data, u64 val) { struct msm_gfx_ldo *ldo_vreg = data; int rc = 0, timeout = 50; u32 reg = 0, voltage = 0; mutex_lock(&ldo_vreg->ldo_mutex); if (ldo_vreg->mode == BHS_MODE || !ldo_vreg->vreg_enabled || val > MAX_LDO_VOLTAGE || val < MIN_LDO_VOLTAGE) { rc = -EINVAL; goto done; } voltage = GET_VREF((u32)val); reg = readl_relaxed(ldo_vreg->ldo_base + LDO_VREF_SET_REG); /* set the new voltage */ reg &= ~VREF_VAL_MASK; reg |= voltage & VREF_VAL_MASK; writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG); /* Initiate VREF update */ reg |= UPDATE_VREF_BIT; writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG); /* complete the writes */ mb(); reg &= ~UPDATE_VREF_BIT; writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG); /* complete the writes */ mb(); while (--timeout) { reg = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_STATUS_REG); if (reg & (LDO_VREF_SETTLED_BIT | LDO_READY_BIT)) break; udelay(10); } if (!timeout) { pr_err("LDO_VREF_SETTLED not set PWRSWITCH_STATUS = 0x%x\n", reg); rc = -EBUSY; } else { ldo_vreg->ldo_voltage_uv = val; pr_debug("LDO voltage set to %d uV\n", ldo_vreg->ldo_voltage_uv); } done: mutex_unlock(&ldo_vreg->ldo_mutex); return rc; } static int debugfs_ldo_get_voltage(void *data, u64 *val) { struct msm_gfx_ldo *ldo_vreg = data; int rc = 0; u32 reg; mutex_lock(&ldo_vreg->ldo_mutex); if (ldo_vreg->mode == BHS_MODE || !ldo_vreg->vreg_enabled) { rc = -EINVAL; goto done; } reg = readl_relaxed(ldo_vreg->ldo_base + LDO_VREF_SET_REG); reg &= VREF_VAL_MASK; *val = (reg * LDO_STEP_VOLATGE) + MIN_LDO_VOLTAGE; done: mutex_unlock(&ldo_vreg->ldo_mutex); return rc; } DEFINE_DEBUGFS_ATTRIBUTE(ldo_voltage_fops, debugfs_ldo_get_voltage, debugfs_ldo_set_voltage, "%llu\n"); static ssize_t msm_gfx_ldo_debug_info_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) { struct msm_gfx_ldo *ldo_vreg = file->private_data; char *debugfs_buf; ssize_t len, ret = 0; u32 i = 0, reg[MAX_LDO_REGS]; debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!debugfs_buf) return -ENOMEM; mutex_lock(&ldo_vreg->ldo_mutex); len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, "Regulator_enable = %d Regulator mode = %s Corner = %d LDO-voltage = %d uV\n", ldo_vreg->vreg_enabled, ldo_vreg->mode == BHS_MODE ? "BHS" : "LDO", ldo_vreg->corner + MIN_CORNER_OFFSET, ldo_vreg->ldo_voltage_uv); ret += len; for (i = 0; i < MAX_LDO_REGS; i++) { reg[i] = 0; reg[i] = readl_relaxed(ldo_vreg->ldo_base + (i * 4)); len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, "%s = 0x%x\n", register_str[i], reg[i]); ret += len; } mutex_unlock(&ldo_vreg->ldo_mutex); ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret); kfree(debugfs_buf); return ret; } static const struct file_operations msm_gfx_ldo_debug_info_fops = { .open = simple_open, .read = msm_gfx_ldo_debug_info_read, }; static void msm_gfx_ldo_debugfs_init(struct msm_gfx_ldo *ldo_vreg) { struct dentry *temp; ldo_vreg->debugfs = debugfs_create_dir("msm_gfx_ldo", NULL); if (!ldo_vreg->debugfs) { pr_err("Couldn't create debug dir\n"); return; } temp = debugfs_create_file("debug_info", 0444, ldo_vreg->debugfs, ldo_vreg, &msm_gfx_ldo_debug_info_fops); if (IS_ERR_OR_NULL(temp)) { pr_err("debug_info node creation failed\n"); return; } temp = debugfs_create_file("ldo_voltage", 0644, ldo_vreg->debugfs, ldo_vreg, &ldo_voltage_fops); if (IS_ERR_OR_NULL(temp)) { pr_err("ldo_voltage node creation failed\n"); return; } temp = debugfs_create_file("ldo_mode_disable", 0644, ldo_vreg->debugfs, ldo_vreg, &ldo_mode_disable_fops); if (IS_ERR_OR_NULL(temp)) { pr_err("ldo_mode_disable node creation failed\n"); return; } } static void msm_gfx_ldo_debugfs_remove(struct msm_gfx_ldo *ldo_vreg) { debugfs_remove_recursive(ldo_vreg->debugfs); } static int msm_gfx_ldo_corner_config_init(struct msm_gfx_ldo *ldo_vreg, struct platform_device *pdev) { int rc; rc = msm_gfx_ldo_target_init(ldo_vreg); if (rc) { pr_err("Unable to initialize target specific data rc=%d\n", rc); return rc; } rc = msm_gfx_ldo_parse_dt(ldo_vreg); if (rc) { pr_err("Unable to pasrse dt rc=%d\n", rc); return rc; } rc = msm_gfx_ldo_efuse_init(pdev, ldo_vreg); if (rc) { pr_err("efuse_init failed rc=%d\n", rc); return rc; } rc = msm_gfx_ldo_voltage_init(ldo_vreg); if (rc) { pr_err("ldo_voltage_init failed rc=%d\n", rc); return rc; } rc = msm_gfx_ldo_mem_acc_init(ldo_vreg); if (rc) { pr_err("Unable to initialize mem_acc rc=%d\n", rc); return rc; } return rc; }; /* Data corresponds to the SoC revision */ static const struct of_device_id msm_gfx_ldo_match_table[] = { { .compatible = "qcom,msm8953-gfx-ldo", .data = (void *)(uintptr_t)MSM8953_SOC_ID, }, { .compatible = "qcom,sdm660-gfx-ldo", .data = (void *)(uintptr_t)SDM660_SOC_ID, }, {} }; static int msm_gfx_ldo_probe(struct platform_device *pdev) { struct msm_gfx_ldo *ldo_vreg; struct regulator_config reg_config = {}; struct regulator_desc *rdesc; struct regulator_init_data *init_data = pdev->dev.platform_data; struct device *dev = &pdev->dev; const struct of_device_id *match; int soc_id, rc; match = of_match_device(msm_gfx_ldo_match_table, dev); if (!match) return -ENODEV; ldo_vreg = devm_kzalloc(dev, sizeof(*ldo_vreg), GFP_KERNEL); if (!ldo_vreg) return -ENOMEM; init_data = of_get_regulator_init_data(dev, dev->of_node, NULL); if (!init_data) { pr_err("regulator init data is missing\n"); return -EINVAL; } init_data->constraints.input_uV = init_data->constraints.max_uV; init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS; ldo_vreg->rdesc.name = init_data->constraints.name; if (ldo_vreg->rdesc.name == NULL) { dev_err(dev, "regulator-name missing\n"); return -EINVAL; } soc_id = (uintptr_t)match->data; ldo_vreg->dev = &pdev->dev; mutex_init(&ldo_vreg->ldo_mutex); platform_set_drvdata(pdev, ldo_vreg); switch (soc_id) { case MSM8953_SOC_ID: ldo_vreg->ldo_init_config = msm8953_ldo_config; ldo_vreg->ops_type = CORNER; rc = msm_gfx_ldo_corner_config_init(ldo_vreg, pdev); if (rc) { pr_err("ldo corner handling initialization failed, rc=%d\n", rc); return rc; } break; case SDM660_SOC_ID: ldo_vreg->ldo_init_config = sdm660_ldo_config; ldo_vreg->ops_type = VOLTAGE; init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_BYPASS; break; default: pr_err("invalid SOC ID = %d\n", soc_id); return -EINVAL; } /* HW initialization */ rc = msm_gfx_ldo_init(pdev, ldo_vreg); if (rc) { pr_err("ldo_init failed rc=%d\n", rc); return rc; } rdesc = &ldo_vreg->rdesc; rdesc->owner = THIS_MODULE; rdesc->type = REGULATOR_VOLTAGE; if (ldo_vreg->ops_type == CORNER) rdesc->ops = &msm_gfx_ldo_corner_ops; else rdesc->ops = &msm_gfx_ldo_voltage_ops; reg_config.dev = &pdev->dev; reg_config.init_data = init_data; reg_config.driver_data = ldo_vreg; reg_config.of_node = pdev->dev.of_node; ldo_vreg->rdev = regulator_register(rdesc, ®_config); if (IS_ERR(ldo_vreg->rdev)) { rc = PTR_ERR(ldo_vreg->rdev); pr_err("regulator_register failed: rc=%d\n", rc); return rc; } msm_gfx_ldo_debugfs_init(ldo_vreg); return 0; } static int msm_gfx_ldo_remove(struct platform_device *pdev) { struct msm_gfx_ldo *ldo_vreg = platform_get_drvdata(pdev); regulator_unregister(ldo_vreg->rdev); msm_gfx_ldo_debugfs_remove(ldo_vreg); return 0; } static struct platform_driver msm_gfx_ldo_driver = { .driver = { .name = "qcom,msm-gfx-ldo", .of_match_table = msm_gfx_ldo_match_table, }, .probe = msm_gfx_ldo_probe, .remove = msm_gfx_ldo_remove, }; static int msm_gfx_ldo_platform_init(void) { return platform_driver_register(&msm_gfx_ldo_driver); } arch_initcall(msm_gfx_ldo_platform_init); static void msm_gfx_ldo_platform_exit(void) { platform_driver_unregister(&msm_gfx_ldo_driver); } module_exit(msm_gfx_ldo_platform_exit); MODULE_DESCRIPTION("MSM GFX LDO driver"); MODULE_LICENSE("GPL v2");