From a46b78247b852345ae4458711a4aec6744a7838c Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:43 +0300 Subject: ASoC: tegra20: ac97: Add reset control Tegra20 AC97 driver doesn't manage the AC97 controller reset, relying on implicit deassertion of the reset by tegra-clk driver, which needs to be fixed since this behaviour is unacceptable by other Tegra drivers. Add explicit reset control to the Tegra20 AC97 driver. Note that AC97 reset was always specified in Tegra20 device-tree, hence DTB ABI changes aren't required. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-2-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_ac97.c | 21 +++++++++++++++++++++ sound/soc/tegra/tegra20_ac97.h | 1 + 2 files changed, 22 insertions(+) diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c index 06c728ae17ed..c454a34c15c4 100644 --- a/sound/soc/tegra/tegra20_ac97.c +++ b/sound/soc/tegra/tegra20_ac97.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -313,6 +314,12 @@ static int tegra20_ac97_platform_probe(struct platform_device *pdev) } dev_set_drvdata(&pdev->dev, ac97); + ac97->reset = devm_reset_control_get_exclusive(&pdev->dev, "ac97"); + if (IS_ERR(ac97->reset)) { + dev_err(&pdev->dev, "Can't retrieve ac97 reset\n"); + return PTR_ERR(ac97->reset); + } + ac97->clk_ac97 = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(ac97->clk_ac97)) { dev_err(&pdev->dev, "Can't retrieve ac97 clock\n"); @@ -364,12 +371,26 @@ static int tegra20_ac97_platform_probe(struct platform_device *pdev) ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; ac97->playback_dma_data.maxburst = 4; + ret = reset_control_assert(ac97->reset); + if (ret) { + dev_err(&pdev->dev, "Failed to assert AC'97 reset: %d\n", ret); + goto err_clk_put; + } + ret = clk_prepare_enable(ac97->clk_ac97); if (ret) { dev_err(&pdev->dev, "clk_enable failed: %d\n", ret); goto err_clk_put; } + usleep_range(10, 100); + + ret = reset_control_deassert(ac97->reset); + if (ret) { + dev_err(&pdev->dev, "Failed to deassert AC'97 reset: %d\n", ret); + goto err_clk_disable_unprepare; + } + ret = snd_soc_set_ac97_ops(&tegra20_ac97_ops); if (ret) { dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret); diff --git a/sound/soc/tegra/tegra20_ac97.h b/sound/soc/tegra/tegra20_ac97.h index e467cd1ff2ca..870ea09ff301 100644 --- a/sound/soc/tegra/tegra20_ac97.h +++ b/sound/soc/tegra/tegra20_ac97.h @@ -78,6 +78,7 @@ struct tegra20_ac97 { struct clk *clk_ac97; struct snd_dmaengine_dai_dma_data capture_dma_data; struct snd_dmaengine_dai_dma_data playback_dma_data; + struct reset_control *reset; struct regmap *regmap; int reset_gpio; int sync_gpio; -- cgit From 9c648ef82d7d4696e80b286d37dae07b67a9a32d Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:44 +0300 Subject: ASoC: tegra20: i2s: Add reset control The I2S reset may be asserted at a boot time, in particular this is the case on Tegra20 AC100 netbook. Tegra20 I2S driver doesn't manage the reset control and currently it happens to work because reset is implicitly deasserted by the tegra-clk driver when I2S clock is enabled. The I2S permanently stays in a reset once tegra-clk is fixed to not touch the resets, which it shouldn't be doing. Add reset control to the Tegra20 I2S driver. Note that I2S reset was always specified in Tegra20 device-tree, hence DTB ABI changes aren't required. Tested-by: Paul Fertser # T20 AC100 Reported-by: Paul Fertser Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-3-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_i2s.c | 31 +++++++++++++++++++++++++++++++ sound/soc/tegra/tegra20_i2s.h | 1 + 2 files changed, 32 insertions(+) diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index d7a3d046c8f8..c0af5352b483 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,8 @@ static int tegra20_i2s_runtime_suspend(struct device *dev) { struct tegra20_i2s *i2s = dev_get_drvdata(dev); + regcache_cache_only(i2s->regmap, true); + clk_disable_unprepare(i2s->clk_i2s); return 0; @@ -47,13 +50,35 @@ static int tegra20_i2s_runtime_resume(struct device *dev) struct tegra20_i2s *i2s = dev_get_drvdata(dev); int ret; + ret = reset_control_assert(i2s->reset); + if (ret) + return ret; + ret = clk_prepare_enable(i2s->clk_i2s); if (ret) { dev_err(dev, "clk_enable failed: %d\n", ret); return ret; } + usleep_range(10, 100); + + ret = reset_control_deassert(i2s->reset); + if (ret) + goto disable_clocks; + + regcache_cache_only(i2s->regmap, false); + regcache_mark_dirty(i2s->regmap); + + ret = regcache_sync(i2s->regmap); + if (ret) + goto disable_clocks; + return 0; + +disable_clocks: + clk_disable_unprepare(i2s->clk_i2s); + + return ret; } static int tegra20_i2s_set_fmt(struct snd_soc_dai *dai, @@ -339,6 +364,12 @@ static int tegra20_i2s_platform_probe(struct platform_device *pdev) i2s->dai = tegra20_i2s_dai_template; i2s->dai.name = dev_name(&pdev->dev); + i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, "i2s"); + if (IS_ERR(i2s->reset)) { + dev_err(&pdev->dev, "Can't retrieve i2s reset\n"); + return PTR_ERR(i2s->reset); + } + i2s->clk_i2s = clk_get(&pdev->dev, NULL); if (IS_ERR(i2s->clk_i2s)) { dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); diff --git a/sound/soc/tegra/tegra20_i2s.h b/sound/soc/tegra/tegra20_i2s.h index 628d3ca09f42..8233e5fa2eff 100644 --- a/sound/soc/tegra/tegra20_i2s.h +++ b/sound/soc/tegra/tegra20_i2s.h @@ -144,6 +144,7 @@ struct tegra20_i2s { struct snd_dmaengine_dai_dma_data capture_dma_data; struct snd_dmaengine_dai_dma_data playback_dma_data; struct regmap *regmap; + struct reset_control *reset; }; #endif -- cgit From 0bbcecaaab15a74ba69f93df46c753f2a64eadca Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:45 +0300 Subject: ASoC: tegra30: i2s: Restore hardware state on runtime PM resume Tegra30 I2S driver syncs regmap cache only on resume from system suspend, but hardware is reset across the runtime suspend because RPM of the parent AHUB driver resets the I2S hardware, hence h/w state is lost after each RPM resume. The problem isn't visible because hardware happens to be fully reprogrammed after each RPM resume. Move hardware syncing to RPM resume in order to restore h/w state properly. Fixes: ed9ce1ed2239 ("ASoC: tegra: ahub: Reset hardware properly") Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-4-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra30_i2s.c | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 6740df541508..3d22c1be6f3d 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -58,8 +58,18 @@ static int tegra30_i2s_runtime_resume(struct device *dev) } regcache_cache_only(i2s->regmap, false); + regcache_mark_dirty(i2s->regmap); + + ret = regcache_sync(i2s->regmap); + if (ret) + goto disable_clocks; return 0; + +disable_clocks: + clk_disable_unprepare(i2s->clk_i2s); + + return ret; } static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai, @@ -551,37 +561,11 @@ static int tegra30_i2s_platform_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM_SLEEP -static int tegra30_i2s_suspend(struct device *dev) -{ - struct tegra30_i2s *i2s = dev_get_drvdata(dev); - - regcache_mark_dirty(i2s->regmap); - - return 0; -} - -static int tegra30_i2s_resume(struct device *dev) -{ - struct tegra30_i2s *i2s = dev_get_drvdata(dev); - int ret; - - ret = pm_runtime_get_sync(dev); - if (ret < 0) { - pm_runtime_put(dev); - return ret; - } - ret = regcache_sync(i2s->regmap); - pm_runtime_put(dev); - - return ret; -} -#endif - static const struct dev_pm_ops tegra30_i2s_pm_ops = { SET_RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend, tegra30_i2s_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(tegra30_i2s_suspend, tegra30_i2s_resume) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; static struct platform_driver tegra30_i2s_driver = { -- cgit From 48d71395896d54eec989179dd265e569fcecb15a Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Sun, 14 Mar 2021 18:44:46 +0300 Subject: reset: Add reset_control_bulk API Follow the clock and regulator subsystems' lead and add a bulk API for reset controls. Signed-off-by: Philipp Zabel Tested-by: Dmitry Osipenko Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-5-digetx@gmail.com Signed-off-by: Mark Brown --- drivers/reset/core.c | 215 ++++++++++++++++++++++++++++++++++ include/linux/reset.h | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 530 insertions(+) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index dbf881b586d9..71c1c8264b2d 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -358,6 +358,30 @@ int reset_control_reset(struct reset_control *rstc) } EXPORT_SYMBOL_GPL(reset_control_reset); +/** + * reset_control_bulk_reset - reset the controlled devices in order + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * Issue a reset on all provided reset controls, in order. + * + * See also: reset_control_reset() + */ +int reset_control_bulk_reset(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int ret, i; + + for (i = 0; i < num_rstcs; i++) { + ret = reset_control_reset(rstcs[i].rstc); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(reset_control_bulk_reset); + /** * reset_control_rearm - allow shared reset line to be re-triggered" * @rstc: reset controller @@ -461,6 +485,36 @@ int reset_control_assert(struct reset_control *rstc) } EXPORT_SYMBOL_GPL(reset_control_assert); +/** + * reset_control_bulk_assert - asserts the reset lines in order + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * Assert the reset lines for all provided reset controls, in order. + * If an assertion fails, already asserted resets are deasserted again. + * + * See also: reset_control_assert() + */ +int reset_control_bulk_assert(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int ret, i; + + for (i = 0; i < num_rstcs; i++) { + ret = reset_control_assert(rstcs[i].rstc); + if (ret) + goto err; + } + + return 0; + +err: + while (i--) + reset_control_deassert(rstcs[i].rstc); + return ret; +} +EXPORT_SYMBOL_GPL(reset_control_bulk_assert); + /** * reset_control_deassert - deasserts the reset line * @rstc: reset controller @@ -511,6 +565,36 @@ int reset_control_deassert(struct reset_control *rstc) } EXPORT_SYMBOL_GPL(reset_control_deassert); +/** + * reset_control_bulk_deassert - deasserts the reset lines in reverse order + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * Deassert the reset lines for all provided reset controls, in reverse order. + * If a deassertion fails, already deasserted resets are asserted again. + * + * See also: reset_control_deassert() + */ +int reset_control_bulk_deassert(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int ret, i; + + for (i = num_rstcs - 1; i >= 0; i--) { + ret = reset_control_deassert(rstcs[i].rstc); + if (ret) + goto err; + } + + return 0; + +err: + while (i < num_rstcs) + reset_control_assert(rstcs[i++].rstc); + return ret; +} +EXPORT_SYMBOL_GPL(reset_control_bulk_deassert); + /** * reset_control_status - returns a negative errno if not supported, a * positive value if the reset line is asserted, or zero if the reset @@ -588,6 +672,36 @@ int reset_control_acquire(struct reset_control *rstc) } EXPORT_SYMBOL_GPL(reset_control_acquire); +/** + * reset_control_bulk_acquire - acquires reset controls for exclusive use + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * This is used to explicitly acquire reset controls requested with + * reset_control_bulk_get_exclusive_release() for temporary exclusive use. + * + * See also: reset_control_acquire(), reset_control_bulk_release() + */ +int reset_control_bulk_acquire(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int ret, i; + + for (i = 0; i < num_rstcs; i++) { + ret = reset_control_acquire(rstcs[i].rstc); + if (ret) + goto err; + } + + return 0; + +err: + while (i--) + reset_control_release(rstcs[i].rstc); + return ret; +} +EXPORT_SYMBOL_GPL(reset_control_bulk_acquire); + /** * reset_control_release() - releases exclusive access to a reset control * @rstc: reset control @@ -610,6 +724,26 @@ void reset_control_release(struct reset_control *rstc) } EXPORT_SYMBOL_GPL(reset_control_release); +/** + * reset_control_bulk_release() - releases exclusive access to reset controls + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * Releases exclusive access right to reset controls previously obtained by a + * call to reset_control_bulk_acquire(). + * + * See also: reset_control_release(), reset_control_bulk_acquire() + */ +void reset_control_bulk_release(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int i; + + for (i = 0; i < num_rstcs; i++) + reset_control_release(rstcs[i].rstc); +} +EXPORT_SYMBOL_GPL(reset_control_bulk_release); + static struct reset_control *__reset_control_get_internal( struct reset_controller_dev *rcdev, unsigned int index, bool shared, bool acquired) @@ -814,6 +948,32 @@ struct reset_control *__reset_control_get(struct device *dev, const char *id, } EXPORT_SYMBOL_GPL(__reset_control_get); +int __reset_control_bulk_get(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs, + bool shared, bool optional, bool acquired) +{ + int ret, i; + + for (i = 0; i < num_rstcs; i++) { + rstcs[i].rstc = __reset_control_get(dev, rstcs[i].id, 0, + shared, optional, acquired); + if (IS_ERR(rstcs[i].rstc)) { + ret = PTR_ERR(rstcs[i].rstc); + goto err; + } + } + + return 0; + +err: + mutex_lock(&reset_list_mutex); + while (i--) + __reset_control_put_internal(rstcs[i].rstc); + mutex_unlock(&reset_list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(__reset_control_bulk_get); + static void reset_control_array_put(struct reset_control_array *resets) { int i; @@ -845,6 +1005,23 @@ void reset_control_put(struct reset_control *rstc) } EXPORT_SYMBOL_GPL(reset_control_put); +/** + * reset_control_bulk_put - free the reset controllers + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + */ +void reset_control_bulk_put(int num_rstcs, struct reset_control_bulk_data *rstcs) +{ + mutex_lock(&reset_list_mutex); + while (num_rstcs--) { + if (IS_ERR_OR_NULL(rstcs[num_rstcs].rstc)) + continue; + __reset_control_put_internal(rstcs[num_rstcs].rstc); + } + mutex_unlock(&reset_list_mutex); +} +EXPORT_SYMBOL_GPL(reset_control_bulk_put); + static void devm_reset_control_release(struct device *dev, void *res) { reset_control_put(*(struct reset_control **)res); @@ -874,6 +1051,44 @@ struct reset_control *__devm_reset_control_get(struct device *dev, } EXPORT_SYMBOL_GPL(__devm_reset_control_get); +struct reset_control_bulk_devres { + int num_rstcs; + struct reset_control_bulk_data *rstcs; +}; + +static void devm_reset_control_bulk_release(struct device *dev, void *res) +{ + struct reset_control_bulk_devres *devres = res; + + reset_control_bulk_put(devres->num_rstcs, devres->rstcs); +} + +int __devm_reset_control_bulk_get(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs, + bool shared, bool optional, bool acquired) +{ + struct reset_control_bulk_devres *ptr; + int ret; + + ptr = devres_alloc(devm_reset_control_bulk_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = __reset_control_bulk_get(dev, num_rstcs, rstcs, shared, optional, acquired); + if (ret < 0) { + devres_free(ptr); + return ret; + } + + ptr->num_rstcs = num_rstcs; + ptr->rstcs = rstcs; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL_GPL(__devm_reset_control_bulk_get); + /** * __device_reset - find reset controller associated with the device * and perform reset diff --git a/include/linux/reset.h b/include/linux/reset.h index b9109efa2a5c..46e6372cb431 100644 --- a/include/linux/reset.h +++ b/include/linux/reset.h @@ -10,6 +10,21 @@ struct device; struct device_node; struct reset_control; +/** + * struct reset_control_bulk_data - Data used for bulk reset control operations. + * + * @id: reset control consumer ID + * @rstc: struct reset_control * to store the associated reset control + * + * The reset APIs provide a series of reset_control_bulk_*() API calls as + * a convenience to consumers which require multiple reset controls. + * This structure is used to manage data for these calls. + */ +struct reset_control_bulk_data { + const char *id; + struct reset_control *rstc; +}; + #ifdef CONFIG_RESET_CONTROLLER int reset_control_reset(struct reset_control *rstc); @@ -20,6 +35,12 @@ int reset_control_status(struct reset_control *rstc); int reset_control_acquire(struct reset_control *rstc); void reset_control_release(struct reset_control *rstc); +int reset_control_bulk_reset(int num_rstcs, struct reset_control_bulk_data *rstcs); +int reset_control_bulk_assert(int num_rstcs, struct reset_control_bulk_data *rstcs); +int reset_control_bulk_deassert(int num_rstcs, struct reset_control_bulk_data *rstcs); +int reset_control_bulk_acquire(int num_rstcs, struct reset_control_bulk_data *rstcs); +void reset_control_bulk_release(int num_rstcs, struct reset_control_bulk_data *rstcs); + struct reset_control *__of_reset_control_get(struct device_node *node, const char *id, int index, bool shared, bool optional, bool acquired); @@ -27,10 +48,18 @@ struct reset_control *__reset_control_get(struct device *dev, const char *id, int index, bool shared, bool optional, bool acquired); void reset_control_put(struct reset_control *rstc); +int __reset_control_bulk_get(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs, + bool shared, bool optional, bool acquired); +void reset_control_bulk_put(int num_rstcs, struct reset_control_bulk_data *rstcs); + int __device_reset(struct device *dev, bool optional); struct reset_control *__devm_reset_control_get(struct device *dev, const char *id, int index, bool shared, bool optional, bool acquired); +int __devm_reset_control_bulk_get(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs, + bool shared, bool optional, bool acquired); struct reset_control *devm_reset_control_array_get(struct device *dev, bool shared, bool optional); @@ -96,6 +125,48 @@ static inline struct reset_control *__reset_control_get( return optional ? NULL : ERR_PTR(-ENOTSUPP); } +static inline int +reset_control_bulk_reset(int num_rstcs, struct reset_control_bulk_data *rstcs) +{ + return 0; +} + +static inline int +reset_control_bulk_assert(int num_rstcs, struct reset_control_bulk_data *rstcs) +{ + return 0; +} + +static inline int +reset_control_bulk_deassert(int num_rstcs, struct reset_control_bulk_data *rstcs) +{ + return 0; +} + +static inline int +reset_control_bulk_acquire(int num_rstcs, struct reset_control_bulk_data *rstcs) +{ + return 0; +} + +static inline void +reset_control_bulk_release(int num_rstcs, struct reset_control_bulk_data *rstcs) +{ +} + +static inline int +__reset_control_bulk_get(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs, + bool shared, bool optional, bool acquired) +{ + return optional ? 0 : -EOPNOTSUPP; +} + +static inline void +reset_control_bulk_put(int num_rstcs, struct reset_control_bulk_data *rstcs) +{ +} + static inline struct reset_control *__devm_reset_control_get( struct device *dev, const char *id, int index, bool shared, bool optional, @@ -104,6 +175,14 @@ static inline struct reset_control *__devm_reset_control_get( return optional ? NULL : ERR_PTR(-ENOTSUPP); } +static inline int +__devm_reset_control_bulk_get(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs, + bool shared, bool optional, bool acquired) +{ + return optional ? 0 : -EOPNOTSUPP; +} + static inline struct reset_control * devm_reset_control_array_get(struct device *dev, bool shared, bool optional) { @@ -155,6 +234,23 @@ __must_check reset_control_get_exclusive(struct device *dev, const char *id) return __reset_control_get(dev, id, 0, false, false, true); } +/** + * reset_control_bulk_get_exclusive - Lookup and obtain exclusive references to + * multiple reset controllers. + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Fills the rstcs array with pointers to exclusive reset controls and + * returns 0, or an IS_ERR() condition containing errno. + */ +static inline int __must_check +reset_control_bulk_get_exclusive(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __reset_control_bulk_get(dev, num_rstcs, rstcs, false, false, true); +} + /** * reset_control_get_exclusive_released - Lookup and obtain a temoprarily * exclusive reference to a reset @@ -176,6 +272,48 @@ __must_check reset_control_get_exclusive_released(struct device *dev, return __reset_control_get(dev, id, 0, false, false, false); } +/** + * reset_control_bulk_get_exclusive_released - Lookup and obtain temporarily + * exclusive references to multiple reset + * controllers. + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Fills the rstcs array with pointers to exclusive reset controls and + * returns 0, or an IS_ERR() condition containing errno. + * reset-controls returned by this function must be acquired via + * reset_control_bulk_acquire() before they can be used and should be released + * via reset_control_bulk_release() afterwards. + */ +static inline int __must_check +reset_control_bulk_get_exclusive_released(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __reset_control_bulk_get(dev, num_rstcs, rstcs, false, false, false); +} + +/** + * reset_control_bulk_get_optional_exclusive_released - Lookup and obtain optional + * temporarily exclusive references to multiple + * reset controllers. + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Optional variant of reset_control_bulk_get_exclusive_released(). If the + * requested reset is not specified in the device tree, this function returns 0 + * instead of an error and missing rtsc is set to NULL. + * + * See reset_control_bulk_get_exclusive_released() for more information. + */ +static inline int __must_check +reset_control_bulk_get_optional_exclusive_released(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __reset_control_bulk_get(dev, num_rstcs, rstcs, false, true, false); +} + /** * reset_control_get_shared - Lookup and obtain a shared reference to a * reset controller. @@ -204,6 +342,23 @@ static inline struct reset_control *reset_control_get_shared( return __reset_control_get(dev, id, 0, true, false, false); } +/** + * reset_control_bulk_get_shared - Lookup and obtain shared references to + * multiple reset controllers. + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Fills the rstcs array with pointers to shared reset controls and + * returns 0, or an IS_ERR() condition containing errno. + */ +static inline int __must_check +reset_control_bulk_get_shared(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __reset_control_bulk_get(dev, num_rstcs, rstcs, true, false, false); +} + /** * reset_control_get_optional_exclusive - optional reset_control_get_exclusive() * @dev: device to be reset by the controller @@ -221,6 +376,26 @@ static inline struct reset_control *reset_control_get_optional_exclusive( return __reset_control_get(dev, id, 0, false, true, true); } +/** + * reset_control_bulk_get_optional_exclusive - optional + * reset_control_bulk_get_exclusive() + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Optional variant of reset_control_bulk_get_exclusive(). If any of the + * requested resets are not specified in the device tree, this function sets + * them to NULL instead of returning an error. + * + * See reset_control_bulk_get_exclusive() for more information. + */ +static inline int __must_check +reset_control_bulk_get_optional_exclusive(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __reset_control_bulk_get(dev, num_rstcs, rstcs, false, true, true); +} + /** * reset_control_get_optional_shared - optional reset_control_get_shared() * @dev: device to be reset by the controller @@ -238,6 +413,26 @@ static inline struct reset_control *reset_control_get_optional_shared( return __reset_control_get(dev, id, 0, true, true, false); } +/** + * reset_control_bulk_get_optional_shared - optional + * reset_control_bulk_get_shared() + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Optional variant of reset_control_bulk_get_shared(). If the requested resets + * are not specified in the device tree, this function sets them to NULL + * instead of returning an error. + * + * See reset_control_bulk_get_shared() for more information. + */ +static inline int __must_check +reset_control_bulk_get_optional_shared(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __reset_control_bulk_get(dev, num_rstcs, rstcs, true, true, false); +} + /** * of_reset_control_get_exclusive - Lookup and obtain an exclusive reference * to a reset controller. @@ -343,6 +538,26 @@ __must_check devm_reset_control_get_exclusive(struct device *dev, return __devm_reset_control_get(dev, id, 0, false, false, true); } +/** + * devm_reset_control_bulk_get_exclusive - resource managed + * reset_control_bulk_get_exclusive() + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Managed reset_control_bulk_get_exclusive(). For reset controllers returned + * from this function, reset_control_put() is called automatically on driver + * detach. + * + * See reset_control_bulk_get_exclusive() for more information. + */ +static inline int __must_check +devm_reset_control_bulk_get_exclusive(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __devm_reset_control_bulk_get(dev, num_rstcs, rstcs, false, false, true); +} + /** * devm_reset_control_get_exclusive_released - resource managed * reset_control_get_exclusive_released() @@ -362,6 +577,26 @@ __must_check devm_reset_control_get_exclusive_released(struct device *dev, return __devm_reset_control_get(dev, id, 0, false, false, false); } +/** + * devm_reset_control_bulk_get_exclusive_released - resource managed + * reset_control_bulk_get_exclusive_released() + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Managed reset_control_bulk_get_exclusive_released(). For reset controllers + * returned from this function, reset_control_put() is called automatically on + * driver detach. + * + * See reset_control_bulk_get_exclusive_released() for more information. + */ +static inline int __must_check +devm_reset_control_bulk_get_exclusive_released(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __devm_reset_control_bulk_get(dev, num_rstcs, rstcs, false, false, false); +} + /** * devm_reset_control_get_optional_exclusive_released - resource managed * reset_control_get_optional_exclusive_released() @@ -381,6 +616,26 @@ __must_check devm_reset_control_get_optional_exclusive_released(struct device *d return __devm_reset_control_get(dev, id, 0, false, true, false); } +/** + * devm_reset_control_bulk_get_optional_exclusive_released - resource managed + * reset_control_bulk_optional_get_exclusive_released() + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Managed reset_control_bulk_optional_get_exclusive_released(). For reset + * controllers returned from this function, reset_control_put() is called + * automatically on driver detach. + * + * See reset_control_bulk_optional_get_exclusive_released() for more information. + */ +static inline int __must_check +devm_reset_control_bulk_get_optional_exclusive_released(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __devm_reset_control_bulk_get(dev, num_rstcs, rstcs, false, true, false); +} + /** * devm_reset_control_get_shared - resource managed reset_control_get_shared() * @dev: device to be reset by the controller @@ -396,6 +651,26 @@ static inline struct reset_control *devm_reset_control_get_shared( return __devm_reset_control_get(dev, id, 0, true, false, false); } +/** + * devm_reset_control_bulk_get_shared - resource managed + * reset_control_bulk_get_shared() + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Managed reset_control_bulk_get_shared(). For reset controllers returned + * from this function, reset_control_put() is called automatically on driver + * detach. + * + * See reset_control_bulk_get_shared() for more information. + */ +static inline int __must_check +devm_reset_control_bulk_get_shared(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __devm_reset_control_bulk_get(dev, num_rstcs, rstcs, true, false, false); +} + /** * devm_reset_control_get_optional_exclusive - resource managed * reset_control_get_optional_exclusive() @@ -414,6 +689,26 @@ static inline struct reset_control *devm_reset_control_get_optional_exclusive( return __devm_reset_control_get(dev, id, 0, false, true, true); } +/** + * devm_reset_control_bulk_get_optional_exclusive - resource managed + * reset_control_bulk_get_optional_exclusive() + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Managed reset_control_bulk_get_optional_exclusive(). For reset controllers + * returned from this function, reset_control_put() is called automatically on + * driver detach. + * + * See reset_control_bulk_get_optional_exclusive() for more information. + */ +static inline int __must_check +devm_reset_control_bulk_get_optional_exclusive(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __devm_reset_control_bulk_get(dev, num_rstcs, rstcs, true, false, true); +} + /** * devm_reset_control_get_optional_shared - resource managed * reset_control_get_optional_shared() @@ -432,6 +727,26 @@ static inline struct reset_control *devm_reset_control_get_optional_shared( return __devm_reset_control_get(dev, id, 0, true, true, false); } +/** + * devm_reset_control_bulk_get_optional_shared - resource managed + * reset_control_bulk_get_optional_shared() + * @dev: device to be reset by the controller + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset line names set + * + * Managed reset_control_bulk_get_optional_shared(). For reset controllers + * returned from this function, reset_control_put() is called automatically on + * driver detach. + * + * See reset_control_bulk_get_optional_shared() for more information. + */ +static inline int __must_check +devm_reset_control_bulk_get_optional_shared(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + return __devm_reset_control_bulk_get(dev, num_rstcs, rstcs, true, true, false); +} + /** * devm_reset_control_get_exclusive_by_index - resource managed * reset_control_get_exclusive() -- cgit From 050086eb6dc945207b1db1d15cd81e9366dfd2f1 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:47 +0300 Subject: ASoC: tegra30: ahub: Switch to use reset-bulk API Switch to use reset-bulk API in order to make code cleaner. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-6-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra30_ahub.c | 104 ++++++++++++++--------------------------- sound/soc/tegra/tegra30_ahub.h | 5 +- sound/soc/tegra/tegra30_i2s.c | 1 + 3 files changed, 40 insertions(+), 70 deletions(-) diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c index 9ef05ca4f6c4..d24c26f4960d 100644 --- a/sound/soc/tegra/tegra30_ahub.c +++ b/sound/soc/tegra/tegra30_ahub.c @@ -65,7 +65,7 @@ static int tegra30_ahub_runtime_resume(struct device *dev) { int ret; - ret = reset_control_assert(ahub->reset); + ret = reset_control_bulk_assert(ahub->nresets, ahub->resets); if (ret) return ret; @@ -75,7 +75,7 @@ static int tegra30_ahub_runtime_resume(struct device *dev) usleep_range(10, 100); - ret = reset_control_deassert(ahub->reset); + ret = reset_control_bulk_deassert(ahub->nresets, ahub->resets); if (ret) goto disable_clocks; @@ -339,41 +339,28 @@ int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif) } EXPORT_SYMBOL_GPL(tegra30_ahub_unset_rx_cif_source); -#define MOD_LIST_MASK_TEGRA30 BIT(0) -#define MOD_LIST_MASK_TEGRA114 BIT(1) -#define MOD_LIST_MASK_TEGRA124 BIT(2) - -#define MOD_LIST_MASK_TEGRA30_OR_LATER \ - (MOD_LIST_MASK_TEGRA30 | MOD_LIST_MASK_TEGRA114 | \ - MOD_LIST_MASK_TEGRA124) -#define MOD_LIST_MASK_TEGRA114_OR_LATER \ - (MOD_LIST_MASK_TEGRA114 | MOD_LIST_MASK_TEGRA124) - -static const struct { - const char *rst_name; - u32 mod_list_mask; -} configlink_mods[] = { - { "d_audio", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "apbif", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "i2s0", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "i2s1", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "i2s2", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "i2s3", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "i2s4", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "dam0", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "dam1", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "dam2", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "spdif", MOD_LIST_MASK_TEGRA30_OR_LATER }, - { "amx", MOD_LIST_MASK_TEGRA114_OR_LATER }, - { "adx", MOD_LIST_MASK_TEGRA114_OR_LATER }, - { "amx1", MOD_LIST_MASK_TEGRA124 }, - { "adx1", MOD_LIST_MASK_TEGRA124 }, - { "afc0", MOD_LIST_MASK_TEGRA124 }, - { "afc1", MOD_LIST_MASK_TEGRA124 }, - { "afc2", MOD_LIST_MASK_TEGRA124 }, - { "afc3", MOD_LIST_MASK_TEGRA124 }, - { "afc4", MOD_LIST_MASK_TEGRA124 }, - { "afc5", MOD_LIST_MASK_TEGRA124 }, +static const struct reset_control_bulk_data tegra30_ahub_resets_data[] = { + { "d_audio" }, + { "apbif" }, + { "i2s0" }, + { "i2s1" }, + { "i2s2" }, + { "i2s3" }, + { "i2s4" }, + { "dam0" }, + { "dam1" }, + { "dam2" }, + { "spdif" }, + { "amx" }, /* Tegra114+ */ + { "adx" }, /* Tegra114+ */ + { "amx1" }, /* Tegra124 */ + { "adx1" }, /* Tegra124 */ + { "afc0" }, /* Tegra124 */ + { "afc1" }, /* Tegra124 */ + { "afc2" }, /* Tegra124 */ + { "afc3" }, /* Tegra124 */ + { "afc4" }, /* Tegra124 */ + { "afc5" }, /* Tegra124 */ }; #define LAST_REG(name) \ @@ -502,17 +489,17 @@ static const struct regmap_config tegra30_ahub_ahub_regmap_config = { }; static struct tegra30_ahub_soc_data soc_data_tegra30 = { - .mod_list_mask = MOD_LIST_MASK_TEGRA30, + .num_resets = 11, .set_audio_cif = tegra30_ahub_set_cif, }; static struct tegra30_ahub_soc_data soc_data_tegra114 = { - .mod_list_mask = MOD_LIST_MASK_TEGRA114, + .num_resets = 13, .set_audio_cif = tegra30_ahub_set_cif, }; static struct tegra30_ahub_soc_data soc_data_tegra124 = { - .mod_list_mask = MOD_LIST_MASK_TEGRA124, + .num_resets = 21, .set_audio_cif = tegra124_ahub_set_cif, }; @@ -527,8 +514,6 @@ static int tegra30_ahub_probe(struct platform_device *pdev) { const struct of_device_id *match; const struct tegra30_ahub_soc_data *soc_data; - struct reset_control *rst; - int i; struct resource *res0; void __iomem *regs_apbif, *regs_ahub; int ret = 0; @@ -541,34 +526,16 @@ static int tegra30_ahub_probe(struct platform_device *pdev) return -EINVAL; soc_data = match->data; - /* - * The AHUB hosts a register bus: the "configlink". For this to - * operate correctly, all devices on this bus must be out of reset. - */ - for (i = 0; i < ARRAY_SIZE(configlink_mods); i++) { - if (!(configlink_mods[i].mod_list_mask & - soc_data->mod_list_mask)) - continue; - - rst = reset_control_get_exclusive(&pdev->dev, - configlink_mods[i].rst_name); - if (IS_ERR(rst)) { - dev_err(&pdev->dev, "Can't get reset %s\n", - configlink_mods[i].rst_name); - ret = PTR_ERR(rst); - return ret; - } - - /* just check presence of the reset control in DT */ - reset_control_put(rst); - } - ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub), GFP_KERNEL); if (!ahub) return -ENOMEM; dev_set_drvdata(&pdev->dev, ahub); + BUILD_BUG_ON(sizeof(ahub->resets) != sizeof(tegra30_ahub_resets_data)); + memcpy(ahub->resets, tegra30_ahub_resets_data, sizeof(ahub->resets)); + + ahub->nresets = soc_data->num_resets; ahub->soc_data = soc_data; ahub->dev = &pdev->dev; @@ -579,10 +546,11 @@ static int tegra30_ahub_probe(struct platform_device *pdev) if (ret) return ret; - ahub->reset = devm_reset_control_array_get_exclusive(&pdev->dev); - if (IS_ERR(ahub->reset)) { - dev_err(&pdev->dev, "Can't get resets: %pe\n", ahub->reset); - return PTR_ERR(ahub->reset); + ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ahub->nresets, + ahub->resets); + if (ret) { + dev_err(&pdev->dev, "Can't get resets: %d\n", ret); + return ret; } res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/sound/soc/tegra/tegra30_ahub.h b/sound/soc/tegra/tegra30_ahub.h index 3b85244f87f1..c9eaf4ec8f6e 100644 --- a/sound/soc/tegra/tegra30_ahub.h +++ b/sound/soc/tegra/tegra30_ahub.h @@ -491,7 +491,7 @@ void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg, struct tegra30_ahub_cif_conf *conf); struct tegra30_ahub_soc_data { - u32 mod_list_mask; + unsigned int num_resets; void (*set_audio_cif)(struct regmap *regmap, unsigned int reg, struct tegra30_ahub_cif_conf *conf); @@ -511,7 +511,8 @@ struct tegra30_ahub_soc_data { struct tegra30_ahub { const struct tegra30_ahub_soc_data *soc_data; struct device *dev; - struct reset_control *reset; + struct reset_control_bulk_data resets[21]; + unsigned int nresets; struct clk_bulk_data clocks[2]; unsigned int nclocks; resource_size_t apbif_addr; diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 3d22c1be6f3d..614b67be1dd9 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include -- cgit From 0911f154a2ae264ee2a7c868c1267a102396d016 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:48 +0300 Subject: ASoC: tegra20: spdif: Correct driver removal order Tegra20 SPDIF driver has a wrong driver removal order, which should be opposite to the registration order, but it's not. In particular the runtime PM is disabled in a wrong order. Fix the order. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-7-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_spdif.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 5839833e23a0..e48d332506a0 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -329,13 +329,13 @@ err_pm_disable: static int tegra20_spdif_platform_remove(struct platform_device *pdev) { + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + pm_runtime_disable(&pdev->dev); if (!pm_runtime_status_suspended(&pdev->dev)) tegra20_spdif_runtime_suspend(&pdev->dev); - tegra_pcm_platform_unregister(&pdev->dev); - snd_soc_unregister_component(&pdev->dev); - return 0; } -- cgit From c53b396f0dd49a626ea2b1fc0a8b9e0a0bf95d4d Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:49 +0300 Subject: ASoC: tegra20: spdif: Remove handing of disabled runtime PM Runtime PM is always available on Tegra since commit 40b2bb1b132a ("ARM: tegra: enforce PM requirement"), hence there is no need to handle the case of a disabled RPM by Tegra drivers. Remove handing of a disabled runtime PM from Tegra20 SPDIF driver. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-8-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_spdif.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index e48d332506a0..7b597ee63cb5 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -294,18 +294,13 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) spdif->playback_dma_data.slave_id = dmareq->start; pm_runtime_enable(&pdev->dev); - if (!pm_runtime_enabled(&pdev->dev)) { - ret = tegra20_spdif_runtime_resume(&pdev->dev); - if (ret) - goto err_pm_disable; - } ret = snd_soc_register_component(&pdev->dev, &tegra20_spdif_component, &tegra20_spdif_dai, 1); if (ret) { dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); ret = -ENOMEM; - goto err_suspend; + goto err_pm_disable; } ret = tegra_pcm_platform_register(&pdev->dev); @@ -318,9 +313,6 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) err_unregister_component: snd_soc_unregister_component(&pdev->dev); -err_suspend: - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra20_spdif_runtime_suspend(&pdev->dev); err_pm_disable: pm_runtime_disable(&pdev->dev); @@ -333,8 +325,6 @@ static int tegra20_spdif_platform_remove(struct platform_device *pdev) snd_soc_unregister_component(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra20_spdif_runtime_suspend(&pdev->dev); return 0; } -- cgit From e33fdd9bee12be35d080bfd4acc9d1e3a0d04001 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:50 +0300 Subject: ASoC: tegra20: i2s: Add system level suspend-resume callbacks Add system level suspend-resume callbacks in order to ensure that I2S is gated before system is suspended. This puts Tegra20 I2S driver on par with the Tegra30 I2S driver. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-9-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_i2s.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index c0af5352b483..267517446d27 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -460,6 +460,8 @@ static const struct of_device_id tegra20_i2s_of_match[] = { static const struct dev_pm_ops tegra20_i2s_pm_ops = { SET_RUNTIME_PM_OPS(tegra20_i2s_runtime_suspend, tegra20_i2s_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; static struct platform_driver tegra20_i2s_driver = { -- cgit From ca6e960ed6b10ba9236da8b3614574bb4524c65e Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:51 +0300 Subject: ASoC: tegra20: i2s: Correct driver removal order Tegra20 I2S driver has a wrong driver removal order, which should be opposite to the registration order, but it's not. In particular the runtime PM is disabled in a wrong order. Fix the order. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-10-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_i2s.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index 267517446d27..0f2bdc2e8598 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -440,13 +440,13 @@ static int tegra20_i2s_platform_remove(struct platform_device *pdev) { struct tegra20_i2s *i2s = dev_get_drvdata(&pdev->dev); + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + pm_runtime_disable(&pdev->dev); if (!pm_runtime_status_suspended(&pdev->dev)) tegra20_i2s_runtime_suspend(&pdev->dev); - tegra_pcm_platform_unregister(&pdev->dev); - snd_soc_unregister_component(&pdev->dev); - clk_put(i2s->clk_i2s); return 0; -- cgit From d3c6ef98dadd1e500445e4c5a9d684cbf3182c7d Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:52 +0300 Subject: ASoC: tegra20: i2s: Use devm_clk_get() Use resource-managed variant of clk_get() to simplify code. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-11-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_i2s.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index 0f2bdc2e8598..fe569198b17a 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -370,7 +370,7 @@ static int tegra20_i2s_platform_probe(struct platform_device *pdev) return PTR_ERR(i2s->reset); } - i2s->clk_i2s = clk_get(&pdev->dev, NULL); + i2s->clk_i2s = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(i2s->clk_i2s)) { dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); ret = PTR_ERR(i2s->clk_i2s); @@ -381,7 +381,7 @@ static int tegra20_i2s_platform_probe(struct platform_device *pdev) regs = devm_ioremap_resource(&pdev->dev, mem); if (IS_ERR(regs)) { ret = PTR_ERR(regs); - goto err_clk_put; + goto err; } i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, @@ -389,7 +389,7 @@ static int tegra20_i2s_platform_probe(struct platform_device *pdev) if (IS_ERR(i2s->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); ret = PTR_ERR(i2s->regmap); - goto err_clk_put; + goto err; } i2s->capture_dma_data.addr = mem->start + TEGRA20_I2S_FIFO2; @@ -430,16 +430,12 @@ err_suspend: tegra20_i2s_runtime_suspend(&pdev->dev); err_pm_disable: pm_runtime_disable(&pdev->dev); -err_clk_put: - clk_put(i2s->clk_i2s); err: return ret; } static int tegra20_i2s_platform_remove(struct platform_device *pdev) { - struct tegra20_i2s *i2s = dev_get_drvdata(&pdev->dev); - tegra_pcm_platform_unregister(&pdev->dev); snd_soc_unregister_component(&pdev->dev); @@ -447,8 +443,6 @@ static int tegra20_i2s_platform_remove(struct platform_device *pdev) if (!pm_runtime_status_suspended(&pdev->dev)) tegra20_i2s_runtime_suspend(&pdev->dev); - clk_put(i2s->clk_i2s); - return 0; } -- cgit From 80ec4a4cb36d3f8bb56b5aa89faceb1145ef7aea Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:53 +0300 Subject: ASoC: tegra20: i2s: Remove handing of disabled runtime PM Runtime PM is always available on Tegra since commit 40b2bb1b132a ("ARM: tegra: enforce PM requirement"), hence there is no need to handle the case of a disabled RPM by Tegra drivers. Remove handing of a disabled runtime PM from Tegra20 I2S driver. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-12-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_i2s.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index fe569198b17a..1b27f81c10fe 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -401,18 +401,13 @@ static int tegra20_i2s_platform_probe(struct platform_device *pdev) i2s->playback_dma_data.maxburst = 4; pm_runtime_enable(&pdev->dev); - if (!pm_runtime_enabled(&pdev->dev)) { - ret = tegra20_i2s_runtime_resume(&pdev->dev); - if (ret) - goto err_pm_disable; - } ret = snd_soc_register_component(&pdev->dev, &tegra20_i2s_component, &i2s->dai, 1); if (ret) { dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); ret = -ENOMEM; - goto err_suspend; + goto err_pm_disable; } ret = tegra_pcm_platform_register(&pdev->dev); @@ -425,9 +420,6 @@ static int tegra20_i2s_platform_probe(struct platform_device *pdev) err_unregister_component: snd_soc_unregister_component(&pdev->dev); -err_suspend: - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra20_i2s_runtime_suspend(&pdev->dev); err_pm_disable: pm_runtime_disable(&pdev->dev); err: @@ -438,10 +430,7 @@ static int tegra20_i2s_platform_remove(struct platform_device *pdev) { tegra_pcm_platform_unregister(&pdev->dev); snd_soc_unregister_component(&pdev->dev); - pm_runtime_disable(&pdev->dev); - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra20_i2s_runtime_suspend(&pdev->dev); return 0; } -- cgit From f852e1e4acf4ebde4c960bab6f89407fa18ca489 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:54 +0300 Subject: ASoC: tegra30: i2s: Correct driver removal order Tegra30 I2S driver has a wrong driver removal order, which should be opposite to the registration order, but it's not. In particular the runtime PM is disabled in a wrong order. Fix the order. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-13-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra30_i2s.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 614b67be1dd9..31e08c5c1405 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -544,10 +544,6 @@ static int tegra30_i2s_platform_remove(struct platform_device *pdev) { struct tegra30_i2s *i2s = dev_get_drvdata(&pdev->dev); - pm_runtime_disable(&pdev->dev); - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra30_i2s_runtime_suspend(&pdev->dev); - tegra_pcm_platform_unregister(&pdev->dev); snd_soc_unregister_component(&pdev->dev); @@ -557,6 +553,10 @@ static int tegra30_i2s_platform_remove(struct platform_device *pdev) tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_i2s_runtime_suspend(&pdev->dev); + clk_put(i2s->clk_i2s); return 0; -- cgit From 52674aef9eb678f30d99f77fd53f6c564d5e2d92 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:55 +0300 Subject: ASoC: tegra30: i2s: Use devm_clk_get() Use resource-managed variant of clk_get() to simplify code. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-14-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra30_i2s.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 31e08c5c1405..0ed982548ce4 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -438,7 +438,7 @@ static int tegra30_i2s_platform_probe(struct platform_device *pdev) i2s->playback_i2s_cif = cif_ids[0]; i2s->capture_i2s_cif = cif_ids[1]; - i2s->clk_i2s = clk_get(&pdev->dev, NULL); + i2s->clk_i2s = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(i2s->clk_i2s)) { dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); ret = PTR_ERR(i2s->clk_i2s); @@ -448,7 +448,7 @@ static int tegra30_i2s_platform_probe(struct platform_device *pdev) regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) { ret = PTR_ERR(regs); - goto err_clk_put; + goto err; } i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, @@ -456,7 +456,7 @@ static int tegra30_i2s_platform_probe(struct platform_device *pdev) if (IS_ERR(i2s->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); ret = PTR_ERR(i2s->regmap); - goto err_clk_put; + goto err; } regcache_cache_only(i2s->regmap, true); @@ -534,8 +534,6 @@ err_suspend: tegra30_i2s_runtime_suspend(&pdev->dev); err_pm_disable: pm_runtime_disable(&pdev->dev); -err_clk_put: - clk_put(i2s->clk_i2s); err: return ret; } @@ -557,8 +555,6 @@ static int tegra30_i2s_platform_remove(struct platform_device *pdev) if (!pm_runtime_status_suspended(&pdev->dev)) tegra30_i2s_runtime_suspend(&pdev->dev); - clk_put(i2s->clk_i2s); - return 0; } -- cgit From b5f6f781fcb27b3ae5a2f04312a190115b5cbbd1 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:56 +0300 Subject: ASoC: tegra30: i2s: Remove handing of disabled runtime PM Runtime PM is always available on Tegra since commit 40b2bb1b132a ("ARM: tegra: enforce PM requirement"), hence there is no need to handle the case of a disabled RPM by Tegra drivers. Remove handing of a disabled runtime PM from Tegra30 I2S driver. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-15-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra30_i2s.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 0ed982548ce4..8730ffa0f691 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -461,11 +461,6 @@ static int tegra30_i2s_platform_probe(struct platform_device *pdev) regcache_cache_only(i2s->regmap, true); pm_runtime_enable(&pdev->dev); - if (!pm_runtime_enabled(&pdev->dev)) { - ret = tegra30_i2s_runtime_resume(&pdev->dev); - if (ret) - goto err_pm_disable; - } i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; i2s->playback_dma_data.maxburst = 4; @@ -475,7 +470,7 @@ static int tegra30_i2s_platform_probe(struct platform_device *pdev) &i2s->playback_dma_data.addr); if (ret) { dev_err(&pdev->dev, "Could not alloc TX FIFO: %d\n", ret); - goto err_suspend; + goto err_pm_disable; } ret = tegra30_ahub_set_rx_cif_source(i2s->playback_i2s_cif, i2s->playback_fifo_cif); @@ -529,9 +524,6 @@ err_unroute_tx_fifo: tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); err_free_tx_fifo: tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); -err_suspend: - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra30_i2s_runtime_suspend(&pdev->dev); err_pm_disable: pm_runtime_disable(&pdev->dev); err: @@ -552,8 +544,6 @@ static int tegra30_i2s_platform_remove(struct platform_device *pdev) tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); pm_runtime_disable(&pdev->dev); - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra30_i2s_runtime_suspend(&pdev->dev); return 0; } -- cgit From 5d956e3cb806870012c443bc265e6ac6188d3c36 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:57 +0300 Subject: ASoC: tegra30: ahub: Reset global variable Tegra30 AHUB uses global variable that is never reset by the driver on a probe failure and on driver removal, meaning that driver will never try to re-probe and can't be unbound. Make driver to reset the variable. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-16-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra30_ahub.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c index d24c26f4960d..409a259e6343 100644 --- a/sound/soc/tegra/tegra30_ahub.c +++ b/sound/soc/tegra/tegra30_ahub.c @@ -518,9 +518,6 @@ static int tegra30_ahub_probe(struct platform_device *pdev) void __iomem *regs_apbif, *regs_ahub; int ret = 0; - if (ahub) - return -ENODEV; - match = of_match_device(tegra30_ahub_of_match, &pdev->dev); if (!match) return -EINVAL; @@ -544,19 +541,21 @@ static int tegra30_ahub_probe(struct platform_device *pdev) ret = devm_clk_bulk_get(&pdev->dev, ahub->nclocks, ahub->clocks); if (ret) - return ret; + goto err_unset_ahub; ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ahub->nresets, ahub->resets); if (ret) { dev_err(&pdev->dev, "Can't get resets: %d\n", ret); - return ret; + goto err_unset_ahub; } res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs_apbif = devm_ioremap_resource(&pdev->dev, res0); - if (IS_ERR(regs_apbif)) - return PTR_ERR(regs_apbif); + if (IS_ERR(regs_apbif)) { + ret = PTR_ERR(regs_apbif); + goto err_unset_ahub; + } ahub->apbif_addr = res0->start; @@ -565,20 +564,22 @@ static int tegra30_ahub_probe(struct platform_device *pdev) if (IS_ERR(ahub->regmap_apbif)) { dev_err(&pdev->dev, "apbif regmap init failed\n"); ret = PTR_ERR(ahub->regmap_apbif); - return ret; + goto err_unset_ahub; } regcache_cache_only(ahub->regmap_apbif, true); regs_ahub = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(regs_ahub)) - return PTR_ERR(regs_ahub); + if (IS_ERR(regs_ahub)) { + ret = PTR_ERR(regs_ahub); + goto err_unset_ahub; + } ahub->regmap_ahub = devm_regmap_init_mmio(&pdev->dev, regs_ahub, &tegra30_ahub_ahub_regmap_config); if (IS_ERR(ahub->regmap_ahub)) { dev_err(&pdev->dev, "ahub regmap init failed\n"); ret = PTR_ERR(ahub->regmap_ahub); - return ret; + goto err_unset_ahub; } regcache_cache_only(ahub->regmap_ahub, true); @@ -595,19 +596,20 @@ static int tegra30_ahub_probe(struct platform_device *pdev) err_pm_disable: pm_runtime_disable(&pdev->dev); +err_unset_ahub: + ahub = NULL; return ret; } static int tegra30_ahub_remove(struct platform_device *pdev) { - if (!ahub) - return -ENODEV; - pm_runtime_disable(&pdev->dev); if (!pm_runtime_status_suspended(&pdev->dev)) tegra30_ahub_runtime_suspend(&pdev->dev); + ahub = NULL; + return 0; } -- cgit From e2965c2ca139e780dc353cef1474103bb037136e Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:58 +0300 Subject: ASoC: tegra30: ahub: Correct suspend-resume callbacks Tegra30 AHUB driver always syncs hardware state on a runtime PM resume, hence there is no needed to re-sync the state on system resume. Replace the suspend-resume callbacks with a generic helpers which ensure that AHUB is suspended using RPM callbacks across system suspend-resume. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-17-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra30_ahub.c | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c index 409a259e6343..e64b41bc5fdf 100644 --- a/sound/soc/tegra/tegra30_ahub.c +++ b/sound/soc/tegra/tegra30_ahub.c @@ -613,36 +613,11 @@ static int tegra30_ahub_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM_SLEEP -static int tegra30_ahub_suspend(struct device *dev) -{ - regcache_mark_dirty(ahub->regmap_ahub); - regcache_mark_dirty(ahub->regmap_apbif); - - return 0; -} - -static int tegra30_ahub_resume(struct device *dev) -{ - int ret; - - ret = pm_runtime_get_sync(dev); - if (ret < 0) { - pm_runtime_put(dev); - return ret; - } - ret = regcache_sync(ahub->regmap_ahub); - ret |= regcache_sync(ahub->regmap_apbif); - pm_runtime_put(dev); - - return ret; -} -#endif - static const struct dev_pm_ops tegra30_ahub_pm_ops = { SET_RUNTIME_PM_OPS(tegra30_ahub_runtime_suspend, tegra30_ahub_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(tegra30_ahub_suspend, tegra30_ahub_resume) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; static struct platform_driver tegra30_ahub_driver = { -- cgit From b5571449e6186bd37e8da16e7bce53f621c05e72 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Sun, 14 Mar 2021 18:44:59 +0300 Subject: ASoC: tegra30: ahub: Remove handing of disabled runtime PM Runtime PM is always available on Tegra since commit 40b2bb1b132a ("ARM: tegra: enforce PM requirement"), hence there is no need to handle the case of a disabled RPM by Tegra drivers. Remove handing of a disabled runtime PM from Tegra30 AHUB driver. Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20210314154459.15375-18-digetx@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra30_ahub.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c index e64b41bc5fdf..d1718f3af3cd 100644 --- a/sound/soc/tegra/tegra30_ahub.c +++ b/sound/soc/tegra/tegra30_ahub.c @@ -584,18 +584,11 @@ static int tegra30_ahub_probe(struct platform_device *pdev) regcache_cache_only(ahub->regmap_ahub, true); pm_runtime_enable(&pdev->dev); - if (!pm_runtime_enabled(&pdev->dev)) { - ret = tegra30_ahub_runtime_resume(&pdev->dev); - if (ret) - goto err_pm_disable; - } of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); return 0; -err_pm_disable: - pm_runtime_disable(&pdev->dev); err_unset_ahub: ahub = NULL; @@ -605,8 +598,6 @@ err_unset_ahub: static int tegra30_ahub_remove(struct platform_device *pdev) { pm_runtime_disable(&pdev->dev); - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra30_ahub_runtime_suspend(&pdev->dev); ahub = NULL; -- cgit