[alsa-devel] [PATCH v3 0/2] Add SPDIF support for Allwinner SoCs
From: Marcus Cooper codekipper@gmail.com
This patch set adds support for the Allwinner SPDIF transceiver.
For now just the SPDIF transmitter has been tested on a Mele A2000(A10) and a Itead Ibox(A20).
In order for this patch set to be functional we require an audio clock patch which will be delivered separately. For those that are interested I've pushed the patches here with all the required changes to get SPDIF audio out of the device.
https://github.com/codekipper/linux-sunxi/commits/spdif_delivery
This is version 3 of the patch series. It's been quite a while since v2 and I'm hoping now that all the previous comments have been acted on.
Thanks in advance, CK
--- Changes since v2: - removed sunxi-machine-spdif and replaced with simple audio card - removed untested compatibilities from device tree documentation - added pm_runtime and remove shutdown - removed pll2 as it's the parent of the spdif clock - rename clk to spdif_clk - removed enabling of mclk output - removed interrupts status being cleared as it's not being used - use default fifo settings for now - fixed alignments for wrapped lines
Changes since v1: - Moved sunxi-machine-spdif.c to separate patch - replaced sunxi in naming scheme with sun4i in the sun4i-spdif driver. - split tx controller into separate enable/disable functions - moved setclk and setfmt functionality into hw params - added support for mono signals. - cleaned up probe clock set up. - removed all writes to transmit status registers. - removed of_id - removed power management code. - Added support for more rates.
Marcus Cooper (2): dt-bindings: add sun4i SPDIF transceiver bindings ASOC: sunxi: Add support for the SPDIF block
.../devicetree/bindings/sound/sun4i,spdif.txt | 46 ++ sound/soc/sunxi/Kconfig | 8 + sound/soc/sunxi/Makefile | 1 + sound/soc/sunxi/sun4i-spdif.c | 586 +++++++++++++++++++++ 4 files changed, 641 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/sun4i,spdif.txt create mode 100644 sound/soc/sunxi/sun4i-spdif.c
From: Marcus Cooper codekipper@gmail.com
Add devicetree bindings for the SPDIF transceiver found on found on Allwinners A10 and A20 SoCs.
Signed-off-by: Marcus Cooper codekipper@gmail.com --- .../devicetree/bindings/sound/sun4i,spdif.txt | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/sun4i,spdif.txt
diff --git a/Documentation/devicetree/bindings/sound/sun4i,spdif.txt b/Documentation/devicetree/bindings/sound/sun4i,spdif.txt new file mode 100644 index 0000000..55ed2f6 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/sun4i,spdif.txt @@ -0,0 +1,46 @@ +Allwinner Sony/Philips Digital Interface Format (S/PDIF) Controller + +The Allwinner S/PDIF audio block is a transceiver that allows the +processor to receive and transmit digital audio via an coaxial cable or +a fibre cable. + +Required properties: + + - compatible : should be one of the following: + - "allwinner,sun4i-a10-spdif": for the Allwinner A10 SoC + + - reg : Offset and length of the register set for the device. + + - interrupts : Contains the spdif interrupt. + + - dmas : Generic dma devicetree binding as described in + Documentation/devicetree/bindings/dma/dma.txt. + + - dma-names : Two dmas have to be defined, "tx" and "rx". + + - clocks : Contains an entry for each entry in clock-names. + + - clock-names : Includes the following entries: + "apb" clock for the spdif bus. + "spdif" clock for spdif controller. + +Optional: + + - spdif-in : Enable block for capturing an SPDIF signal. + + - spdif-out : Enable block for transmitting an SPDIF signal. + +Example: + +spdif: spdif@01c21000 { + compatible = "allwinner,sun4i-a10-spdif"; + reg = <0x01c21000 0x40>; + interrupts = <13>; + clocks = <&apb0_gates 1>, <&spdif_clk>; + clock-names = "apb", "spdif"; + dmas = <&dma 0 2>, <&dma 0 2>; + dma-names = "rx", "tx"; + spdif-in = "disabled"; + spdif-out = "okay"; + status = "okay"; +};
Hi,
On Tue, Feb 02, 2016 at 03:49:53PM +0100, codekipper@gmail.com wrote:
From: Marcus Cooper codekipper@gmail.com
Add devicetree bindings for the SPDIF transceiver found on found on Allwinners A10 and A20 SoCs.
Signed-off-by: Marcus Cooper codekipper@gmail.com
.../devicetree/bindings/sound/sun4i,spdif.txt | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/sun4i,spdif.txt
diff --git a/Documentation/devicetree/bindings/sound/sun4i,spdif.txt b/Documentation/devicetree/bindings/sound/sun4i,spdif.txt new file mode 100644 index 0000000..55ed2f6 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/sun4i,spdif.txt @@ -0,0 +1,46 @@ +Allwinner Sony/Philips Digital Interface Format (S/PDIF) Controller
+The Allwinner S/PDIF audio block is a transceiver that allows the +processor to receive and transmit digital audio via an coaxial cable or +a fibre cable.
+Required properties:
- compatible : should be one of the following:
- "allwinner,sun4i-a10-spdif": for the Allwinner A10 SoC
- reg : Offset and length of the register set for the device.
- interrupts : Contains the spdif interrupt.
- dmas : Generic dma devicetree binding as described in
Documentation/devicetree/bindings/dma/dma.txt.
- dma-names : Two dmas have to be defined, "tx" and "rx".
- clocks : Contains an entry for each entry in clock-names.
- clock-names : Includes the following entries:
- "apb" clock for the spdif bus.
- "spdif" clock for spdif controller.
+Optional:
- spdif-in : Enable block for capturing an SPDIF signal.
- spdif-out : Enable block for transmitting an SPDIF signal.
Is that a generic property? What values can it hold?
Thanks, Maxime
On 2 February 2016 at 23:31, Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
On Tue, Feb 02, 2016 at 03:49:53PM +0100, codekipper@gmail.com wrote:
From: Marcus Cooper codekipper@gmail.com
Add devicetree bindings for the SPDIF transceiver found on found on Allwinners A10 and A20 SoCs.
Signed-off-by: Marcus Cooper codekipper@gmail.com
.../devicetree/bindings/sound/sun4i,spdif.txt | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/sun4i,spdif.txt
diff --git a/Documentation/devicetree/bindings/sound/sun4i,spdif.txt b/Documentation/devicetree/bindings/sound/sun4i,spdif.txt new file mode 100644 index 0000000..55ed2f6 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/sun4i,spdif.txt @@ -0,0 +1,46 @@ +Allwinner Sony/Philips Digital Interface Format (S/PDIF) Controller
+The Allwinner S/PDIF audio block is a transceiver that allows the +processor to receive and transmit digital audio via an coaxial cable or +a fibre cable.
+Required properties:
- compatible : should be one of the following:
- "allwinner,sun4i-a10-spdif": for the Allwinner A10 SoC
- reg : Offset and length of the register set for the device.
- interrupts : Contains the spdif interrupt.
- dmas : Generic dma devicetree binding as described in
Documentation/devicetree/bindings/dma/dma.txt.
- dma-names : Two dmas have to be defined, "tx" and "rx".
- clocks : Contains an entry for each entry in clock-names.
- clock-names : Includes the following entries:
"apb" clock for the spdif bus.
"spdif" clock for spdif controller.
+Optional:
- spdif-in : Enable block for capturing an SPDIF signal.
- spdif-out : Enable block for transmitting an SPDIF signal.
Is that a generic property? What values can it hold?
Hi Maxime,
thanks for the review. I'm starting to prep a v4 patch series and I'm here wondering if I should drop the spdif-in/out for now and just rely on the status property to enable playback. It would simplify the driver even more and it could be added back when someone gets around to delivering spdif capture. I think that's going to be pretty low on my list of things to do as I've only got one dev board where it's possible. BR, Lazy Lazy CK
Thanks, Maxime
-- Maxime Ripard, Free Electrons Embedded Linux, Kernel and Android engineering http://free-electrons.com
From: Marcus Cooper codekipper@gmail.com
The sun4i, sun5i and sun7i SoC families have an SPDIF block which is capable of playback and capture.
This patch enables the playback of this block for the sun4i families.
Signed-off-by: Marcus Cooper codekipper@gmail.com --- sound/soc/sunxi/Kconfig | 8 + sound/soc/sunxi/Makefile | 1 + sound/soc/sunxi/sun4i-spdif.c | 586 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 595 insertions(+) create mode 100644 sound/soc/sunxi/sun4i-spdif.c
diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index 84c72ec..6af7224 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -8,4 +8,12 @@ config SND_SUN4I_CODEC Select Y or M to add support for the Codec embedded in the Allwinner A10 and affiliated SoCs.
+config SND_SUN4I_SPDIF + tristate "Allwinner A10 SPDIF Support" + depends on OF + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M to add support for the S/PDIF audio block in the Allwinner + A10 and affiliated SoCs. endmenu diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile index ea8a08c..8f5e889 100644 --- a/sound/soc/sunxi/Makefile +++ b/sound/soc/sunxi/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
+obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c new file mode 100644 index 0000000..16265b3 --- /dev/null +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -0,0 +1,586 @@ +/* + * ALSA SoC SPDIF Audio Layer + * + * Copyright 2015 Andrea Venturi be17068@iperbole.bo.it + * Copyright 2015 Marcus Cooper codekipper@gmail.com + * + * Based on the Allwinner SDK driver, released under the GPL. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/regmap.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#define SUN4I_SPDIF_CTL (0x00) + #define SUN4I_SPDIF_CTL_MCLKDIV(v) ((v) << 4) /* v even */ + #define SUN4I_SPDIF_CTL_MCLKOUTEN BIT(2) + #define SUN4I_SPDIF_CTL_GEN BIT(1) + #define SUN4I_SPDIF_CTL_RESET BIT(0) + +#define SUN4I_SPDIF_TXCFG (0x04) + #define SUN4I_SPDIF_TXCFG_SINGLEMOD BIT(31) + #define SUN4I_SPDIF_TXCFG_ASS BIT(17) + #define SUN4I_SPDIF_TXCFG_NONAUDIO BIT(16) + #define SUN4I_SPDIF_TXCFG_TXRATIO(v) ((v) << 4) + #define SUN4I_SPDIF_TXCFG_TXRATIO_MASK GENMASK(8, 4) + #define SUN4I_SPDIF_TXCFG_FMTRVD GENMASK(3, 2) + #define SUN4I_SPDIF_TXCFG_FMT16BIT (0 << 2) + #define SUN4I_SPDIF_TXCFG_FMT20BIT (1 << 2) + #define SUN4I_SPDIF_TXCFG_FMT24BIT (2 << 2) + #define SUN4I_SPDIF_TXCFG_CHSTMODE BIT(1) + #define SUN4I_SPDIF_TXCFG_TXEN BIT(0) + +#define SUN4I_SPDIF_RXCFG (0x08) + #define SUN4I_SPDIF_RXCFG_LOCKFLAG BIT(4) + #define SUN4I_SPDIF_RXCFG_CHSTSRC BIT(3) + #define SUN4I_SPDIF_RXCFG_CHSTCP BIT(1) + #define SUN4I_SPDIF_RXCFG_RXEN BIT(0) + +#define SUN4I_SPDIF_TXFIFO (0x0C) + +#define SUN4I_SPDIF_RXFIFO (0x10) + +#define SUN4I_SPDIF_FCTL (0x14) + #define SUN4I_SPDIF_FCTL_FIFOSRC BIT(31) + #define SUN4I_SPDIF_FCTL_FTX BIT(17) + #define SUN4I_SPDIF_FCTL_FRX BIT(16) + #define SUN4I_SPDIF_FCTL_TXTL(v) ((v) << 8) + #define SUN4I_SPDIF_FCTL_TXTL_MASK GENMASK(12, 8) + #define SUN4I_SPDIF_FCTL_RXTL(v) ((v) << 3) + #define SUN4I_SPDIF_FCTL_RXTL_MASK GENMASK(7, 3) + #define SUN4I_SPDIF_FCTL_TXIM BIT(2) + #define SUN4I_SPDIF_FCTL_RXOM(v) ((v) << 0) + #define SUN4I_SPDIF_FCTL_RXOM_MASK GENMASK(1, 0) + +#define SUN4I_SPDIF_FSTA (0x18) + #define SUN4I_SPDIF_FSTA_TXE BIT(14) + #define SUN4I_SPDIF_FSTA_TXECNTSHT (8) + #define SUN4I_SPDIF_FSTA_RXA BIT(6) + #define SUN4I_SPDIF_FSTA_RXACNTSHT (0) + +#define SUN4I_SPDIF_INT (0x1C) + #define SUN4I_SPDIF_INT_RXLOCKEN BIT(18) + #define SUN4I_SPDIF_INT_RXUNLOCKEN BIT(17) + #define SUN4I_SPDIF_INT_RXPARERREN BIT(16) + #define SUN4I_SPDIF_INT_TXDRQEN BIT(7) + #define SUN4I_SPDIF_INT_TXUIEN BIT(6) + #define SUN4I_SPDIF_INT_TXOIEN BIT(5) + #define SUN4I_SPDIF_INT_TXEIEN BIT(4) + #define SUN4I_SPDIF_INT_RXDRQEN BIT(2) + #define SUN4I_SPDIF_INT_RXOIEN BIT(1) + #define SUN4I_SPDIF_INT_RXAIEN BIT(0) + +#define SUN4I_SPDIF_ISTA (0x20) + #define SUN4I_SPDIF_ISTA_RXLOCKSTA BIT(18) + #define SUN4I_SPDIF_ISTA_RXUNLOCKSTA BIT(17) + #define SUN4I_SPDIF_ISTA_RXPARERRSTA BIT(16) + #define SUN4I_SPDIF_ISTA_TXUSTA BIT(6) + #define SUN4I_SPDIF_ISTA_TXOSTA BIT(5) + #define SUN4I_SPDIF_ISTA_TXESTA BIT(4) + #define SUN4I_SPDIF_ISTA_RXOSTA BIT(1) + #define SUN4I_SPDIF_ISTA_RXASTA BIT(0) + +#define SUN4I_SPDIF_TXCNT (0x24) + +#define SUN4I_SPDIF_RXCNT (0x28) + +#define SUN4I_SPDIF_TXCHSTA0 (0x2C) + #define SUN4I_SPDIF_TXCHSTA0_CLK(v) ((v) << 28) + #define SUN4I_SPDIF_TXCHSTA0_SAMFREQ(v) ((v) << 24) + #define SUN4I_SPDIF_TXCHSTA0_SAMFREQ_MASK GENMASK(27, 24) + #define SUN4I_SPDIF_TXCHSTA0_CHNUM(v) ((v) << 20) + #define SUN4I_SPDIF_TXCHSTA0_CHNUM_MASK GENMASK(23, 20) + #define SUN4I_SPDIF_TXCHSTA0_SRCNUM(v) ((v) << 16) + #define SUN4I_SPDIF_TXCHSTA0_CATACOD(v) ((v) << 8) + #define SUN4I_SPDIF_TXCHSTA0_MODE(v) ((v) << 6) + #define SUN4I_SPDIF_TXCHSTA0_EMPHASIS(v) ((v) << 3) + #define SUN4I_SPDIF_TXCHSTA0_CP BIT(2) + #define SUN4I_SPDIF_TXCHSTA0_AUDIO BIT(1) + #define SUN4I_SPDIF_TXCHSTA0_PRO BIT(0) + +#define SUN4I_SPDIF_TXCHSTA1 (0x30) + #define SUN4I_SPDIF_TXCHSTA1_CGMSA(v) ((v) << 8) + #define SUN4I_SPDIF_TXCHSTA1_ORISAMFREQ(v) ((v) << 4) + #define SUN4I_SPDIF_TXCHSTA1_ORISAMFREQ_MASK GENMASK(7, 4) + #define SUN4I_SPDIF_TXCHSTA1_SAMWORDLEN(v) ((v) << 1) + #define SUN4I_SPDIF_TXCHSTA1_MAXWORDLEN BIT(0) + +#define SUN4I_SPDIF_RXCHSTA0 (0x34) + #define SUN4I_SPDIF_RXCHSTA0_CLK(v) ((v) << 28) + #define SUN4I_SPDIF_RXCHSTA0_SAMFREQ(v) ((v) << 24) + #define SUN4I_SPDIF_RXCHSTA0_CHNUM(v) ((v) << 20) + #define SUN4I_SPDIF_RXCHSTA0_SRCNUM(v) ((v) << 16) + #define SUN4I_SPDIF_RXCHSTA0_CATACOD(v) ((v) << 8) + #define SUN4I_SPDIF_RXCHSTA0_MODE(v) ((v) << 6) + #define SUN4I_SPDIF_RXCHSTA0_EMPHASIS(v) ((v) << 3) + #define SUN4I_SPDIF_RXCHSTA0_CP BIT(2) + #define SUN4I_SPDIF_RXCHSTA0_AUDIO BIT(1) + #define SUN4I_SPDIF_RXCHSTA0_PRO BIT(0) + +#define SUN4I_SPDIF_RXCHSTA1 (0x38) + #define SUN4I_SPDIF_RXCHSTA1_CGMSA(v) ((v) << 8) + #define SUN4I_SPDIF_RXCHSTA1_ORISAMFREQ(v) ((v) << 4) + #define SUN4I_SPDIF_RXCHSTA1_SAMWORDLEN(v) ((v) << 1) + #define SUN4I_SPDIF_RXCHSTA1_MAXWORDLEN BIT(0) + +/* Defines for Sampling Frequency */ +#define SUN4I_SPDIF_SAMFREQ_44_1KHZ 0x0 +#define SUN4I_SPDIF_SAMFREQ_NOT_INDICATED 0x1 +#define SUN4I_SPDIF_SAMFREQ_48KHZ 0x2 +#define SUN4I_SPDIF_SAMFREQ_32KHZ 0x3 +#define SUN4I_SPDIF_SAMFREQ_22_05KHZ 0x4 +#define SUN4I_SPDIF_SAMFREQ_24KHZ 0x6 +#define SUN4I_SPDIF_SAMFREQ_88_2KHZ 0x8 +#define SUN4I_SPDIF_SAMFREQ_76_8KHZ 0x9 +#define SUN4I_SPDIF_SAMFREQ_96KHZ 0xa +#define SUN4I_SPDIF_SAMFREQ_176_4KHZ 0xc +#define SUN4I_SPDIF_SAMFREQ_192KHZ 0xe + +/* + * Original sampling frequency can be represented by inverting the value of the + * sampling frequency. + */ +#define ORIGINAL(v) ((~v) & 0xf) + +struct sun4i_spdif_dev { + struct platform_device *pdev; + struct clk *spdif_clk; + struct clk *apb_clk; + struct snd_soc_dai_driver cpu_dai_drv; + struct regmap *regmap; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_dmaengine_dai_dma_data dma_params_rx; + bool playback_supported; + bool capture_supported; +}; + +static void sun4i_spdif_configure(struct sun4i_spdif_dev *host) +{ + /* soft reset SPDIF */ + regmap_write(host->regmap, SUN4I_SPDIF_CTL, SUN4I_SPDIF_CTL_RESET); + + /* flush TX FIFO */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, + SUN4I_SPDIF_FCTL_FTX, SUN4I_SPDIF_FCTL_FTX); + + /* clear TX counter */ + regmap_write(host->regmap, SUN4I_SPDIF_TXCNT, 0); +} + +static void sun4i_snd_txctrl_on(struct snd_pcm_substream *substream, + struct sun4i_spdif_dev *host) +{ + if (substream->runtime->channels == 1) + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, + SUN4I_SPDIF_TXCFG_SINGLEMOD, + SUN4I_SPDIF_TXCFG_SINGLEMOD); + + /* SPDIF TX ENABLE */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, + SUN4I_SPDIF_TXCFG_TXEN, SUN4I_SPDIF_TXCFG_TXEN); + + /* DRQ ENABLE */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_INT, + SUN4I_SPDIF_INT_TXDRQEN, SUN4I_SPDIF_INT_TXDRQEN); + + /* Global enable */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_CTL, + SUN4I_SPDIF_CTL_GEN, SUN4I_SPDIF_CTL_GEN); +} + +static void sun4i_snd_txctrl_off(struct snd_pcm_substream *substream, + struct sun4i_spdif_dev *host) +{ + /* SPDIF TX DISABLE */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, + SUN4I_SPDIF_TXCFG_TXEN, 0); + + /* DRQ DISABLE */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_INT, + SUN4I_SPDIF_INT_TXDRQEN, 0); + + /* Global disable */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_CTL, + SUN4I_SPDIF_CTL_GEN, 0); +} + +static int sun4i_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + sun4i_spdif_configure(host); + + return 0; +} + +static int sun4i_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + int ret = 0; + int fmt; + unsigned long rate = params_rate(params); + u32 mclk_div = 0; + unsigned int mclk = 0; + u32 reg_val; + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); + struct platform_device *pdev = host->pdev; + + /* Add the PCM and raw data select interface */ + switch (params_channels(params)) { + case 1: /* PCM mode */ + case 2: + fmt = 0; + break; + case 4: /* raw data mode */ + fmt = SUN4I_SPDIF_TXCFG_NONAUDIO; + break; + default: + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + fmt |= SUN4I_SPDIF_TXCFG_FMT16BIT; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + fmt |= SUN4I_SPDIF_TXCFG_FMT20BIT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + fmt |= SUN4I_SPDIF_TXCFG_FMT24BIT; + break; + default: + return -EINVAL; + } + + switch (rate) { + case 22050: + case 44100: + case 88200: + case 176400: + mclk = 22579200; + break; + case 24000: + case 32000: + case 48000: + case 96000: + case 192000: + mclk = 24576000; + break; + default: + return -EINVAL; + } + + ret = clk_set_rate(host->spdif_clk, mclk); + if (ret < 0) { + dev_err(&pdev->dev, + "Setting SPDIF clock rate for %d Hz failed!\n", mclk); + return ret; + } + + regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, + SUN4I_SPDIF_FCTL_TXIM, SUN4I_SPDIF_FCTL_TXIM); + + switch (rate) { + case 22050: + case 24000: + mclk_div = 8; + break; + case 32000: + mclk_div = 6; + break; + case 44100: + case 48000: + mclk_div = 4; + break; + case 88200: + case 96000: + mclk_div = 2; + break; + case 176400: + case 192000: + mclk_div = 1; + break; + default: + return -EINVAL; + } + + reg_val = 0; + reg_val |= SUN4I_SPDIF_TXCFG_ASS; + reg_val |= fmt; /* set non audio and bit depth */ + reg_val |= SUN4I_SPDIF_TXCFG_CHSTMODE; + reg_val |= SUN4I_SPDIF_TXCFG_TXRATIO(mclk_div - 1); + regmap_write(host->regmap, SUN4I_SPDIF_TXCFG, reg_val); + + return 0; +} + +static int sun4i_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(dai); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + sun4i_snd_txctrl_on(substream, host); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sun4i_snd_txctrl_off(substream, host); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int sun4i_spdif_soc_dai_probe(struct snd_soc_dai *dai) +{ + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &host->dma_params_tx, + &host->dma_params_rx); + return 0; +} + +static const struct snd_soc_dai_ops sun4i_spdif_dai_ops = { + .startup = sun4i_spdif_startup, + .trigger = sun4i_spdif_trigger, + .hw_params = sun4i_spdif_hw_params, +}; + +static const struct regmap_config sun4i_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN4I_SPDIF_RXCHSTA1, +}; + +#define SUN4I_RATES SNDRV_PCM_RATE_8000_192000 + +#define SUN4I_FORMATS (SNDRV_PCM_FORMAT_S16_LE | \ + SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_LE) + +static struct snd_soc_dai_driver sun4i_spdif_dai = { + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SUN4I_RATES, + .formats = SUN4I_FORMATS, + }, + .probe = sun4i_spdif_soc_dai_probe, + .ops = &sun4i_spdif_dai_ops, + .name = "spdif", +}; + +static const struct snd_soc_dapm_widget dit_widgets[] = { + SND_SOC_DAPM_OUTPUT("spdif-out"), +}; + +static const struct snd_soc_dapm_route dit_routes[] = { + { "spdif-out", NULL, "Playback" }, +}; + +static const struct of_device_id sun4i_spdif_of_match[] = { + { .compatible = "allwinner,sun4i-a10-spdif", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sun4i_spdif_of_match); + +static const struct snd_soc_component_driver sun4i_spdif_component = { + .name = "sun4i-spdif", +}; + +#ifdef CONFIG_PM +static int sun4i_spdif_runtime_suspend(struct device *dev) +{ + struct sun4i_spdif_dev *host = dev_get_drvdata(dev); + + clk_disable_unprepare(host->spdif_clk); + + return 0; +} + +static int sun4i_spdif_runtime_resume(struct device *dev) +{ + struct sun4i_spdif_dev *host = dev_get_drvdata(dev); + + clk_prepare_enable(host->spdif_clk); + + return 0; +} +#endif /* CONFIG_PM */ + +static int sun4i_spdif_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sun4i_spdif_dev *host; + struct resource *res; + int ret; + void __iomem *base; + + dev_dbg(&pdev->dev, "Entered %s\n", __func__); + + if (!np) + return -ENODEV; + + if (!of_match_device(sun4i_spdif_of_match, &pdev->dev)) { + dev_err(&pdev->dev, "No matched devices found.\n"); + return -EINVAL; + } + + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->pdev = pdev; + + /* Initialize this copy of the CPU DAI driver structure */ + memcpy(&host->cpu_dai_drv, &sun4i_spdif_dai, sizeof(sun4i_spdif_dai)); + host->cpu_dai_drv.name = dev_name(&pdev->dev); + + /* Get the addresses */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_spdif_regmap_config); + + /* Clocks */ + host->apb_clk = devm_clk_get(&pdev->dev, "apb"); + if (IS_ERR(host->apb_clk)) { + dev_err(&pdev->dev, "failed to get a apb clock.\n"); + return PTR_ERR(host->apb_clk); + } + + ret = clk_prepare_enable(host->apb_clk); + if (ret) { + dev_err(&pdev->dev, "try to enable apb_spdif_clk failed\n"); + return ret; + } + + host->spdif_clk = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(host->spdif_clk)) { + dev_err(&pdev->dev, "failed to get a spdif clock.\n"); + goto exit_clkdisable_apb_clk; + } + + host->playback_supported = false; + host->capture_supported = false; + + if (of_property_read_bool(np, "spdif-out")) + host->playback_supported = true; + + if (!host->playback_supported) { + dev_err(&pdev->dev, "no enabled S/PDIF DAI link\n"); + goto exit_clkdisable_clk; + } + + host->dma_params_tx.addr = res->start + SUN4I_SPDIF_TXFIFO; + host->dma_params_tx.maxburst = 4; + host->dma_params_tx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + host->dma_params_rx.addr = res->start + SUN4I_SPDIF_RXFIFO; + host->dma_params_rx.maxburst = 4; + host->dma_params_rx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + platform_set_drvdata(pdev, host); + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_spdif_component, &sun4i_spdif_dai, 1); + if (ret) + goto exit_clkdisable_clk; + + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto exit_clkdisable_clk; + return 0; + +exit_clkdisable_clk: + clk_disable_unprepare(host->spdif_clk); +exit_clkdisable_apb_clk: + clk_disable_unprepare(host->apb_clk); + return ret; +} + +static int sun4i_spdif_remove(struct platform_device *pdev) +{ + struct sun4i_spdif_dev *host = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_platform(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + if (!IS_ERR(host->spdif_clk)) { + clk_disable_unprepare(host->spdif_clk); + clk_disable_unprepare(host->apb_clk); + } + + return 0; +} + +static const struct dev_pm_ops sun4i_spdif_pm = { + SET_RUNTIME_PM_OPS(sun4i_spdif_runtime_suspend, + sun4i_spdif_runtime_resume, NULL) +}; + +static struct platform_driver sun4i_spdif_driver = { + .driver = { + .name = "sun4i-spdif", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sun4i_spdif_of_match), + .pm = &sun4i_spdif_pm, + }, + .probe = sun4i_spdif_probe, + .remove = sun4i_spdif_remove, +}; + +module_platform_driver(sun4i_spdif_driver); + +MODULE_AUTHOR("Marcus Cooper codekipper@gmail.com"); +MODULE_AUTHOR("Andrea Venturi be17068@iperbole.bo.it"); +MODULE_DESCRIPTION("Allwinner sun4i SPDIF SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sun4i-spdif");
Hi Marcus,
[auto build test WARNING on asoc/for-next] [also build test WARNING on v4.5-rc2 next-20160201] [if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/codekipper-gmail-com/Add-SPDIF-supp... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
coccinelle warnings: (new ones prefixed by >>)
sound/soc/sunxi/sun4i-spdif.c:572:3-8: No need to set .owner here. The core will do it.
Please review and possibly fold the followup patch.
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
sound/soc/sunxi/sun4i-spdif.c:572:3-8: No need to set .owner here. The core will do it.
Remove .owner field if calls are used which set it automatically
Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci
CC: Marcus Cooper codekipper@gmail.com Signed-off-by: Fengguang Wu fengguang.wu@intel.com ---
sun4i-spdif.c | 1 - 1 file changed, 1 deletion(-)
--- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -569,7 +569,6 @@ static const struct dev_pm_ops sun4i_spd static struct platform_driver sun4i_spdif_driver = { .driver = { .name = "sun4i-spdif", - .owner = THIS_MODULE, .of_match_table = of_match_ptr(sun4i_spdif_of_match), .pm = &sun4i_spdif_pm, },
Hi,
It looks mostly good on my side, a few comments though.
On Tue, Feb 02, 2016 at 03:49:54PM +0100, codekipper@gmail.com wrote:
+#ifdef CONFIG_PM +static int sun4i_spdif_runtime_suspend(struct device *dev) +{
- struct sun4i_spdif_dev *host = dev_get_drvdata(dev);
- clk_disable_unprepare(host->spdif_clk);
- return 0;
+}
+static int sun4i_spdif_runtime_resume(struct device *dev) +{
- struct sun4i_spdif_dev *host = dev_get_drvdata(dev);
- clk_prepare_enable(host->spdif_clk);
- return 0;
+} +#endif /* CONFIG_PM */
You can also shut down the bus clock here. That will reset the device, so you'll have to reinit it in your resume, but since you won't be suspended while you play something, and that you set up most of your controller any way when you start streaming, it shouldn't be a big deal.
+static int sun4i_spdif_probe(struct platform_device *pdev) +{
- struct device_node *np = pdev->dev.of_node;
- struct sun4i_spdif_dev *host;
- struct resource *res;
- int ret;
- void __iomem *base;
- dev_dbg(&pdev->dev, "Entered %s\n", __func__);
- if (!np)
return -ENODEV;
You won't probe without DT.
- if (!of_match_device(sun4i_spdif_of_match, &pdev->dev)) {
dev_err(&pdev->dev, "No matched devices found.\n");
return -EINVAL;
- }
And you won't probe if the compatibles don't match.
- host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
- if (!host)
return -ENOMEM;
- host->pdev = pdev;
- /* Initialize this copy of the CPU DAI driver structure */
- memcpy(&host->cpu_dai_drv, &sun4i_spdif_dai, sizeof(sun4i_spdif_dai));
- host->cpu_dai_drv.name = dev_name(&pdev->dev);
- /* Get the addresses */
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(base))
return PTR_ERR(base);
- host->regmap = devm_regmap_init_mmio(&pdev->dev, base,
&sun4i_spdif_regmap_config);
- /* Clocks */
- host->apb_clk = devm_clk_get(&pdev->dev, "apb");
- if (IS_ERR(host->apb_clk)) {
dev_err(&pdev->dev, "failed to get a apb clock.\n");
return PTR_ERR(host->apb_clk);
- }
- ret = clk_prepare_enable(host->apb_clk);
- if (ret) {
dev_err(&pdev->dev, "try to enable apb_spdif_clk failed\n");
return ret;
- }
- host->spdif_clk = devm_clk_get(&pdev->dev, "spdif");
- if (IS_ERR(host->spdif_clk)) {
dev_err(&pdev->dev, "failed to get a spdif clock.\n");
goto exit_clkdisable_apb_clk;
- }
- host->playback_supported = false;
- host->capture_supported = false;
- if (of_property_read_bool(np, "spdif-out"))
host->playback_supported = true;
- if (!host->playback_supported) {
dev_err(&pdev->dev, "no enabled S/PDIF DAI link\n");
goto exit_clkdisable_clk;
- }
- host->dma_params_tx.addr = res->start + SUN4I_SPDIF_TXFIFO;
- host->dma_params_tx.maxburst = 4;
- host->dma_params_tx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- host->dma_params_rx.addr = res->start + SUN4I_SPDIF_RXFIFO;
- host->dma_params_rx.maxburst = 4;
- host->dma_params_rx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- platform_set_drvdata(pdev, host);
- ret = devm_snd_soc_register_component(&pdev->dev,
&sun4i_spdif_component, &sun4i_spdif_dai, 1);
- if (ret)
goto exit_clkdisable_clk;
- pm_runtime_enable(&pdev->dev);
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
- if (ret)
goto exit_clkdisable_clk;
- return 0;
+exit_clkdisable_clk:
- clk_disable_unprepare(host->spdif_clk);
You disabled a clock that you didn't enable.
+exit_clkdisable_apb_clk:
- clk_disable_unprepare(host->apb_clk);
- return ret;
+}
+static int sun4i_spdif_remove(struct platform_device *pdev) +{
- struct sun4i_spdif_dev *host = dev_get_drvdata(&pdev->dev);
- snd_soc_unregister_platform(&pdev->dev);
- snd_soc_unregister_component(&pdev->dev);
- pm_runtime_disable(&pdev->dev);
- if (!IS_ERR(host->spdif_clk)) {
Probe will fail on this condition. If probe failed, remove won't be called.
clk_disable_unprepare(host->spdif_clk);
And here as well.
clk_disable_unprepare(host->apb_clk);
- }
- return 0;
+}
+static const struct dev_pm_ops sun4i_spdif_pm = {
- SET_RUNTIME_PM_OPS(sun4i_spdif_runtime_suspend,
sun4i_spdif_runtime_resume, NULL)
+};
+static struct platform_driver sun4i_spdif_driver = {
- .driver = {
.name = "sun4i-spdif",
.owner = THIS_MODULE,
This is not needed.
.of_match_table = of_match_ptr(sun4i_spdif_of_match),
.pm = &sun4i_spdif_pm,
IIRC, the pm field is not defined if PM is not set.
Have you tested your driver without PM?
- },
- .probe = sun4i_spdif_probe,
- .remove = sun4i_spdif_remove,
+};
+module_platform_driver(sun4i_spdif_driver);
+MODULE_AUTHOR("Marcus Cooper codekipper@gmail.com"); +MODULE_AUTHOR("Andrea Venturi be17068@iperbole.bo.it"); +MODULE_DESCRIPTION("Allwinner sun4i SPDIF SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sun4i-spdif");
Thanks! Maxime
On 2 February 2016 at 22:17, Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
It looks mostly good on my side, a few comments though.
On Tue, Feb 02, 2016 at 03:49:54PM +0100, codekipper@gmail.com wrote:
+#ifdef CONFIG_PM +static int sun4i_spdif_runtime_suspend(struct device *dev) +{
struct sun4i_spdif_dev *host = dev_get_drvdata(dev);
clk_disable_unprepare(host->spdif_clk);
return 0;
+}
+static int sun4i_spdif_runtime_resume(struct device *dev) +{
struct sun4i_spdif_dev *host = dev_get_drvdata(dev);
clk_prepare_enable(host->spdif_clk);
return 0;
+} +#endif /* CONFIG_PM */
You can also shut down the bus clock here. That will reset the device, so you'll have to reinit it in your resume, but since you won't be suspended while you play something, and that you set up most of your controller any way when you start streaming, it shouldn't be a big deal.
ACK
+static int sun4i_spdif_probe(struct platform_device *pdev) +{
struct device_node *np = pdev->dev.of_node;
struct sun4i_spdif_dev *host;
struct resource *res;
int ret;
void __iomem *base;
dev_dbg(&pdev->dev, "Entered %s\n", __func__);
if (!np)
return -ENODEV;
You won't probe without DT.
ACK
if (!of_match_device(sun4i_spdif_of_match, &pdev->dev)) {
dev_err(&pdev->dev, "No matched devices found.\n");
return -EINVAL;
}
And you won't probe if the compatibles don't match.
ACK
host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
if (!host)
return -ENOMEM;
host->pdev = pdev;
/* Initialize this copy of the CPU DAI driver structure */
memcpy(&host->cpu_dai_drv, &sun4i_spdif_dai, sizeof(sun4i_spdif_dai));
host->cpu_dai_drv.name = dev_name(&pdev->dev);
/* Get the addresses */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
host->regmap = devm_regmap_init_mmio(&pdev->dev, base,
&sun4i_spdif_regmap_config);
/* Clocks */
host->apb_clk = devm_clk_get(&pdev->dev, "apb");
if (IS_ERR(host->apb_clk)) {
dev_err(&pdev->dev, "failed to get a apb clock.\n");
return PTR_ERR(host->apb_clk);
}
ret = clk_prepare_enable(host->apb_clk);
if (ret) {
dev_err(&pdev->dev, "try to enable apb_spdif_clk failed\n");
return ret;
}
host->spdif_clk = devm_clk_get(&pdev->dev, "spdif");
if (IS_ERR(host->spdif_clk)) {
dev_err(&pdev->dev, "failed to get a spdif clock.\n");
goto exit_clkdisable_apb_clk;
}
host->playback_supported = false;
host->capture_supported = false;
if (of_property_read_bool(np, "spdif-out"))
host->playback_supported = true;
if (!host->playback_supported) {
dev_err(&pdev->dev, "no enabled S/PDIF DAI link\n");
goto exit_clkdisable_clk;
}
host->dma_params_tx.addr = res->start + SUN4I_SPDIF_TXFIFO;
host->dma_params_tx.maxburst = 4;
host->dma_params_tx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
host->dma_params_rx.addr = res->start + SUN4I_SPDIF_RXFIFO;
host->dma_params_rx.maxburst = 4;
host->dma_params_rx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
platform_set_drvdata(pdev, host);
ret = devm_snd_soc_register_component(&pdev->dev,
&sun4i_spdif_component, &sun4i_spdif_dai, 1);
if (ret)
goto exit_clkdisable_clk;
pm_runtime_enable(&pdev->dev);
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret)
goto exit_clkdisable_clk;
return 0;
+exit_clkdisable_clk:
clk_disable_unprepare(host->spdif_clk);
You disabled a clock that you didn't enable.
ACK
+exit_clkdisable_apb_clk:
clk_disable_unprepare(host->apb_clk);
return ret;
+}
+static int sun4i_spdif_remove(struct platform_device *pdev) +{
struct sun4i_spdif_dev *host = dev_get_drvdata(&pdev->dev);
snd_soc_unregister_platform(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!IS_ERR(host->spdif_clk)) {
Probe will fail on this condition. If probe failed, remove won't be called.
clk_disable_unprepare(host->spdif_clk);
And here as well.
clk_disable_unprepare(host->apb_clk);
}
return 0;
+}
+static const struct dev_pm_ops sun4i_spdif_pm = {
SET_RUNTIME_PM_OPS(sun4i_spdif_runtime_suspend,
sun4i_spdif_runtime_resume, NULL)
+};
+static struct platform_driver sun4i_spdif_driver = {
.driver = {
.name = "sun4i-spdif",
.owner = THIS_MODULE,
This is not needed.
ACK
.of_match_table = of_match_ptr(sun4i_spdif_of_match),
.pm = &sun4i_spdif_pm,
IIRC, the pm field is not defined if PM is not set.
Have you tested your driver without PM?
No but I'll have a go!
},
.probe = sun4i_spdif_probe,
.remove = sun4i_spdif_remove,
+};
+module_platform_driver(sun4i_spdif_driver);
+MODULE_AUTHOR("Marcus Cooper codekipper@gmail.com"); +MODULE_AUTHOR("Andrea Venturi be17068@iperbole.bo.it"); +MODULE_DESCRIPTION("Allwinner sun4i SPDIF SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sun4i-spdif");
Thanks! Maxime
Thanks, CK
-- Maxime Ripard, Free Electrons Embedded Linux, Kernel and Android engineering http://free-electrons.com
participants (5)
-
Code Kipper
-
codekipper@gmail.com
-
kbuild test robot
-
Mark Brown
-
Maxime Ripard