[alsa-devel] [PATCH v3 0/6] Support ZTE zx296702 SPDIF/I2S
Changes vs v2: - remove pcm dma driver and merge the related call into DAI driver - Add zx HDMI codec with direct HDMI function call - Other typo and minor fix
Jun Nie (6): dt: Add documentation for the ZTE SPDIF controller dt: Add documentation for the ZTE I2S controller dt: Add doc for the ZTE HDMI audio codec binding ASoC: zx: Add zx296702 SPDIF support ASoC: zx: Add ZTE zx296702 I2S DAI driver ASoC: zx: add zx296702 hdmi codec
.../devicetree/bindings/sound/zte,zx-hdmi.txt | 10 + .../devicetree/bindings/sound/zte,zx-i2s.txt | 44 +++ .../devicetree/bindings/sound/zte,zx-spdif.txt | 28 ++ include/sound/zx_hdmi_audio.h | 7 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 1 + sound/soc/codecs/zx296702_hdmi.c | 121 ++++++ sound/soc/zte/Kconfig | 17 + sound/soc/zte/Makefile | 2 + sound/soc/zte/zx296702-i2s.c | 437 +++++++++++++++++++++ sound/soc/zte/zx296702-spdif.c | 370 +++++++++++++++++ 13 files changed, 1042 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/zte,zx-hdmi.txt create mode 100644 Documentation/devicetree/bindings/sound/zte,zx-i2s.txt create mode 100644 Documentation/devicetree/bindings/sound/zte,zx-spdif.txt create mode 100644 include/sound/zx_hdmi_audio.h create mode 100644 sound/soc/codecs/zx296702_hdmi.c create mode 100644 sound/soc/zte/Kconfig create mode 100644 sound/soc/zte/Makefile create mode 100644 sound/soc/zte/zx296702-i2s.c create mode 100644 sound/soc/zte/zx296702-spdif.c
This patch adds the devicetree documentation for the ZTE zx296702 SPDIF audio controller.
Signed-off-by: Jun Nie jun.nie@linaro.org --- .../devicetree/bindings/sound/zte,zx-spdif.txt | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/zte,zx-spdif.txt
diff --git a/Documentation/devicetree/bindings/sound/zte,zx-spdif.txt b/Documentation/devicetree/bindings/sound/zte,zx-spdif.txt new file mode 100644 index 0000000..989544e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/zte,zx-spdif.txt @@ -0,0 +1,28 @@ +ZTE ZX296702 SPDIF controller + +Required properties: + - compatible : Must be "zte,zx296702-spdif" + - reg : Must contain SPDIF core's registers location and length + - clocks : Pairs of phandle and specifier referencing the controller's clocks. + - clock-names: "tx" for the clock to the SPDIF interface. + - dmas: Pairs of phandle and specifier for the DMA channel that is used by + the core. The core expects one dma channel for transmit. + - dma-names : Must be "tx" + +For more details on the 'dma', 'dma-names', 'clock' and 'clock-names' properties +please check: + * resource-names.txt + * clock/clock-bindings.txt + * dma/dma.txt + +Example: + spdif0: spdif0@0b004000 { + compatible = "zte,zx296702-spdif"; + reg = <0x0b004000 0x1000>; + clocks = <&lsp0clk ZX296702_SPDIF0_DIV>; + clock-names = "tx"; + interrupts = <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&dma 4>; + dma-names = "tx"; + status = "okay"; + };
This patch adds the devicetree documentation for the ZTE zx296702 I2S audio controller.
Signed-off-by: Jun Nie jun.nie@linaro.org --- .../devicetree/bindings/sound/zte,zx-i2s.txt | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/zte,zx-i2s.txt
diff --git a/Documentation/devicetree/bindings/sound/zte,zx-i2s.txt b/Documentation/devicetree/bindings/sound/zte,zx-i2s.txt new file mode 100644 index 0000000..7e5aa6f --- /dev/null +++ b/Documentation/devicetree/bindings/sound/zte,zx-i2s.txt @@ -0,0 +1,44 @@ +ZTE ZX296702 I2S controller + +Required properties: + - compatible : Must be "zte,zx296702-i2s" + - reg : Must contain I2S core's registers location and length + - clocks : Pairs of phandle and specifier referencing the controller's clocks. + - clock-names: "tx" for the clock to the I2S interface. + - dmas: Pairs of phandle and specifier for the DMA channel that is used by + the core. The core expects two dma channels for transmit. + - dma-names : Must be "tx" and "rx" + +For more details on the 'dma', 'dma-names', 'clock' and 'clock-names' properties +please check: + * resource-names.txt + * clock/clock-bindings.txt + * dma/dma.txt + +Example: + i2s0: i2s0@0b005000 { + #sound-dai-cells = <0>; + compatible = "zte,zx296702-i2s"; + reg = <0x0b005000 0x1000>; + clocks = <&lsp0clk ZX296702_I2S0_DIV>; + clock-names = "tx"; + interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&dma 5>, <&dma 6>; + dma-names = "tx", "rx"; + status = "okay"; + }; + + sound { + compatible = "simple-audio-card"; + simple-audio-card,name = "zx296702_snd"; + simple-audio-card,format = "left_j"; + simple-audio-card,bitclock-master = <&sndcodec>; + simple-audio-card,frame-master = <&sndcodec>; + sndcpu: simple-audio-card,cpu { + sound-dai = <&i2s0>; + }; + + sndcodec: simple-audio-card,codec { + sound-dai = <&acodec>; + }; + };
This patch adds the devicetree documentation for the ZTE zx296702 HDMI audio codec binding.
Signed-off-by: Jun Nie jun.nie@linaro.org --- Documentation/devicetree/bindings/sound/zte,zx-hdmi.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/zte,zx-hdmi.txt
diff --git a/Documentation/devicetree/bindings/sound/zte,zx-hdmi.txt b/Documentation/devicetree/bindings/sound/zte,zx-hdmi.txt new file mode 100644 index 0000000..624d75d --- /dev/null +++ b/Documentation/devicetree/bindings/sound/zte,zx-hdmi.txt @@ -0,0 +1,10 @@ +ZTE HDMI ASoC codec binding + +Required properties: + - compatible : Must be "zte,hdmi-audio" + +Example: + acodec: acodec { + #sound-dai-cells = <0>; + compatible = "zte,hdmi-audio"; + };
Add driver for zx296702 SPDIF controller
Signed-off-by: Jun Nie jun.nie@linaro.org --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/zte/Kconfig | 8 + sound/soc/zte/Makefile | 1 + sound/soc/zte/zx296702-spdif.c | 370 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 381 insertions(+) create mode 100644 sound/soc/zte/Kconfig create mode 100644 sound/soc/zte/Makefile create mode 100644 sound/soc/zte/zx296702-spdif.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 3ba52da..e2828e1 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -57,6 +57,7 @@ source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" source "sound/soc/ux500/Kconfig" source "sound/soc/xtensa/Kconfig" +source "sound/soc/zte/Kconfig"
# Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 974ba70..57bf32d 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -38,3 +38,4 @@ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ obj-$(CONFIG_SND_SOC) += ux500/ obj-$(CONFIG_SND_SOC) += xtensa/ +obj-$(CONFIG_SND_SOC) += zte/ diff --git a/sound/soc/zte/Kconfig b/sound/soc/zte/Kconfig new file mode 100644 index 0000000..4f05573 --- /dev/null +++ b/sound/soc/zte/Kconfig @@ -0,0 +1,8 @@ +config ZX296702_SPDIF + tristate "ZX296702 spdif" + depends on SOC_ZX296702 || COMPILE_TEST + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to the + zx296702 spdif interface diff --git a/sound/soc/zte/Makefile b/sound/soc/zte/Makefile new file mode 100644 index 0000000..fb3a4a0 --- /dev/null +++ b/sound/soc/zte/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ZX296702_SPDIF) += zx296702-spdif.o diff --git a/sound/soc/zte/zx296702-spdif.c b/sound/soc/zte/zx296702-spdif.c new file mode 100644 index 0000000..f108f39 --- /dev/null +++ b/sound/soc/zte/zx296702-spdif.c @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2015 Linaro + * + * Author: Jun Nie jun.nie@linaro.org + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/dmaengine.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <sound/asoundef.h> +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#define ZX_CTRL 0x04 +#define ZX_FIFOCTRL 0x08 +#define ZX_INT_STATUS 0x10 +#define ZX_INT_MASK 0x14 +#define ZX_DATA 0x18 +#define ZX_VALID_BIT 0x1c +#define ZX_CH_STA_1 0x20 +#define ZX_CH_STA_2 0x24 +#define ZX_CH_STA_3 0x28 +#define ZX_CH_STA_4 0x2c +#define ZX_CH_STA_5 0x30 +#define ZX_CH_STA_6 0x34 + +#define ZX_CTRL_MODA_16 (0 << 6) +#define ZX_CTRL_MODA_18 BIT(6) +#define ZX_CTRL_MODA_20 (2 << 6) +#define ZX_CTRL_MODA_24 (3 << 6) +#define ZX_CTRL_MODA_MASK (3 << 6) + +#define ZX_CTRL_ENB BIT(4) +#define ZX_CTRL_DNB (0 << 4) +#define ZX_CTRL_ENB_MASK BIT(4) + +#define ZX_CTRL_TX_OPEN BIT(0) +#define ZX_CTRL_TX_CLOSE (0 << 0) +#define ZX_CTRL_TX_MASK BIT(0) + +#define ZX_CTRL_OPEN (ZX_CTRL_TX_OPEN | ZX_CTRL_ENB) +#define ZX_CTRL_CLOSE (ZX_CTRL_TX_CLOSE | ZX_CTRL_DNB) + +#define ZX_CTRL_DOUBLE_TRACK (0 << 8) +#define ZX_CTRL_LEFT_TRACK BIT(8) +#define ZX_CTRL_RIGHT_TRACK (2 << 8) +#define ZX_CTRL_TRACK_MASK (3 << 8) + +#define ZX_FIFOCTRL_TXTH_MASK (0x1f << 8) +#define ZX_FIFOCTRL_TXTH(x) (x << 8) +#define ZX_FIFOCTRL_TX_DMA_EN BIT(2) +#define ZX_FIFOCTRL_TX_DMA_DIS (0 << 2) +#define ZX_FIFOCTRL_TX_DMA_EN_MASK BIT(2) +#define ZX_FIFOCTRL_TX_FIFO_RST BIT(0) +#define ZX_FIFOCTRL_TX_FIFO_RST_MASK BIT(0) + +#define ZX_VALID_DOUBLE_TRACK (0 << 0) +#define ZX_VALID_LEFT_TRACK BIT(1) +#define ZX_VALID_RIGHT_TRACK (2 << 0) +#define ZX_VALID_TRACK_MASK (3 << 0) + +#define ZX_SPDIF_CLK_RAT (4 * 32) + +struct zx_spdif_info { + struct snd_dmaengine_dai_dma_data dma_data; + struct clk *dai_clk; + void __iomem *reg_base; + resource_size_t mapbase; +}; + +static int zx_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct zx_spdif_info *zx_spdif = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, zx_spdif); + zx_spdif->dma_data.addr = zx_spdif->mapbase + ZX_DATA; + zx_spdif->dma_data.maxburst = 8; + snd_soc_dai_init_dma_data(dai, &zx_spdif->dma_data, NULL); + return 0; +} + +static int zx_spdif_chanstats(void __iomem *base, unsigned int rate) +{ + u32 cstas1; + + switch (rate) { + case 22050: + cstas1 = IEC958_AES3_CON_FS_22050; + break; + case 24000: + cstas1 = IEC958_AES3_CON_FS_24000; + break; + case 32000: + cstas1 = IEC958_AES3_CON_FS_32000; + break; + case 44100: + cstas1 = IEC958_AES3_CON_FS_44100; + break; + case 48000: + cstas1 = IEC958_AES3_CON_FS_48000; + break; + case 88200: + cstas1 = IEC958_AES3_CON_FS_88200; + break; + case 96000: + cstas1 = IEC958_AES3_CON_FS_96000; + break; + case 176400: + cstas1 = IEC958_AES3_CON_FS_176400; + break; + case 192000: + cstas1 = IEC958_AES3_CON_FS_192000; + break; + default: + return -EINVAL; + } + cstas1 = cstas1 << 24; + cstas1 |= IEC958_AES0_CON_NOT_COPYRIGHT; + + writel_relaxed(cstas1, base + ZX_CH_STA_1); + return 0; +} + +static int zx_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct zx_spdif_info *zx_spdif = dev_get_drvdata(socdai->dev); + struct zx_spdif_info *spdif = snd_soc_dai_get_drvdata(socdai); + struct snd_dmaengine_dai_dma_data *dma_data = &zx_spdif->dma_data; + u32 val, ch_num, rate; + int ret; + + dma_data = snd_soc_dai_get_dma_data(socdai, substream); + dma_data->addr_width = params_width(params) >> 3; + + val = readl_relaxed(zx_spdif->reg_base + ZX_CTRL); + val &= ~ZX_CTRL_MODA_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val |= ZX_CTRL_MODA_16; + break; + + case SNDRV_PCM_FORMAT_S18_3LE: + val |= ZX_CTRL_MODA_18; + break; + + case SNDRV_PCM_FORMAT_S20_3LE: + val |= ZX_CTRL_MODA_20; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + val |= ZX_CTRL_MODA_24; + break; + default: + dev_err(socdai->dev, "Format not support!\n"); + return -EINVAL; + } + + ch_num = params_channels(params); + if (ch_num == 2) + val |= ZX_CTRL_DOUBLE_TRACK; + else + val |= ZX_CTRL_LEFT_TRACK; + writel_relaxed(val, zx_spdif->reg_base + ZX_CTRL); + + val = readl_relaxed(zx_spdif->reg_base + ZX_VALID_BIT); + val &= ~ZX_VALID_TRACK_MASK; + if (ch_num == 2) + val |= ZX_VALID_DOUBLE_TRACK; + else + val |= ZX_VALID_RIGHT_TRACK; + writel_relaxed(val, zx_spdif->reg_base + ZX_VALID_BIT); + + rate = params_rate(params); + ret = zx_spdif_chanstats(zx_spdif->reg_base, rate); + if (ret) + return ret; + ret = clk_set_rate(spdif->dai_clk, rate * ch_num * ZX_SPDIF_CLK_RAT); + if (ret) + return ret; + + return 0; +} + +static void zx_spdif_cfg_tx(void __iomem *base, int on) +{ + u32 val; + + val = readl_relaxed(base + ZX_CTRL); + val &= ~(ZX_CTRL_ENB_MASK | ZX_CTRL_TX_MASK); + val |= on ? ZX_CTRL_OPEN : ZX_CTRL_CLOSE; + writel_relaxed(val, base + ZX_CTRL); + + val = readl_relaxed(base + ZX_FIFOCTRL); + val &= ~ZX_FIFOCTRL_TX_DMA_EN_MASK; + if (on) + val |= ZX_FIFOCTRL_TX_DMA_EN; + writel_relaxed(val, base + ZX_FIFOCTRL); +} + +static int zx_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + u32 val; + struct zx_spdif_info *zx_spdif = dev_get_drvdata(dai->dev); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + val = readl_relaxed(zx_spdif->reg_base + ZX_FIFOCTRL); + val |= ZX_FIFOCTRL_TX_FIFO_RST; + writel_relaxed(val, zx_spdif->reg_base + ZX_FIFOCTRL); + /* fall thru */ + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + zx_spdif_cfg_tx(zx_spdif->reg_base, true); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + zx_spdif_cfg_tx(zx_spdif->reg_base, false); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int zx_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_spdif_info *zx_spdif = dev_get_drvdata(dai->dev); + + return clk_prepare_enable(zx_spdif->dai_clk); +} + +static void zx_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_spdif_info *zx_spdif = dev_get_drvdata(dai->dev); + + clk_disable_unprepare(zx_spdif->dai_clk); +} + +#define ZX_RATES \ + (SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define ZX_FORMAT \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE \ + | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops zx_spdif_dai_ops = { + .trigger = zx_spdif_trigger, + .startup = zx_spdif_startup, + .shutdown = zx_spdif_shutdown, + .hw_params = zx_spdif_hw_params, +}; + +static struct snd_soc_dai_driver zx_spdif_dai = { + .name = "spdif", + .id = 0, + .probe = zx_spdif_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = ZX_RATES, + .formats = ZX_FORMAT, + }, + .ops = &zx_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver zx_spdif_component = { + .name = "spdif", +}; + +static void zx_spdif_dev_init(void __iomem *base) +{ + u32 val; + + writel_relaxed(0, base + ZX_CTRL); + writel_relaxed(0, base + ZX_INT_MASK); + writel_relaxed(0xf, base + ZX_INT_STATUS); + writel_relaxed(0x1, base + ZX_FIFOCTRL); + + val = readl_relaxed(base + ZX_FIFOCTRL); + val &= ~(ZX_FIFOCTRL_TXTH_MASK | ZX_FIFOCTRL_TX_FIFO_RST_MASK); + val |= ZX_FIFOCTRL_TXTH(8); + writel_relaxed(val, base + ZX_FIFOCTRL); +} + +static int zx_spdif_probe(struct platform_device *pdev) +{ + struct resource *res; + struct zx_spdif_info *zx_spdif; + int ret; + + zx_spdif = kzalloc(sizeof(*zx_spdif), GFP_KERNEL); + if (!zx_spdif) + return -ENOMEM; + + zx_spdif->dai_clk = devm_clk_get(&pdev->dev, "tx"); + if (IS_ERR(zx_spdif->dai_clk)) { + dev_err(&pdev->dev, "Fail to get clk\n"); + return PTR_ERR(zx_spdif->dai_clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + zx_spdif->mapbase = res->start; + zx_spdif->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (!zx_spdif->reg_base) { + dev_err(&pdev->dev, "ioremap failed!\n"); + return -EIO; + } + + zx_spdif_dev_init(zx_spdif->reg_base); + platform_set_drvdata(pdev, zx_spdif); + + ret = snd_soc_register_component(&pdev->dev, &zx_spdif_component, + &zx_spdif_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register DAI failed: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id zx_spdif_dt_ids[] = { + { .compatible = "zte,zx296702-spdif", }, + {} +}; +MODULE_DEVICE_TABLE(of, zx_spdif_dt_ids); + +static struct platform_driver spdif_driver = { + .probe = zx_spdif_probe, + .driver = { + .name = "zx-spdif", + .owner = THIS_MODULE, + .of_match_table = zx_spdif_dt_ids, + }, +}; + +module_platform_driver(spdif_driver); + +MODULE_AUTHOR("Jun Nie jun.nie@linaro.org"); +MODULE_DESCRIPTION("ZTE SPDIF SoC DAI"); +MODULE_LICENSE("GPL");
On 05/29/2015 03:31 PM, Jun Nie wrote: [...]
+static int zx_spdif_probe(struct platform_device *pdev) +{
- struct resource *res;
- struct zx_spdif_info *zx_spdif;
- int ret;
- zx_spdif = kzalloc(sizeof(*zx_spdif), GFP_KERNEL);
extra space and devm_
- if (!zx_spdif)
return -ENOMEM;
- zx_spdif->dai_clk = devm_clk_get(&pdev->dev, "tx");
- if (IS_ERR(zx_spdif->dai_clk)) {
dev_err(&pdev->dev, "Fail to get clk\n");
return PTR_ERR(zx_spdif->dai_clk);
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- zx_spdif->mapbase = res->start;
- zx_spdif->reg_base = devm_ioremap_resource(&pdev->dev, res);
- if (!zx_spdif->reg_base) {
dev_err(&pdev->dev, "ioremap failed!\n");
return -EIO;
- }
- zx_spdif_dev_init(zx_spdif->reg_base);
- platform_set_drvdata(pdev, zx_spdif);
- ret = snd_soc_register_component(&pdev->dev, &zx_spdif_component,
&zx_spdif_dai, 1);
devm_
- if (ret) {
dev_err(&pdev->dev, "Register DAI failed: %d\n", ret);
return ret;
- }
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
- if (ret)
dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret);
- return ret;
Add ZTE zx296702 I2S interface DAI driver
Signed-off-by: Jun Nie jun.nie@linaro.org --- sound/soc/zte/Kconfig | 9 + sound/soc/zte/Makefile | 1 + sound/soc/zte/zx296702-i2s.c | 437 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 447 insertions(+) create mode 100644 sound/soc/zte/zx296702-i2s.c
diff --git a/sound/soc/zte/Kconfig b/sound/soc/zte/Kconfig index 4f05573..c47eb25 100644 --- a/sound/soc/zte/Kconfig +++ b/sound/soc/zte/Kconfig @@ -6,3 +6,12 @@ config ZX296702_SPDIF help Say Y or M if you want to add support for codecs attached to the zx296702 spdif interface + +config ZX296702_I2S + tristate "ZX296702 i2s" + depends on SOC_ZX296702 || COMPILE_TEST + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to the + zx296702 i2s interface diff --git a/sound/soc/zte/Makefile b/sound/soc/zte/Makefile index fb3a4a0..254ed2c 100644 --- a/sound/soc/zte/Makefile +++ b/sound/soc/zte/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_ZX296702_SPDIF) += zx296702-spdif.o +obj-$(CONFIG_ZX296702_I2S) += zx296702-i2s.o diff --git a/sound/soc/zte/zx296702-i2s.c b/sound/soc/zte/zx296702-i2s.c new file mode 100644 index 0000000..cf6005c --- /dev/null +++ b/sound/soc/zte/zx296702-i2s.c @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2015 Linaro + * + * Author: Jun Nie jun.nie@linaro.org + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#define ZX_I2S_PROCESS_CTRL 0x04 +#define ZX_I2S_TIMING_CTRL 0x08 +#define ZX_I2S_FIFO_CTRL 0x0C +#define ZX_I2S_FIFO_STATUS 0x10 +#define ZX_I2S_INT_EN 0x14 +#define ZX_I2S_INT_STATUS 0x18 +#define ZX_I2S_DATA 0x1C +#define ZX_I2S_FRAME_CNTR 0x20 + +#define I2S_DEAGULT_FIFO_THRES (0x10) +#define I2S_MAX_FIFO_THRES (0x20) + +#define ZX_I2S_PROCESS_TX_EN (1 << 0) +#define ZX_I2S_PROCESS_TX_DIS (0 << 0) +#define ZX_I2S_PROCESS_RX_EN (1 << 1) +#define ZX_I2S_PROCESS_RX_DIS (0 << 1) +#define ZX_I2S_PROCESS_I2S_EN (1 << 2) +#define ZX_I2S_PROCESS_I2S_DIS (0 << 2) + +#define ZX_I2S_TIMING_MAST (1 << 0) +#define ZX_I2S_TIMING_SLAVE (0 << 0) +#define ZX_I2S_TIMING_MS_MASK (1 << 0) +#define ZX_I2S_TIMING_LOOP (1 << 1) +#define ZX_I2S_TIMING_NOR (0 << 1) +#define ZX_I2S_TIMING_LOOP_MASK (1 << 1) +#define ZX_I2S_TIMING_PTNR (1 << 2) +#define ZX_I2S_TIMING_NTPR (0 << 2) +#define ZX_I2S_TIMING_PHASE_MASK (1 << 2) +#define ZX_I2S_TIMING_TDM (1 << 3) +#define ZX_I2S_TIMING_I2S (0 << 3) +#define ZX_I2S_TIMING_TIMING_MASK (1 << 3) +#define ZX_I2S_TIMING_LONG_SYNC (1 << 4) +#define ZX_I2S_TIMING_SHORT_SYNC (0 << 4) +#define ZX_I2S_TIMING_SYNC_MASK (1 << 4) +#define ZX_I2S_TIMING_TEAK_EN (1 << 5) +#define ZX_I2S_TIMING_TEAK_DIS (0 << 5) +#define ZX_I2S_TIMING_TEAK_MASK (1 << 5) +#define ZX_I2S_TIMING_STD_I2S (0 << 6) +#define ZX_I2S_TIMING_MSB_JUSTIF (1 << 6) +#define ZX_I2S_TIMING_LSB_JUSTIF (2 << 6) +#define ZX_I2S_TIMING_ALIGN_MASK (3 << 6) +#define ZX_I2S_TIMING_CHN_MASK (7 << 8) +#define ZX_I2S_TIMING_CHN(x) ((x - 1) << 8) +#define ZX_I2S_TIMING_LANE_MASK (3 << 11) +#define ZX_I2S_TIMING_LANE(x) ((x - 1) << 11) +#define ZX_I2S_TIMING_TSCFG_MASK (7 << 13) +#define ZX_I2S_TIMING_TSCFG(x) (x << 13) +#define ZX_I2S_TIMING_TS_WIDTH_MASK (0x1f << 16) +#define ZX_I2S_TIMING_TS_WIDTH(x) ((x - 1) << 16) +#define ZX_I2S_TIMING_DATA_SIZE_MASK (0x1f << 21) +#define ZX_I2S_TIMING_DATA_SIZE(x) ((x - 1) << 21) +#define ZX_I2S_TIMING_CFG_ERR_MASK (1 << 31) + +#define ZX_I2S_FIFO_CTRL_TX_RST (1 << 0) +#define ZX_I2S_FIFO_CTRL_TX_RST_MASK (1 << 0) +#define ZX_I2S_FIFO_CTRL_RX_RST (1 << 1) +#define ZX_I2S_FIFO_CTRL_RX_RST_MASK (1 << 1) +#define ZX_I2S_FIFO_CTRL_TX_DMA_EN (1 << 4) +#define ZX_I2S_FIFO_CTRL_TX_DMA_DIS (0 << 4) +#define ZX_I2S_FIFO_CTRL_TX_DMA_MASK (1 << 4) +#define ZX_I2S_FIFO_CTRL_RX_DMA_EN (1 << 5) +#define ZX_I2S_FIFO_CTRL_RX_DMA_DIS (0 << 5) +#define ZX_I2S_FIFO_CTRL_RX_DMA_MASK (1 << 5) +#define ZX_I2S_FIFO_CTRL_TX_THRES_MASK (0x1F << 8) +#define ZX_I2S_FIFO_CTRL_RX_THRES_MASK (0x1F << 16) + +#define CLK_RAT (32 * 4) + +struct zx_i2s_info { + struct snd_dmaengine_dai_dma_data dma_playback; + struct snd_dmaengine_dai_dma_data dma_capture; + struct clk *dai_clk; + void __iomem *reg_base; + int master; + resource_size_t mapbase; +}; + +static void zx_i2s_tx_en(void __iomem *base, bool on) +{ + unsigned long val; + + val = readl_relaxed(base + ZX_I2S_PROCESS_CTRL); + if (on) + val |= ZX_I2S_PROCESS_TX_EN | ZX_I2S_PROCESS_I2S_EN; + else + val &= ~(ZX_I2S_PROCESS_TX_EN | ZX_I2S_PROCESS_I2S_EN); + writel_relaxed(val, base + ZX_I2S_PROCESS_CTRL); +} + +static void zx_i2s_rx_en(void __iomem *base, bool on) +{ + unsigned long val; + + val = readl_relaxed(base + ZX_I2S_PROCESS_CTRL); + if (on) + val |= ZX_I2S_PROCESS_RX_EN | ZX_I2S_PROCESS_I2S_EN; + else + val &= ~(ZX_I2S_PROCESS_RX_EN | ZX_I2S_PROCESS_I2S_EN); + writel_relaxed(val, base + ZX_I2S_PROCESS_CTRL); +} + +static void zx_i2s_tx_dma_en(void __iomem *base, bool on) +{ + unsigned long val; + + val = readl_relaxed(base + ZX_I2S_FIFO_CTRL); + val |= ZX_I2S_FIFO_CTRL_TX_RST | (I2S_DEAGULT_FIFO_THRES << 8); + if (on) + val |= ZX_I2S_FIFO_CTRL_TX_DMA_EN; + else + val &= ~ZX_I2S_FIFO_CTRL_TX_DMA_EN; + writel_relaxed(val, base + ZX_I2S_FIFO_CTRL); +} + +static void zx_i2s_rx_dma_en(void __iomem *base, bool on) +{ + unsigned long val; + + val = readl_relaxed(base + ZX_I2S_FIFO_CTRL); + val |= ZX_I2S_FIFO_CTRL_RX_RST | (I2S_DEAGULT_FIFO_THRES << 16); + if (on) + val |= ZX_I2S_FIFO_CTRL_RX_DMA_EN; + else + val &= ~ZX_I2S_FIFO_CTRL_RX_DMA_EN; + writel_relaxed(val, base + ZX_I2S_FIFO_CTRL); +} + +#define ZX_I2S_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000| \ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define ZX_I2S_FMTBIT \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int zx_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct zx_i2s_info *zx_i2s = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, zx_i2s); + zx_i2s->dma_playback.addr = zx_i2s->mapbase + ZX_I2S_DATA; + zx_i2s->dma_playback.maxburst = 16; + zx_i2s->dma_capture.addr = zx_i2s->mapbase + ZX_I2S_DATA; + zx_i2s->dma_capture.maxburst = 16; + snd_soc_dai_init_dma_data(dai, &zx_i2s->dma_playback, + &zx_i2s->dma_capture); + return 0; +} + +static int zx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct zx_i2s_info *i2s = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long val; + + val = readl_relaxed(i2s->reg_base + ZX_I2S_TIMING_CTRL); + val &= ~(ZX_I2S_TIMING_TIMING_MASK | ZX_I2S_TIMING_ALIGN_MASK | + ZX_I2S_TIMING_TEAK_MASK | ZX_I2S_TIMING_SYNC_MASK | + ZX_I2S_TIMING_MS_MASK); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= (ZX_I2S_TIMING_I2S | ZX_I2S_TIMING_STD_I2S); + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= (ZX_I2S_TIMING_I2S | ZX_I2S_TIMING_MSB_JUSTIF); + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= (ZX_I2S_TIMING_I2S | ZX_I2S_TIMING_LSB_JUSTIF); + break; + default: + dev_err(cpu_dai->dev, "Unknown i2s timeing\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->master = 1; + val |= ZX_I2S_TIMING_MAST; + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->master = 0; + val |= ZX_I2S_TIMING_SLAVE; + break; + default: + dev_err(cpu_dai->dev, "Unknown master/slave format\n"); + return -EINVAL; + } + + writel_relaxed(val, i2s->reg_base + ZX_I2S_TIMING_CTRL); + return 0; +} + +static int zx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct zx_i2s_info *i2s = snd_soc_dai_get_drvdata(socdai); + struct snd_dmaengine_dai_dma_data *dma_data; + unsigned int lane, ch_num, len, ret = 0; + unsigned long val, format; + unsigned long chn_cfg; + + dma_data = snd_soc_dai_get_dma_data(socdai, substream); + dma_data->addr_width = params_width(params) >> 3; + + val = readl_relaxed(i2s->reg_base + ZX_I2S_TIMING_CTRL); + val &= ~(ZX_I2S_TIMING_TS_WIDTH_MASK | ZX_I2S_TIMING_DATA_SIZE_MASK | + ZX_I2S_TIMING_LANE_MASK | ZX_I2S_TIMING_CHN_MASK | + ZX_I2S_TIMING_TSCFG_MASK); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + format = 0; + len = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + format = 1; + len = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + format = 2; + len = 32; + break; + default: + dev_err(socdai->dev, "Unknown data format\n"); + return -EINVAL; + } + val |= ZX_I2S_TIMING_TS_WIDTH(len) | ZX_I2S_TIMING_DATA_SIZE(len); + + ch_num = params_channels(params); + switch (ch_num) { + case 1: + lane = 1; + chn_cfg = 2; + break; + case 2: + case 4: + case 6: + case 8: + lane = ch_num / 2; + chn_cfg = 3; + break; + default: + dev_err(socdai->dev, "Not support channel num %d\n", ch_num); + return -EINVAL; + } + val |= ZX_I2S_TIMING_LANE(lane); + val |= ZX_I2S_TIMING_TSCFG(chn_cfg); + val |= ZX_I2S_TIMING_CHN(ch_num); + writel_relaxed(val, i2s->reg_base + ZX_I2S_TIMING_CTRL); + + if (i2s->master) + ret = clk_set_rate(i2s->dai_clk, + params_rate(params) * ch_num * CLK_RAT); + return ret; +} + +static int zx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct zx_i2s_info *zx_i2s = dev_get_drvdata(dai->dev); + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (capture) + zx_i2s_rx_dma_en(zx_i2s->reg_base, true); + else + zx_i2s_tx_dma_en(zx_i2s->reg_base, true); + /* fall thru */ + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (capture) + zx_i2s_rx_en(zx_i2s->reg_base, true); + else + zx_i2s_tx_en(zx_i2s->reg_base, true); + break; + + case SNDRV_PCM_TRIGGER_STOP: + if (capture) + zx_i2s_rx_dma_en(zx_i2s->reg_base, false); + else + zx_i2s_tx_dma_en(zx_i2s->reg_base, false); + /* fall thru */ + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (capture) + zx_i2s_rx_en(zx_i2s->reg_base, false); + else + zx_i2s_tx_en(zx_i2s->reg_base, false); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int zx_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_i2s_info *zx_i2s = dev_get_drvdata(dai->dev); + + return clk_prepare_enable(zx_i2s->dai_clk); +} + +static void zx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_i2s_info *zx_i2s = dev_get_drvdata(dai->dev); + + clk_disable_unprepare(zx_i2s->dai_clk); +} + +static struct snd_soc_dai_ops zx_i2s_dai_ops = { + .trigger = zx_i2s_trigger, + .hw_params = zx_i2s_hw_params, + .set_fmt = zx_i2s_set_fmt, + .startup = zx_i2s_startup, + .shutdown = zx_i2s_shutdown, +}; + +static const struct snd_soc_component_driver zx_i2s_component = { + .name = "zx-i2s", +}; + +struct snd_soc_dai_driver zx_i2s_dai = { + .name = "zx-i2s-dai", + .id = 0, + .probe = zx_i2s_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 8, + .rates = ZX_I2S_RATES, + .formats = ZX_I2S_FMTBIT, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = ZX_I2S_RATES, + .formats = ZX_I2S_FMTBIT, + }, + .ops = &zx_i2s_dai_ops, +}; + +static int zx_i2s_probe(struct platform_device *pdev) +{ + struct resource *res; + struct zx_i2s_info *zx_i2s; + int ret; + + zx_i2s = kzalloc(sizeof(*zx_i2s), GFP_KERNEL); + if (!zx_i2s) + return -ENOMEM; + + zx_i2s->dai_clk = devm_clk_get(&pdev->dev, "tx"); + if (IS_ERR(zx_i2s->dai_clk)) { + dev_err(&pdev->dev, "Fail to get clk\n"); + return PTR_ERR(zx_i2s->dai_clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + zx_i2s->mapbase = res->start; + zx_i2s->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (!zx_i2s->reg_base) { + dev_err(&pdev->dev, "ioremap failed!\n"); + return -EIO; + } + + writel_relaxed(0, zx_i2s->reg_base + ZX_I2S_FIFO_CTRL); + platform_set_drvdata(pdev, zx_i2s); + + ret = snd_soc_register_component(&pdev->dev, &zx_i2s_component, + &zx_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register DAI failed: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id zx_i2s_dt_ids[] = { + { .compatible = "zte,zx296702-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, zx_i2s_dt_ids); + +static struct platform_driver i2s_driver = { + .probe = zx_i2s_probe, + .driver = { + .name = "zx-i2s", + .owner = THIS_MODULE, + .of_match_table = zx_i2s_dt_ids, + }, +}; + +module_platform_driver(i2s_driver); + +MODULE_AUTHOR("Jun Nie jun.nie@linaro.org"); +MODULE_DESCRIPTION("ZTE I2S SoC DAI"); +MODULE_LICENSE("GPL");
Add zx296702 hdmi codec to enable SPDIF and I2S output via HDMI. The SPDIF/I2S route is exclusive with current software config and need specify which is valid in defconfig.
Signed-off-by: Jun Nie jun.nie@linaro.org --- include/sound/zx_hdmi_audio.h | 7 +++ sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 1 + sound/soc/codecs/zx296702_hdmi.c | 121 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 include/sound/zx_hdmi_audio.h create mode 100644 sound/soc/codecs/zx296702_hdmi.c
diff --git a/include/sound/zx_hdmi_audio.h b/include/sound/zx_hdmi_audio.h new file mode 100644 index 0000000..7eb0e81 --- /dev/null +++ b/include/sound/zx_hdmi_audio.h @@ -0,0 +1,7 @@ +#ifndef __ZX_HDMI_AUDIO_H__ +#define __ZX_HDMI_AUDIO_H__ + +int zx_hdmi_audio_cfg(int audio_codec, int audio_way, + u32 sample_rate, u32 sample_len); +void zx_hdmi_audio_en(int on); +#endif /* __ZX_HDMI_AUDIO_H__ */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 061c465..1ba34ca 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -844,6 +844,9 @@ config SND_SOC_WM9712 config SND_SOC_WM9713 tristate
+config SND_SOC_ZX_HDMI + tristate + # Amp config SND_SOC_LM4857 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index abe2d7e..b6c4630 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -357,6 +357,7 @@ obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o +obj-$(CONFIG_SND_SOC_ZX_HDMI) += zx296702_hdmi.o
# Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/zx296702_hdmi.c b/sound/soc/codecs/zx296702_hdmi.c new file mode 100644 index 0000000..b256e54 --- /dev/null +++ b/sound/soc/codecs/zx296702_hdmi.c @@ -0,0 +1,121 @@ +/* + * ALSA SoC codec driver for HDMI audio codecs. + * Copyright (C) 2015 Linaro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + */ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/zx_hdmi_audio.h> + +#define DRV_NAME "zx-hdmi-codec" + +static int zx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ +#ifdef CONFIG_ZX_HDMI_SND_SPDIF + zx_hdmi_audio_cfg(1, 0, params_rate(params), +#endif +#ifdef CONFIG_ZX_HDMI_SND_I2S + zx_hdmi_audio_cfg(1, 1, params_rate(params), +#endif + params_physical_width(params)); + return 0; +} + +static int zx_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +#ifdef CONFIG_ZX_HDMI_SND_SPDIF + zx_hdmi_audio_en(1); +#endif + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +#ifdef CONFIG_ZX_HDMI_SND_SPDIF + /* Mute HDMI before disabling spdif */ + zx_hdmi_audio_en(0); +#endif + break; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops zx_dai_ops = { + .trigger = zx_trigger, + .hw_params = zx_hw_params, +}; + +static struct snd_soc_dai_driver zx_hdmi_codec_dai = { + .name = "zx-hdmi-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 24, + }, + .ops = &zx_dai_ops, +}; + +#ifdef CONFIG_OF +static const struct of_device_id zx_hdmi_audio_codec_ids[] = { + { .compatible = "zte,hdmi-audio", }, + { } +}; +MODULE_DEVICE_TABLE(of, zx_hdmi_audio_codec_ids); +#endif + +static struct snd_soc_codec_driver zx_hdmi_codec = { +}; + +static int zx_hdmi_codec_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, &zx_hdmi_codec, + &zx_hdmi_codec_dai, 1); +} + +static int zx_hdmi_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver zx_hdmi_codec_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(zx_hdmi_audio_codec_ids), + }, + + .probe = zx_hdmi_codec_probe, + .remove = zx_hdmi_codec_remove, +}; + +module_platform_driver(zx_hdmi_codec_driver); + +MODULE_AUTHOR("Jun Nie jun.nie@linaro.org"); +MODULE_DESCRIPTION("ZX296702 ASoC HDMI codec driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME);
On 05/29/2015 03:31 PM, Jun Nie wrote:
Add zx296702 hdmi codec to enable SPDIF and I2S output via HDMI. The SPDIF/I2S route is exclusive with current software config and need specify which is valid in defconfig.
That's an issue, the hardware configuration should not depend on kernel configuration setting. This makes it impossible to run the same kernel on platforms with conflicting settings. It should be possible to configure this dynamically at boot time based on platform_data/devicetree, etc.
But is this device real hardware anyway?
Signed-off-by: Jun Nie jun.nie@linaro.org
include/sound/zx_hdmi_audio.h | 7 +++ sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 1 + sound/soc/codecs/zx296702_hdmi.c | 121 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 include/sound/zx_hdmi_audio.h create mode 100644 sound/soc/codecs/zx296702_hdmi.c
diff --git a/include/sound/zx_hdmi_audio.h b/include/sound/zx_hdmi_audio.h new file mode 100644 index 0000000..7eb0e81 --- /dev/null +++ b/include/sound/zx_hdmi_audio.h @@ -0,0 +1,7 @@ +#ifndef __ZX_HDMI_AUDIO_H__ +#define __ZX_HDMI_AUDIO_H__
+int zx_hdmi_audio_cfg(int audio_codec, int audio_way,
u32 sample_rate, u32 sample_len);
+void zx_hdmi_audio_en(int on);
Where is the implementation for these functions? The fact that they seem to operate on global state indicates that something is wrong wit this API.
+#endif /* __ZX_HDMI_AUDIO_H__ */
On Fri, May 29, 2015 at 04:10:54PM +0200, Lars-Peter Clausen wrote:
On 05/29/2015 03:31 PM, Jun Nie wrote:
Add zx296702 hdmi codec to enable SPDIF and I2S output via HDMI. The SPDIF/I2S route is exclusive with current software config and need specify which is valid in defconfig.
That's an issue, the hardware configuration should not depend on kernel configuration setting. This makes it impossible to run the same kernel on platforms with conflicting settings. It should be possible to configure this dynamically at boot time based on platform_data/devicetree, etc.
But is this device real hardware anyway?
See previous discussions - the SoC has an internal HDMI encoder connected to a S/PDIF IP which can also be brought out directly as that. The solution suggested in the previous discussions was to represent the HDMI IP as a CODEC and connect the S/PDIF IP up to it with a machine driver, I've not looked at this patch yet though.
2015-05-29 23:18 GMT+08:00 Mark Brown broonie@kernel.org:
On Fri, May 29, 2015 at 04:10:54PM +0200, Lars-Peter Clausen wrote:
On 05/29/2015 03:31 PM, Jun Nie wrote:
Add zx296702 hdmi codec to enable SPDIF and I2S output via HDMI. The SPDIF/I2S route is exclusive with current software config and need specify which is valid in defconfig.
That's an issue, the hardware configuration should not depend on kernel configuration setting. This makes it impossible to run the same kernel on platforms with conflicting settings. It should be possible to configure this dynamically at boot time based on platform_data/devicetree, etc.
But is this device real hardware anyway?
It is a ready hardware with HDMI audio configured either from internal SPDIF or I2S interface. Because DAI and CODEC are totally independent in ASoC design, so CODEC does not know what DAI interface is active. I guess I can add a dts property to indicate that from machine dts config.
See previous discussions - the SoC has an internal HDMI encoder connected to a S/PDIF IP which can also be brought out directly as that. The solution suggested in the previous discussions was to represent the HDMI IP as a CODEC and connect the S/PDIF IP up to it with a machine driver, I've not looked at this patch yet though.
So I need implement dai link in machine audio card driver, while not use simple card and device tree initialization. In this way, I surely can embed HDMI audio codec in HDMI driver. Is that right?
I had try to initialize audio all from dts, so need a HDMI codec dt node to connect with DAI. However, I cannot find a way to create an independent HDMI CODEC dt node because it is brought up from HDMI driver. If my above understanding is correct, a machine level DAI link shall resolve this issue with dropping simple card and DAI link in dts. Thanks for your comments!
BTW: HDMI driver is far from mature, so you did not see HDMI function implementation in this patch serial.
Jun
On Mon, Jun 01, 2015 at 09:42:34AM +0800, Jun Nie wrote:
So I need implement dai link in machine audio card driver, while not use simple card and device tree initialization. In this way, I surely can embed HDMI audio codec in HDMI driver. Is that right?
I'm sorry, I don't entirely follow what you're saying here.
I had try to initialize audio all from dts, so need a HDMI codec dt node to connect with DAI. However, I cannot find a way to create an independent HDMI CODEC dt node because it is brought up from HDMI driver. If my above understanding is correct, a machine level DAI link shall resolve this issue with dropping simple card and DAI link in dts. Thanks for your comments!
If the HDMI encoder is simple I'd expect it to be possible to use it with simple-card. If it isn't then we can look at why.
BTW: HDMI driver is far from mature, so you did not see HDMI function implementation in this patch serial.
Sure, no problem.
2015-06-03 2:13 GMT+08:00 Mark Brown broonie@kernel.org:
On Mon, Jun 01, 2015 at 09:42:34AM +0800, Jun Nie wrote:
So I need implement dai link in machine audio card driver, while not use simple card and device tree initialization. In this way, I surely can embed HDMI audio codec in HDMI driver. Is that right?
I'm sorry, I don't entirely follow what you're saying here.
I am guessing you suggest me to implement a ASoC board driver, like sound/soc/omap/omap3pandora.c. I can link HDMI codec with DAI with snd_soc_dai_link structure to avoid specify the connection in dts.
I had try to initialize audio all from dts, so need a HDMI codec dt node to connect with DAI. However, I cannot find a way to create an independent HDMI CODEC dt node because it is brought up from HDMI driver. If my above understanding is correct, a machine level DAI link shall resolve this issue with dropping simple card and DAI link in dts. Thanks for your comments!
If the HDMI encoder is simple I'd expect it to be possible to use it with simple-card. If it isn't then we can look at why.
Most of devices is initialized from devicetree if not all on my board. So I need fill CODEC/DAI dt node for imple-card in dts. I experience the difficulty that I cannot create HDMI CODEC dt node. Because HDMI CODEC shall be part of HDMI driver and be brought up with direct call to snd_soc_register_codec in HDMI driver probe function per my understanding. Then I do not have an independent dt node for HDMI CODEC, thus cannot link the CODEC to DAI with dts information. Do you see any chance to connect a DAI to an embedded CODEC of HDMI video device? Thank you!
BTW: HDMI driver is far from mature, so you did not see HDMI function implementation in this patch serial.
Sure, no problem.
On Wed, Jun 03, 2015 at 09:47:46AM +0800, Jun Nie wrote:
2015-06-03 2:13 GMT+08:00 Mark Brown broonie@kernel.org:
On Mon, Jun 01, 2015 at 09:42:34AM +0800, Jun Nie wrote:
So I need implement dai link in machine audio card driver, while not use simple card and device tree initialization. In this way, I surely can embed HDMI audio codec in HDMI driver. Is that right?
I'm sorry, I don't entirely follow what you're saying here.
I am guessing you suggest me to implement a ASoC board driver, like sound/soc/omap/omap3pandora.c. I can link HDMI codec with DAI with snd_soc_dai_link structure to avoid specify the connection in dts.
You can do that if you like, but it's also fine to show the link between the S/PDIF and HDMI IPs in DT if you like.
I had try to initialize audio all from dts, so need a HDMI codec dt node to connect with DAI. However, I cannot find a way to create an independent HDMI CODEC dt node because it is brought up from HDMI driver. If my above understanding is correct, a machine level DAI link shall resolve this issue with dropping simple card and DAI link in dts. Thanks for your comments!
If the HDMI encoder is simple I'd expect it to be possible to use it with simple-card. If it isn't then we can look at why.
Most of devices is initialized from devicetree if not all on my board. So I need fill CODEC/DAI dt node for imple-card in dts. I experience the difficulty that I cannot create HDMI CODEC dt node. Because HDMI CODEC shall be part of HDMI driver and be brought up with direct call to snd_soc_register_codec in HDMI driver probe function per my understanding. Then I do not have an independent dt node for HDMI CODEC, thus cannot link the CODEC to DAI with dts information. Do you see any chance to connect a DAI to an embedded CODEC of HDMI video device? Thank you!
Are you saying that the HDMI IP doesn't appear in DT at all? If the HDMI IP appears in DT it should be possible to reference it.
2015-06-03 19:01 GMT+08:00 Mark Brown broonie@kernel.org:
On Wed, Jun 03, 2015 at 09:47:46AM +0800, Jun Nie wrote:
2015-06-03 2:13 GMT+08:00 Mark Brown broonie@kernel.org:
On Mon, Jun 01, 2015 at 09:42:34AM +0800, Jun Nie wrote:
So I need implement dai link in machine audio card driver, while not use simple card and device tree initialization. In this way, I surely can embed HDMI audio codec in HDMI driver. Is that right?
I'm sorry, I don't entirely follow what you're saying here.
I am guessing you suggest me to implement a ASoC board driver, like sound/soc/omap/omap3pandora.c. I can link HDMI codec with DAI with snd_soc_dai_link structure to avoid specify the connection in dts.
You can do that if you like, but it's also fine to show the link between the S/PDIF and HDMI IPs in DT if you like.
I had try to initialize audio all from dts, so need a HDMI codec dt node to connect with DAI. However, I cannot find a way to create an independent HDMI CODEC dt node because it is brought up from HDMI driver. If my above understanding is correct, a machine level DAI link shall resolve this issue with dropping simple card and DAI link in dts. Thanks for your comments!
If the HDMI encoder is simple I'd expect it to be possible to use it with simple-card. If it isn't then we can look at why.
Most of devices is initialized from devicetree if not all on my board. So I need fill CODEC/DAI dt node for imple-card in dts. I experience the difficulty that I cannot create HDMI CODEC dt node. Because HDMI CODEC shall be part of HDMI driver and be brought up with direct call to snd_soc_register_codec in HDMI driver probe function per my understanding. Then I do not have an independent dt node for HDMI CODEC, thus cannot link the CODEC to DAI with dts information. Do you see any chance to connect a DAI to an embedded CODEC of HDMI video device? Thank you!
Are you saying that the HDMI IP doesn't appear in DT at all? If the HDMI IP appears in DT it should be possible to reference it.
Thanks for confirmation. Seems I have much to learn in DT. Will create a CODEC device in HDMI display driver and feed HDMI device to simple card as below to have a try. Thanks! Do you think other patches are OK to merge except the two HDMI patches?
hdmi: hdmi@0x12340000 { compatible = "zte,zx296702-hdmi"; reg = <0x12340000 0x1000>; };
sound { compatible = "simple-audio-card"; simple-audio-card,name = "zx296702_snd"; simple-audio-card,cpu { sound-dai = <&spdif0>; };
simple-audio-card,codec { sound-dai = <&hdmi>; }; };
On Wed, Jun 03, 2015 at 08:40:31PM +0800, Jun Nie wrote:
2015-06-03 19:01 GMT+08:00 Mark Brown broonie@kernel.org:
Are you saying that the HDMI IP doesn't appear in DT at all? If the HDMI IP appears in DT it should be possible to reference it.
Thanks for confirmation. Seems I have much to learn in DT. Will create a CODEC device in HDMI display driver and feed HDMI device to simple card as below to have a try. Thanks! Do you think other patches are OK to merge except the two HDMI patches?
Lars had some comments on the S/PDIF driver. I didn't look at the I2S driver yet, I'll try to do that today.
On Fri, May 29, 2015 at 09:31:21PM +0800, Jun Nie wrote:
+int zx_hdmi_audio_cfg(int audio_codec, int audio_way,
u32 sample_rate, u32 sample_len);
+void zx_hdmi_audio_en(int on);
It's a bit hard to review this properly without knowing what these functions do.
+static int zx_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{ +#ifdef CONFIG_ZX_HDMI_SND_SPDIF
- zx_hdmi_audio_cfg(1, 0, params_rate(params),
+#endif +#ifdef CONFIG_ZX_HDMI_SND_I2S
- zx_hdmi_audio_cfg(1, 1, params_rate(params),
+#endif
params_physical_width(params));
- return 0;
+}
The magic numbers aren't great and it does seem a bit odd to call both I2S and S/PDIF versions always - but I guess this will be restructured following our discussion in the other thread anyway.
2015-06-04 1:58 GMT+08:00 Mark Brown broonie@kernel.org:
On Fri, May 29, 2015 at 09:31:21PM +0800, Jun Nie wrote:
+int zx_hdmi_audio_cfg(int audio_codec, int audio_way,
u32 sample_rate, u32 sample_len);
+void zx_hdmi_audio_en(int on);
It's a bit hard to review this properly without knowing what these functions do.
+static int zx_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{ +#ifdef CONFIG_ZX_HDMI_SND_SPDIF
zx_hdmi_audio_cfg(1, 0, params_rate(params),
+#endif +#ifdef CONFIG_ZX_HDMI_SND_I2S
zx_hdmi_audio_cfg(1, 1, params_rate(params),
+#endif
params_physical_width(params));
return 0;
+}
The magic numbers aren't great and it does seem a bit odd to call both I2S and S/PDIF versions always - but I guess this will be restructured following our discussion in the other thread anyway.
Yes, HDMI audio will be moved into video driver side and I will add a DT property to indicate active HDMI I2S or SPDIF from machine dts.
Will post patches with taking Lars's comments with dropping HDMI stuff.
participants (3)
-
Jun Nie
-
Lars-Peter Clausen
-
Mark Brown