[PATCH v1 00/20] Support HDMI audio on NVIDIA Tegra20
This series revives Tegra20 S/PDIF driver which was upstreamed long time ago, but never was used. It also turns Tegra DRM HDMI driver into HDMI audio CODEC provider. Finally, HDMI audio is enabled in device-trees. For now the audio is enable only for Acer A500 tablet and Toshiba AC100 netbook because they're already supported by upstream, later on ASUS TF101 tablet will join them.
I based S/PDIF patches on Arnd's Bergmann patch from a separate series [1] that removes obsolete slave_id. This eases merging of the patches by removing the merge conflict.
[1] https://patchwork.ozlabs.org/project/linux-tegra/list/?submitter=80402
Arnd Bergmann (1): ASoC: tegra20-spdif: stop setting slave_id
Dmitry Osipenko (19): ASoC: dt-bindings: Add binding for Tegra20 S/PDIF ASoC: dt-bindings: tegra20-i2s: Convert to schema ASoC: dt-bindings: tegra20-i2s: Document new nvidia,fixed-parent-rate property dt-bindings: host1x: Document optional HDMI sound-dai-cells ASoC: tegra20: spdif: Support device-tree ASoC: tegra20: spdif: Set FIFO trigger level ASoC: tegra20: spdif: Improve driver's code ASoC: tegra20: spdif: Use more resource-managed helpers ASoC: tegra20: spdif: Reset hardware ASoC: tegra20: spdif: Support system suspend ASoC: tegra20: spdif: Filter out unsupported rates ASoC: tegra20: i2s: Filter out unsupported rates drm/tegra: hdmi: Unwind tegra_hdmi_init() errors drm/tegra: hdmi: Register audio CODEC on Tegra20 ARM: tegra_defconfig: Enable S/PDIF driver ARM: tegra: Add S/PDIF node to Tegra20 device-tree ARM: tegra: Add HDMI audio graph to Tegra20 device-tree ARM: tegra: acer-a500: Enable S/PDIF and HDMI audio ARM: tegra: paz00: Enable S/PDIF and HDMI audio
.../display/tegra/nvidia,tegra20-host1x.txt | 1 + .../bindings/sound/nvidia,tegra20-i2s.txt | 30 --- .../bindings/sound/nvidia,tegra20-i2s.yaml | 78 +++++++ .../bindings/sound/nvidia,tegra20-spdif.yaml | 88 ++++++++ .../boot/dts/tegra20-acer-a500-picasso.dts | 8 + arch/arm/boot/dts/tegra20-paz00.dts | 8 + arch/arm/boot/dts/tegra20.dtsi | 40 +++- arch/arm/configs/tegra_defconfig | 1 + drivers/gpu/drm/tegra/Kconfig | 3 + drivers/gpu/drm/tegra/hdmi.c | 168 +++++++++++++-- sound/soc/tegra/tegra20_i2s.c | 49 +++++ sound/soc/tegra/tegra20_spdif.c | 195 +++++++++++++----- sound/soc/tegra/tegra20_spdif.h | 1 + sound/soc/tegra/tegra_pcm.c | 6 + sound/soc/tegra/tegra_pcm.h | 1 + 15 files changed, 575 insertions(+), 102 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.txt create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-spdif.yaml
Add device-tree binding for Tegra20 S/PDIF controller.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- .../bindings/sound/nvidia,tegra20-spdif.yaml | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-spdif.yaml
diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra20-spdif.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra20-spdif.yaml new file mode 100644 index 000000000000..4a2747d64772 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra20-spdif.yaml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra20-spdif.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra20 S/PDIF Controller + +description: | + The S/PDIF controller supports both input and output in serial audio + digital interface format. The input controller can digitally recover + a clock from the received stream. The S/PDIF controller is also used + to generate the embedded audio for HDMI output channel. + +maintainers: + - Thierry Reding treding@nvidia.com + - Jon Hunter jonathanh@nvidia.com + +properties: + compatible: + const: nvidia,tegra20-spdif + + reg: + maxItems: 1 + + resets: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + minItems: 2 + + clock-names: + items: + - const: spdif_out + - const: spdif_in + + dmas: + minItems: 2 + + dma-names: + items: + - const: rx + - const: tx + + "#sound-dai-cells": + const: 0 + + nvidia,fixed-parent-rate: + description: | + Specifies whether board prefers parent clock to stay at a fixed rate. + This allows multiple Tegra20 audio components work simultaneously by + limiting number of supportable audio rates. + type: boolean + + assigned-clocks: true + assigned-clock-parents: true + +required: + - compatible + - reg + - resets + - interrupts + - clocks + - clock-names + - dmas + - dma-names + - "#sound-dai-cells" + +additionalProperties: false + +examples: + - | + spdif@70002400 { + compatible = "nvidia,tegra20-spdif"; + reg = <0x70002400 0x200>; + interrupts = <77>; + clocks = <&tegra_car99>, <&tegra_car 98>; + clock-names = "spdif_out", "spdif_in"; + resets = <&tegra_car 10>; + dmas = <&apbdma 3>, <&apbdma 3>; + dma-names = "rx", "tx"; + #sound-dai-cells = <0>; + }; + +...
Convert NVIDIA Tegra20 I2S binding to schema.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- .../bindings/sound/nvidia,tegra20-i2s.txt | 30 -------- .../bindings/sound/nvidia,tegra20-i2s.yaml | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 30 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.txt create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml
diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.txt b/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.txt deleted file mode 100644 index dc30c6bfbe95..000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.txt +++ /dev/null @@ -1,30 +0,0 @@ -NVIDIA Tegra 20 I2S controller - -Required properties: -- compatible : "nvidia,tegra20-i2s" -- reg : Should contain I2S registers location and length -- interrupts : Should contain I2S interrupt -- resets : Must contain an entry for each entry in reset-names. - See ../reset/reset.txt for details. -- reset-names : Must include the following entries: - - i2s -- dmas : Must contain an entry for each entry in clock-names. - See ../dma/dma.txt for details. -- dma-names : Must include the following entries: - - rx - - tx -- clocks : Must contain one entry, for the module clock. - See ../clocks/clock-bindings.txt for details. - -Example: - -i2s@70002800 { - compatible = "nvidia,tegra20-i2s"; - reg = <0x70002800 0x200>; - interrupts = < 45 >; - clocks = <&tegra_car 11>; - resets = <&tegra_car 11>; - reset-names = "i2s"; - dmas = <&apbdma 21>, <&apbdma 21>; - dma-names = "rx", "tx"; -}; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml new file mode 100644 index 000000000000..cd87eb4ef995 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra20-i2s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra20 I2S Controller + +description: | + The I2S Controller streams synchronous serial audio data between system + memory and an external audio device. The controller supports the I2S Left + Justified Mode, Right Justified Mode, and DSP mode formats. + +maintainers: + - Thierry Reding treding@nvidia.com + - Jon Hunter jonathanh@nvidia.com + +properties: + compatible: + const: nvidia,tegra20-i2s + + reg: + maxItems: 1 + + resets: + maxItems: 1 + + reset-names: + const: i2s + + interrupts: + maxItems: 1 + + clocks: + minItems: 1 + + dmas: + minItems: 2 + + dma-names: + items: + - const: rx + - const: tx + +required: + - compatible + - reg + - resets + - reset-names + - interrupts + - clocks + - clock-names + - dmas + - dma-names + +additionalProperties: false + +examples: + - | + i2s@70002800 { + compatible = "nvidia,tegra20-i2s"; + reg = <0x70002800 0x200>; + interrupts = <45>; + clocks = <&tegra_car 11>; + resets = <&tegra_car 11>; + reset-names = "i2s"; + dmas = <&apbdma 21>, <&apbdma 21>; + dma-names = "rx", "tx"; + }; + +...
On Thu, 25 Nov 2021 01:00:39 +0300, Dmitry Osipenko wrote:
Convert NVIDIA Tegra20 I2S binding to schema.
Signed-off-by: Dmitry Osipenko digetx@gmail.com
.../bindings/sound/nvidia,tegra20-i2s.txt | 30 -------- .../bindings/sound/nvidia,tegra20-i2s.yaml | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 30 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.txt create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml
My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check' on your patch (DT_CHECKER_FLAGS is new in v5.13):
yamllint warnings/errors:
dtschema/dtc warnings/errors: /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.example.dt.yaml: i2s@70002800: 'clock-names' is a required property From schema: /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/patch/1559387
This check can fail if there are any dependencies. The base for a patch series is generally the most recent rc1.
If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date:
pip3 install dtschema --upgrade
Please check and re-submit.
26.11.2021 00:26, Rob Herring пишет:
On Thu, 25 Nov 2021 01:00:39 +0300, Dmitry Osipenko wrote:
Convert NVIDIA Tegra20 I2S binding to schema.
Signed-off-by: Dmitry Osipenko digetx@gmail.com
.../bindings/sound/nvidia,tegra20-i2s.txt | 30 -------- .../bindings/sound/nvidia,tegra20-i2s.yaml | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 30 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.txt create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml
My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check' on your patch (DT_CHECKER_FLAGS is new in v5.13):
yamllint warnings/errors:
dtschema/dtc warnings/errors: /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.example.dt.yaml: i2s@70002800: 'clock-names' is a required property From schema: /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/patch/1559387
This check can fail if there are any dependencies. The base for a patch series is generally the most recent rc1.
If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date:
pip3 install dtschema --upgrade
Please check and re-submit.
Indeed, I missed to re-check the I2S binding after last minute changes. That's compelling reason to make v2.
Then I'll also remove the now-unnecessary assigned-clocks property from the S/PDIF binding.
Document new nvidia,fixed-parent-rate property which instructs that this board wants parent clock to stay at a fixed rate. It allows to prevent conflicts between audio components that share same parent PLL. For instance, this property allows to have HDMI audio, speaker and headphones in the system playing audio simultaneously, which is a common pattern for consumer devices.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- .../devicetree/bindings/sound/nvidia,tegra20-i2s.yaml | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml index cd87eb4ef995..28f964c759f6 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml @@ -42,6 +42,13 @@ properties: - const: rx - const: tx
+ nvidia,fixed-parent-rate: + description: | + Specifies whether board prefers parent clock to stay at a fixed rate. + This allows multiple Tegra20 audio components work simultaneously by + limiting number of supportable audio rates. + type: boolean + required: - compatible - reg
Document new optional sound-dai-cells property of HDMI node. This node will be used as endpoint of HDMI sound DAI graph.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- .../devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt | 1 + 1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt index e61999ce54e9..27b746f28f31 100644 --- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt +++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt @@ -297,6 +297,7 @@ of the following host1x client modules: - nvidia,edid: supplies a binary EDID blob - nvidia,panel: phandle of a display panel - operating-points-v2: See ../bindings/opp/opp.txt for details. + - #sound-dai-cells: Should be 0.
- tvo: TV encoder output
From: Arnd Bergmann arnd@arndb.de
The DMA resource is never set up anywhere, and passing this as slave_id has not been the proper procedure in a long time.
As a preparation for removing all slave_id references from the ALSA code, remove this one.
According to Dmitry Osipenko, this driver has never been used and the mechanism for configuring DMA would not work as it is implemented, so this part will get rewritten when the driver gets put into use again in the future.
Reviewed-by: Dmitry Osipenko digetx@gmail.com Signed-off-by: Arnd Bergmann arnd@arndb.de --- sound/soc/tegra/tegra20_spdif.c | 1 - 1 file changed, 1 deletion(-)
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 7751575cd6d6..57a6c576b91f 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -290,7 +290,6 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT; spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; spdif->playback_dma_data.maxburst = 4; - spdif->playback_dma_data.slave_id = dmareq->start;
pm_runtime_enable(&pdev->dev);
Tegra20 S/PDIF driver was added in a pre-DT era and was never used since that time. Revive driver by adding device-tree support.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- sound/soc/tegra/tegra20_spdif.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 57a6c576b91f..bd81df5378d1 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -10,6 +10,7 @@ #include <linux/device.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -332,10 +333,17 @@ static const struct dev_pm_ops tegra20_spdif_pm_ops = { tegra20_spdif_runtime_resume, NULL) };
+static const struct of_device_id tegra20_spdif_of_match[] = { + { .compatible = "nvidia,tegra20-spdif", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra20_spdif_of_match); + static struct platform_driver tegra20_spdif_driver = { .driver = { .name = DRV_NAME, .pm = &tegra20_spdif_pm_ops, + .of_match_table = tegra20_spdif_of_match, }, .probe = tegra20_spdif_platform_probe, .remove = tegra20_spdif_platform_remove, @@ -346,4 +354,3 @@ module_platform_driver(tegra20_spdif_driver); MODULE_AUTHOR("Stephen Warren swarren@nvidia.com"); MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME);
Program FIFO trigger level properly to fix x4 accelerated playback.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- sound/soc/tegra/tegra20_spdif.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index bd81df5378d1..6650875d2555 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -70,6 +70,14 @@ static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream,
regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, mask, val);
+ /* + * FIFO trigger level must be bigger than DMA burst or equal to it, + * otherwise data is discarded on overflow. + */ + regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_DATA_FIFO_CSR, + TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK, + TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL); + switch (params_rate(params)) { case 32000: spdifclock = 4096000;
On Thu, Nov 25, 2021 at 01:00:44AM +0300, Dmitry Osipenko wrote:
Program FIFO trigger level properly to fix x4 accelerated playback.
Fixes like this should really go before any new stuff so they can be sent as fixes and backported.
25.11.2021 15:02, Mark Brown пишет:
On Thu, Nov 25, 2021 at 01:00:44AM +0300, Dmitry Osipenko wrote:
Program FIFO trigger level properly to fix x4 accelerated playback.
Fixes like this should really go before any new stuff so they can be sent as fixes and backported.
This driver never worked before this patchset, hence there is nothing to backport, this is explained in the cover letter. But in general you're correct.
On Thu, Nov 25, 2021 at 03:04:35PM +0300, Dmitry Osipenko wrote:
25.11.2021 15:02, Mark Brown пишет:
On Thu, Nov 25, 2021 at 01:00:44AM +0300, Dmitry Osipenko wrote:
Program FIFO trigger level properly to fix x4 accelerated playback.
Fixes like this should really go before any new stuff so they can be sent as fixes and backported.
This driver never worked before this patchset, hence there is nothing to backport, this is explained in the cover letter. But in general you're correct.
That's not going to stop the stable people backporting things, and I'd guess it might've worked at some point on some systems - I'm not seeing anything that jumps out as making the driver completely unworkable in your patches.
25.11.2021 15:28, Mark Brown пишет:
On Thu, Nov 25, 2021 at 03:04:35PM +0300, Dmitry Osipenko wrote:
25.11.2021 15:02, Mark Brown пишет:
On Thu, Nov 25, 2021 at 01:00:44AM +0300, Dmitry Osipenko wrote:
Program FIFO trigger level properly to fix x4 accelerated playback.
Fixes like this should really go before any new stuff so they can be sent as fixes and backported.
This driver never worked before this patchset, hence there is nothing to backport, this is explained in the cover letter. But in general you're correct.
That's not going to stop the stable people backporting things, and I'd guess it might've worked at some point on some systems - I'm not seeing anything that jumps out as making the driver completely unworkable in your patches.
I can change commit message with the "fix" word removed, this should prevent patch from backporting.
This driver never worked in mainline because S/PDIF device was never created, thus driver was never bound. Driver doesn't work properly without this patch. Nobody used this driver as-is before this patchset.
On Thu, Nov 25, 2021 at 03:53:52PM +0300, Dmitry Osipenko wrote:
25.11.2021 15:28, Mark Brown пишет:
On Thu, Nov 25, 2021 at 03:04:35PM +0300, Dmitry Osipenko wrote:
This driver never worked before this patchset, hence there is nothing to backport, this is explained in the cover letter. But in general you're correct.
That's not going to stop the stable people backporting things, and I'd guess it might've worked at some point on some systems - I'm not seeing anything that jumps out as making the driver completely unworkable in your patches.
I can change commit message with the "fix" word removed, this should prevent patch from backporting.
I wouldn't count on it TBH. In any case, definitely no need to resend for this alone.
This driver never worked in mainline because S/PDIF device was never created, thus driver was never bound. Driver doesn't work properly without this patch. Nobody used this driver as-is before this patchset.
Someone might've been using it with an out of tree board file, I guess on an older stable at this point.
25.11.2021 16:18, Mark Brown пишет:
On Thu, Nov 25, 2021 at 03:53:52PM +0300, Dmitry Osipenko wrote:
25.11.2021 15:28, Mark Brown пишет:
On Thu, Nov 25, 2021 at 03:04:35PM +0300, Dmitry Osipenko wrote:
This driver never worked before this patchset, hence there is nothing to backport, this is explained in the cover letter. But in general you're correct.
That's not going to stop the stable people backporting things, and I'd guess it might've worked at some point on some systems - I'm not seeing anything that jumps out as making the driver completely unworkable in your patches.
I can change commit message with the "fix" word removed, this should prevent patch from backporting.
I wouldn't count on it TBH. In any case, definitely no need to resend for this alone.
Alright, I'll keep this in mind for a potential v2. I guess Rob may ask to remove the assigned-clocks from S/PDIF DT binding because I just found that there is no needed to specify that property explicitly anymore.
This driver never worked in mainline because S/PDIF device was never created, thus driver was never bound. Driver doesn't work properly without this patch. Nobody used this driver as-is before this patchset.
Someone might've been using it with an out of tree board file, I guess on an older stable at this point.
I'm very doubtful. Still, this patch could be easily backported because all code refactoring changes that potentially may cause merge conflicts are made after this patch. Ideally, if we really needed to backport this patch, then it should've been one of the first patches as you suggested.
- Clean up whitespaces, defines and variables.
- Remove obsolete code.
- Adhere to upstream coding style.
- Don't override returned error code.
- Replace pr_err with dev_err.
No functional changes are made by this patch. This is a minor code's refactoring that will ease further maintenance of the driver.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- sound/soc/tegra/tegra20_spdif.c | 49 ++++++++++++--------------------- 1 file changed, 18 insertions(+), 31 deletions(-)
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 6650875d2555..9383683aa4e9 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -23,8 +23,6 @@
#include "tegra20_spdif.h"
-#define DRV_NAME "tegra20-spdif" - static __maybe_unused int tegra20_spdif_runtime_suspend(struct device *dev) { struct tegra20_spdif *spdif = dev_get_drvdata(dev); @@ -49,11 +47,10 @@ static __maybe_unused int tegra20_spdif_runtime_resume(struct device *dev) }
static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { - struct device *dev = dai->dev; - struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); unsigned int mask = 0, val = 0; int ret, spdifclock;
@@ -106,7 +103,7 @@ static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream,
ret = clk_set_rate(spdif->clk_spdif_out, spdifclock); if (ret) { - dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret); + dev_err(dai->dev, "Can't set SPDIF clock rate: %d\n", ret); return ret; }
@@ -127,9 +124,9 @@ static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif) }
static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) + struct snd_soc_dai *dai) { - struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -151,7 +148,7 @@ static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
static int tegra20_spdif_probe(struct snd_soc_dai *dai) { - struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
dai->capture_dma_data = NULL; dai->playback_dma_data = &spdif->playback_dma_data; @@ -160,26 +157,26 @@ static int tegra20_spdif_probe(struct snd_soc_dai *dai) }
static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = { - .hw_params = tegra20_spdif_hw_params, - .trigger = tegra20_spdif_trigger, + .hw_params = tegra20_spdif_hw_params, + .trigger = tegra20_spdif_trigger, };
static struct snd_soc_dai_driver tegra20_spdif_dai = { - .name = DRV_NAME, + .name = "tegra20-spdif", .probe = tegra20_spdif_probe, .playback = { .stream_name = "Playback", .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000, + SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .ops = &tegra20_spdif_dai_ops, };
static const struct snd_soc_component_driver tegra20_spdif_component = { - .name = DRV_NAME, + .name = "tegra20-spdif", };
static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg) @@ -260,7 +257,7 @@ static const struct regmap_config tegra20_spdif_regmap_config = { static int tegra20_spdif_platform_probe(struct platform_device *pdev) { struct tegra20_spdif *spdif; - struct resource *mem, *dmareq; + struct resource *mem; void __iomem *regs; int ret;
@@ -273,27 +270,19 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev)
spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out"); if (IS_ERR(spdif->clk_spdif_out)) { - pr_err("Can't retrieve spdif clock\n"); - ret = PTR_ERR(spdif->clk_spdif_out); - return ret; + dev_err(&pdev->dev, "Could not retrieve spdif clock\n"); + return PTR_ERR(spdif->clk_spdif_out); }
regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); if (IS_ERR(regs)) return PTR_ERR(regs);
- dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!dmareq) { - dev_err(&pdev->dev, "No DMA resource\n"); - return -ENODEV; - } - spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, - &tegra20_spdif_regmap_config); + &tegra20_spdif_regmap_config); if (IS_ERR(spdif->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); - ret = PTR_ERR(spdif->regmap); - return ret; + return PTR_ERR(spdif->regmap); }
spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT; @@ -306,7 +295,6 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) &tegra20_spdif_dai, 1); if (ret) { dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); - ret = -ENOMEM; goto err_pm_disable; }
@@ -349,14 +337,13 @@ MODULE_DEVICE_TABLE(of, tegra20_spdif_of_match);
static struct platform_driver tegra20_spdif_driver = { .driver = { - .name = DRV_NAME, + .name = "tegra20-spdif", .pm = &tegra20_spdif_pm_ops, .of_match_table = tegra20_spdif_of_match, }, .probe = tegra20_spdif_platform_probe, .remove = tegra20_spdif_platform_remove, }; - module_platform_driver(tegra20_spdif_driver);
MODULE_AUTHOR("Stephen Warren swarren@nvidia.com");
Use resource-managed helpers to make code cleaner. Driver's remove callback isn't needed anymore since driver is completely resource-managed now.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- sound/soc/tegra/tegra20_spdif.c | 33 +++++++++------------------------ sound/soc/tegra/tegra_pcm.c | 6 ++++++ sound/soc/tegra/tegra_pcm.h | 1 + 3 files changed, 16 insertions(+), 24 deletions(-)
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 9383683aa4e9..6ca48bc322ae 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -289,38 +289,24 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; spdif->playback_dma_data.maxburst = 4;
- pm_runtime_enable(&pdev->dev); + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret;
- ret = snd_soc_register_component(&pdev->dev, &tegra20_spdif_component, - &tegra20_spdif_dai, 1); + ret = devm_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); - goto err_pm_disable; + return ret; }
- ret = tegra_pcm_platform_register(&pdev->dev); + ret = devm_tegra_pcm_platform_register(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); - goto err_unregister_component; + return ret; }
- return 0; - -err_unregister_component: - snd_soc_unregister_component(&pdev->dev); -err_pm_disable: - pm_runtime_disable(&pdev->dev); - - return ret; -} - -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); - return 0; }
@@ -342,7 +328,6 @@ static struct platform_driver tegra20_spdif_driver = { .of_match_table = tegra20_spdif_of_match, }, .probe = tegra20_spdif_platform_probe, - .remove = tegra20_spdif_platform_remove, }; module_platform_driver(tegra20_spdif_driver);
diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c index ef1e74d95236..468c8e77de21 100644 --- a/sound/soc/tegra/tegra_pcm.c +++ b/sound/soc/tegra/tegra_pcm.c @@ -48,6 +48,12 @@ int tegra_pcm_platform_register(struct device *dev) } EXPORT_SYMBOL_GPL(tegra_pcm_platform_register);
+int devm_tegra_pcm_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0); +} +EXPORT_SYMBOL_GPL(devm_tegra_pcm_platform_register); + int tegra_pcm_platform_register_with_chan_names(struct device *dev, struct snd_dmaengine_pcm_config *config, char *txdmachan, char *rxdmachan) diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h index d602126c65b7..2a36eea1740d 100644 --- a/sound/soc/tegra/tegra_pcm.h +++ b/sound/soc/tegra/tegra_pcm.h @@ -32,6 +32,7 @@ int tegra_pcm_hw_params(struct snd_soc_component *component, snd_pcm_uframes_t tegra_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream); int tegra_pcm_platform_register(struct device *dev); +int devm_tegra_pcm_platform_register(struct device *dev); int tegra_pcm_platform_register_with_chan_names(struct device *dev, struct snd_dmaengine_pcm_config *config, char *txdmachan, char *rxdmachan);
Reset S/PDIF controller on runtime PM suspend/resume to ensure that we always have a consistent hardware state.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- sound/soc/tegra/tegra20_spdif.c | 32 ++++++++++++++++++++++++++++++++ sound/soc/tegra/tegra20_spdif.h | 1 + 2 files changed, 33 insertions(+)
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 6ca48bc322ae..8b9bac30d5cd 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -7,6 +7,7 @@ */
#include <linux/clk.h> +#include <linux/delay.h> #include <linux/device.h> #include <linux/io.h> #include <linux/module.h> @@ -14,6 +15,7 @@ #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> +#include <linux/reset.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/pcm.h> @@ -27,6 +29,8 @@ static __maybe_unused int tegra20_spdif_runtime_suspend(struct device *dev) { struct tegra20_spdif *spdif = dev_get_drvdata(dev);
+ regcache_cache_only(spdif->regmap, true); + clk_disable_unprepare(spdif->clk_spdif_out);
return 0; @@ -37,13 +41,35 @@ static __maybe_unused int tegra20_spdif_runtime_resume(struct device *dev) struct tegra20_spdif *spdif = dev_get_drvdata(dev); int ret;
+ ret = reset_control_assert(spdif->reset); + if (ret) + return ret; + ret = clk_prepare_enable(spdif->clk_spdif_out); if (ret) { dev_err(dev, "clk_enable failed: %d\n", ret); return ret; }
+ usleep_range(10, 100); + + ret = reset_control_deassert(spdif->reset); + if (ret) + goto disable_clocks; + + regcache_cache_only(spdif->regmap, false); + regcache_mark_dirty(spdif->regmap); + + ret = regcache_sync(spdif->regmap); + if (ret) + goto disable_clocks; + return 0; + +disable_clocks: + clk_disable_unprepare(spdif->clk_spdif_out); + + return ret; }
static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, @@ -268,6 +294,12 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev)
dev_set_drvdata(&pdev->dev, spdif);
+ spdif->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(spdif->reset)) { + dev_err(&pdev->dev, "Can't retrieve spdif reset\n"); + return PTR_ERR(spdif->reset); + } + spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out"); if (IS_ERR(spdif->clk_spdif_out)) { dev_err(&pdev->dev, "Could not retrieve spdif clock\n"); diff --git a/sound/soc/tegra/tegra20_spdif.h b/sound/soc/tegra/tegra20_spdif.h index 1973ffc2d5c7..ff4b79e2052f 100644 --- a/sound/soc/tegra/tegra20_spdif.h +++ b/sound/soc/tegra/tegra20_spdif.h @@ -451,6 +451,7 @@ struct tegra20_spdif { 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
Support system suspend by enforcing runtime PM suspend/resume. Now there is no doubt that h/w is indeed stopped during suspend and that h/w state will be properly restored after resume.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- sound/soc/tegra/tegra20_spdif.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 8b9bac30d5cd..89f7fc5c8aad 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -345,6 +345,8 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) static const struct dev_pm_ops tegra20_spdif_pm_ops = { SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend, tegra20_spdif_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) };
static const struct of_device_id tegra20_spdif_of_match[] = {
SPDIF and other SoC components share audio PLL on Tegra, thus only one component may set the desired base clock rate. This creates problem for HDMI audio because it uses SPDIF and audio may not work if SPDIF's clock doesn't exactly match standard audio rate since some receivers may reject audio in that case. Filter out audio rates which SPDIF output can't support, assuming that other components won't change rate at runtime.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- sound/soc/tegra/tegra20_spdif.c | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+)
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 89f7fc5c8aad..f3e3c6df232b 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -79,6 +79,7 @@ static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); unsigned int mask = 0, val = 0; int ret, spdifclock; + long rate;
mask |= TEGRA20_SPDIF_CTRL_PACK | TEGRA20_SPDIF_CTRL_BIT_MODE_MASK; @@ -133,6 +134,12 @@ static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, return ret; }
+ rate = clk_get_rate(spdif->clk_spdif_out); + if (rate != spdifclock) + dev_warn(dai->dev, + "SPDIF clock rate %d doesn't match requested rate %lu\n", + spdifclock, rate); + return 0; }
@@ -172,6 +179,59 @@ static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd, return 0; }
+static int tegra20_spdif_filter_rates(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = hw_param_interval(params, rule->var); + struct snd_soc_dai *dai = rule->private; + struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); + struct clk *parent = clk_get_parent(spdif->clk_spdif_out); + const unsigned int rates[] = { 32000, 44100, 48000 }; + long i, parent_rate, valid_rates = 0; + + parent_rate = clk_get_rate(parent); + if (parent_rate <= 0) { + dev_err(dai->dev, "Can't get parent clock rate: %ld\n", + parent_rate); + return parent_rate ?: -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(rates); i++) { + if (parent_rate % (rates[i] * 128) == 0) + valid_rates |= BIT(i); + } + + /* + * At least one rate must be valid, otherwise the parent clock isn't + * audio PLL. Nothing should be filtered in this case. + */ + if (!valid_rates) + valid_rates = BIT(ARRAY_SIZE(rates)) - 1; + + return snd_interval_list(r, ARRAY_SIZE(rates), rates, valid_rates); +} + +static int tegra20_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (!device_property_read_bool(dai->dev, "nvidia,fixed-parent-rate")) + return 0; + + /* + * SPDIF and I2S share audio PLL. HDMI takes audio packets from SPDIF + * and audio may not work on some TVs if clock rate isn't precise. + * + * PLL rate is controlled by I2S side. Filter out audio rates that + * don't match PLL rate at the start of stream to allow both SPDIF + * and I2S work simultaneously, assuming that PLL rate won't be + * changed later on. + */ + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + tegra20_spdif_filter_rates, dai, + SNDRV_PCM_HW_PARAM_RATE, -1); +} + static int tegra20_spdif_probe(struct snd_soc_dai *dai) { struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); @@ -185,6 +245,7 @@ static int tegra20_spdif_probe(struct snd_soc_dai *dai) static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = { .hw_params = tegra20_spdif_hw_params, .trigger = tegra20_spdif_trigger, + .startup = tegra20_spdif_startup, };
static struct snd_soc_dai_driver tegra20_spdif_dai = {
Support new nvidia,fixed-parent-rate device-tree property which instructs I2S that board wants parent clock rate to stay at a fixed rate. This allows to play audio over S/PDIF and I2S simultaneously. The root of the problem is that audio components on Tegra share the same audio PLL, and thus, only a subset of rates can be supported if we want to play audio simultaneously. Filter out audio rates that don't match parent clock rate if device-tree has the nvidia,fixed-parent-rate property.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- sound/soc/tegra/tegra20_i2s.c | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+)
diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index 266d2cab9f49..27365a877e47 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -262,10 +262,59 @@ static int tegra20_i2s_probe(struct snd_soc_dai *dai) return 0; }
+static const unsigned int tegra20_i2s_rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000 +}; + +static int tegra20_i2s_filter_rates(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = hw_param_interval(params, rule->var); + struct snd_soc_dai *dai = rule->private; + struct tegra20_i2s *i2s = dev_get_drvdata(dai->dev); + struct clk *parent = clk_get_parent(i2s->clk_i2s); + long i, parent_rate, valid_rates = 0; + + parent_rate = clk_get_rate(parent); + if (parent_rate <= 0) { + dev_err(dai->dev, "Can't get parent clock rate: %ld\n", + parent_rate); + return parent_rate ?: -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(tegra20_i2s_rates); i++) { + if (parent_rate % (tegra20_i2s_rates[i] * 128) == 0) + valid_rates |= BIT(i); + } + + /* + * At least one rate must be valid, otherwise the parent clock isn't + * audio PLL. Nothing should be filtered in this case. + */ + if (!valid_rates) + valid_rates = BIT(ARRAY_SIZE(tegra20_i2s_rates)) - 1; + + return snd_interval_list(r, ARRAY_SIZE(tegra20_i2s_rates), + tegra20_i2s_rates, valid_rates); +} + +static int tegra20_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (!device_property_read_bool(dai->dev, "nvidia,fixed-parent-rate")) + return 0; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + tegra20_i2s_filter_rates, dai, + SNDRV_PCM_HW_PARAM_RATE, -1); +} + static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = { .set_fmt = tegra20_i2s_set_fmt, .hw_params = tegra20_i2s_hw_params, .trigger = tegra20_i2s_trigger, + .startup = tegra20_i2s_startup, };
static const struct snd_soc_dai_driver tegra20_i2s_dai_template = {
Add missing error unwinding to tegra_hdmi_init(), for consistency.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- drivers/gpu/drm/tegra/hdmi.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 9a87d351a828..142ad696426f 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -1456,22 +1456,31 @@ static int tegra_hdmi_init(struct host1x_client *client) if (err < 0) { dev_err(client->dev, "failed to enable HDMI regulator: %d\n", err); - return err; + goto output_exit; }
err = regulator_enable(hdmi->pll); if (err < 0) { dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err); - return err; + goto disable_hdmi; }
err = regulator_enable(hdmi->vdd); if (err < 0) { dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err); - return err; + goto disable_pll; }
return 0; + +disable_pll: + regulator_disable(hdmi->pll); +disable_hdmi: + regulator_disable(hdmi->hdmi); +output_exit: + tegra_output_exit(&hdmi->output); + + return err; }
static int tegra_hdmi_exit(struct host1x_client *client)
Tegra20 SoC supports only S/PDIF source for HDMI audio. Register ASoC HDMI S/PDIF CODEC for Tegra20, it will be linked with the S/PDIF CPU DAI.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- drivers/gpu/drm/tegra/Kconfig | 3 + drivers/gpu/drm/tegra/hdmi.c | 153 +++++++++++++++++++++++++++++++--- 2 files changed, 145 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig index 32fe64553d2e..40f0d14cb240 100644 --- a/drivers/gpu/drm/tegra/Kconfig +++ b/drivers/gpu/drm/tegra/Kconfig @@ -13,6 +13,9 @@ config DRM_TEGRA_ORIG select INTERCONNECT select IOMMU_IOVA select CEC_CORE if CEC_NOTIFIER + select SND_SIMPLE_CARD if SND_SOC_TEGRA20_SPDIF + select SND_SOC_HDMI_CODEC if SND_SOC_TEGRA20_SPDIF + select SND_AUDIO_GRAPH_CARD if SND_SOC_TEGRA20_SPDIF help Choose this option if you have an NVIDIA Tegra SoC.
diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 142ad696426f..a99861f95e89 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -18,6 +18,8 @@
#include <soc/tegra/common.h>
+#include <sound/hdmi-codec.h> + #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_debugfs.h> @@ -81,6 +83,9 @@ struct tegra_hdmi { bool dvi;
struct drm_info_list *debugfs_files; + + struct platform_device *audio_pdev; + struct mutex audio_lock; };
static inline struct tegra_hdmi * @@ -363,6 +368,18 @@ static const struct tmds_config tegra124_tmds_config[] = { }, };
+static void tegra_hdmi_audio_lock(struct tegra_hdmi *hdmi) +{ + mutex_lock(&hdmi->audio_lock); + disable_irq(hdmi->irq); +} + +static void tegra_hdmi_audio_unlock(struct tegra_hdmi *hdmi) +{ + enable_irq(hdmi->irq); + mutex_unlock(&hdmi->audio_lock); +} + static int tegra_hdmi_get_audio_config(unsigned int audio_freq, unsigned int pix_clock, struct tegra_hdmi_audio_config *config) @@ -832,6 +849,23 @@ static void tegra_hdmi_setup_tmds(struct tegra_hdmi *hdmi, HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT); }
+static int tegra_hdmi_reconfigure_audio(struct tegra_hdmi *hdmi) +{ + int err; + + err = tegra_hdmi_setup_audio(hdmi); + if (err < 0) { + tegra_hdmi_disable_audio_infoframe(hdmi); + tegra_hdmi_disable_audio(hdmi); + } else { + tegra_hdmi_setup_audio_infoframe(hdmi); + tegra_hdmi_enable_audio_infoframe(hdmi); + tegra_hdmi_enable_audio(hdmi); + } + + return err; +} + static bool tegra_output_is_hdmi(struct tegra_output *output) { struct edid *edid; @@ -1138,6 +1172,8 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder) u32 value; int err;
+ tegra_hdmi_audio_lock(hdmi); + /* * The following accesses registers of the display controller, so make * sure it's only executed when the output is attached to one. @@ -1162,6 +1198,10 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder) tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_ENABLE); tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_MASK);
+ hdmi->pixel_clock = 0; + + tegra_hdmi_audio_unlock(hdmi); + err = host1x_client_suspend(&hdmi->client); if (err < 0) dev_err(hdmi->dev, "failed to suspend: %d\n", err); @@ -1185,6 +1225,8 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) return; }
+ tegra_hdmi_audio_lock(hdmi); + /* * Enable and unmask the HDA codec SCRATCH0 register interrupt. This * is used for interoperability between the HDA codec driver and the @@ -1390,6 +1432,8 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) }
/* TODO: add HDCP support */ + + tegra_hdmi_audio_unlock(hdmi); }
static int @@ -1419,6 +1463,91 @@ static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = { .atomic_check = tegra_hdmi_encoder_atomic_check, };
+static int tegra_hdmi_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct tegra_hdmi *hdmi = data; + int ret = 0; + + tegra_hdmi_audio_lock(hdmi); + + hdmi->format.sample_rate = hparms->sample_rate; + hdmi->format.channels = hparms->channels; + + if (hdmi->pixel_clock && !hdmi->dvi) + ret = tegra_hdmi_reconfigure_audio(hdmi); + + tegra_hdmi_audio_unlock(hdmi); + + return ret; +} + +static int tegra_hdmi_audio_startup(struct device *dev, void *data) +{ + struct tegra_hdmi *hdmi = data; + int ret; + + ret = host1x_client_resume(&hdmi->client); + if (ret < 0) + dev_err(hdmi->dev, "failed to resume: %d\n", ret); + + return ret; +} + +static void tegra_hdmi_audio_shutdown(struct device *dev, void *data) +{ + struct tegra_hdmi *hdmi = data; + int ret; + + tegra_hdmi_audio_lock(hdmi); + + hdmi->format.sample_rate = 0; + hdmi->format.channels = 0; + + tegra_hdmi_audio_unlock(hdmi); + + ret = host1x_client_suspend(&hdmi->client); + if (ret < 0) + dev_err(hdmi->dev, "failed to suspend: %d\n", ret); +} + +static const struct hdmi_codec_ops tegra_hdmi_codec_ops = { + .hw_params = tegra_hdmi_hw_params, + .audio_startup = tegra_hdmi_audio_startup, + .audio_shutdown = tegra_hdmi_audio_shutdown, +}; + +static int tegra_hdmi_codec_register(struct tegra_hdmi *hdmi) +{ + struct hdmi_codec_pdata codec_data = {}; + + if (hdmi->config->has_hda) + return 0; + + codec_data.ops = &tegra_hdmi_codec_ops; + codec_data.data = hdmi; + codec_data.spdif = 1; + + hdmi->audio_pdev = platform_device_register_data(hdmi->dev, + HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &codec_data, + sizeof(codec_data)); + if (IS_ERR(hdmi->audio_pdev)) + return PTR_ERR(hdmi->audio_pdev); + + hdmi->format.channels = 2; + + return 0; +} + +static void tegra_hdmi_codec_unregister(struct tegra_hdmi *hdmi) +{ + if (hdmi->audio_pdev) + platform_device_unregister(hdmi->audio_pdev); +} + static int tegra_hdmi_init(struct host1x_client *client) { struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); @@ -1471,8 +1600,16 @@ static int tegra_hdmi_init(struct host1x_client *client) goto disable_pll; }
+ err = tegra_hdmi_codec_register(hdmi); + if (err < 0) { + dev_err(hdmi->dev, "failed to register audio codec: %d\n", err); + goto disable_vdd; + } + return 0;
+disable_vdd: + regulator_disable(hdmi->vdd); disable_pll: regulator_disable(hdmi->pll); disable_hdmi: @@ -1487,6 +1624,8 @@ static int tegra_hdmi_exit(struct host1x_client *client) { struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
+ tegra_hdmi_codec_unregister(hdmi); + tegra_output_exit(&hdmi->output);
regulator_disable(hdmi->vdd); @@ -1611,7 +1750,6 @@ static irqreturn_t tegra_hdmi_irq(int irq, void *data) { struct tegra_hdmi *hdmi = data; u32 value; - int err;
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_INT_STATUS); tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_INT_STATUS); @@ -1626,16 +1764,7 @@ static irqreturn_t tegra_hdmi_irq(int irq, void *data) format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK;
tegra_hda_parse_format(format, &hdmi->format); - - err = tegra_hdmi_setup_audio(hdmi); - if (err < 0) { - tegra_hdmi_disable_audio_infoframe(hdmi); - tegra_hdmi_disable_audio(hdmi); - } else { - tegra_hdmi_setup_audio_infoframe(hdmi); - tegra_hdmi_enable_audio_infoframe(hdmi); - tegra_hdmi_enable_audio(hdmi); - } + tegra_hdmi_reconfigure_audio(hdmi); } else { tegra_hdmi_disable_audio_infoframe(hdmi); tegra_hdmi_disable_audio(hdmi); @@ -1663,6 +1792,8 @@ static int tegra_hdmi_probe(struct platform_device *pdev) hdmi->stereo = false; hdmi->dvi = false;
+ mutex_init(&hdmi->audio_lock); + hdmi->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(hdmi->clk)) { dev_err(&pdev->dev, "failed to get clock\n");
Enable Tegra20 S/PDIF driver. It's a part of HDMI audio subsystem on Tegra.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- arch/arm/configs/tegra_defconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/arch/arm/configs/tegra_defconfig b/arch/arm/configs/tegra_defconfig index 817b39190d54..1b8f8fdbedc5 100644 --- a/arch/arm/configs/tegra_defconfig +++ b/arch/arm/configs/tegra_defconfig @@ -235,6 +235,7 @@ CONFIG_SND_HDA_CODEC_HDMI=y CONFIG_SND_SOC=y CONFIG_SND_SOC_TEGRA=y CONFIG_SND_SOC_TEGRA20_I2S=y +CONFIG_SND_SOC_TEGRA20_SPDIF=y CONFIG_SND_SOC_TEGRA30_I2S=y CONFIG_SND_SOC_TEGRA_RT5640=y CONFIG_SND_SOC_TEGRA_WM8753=y
Add S/PDIF node to Tegra20 device-tree. It's needed for enabling HDMI audio support.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- arch/arm/boot/dts/tegra20.dtsi | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi index 63c2c2f8c0ce..799da7dc929b 100644 --- a/arch/arm/boot/dts/tegra20.dtsi +++ b/arch/arm/boot/dts/tegra20.dtsi @@ -197,6 +197,7 @@ hdmi@54280000 { reset-names = "hdmi"; power-domains = <&pd_core>; operating-points-v2 = <&hdmi_dvfs_opp_table>; + #sound-dai-cells = <0>; status = "disabled"; };
@@ -396,6 +397,23 @@ tegra_ac97: ac97@70002000 { status = "disabled"; };
+ tegra_spdif: spdif@70002400 { + compatible = "nvidia,tegra20-spdif"; + reg = <0x70002400 0x200>; + interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&tegra_car TEGRA20_CLK_SPDIF_OUT>, + <&tegra_car TEGRA20_CLK_SPDIF_IN>; + clock-names = "spdif_out", "spdif_in"; + resets = <&tegra_car 10>; + dmas = <&apbdma 3>, <&apbdma 3>; + dma-names = "rx", "tx"; + #sound-dai-cells = <0>; + status = "disabled"; + + assigned-clocks = <&tegra_car TEGRA20_CLK_SPDIF_OUT>; + assigned-clock-parents = <&tegra_car TEGRA20_CLK_PLL_A_OUT0>; + }; + tegra_i2s1: i2s@70002800 { compatible = "nvidia,tegra20-i2s"; reg = <0x70002800 0x200>;
25.11.2021 01:00, Dmitry Osipenko пишет:
Add S/PDIF node to Tegra20 device-tree. It's needed for enabling HDMI audio support.
Signed-off-by: Dmitry Osipenko digetx@gmail.com
arch/arm/boot/dts/tegra20.dtsi | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi index 63c2c2f8c0ce..799da7dc929b 100644 --- a/arch/arm/boot/dts/tegra20.dtsi +++ b/arch/arm/boot/dts/tegra20.dtsi @@ -197,6 +197,7 @@ hdmi@54280000 { reset-names = "hdmi"; power-domains = <&pd_core>; operating-points-v2 = <&hdmi_dvfs_opp_table>;
};#sound-dai-cells = <0>; status = "disabled";
@@ -396,6 +397,23 @@ tegra_ac97: ac97@70002000 { status = "disabled"; };
- tegra_spdif: spdif@70002400 {
compatible = "nvidia,tegra20-spdif";
reg = <0x70002400 0x200>;
interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA20_CLK_SPDIF_OUT>,
<&tegra_car TEGRA20_CLK_SPDIF_IN>;
clock-names = "spdif_out", "spdif_in";
resets = <&tegra_car 10>;
dmas = <&apbdma 3>, <&apbdma 3>;
dma-names = "rx", "tx";
#sound-dai-cells = <0>;
status = "disabled";
assigned-clocks = <&tegra_car TEGRA20_CLK_SPDIF_OUT>;
assigned-clock-parents = <&tegra_car TEGRA20_CLK_PLL_A_OUT0>;
- };
- tegra_i2s1: i2s@70002800 { compatible = "nvidia,tegra20-i2s"; reg = <0x70002800 0x200>;
@Thierry, this patch is made on top of [1].
[1] https://patchwork.ozlabs.org/project/linux-tegra/list/?series=271954
Add HDMI audio graph to Tegra20 device-tree to enable HDMI audio on Tegra20 devices.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- arch/arm/boot/dts/tegra20.dtsi | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi index 799da7dc929b..a2cdc591b4be 100644 --- a/arch/arm/boot/dts/tegra20.dtsi +++ b/arch/arm/boot/dts/tegra20.dtsi @@ -186,7 +186,7 @@ rgb { }; };
- hdmi@54280000 { + tegra_hdmi: hdmi@54280000 { compatible = "nvidia,tegra20-hdmi"; reg = <0x54280000 0x00040000>; interrupts = <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>; @@ -1063,4 +1063,24 @@ pmu { interrupt-affinity = <&{/cpus/cpu@0}>, <&{/cpus/cpu@1}>; }; + + sound-hdmi { + compatible = "simple-audio-card"; + simple-audio-card,name = "NVIDIA Tegra20 HDMI"; + + #address-cells = <1>; + #size-cells = <0>; + + simple-audio-card,dai-link@0 { + reg = <0>; + + cpu { + sound-dai = <&tegra_spdif>; + }; + + codec { + sound-dai = <&tegra_hdmi>; + }; + }; + }; };
Enable S/PDIF controller to enable HDMI audio support on Acer A500. Use nvidia,fixed-parent-rate property that prevents audio rate conflict between S/PDIF and I2S.
Signed-off-by: Dmitry Osipenko digetx@gmail.com --- arch/arm/boot/dts/tegra20-acer-a500-picasso.dts | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts b/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts index db388ddd062f..f47b946627c3 100644 --- a/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts +++ b/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts @@ -376,8 +376,16 @@ pta { }; };
+ tegra_spdif: spdif@70002400 { + status = "okay"; + + nvidia,fixed-parent-rate; + }; + tegra_i2s1: i2s@70002800 { status = "okay"; + + nvidia,fixed-parent-rate; };
uartb: serial@70006040 {
Enable S/PDIF controller to enable HDMI audio support on Toshiba AC100. Use nvidia,fixed-parent-rate property that prevents audio rate conflict between S/PDIF and I2S.
Tested-by: Agneli poczt@protonmail.ch Signed-off-by: Dmitry Osipenko digetx@gmail.com --- arch/arm/boot/dts/tegra20-paz00.dts | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/arch/arm/boot/dts/tegra20-paz00.dts b/arch/arm/boot/dts/tegra20-paz00.dts index 5b2260f61f05..921a811632a1 100644 --- a/arch/arm/boot/dts/tegra20-paz00.dts +++ b/arch/arm/boot/dts/tegra20-paz00.dts @@ -264,8 +264,16 @@ conf_ld17_0 { }; };
+ spdif@70002400 { + status = "okay"; + + nvidia,fixed-parent-rate; + }; + i2s@70002800 { status = "okay"; + + nvidia,fixed-parent-rate; };
serial@70006000 {
participants (3)
-
Dmitry Osipenko
-
Mark Brown
-
Rob Herring