aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound/soc/tegra/tegra20_spdif.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/tegra/tegra20_spdif.c')
-rw-r--r--sound/soc/tegra/tegra20_spdif.c219
1 files changed, 144 insertions, 75 deletions
diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c
index 5839833e23a0..380011233eb1 100644
--- a/sound/soc/tegra/tegra20_spdif.c
+++ b/sound/soc/tegra/tegra20_spdif.c
@@ -7,12 +7,15 @@
*/
#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#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>
@@ -22,39 +25,61 @@
#include "tegra20_spdif.h"
-#define DRV_NAME "tegra20-spdif"
-
-static int tegra20_spdif_runtime_suspend(struct device *dev)
+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;
}
-static int tegra20_spdif_runtime_resume(struct device *dev)
+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,
- 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;
+ long rate;
mask |= TEGRA20_SPDIF_CTRL_PACK |
TEGRA20_SPDIF_CTRL_BIT_MODE_MASK;
@@ -69,6 +94,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;
@@ -97,10 +130,16 @@ 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;
}
+ rate = clk_get_rate(spdif->clk_spdif_out);
+ if (rate != spdifclock)
+ dev_warn_once(dai->dev,
+ "SPDIF clock rate %d doesn't match requested rate %lu\n",
+ spdifclock, rate);
+
return 0;
}
@@ -118,9 +157,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:
@@ -140,37 +179,90 @@ 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);
+ static const unsigned int rates[] = { 32000, 44100, 48000 };
+ unsigned long i, parent_rate, valid_rates = 0;
+
+ parent_rate = clk_get_rate(parent);
+ if (!parent_rate) {
+ dev_err(dai->dev, "Can't get parent clock rate\n");
+ return -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 = 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;
+ snd_soc_dai_init_dma_data(dai, &spdif->playback_dma_data, NULL);
return 0;
}
static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = {
- .hw_params = tegra20_spdif_hw_params,
- .trigger = tegra20_spdif_trigger,
+ .probe = tegra20_spdif_probe,
+ .hw_params = tegra20_spdif_hw_params,
+ .trigger = tegra20_spdif_trigger,
+ .startup = tegra20_spdif_startup,
};
static struct snd_soc_dai_driver tegra20_spdif_dai = {
- .name = DRV_NAME,
- .probe = tegra20_spdif_probe,
+ .name = "tegra20-spdif",
.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",
+ .legacy_dai_naming = 1,
};
static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg)
@@ -251,7 +343,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;
@@ -262,100 +354,77 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev)
dev_set_drvdata(&pdev->dev, spdif);
- spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out");
+ 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, "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);
}
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- regs = devm_ioremap_resource(&pdev->dev, mem);
+ 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;
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);
- if (!pm_runtime_enabled(&pdev->dev)) {
- ret = tegra20_spdif_runtime_resume(&pdev->dev);
- if (ret)
- goto err_pm_disable;
- }
+ 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);
- ret = -ENOMEM;
- goto err_suspend;
+ 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_suspend:
- if (!pm_runtime_status_suspended(&pdev->dev))
- tegra20_spdif_runtime_suspend(&pdev->dev);
-err_pm_disable:
- pm_runtime_disable(&pdev->dev);
-
- return ret;
-}
-
-static int tegra20_spdif_platform_remove(struct platform_device *pdev)
-{
- 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;
}
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[] = {
+ { .compatible = "nvidia,tegra20-spdif", },
+ {},
+};
+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>");
MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver");
MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:" DRV_NAME);