diff options
| author | Mark Brown <broonie@kernel.org> | 2026-02-06 01:52:13 +0300 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-02-06 01:52:13 +0300 |
| commit | dacaa439fa3ac8780847392342d886f14ad1b765 (patch) | |
| tree | 24adc244b3e70a04e638cd67777ee258bb08cf7d | |
| parent | 5209af4db0591452477b17ef2718734c18483476 (diff) | |
| parent | d94ea902462ac39846d878aa67b78408727e0674 (diff) | |
| download | linux-dacaa439fa3ac8780847392342d886f14ad1b765.tar.xz | |
ASoC: rockchip: spdif: Cleanups and port features
Merge series from Sebastian Reichel <sebastian.reichel@collabora.com>:
I'm currently working on DisplayPort audio support for the Rockchip
RK3588/RK3576 SoCs, which preferrably use S/PDIF as DAI source.
Apparently the upstream Rockchip S/PDIF driver is lacking a couple of
features right now, which are necessary to get things going (i.e.
setting the sysclk from the machine driver). I found the missing bits
in Rockchip's 6.1 BSP kernel and ported them over. This series effectly
brings the mainline kernel on-par with the BSP driver, but also contains
a couple of cleanup patches of my own to bring the driver to the modern
age.
| -rw-r--r-- | sound/soc/rockchip/Kconfig | 1 | ||||
| -rw-r--r-- | sound/soc/rockchip/rockchip_spdif.c | 225 | ||||
| -rw-r--r-- | sound/soc/rockchip/rockchip_spdif.h | 57 |
3 files changed, 176 insertions, 107 deletions
diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig index bd210fafe9fe..391ce2225fde 100644 --- a/sound/soc/rockchip/Kconfig +++ b/sound/soc/rockchip/Kconfig @@ -41,6 +41,7 @@ config SND_SOC_ROCKCHIP_SAI config SND_SOC_ROCKCHIP_SPDIF tristate "Rockchip SPDIF Device Driver" + select SND_PCM_IEC958 select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y or M if you want to add support for SPDIF driver for diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c index d365168934dc..581624f2682e 100644 --- a/sound/soc/rockchip/rockchip_spdif.c +++ b/sound/soc/rockchip/rockchip_spdif.c @@ -5,10 +5,11 @@ * * Copyright (c) 2014 Rockchip Electronics Co. Ltd. * Author: Jianqun <jay.xu@rock-chips.com> - * Copyright (c) 2015 Collabora Ltd. + * Copyright (c) 2015-2026 Collabora Ltd. * Author: Sjoerd Simons <sjoerd.simons@collabora.co.uk> */ +#include <linux/bitfield.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/clk.h> @@ -16,6 +17,7 @@ #include <linux/mfd/syscon.h> #include <linux/regmap.h> #include <sound/pcm_params.h> +#include <sound/pcm_iec958.h> #include <sound/dmaengine_pcm.h> #include "rockchip_spdif.h" @@ -27,7 +29,25 @@ enum rk_spdif_type { RK_SPDIF_RK3366, }; -#define RK3288_GRF_SOC_CON2 0x24c +/* + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * CS0: | Mode | d | c | b | a | + * CS1: | Category Code | + * CS2: | Channel Number | Source Number | + * CS3: | Clock Accuracy | Sample Freq | + * CS4: | Ori Sample Freq | Word Length | + * CS5: | | CGMS-A | + * CS6~CS23: Reserved + * + * a: use of channel status block + * b: linear PCM identification: 0 for lpcm, 1 for nlpcm + * c: copyright information + * d: additional format information + */ +#define CS_BYTE 6 +#define CS_FRAME(c) ((c) << 16 | (c)) + +#define RK3288_GRF_SOC_CON2 0x24c struct rk_spdif_dev { struct device *dev; @@ -40,29 +60,6 @@ struct rk_spdif_dev { struct regmap *regmap; }; -static const struct of_device_id rk_spdif_match[] __maybe_unused = { - { .compatible = "rockchip,rk3066-spdif", - .data = (void *)RK_SPDIF_RK3066 }, - { .compatible = "rockchip,rk3188-spdif", - .data = (void *)RK_SPDIF_RK3188 }, - { .compatible = "rockchip,rk3228-spdif", - .data = (void *)RK_SPDIF_RK3366 }, - { .compatible = "rockchip,rk3288-spdif", - .data = (void *)RK_SPDIF_RK3288 }, - { .compatible = "rockchip,rk3328-spdif", - .data = (void *)RK_SPDIF_RK3366 }, - { .compatible = "rockchip,rk3366-spdif", - .data = (void *)RK_SPDIF_RK3366 }, - { .compatible = "rockchip,rk3368-spdif", - .data = (void *)RK_SPDIF_RK3366 }, - { .compatible = "rockchip,rk3399-spdif", - .data = (void *)RK_SPDIF_RK3366 }, - { .compatible = "rockchip,rk3568-spdif", - .data = (void *)RK_SPDIF_RK3366 }, - {}, -}; -MODULE_DEVICE_TABLE(of, rk_spdif_match); - static int rk_spdif_runtime_suspend(struct device *dev) { struct rk_spdif_dev *spdif = dev_get_drvdata(dev); @@ -109,39 +106,63 @@ static int rk_spdif_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct rk_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int mclk_rate = clk_get_rate(spdif->mclk); unsigned int val = SPDIF_CFGR_HALFWORD_ENABLE; - int srate, mclk; - int ret; + int bmc, div, ret, i; + u16 *fc; + u8 cs[CS_BYTE]; + + ret = snd_pcm_create_iec958_consumer_hw_params(params, cs, sizeof(cs)); + if (ret < 0) + return ret; - srate = params_rate(params); - mclk = srate * 128; + fc = (u16 *)cs; + for (i = 0; i < CS_BYTE / 2; i++) + regmap_write(spdif->regmap, SPDIF_CHNSRn(i), CS_FRAME(fc[i])); + + regmap_update_bits(spdif->regmap, SPDIF_CFGR, SPDIF_CFGR_CSE_MASK, + SPDIF_CFGR_CSE_EN); + + /* bmc = 128fs */ + bmc = 128 * params_rate(params); + div = DIV_ROUND_CLOSEST(mclk_rate, bmc); + val |= SPDIF_CFGR_CLK_DIV(div); switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: val |= SPDIF_CFGR_VDW_16; + val |= SPDIF_CFGR_ADJ_RIGHT_J; break; case SNDRV_PCM_FORMAT_S20_3LE: val |= SPDIF_CFGR_VDW_20; + val |= SPDIF_CFGR_ADJ_RIGHT_J; break; case SNDRV_PCM_FORMAT_S24_LE: val |= SPDIF_CFGR_VDW_24; + val |= SPDIF_CFGR_ADJ_RIGHT_J; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val |= SPDIF_CFGR_VDW_24; + val |= SPDIF_CFGR_ADJ_LEFT_J; break; default: return -EINVAL; } - /* Set clock and calculate divider */ - ret = clk_set_rate(spdif->mclk, mclk); - if (ret != 0) { - dev_err(spdif->dev, "Failed to set module clock rate: %d\n", - ret); - return ret; - } + /* + * clear MCLK domain logic before setting Fmclk and Fsdo to ensure + * that switching between S16_LE and S32_LE audio does not result + * in accidential channels swap. + */ + regmap_update_bits(spdif->regmap, SPDIF_CFGR, SPDIF_CFGR_CLR_MASK, + SPDIF_CFGR_CLR_EN); + udelay(1); ret = regmap_update_bits(spdif->regmap, SPDIF_CFGR, SPDIF_CFGR_CLK_DIV_MASK | - SPDIF_CFGR_HALFWORD_ENABLE | - SDPIF_CFGR_VDW_MASK, val); + SPDIF_CFGR_HALFWORD_MASK | + SDPIF_CFGR_VDW_MASK | + SPDIF_CFGR_ADJ_MASK, val); return ret; } @@ -157,7 +178,7 @@ static int rk_spdif_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ret = regmap_update_bits(spdif->regmap, SPDIF_DMACR, - SPDIF_DMACR_TDE_ENABLE | + SPDIF_DMACR_TDE_MASK | SPDIF_DMACR_TDL_MASK, SPDIF_DMACR_TDE_ENABLE | SPDIF_DMACR_TDL(16)); @@ -166,21 +187,21 @@ static int rk_spdif_trigger(struct snd_pcm_substream *substream, return ret; ret = regmap_update_bits(spdif->regmap, SPDIF_XFER, - SPDIF_XFER_TXS_START, + SPDIF_XFER_TXS_MASK, SPDIF_XFER_TXS_START); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ret = regmap_update_bits(spdif->regmap, SPDIF_DMACR, - SPDIF_DMACR_TDE_ENABLE, + SPDIF_DMACR_TDE_MASK, SPDIF_DMACR_TDE_DISABLE); if (ret != 0) return ret; ret = regmap_update_bits(spdif->regmap, SPDIF_XFER, - SPDIF_XFER_TXS_START, + SPDIF_XFER_TXS_MASK, SPDIF_XFER_TXS_STOP); break; default: @@ -200,7 +221,24 @@ static int rk_spdif_dai_probe(struct snd_soc_dai *dai) return 0; } +static int rk_spdif_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct rk_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + int ret; + + if (!freq) + return 0; + + ret = clk_set_rate(spdif->mclk, freq); + if (ret) + dev_err(spdif->dev, "Failed to set mclk: %d\n", ret); + + return ret; +} + static const struct snd_soc_dai_ops rk_spdif_dai_ops = { + .set_sysclk = rk_spdif_set_sysclk, .probe = rk_spdif_dai_probe, .hw_params = rk_spdif_hw_params, .trigger = rk_spdif_trigger, @@ -211,14 +249,11 @@ static struct snd_soc_dai_driver rk_spdif_dai = { .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_96000 | - SNDRV_PCM_RATE_192000), + .rates = SNDRV_PCM_RATE_8000_192000, .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | - SNDRV_PCM_FMTBIT_S24_LE), + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), }, .ops = &rk_spdif_dai_ops, }; @@ -236,6 +271,9 @@ static bool rk_spdif_wr_reg(struct device *dev, unsigned int reg) case SPDIF_INTCR: case SPDIF_XFER: case SPDIF_SMPDR: + case SPDIF_VLDFRn(0) ... SPDIF_VLDFRn(11): + case SPDIF_USRDRn(0) ... SPDIF_USRDRn(11): + case SPDIF_CHNSRn(0) ... SPDIF_CHNSRn(11): return true; default: return false; @@ -251,6 +289,9 @@ static bool rk_spdif_rd_reg(struct device *dev, unsigned int reg) case SPDIF_INTSR: case SPDIF_XFER: case SPDIF_SMPDR: + case SPDIF_VLDFRn(0) ... SPDIF_VLDFRn(11): + case SPDIF_USRDRn(0) ... SPDIF_USRDRn(11): + case SPDIF_CHNSRn(0) ... SPDIF_CHNSRn(11): return true; default: return false; @@ -273,32 +314,38 @@ static const struct regmap_config rk_spdif_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .max_register = SPDIF_SMPDR, + .max_register = SPDIF_VERSION, .writeable_reg = rk_spdif_wr_reg, .readable_reg = rk_spdif_rd_reg, .volatile_reg = rk_spdif_volatile_reg, .cache_type = REGCACHE_FLAT, }; +static void rk_spdif_suspend(void *data) +{ + struct device *dev = data; + + if (!pm_runtime_status_suspended(dev)) + rk_spdif_runtime_suspend(dev); +} + static int rk_spdif_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; + enum rk_spdif_type spdif_type; struct rk_spdif_dev *spdif; - const struct of_device_id *match; struct resource *res; void __iomem *regs; int ret; - match = of_match_node(rk_spdif_match, np); - if (match->data == (void *)RK_SPDIF_RK3288) { + spdif_type = (uintptr_t) device_get_match_data(&pdev->dev); + if (spdif_type == RK_SPDIF_RK3288) { struct regmap *grf; grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); - if (IS_ERR(grf)) { - dev_err(&pdev->dev, + if (IS_ERR(grf)) + return dev_err_probe(&pdev->dev, PTR_ERR(grf), "rockchip_spdif missing 'rockchip,grf'\n"); - return PTR_ERR(grf); - } /* Select the 8 channel SPDIF solution on RK3288 as * the 2 channel one does not appear to work @@ -334,55 +381,63 @@ static int rk_spdif_probe(struct platform_device *pdev) spdif->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, spdif); - pm_runtime_enable(&pdev->dev); + ret = devm_add_action_or_reset(&pdev->dev, rk_spdif_suspend, &pdev->dev); + if (ret) + return ret; + + devm_pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { ret = rk_spdif_runtime_resume(&pdev->dev); if (ret) - goto err_pm_runtime; + return ret; } + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Could not register PCM\n"); + ret = devm_snd_soc_register_component(&pdev->dev, &rk_spdif_component, &rk_spdif_dai, 1); - if (ret) { - dev_err(&pdev->dev, "Could not register DAI\n"); - goto err_pm_suspend; - } - - ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) { - dev_err(&pdev->dev, "Could not register PCM\n"); - goto err_pm_suspend; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "Could not register DAI\n"); return 0; - -err_pm_suspend: - if (!pm_runtime_status_suspended(&pdev->dev)) - rk_spdif_runtime_suspend(&pdev->dev); -err_pm_runtime: - pm_runtime_disable(&pdev->dev); - - return ret; -} - -static void rk_spdif_remove(struct platform_device *pdev) -{ - pm_runtime_disable(&pdev->dev); - if (!pm_runtime_status_suspended(&pdev->dev)) - rk_spdif_runtime_suspend(&pdev->dev); } static const struct dev_pm_ops rk_spdif_pm_ops = { RUNTIME_PM_OPS(rk_spdif_runtime_suspend, rk_spdif_runtime_resume, NULL) }; +static const struct of_device_id rk_spdif_match[] = { + { .compatible = "rockchip,rk3066-spdif", + .data = (void *)RK_SPDIF_RK3066 }, + { .compatible = "rockchip,rk3188-spdif", + .data = (void *)RK_SPDIF_RK3188 }, + { .compatible = "rockchip,rk3228-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3288-spdif", + .data = (void *)RK_SPDIF_RK3288 }, + { .compatible = "rockchip,rk3328-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3366-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3368-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3399-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3568-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + {}, +}; +MODULE_DEVICE_TABLE(of, rk_spdif_match); + static struct platform_driver rk_spdif_driver = { .probe = rk_spdif_probe, - .remove = rk_spdif_remove, .driver = { .name = "rockchip-spdif", - .of_match_table = of_match_ptr(rk_spdif_match), + .of_match_table = rk_spdif_match, .pm = pm_ptr(&rk_spdif_pm_ops), }, }; diff --git a/sound/soc/rockchip/rockchip_spdif.h b/sound/soc/rockchip/rockchip_spdif.h index d8be9aae5b19..ec33295e2512 100644 --- a/sound/soc/rockchip/rockchip_spdif.h +++ b/sound/soc/rockchip/rockchip_spdif.h @@ -2,7 +2,7 @@ /* * ALSA SoC Audio Layer - Rockchip SPDIF transceiver driver * - * Copyright (c) 2015 Collabora Ltd. + * Copyright (c) 2015-2026 Collabora Ltd. * Author: Sjoerd Simons <sjoerd.simons@collabora.co.uk> */ @@ -13,41 +13,50 @@ * CFGR * transfer configuration register */ -#define SPDIF_CFGR_CLK_DIV_SHIFT (16) -#define SPDIF_CFGR_CLK_DIV_MASK (0xff << SPDIF_CFGR_CLK_DIV_SHIFT) -#define SPDIF_CFGR_CLK_DIV(x) (x << SPDIF_CFGR_CLK_DIV_SHIFT) +#define SPDIF_CFGR_CLK_DIV_MASK GENMASK(23, 16) +#define SPDIF_CFGR_CLK_DIV(x) FIELD_PREP(SPDIF_CFGR_CLK_DIV_MASK, x-1) -#define SPDIF_CFGR_HALFWORD_SHIFT 2 -#define SPDIF_CFGR_HALFWORD_DISABLE (0 << SPDIF_CFGR_HALFWORD_SHIFT) -#define SPDIF_CFGR_HALFWORD_ENABLE (1 << SPDIF_CFGR_HALFWORD_SHIFT) +#define SPDIF_CFGR_CLR_MASK BIT(7) +#define SPDIF_CFGR_CLR_EN FIELD_PREP(SPDIF_CFGR_CLR_MASK, 1) +#define SPDIF_CFGR_CLR_DIS FIELD_PREP(SPDIF_CFGR_CLR_MASK, 0) -#define SPDIF_CFGR_VDW_SHIFT 0 -#define SPDIF_CFGR_VDW(x) (x << SPDIF_CFGR_VDW_SHIFT) -#define SDPIF_CFGR_VDW_MASK (0xf << SPDIF_CFGR_VDW_SHIFT) +#define SPDIF_CFGR_CSE_MASK BIT(6) +#define SPDIF_CFGR_CSE_EN FIELD_PREP(SPDIF_CFGR_CSE_MASK, 1) +#define SPDIF_CFGR_CSE_DIS FIELD_PREP(SPDIF_CFGR_CSE_MASK, 0) -#define SPDIF_CFGR_VDW_16 SPDIF_CFGR_VDW(0x0) -#define SPDIF_CFGR_VDW_20 SPDIF_CFGR_VDW(0x1) -#define SPDIF_CFGR_VDW_24 SPDIF_CFGR_VDW(0x2) +#define SPDIF_CFGR_ADJ_MASK BIT(3) +#define SPDIF_CFGR_ADJ_LEFT_J FIELD_PREP(SPDIF_CFGR_ADJ_MASK, 1) +#define SPDIF_CFGR_ADJ_RIGHT_J FIELD_PREP(SPDIF_CFGR_ADJ_MASK, 0) + +#define SPDIF_CFGR_HALFWORD_MASK BIT(2) +#define SPDIF_CFGR_HALFWORD_DISABLE FIELD_PREP(SPDIF_CFGR_HALFWORD_MASK, 0) +#define SPDIF_CFGR_HALFWORD_ENABLE FIELD_PREP(SPDIF_CFGR_HALFWORD_MASK, 1) + +#define SDPIF_CFGR_VDW_MASK GENMASK(1, 0) +#define SPDIF_CFGR_VDW(x) FIELD_PREP(SDPIF_CFGR_VDW_MASK, x) + +#define SPDIF_CFGR_VDW_16 SPDIF_CFGR_VDW(0x0) +#define SPDIF_CFGR_VDW_20 SPDIF_CFGR_VDW(0x1) +#define SPDIF_CFGR_VDW_24 SPDIF_CFGR_VDW(0x2) /* * DMACR * DMA control register */ -#define SPDIF_DMACR_TDE_SHIFT 5 -#define SPDIF_DMACR_TDE_DISABLE (0 << SPDIF_DMACR_TDE_SHIFT) -#define SPDIF_DMACR_TDE_ENABLE (1 << SPDIF_DMACR_TDE_SHIFT) +#define SPDIF_DMACR_TDE_MASK BIT(5) +#define SPDIF_DMACR_TDE_DISABLE FIELD_PREP(SPDIF_DMACR_TDE_MASK, 0) +#define SPDIF_DMACR_TDE_ENABLE FIELD_PREP(SPDIF_DMACR_TDE_MASK, 1) -#define SPDIF_DMACR_TDL_SHIFT 0 -#define SPDIF_DMACR_TDL(x) ((x) << SPDIF_DMACR_TDL_SHIFT) -#define SPDIF_DMACR_TDL_MASK (0x1f << SPDIF_DMACR_TDL_SHIFT) +#define SPDIF_DMACR_TDL_MASK GENMASK(4, 0) +#define SPDIF_DMACR_TDL(x) FIELD_PREP(SPDIF_DMACR_TDL_MASK, x) /* * XFER * Transfer control register */ -#define SPDIF_XFER_TXS_SHIFT 0 -#define SPDIF_XFER_TXS_STOP (0 << SPDIF_XFER_TXS_SHIFT) -#define SPDIF_XFER_TXS_START (1 << SPDIF_XFER_TXS_SHIFT) +#define SPDIF_XFER_TXS_MASK BIT(0) +#define SPDIF_XFER_TXS_STOP FIELD_PREP(SPDIF_XFER_TXS_MASK, 0) +#define SPDIF_XFER_TXS_START FIELD_PREP(SPDIF_XFER_TXS_MASK, 1) #define SPDIF_CFGR (0x0000) #define SPDIF_SDBLR (0x0004) @@ -56,5 +65,9 @@ #define SPDIF_INTSR (0x0010) #define SPDIF_XFER (0x0018) #define SPDIF_SMPDR (0x0020) +#define SPDIF_VLDFRn(x) (0x0060 + (x) * 4) +#define SPDIF_USRDRn(x) (0x0090 + (x) * 4) +#define SPDIF_CHNSRn(x) (0x00c0 + (x) * 4) +#define SPDIF_VERSION (0x01c0) #endif /* _ROCKCHIP_SPDIF_H */ |
