diff --git a/drivers/clk/qcom/gdsc-regulator.c b/drivers/clk/qcom/gdsc-regulator.c index 158849549a18..7ec16810198a 100644 --- a/drivers/clk/qcom/gdsc-regulator.c +++ b/drivers/clk/qcom/gdsc-regulator.c @@ -19,6 +19,9 @@ #include #include #include +#include +#include +#include #include "../../regulator/internal.h" #include "gdsc-debug.h" @@ -45,6 +48,8 @@ /* Timeout Delay */ #define TIMEOUT_US 100 +#define MBOX_TOUT_MS 100 + struct collapse_vote { struct regmap *regmap; u32 vote_bit; @@ -62,6 +67,8 @@ struct gdsc { struct regmap *acd_misc_reset; struct collapse_vote collapse_vote; struct clk **clocks; + struct mbox_client mbox_client; + struct mbox_chan *mbox; struct reset_control **reset_clocks; bool toggle_logic; bool retain_ff_enable; @@ -78,6 +85,8 @@ struct gdsc { int root_clk_idx; u32 gds_timeout; bool skip_disable_before_enable; + bool skip_disable; + bool bypass_skip_disable; }; enum gdscr_status { @@ -163,6 +172,13 @@ static int gdsc_is_enabled(struct regulator_dev *rdev) { struct gdsc *sc = rdev_get_drvdata(rdev); + /* + * Return the logical GDSC enable state given that it will only be + * physically disabled by AOP during system sleep. + */ + if (sc->skip_disable) + return sc->is_gdsc_enabled; + if (!sc->toggle_logic) return !sc->resets_asserted; @@ -172,6 +188,35 @@ static int gdsc_is_enabled(struct regulator_dev *rdev) return sc->is_gdsc_enabled; } +#define MAX_LEN 96 + +static int gdsc_qmp_enable(struct gdsc *sc) +{ + char buf[MAX_LEN] = "{class: clock, res: gpu_noc_wa}"; + struct qmp_pkt pkt; + uint32_t regval; + int ret; + + regmap_read(sc->regmap, REG_OFFSET, ®val); + if (!(regval & SW_COLLAPSE_MASK)) { + /* + * Do not enable via a QMP request if the GDSC is already + * enabled by software. + */ + return 0; + } + + pkt.size = MAX_LEN; + pkt.data = buf; + + ret = mbox_send_message(sc->mbox, &pkt); + if (ret < 0) + dev_err(&sc->rdev->dev, "qmp message send failed, ret=%d\n", + ret); + + return ret; +} + static int gdsc_enable(struct regulator_dev *rdev) { struct gdsc *sc = rdev_get_drvdata(rdev); @@ -260,7 +305,11 @@ static int gdsc_enable(struct regulator_dev *rdev) } /* Enable gdsc */ - if (sc->collapse_vote.regmap) { + if (sc->mbox) { + ret = gdsc_qmp_enable(sc); + if (ret < 0) + return ret; + } else if (sc->collapse_vote.regmap) { regmap_update_bits(sc->collapse_vote.regmap, REG_OFFSET, BIT(sc->collapse_vote.vote_bit), ~BIT(sc->collapse_vote.vote_bit)); @@ -380,7 +429,12 @@ static int gdsc_disable(struct regulator_dev *rdev) /* Delay to account for staggered memory powerdown. */ udelay(1); - if (sc->toggle_logic) { + if (sc->skip_disable && !sc->bypass_skip_disable) { + /* + * Don't change the GDSCR register state on disable. AOP will + * handle this during system sleep. + */ + } else if (sc->toggle_logic) { if (sc->sw_reset) { if (sc->acd_misc_reset) regmap_update_bits(sc->acd_misc_reset, REG_OFFSET, @@ -466,6 +520,12 @@ static unsigned int gdsc_get_mode(struct regulator_dev *rdev) { struct gdsc *sc = rdev_get_drvdata(rdev); + if (sc->skip_disable) { + if (sc->bypass_skip_disable) + return REGULATOR_MODE_IDLE; + return REGULATOR_MODE_NORMAL; + } + return sc->is_gdsc_hw_ctrl_mode ? REGULATOR_MODE_FAST : REGULATOR_MODE_NORMAL; } @@ -477,6 +537,22 @@ static int gdsc_set_mode(struct regulator_dev *rdev, unsigned int mode) uint32_t regval; int ret = 0; + if (sc->skip_disable) { + switch (mode) { + case REGULATOR_MODE_IDLE: + sc->bypass_skip_disable = true; + break; + case REGULATOR_MODE_NORMAL: + sc->bypass_skip_disable = false; + break; + default: + ret = -EINVAL; + break; + } + + return ret; + } + if (rdev->supply) { parent_rdev = rdev->supply->rdev; /* @@ -705,6 +781,20 @@ static int gdsc_parse_dt_data(struct gdsc *sc, struct device *dev, } } + sc->skip_disable = of_property_read_bool(dev->of_node, + "qcom,skip-disable"); + if (sc->skip_disable) { + /* + * If the disable skipping feature is allowed, then use mode + * control to enable and disable the feature at runtime instead + * of using it to enable and disable hardware triggering. + */ + (*init_data)->constraints.valid_ops_mask |= + REGULATOR_CHANGE_MODE; + (*init_data)->constraints.valid_modes_mask = + REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE; + } + sc->toggle_logic = !of_property_read_bool(dev->of_node, "qcom,skip-logic-collapse"); if (!sc->toggle_logic) { @@ -840,6 +930,27 @@ static int gdsc_probe(struct platform_device *pdev) regmap_read(sc->regmap, REG_OFFSET, ®val); regval &= ~(HW_CONTROL_MASK | SW_OVERRIDE_MASK); + + if (of_find_property(pdev->dev.of_node, "mboxes", NULL)) { + sc->mbox_client.dev = &pdev->dev; + sc->mbox_client.tx_block = true; + sc->mbox_client.tx_tout = MBOX_TOUT_MS; + sc->mbox_client.knows_txdone = false; + + sc->mbox = mbox_request_channel(&sc->mbox_client, 0); + if (IS_ERR(sc->mbox)) { + ret = PTR_ERR(sc->mbox); + if (ret == -EAGAIN) + ret = -EPROBE_DEFER; + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "mailbox channel request failed, ret=%d\n", + ret); + sc->mbox = NULL; + goto err; + } + } + + if (!of_property_read_u32(pdev->dev.of_node, "qcom,clk-dis-wait-val", &clk_dis_wait_val)) { clk_dis_wait_val = clk_dis_wait_val << CLK_DIS_WAIT_SHIFT; @@ -859,7 +970,7 @@ static int gdsc_probe(struct platform_device *pdev) if (ret) { dev_err(dev, "%s enable timed out: 0x%x\n", sc->rdesc.name, regval); - return ret; + goto err; } } @@ -867,14 +978,14 @@ static int gdsc_probe(struct platform_device *pdev) if (ret) { dev_err(dev, "%s failed to get initial enable state, ret=%d\n", sc->rdesc.name, ret); - return ret; + goto err; } ret = gdsc_init_hw_ctrl_mode(sc); if (ret) { dev_err(dev, "%s failed to get initial hw_ctrl state, ret=%d\n", sc->rdesc.name, ret); - return ret; + goto err; } sc->rdesc.id = atomic_inc_return(&gdsc_count); @@ -893,16 +1004,23 @@ static int gdsc_probe(struct platform_device *pdev) ret = PTR_ERR(sc->rdev); dev_err(dev, "regulator_register(\"%s\") failed, ret=%d\n", sc->rdesc.name, ret); - return ret; + goto err; } ret = devm_regulator_proxy_consumer_register(dev, dev->of_node); - if (ret) + if (ret) { dev_err(dev, "failed to register proxy consumer, ret=%d\n", ret); + goto err; + } platform_set_drvdata(pdev, sc); + return 0; +err: + if (sc->mbox) + mbox_free_channel(sc->mbox); + return ret; }