Add initial OPP and SoC CORE voltage scaling support to the Tegra UDC driver. This is required for enabling system-wide DVFS on older Tegra SoCs. Tested-by: Peter Geis <pgwipeout@xxxxxxxxx> Signed-off-by: Dmitry Osipenko <digetx@xxxxxxxxx> --- drivers/usb/chipidea/Kconfig | 1 + drivers/usb/chipidea/ci_hdrc_tegra.c | 79 ++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 8bafcfc6080d..6a5bc08711d6 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -56,6 +56,7 @@ config USB_CHIPIDEA_TEGRA tristate "Enable Tegra UDC glue driver" if EMBEDDED depends on OF depends on USB_CHIPIDEA_UDC + select PM_OPP default USB_CHIPIDEA endif diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index 7455df0ede49..7f0403e810fe 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -6,6 +6,7 @@ #include <linux/clk.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/pm_opp.h> #include <linux/reset.h> #include <linux/usb/chipidea.h> @@ -47,6 +48,79 @@ static const struct of_device_id tegra_udc_of_match[] = { }; MODULE_DEVICE_TABLE(of, tegra_udc_of_match); +static void tegra_udc_deinit_opp_table(void *data) +{ + struct device *dev = data; + struct opp_table *opp_table; + + opp_table = dev_pm_opp_get_opp_table(dev); + dev_pm_opp_of_remove_table(dev); + dev_pm_opp_put_regulators(opp_table); + dev_pm_opp_put_opp_table(opp_table); +} + +static int devm_tegra_udc_init_opp_table(struct device *dev) +{ + unsigned long rate = ULONG_MAX; + struct opp_table *opp_table; + const char *rname = "core"; + struct dev_pm_opp *opp; + int err; + + /* legacy device-trees don't have OPP table */ + if (!device_property_present(dev, "operating-points-v2")) + return 0; + + /* voltage scaling is optional */ + if (device_property_present(dev, "core-supply")) + opp_table = dev_pm_opp_set_regulators(dev, &rname, 1); + else + opp_table = dev_pm_opp_get_opp_table(dev); + + if (IS_ERR(opp_table)) + return dev_err_probe(dev, PTR_ERR(opp_table), + "failed to prepare OPP table\n"); + + err = dev_pm_opp_of_add_table(dev); + if (err) { + dev_err(dev, "failed to add OPP table: %d\n", err); + goto put_table; + } + + /* find suitable OPP for the maximum clock rate */ + opp = dev_pm_opp_find_freq_floor(dev, &rate); + err = PTR_ERR_OR_ZERO(opp); + if (err) { + dev_err(dev, "failed to get OPP: %d\n", err); + goto remove_table; + } + + dev_pm_opp_put(opp); + + /* + * First dummy rate-set initializes voltage vote by setting voltage + * in accordance to the clock rate. + */ + err = dev_pm_opp_set_rate(dev, rate); + if (err) { + dev_err(dev, "failed to initialize OPP clock: %d\n", err); + goto remove_table; + } + + err = devm_add_action(dev, tegra_udc_deinit_opp_table, dev); + if (err) + goto remove_table; + + return 0; + +remove_table: + dev_pm_opp_of_remove_table(dev); +put_table: + dev_pm_opp_put_regulators(opp_table); + + return err; +} + static int tegra_udc_probe(struct platform_device *pdev) { const struct tegra_udc_soc_info *soc; @@ -77,6 +151,11 @@ static int tegra_udc_probe(struct platform_device *pdev) return err; } + err = devm_tegra_udc_init_opp_table(&pdev->dev); + if (err) + return dev_err_probe(&pdev->dev, err, + "failed to initialize OPP\n"); + err = clk_prepare_enable(udc->clk); if (err < 0) { dev_err(&pdev->dev, "failed to enable clock: %d\n", err); -- 2.27.0