// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021-2024, Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. */ #include <linux/clk.h> #include <linux/pm_runtime.h> #include "dp_power.h" #include "dp_catalog.h" #include "dp_debug.h" #include "dp_pll.h" #define DP_CLIENT_NAME_SIZE 20 #define XO_CLK_KHZ 19200 struct dp_power_private { struct dp_parser *parser; struct dp_pll *pll; struct platform_device *pdev; struct clk *pixel_clk_rcg; struct clk *pixel_parent; struct clk *pixel1_clk_rcg; struct clk *xo_clk; struct clk *link_clk_rcg; struct clk *link_parent; struct dp_power dp_power; bool core_clks_on; bool link_clks_on; bool strm0_clks_on; bool strm1_clks_on; bool strm0_clks_parked; bool strm1_clks_parked; bool link_clks_parked; }; static int dp_power_regulator_init(struct dp_power_private *power) { int rc = 0, i = 0, j = 0; struct platform_device *pdev; struct dp_parser *parser; parser = power->parser; pdev = power->pdev; for (i = DP_CORE_PM; !rc && (i < DP_MAX_PM); i++) { rc = msm_dss_get_vreg(&pdev->dev, parser->mp[i].vreg_config, parser->mp[i].num_vreg, 1); if (rc) { DP_ERR("failed to init vregs for %s\n", dp_parser_pm_name(i)); for (j = i - 1; j >= DP_CORE_PM; j--) { msm_dss_get_vreg(&pdev->dev, parser->mp[j].vreg_config, parser->mp[j].num_vreg, 0); } goto error; } } error: return rc; } static void dp_power_regulator_deinit(struct dp_power_private *power) { int rc = 0, i = 0; struct platform_device *pdev; struct dp_parser *parser; parser = power->parser; pdev = power->pdev; for (i = DP_CORE_PM; (i < DP_MAX_PM); i++) { rc = msm_dss_get_vreg(&pdev->dev, parser->mp[i].vreg_config, parser->mp[i].num_vreg, 0); if (rc) DP_ERR("failed to deinit vregs for %s\n", dp_parser_pm_name(i)); } } static void dp_power_phy_gdsc(struct dp_power *dp_power, bool on) { int rc = 0; if (IS_ERR_OR_NULL(dp_power->dp_phy_gdsc)) return; if (on) rc = regulator_enable(dp_power->dp_phy_gdsc); else rc = regulator_disable(dp_power->dp_phy_gdsc); if (rc) DP_ERR("Fail to %s dp_phy_gdsc regulator ret =%d\n", on ? "enable" : "disable", rc); } static int dp_power_regulator_ctrl(struct dp_power_private *power, bool enable) { int rc = 0, i = 0, j = 0; struct dp_parser *parser; parser = power->parser; for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { /* * The DP_PLL_PM regulator is controlled by dp_display based * on the link configuration. */ if (i == DP_PLL_PM) { /* DP GDSC vote is needed for new chipsets, define gdsc phandle if needed */ dp_power_phy_gdsc(&power->dp_power, enable); DP_DEBUG("skipping: '%s' vregs for %s\n", enable ? "enable" : "disable", dp_parser_pm_name(i)); continue; } rc = msm_dss_enable_vreg( parser->mp[i].vreg_config, parser->mp[i].num_vreg, enable); if (rc) { DP_ERR("failed to '%s' vregs for %s\n", enable ? "enable" : "disable", dp_parser_pm_name(i)); if (enable) { for (j = i-1; j >= DP_CORE_PM; j--) { msm_dss_enable_vreg( parser->mp[j].vreg_config, parser->mp[j].num_vreg, 0); } } goto error; } } error: return rc; } static int dp_power_pinctrl_set(struct dp_power_private *power, bool active) { int rc = -EFAULT; struct pinctrl_state *pin_state; struct dp_parser *parser; parser = power->parser; if (IS_ERR_OR_NULL(parser->pinctrl.pin)) return 0; pin_state = active ? parser->pinctrl.state_active : parser->pinctrl.state_suspend; if (!IS_ERR_OR_NULL(pin_state)) { rc = pinctrl_select_state(parser->pinctrl.pin, pin_state); if (rc) DP_ERR("can not set %s pins\n", active ? "dp_active" : "dp_sleep"); } else { DP_ERR("invalid '%s' pinstate\n", active ? "dp_active" : "dp_sleep"); } return rc; } static void dp_power_clk_put(struct dp_power_private *power) { enum dp_pm_type module; for (module = DP_CORE_PM; module < DP_MAX_PM; module++) { struct dss_module_power *pm = &power->parser->mp[module]; if (!pm->num_clk) continue; msm_dss_mmrm_deregister(&power->pdev->dev, pm); msm_dss_put_clk(pm->clk_config, pm->num_clk); } } static int dp_power_clk_init(struct dp_power_private *power, bool enable) { int rc = 0; struct device *dev; enum dp_pm_type module; dev = &power->pdev->dev; if (enable) { for (module = DP_CORE_PM; module < DP_MAX_PM; module++) { struct dss_module_power *pm = &power->parser->mp[module]; if (!pm->num_clk) continue; rc = msm_dss_get_clk(dev, pm->clk_config, pm->num_clk); if (rc) { DP_ERR("failed to get %s clk. err=%d\n", dp_parser_pm_name(module), rc); goto exit; } } power->pixel_clk_rcg = clk_get(dev, "pixel_clk_rcg"); if (IS_ERR(power->pixel_clk_rcg)) { DP_ERR("Unable to get DP pixel clk RCG: %ld\n", PTR_ERR(power->pixel_clk_rcg)); rc = PTR_ERR(power->pixel_clk_rcg); power->pixel_clk_rcg = NULL; goto err_pixel_clk_rcg; } power->pixel_parent = clk_get(dev, "pixel_parent"); if (IS_ERR(power->pixel_parent)) { DP_ERR("Unable to get DP pixel RCG parent: %d\n", PTR_ERR(power->pixel_parent)); rc = PTR_ERR(power->pixel_parent); power->pixel_parent = NULL; goto err_pixel_parent; } power->xo_clk = clk_get(dev, "rpmh_cxo_clk"); if (IS_ERR(power->xo_clk)) { DP_ERR("Unable to get XO clk: %d\n", PTR_ERR(power->xo_clk)); rc = PTR_ERR(power->xo_clk); power->xo_clk = NULL; goto err_xo_clk; } if (power->parser->has_mst) { power->pixel1_clk_rcg = clk_get(dev, "pixel1_clk_rcg"); if (IS_ERR(power->pixel1_clk_rcg)) { DP_ERR("Unable to get DP pixel1 clk RCG: %d\n", PTR_ERR(power->pixel1_clk_rcg)); rc = PTR_ERR(power->pixel1_clk_rcg); power->pixel1_clk_rcg = NULL; goto err_pixel1_clk_rcg; } } power->link_clk_rcg = clk_get(dev, "link_clk_src"); if (IS_ERR(power->link_clk_rcg)) { DP_ERR("Unable to get DP link clk RCG: %ld\n", PTR_ERR(power->link_clk_rcg)); rc = PTR_ERR(power->link_clk_rcg); power->link_clk_rcg = NULL; goto err_link_clk_rcg; } /* If link_parent node is available, convert clk rates to HZ for byte2 ops */ power->pll->clk_factor = 1000; power->link_parent = clk_get(dev, "link_parent"); if (IS_ERR(power->link_parent)) { DP_WARN("Unable to get DP link parent: %ld\n", PTR_ERR(power->link_parent)); power->link_parent = NULL; power->pll->clk_factor = 1; } } else { if (power->pixel1_clk_rcg) clk_put(power->pixel1_clk_rcg); if (power->pixel_parent) clk_put(power->pixel_parent); if (power->pixel_clk_rcg) clk_put(power->pixel_clk_rcg); if (power->link_parent) clk_put(power->link_parent); if (power->link_clk_rcg) clk_put(power->link_clk_rcg); dp_power_clk_put(power); } return rc; err_link_clk_rcg: if (power->pixel1_clk_rcg) clk_put(power->pixel1_clk_rcg); err_pixel1_clk_rcg: clk_put(power->xo_clk); err_xo_clk: clk_put(power->pixel_parent); err_pixel_parent: clk_put(power->pixel_clk_rcg); err_pixel_clk_rcg: dp_power_clk_put(power); exit: return rc; } static int dp_power_park_module(struct dp_power_private *power, enum dp_pm_type module) { struct dss_module_power *mp; struct clk *clk = NULL; int rc = 0; bool *parked; mp = &power->parser->mp[module]; if (module == DP_STREAM0_PM) { clk = power->pixel_clk_rcg; parked = &power->strm0_clks_parked; } else if (module == DP_STREAM1_PM) { clk = power->pixel1_clk_rcg; parked = &power->strm1_clks_parked; } else if (module == DP_LINK_PM) { clk = power->link_clk_rcg; parked = &power->link_clks_parked; } else { goto exit; } if (!clk) { DP_WARN("clk type %d not supported\n", module); rc = -EINVAL; goto exit; } if (!power->xo_clk) { rc = -EINVAL; goto exit; } if (*parked) goto exit; rc = clk_set_parent(clk, power->xo_clk); if (rc) { DP_ERR("unable to set xo parent on clk %d\n", module); goto exit; } mp->clk_config->rate = XO_CLK_KHZ * 1000; rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk); if (rc) { DP_ERR("failed to set clk rate.\n"); goto exit; } *parked = true; exit: return rc; } static int dp_power_clk_set_rate(struct dp_power_private *power, enum dp_pm_type module, bool enable) { int rc = 0; struct dss_module_power *mp; if (!power) { DP_ERR("invalid power data\n"); rc = -EINVAL; goto exit; } mp = &power->parser->mp[module]; if (enable) { rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk); if (rc) { DP_ERR("failed to set clks rate.\n"); goto exit; } rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, 1); if (rc) { DP_ERR("failed to enable clks\n"); goto exit; } } else { rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, 0); if (rc) { DP_ERR("failed to disable clks\n"); goto exit; } dp_power_park_module(power, module); } exit: return rc; } static bool dp_power_clk_status(struct dp_power *dp_power, enum dp_pm_type pm_type) { struct dp_power_private *power; if (!dp_power) { DP_ERR("invalid power data\n"); return false; } power = container_of(dp_power, struct dp_power_private, dp_power); if (pm_type == DP_LINK_PM) return power->link_clks_on; else if (pm_type == DP_CORE_PM) return power->core_clks_on; else if (pm_type == DP_STREAM0_PM) return power->strm0_clks_on; else if (pm_type == DP_STREAM1_PM) return power->strm1_clks_on; else return false; } static int dp_power_clk_enable(struct dp_power *dp_power, enum dp_pm_type pm_type, bool enable) { int rc = 0; struct dss_module_power *mp; struct dp_power_private *power; if (!dp_power) { DP_ERR("invalid power data\n"); rc = -EINVAL; goto error; } power = container_of(dp_power, struct dp_power_private, dp_power); mp = &power->parser->mp[pm_type]; if (pm_type >= DP_MAX_PM) { DP_ERR("unsupported power module: %s\n", dp_parser_pm_name(pm_type)); return -EINVAL; } if (enable) { if (dp_power_clk_status(dp_power, pm_type)) { DP_DEBUG("%s clks already enabled\n", dp_parser_pm_name(pm_type)); return 0; } if ((pm_type == DP_CTRL_PM) && (!power->core_clks_on)) { DP_DEBUG("Need to enable core clks before link clks\n"); rc = dp_power_clk_set_rate(power, pm_type, enable); if (rc) { DP_ERR("failed to enable clks: %s. err=%d\n", dp_parser_pm_name(DP_CORE_PM), rc); goto error; } else { power->core_clks_on = true; } } if (pm_type == DP_LINK_PM && power->link_parent) { rc = clk_set_parent(power->link_clk_rcg, power->link_parent); if (rc) { DP_ERR("failed to set link parent\n"); goto error; } } if (((pm_type == DP_STREAM0_PM) || (pm_type == DP_STREAM1_PM)) && (!power->link_clks_on)) { DP_ERR("Need to enable link clk before stream clks\n"); goto error; } } rc = dp_power_clk_set_rate(power, pm_type, enable); if (rc) { DP_ERR("failed to '%s' clks for: %s. err=%d\n", enable ? "enable" : "disable", dp_parser_pm_name(pm_type), rc); goto error; } if (pm_type == DP_CORE_PM) power->core_clks_on = enable; else if (pm_type == DP_STREAM0_PM) power->strm0_clks_on = enable; else if (pm_type == DP_STREAM1_PM) power->strm1_clks_on = enable; else if (pm_type == DP_LINK_PM) power->link_clks_on = enable; if (pm_type == DP_STREAM0_PM) power->strm0_clks_parked = false; if (pm_type == DP_STREAM1_PM) power->strm1_clks_parked = false; if (pm_type == DP_LINK_PM) power->link_clks_parked = false; /* * This log is printed only when user connects or disconnects * a DP cable. As this is a user-action and not a frequent * usecase, it is not going to flood the kernel logs. Also, * helpful in debugging the NOC issues. */ DP_INFO("core:%s link:%s strm0:%s strm1:%s\n", power->core_clks_on ? "on" : "off", power->link_clks_on ? "on" : "off", power->strm0_clks_on ? "on" : "off", power->strm1_clks_on ? "on" : "off"); error: return rc; } static int dp_power_request_gpios(struct dp_power_private *power) { int rc = 0, i; struct device *dev; struct dss_module_power *mp; static const char * const gpio_names[] = { "aux_enable", "aux_sel", "usbplug_cc", }; if (!power) { DP_ERR("invalid power data\n"); return -EINVAL; } dev = &power->pdev->dev; mp = &power->parser->mp[DP_CORE_PM]; for (i = 0; i < ARRAY_SIZE(gpio_names); i++) { unsigned int gpio = mp->gpio_config[i].gpio; if (gpio_is_valid(gpio)) { rc = gpio_request(gpio, gpio_names[i]); if (rc) { DP_ERR("request %s gpio failed, rc=%d\n", gpio_names[i], rc); goto error; } } } return 0; error: for (i = 0; i < ARRAY_SIZE(gpio_names); i++) { unsigned int gpio = mp->gpio_config[i].gpio; if (gpio_is_valid(gpio)) gpio_free(gpio); } return rc; } static bool dp_power_find_gpio(const char *gpio1, const char *gpio2) { return !!strnstr(gpio1, gpio2, strlen(gpio1)); } static void dp_power_set_gpio(struct dp_power_private *power, bool flip) { int i; struct dss_module_power *mp = &power->parser->mp[DP_CORE_PM]; struct dss_gpio *config = mp->gpio_config; for (i = 0; i < mp->num_gpio; i++) { if (dp_power_find_gpio(config->gpio_name, "aux-sel")) config->value = flip; if (gpio_is_valid(config->gpio)) { DP_DEBUG("gpio %s, value %d\n", config->gpio_name, config->value); if (dp_power_find_gpio(config->gpio_name, "aux-en") || dp_power_find_gpio(config->gpio_name, "aux-sel")) gpio_direction_output(config->gpio, config->value); else gpio_set_value(config->gpio, config->value); } config++; } } static int dp_power_config_gpios(struct dp_power_private *power, bool flip, bool enable) { int rc = 0, i; struct dss_module_power *mp; struct dss_gpio *config; mp = &power->parser->mp[DP_CORE_PM]; config = mp->gpio_config; if (enable) { rc = dp_power_request_gpios(power); if (rc) { DP_ERR("gpio request failed\n"); return rc; } dp_power_set_gpio(power, flip); } else { for (i = 0; i < mp->num_gpio; i++) { if (gpio_is_valid(config[i].gpio)) { gpio_set_value(config[i].gpio, 0); gpio_free(config[i].gpio); } } } return 0; } static int dp_power_mmrm_init(struct dp_power *dp_power, struct sde_power_handle *phandle, void *dp, int (*dp_display_mmrm_callback)(struct mmrm_client_notifier_data *notifier_data)) { int rc = 0; enum dp_pm_type module; struct dp_power_private *power = container_of(dp_power, struct dp_power_private, dp_power); struct device *dev = &power->pdev->dev; for (module = DP_CORE_PM; module < DP_MAX_PM; module++) { struct dss_module_power *pm = &power->parser->mp[module]; if (!pm->num_clk) continue; rc = msm_dss_mmrm_register(dev, pm, dp_display_mmrm_callback, dp, &phandle->mmrm_enable); if (rc) DP_ERR("mmrm register failed rc=%d\n", rc); } return rc; } static int dp_power_client_init(struct dp_power *dp_power, struct sde_power_handle *phandle, struct drm_device *drm_dev) { int rc = 0; struct dp_power_private *power; if (!drm_dev) { DP_ERR("invalid drm_dev\n"); return -EINVAL; } power = container_of(dp_power, struct dp_power_private, dp_power); rc = dp_power_regulator_init(power); if (rc) { DP_ERR("failed to init regulators\n"); goto error_power; } rc = dp_power_clk_init(power, true); if (rc) { DP_ERR("failed to init clocks\n"); goto error_clk; } dp_power->phandle = phandle; dp_power->drm_dev = drm_dev; return 0; error_clk: dp_power_regulator_deinit(power); error_power: return rc; } static void dp_power_client_deinit(struct dp_power *dp_power) { struct dp_power_private *power; if (!dp_power) { DP_ERR("invalid power data\n"); return; } power = container_of(dp_power, struct dp_power_private, dp_power); dp_power_clk_init(power, false); dp_power_regulator_deinit(power); } static int dp_power_park_clocks(struct dp_power *dp_power) { int rc = 0; struct dp_power_private *power; if (!dp_power) { DP_ERR("invalid power data\n"); return -EINVAL; } power = container_of(dp_power, struct dp_power_private, dp_power); rc = dp_power_park_module(power, DP_STREAM0_PM); if (rc) { DP_ERR("failed to park stream 0. err=%d\n", rc); goto error; } rc = dp_power_park_module(power, DP_STREAM1_PM); if (rc) { DP_ERR("failed to park stream 1. err=%d\n", rc); goto error; } rc = dp_power_park_module(power, DP_LINK_PM); if (rc) { DP_ERR("failed to park link clock. err=%d\n", rc); goto error; } error: return rc; } static int dp_power_set_pixel_clk_parent(struct dp_power *dp_power, u32 strm_id) { int rc = 0; struct dp_power_private *power; if (!dp_power || strm_id >= DP_STREAM_MAX) { DP_ERR("invalid power data. stream %d\n", strm_id); rc = -EINVAL; goto exit; } power = container_of(dp_power, struct dp_power_private, dp_power); if (strm_id == DP_STREAM_0) { if (power->pixel_clk_rcg && power->pixel_parent) rc = clk_set_parent(power->pixel_clk_rcg, power->pixel_parent); else DP_WARN("skipped for strm_id=%d\n", strm_id); } else if (strm_id == DP_STREAM_1) { if (power->pixel1_clk_rcg && power->pixel_parent) rc = clk_set_parent(power->pixel1_clk_rcg, power->pixel_parent); else DP_WARN("skipped for strm_id=%d\n", strm_id); } if (rc) DP_ERR("failed. strm_id=%d, rc=%d\n", strm_id, rc); exit: return rc; } static u64 dp_power_clk_get_rate(struct dp_power *dp_power, char *clk_name) { size_t i; enum dp_pm_type j; struct dss_module_power *mp; struct dp_power_private *power; bool clk_found = false; u64 rate = 0; if (!clk_name) { DP_ERR("invalid pointer for clk_name\n"); return 0; } power = container_of(dp_power, struct dp_power_private, dp_power); mp = &dp_power->phandle->mp; for (i = 0; i < mp->num_clk; i++) { if (!strcmp(mp->clk_config[i].clk_name, clk_name)) { rate = clk_get_rate(mp->clk_config[i].clk); clk_found = true; break; } } for (j = DP_CORE_PM; j < DP_MAX_PM && !clk_found; j++) { mp = &power->parser->mp[j]; for (i = 0; i < mp->num_clk; i++) { if (!strcmp(mp->clk_config[i].clk_name, clk_name)) { rate = clk_get_rate(mp->clk_config[i].clk); clk_found = true; break; } } } return rate; } static int dp_power_init(struct dp_power *dp_power, bool flip) { int rc = 0; struct dp_power_private *power; if (!dp_power) { DP_ERR("invalid power data\n"); rc = -EINVAL; goto exit; } power = container_of(dp_power, struct dp_power_private, dp_power); rc = dp_power_regulator_ctrl(power, true); if (rc) { DP_ERR("failed to enable regulators\n"); goto exit; } rc = dp_power_pinctrl_set(power, true); if (rc) { DP_ERR("failed to set pinctrl state\n"); goto err_pinctrl; } rc = dp_power_config_gpios(power, flip, true); if (rc) { DP_ERR("failed to enable gpios\n"); goto err_gpio; } rc = pm_runtime_resume_and_get(dp_power->drm_dev->dev); if (rc < 0) { DP_ERR("failed to enable power resource %d\n", rc); goto err_sde_power; } rc = dp_power_clk_enable(dp_power, DP_CORE_PM, true); if (rc) { DP_ERR("failed to enable DP core clocks\n"); goto err_clk; } return 0; err_clk: pm_runtime_put_sync(dp_power->drm_dev->dev); err_sde_power: dp_power_config_gpios(power, flip, false); err_gpio: dp_power_pinctrl_set(power, false); err_pinctrl: dp_power_regulator_ctrl(power, false); exit: return rc; } static int dp_power_deinit(struct dp_power *dp_power) { int rc = 0; struct dp_power_private *power; if (!dp_power) { DP_ERR("invalid power data\n"); rc = -EINVAL; goto exit; } power = container_of(dp_power, struct dp_power_private, dp_power); if (power->link_clks_on) dp_power_clk_enable(dp_power, DP_LINK_PM, false); dp_power_clk_enable(dp_power, DP_CORE_PM, false); pm_runtime_put_sync(dp_power->drm_dev->dev); dp_power_config_gpios(power, false, false); dp_power_pinctrl_set(power, false); dp_power_regulator_ctrl(power, false); exit: return rc; } struct dp_power *dp_power_get(struct dp_parser *parser, struct dp_pll *pll) { int rc = 0; struct dp_power_private *power; struct dp_power *dp_power; struct device *dev; if (!parser || !pll) { DP_ERR("invalid input\n"); rc = -EINVAL; goto error; } power = kzalloc(sizeof(*power), GFP_KERNEL); if (!power) { rc = -ENOMEM; goto error; } power->parser = parser; power->pll = pll; power->pdev = parser->pdev; dp_power = &power->dp_power; dev = &power->pdev->dev; dp_power->init = dp_power_init; dp_power->deinit = dp_power_deinit; dp_power->clk_enable = dp_power_clk_enable; dp_power->clk_status = dp_power_clk_status; dp_power->set_pixel_clk_parent = dp_power_set_pixel_clk_parent; dp_power->park_clocks = dp_power_park_clocks; dp_power->clk_get_rate = dp_power_clk_get_rate; dp_power->power_client_init = dp_power_client_init; dp_power->power_client_deinit = dp_power_client_deinit; dp_power->power_mmrm_init = dp_power_mmrm_init; dp_power->dp_phy_gdsc = devm_regulator_get(dev, "dp_phy_gdsc"); if (IS_ERR(dp_power->dp_phy_gdsc)) { dp_power->dp_phy_gdsc = NULL; DP_DEBUG("Optional GDSC regulator is missing\n"); } return dp_power; error: return ERR_PTR(rc); } void dp_power_put(struct dp_power *dp_power) { struct dp_power_private *power = NULL; if (!dp_power) return; power = container_of(dp_power, struct dp_power_private, dp_power); kfree(power); }