diff options
Diffstat (limited to 'drivers/clk/msm/gdsc.c')
-rw-r--r-- | drivers/clk/msm/gdsc.c | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/drivers/clk/msm/gdsc.c b/drivers/clk/msm/gdsc.c new file mode 100644 index 000000000000..cdaba72532d4 --- /dev/null +++ b/drivers/clk/msm/gdsc.c @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/reset.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk.h> + +#define PWR_ON_MASK BIT(31) +#define EN_REST_WAIT_MASK (0xF << 20) +#define EN_FEW_WAIT_MASK (0xF << 16) +#define CLK_DIS_WAIT_MASK (0xF << 12) +#define SW_OVERRIDE_MASK BIT(2) +#define HW_CONTROL_MASK BIT(1) +#define SW_COLLAPSE_MASK BIT(0) +#define GMEM_CLAMP_IO_MASK BIT(0) +#define GMEM_RESET_MASK BIT(4) +#define BCR_BLK_ARES_BIT BIT(0) + +/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */ +#define EN_REST_WAIT_VAL (0x2 << 20) +#define EN_FEW_WAIT_VAL (0x8 << 16) +#define CLK_DIS_WAIT_VAL (0x2 << 12) + +#define TIMEOUT_US 100 + +struct gdsc { + struct regulator_dev *rdev; + struct regulator_desc rdesc; + void __iomem *gdscr; + struct clk **clocks; + struct reset_control **reset_clocks; + int clock_count; + int reset_count; + bool toggle_mem; + bool toggle_periph; + bool toggle_logic; + bool resets_asserted; + bool root_en; + bool force_root_en; + int root_clk_idx; + bool no_status_check_on_disable; + bool is_gdsc_enabled; + bool allow_clear; + bool reset_aon; + void __iomem *domain_addr; + void __iomem *hw_ctrl_addr; + void __iomem *sw_reset_addr; + u32 gds_timeout; +}; + +enum gdscr_status { + ENABLED, + DISABLED, +}; + +static DEFINE_MUTEX(gdsc_seq_lock); + +void gdsc_allow_clear_retention(struct regulator *regulator) +{ + struct gdsc *sc = regulator_get_drvdata(regulator); + + if (sc) + sc->allow_clear = true; +} + +static int poll_gdsc_status(struct gdsc *sc, enum gdscr_status status) +{ + void __iomem *gdscr; + int count = sc->gds_timeout; + u32 val; + + if (sc->hw_ctrl_addr) + gdscr = sc->hw_ctrl_addr; + else + gdscr = sc->gdscr; + + for (; count > 0; count--) { + val = readl_relaxed(gdscr); + val &= PWR_ON_MASK; + switch (status) { + case ENABLED: + if (val) + return 0; + break; + case DISABLED: + if (!val) + return 0; + break; + } + /* + * There is no guarantee about the delay needed for the enable + * bit in the GDSCR to be set or reset after the GDSC state + * changes. Hence, keep on checking for a reasonable number + * of times until the bit is set with the least possible delay + * between succeessive tries. + */ + udelay(1); + } + return -ETIMEDOUT; +} + +static int gdsc_is_enabled(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + + if (!sc->toggle_logic) + return !sc->resets_asserted; + + regval = readl_relaxed(sc->gdscr); + if (regval & PWR_ON_MASK) { + /* + * The GDSC might be turned on due to TZ/HYP vote on the + * votable GDS registers. Check the SW_COLLAPSE_MASK to + * determine if HLOS has voted for it. + */ + if (!(regval & SW_COLLAPSE_MASK)) + return true; + } + return false; +} + +static int gdsc_enable(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval, hw_ctrl_regval = 0x0; + int i, ret = 0; + + mutex_lock(&gdsc_seq_lock); + + if (sc->root_en || sc->force_root_en) + clk_prepare_enable(sc->clocks[sc->root_clk_idx]); + + if (sc->toggle_logic) { + if (sc->sw_reset_addr) { + regval = readl_relaxed(sc->sw_reset_addr); + regval |= BCR_BLK_ARES_BIT; + writel_relaxed(regval, sc->sw_reset_addr); + /* + * BLK_ARES should be kept asserted for 1us before + * being de-asserted. + */ + wmb(); + udelay(1); + + regval &= ~BCR_BLK_ARES_BIT; + writel_relaxed(regval, sc->sw_reset_addr); + + /* Make sure de-assert goes through before continuing */ + wmb(); + } + + if (sc->domain_addr) { + if (sc->reset_aon) { + regval = readl_relaxed(sc->domain_addr); + regval |= GMEM_RESET_MASK; + writel_relaxed(regval, sc->domain_addr); + /* + * Keep reset asserted for at-least 1us before + * continuing. + */ + wmb(); + udelay(1); + + regval &= ~GMEM_RESET_MASK; + writel_relaxed(regval, sc->domain_addr); + /* + * Make sure GMEM_RESET is de-asserted before + * continuing. + */ + wmb(); + } + + regval = readl_relaxed(sc->domain_addr); + regval &= ~GMEM_CLAMP_IO_MASK; + writel_relaxed(regval, sc->domain_addr); + /* + * Make sure CLAMP_IO is de-asserted before continuing. + */ + wmb(); + } + + regval = readl_relaxed(sc->gdscr); + if (regval & HW_CONTROL_MASK) { + dev_warn(&rdev->dev, "Invalid enable while %s is under HW control\n", + sc->rdesc.name); + mutex_unlock(&gdsc_seq_lock); + return -EBUSY; + } + + regval &= ~SW_COLLAPSE_MASK; + writel_relaxed(regval, sc->gdscr); + + /* Wait for 8 XO cycles before polling the status bit. */ + mb(); + udelay(1); + + ret = poll_gdsc_status(sc, ENABLED); + if (ret) { + regval = readl_relaxed(sc->gdscr); + if (sc->hw_ctrl_addr) { + hw_ctrl_regval = + readl_relaxed(sc->hw_ctrl_addr); + dev_warn(&rdev->dev, "%s state (after %d us timeout): 0x%x, GDS_HW_CTRL: 0x%x. Re-polling.\n", + sc->rdesc.name, sc->gds_timeout, + regval, hw_ctrl_regval); + + ret = poll_gdsc_status(sc, ENABLED); + if (ret) { + dev_err(&rdev->dev, "%s final state (after additional %d us timeout): 0x%x, GDS_HW_CTRL: 0x%x\n", + sc->rdesc.name, sc->gds_timeout, + readl_relaxed(sc->gdscr), + readl_relaxed(sc->hw_ctrl_addr)); + + mutex_unlock(&gdsc_seq_lock); + return ret; + } + } else { + dev_err(&rdev->dev, "%s enable timed out: 0x%x\n", + sc->rdesc.name, + regval); + udelay(sc->gds_timeout); + regval = readl_relaxed(sc->gdscr); + dev_err(&rdev->dev, "%s final state: 0x%x (%d us after timeout)\n", + sc->rdesc.name, regval, + sc->gds_timeout); + mutex_unlock(&gdsc_seq_lock); + return ret; + } + } + } else { + for (i = 0; i < sc->reset_count; i++) + reset_control_deassert(sc->reset_clocks[i]); + sc->resets_asserted = false; + } + + for (i = 0; i < sc->clock_count; i++) { + if (unlikely(i == sc->root_clk_idx)) + continue; + if (sc->toggle_mem) + clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_MEM); + if (sc->toggle_periph) + clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_PERIPH); + } + + /* + * If clocks to this power domain were already on, they will take an + * additional 4 clock cycles to re-enable after the rail is enabled. + * Delay to account for this. A delay is also needed to ensure clocks + * are not enabled within 400ns of enabling power to the memories. + */ + udelay(1); + + /* Delay to account for staggered memory powerup. */ + udelay(1); + + if (sc->force_root_en) + clk_disable_unprepare(sc->clocks[sc->root_clk_idx]); + sc->is_gdsc_enabled = true; + + mutex_unlock(&gdsc_seq_lock); + + return ret; +} + +static int gdsc_disable(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + int i, ret = 0; + + mutex_lock(&gdsc_seq_lock); + + if (sc->force_root_en) + clk_prepare_enable(sc->clocks[sc->root_clk_idx]); + + for (i = sc->clock_count-1; i >= 0; i--) { + if (unlikely(i == sc->root_clk_idx)) + continue; + if (sc->toggle_mem && sc->allow_clear) + clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_MEM); + if (sc->toggle_periph && sc->allow_clear) + clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_PERIPH); + } + + /* Delay to account for staggered memory powerdown. */ + udelay(1); + + if (sc->toggle_logic) { + regval = readl_relaxed(sc->gdscr); + if (regval & HW_CONTROL_MASK) { + dev_warn(&rdev->dev, "Invalid disable while %s is under HW control\n", + sc->rdesc.name); + mutex_unlock(&gdsc_seq_lock); + return -EBUSY; + } + + regval |= SW_COLLAPSE_MASK; + writel_relaxed(regval, sc->gdscr); + /* Wait for 8 XO cycles before polling the status bit. */ + mb(); + udelay(1); + + if (sc->no_status_check_on_disable) { + /* + * Add a short delay here to ensure that gdsc_enable + * right after it was disabled does not put it in a + * wierd state. + */ + udelay(TIMEOUT_US); + } else { + ret = poll_gdsc_status(sc, DISABLED); + if (ret) + dev_err(&rdev->dev, "%s disable timed out: 0x%x\n", + sc->rdesc.name, regval); + } + + if (sc->domain_addr) { + regval = readl_relaxed(sc->domain_addr); + regval |= GMEM_CLAMP_IO_MASK; + writel_relaxed(regval, sc->domain_addr); + /* Make sure CLAMP_IO is asserted before continuing. */ + wmb(); + } + } else { + for (i = sc->reset_count-1; i >= 0; i--) + reset_control_assert(sc->reset_clocks[i]); + sc->resets_asserted = true; + } + + /* + * Check if gdsc_enable was called for this GDSC. If not, the root + * clock will not have been enabled prior to this. + */ + if ((sc->is_gdsc_enabled && sc->root_en) || sc->force_root_en) + clk_disable_unprepare(sc->clocks[sc->root_clk_idx]); + sc->is_gdsc_enabled = false; + + mutex_unlock(&gdsc_seq_lock); + + return ret; +} + +static unsigned int gdsc_get_mode(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + + mutex_lock(&gdsc_seq_lock); + regval = readl_relaxed(sc->gdscr); + mutex_unlock(&gdsc_seq_lock); + if (regval & HW_CONTROL_MASK) + return REGULATOR_MODE_FAST; + return REGULATOR_MODE_NORMAL; +} + +static int gdsc_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + int ret = 0; + + mutex_lock(&gdsc_seq_lock); + + regval = readl_relaxed(sc->gdscr); + + /* + * HW control can only be enable/disabled when SW_COLLAPSE + * indicates on. + */ + if (regval & SW_COLLAPSE_MASK) { + dev_err(&rdev->dev, "can't enable hw collapse now\n"); + mutex_unlock(&gdsc_seq_lock); + return -EBUSY; + } + + switch (mode) { + case REGULATOR_MODE_FAST: + /* Turn on HW trigger mode */ + regval |= HW_CONTROL_MASK; + writel_relaxed(regval, sc->gdscr); + /* + * There may be a race with internal HW trigger signal, + * that will result in GDSC going through a power down and + * up cycle. In case HW trigger signal is controlled by + * firmware that also poll same status bits as we do, FW + * might read an 'on' status before the GDSC can finish + * power cycle. We wait 1us before returning to ensure + * FW can't immediately poll the status bit. + */ + mb(); + udelay(1); + break; + + case REGULATOR_MODE_NORMAL: + /* Turn off HW trigger mode */ + regval &= ~HW_CONTROL_MASK; + writel_relaxed(regval, sc->gdscr); + /* + * There may be a race with internal HW trigger signal, + * that will result in GDSC going through a power down and + * up cycle. If we poll too early, status bit will + * indicate 'on' before the GDSC can finish the power cycle. + * Account for this case by waiting 1us before polling. + */ + mb(); + udelay(1); + + ret = poll_gdsc_status(sc, ENABLED); + if (ret) + dev_err(&rdev->dev, "%s set_mode timed out: 0x%x\n", + sc->rdesc.name, regval); + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&gdsc_seq_lock); + + return ret; +} + +static struct regulator_ops gdsc_ops = { + .is_enabled = gdsc_is_enabled, + .enable = gdsc_enable, + .disable = gdsc_disable, + .set_mode = gdsc_set_mode, + .get_mode = gdsc_get_mode, +}; + +static int gdsc_probe(struct platform_device *pdev) +{ + static atomic_t gdsc_count = ATOMIC_INIT(-1); + struct regulator_config reg_config = {}; + struct regulator_init_data *init_data; + struct resource *res; + struct gdsc *sc; + uint32_t regval, clk_dis_wait_val = CLK_DIS_WAIT_VAL; + bool retain_mem, retain_periph, support_hw_trigger; + int i, ret; + u32 timeout; + + sc = devm_kzalloc(&pdev->dev, sizeof(struct gdsc), GFP_KERNEL); + if (sc == NULL) + return -ENOMEM; + + init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node, + &sc->rdesc); + if (init_data == NULL) + return -ENOMEM; + + if (of_get_property(pdev->dev.of_node, "parent-supply", NULL)) + init_data->supply_regulator = "parent"; + + ret = of_property_read_string(pdev->dev.of_node, "regulator-name", + &sc->rdesc.name); + if (ret) + return ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) + return -EINVAL; + sc->gdscr = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (sc->gdscr == NULL) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "domain_addr"); + if (res) { + sc->domain_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (sc->domain_addr == NULL) + return -ENOMEM; + } + + sc->reset_aon = of_property_read_bool(pdev->dev.of_node, + "qcom,reset-aon-logic"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "sw_reset"); + if (res) { + sc->sw_reset_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (sc->sw_reset_addr == NULL) + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "hw_ctrl_addr"); + if (res) { + sc->hw_ctrl_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (sc->hw_ctrl_addr == NULL) + return -ENOMEM; + } + + sc->gds_timeout = TIMEOUT_US; + ret = of_property_read_u32(pdev->dev.of_node, "qcom,gds-timeout", + &timeout); + if (!ret) + sc->gds_timeout = timeout; + + sc->clock_count = of_property_count_strings(pdev->dev.of_node, + "clock-names"); + if (sc->clock_count == -EINVAL) { + sc->clock_count = 0; + } else if (IS_ERR_VALUE(sc->clock_count)) { + dev_err(&pdev->dev, "Failed to get clock names\n"); + return -EINVAL; + } + + sc->clocks = devm_kzalloc(&pdev->dev, + sizeof(struct clk *) * sc->clock_count, GFP_KERNEL); + if (!sc->clocks) + return -ENOMEM; + + sc->root_clk_idx = -1; + + sc->root_en = of_property_read_bool(pdev->dev.of_node, + "qcom,enable-root-clk"); + sc->force_root_en = of_property_read_bool(pdev->dev.of_node, + "qcom,force-enable-root-clk"); + for (i = 0; i < sc->clock_count; i++) { + const char *clock_name; + of_property_read_string_index(pdev->dev.of_node, "clock-names", + i, &clock_name); + sc->clocks[i] = devm_clk_get(&pdev->dev, clock_name); + if (IS_ERR(sc->clocks[i])) { + int rc = PTR_ERR(sc->clocks[i]); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get %s\n", + clock_name); + return rc; + } + + if (!strcmp(clock_name, "core_root_clk")) + sc->root_clk_idx = i; + } + + if ((sc->root_en || sc->force_root_en) && (sc->root_clk_idx == -1)) { + dev_err(&pdev->dev, "Failed to get root clock name\n"); + return -EINVAL; + } + + sc->rdesc.id = atomic_inc_return(&gdsc_count); + sc->rdesc.ops = &gdsc_ops; + sc->rdesc.type = REGULATOR_VOLTAGE; + sc->rdesc.owner = THIS_MODULE; + platform_set_drvdata(pdev, sc); + + /* + * Disable HW trigger: collapse/restore occur based on registers writes. + * Disable SW override: Use hardware state-machine for sequencing. + */ + regval = readl_relaxed(sc->gdscr); + regval &= ~(HW_CONTROL_MASK | SW_OVERRIDE_MASK); + + 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 << 12; + + /* Configure wait time between states. */ + regval &= ~(EN_REST_WAIT_MASK | EN_FEW_WAIT_MASK | CLK_DIS_WAIT_MASK); + regval |= EN_REST_WAIT_VAL | EN_FEW_WAIT_VAL | clk_dis_wait_val; + writel_relaxed(regval, sc->gdscr); + + sc->no_status_check_on_disable = + of_property_read_bool(pdev->dev.of_node, + "qcom,no-status-check-on-disable"); + retain_mem = of_property_read_bool(pdev->dev.of_node, + "qcom,retain-mem"); + sc->toggle_mem = !retain_mem; + retain_periph = of_property_read_bool(pdev->dev.of_node, + "qcom,retain-periph"); + sc->toggle_periph = !retain_periph; + sc->toggle_logic = !of_property_read_bool(pdev->dev.of_node, + "qcom,skip-logic-collapse"); + support_hw_trigger = of_property_read_bool(pdev->dev.of_node, + "qcom,support-hw-trigger"); + if (support_hw_trigger) { + init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_MODE; + init_data->constraints.valid_modes_mask |= + REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST; + } + + if (!sc->toggle_logic) { + sc->reset_count = of_property_count_strings(pdev->dev.of_node, + "reset-names"); + if (sc->reset_count == -EINVAL) { + sc->reset_count = 0; + } else if (IS_ERR_VALUE(sc->reset_count)) { + dev_err(&pdev->dev, "Failed to get reset reset names\n"); + return -EINVAL; + } + + sc->reset_clocks = devm_kzalloc(&pdev->dev, + sizeof(struct reset_control *) * + sc->reset_count, + GFP_KERNEL); + if (!sc->reset_clocks) + return -ENOMEM; + + for (i = 0; i < sc->reset_count; i++) { + const char *reset_name; + + of_property_read_string_index(pdev->dev.of_node, + "reset-names", i, &reset_name); + sc->reset_clocks[i] = devm_reset_control_get(&pdev->dev, + reset_name); + if (IS_ERR(sc->reset_clocks[i])) { + int rc = PTR_ERR(sc->reset_clocks[i]); + + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get %s\n", + reset_name); + return rc; + } + } + + regval &= ~SW_COLLAPSE_MASK; + writel_relaxed(regval, sc->gdscr); + + ret = poll_gdsc_status(sc, ENABLED); + if (ret) { + dev_err(&pdev->dev, "%s enable timed out: 0x%x\n", + sc->rdesc.name, regval); + return ret; + } + } + + sc->allow_clear = of_property_read_bool(pdev->dev.of_node, + "qcom,disallow-clear"); + sc->allow_clear = !sc->allow_clear; + + for (i = 0; i < sc->clock_count; i++) { + if (retain_mem || (regval & PWR_ON_MASK) || !sc->allow_clear) + clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_MEM); + else + clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_MEM); + + if (retain_periph || (regval & PWR_ON_MASK) || !sc->allow_clear) + clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_PERIPH); + else + clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_PERIPH); + } + + reg_config.dev = &pdev->dev; + reg_config.init_data = init_data; + reg_config.driver_data = sc; + reg_config.of_node = pdev->dev.of_node; + sc->rdev = regulator_register(&sc->rdesc, ®_config); + if (IS_ERR(sc->rdev)) { + dev_err(&pdev->dev, "regulator_register(\"%s\") failed.\n", + sc->rdesc.name); + return PTR_ERR(sc->rdev); + } + + return 0; +} + +static int gdsc_remove(struct platform_device *pdev) +{ + struct gdsc *sc = platform_get_drvdata(pdev); + regulator_unregister(sc->rdev); + return 0; +} + +static struct of_device_id gdsc_match_table[] = { + { .compatible = "qcom,gdsc" }, + {} +}; + +static struct platform_driver gdsc_driver = { + .probe = gdsc_probe, + .remove = gdsc_remove, + .driver = { + .name = "gdsc", + .of_match_table = gdsc_match_table, + .owner = THIS_MODULE, + }, +}; + +static int __init gdsc_init(void) +{ + return platform_driver_register(&gdsc_driver); +} +subsys_initcall(gdsc_init); + +static void __exit gdsc_exit(void) +{ + platform_driver_unregister(&gdsc_driver); +} +module_exit(gdsc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM8974 GDSC power rail regulator driver"); |