[alsa-devel] [PATCH v2 0/9] asoc: Add audio for sti platforms
version 2: corrections after first review - bindings: . suppress useless dts declarations
- uniperif_player.c: . Suppress standby mode in uniplayer ( will be part of a dedicated patch) . Deliver IEC and clock adjustement controls in separate patch - uniperif_reader.c: . kept as separate file as behavior and feature not same than uniplayer . minor corrections - codec/sti-sas.c . implement DAPM for DAC . rework registers declaration to separate device and driver part
version 1: - First patches submission
Arnaud Pouliquen (9): ASoC: sti: add binding for ASoc driver ASoC: sti: Add uniperipheral header file ASoC: sti: Add CPU DAI driver for playback ASoC: sti: Add CPU DAI driver for capture ASoC: sti: Add platform driver ASoC: Add ability to build sti drivers ASoC: Codec: Add sti platform codec ASoC: sti: Add clock adjustement control ASoC: sti: Add IEC control
.../devicetree/bindings/sound/st,sti-asoc-card.txt | 158 +++ sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sti-sas.c | 645 +++++++++++ sound/soc/sti/Kconfig | 17 + sound/soc/sti/Makefile | 5 + sound/soc/sti/sti_platform.c | 642 ++++++++++ sound/soc/sti/uniperif.h | 1224 ++++++++++++++++++++ sound/soc/sti/uniperif_player.c | 1218 +++++++++++++++++++ sound/soc/sti/uniperif_reader.c | 459 ++++++++ 12 files changed, 4376 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt create mode 100644 sound/soc/codecs/sti-sas.c create mode 100644 sound/soc/sti/Kconfig create mode 100644 sound/soc/sti/Makefile create mode 100644 sound/soc/sti/sti_platform.c create mode 100644 sound/soc/sti/uniperif.h create mode 100644 sound/soc/sti/uniperif_player.c create mode 100644 sound/soc/sti/uniperif_reader.c
Add Asoc driver bindings documentation. Describe the required properties for each of the hardware IPs drivers.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- .../devicetree/bindings/sound/st,sti-asoc-card.txt | 158 +++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt
diff --git a/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt b/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt new file mode 100644 index 0000000..e4296bb --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt @@ -0,0 +1,158 @@ +STMicroelectronics sti ASOC cards + +The sti ASoC Sound Card can be used, for all sti SoCs using internal sti-sas +codec or external codecs. + +sti sound drivers allows to expose sti SOC audio interface through the +generic ASOC simple card. For details about sound card declaration please refer to +Documentation/devicetree/bindings/sound/simple-card.txt. + +-sti-asoc-platform: audio platform engine. +Required properties: + - compatible: "st,sti-audio-platform". + - st,syscfg: phandle to boot-device system configuration registers + - clocks: CPU_DAI IP clock source, listed in the same order than the + CPU_DAI properties. + - clock-names: name of the clocks listed in clocks property in the same order + - reg: CPU DAI IP Base address and size entries, listed in same + order than the CPU_DAI properties. + - reg-names: names of the mapped memory regions listed in regs property in + the same order. + - interrupts: CPU_DAI interrupt line, listed in the same order than the + CPU_DAI properties. + - dma: CPU_DAI DMA controller phandle and DMA request line, listed in the same + order than the CPU_DAI properties. + - dma-names: identifier string for each DMA request line in the dmas property. + +- cpu-dai: sti-asoc-platform subnode that describes properties for a CPU-DAI instance. +Required properties: + - dai-name: DAI name that describes the IP. + - uniperiph-id: internal SOC IP instance ID. + - version: IP version integrated in SOC. + - IP mode: IP working mode depending on associated codec. + "HDMI" connected to HDMI codec IP and IEC HDMI formats. + "SPDIF"connected to SPDIF codec and support SPDIF formats. + "PCM" PCM standard mode for I2S or TDM bus. + +Optional properties: + - standby: standby mode activation deactivation. this mode is specific to + Uni player IP and allows to transmit bus clock and a pattern data + while audio stream is stopped or paused. + - auto-suspend-delay: runtime auto suspend delay (in ms) before deactivating + stanby mode. Equivalent to the DAPM mechanism but for + CPU DAI. + - pinctrl-0: defined for CPU_DAI@1 and CPU_DAI@4 to describe I2S PIOs for + external codecs connection. + - pinctrl-names: should contain only one value - "default". + +- sti-sas-codec: internal audio codec IPs driver +Required properties: + - compatible: "st,sti<chip>-sas-codec" . + Should be chip "st,sti416-sas-codec" or "st,sti417-sas-codec" + - st,syscfg: phandle to boot-device system configuration registers. + - pinctrl-0: SPDIF PIO description. + - pinctrl-names: should contain only one value - "default". + +audio_controller: sti-asoc-platform { + #sound-dai-cells = <1>; + compatible = "st,sti-audio-platform"; + st,syscfg = <&syscfg_core>; + clocks = <&clk_s_d0_flexgen CLK_PCM_0>, /*UPP 0*/ + <&clk_s_d0_flexgen CLK_PCM_1>, /*UPP 1*/ + <&clk_s_d0_flexgen CLK_PCM_2>, /*UPP 2*/ + <&clk_s_d0_flexgen CLK_SPDIFF>, /*UPP 3 */ + <0>, /*UPR 0: no clock*/ + <0>; /*UPR 1: no clock */ + clock-names = "uni_p0", "uni_p1", "uni_p2", "uni_p3", + "no_clock", "no_clock"; + reg = <0x8D80000 0x158>, <0x8D81000 0x158>, + <0x8D82000 0x158>, <0x8D85000 0x158>, + <0x8D83000 0x158>, <0x8D84000 0x158>; + interrupts = <GIC_SPI 84 IRQ_TYPE_NONE>, + <GIC_SPI 85 IRQ_TYPE_NONE>, + <GIC_SPI 86 IRQ_TYPE_NONE>, + <GIC_SPI 89 IRQ_TYPE_NONE>, + <GIC_SPI 87 IRQ_TYPE_NONE>, + <GIC_SPI 88 IRQ_TYPE_NONE>; + + dmas = <&fdma0 2 0 1>, <&fdma0 3 0 1>, + <&fdma0 4 0 1>, <&fdma0 7 0 1>, + <&fdma0 5 0 1>, <&fdma0 6 0 1>; + + dma-names = "tx-0", "tx-1", + "tx-2", "tx-3", + "rx-4", "rx-5"; + cpu-dai@0 { + dai-name = "Uni Player #0 (HDMI)"; + uniperiph-id = <0>; + version = <5>; + mode = "HDMI"; + }; + + cpu-dai@1 { + dai-name = "Uni Player #1 (PIO)"; + uniperiph-id = <1>; + version = <5>; + mode = "PCM"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2s_8ch_out>; + }; + cpu-dai@2 { + dai-name = "Uni Player #2 (DAC)"; + uniperiph-id = <2>; + version = <5>; + mode = "PCM"; + }; + cpu-dai@3 { + dai-name = "Uni Player #3 (SPDIF)"; + uniperiph-id = <3>; + version = <5>; + mode = "SPDIF"; + }; + cpu-dai@4 { + dai-name = "Uni Reader #0 (PCM IN)"; + version = <3>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2s_8ch_in>; + }; + cpu-dai@5 { + dai-name = "Uni Reader #1 (HDMI)"; + version = <3>; + }; + }; + + sti_sas_codec: sti-sas-codec { + compatible = "st,stih407-sas-codec"; + #sound-dai-cells = <1>; + st,reg_audio = <&syscfg_core>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_spdif_out >; + }; + +Example of audio card declaration: + sound { + compatible = "simple-audio-card"; + simple-audio-card,name = "sti audio card"; + status = "okay"; + + simple-audio-card,dai-link@0 { /* DAC */ + format = "i2s"; + cpu { + sound-dai = <&audio_controller 2>; + }; + + codec { + sound-dai = <&sti_sasg_codec 1>; + }; + }; + simple-audio-card,dai-link@1 { /* SPDIF */ + format = "left_j"; + cpu { + sound-dai = <&audio_controller 3>; + }; + + codec { + sound-dai = <&sti_sasg_codec 0>; + }; + }; + };
On Mon, May 18, 2015 at 02:12:48PM +0200, Arnaud Pouliquen wrote:
Add Asoc driver bindings documentation.
ASoC.
- standby: standby mode activation deactivation. this mode is specific to
Uni player IP and allows to transmit bus clock and a pattern data
while audio stream is stopped or paused.
- auto-suspend-delay: runtime auto suspend delay (in ms) before deactivating
stanby mode. Equivalent to the DAPM mechanism but for
CPU DAI.
It's still not clear to me that these are things that should be configured via the DT. Otherwise this looks OK.
Oops...sorry, i missed to clean these lines... I suppressed standby mode in my V2. I plan to propose this mode in a separate patch based on DAPM... I let you check the rest of the patches then will clean lines in the V3
On 05/22/2015 02:43 PM, Mark Brown wrote:
On Mon, May 18, 2015 at 02:12:48PM +0200, Arnaud Pouliquen wrote:
Add Asoc driver bindings documentation.
ASoC.
- standby: standby mode activation deactivation. this mode is specific to
Uni player IP and allows to transmit bus clock and a pattern data
while audio stream is stopped or paused.
- auto-suspend-delay: runtime auto suspend delay (in ms) before deactivating
stanby mode. Equivalent to the DAPM mechanism but for
CPU DAI.
It's still not clear to me that these are things that should be configured via the DT. Otherwise this looks OK.
On Fri, May 22, 2015 at 03:24:10PM +0200, Arnaud Pouliquen wrote:
Oops...sorry, i missed to clean these lines...
Please don't top post.
I suppressed standby mode in my V2. I plan to propose this mode in a separate patch based on DAPM... I let you check the rest of the patches then will clean lines in the V3
Ah, OK.
Add the Uniperipheral header file for uniperipheral IPs registers definition.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/sti/uniperif.h | 1099 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1099 insertions(+) create mode 100644 sound/soc/sti/uniperif.h
diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h new file mode 100644 index 0000000..043853e --- /dev/null +++ b/sound/soc/sti/uniperif.h @@ -0,0 +1,1099 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen arnaud.pouliquen@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef __SND_ST_AUD_UNIPERIF_H +#define __SND_ST_AUD_UNIPERIF_H + +#include <linux/regmap.h> + +/* + * Register access macros + */ + +#define GET_UNIPERIF_REG(ip, offset, shift, mask) \ + ((readl_relaxed(ip->base + offset) >> shift) & mask) +#define SET_UNIPERIF_REG(ip, offset, shift, mask, value) \ + writel_relaxed(((readl_relaxed(ip->base + offset) & \ + ~(mask << shift)) | (((value) & mask) << shift)), ip->base + offset) +#define SET_UNIPERIF_BIT_REG(ip, offset, shift, mask, value) \ + writel_relaxed((((value) & mask) << shift), ip->base + offset) + +/* + * AUD_UNIPERIF_SOFT_RST reg + */ + +#define UNIPERIF_SOFT_RST_OFFSET(ip) 0x0000 +#define GET_UNIPERIF_SOFT_RST(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + readl_relaxed(ip->base + UNIPERIF_SOFT_RST_OFFSET(ip)) : 0) +#define SET_UNIPERIF_SOFT_RST(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_SOFT_RST_OFFSET(ip)) + +/* SOFT_RST */ +#define UNIPERIF_SOFT_RST_SOFT_RST_SHIFT(ip) 0x0 +#define UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip) 0x1 +#define SET_UNIPERIF_SOFT_RST_SOFT_RST(ip) \ + SET_UNIPERIF_BIT_REG(ip, \ + UNIPERIF_SOFT_RST_OFFSET(ip), \ + UNIPERIF_SOFT_RST_SOFT_RST_SHIFT(ip), \ + UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip), 1) +#define GET_UNIPERIF_SOFT_RST_SOFT_RST(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_SOFT_RST_OFFSET(ip), \ + UNIPERIF_SOFT_RST_SOFT_RST_SHIFT(ip), \ + UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip)) + +/* + * AUD_UNIPERIF_FIFO_DATA reg + */ + +#define UNIPERIF_FIFO_DATA_OFFSET(ip) 0x0004 +#define SET_UNIPERIF_DATA(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_FIFO_DATA_OFFSET(ip)) + +/* + * AUD_UNIPERIF_CHANNEL_STA_REGN reg + */ + +#define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n)) +#define GET_UNIPERIF_CHANNEL_STA_REGN(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REGN(ip, n)) +#define SET_UNIPERIF_CHANNEL_STA_REGN(ip, n, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_CHANNEL_STA_REGN(ip, n)) + +#define UNIPERIF_CHANNEL_STA_REG0_OFFSET(ip) 0x0060 +#define GET_UNIPERIF_CHANNEL_STA_REG0(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG0_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG0(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG0_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG1_OFFSET(ip) 0x0064 +#define GET_UNIPERIF_CHANNEL_STA_REG1(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG1_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG1(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG1_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG2_OFFSET(ip) 0x0068 +#define GET_UNIPERIF_CHANNEL_STA_REG2(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG2_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG2(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG2_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG3_OFFSET(ip) 0x006C +#define GET_UNIPERIF_CHANNEL_STA_REG3(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG3_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG3(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG3_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG4_OFFSET(ip) 0x0070 +#define GET_UNIPERIF_CHANNEL_STA_REG4(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG4_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG4(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG4_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip) 0x0074 +#define GET_UNIPERIF_CHANNEL_STA_REG5(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG5(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip)) + +/* + * AUD_UNIPERIF_ITS reg + */ + +#define UNIPERIF_ITS_OFFSET(ip) 0x000C +#define GET_UNIPERIF_ITS(ip) \ + readl_relaxed(ip->base + UNIPERIF_ITS_OFFSET(ip)) + +/* MEM_BLK_READ */ +#define UNIPERIF_ITS_MEM_BLK_READ_SHIFT(ip) 5 +#define UNIPERIF_ITS_MEM_BLK_READ_MASK(ip) \ + (BIT(UNIPERIF_ITS_MEM_BLK_READ_SHIFT(ip))) + +/* FIFO_ERROR */ +#define UNIPERIF_ITS_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITS_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITS_FIFO_ERROR_SHIFT(ip))) + +/* DMA_ERROR */ +#define UNIPERIF_ITS_DMA_ERROR_SHIFT(ip) 9 +#define UNIPERIF_ITS_DMA_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITS_DMA_ERROR_SHIFT(ip))) + +/* UNDERFLOW_REC_DONE */ +#define UNIPERIF_ITS_UNDERFLOW_REC_DONE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 12) +#define UNIPERIF_ITS_UNDERFLOW_REC_DONE_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITS_UNDERFLOW_REC_DONE_SHIFT(ip)))) + +/* UNDERFLOW_REC_FAILED */ +#define UNIPERIF_ITS_UNDERFLOW_REC_FAILED_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 13) +#define UNIPERIF_ITS_UNDERFLOW_REC_FAILED_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITS_UNDERFLOW_REC_FAILED_SHIFT(ip)))) + +/* + * AUD_UNIPERIF_ITS_BCLR reg + */ + +/* FIFO_ERROR */ +#define UNIPERIF_ITS_BCLR_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITS_BCLR_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITS_BCLR_FIFO_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITS_BCLR_FIFO_ERROR(ip) \ + SET_UNIPERIF_ITS_BCLR(ip, \ + UNIPERIF_ITS_BCLR_FIFO_ERROR_MASK(ip)) + +#define UNIPERIF_ITS_BCLR_OFFSET(ip) 0x0010 +#define SET_UNIPERIF_ITS_BCLR(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_ITS_BCLR_OFFSET(ip)) + +/* + * AUD_UNIPERIF_ITM reg + */ + +#define UNIPERIF_ITM_OFFSET(ip) 0x0018 +#define GET_UNIPERIF_ITM(ip) \ + readl_relaxed(ip->base + UNIPERIF_ITM_OFFSET(ip)) + +/* FIFO_ERROR */ +#define UNIPERIF_ITM_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITM_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_FIFO_ERROR_SHIFT(ip))) + +/* UNDERFLOW_REC_DONE */ +#define UNIPERIF_ITM_UNDERFLOW_REC_DONE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 12) +#define UNIPERIF_ITM_UNDERFLOW_REC_DONE_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITM_UNDERFLOW_REC_DONE_SHIFT(ip)))) + +/* UNDERFLOW_REC_FAILED */ +#define UNIPERIF_ITM_UNDERFLOW_REC_FAILED_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 13) +#define UNIPERIF_ITM_UNDERFLOW_REC_FAILED_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITM_UNDERFLOW_REC_FAILED_SHIFT(ip)))) + +/* + * AUD_UNIPERIF_ITM_BCLR reg + */ + +#define UNIPERIF_ITM_BCLR_OFFSET(ip) 0x001c +#define SET_UNIPERIF_ITM_BCLR(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_ITM_BCLR_OFFSET(ip)) + +/* FIFO_ERROR */ +#define UNIPERIF_ITM_BCLR_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITM_BCLR_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_BCLR_FIFO_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BCLR_FIFO_ERROR(ip) \ + SET_UNIPERIF_ITM_BCLR(ip, \ + UNIPERIF_ITM_BCLR_FIFO_ERROR_MASK(ip)) + +/* DMA_ERROR */ +#define UNIPERIF_ITM_BCLR_DMA_ERROR_SHIFT(ip) 9 +#define UNIPERIF_ITM_BCLR_DMA_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_BCLR_DMA_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BCLR_DMA_ERROR(ip) \ + SET_UNIPERIF_ITM_BCLR(ip, \ + UNIPERIF_ITM_BCLR_DMA_ERROR_MASK(ip)) + +/* + * AUD_UNIPERIF_ITM_BSET reg + */ + +#define UNIPERIF_ITM_BSET_OFFSET(ip) 0x0020 +#define SET_UNIPERIF_ITM_BSET(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_ITM_BSET_OFFSET(ip)) + +/* FIFO_ERROR */ +#define UNIPERIF_ITM_BSET_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITM_BSET_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_BSET_FIFO_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BSET_FIFO_ERROR(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_FIFO_ERROR_MASK(ip)) + +/* MEM_BLK_READ */ +#define UNIPERIF_ITM_BSET_MEM_BLK_READ_SHIFT(ip) 5 +#define UNIPERIF_ITM_BSET_MEM_BLK_READ_MASK(ip) \ + (BIT(UNIPERIF_ITM_BSET_MEM_BLK_READ_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BSET_MEM_BLK_READ(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_MEM_BLK_READ_MASK(ip)) + +/* DMA_ERROR */ +#define UNIPERIF_ITM_BSET_DMA_ERROR_SHIFT(ip) 9 +#define UNIPERIF_ITM_BSET_DMA_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_BSET_DMA_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BSET_DMA_ERROR(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_DMA_ERROR_MASK(ip)) + +/* UNDERFLOW_REC_DONE */ +#define UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 12) +#define UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE_SHIFT(ip)))) +#define SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE_MASK(ip)) + +/* UNDERFLOW_REC_FAILED */ +#define UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 13) +#define UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED_SHIFT(ip)))) +#define SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED_MASK(ip)) + +/* + * UNIPERIF_CONFIG reg + */ + +#define UNIPERIF_CONFIG_OFFSET(ip) 0x0040 +#define GET_UNIPERIF_CONFIG(ip) \ + readl_relaxed(ip->base + UNIPERIF_CONFIG_OFFSET(ip)) +#define SET_UNIPERIF_CONFIG(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CONFIG_OFFSET(ip)) + +/* PARITY_CNTR */ +#define UNIPERIF_CONFIG_PARITY_CNTR_SHIFT(ip) 0 +#define UNIPERIF_CONFIG_PARITY_CNTR_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_PARITY_CNTR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_MASK(ip)) +#define SET_UNIPERIF_CONFIG_PARITY_CNTR_BY_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_PARITY_CNTR_BY_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_MASK(ip), 1) + +/* CHANNEL_STA_CNTR */ +#define UNIPERIF_CONFIG_CHANNEL_STA_CNTR_SHIFT(ip) 1 +#define UNIPERIF_CONFIG_CHANNEL_STA_CNTR_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_MASK(ip)) +#define SET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR_BY_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR_BY_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_MASK(ip), 1) + +/* USER_DAT_CNTR */ +#define UNIPERIF_CONFIG_USER_DAT_CNTR_SHIFT(ip) 2 +#define UNIPERIF_CONFIG_USER_DAT_CNTR_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_USER_DAT_CNTR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_MASK(ip)) +#define SET_UNIPERIF_CONFIG_USER_DAT_CNTR_BY_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_MASK(ip), 1) +#define SET_UNIPERIF_CONFIG_USER_DAT_CNTR_BY_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_MASK(ip), 0) + +/* VALIDITY_DAT_CNTR */ +#define UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_SHIFT(ip) 3 +#define UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_MASK(ip)) +#define SET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_BY_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_BY_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_MASK(ip), 1) + +/* ONE_BIT_AUD_SUPPORT */ +#define UNIPERIF_CONFIG_ONE_BIT_AUD_SHIFT(ip) 4 +#define UNIPERIF_CONFIG_ONE_BIT_AUD_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_ONE_BIT_AUD(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_SHIFT(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_MASK(ip)) +#define SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_SHIFT(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_ONE_BIT_AUD_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_SHIFT(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_MASK(ip), 1) + +/* MEMORY_FMT */ +#define UNIPERIF_CONFIG_MEM_FMT_SHIFT(ip) 5 +#define UNIPERIF_CONFIG_MEM_FMT_MASK(ip) 0x1 +#define VALUE_UNIPERIF_CONFIG_MEM_FMT_16_0(ip) 0 +#define VALUE_UNIPERIF_CONFIG_MEM_FMT_16_16(ip) 1 +#define GET_UNIPERIF_CONFIG_MEM_FMT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MEM_FMT_SHIFT(ip), \ + UNIPERIF_CONFIG_MEM_FMT_MASK(ip)) +#define SET_UNIPERIF_CONFIG_MEM_FMT(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MEM_FMT_SHIFT(ip), \ + UNIPERIF_CONFIG_MEM_FMT_MASK(ip), value) +#define SET_UNIPERIF_CONFIG_MEM_FMT_16_0(ip) \ + SET_UNIPERIF_CONFIG_MEM_FMT(ip, \ + VALUE_UNIPERIF_CONFIG_MEM_FMT_16_0(ip)) +#define SET_UNIPERIF_CONFIG_MEM_FMT_16_16(ip) \ + SET_UNIPERIF_CONFIG_MEM_FMT(ip, \ + VALUE_UNIPERIF_CONFIG_MEM_FMT_16_16(ip)) + +/* REPEAT_CHL_STS */ +#define UNIPERIF_CONFIG_REPEAT_CHL_STS_SHIFT(ip) 6 +#define UNIPERIF_CONFIG_REPEAT_CHL_STS_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_REPEAT_CHL_STS(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_SHIFT(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_MASK(ip)) +#define SET_UNIPERIF_CONFIG_REPEAT_CHL_STS_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_SHIFT(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_REPEAT_CHL_STS_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_SHIFT(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_MASK(ip), 1) + +/* BACK_STALL_REQ */ +#define UNIPERIF_CONFIG_BACK_STALL_REQ_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 7 : -1) +#define UNIPERIF_CONFIG_BACK_STALL_REQ_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_BACK_STALL_REQ(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_SHIFT(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_MASK(ip)) +#define SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_SHIFT(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_BACK_STALL_REQ_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_SHIFT(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_MASK(ip), 1) + +/* FDMA_TRIGGER_LIMIT */ +#define UNIPERIF_CONFIG_DMA_TRIG_LIMIT_SHIFT(ip) 8 +#define UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(ip) 0x7F +#define GET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_DMA_TRIG_LIMIT_SHIFT(ip), \ + UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(ip)) +#define SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_DMA_TRIG_LIMIT_SHIFT(ip), \ + UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(ip), value) + +/* CHL_STS_UPDATE */ +#define UNIPERIF_CONFIG_CHL_STS_UPDATE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 16 : -1) +#define UNIPERIF_CONFIG_CHL_STS_UPDATE_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_CHL_STS_UPDATE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHL_STS_UPDATE_SHIFT(ip), \ + UNIPERIF_CONFIG_CHL_STS_UPDATE_MASK(ip)) +#define SET_UNIPERIF_CONFIG_CHL_STS_UPDATE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHL_STS_UPDATE_SHIFT(ip), \ + UNIPERIF_CONFIG_CHL_STS_UPDATE_MASK(ip), 1) + +/* IDLE_MOD */ +#define UNIPERIF_CONFIG_IDLE_MOD_SHIFT(ip) 18 +#define UNIPERIF_CONFIG_IDLE_MOD_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_IDLE_MOD(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_SHIFT(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_MASK(ip)) +#define SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_SHIFT(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_IDLE_MOD_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_SHIFT(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_MASK(ip), 1) + +/* SUBFRAME_SELECTION */ +#define UNIPERIF_CONFIG_SUBFRAME_SEL_SHIFT(ip) 19 +#define UNIPERIF_CONFIG_SUBFRAME_SEL_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_SUBFRAME_SEL(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_SHIFT(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_MASK(ip)) +#define SET_UNIPERIF_CONFIG_SUBFRAME_SEL_SUBF1_SUBF0(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_SHIFT(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_MASK(ip), 1) +#define SET_UNIPERIF_CONFIG_SUBFRAME_SEL_SUBF0_SUBF1(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_SHIFT(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_MASK(ip), 0) + +/* FULL_SW_CONTROL */ +#define UNIPERIF_CONFIG_SPDIF_SW_CTRL_SHIFT(ip) 20 +#define UNIPERIF_CONFIG_SPDIF_SW_CTRL_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_SPDIF_SW_CTRL(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_SHIFT(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_MASK(ip)) +#define SET_UNIPERIF_CONFIG_SPDIF_SW_CTRL_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_SHIFT(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_MASK(ip), 1) +#define SET_UNIPERIF_CONFIG_SPDIF_SW_CTRL_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_SHIFT(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_MASK(ip), 0) + +/* MASTER_CLKEDGE */ +#define UNIPERIF_CONFIG_MSTR_CLKEDGE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 24 : -1) +#define UNIPERIF_CONFIG_MSTR_CLKEDGE_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_MSTR_CLKEDGE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_SHIFT(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_MASK(ip)) +#define SET_UNIPERIF_CONFIG_MSTR_CLKEDGE_FALLING(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_SHIFT(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_MASK(ip), 1) +#define SET_UNIPERIF_CONFIG_MSTR_CLKEDGE_RISING(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_SHIFT(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_MASK(ip), 0) + +/* + * UNIPERIF_CTRL reg + */ + +#define UNIPERIF_CTRL_OFFSET(ip) 0x0044 +#define GET_UNIPERIF_CTRL(ip) \ + readl_relaxed(ip->base + UNIPERIF_CTRL_OFFSET(ip)) +#define SET_UNIPERIF_CTRL(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CTRL_OFFSET(ip)) + +/* OPERATION */ +#define UNIPERIF_CTRL_OPERATION_SHIFT(ip) 0 +#define UNIPERIF_CTRL_OPERATION_MASK(ip) 0x7 +#define GET_UNIPERIF_CTRL_OPERATION(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_OFF(ip) 0 +#define SET_UNIPERIF_CTRL_OPERATION_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_OFF(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_MUTE_PCM_NULL(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 1 : -1) +#define SET_UNIPERIF_CTRL_OPERATION_MUTE_PCM_NULL(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_MUTE_PCM_NULL(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_MUTE_PAUSE_BURST(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 2 : -1) +#define SET_UNIPERIF_CTRL_OPERATION_MUTE_PAUSE_BURST(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_MUTE_PAUSE_BURST(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_PCM_DATA(ip) 3 +#define SET_UNIPERIF_CTRL_OPERATION_PCM_DATA(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_PCM_DATA(ip)) +/* This is the same as above! */ +#define VALUE_UNIPERIF_CTRL_OPERATION_AUDIO_DATA(ip) 3 +#define SET_UNIPERIF_CTRL_OPERATION_AUDIO_DATA(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_AUDIO_DATA(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_ENC_DATA(ip) 4 +#define SET_UNIPERIF_CTRL_OPERATION_ENC_DATA(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_ENC_DATA(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_CD_DATA(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 5 : -1) +#define SET_UNIPERIF_CTRL_OPERATION_CD_DATA(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_CD_DATA(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_STANDBY(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 7) +#define SET_UNIPERIF_CTRL_OPERATION_STANDBY(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_STANDBY(ip)) + +/* EXIT_STBY_ON_EOBLOCK */ +#define UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 3) +#define UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_SHIFT(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_MASK(ip)) +#define SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_SHIFT(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_MASK(ip), 0) +#define SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_SHIFT(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_MASK(ip), 1) + +/* ROUNDING */ +#define UNIPERIF_CTRL_ROUNDING_SHIFT(ip) 4 +#define UNIPERIF_CTRL_ROUNDING_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_ROUNDING(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ROUNDING_SHIFT(ip), \ + UNIPERIF_CTRL_ROUNDING_MASK(ip)) +#define SET_UNIPERIF_CTRL_ROUNDING_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ROUNDING_SHIFT(ip), \ + UNIPERIF_CTRL_ROUNDING_MASK(ip), 0) +#define SET_UNIPERIF_CTRL_ROUNDING_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ROUNDING_SHIFT(ip), \ + UNIPERIF_CTRL_ROUNDING_MASK(ip), 1) + +/* DIVIDER */ +#define UNIPERIF_CTRL_DIVIDER_SHIFT(ip) 5 +#define UNIPERIF_CTRL_DIVIDER_MASK(ip) 0xff +#define GET_UNIPERIF_CTRL_DIVIDER(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_DIVIDER_SHIFT(ip), \ + UNIPERIF_CTRL_DIVIDER_MASK(ip)) +#define SET_UNIPERIF_CTRL_DIVIDER(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_DIVIDER_SHIFT(ip), \ + UNIPERIF_CTRL_DIVIDER_MASK(ip), value) + +/* BYTE_SWAP */ +#define UNIPERIF_CTRL_BYTE_SWP_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 13 : -1) +#define UNIPERIF_CTRL_BYTE_SWP_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_BYTE_SWP(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_BYTE_SWP_SHIFT(ip), \ + UNIPERIF_CTRL_BYTE_SWP_MASK(ip)) +#define SET_UNIPERIF_CTRL_BYTE_SWP_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_BYTE_SWP_SHIFT(ip), \ + UNIPERIF_CTRL_BYTE_SWP_MASK(ip), 0) +#define SET_UNIPERIF_CTRL_BYTE_SWP_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_BYTE_SWP_SHIFT(ip), \ + UNIPERIF_CTRL_BYTE_SWP_MASK(ip), 1) + +/* ZERO_STUFFING_HW_SW */ +#define UNIPERIF_CTRL_ZERO_STUFF_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 14 : -1) +#define UNIPERIF_CTRL_ZERO_STUFF_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_ZERO_STUFF(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_SHIFT(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_MASK(ip)) +#define SET_UNIPERIF_CTRL_ZERO_STUFF_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_SHIFT(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_MASK(ip), 1) +#define SET_UNIPERIF_CTRL_ZERO_STUFF_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_SHIFT(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_MASK(ip), 0) + +/* SPDIF_LAT */ +#define UNIPERIF_CTRL_SPDIF_LAT_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 16 : -1) +#define UNIPERIF_CTRL_SPDIF_LAT_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_SPDIF_LAT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_MASK(ip)) +#define SET_UNIPERIF_CTRL_SPDIF_LAT_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_MASK(ip), 1) +#define SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_MASK(ip), 0) + +/* EN_SPDIF_FORMATTING */ +#define UNIPERIF_CTRL_SPDIF_FMT_SHIFT(ip) 17 +#define UNIPERIF_CTRL_SPDIF_FMT_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_SPDIF_FMT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_MASK(ip)) +#define SET_UNIPERIF_CTRL_SPDIF_FMT_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_MASK(ip), 1) +#define SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_MASK(ip), 0) + +/* READER_OUT_SELECT */ +#define UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 18 : -1) +#define UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_READER_OUT_SEL(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip)) +#define SET_UNIPERIF_CTRL_READER_OUT_SEL_IN_MEM(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 0) +#define SET_UNIPERIF_CTRL_READER_OUT_SEL_ON_I2S_LINE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \ + CORAUD_UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 1) + +/* UNDERFLOW_REC_WINDOW */ +#define UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip) 20 +#define UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_MASK(ip) 0xff +#define GET_UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip), \ + UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_MASK(ip)) +#define SET_UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip), \ + UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_MASK(ip), value) + +/* + * UNIPERIF_I2S_FMT a.k.a UNIPERIF_FORMAT reg + */ + +#define UNIPERIF_I2S_FMT_OFFSET(ip) 0x0048 +#define GET_UNIPERIF_I2S_FMT(ip) \ + readl_relaxed(ip->base + UNIPERIF_I2S_FMT_OFFSET(ip)) +#define SET_UNIPERIF_I2S_FMT(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_I2S_FMT_OFFSET(ip)) + +/* NBIT */ +#define UNIPERIF_I2S_FMT_NBIT_SHIFT(ip) 0 +#define UNIPERIF_I2S_FMT_NBIT_MASK(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_NBIT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NBIT_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NBIT_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_NBIT_32(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NBIT_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NBIT_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_NBIT_16(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NBIT_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NBIT_MASK(ip), 1) + +/* DATA_SIZE */ +#define UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip) 1 +#define UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip) 0x7 +#define GET_UNIPERIF_I2S_FMT_DATA_SIZE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_18(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 1) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_20(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 2) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_24(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 3) +#define SET_UNIPERIF_I2S_FMTL_DATA_SIZE_28(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 4) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 5) + +/* LR_POL */ +#define UNIPERIF_I2S_FMT_LR_POL_SHIFT(ip) 4 +#define UNIPERIF_I2S_FMT_LR_POL_MASK(ip) 0x1 +#define VALUE_UNIPERIF_I2S_FMT_LR_POL_LOW(ip) 0x0 +#define VALUE_UNIPERIF_I2S_FMT_LR_POL_HIG(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_LR_POL(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_LR_POL_SHIFT(ip), \ + UNIPERIF_I2S_FMT_LR_POL_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_LR_POL(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_LR_POL_SHIFT(ip), \ + UNIPERIF_I2S_FMT_LR_POL_MASK(ip), value) +#define SET_UNIPERIF_I2S_FMT_LR_POL_LOW(ip) \ + SET_UNIPERIF_I2S_FMT_LR_POL(ip, \ + VALUE_UNIPERIF_I2S_FMT_LR_POL_LOW(ip)) +#define SET_UNIPERIF_I2S_FMT_LR_POL_HIG(ip) \ + SET_UNIPERIF_I2S_FMT_LR_POL(ip, \ + VALUE_UNIPERIF_I2S_FMT_LR_POL_HIG(ip)) + +/* SCLK_EDGE */ +#define UNIPERIF_I2S_FMT_SCLK_EDGE_SHIFT(ip) 5 +#define UNIPERIF_I2S_FMT_SCLK_EDGE_MASK(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_SCLK_EDGE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_MASK(ip), 1) + +/* PADDING */ +#define UNIPERIF_I2S_FMT_PADDING_SHIFT(ip) 6 +#define UNIPERIF_I2S_FMT_PADDING_MASK(ip) 0x1 +#define UNIPERIF_I2S_FMT_PADDING_MASK(ip) 0x1 +#define VALUE_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(ip) 0x0 +#define VALUE_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_PADDING(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_PADDING_SHIFT(ip), \ + UNIPERIF_I2S_FMT_PADDING_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_PADDING(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_PADDING_SHIFT(ip), \ + UNIPERIF_I2S_FMT_PADDING_MASK(ip), value) +#define SET_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(ip) \ + SET_UNIPERIF_I2S_FMT_PADDING(ip, \ + VALUE_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(ip)) +#define SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(ip) \ + SET_UNIPERIF_I2S_FMT_PADDING(ip, \ + VALUE_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(ip)) + +/* ALIGN */ +#define UNIPERIF_I2S_FMT_ALIGN_SHIFT(ip) 7 +#define UNIPERIF_I2S_FMT_ALIGN_MASK(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_ALIGN(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ALIGN_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ALIGN_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ALIGN_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ALIGN_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_ALIGN_RIGHT(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ALIGN_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ALIGN_MASK(ip), 1) + +/* ORDER */ +#define UNIPERIF_I2S_FMT_ORDER_SHIFT(ip) 8 +#define UNIPERIF_I2S_FMT_ORDER_MASK(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_ORDER(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ORDER_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ORDER_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_ORDER_LSB(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ORDER_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ORDER_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_ORDER_MSB(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ORDER_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ORDER_MASK(ip), 1) + +/* NUM_CH */ +#define UNIPERIF_I2S_FMT_NUM_CH_SHIFT(ip) 9 +#define UNIPERIF_I2S_FMT_NUM_CH_MASK(ip) 0x7 +#define GET_UNIPERIF_I2S_FMT_NUM_CH(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NUM_CH_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NUM_CH_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_NUM_CH(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NUM_CH_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NUM_CH_MASK(ip), value) + +/* NO_OF_SAMPLES_TO_READ */ +#define UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_SHIFT(ip) 12 +#define UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_MASK(ip) 0xfffff +#define GET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_MASK(ip), value) + +/* + * UNIPERIF_BIT_CONTROL reg + */ + +#define UNIPERIF_BIT_CONTROL_OFFSET(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 0x004c) +#define GET_UNIPERIF_BIT_CONTROL(ip) \ + readl_relaxed(ip->base + UNIPERIF_BIT_CONTROL_OFFSET(ip)) +#define SET_UNIPERIF_BIT_CONTROL(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_BIT_CONTROL_OFFSET(ip)) + +/* CLR_UNDERFLOW_DURATION */ +#define UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_SHIFT(ip) 0 +#define UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_MASK(ip) 0x1 +#define GET_UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_BIT_CONTROL_OFFSET(ip), \ + UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_SHIFT(ip), \ + UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_MASK(ip)) +#define SET_UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_BIT_CONTROL_OFFSET(ip), \ + UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_SHIFT(ip), \ + UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_MASK(ip), 1) + +/* CHL_STS_UPDATE */ +#define UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_SHIFT(ip) 1 +#define UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_MASK(ip) 0x1 +#define GET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_BIT_CONTROL_OFFSET(ip), \ + UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_SHIFT(ip), \ + UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_MASK(ip)) +#define SET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(ip) \ + SET_UNIPERIF_BIT_REG(ip, \ + UNIPERIF_BIT_CONTROL_OFFSET(ip), \ + UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_SHIFT(ip), \ + UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_MASK(ip), 1) + +/* + * UNIPERIF_STATUS_1 reg + */ + +#define UNIPERIF_STATUS_1_OFFSET(ip) 0x0050 +#define GET_UNIPERIF_STATUS_1(ip) \ + readl_relaxed(ip->base + UNIPERIF_STATUS_1_OFFSET(ip)) +#define SET_UNIPERIF_STATUS_1(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_STATUS_1_OFFSET(ip)) + +/* UNDERFLOW_DURATION */ +#define UNIPERIF_STATUS_1_UNDERFLOW_DURATION_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 0) +#define UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip) 0xff +#define GET_UNIPERIF_STATUS_1_UNDERFLOW_DURATION(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_STATUS_1_OFFSET(ip), \ + UNIPERIF_STATUS_1_UNDERFLOW_DURATION_SHIFT(ip), \ + UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip)) +#define SET_UNIPERIF_STATUS_1_UNDERFLOW_DURATION(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_STATUS_1_OFFSET(ip), \ + UNIPERIF_STATUS_1_UNDERFLOW_DURATION_SHIFT(ip), \ + UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip), value) + +/* + * AUD_UNIPERIF_CHANNEL_STA_REGN reg + */ + +#define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n)) +#define GET_UNIPERIF_CHANNEL_STA_REGN(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REGN(ip, n)) +#define SET_UNIPERIF_CHANNEL_STA_REGN(ip, n, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_CHANNEL_STA_REGN(ip, n)) + +/* + * AUD_UNIPERIF_USER_VALIDITY reg + */ + +#define UNIPERIF_USER_VALIDITY_OFFSET(ip) 0x0090 +#define GET_UNIPERIF_USER_VALIDITY(ip) \ + readl_relaxed(ip->base + UNIPERIF_USER_VALIDITY_OFFSET(ip)) +#define SET_UNIPERIF_USER_VALIDITY(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_USER_VALIDITY_OFFSET(ip)) + +/* VALIDITY_LEFT_AND_RIGHT */ +#define UNIPERIF_USER_VALIDITY_VALIDITY_LR_SHIFT(ip) 0 +#define UNIPERIF_USER_VALIDITY_VALIDITY_LR_MASK(ip) 0x3 +#define GET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_USER_VALIDITY_OFFSET(ip), \ + UNIPERIF_USER_VALIDITY_VALIDITY_LR_SHIFT(ip), \ + UNIPERIF_USER_VALIDITY_VALIDITY_LR_MASK(ip)) +#define SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_USER_VALIDITY_OFFSET(ip), \ + UNIPERIF_USER_VALIDITY_VALIDITY_LR_SHIFT(ip), \ + UNIPERIF_USER_VALIDITY_VALIDITY_LR_MASK(ip), \ + value ? 0x3 : 0) + +/* + * UNIPERIF_DBG_STANDBY_LEFT_SP reg + */ +#define UNIPERIF_DBG_STANDBY_LEFT_SP_OFFSET(ip) 0x0150 +#define UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 0) +#define UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 0xFFFFFF) +#define GET_UNIPERIF_DBG_STANDBY_LEFT_SP(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_DBG_STANDBY_LEFT_SP_OFFSET(ip), \ + UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip), \ + UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip)) +#define SET_UNIPERIF_DBG_STANDBY_LEFT_SP(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_DBG_STANDBY_LEFT_SP_OFFSET(ip), \ + UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip), \ + UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip), value)
Add code to manage Uniperipheral player IP instances. These DAIs are dedicated to playback and support I2S and IEC mode.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/sti/uniperif.h | 121 +++++ sound/soc/sti/uniperif_player.c | 1006 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1127 insertions(+) create mode 100644 sound/soc/sti/uniperif_player.c
diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index 043853e..1e67489 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1097,3 +1097,124 @@ UNIPERIF_DBG_STANDBY_LEFT_SP_OFFSET(ip), \ UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip), \ UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip), value) + +/* + * uniperipheral IP capabilities + */ + +#define UNIPERIF_FIFO_SIZE 70 /* FIFO is 70 cells deep */ +#define UNIPERIF_FIFO_FRAMES 4 /* FDMA trigger limit in frames */ + +/* + * Uniperipheral IP revisions + */ +enum uniperif_version { + SND_ST_UNIPERIF_VERSION_UNKNOWN, + /* SASG1 (Orly), Newman */ + SND_ST_UNIPERIF_VERSION_C6AUD0_UNI_1_0, + /* SASC1, SASG2 (Orly2) */ + SND_ST_UNIPERIF_VERSION_UNI_PLR_1_0, + /* SASC1, SASG2 (Orly2), TELSS, Cannes */ + SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0, + /* TELSS (SASC1) */ + SND_ST_UNIPERIF_VERSION_TDM_PLR_1_0, + /* Cannes/Monaco */ + SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 +}; + +enum uniperif_type { + SND_ST_UNIPERIF_PLAYER_TYPE_NONE, + SND_ST_UNIPERIF_PLAYER_TYPE_HDMI, + SND_ST_UNIPERIF_PLAYER_TYPE_PCM, + SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF +}; + +enum uniperif_state { + UNIPERIF_STATE_STOPPED, + UNIPERIF_STATE_STARTED, + UNIPERIF_STATE_STANDBY, + UNIPERIF_STATE_UNDERFLOW, + UNIPERIF_STATE_OVERFLOW = UNIPERIF_STATE_UNDERFLOW, + UNIPERIF_STATE_XRUN +}; + +enum uniperif_iec958_encoding_mode { + UNIPERIF_IEC958_ENCODING_MODE_PCM, + UNIPERIF_IEC958_ENCODING_MODE_ENCODED +}; + +struct uniperif_info { + int ver; + int uni_num; /* instance value of the uniperipheral IP */ + enum uniperif_type player_type; + int underflow_enabled; /* Underflow recovery mode */ + int standby_enabled; + unsigned int suspend_delay; +}; + +struct uniperif_iec958_settings { + enum uniperif_iec958_encoding_mode encoding_mode; + struct snd_aes_iec958 iec958; +}; + +struct uniperif; + +struct uniperif_ops { + int (*open)(struct uniperif *player); + void (*close)(struct uniperif *player); + int (*prepare)(struct uniperif *player, + struct snd_pcm_runtime *runtime); + int (*trigger)(struct uniperif *player, int cmd); +}; + +struct uniperif { + /* System information */ + struct uniperif_info *info; + struct device *dev; + int ver; /* IP version, used by register access macros */ + struct regmap_field *clk_sel; + + /* capabilities */ + const struct snd_pcm_hardware *hw; + + /* Resources */ + struct resource *mem_region; + void *base; + unsigned long fifo_phys_address; + int irq; + + /* Clocks */ + struct clk *clk; + int clk_div; + int clk_adj; + int rate; + + /* Runtime data */ + enum uniperif_state state; + + struct snd_pcm_substream *substream; + + /* Specific to IEC958 player */ + struct uniperif_iec958_settings stream_settings; + spinlock_t lock; /* lock on resource updated by alsa controls */ + + /*alsa ctrl*/ + struct snd_kcontrol_new *snd_ctrls; + int num_ctrls; + + /* dai properties */ + unsigned int daifmt; + + /* DAI callbacks */ + const struct uniperif_ops *ops; + + /* work struct */ + struct delayed_work delayed_work; +}; + +/* uniperiph player*/ +int uni_player_init(struct platform_device *pdev, struct device_node *node, + struct uniperif **uni_player, int idx); +int uni_player_remove(struct platform_device *pdev); + +#endif diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c new file mode 100644 index 0000000..b41cc82 --- /dev/null +++ b/sound/soc/sti/uniperif_player.c @@ -0,0 +1,1006 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen arnaud.pouliquen@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> + +#include <sound/asoundef.h> +#include <sound/soc.h> + +#include "uniperif.h" + +/* + * Some hardware-related definitions + */ + +#define DEFAULT_OVERSAMPLING 128 /* make all ip's running at same rate*/ + +#define MIN_IEC958_SAMPLE_RATE 32000 + +/* sys config registers definitions */ +#define SYS_CFG_AUDIO_GLUE 0xA4 +#define SYS_CFG_AUDI0_GLUE_PCM_CLKX 8 + +/* + * Driver specific types. + */ +#define UNIPERIF_PLAYER_TYPE_IS_HDMI(p) \ + ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_HDMI) +#define UNIPERIF_PLAYER_TYPE_IS_PCM(p) \ + ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_PCM) +#define UNIPERIF_PLAYER_TYPE_IS_SPDIF(p) \ + ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF) +#define UNIPERIF_PLAYER_TYPE_IS_IEC958(p) \ + (UNIPERIF_PLAYER_TYPE_IS_HDMI(p) || \ + UNIPERIF_PLAYER_TYPE_IS_SPDIF(p)) + +/* + * Note: snd_pcm_hardware is linked to DMA controller but is declared here to + * integrate DAI_CPU capability in term of rate and supported channels + */ +const struct snd_pcm_hardware uni_player_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 192000, + + .channels_min = 2, + .channels_max = 8, + + .periods_min = 2, + .periods_max = 48, + + .period_bytes_min = 128, + .period_bytes_max = 64 * PAGE_SIZE, + .buffer_bytes_max = 256 * PAGE_SIZE +}; + +static inline int reset_player(struct uniperif *player) +{ + int count = 10; + + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) { + while (GET_UNIPERIF_SOFT_RST_SOFT_RST(player) && count) { + udelay(5); + count--; + } + } + + if (!count) { + dev_err(player->dev, "Failed to reset uniperif"); + return -EIO; + } + + return 0; +} + +/* + * uni_player_irq_handler + * In case of error audio stream is stopped; stop action is protected via PCM + * stream lock for avoiding the race with trigger callback. + */ +static irqreturn_t uni_player_irq_handler(int irq, void *dev_id) +{ + irqreturn_t ret = IRQ_NONE; + struct uniperif *player = dev_id; + unsigned int status; + unsigned int tmp; + + if (player->state == UNIPERIF_STATE_STOPPED) { + /* Unexpected IRQ: do nothing */ + dev_warn(player->dev, "unexpected IRQ"); + return IRQ_HANDLED; + } + + /* Get interrupt status & clear them immediately */ + status = GET_UNIPERIF_ITS(player); + SET_UNIPERIF_ITS_BCLR(player, status); + + /* Check for fifo error (underrun) */ + if (unlikely(status & UNIPERIF_ITS_FIFO_ERROR_MASK(player))) { + dev_err(player->dev, "FIFO underflow error detected"); + + /* Interrupt is just for information when underflow recovery */ + if (player->info->underflow_enabled) { + /* Update state to underflow */ + player->state = UNIPERIF_STATE_UNDERFLOW; + + } else { + /* Disable interrupt so doesn't continually fire */ + SET_UNIPERIF_ITM_BCLR_FIFO_ERROR(player); + + /* Stop the player */ + snd_pcm_stream_lock(player->substream); + snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock(player->substream); + } + + ret = IRQ_HANDLED; + } + + /* Check for dma error (over run) */ + if (unlikely(status & UNIPERIF_ITS_DMA_ERROR_MASK(player))) { + dev_err(player->dev, "DMA error detected"); + + /* Disable interrupt so doesn't continually fire */ + SET_UNIPERIF_ITM_BCLR_DMA_ERROR(player); + + /* Stop the player */ + snd_pcm_stream_lock(player->substream); + snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock(player->substream); + + ret = IRQ_HANDLED; + } + + /* Check for underflow recovery done */ + if (unlikely(status & UNIPERIF_ITM_UNDERFLOW_REC_DONE_MASK(player))) { + if (!player->info->underflow_enabled) { + dev_err(player->dev, "unexpected Underflow recovering"); + return -EPERM; + } + /* Read the underflow recovery duration */ + tmp = GET_UNIPERIF_STATUS_1_UNDERFLOW_DURATION(player); + + /* Clear the underflow recovery duration */ + SET_UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION(player); + + /* Update state to started */ + player->state = UNIPERIF_STATE_STARTED; + + ret = IRQ_HANDLED; + } + + /* Check if underflow recovery failed */ + if (unlikely(status & + UNIPERIF_ITM_UNDERFLOW_REC_FAILED_MASK(player))) { + dev_err(player->dev, "Underflow recovery failed"); + + /* Stop the player */ + snd_pcm_stream_lock(player->substream); + snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock(player->substream); + + ret = IRQ_HANDLED; + } + + /* Error on unhandled interrupt */ + if (ret != IRQ_HANDLED) + dev_err(player->dev, "IRQ status : %#x", status); + + return ret; +} + +static void uni_player_set_channel_status(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int n; + unsigned int status; + + /* + * Some AVRs and TVs require the channel status to contain a correct + * sampling frequency. If no sample rate is already specified, then + * set one. + */ + if (runtime && (player->stream_settings.iec958.status[3] + == IEC958_AES3_CON_FS_NOTID)) { + switch (runtime->rate) { + case 22050: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_22050; + break; + case 44100: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_44100; + break; + case 88200: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_88200; + break; + case 176400: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_176400; + break; + case 24000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_24000; + break; + case 48000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_48000; + break; + case 96000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_96000; + break; + case 192000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_192000; + break; + case 32000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_32000; + break; + case 768000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_768000; + break; + default: + /* Mark as sampling frequency not indicated */ + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_NOTID; + break; + } + } + + /* Audio mode: + * Use audio mode status to select PCM or encoded mode + */ + if (player->stream_settings.iec958.status[0] & IEC958_AES0_NONAUDIO) + player->stream_settings.encoding_mode = + UNIPERIF_IEC958_ENCODING_MODE_ENCODED; + else + player->stream_settings.encoding_mode = + UNIPERIF_IEC958_ENCODING_MODE_PCM; + + if (player->stream_settings.encoding_mode == + UNIPERIF_IEC958_ENCODING_MODE_PCM) + /* Clear user validity bits */ + SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 0); + else + /* Set user validity bits */ + SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 1); + + /* Program the new channel status */ + for (n = 0; n < 6; ++n) { + status = + player->stream_settings.iec958.status[0 + (n * 4)] & 0xf; + status |= + player->stream_settings.iec958.status[1 + (n * 4)] << 8; + status |= + player->stream_settings.iec958.status[2 + (n * 4)] << 16; + status |= + player->stream_settings.iec958.status[3 + (n * 4)] << 24; + SET_UNIPERIF_CHANNEL_STA_REGN(player, n, status); + } + + /* Update the channel status */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + SET_UNIPERIF_CONFIG_CHL_STS_UPDATE(player); + else + SET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(player); +} + +static int uni_player_prepare_iec958(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + if (!runtime) { + dev_err(player->dev, "%s: invalid pointer(s)", __func__); + return -EINVAL; + } + + /* Oversampling must be multiple of 128 as iec958 frame is 32-bits */ + if ((player->clk_div % 128) || + (player->clk_div <= 0)) { + dev_err(player->dev, "%s: invalid clk_div %d", + __func__, player->clk_div); + return -EINVAL; + } + + /* No sample rates below 32kHz are supported for iec958 */ + if (runtime->rate < MIN_IEC958_SAMPLE_RATE) { + dev_err(player->dev, "Invalid rate (%d)", runtime->rate); + return -EINVAL; + } + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* 16/16 memory format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_16(player); + /* 16-bits per sub-frame */ + SET_UNIPERIF_I2S_FMT_NBIT_32(player); + /* Set 16-bit sample precision */ + SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(player); + break; + case SNDRV_PCM_FORMAT_S32_LE: + /* 16/0 memory format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player); + /* 32-bits per sub-frame */ + SET_UNIPERIF_I2S_FMT_NBIT_32(player); + /* Set 24-bit sample precision */ + SET_UNIPERIF_I2S_FMT_DATA_SIZE_24(player); + break; + default: + dev_err(player->dev, "format not supported"); + return -EINVAL; + } + + /* Set parity to be calculated by the hardware */ + SET_UNIPERIF_CONFIG_PARITY_CNTR_BY_HW(player); + + /* Set channel status bits to be inserted by the hardware */ + SET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR_BY_HW(player); + + /* Set user data bits to be inserted by the hardware */ + SET_UNIPERIF_CONFIG_USER_DAT_CNTR_BY_HW(player); + + /* Set validity bits to be inserted by the hardware */ + SET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_BY_HW(player); + + /* Set full software control to disabled */ + SET_UNIPERIF_CONFIG_SPDIF_SW_CTRL_DISABLE(player); + + SET_UNIPERIF_CTRL_ZERO_STUFF_HW(player); + + /* Update the channel status */ + uni_player_set_channel_status(player, runtime); + + /* Clear the user validity user bits */ + SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 0); + + /* Disable one-bit audio mode */ + SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player); + + /* Enable consecutive frames repetition of Z preamble (not for HBRA) */ + SET_UNIPERIF_CONFIG_REPEAT_CHL_STS_ENABLE(player); + + /* Change to SUF0_SUBF1 and left/right channels swap! */ + SET_UNIPERIF_CONFIG_SUBFRAME_SEL_SUBF1_SUBF0(player); + + /* Set data output on rising edge */ + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(player); + + /* Set data output as MSB first */ + SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); + + if (player->stream_settings.encoding_mode == + UNIPERIF_IEC958_ENCODING_MODE_ENCODED) + SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_ON(player); + else + SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_OFF(player); + + /* Set the number of channels (maximum supported by spdif is 2) */ + if (UNIPERIF_PLAYER_TYPE_IS_SPDIF(player) && + (runtime->channels != 2)) { + dev_err(player->dev, "invalid nb of channels"); + return -EINVAL; + } + + SET_UNIPERIF_I2S_FMT_NUM_CH(player, runtime->channels / 2); + + /* Set rounding to off */ + SET_UNIPERIF_CTRL_ROUNDING_OFF(player); + + /* Set clock divisor */ + SET_UNIPERIF_CTRL_DIVIDER(player, + player->clk_div / 128); + + /* Set the spdif latency to not wait before starting player */ + SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player); + + /* + * Ensure iec958 formatting is off. It will be enabled in function + * uni_player_start() at the same time as the operation + * mode is set to work around a silicon issue. + */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player); + else + SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player); + + return 0; +} + +static int uni_player_prepare_pcm(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int output_frame_size, slot_width; + + if (!runtime) { + dev_err(player->dev, "%s: invalid pointer(s)", __func__); + return -EINVAL; + } + + /* Force slot width to 32 in I2S mode (HW constraint) */ + if ((player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == + SND_SOC_DAIFMT_I2S) { + slot_width = 32; + } else { + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + slot_width = 16; + break; + default: + slot_width = 32; + break; + } + } + output_frame_size = slot_width * runtime->channels; + + if (player->clk_div <= 0) { + dev_err(player->dev, "%s: invalid clk_div", __func__); + return -EINVAL; + } + + /* + * For 32 bits subframe clk_div must be a multiple of 128, + * for 16 bits must be a multiple of 64 + */ + if ((slot_width == 32) && (player->clk_div % 128)) { + dev_err(player->dev, "%s: invalid clk_div", __func__); + return -EINVAL; + } + + if ((slot_width == 16) && (player->clk_div % 64)) { + dev_err(player->dev, "%s: invalid clk_div", __func__); + return -EINVAL; + } + + /* + * Number of bits per subframe (which is one channel sample) + * on output - Transfer 16 or 32 bits from FIFO + */ + switch (slot_width) { + case 32: + SET_UNIPERIF_I2S_FMT_NBIT_32(player); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(player); + break; + case 16: + SET_UNIPERIF_I2S_FMT_NBIT_16(player); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(player); + break; + default: + dev_err(player->dev, "subframe format not supported"); + return -EINVAL; + } + + /* Configure data memory format */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* One data word contains two samples */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_16(player); + break; + + case SNDRV_PCM_FORMAT_S32_LE: + /* + * Actually "16 bits/0 bits" means "32/28/24/20/18/16 bits + * on the left than zeros (if less than 32 bytes)"... ;-) + */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player); + break; + + default: + dev_err(player->dev, "format not supported"); + return -EINVAL; + } + + /* Set rounding to off */ + SET_UNIPERIF_CTRL_ROUNDING_OFF(player); + + /* Set clock divisor */ + SET_UNIPERIF_CTRL_DIVIDER(player, + player->clk_div / (2 * output_frame_size)); + + /* Number of channelsmust be even*/ + if ((runtime->channels % 2) || (runtime->channels < 2) || + (runtime->channels > 10)) { + dev_err(player->dev, "%s: invalid nb of channels", __func__); + return -EINVAL; + } + + SET_UNIPERIF_I2S_FMT_NUM_CH(player, runtime->channels / 2); + + /* Set 1-bit audio format to disabled */ + SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player); + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(player); + + /* No iec958 formatting as outputting to DAC */ + SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player); + + return 0; +} + +static int uni_player_prepare(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int transfer_size, trigger_limit; + int ret; + + /* The player should be stopped */ + if (player->state != UNIPERIF_STATE_STOPPED) { + dev_err(player->dev, "%s: invalid player state %d", __func__, + player->state); + return -EINVAL; + } + + /* Calculate transfer size (in fifo cells and bytes) for frame count */ + transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + + /* Calculate number of empty cells available before asserting DREQ */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size; + else + /* + * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 + * FDMA_TRIGGER_LIMIT also controls when the state switches + * from OFF or STANDBY to AUDIO DATA. + */ + trigger_limit = transfer_size; + + /* Trigger limit must be an even number */ + if ((!trigger_limit % 2) || (trigger_limit != 1 && transfer_size % 2) || + (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(player))) { + dev_err(player->dev, "invalid trigger limit %d", trigger_limit); + return -EINVAL; + } + + SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(player, trigger_limit); + + /* Uniperipheral setup is depend on player type */ + switch (player->info->player_type) { + case SND_ST_UNIPERIF_PLAYER_TYPE_HDMI: + ret = uni_player_prepare_iec958(player, runtime); + break; + case SND_ST_UNIPERIF_PLAYER_TYPE_PCM: + ret = uni_player_prepare_pcm(player, runtime); + break; + case SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF: + ret = uni_player_prepare_iec958(player, runtime); + break; + default: + dev_err(player->dev, "invalid player type"); + return -EINVAL; + } + + if (ret) + return ret; + + switch (player->daifmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + case SND_SOC_DAIFMT_NB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(player); + break; + default: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(player); + } + + switch (player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player); + SET_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(player); + break; + case SND_SOC_DAIFMT_LEFT_J: + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player); + SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(player); + break; + case SND_SOC_DAIFMT_RIGHT_J: + SET_UNIPERIF_I2S_FMT_ALIGN_RIGHT(player); + SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(player); + break; + default: + dev_err(player->dev, "format not supported"); + return -EINVAL; + } + + /* Set the interrupt mask */ + SET_UNIPERIF_ITM_BSET_DMA_ERROR(player); + SET_UNIPERIF_ITM_BSET_FIFO_ERROR(player); + + /* Enable underflow recovery interrupts */ + if (player->info->underflow_enabled) { + SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE(player); + SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED(player); + } + + /* Set clock rate */ + ret = clk_set_rate(player->clk, runtime->rate * player->clk_div); + if (ret) { + dev_err(player->dev, "Failed to set clock rate"); + return ret; + } + + SET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(player, 0); + + /* Reset uniperipheral player */ + SET_UNIPERIF_SOFT_RST_SOFT_RST(player); + + return reset_player(player); +} + +static int uni_player_start(struct uniperif *player) +{ + int ret; + + /* The player should be stopped */ + if (player->state != UNIPERIF_STATE_STOPPED) { + dev_err(player->dev, "%s: invalid player state", __func__); + return -EINVAL; + } + + /* pinctrl: switch pinstate to default */ + ret = pinctrl_pm_select_default_state(player->dev); + if (ret) { + dev_err(player->dev, + "%s: failed to select default pinctrl state", + __func__); + return ret; + } + + ret = clk_prepare_enable(player->clk); + if (ret) { + dev_err(player->dev, "%s: Failed to enable clock", __func__); + return ret; + } + + /* Clear any pending interrupts */ + SET_UNIPERIF_ITS_BCLR(player, GET_UNIPERIF_ITS(player)); + + /* Enable player interrupts */ + enable_irq(player->irq); + + /* Reset uniperipheral player */ + SET_UNIPERIF_SOFT_RST_SOFT_RST(player); + + ret = reset_player(player); + if (ret < 0) + return ret; + + /* + * Does not use IEC61937 features of the uniperipheral hardware. + * Instead it performs IEC61937 in software and inserts it directly + * into the audio data stream. As such, when encoded mode is selected, + * linear pcm mode is still used, but with the differences of the + * channel status bits set for encoded mode and the validity bits set. + */ + SET_UNIPERIF_CTRL_OPERATION_PCM_DATA(player); + + /* + * If iec958 formatting is required for hdmi or spdif, then it must be + * enabled after the operation mode is set. If set prior to this, it + * will not take affect and hang the player. + */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + if (UNIPERIF_PLAYER_TYPE_IS_IEC958(player)) + SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player); + + /* Force channel status update (no update if clk disable) */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + SET_UNIPERIF_CONFIG_CHL_STS_UPDATE(player); + else + SET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(player); + + /* Update state to started */ + player->state = UNIPERIF_STATE_STARTED; + + return 0; +} + +static int uni_player_stop(struct uniperif *player) +{ + int ret; + + /* The player should not be in stopped state */ + if (player->state == UNIPERIF_STATE_STOPPED) { + dev_err(player->dev, "%s: invalid player state", __func__); + return -EINVAL; + } + + /* Turn the player off */ + SET_UNIPERIF_CTRL_OPERATION_OFF(player); + + /* Soft reset the player */ + SET_UNIPERIF_SOFT_RST_SOFT_RST(player); + + ret = reset_player(player); + if (ret < 0) + return ret; + + /* Disable interrupts */ + SET_UNIPERIF_ITM_BCLR(player, GET_UNIPERIF_ITM(player)); + disable_irq_nosync(player->irq); + + /* Disable clock */ + clk_disable_unprepare(player->clk); + + /* Update state to stopped and return */ + player->state = UNIPERIF_STATE_STOPPED; + + /* Pinctrl: switch pinstate to sleep */ + ret = pinctrl_pm_select_sleep_state(player->dev); + if (ret) { + dev_err(player->dev, + "%s: failed to select sleep pinctrl state", + __func__); + return ret; + } + + return 0; +} + +static int uni_player_suspend(struct uniperif *player) +{ + /* The player should be in stopped state */ + if (player->state != UNIPERIF_STATE_STOPPED) { + dev_err(player->dev, "%s: invalid player state( %d)", + __func__, (int)player->state); + return -EBUSY; + } + + return 0; +} + +static int uni_player_resume(struct uniperif *player) +{ + int ret; + + /* Select the frequency synthesizer clock */ + if (player->clk_sel) { + ret = regmap_field_write(player->clk_sel, 1); + if (ret) { + dev_err(player->dev, + "%s: Failed to select freq synth clock", + __func__); + return ret; + } + } + + SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(player); + SET_UNIPERIF_CTRL_ROUNDING_OFF(player); + SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player); + SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player); + + return 0; +} + +static int uni_player_trigger(struct uniperif *player, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + return uni_player_start(player); + case SNDRV_PCM_TRIGGER_STOP: + return uni_player_stop(player); + case SNDRV_PCM_TRIGGER_SUSPEND: + return uni_player_suspend(player); + case SNDRV_PCM_TRIGGER_RESUME: + return uni_player_resume(player); + default: + return -EINVAL; + } +} + +static void uni_player_close(struct uniperif *player) +{ + if (player->state != UNIPERIF_STATE_STOPPED) + /* Stop the player */ + uni_player_stop(player); +} + +/* + * Platform driver routines + */ + +static int uni_player_parse_dt_clk_glue(struct platform_device *pdev, + struct uniperif *player) +{ + int bit_offset; + struct device_node *node = pdev->dev.of_node; + struct regmap *regmap; + + bit_offset = SYS_CFG_AUDI0_GLUE_PCM_CLKX + player->info->uni_num; + + regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); + + if (regmap) { + struct reg_field regfield = + REG_FIELD(SYS_CFG_AUDIO_GLUE, bit_offset, bit_offset); + + player->clk_sel = regmap_field_alloc(regmap, regfield); + } else { + dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n"); + return -EINVAL; + } + + return 0; +} + +static int uni_player_parse_dt(struct platform_device *pdev, + struct device_node *pnode, + struct uniperif *player) +{ + struct uniperif_info *info; + struct device *dev = &pdev->dev; + const char *mode; + + /* Allocate memory for the info structure */ + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!pnode) { + dev_err(dev, "%s: invalid pnode", __func__); + return -EINVAL; + } + + of_property_read_u32(pnode, "version", &info->ver); + + of_property_read_u32(pnode, "uniperiph-id", &info->uni_num); + + /* Read the device mode property */ + of_property_read_string(pnode, "mode", &mode); + + if (strcasecmp(mode, "hdmi") == 0) + info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_HDMI; + else if (strcasecmp(mode, "pcm") == 0) + info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_PCM; + else if (strcasecmp(mode, "spdif") == 0) + info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF; + else + info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_NONE; + + /* Save the info structure */ + player->info = info; + + /* Get the PCM_CLK_SEL bit from audio-glue-ctrl SoC register */ + if (uni_player_parse_dt_clk_glue(pdev, player)) + return -EINVAL; + + return 0; +} + +const struct uniperif_ops uni_player_ops = { + .close = uni_player_close, + .prepare = uni_player_prepare, + .trigger = uni_player_trigger +}; + +int uni_player_init(struct platform_device *pdev, struct device_node *node, + struct uniperif **uni_player, int idx) +{ + struct uniperif *player; + int ret = 0; + + player = devm_kzalloc(&pdev->dev, sizeof(*player), GFP_KERNEL); + + if (!player) + return -ENOMEM; + + player->dev = &pdev->dev; + player->state = UNIPERIF_STATE_STOPPED; + player->hw = &uni_player_pcm_hw; + player->ops = &uni_player_ops; + + ret = uni_player_parse_dt(pdev, node, player); + + if (ret < 0) { + dev_err(player->dev, "Failed to parse DeviceTree"); + return ret; + } + + /* Get uniperif resource */ + player->clk = of_clk_get(pdev->dev.of_node, idx); + + if (IS_ERR(player->clk)) { + ret = (int)PTR_ERR(player->clk); + dev_err(player->dev, "%s: ERROR: clk_get failed (%d)!\n", + __func__, ret); + return -EINVAL; + } + + /* Select the frequency synthesizer clock */ + if (player->clk_sel) { + ret = regmap_field_write(player->clk_sel, 1); + if (ret) { + dev_err(player->dev, + "%s: Failed to select freq synth clock", + __func__); + return ret; + } + } + + if (player->info->ver == SND_ST_UNIPERIF_VERSION_UNKNOWN) { + dev_err(player->dev, "Unknown uniperipheral version "); + return -EINVAL; + } + player->ver = player->info->ver; + + /* Underflow recovery is only supported on later ip revisions */ + if (player->ver >= SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + player->info->underflow_enabled = 1; + + /* Get resources */ + + player->mem_region = platform_get_resource(pdev, IORESOURCE_MEM, idx); + + if (!player->mem_region) { + dev_err(&pdev->dev, "Failed to get memory resource"); + return -ENODEV; + } + + player->base = devm_ioremap_resource(&pdev->dev, + player->mem_region); + + if (!player->base) { + dev_err(&pdev->dev, "Failed to ioremap memory region"); + return -ENXIO; + } + + player->fifo_phys_address = player->mem_region->start + + UNIPERIF_FIFO_DATA_OFFSET(player); + + player->irq = platform_get_irq(pdev, idx); + + if (player->irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ resource"); + return -ENXIO; + } + + ret = devm_request_irq(&pdev->dev, player->irq, + uni_player_irq_handler, IRQF_SHARED, + dev_name(&pdev->dev), player); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request IRQ"); + return -EBUSY; + } + + /* request_irq() enables the interrupt immediately; as it is + * lethal in concurrent audio environment, we want to have + * it disabled for most of the time... + */ + disable_irq(player->irq); + + player->clk_div = DEFAULT_OVERSAMPLING; + + *uni_player = player; + + /* Ensure that disabled by default */ + SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(player); + SET_UNIPERIF_CTRL_ROUNDING_OFF(player); + SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player); + SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player); + + if (UNIPERIF_PLAYER_TYPE_IS_IEC958(player)) { + /* Set default iec958 status bits */ + + /* Consumer, PCM, copyright, 2ch, mode 0 */ + player->stream_settings.iec958.status[0] = 0x00; + /* Broadcast reception category */ + player->stream_settings.iec958.status[1] = + IEC958_AES1_CON_GENERAL; + /* Do not take into account source or channel number */ + player->stream_settings.iec958.status[2] = + IEC958_AES2_CON_SOURCE_UNSPEC; + /* Sampling frequency not indicated */ + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_NOTID; + /* Max sample word 24-bit, sample word length not indicated */ + player->stream_settings.iec958.status[4] = + IEC958_AES4_CON_MAX_WORDLEN_24 | + IEC958_AES4_CON_WORDLEN_24_20; + } + + return 0; +} +EXPORT_SYMBOL_GPL(uni_player_init); + +int uni_player_remove(struct platform_device *pdev) +{ + struct uniperif *player = platform_get_drvdata(pdev); + + if (player->clk) + clk_put(player->clk); + + return 0; +} +EXPORT_SYMBOL_GPL(uni_player_remove);
On Mon, May 18, 2015 at 02:12:50PM +0200, Arnaud Pouliquen wrote:
Add code to manage Uniperipheral player IP instances. These DAIs are dedicated to playback and support I2S and IEC mode.
There's some small things below but the main thing is that I'm still unclear about the driver internal abstraction layers. Perhaps that will become obvious later in the series...
+struct uniperif_ops {
- int (*open)(struct uniperif *player);
- void (*close)(struct uniperif *player);
- int (*prepare)(struct uniperif *player,
struct snd_pcm_runtime *runtime);
- int (*trigger)(struct uniperif *player, int cmd);
+};
These ops still look suspiciously like the ALSA level ops which suggests a driver internal abstraction layer. Why is that happening?
+static irqreturn_t uni_player_irq_handler(int irq, void *dev_id) +{
- irqreturn_t ret = IRQ_NONE;
- struct uniperif *player = dev_id;
- unsigned int status;
- unsigned int tmp;
- if (player->state == UNIPERIF_STATE_STOPPED) {
/* Unexpected IRQ: do nothing */
dev_warn(player->dev, "unexpected IRQ");
return IRQ_HANDLED;
- }
Just return IRQ_NONE here - as well as coping with any sharing of interrupts that ends up happening in the future one of the reasons for reporting if we handled things or not is to allow us to reuse the support that genirq has for handling things like spurious interrupts. In this case if something goes seriously wrong it'll complain and eventually shut down the interrupt while this will result in a continual spam to the console with no error handling.
- /* Error on unhandled interrupt */
- if (ret != IRQ_HANDLED)
dev_err(player->dev, "IRQ status : %#x", status);
Let genirq worry about this.
+static void uni_player_set_channel_status(struct uniperif *player,
struct snd_pcm_runtime *runtime)
+{
- int n;
- unsigned int status;
- /*
* Some AVRs and TVs require the channel status to contain a correct
* sampling frequency. If no sample rate is already specified, then
* set one.
*/
Please take a look at Russell King's patch "sound/core: add IEC958 channel status helper" (currently in review though I'd expect it to hit -next the next time it's built) - it does this (or most of it anyway) in a factored out function that can be shared between drivers.
- /* No sample rates below 32kHz are supported for iec958 */
- if (runtime->rate < MIN_IEC958_SAMPLE_RATE) {
dev_err(player->dev, "Invalid rate (%d)", runtime->rate);
return -EINVAL;
- }
The driver should be imposing constraints which prevent this happening either by using a different driver structure or by registering new constraints from code on open.
- /* Set the number of channels (maximum supported by spdif is 2) */
- if (UNIPERIF_PLAYER_TYPE_IS_SPDIF(player) &&
(runtime->channels != 2)) {
dev_err(player->dev, "invalid nb of channels");
return -EINVAL;
- }
Similarly here.
- /* Calculate number of empty cells available before asserting DREQ */
- if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size;
- else
/*
* Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0
* FDMA_TRIGGER_LIMIT also controls when the state switches
* from OFF or STANDBY to AUDIO DATA.
*/
trigger_limit = transfer_size;
Please use braces for multi-line branches of if statements even if the extra lines are just comments, it makes things easier to read.
- switch (player->daifmt & SND_SOC_DAIFMT_INV_MASK) {
- case SND_SOC_DAIFMT_IB_IF:
- case SND_SOC_DAIFMT_NB_IF:
SET_UNIPERIF_I2S_FMT_LR_POL_HIG(player);
break;
I didn't spot the code where the inverted and normal bit clock polarities are handled?
+const struct uniperif_ops uni_player_ops = {
- .close = uni_player_close,
- .prepare = uni_player_prepare,
- .trigger = uni_player_trigger
+};
Again a driver internal abstraction layer...
- player->mem_region = platform_get_resource(pdev, IORESOURCE_MEM, idx);
- if (!player->mem_region) {
dev_err(&pdev->dev, "Failed to get memory resource");
return -ENODEV;
- }
- player->base = devm_ioremap_resource(&pdev->dev,
devm_ioremap_resource() will do the null check for you.
- ret = devm_request_irq(&pdev->dev, player->irq,
uni_player_irq_handler, IRQF_SHARED,
dev_name(&pdev->dev), player);
- if (ret < 0) {
dev_err(&pdev->dev, "Failed to request IRQ");
return -EBUSY;
- }
Use the error code you got, don't ignore it.
- /* request_irq() enables the interrupt immediately; as it is
* lethal in concurrent audio environment, we want to have
* it disabled for most of the time...
*/
- disable_irq(player->irq);
That's a bit worrying... can you expand on what problems you see if the interrupt is left enabled?
+int uni_player_remove(struct platform_device *pdev) +{
- struct uniperif *player = platform_get_drvdata(pdev);
- if (player->clk)
clk_put(player->clk);
- return 0;
+} +EXPORT_SYMBOL_GPL(uni_player_remove);
What's this about - if nothing else shouldn't we be using devm_clk_get()?
On 05/25/2015 02:37 PM, Mark Brown wrote:
On Mon, May 18, 2015 at 02:12:50PM +0200, Arnaud Pouliquen wrote:
Add code to manage Uniperipheral player IP instances. These DAIs are dedicated to playback and support I2S and IEC mode.
There's some small things below but the main thing is that I'm still unclear about the driver internal abstraction layers. Perhaps that will become obvious later in the series...
Abstraction layers are used to have same interface for uniperiph player and uniperiph reader DAIs. I think there are two points to discuss before i rework it:
1) Are you still ok that i keep 2 separate files for player and reader IP management? Just for remind here is discussion started on V1 patch set:
Arnaud: I splitted reader and player code,because it is 2 different IPs with some specific features and behavior ( clock, master/slave mode, IEC, standby ...). From my point of view is is more clear like this, but It is feasible to merge both code adding conditions on direction in most functions. Please tell me what you prefer. I case of merge i suppose that the best is to not define uniperif_ops struct but externalize functions...
Marc: That's reasonable, we just shouldn't be seeing large chunks of obvious code duplication.
First I just see that i proposed to remove uniperif_ops, I forgot...my apologize Then to complete discussion, I'm aware that code seems similar regarding patches 3 and 4 with some duplications. but patches 8 & 9 add controls on uniperiph player, that make add differences. I also plan to re-implement the standby mode available only on uniperif player IP (after driver acceptation). This specific mode will allows to to keep player on to send silence on bus when PCM stream is stopped.
2) If both files are kept what could be done is to: - suppress sti_platform_dai structure and call directly functions - use directly snd_soc_dai_ops in to replace uniperif_ops and keep common functions in sti_platform.c This could be ok for you?
+static void uni_player_set_channel_status(struct uniperif *player,
struct snd_pcm_runtime *runtime)
+{
- int n;
- unsigned int status;
- /*
* Some AVRs and TVs require the channel status to contain a correct
* sampling frequency. If no sample rate is already specified, then
* set one.
*/
Please take a look at Russell King's patch "sound/core: add IEC958 channel status helper" (currently in review though I'd expect it to hit -next the next time it's built) - it does this (or most of it anyway) in a factored out function that can be shared between drivers.
I will check Russell King's code, and try to use helper functions.
- switch (player->daifmt & SND_SOC_DAIFMT_INV_MASK) {
- case SND_SOC_DAIFMT_IB_IF:
- case SND_SOC_DAIFMT_NB_IF:
SET_UNIPERIF_I2S_FMT_LR_POL_HIG(player);
break;
I didn't spot the code where the inverted and normal bit clock polarities are handled?
yes missing, i will rework this... Just a question what do you consider as normal clock? data output on rising edge?
Add code to manage Uniperipheral reader IP instances. These DAIs are dedicated to capture and support I2S and IEC mode.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/sti/uniperif.h | 4 + sound/soc/sti/uniperif_reader.c | 459 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 463 insertions(+) create mode 100644 sound/soc/sti/uniperif_reader.c
diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index 1e67489..c18584c 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1217,4 +1217,8 @@ int uni_player_init(struct platform_device *pdev, struct device_node *node, struct uniperif **uni_player, int idx); int uni_player_remove(struct platform_device *pdev);
+/* uniperiph reader */ +int uni_reader_init(struct platform_device *pdev, struct device_node *node, + struct uniperif **uni_reader, int idx); +int uni_reader_remove(struct platform_device *pdev); #endif diff --git a/sound/soc/sti/uniperif_reader.c b/sound/soc/sti/uniperif_reader.c new file mode 100644 index 0000000..eaae613 --- /dev/null +++ b/sound/soc/sti/uniperif_reader.c @@ -0,0 +1,459 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen arnaud.pouliquen@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include <sound/soc.h> + +#include "uniperif.h" + +/* + * Note: snd_pcm_hardware is linked to DMA controller but is declared here to + * integrate unireader capability in term of rate and supported channels + */ +const struct snd_pcm_hardware uni_reader_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 96000, + + .channels_min = 2, + .channels_max = 8, + + .periods_min = 2, + .periods_max = 48, + + .period_bytes_min = 128, + .period_bytes_max = 64 * PAGE_SIZE, + .buffer_bytes_max = 256 * PAGE_SIZE +}; + +/* + * uni_reader_irq_handler + * In case of error audio stream is stopped; stop action is protected via PCM + * stream lock for avoiding the race with trigger callback. + */ +static irqreturn_t uni_reader_irq_handler(int irq, void *dev_id) +{ + irqreturn_t ret = IRQ_NONE; + struct uniperif *reader = dev_id; + unsigned int status; + + if (reader->state == UNIPERIF_STATE_STOPPED) { + /* Unexpected IRQ: do nothing */ + dev_warn(reader->dev, "unexpected IRQ "); + return IRQ_HANDLED; + } + + /* Get interrupt status & clear them immediately */ + status = GET_UNIPERIF_ITS(reader); + SET_UNIPERIF_ITS_BCLR(reader, status); + + /* Check for fifo overflow error */ + if (unlikely(status & UNIPERIF_ITS_FIFO_ERROR_MASK(reader))) { + dev_err(reader->dev, "FIFO error detected"); + + snd_pcm_stream_lock(reader->substream); + snd_pcm_stop(reader->substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock(reader->substream); + + return IRQ_HANDLED; + } + + return ret; +} + +static int uni_reader_prepare_pcm(struct uniperif *reader, + struct snd_pcm_runtime *runtime) +{ + int slot_width; + + if (!runtime) { + dev_err(reader->dev, "%s: invalid pointer(s)", __func__); + return -EINVAL; + } + + /* Force slot width to 32 in I2S mode */ + if ((reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) + == SND_SOC_DAIFMT_I2S) { + slot_width = 32; + } else { + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + slot_width = 16; + break; + default: + slot_width = 32; + break; + } + } + + /* Number of bits per subframe (i.e one channel sample) on input. */ + switch (slot_width) { + case 32: + SET_UNIPERIF_I2S_FMT_NBIT_32(reader); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(reader); + break; + case 16: + SET_UNIPERIF_I2S_FMT_NBIT_16(reader); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(reader); + break; + default: + dev_err(reader->dev, "subframe format not supported"); + return -EINVAL; + } + + /* Configure data memory format */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* One data word contains two samples */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_16(reader); + break; + + case SNDRV_PCM_FORMAT_S32_LE: + /* + * Actually "16 bits/0 bits" means "32/28/24/20/18/16 bits + * on the MSB then zeros (if less than 32 bytes)"... + */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(reader); + break; + + default: + dev_err(reader->dev, "format not supported"); + return -EINVAL; + } + + SET_UNIPERIF_CONFIG_MSTR_CLKEDGE_RISING(reader); + SET_UNIPERIF_CTRL_READER_OUT_SEL_IN_MEM(reader); + + /* + * Serial audio interface format - for detailed explanation + * see ie.: + * http: www.cirrus.com/en/pubs/appNote/AN282REV1.pdf + */ + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); + + /* Data clocking (changing) on the rising edge */ + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); + + /* Number of channels must be even */ + + if ((runtime->channels % 2) || (runtime->channels < 2) || + (runtime->channels > 10)) { + dev_err(reader->dev, "%s: invalid nb of channels", __func__); + return -EINVAL; + } + + SET_UNIPERIF_I2S_FMT_NUM_CH(reader, runtime->channels / 2); + + return 0; +} + +static int uni_reader_prepare(struct uniperif *reader, + struct snd_pcm_runtime *runtime) +{ + int transfer_size, trigger_limit; + int ret; + int count = 10; + + /* The reader should be stopped */ + if (reader->state != UNIPERIF_STATE_STOPPED) { + dev_err(reader->dev, "%s: invalid reader state %d", __func__, + reader->state); + return -EINVAL; + } + + /* Calculate transfer size (in fifo cells and bytes) for frame count */ + transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + + /* Calculate number of empty cells available before asserting DREQ */ + if (reader->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size; + else + /* + * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 + * FDMA_TRIGGER_LIMIT also controls when the state switches + * from OFF or STANDBY to AUDIO DATA. + */ + trigger_limit = transfer_size; + + /* Trigger limit must be an even number */ + if ((!trigger_limit % 2) || + (trigger_limit != 1 && transfer_size % 2) || + (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(reader))) { + dev_err(reader->dev, "invalid trigger limit %d", trigger_limit); + return -EINVAL; + } + + SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(reader, trigger_limit); + + ret = uni_reader_prepare_pcm(reader, runtime); + + switch (reader->daifmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + case SND_SOC_DAIFMT_NB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); + break; + default: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); + } + + switch (reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); + SET_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(reader); + break; + case SND_SOC_DAIFMT_LEFT_J: + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); + SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(reader); + break; + case SND_SOC_DAIFMT_RIGHT_J: + SET_UNIPERIF_I2S_FMT_ALIGN_RIGHT(reader); + SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(reader); + break; + default: + dev_err(reader->dev, "format not supported"); + return -EINVAL; + } + + /* Clear any pending interrupts */ + SET_UNIPERIF_ITS_BCLR(reader, GET_UNIPERIF_ITS(reader)); + + SET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(reader, 0); + + /* Set the interrupt mask */ + SET_UNIPERIF_ITM_BSET_DMA_ERROR(reader); + SET_UNIPERIF_ITM_BSET_FIFO_ERROR(reader); + SET_UNIPERIF_ITM_BSET_MEM_BLK_READ(reader); + + /* Enable underflow recovery interrupts */ + if (reader->info->underflow_enabled) { + SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE(reader); + SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED(reader); + } + + /* Reset uniperipheral reader */ + SET_UNIPERIF_SOFT_RST_SOFT_RST(reader); + + while (GET_UNIPERIF_SOFT_RST_SOFT_RST(reader)) { + udelay(5); + count--; + } + if (!count) { + dev_err(reader->dev, "Failed to reset uniperif"); + return -EIO; + } + + return 0; +} + +static int uni_reader_start(struct uniperif *reader) +{ + /* The reader should be stopped */ + if (reader->state != UNIPERIF_STATE_STOPPED) { + dev_err(reader->dev, "%s: invalid reader state", __func__); + return -EINVAL; + } + + /* Enable reader interrupts (and clear possible stalled ones) */ + enable_irq(reader->irq); + SET_UNIPERIF_ITS_BCLR_FIFO_ERROR(reader); + SET_UNIPERIF_ITM_BSET_FIFO_ERROR(reader); + + /* Launch the reader */ + SET_UNIPERIF_CTRL_OPERATION_PCM_DATA(reader); + + /* Update state to started */ + reader->state = UNIPERIF_STATE_STARTED; + return 0; +} + +static int uni_reader_stop(struct uniperif *reader) +{ + /* The reader should not be in stopped state */ + if (reader->state == UNIPERIF_STATE_STOPPED) { + dev_err(reader->dev, "%s: invalid reader state", __func__); + return -EINVAL; + } + + /* Turn the reader off */ + SET_UNIPERIF_CTRL_OPERATION_OFF(reader); + + /* Disable interrupts */ + SET_UNIPERIF_ITM_BCLR(reader, GET_UNIPERIF_ITM(reader)); + disable_irq_nosync(reader->irq); + + /* Update state to stopped and return */ + reader->state = UNIPERIF_STATE_STOPPED; + + return 0; +} + +static int uni_reader_suspend(struct uniperif *reader) +{ + /* pinctrl: switch pinstate to sleep */ + return pinctrl_pm_select_sleep_state(reader->dev); +} + +static int uni_reader_resume(struct uniperif *reader) +{ + /* Ensure that disable by default */ + SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(reader); + SET_UNIPERIF_CTRL_ROUNDING_OFF(reader); + SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(reader); + SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(reader); + + /* Pinctrl: switch pinstate to default */ + return pinctrl_pm_select_default_state(reader->dev); +} + +static int uni_reader_trigger(struct uniperif *reader, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + return uni_reader_start(reader); + case SNDRV_PCM_TRIGGER_STOP: + return uni_reader_stop(reader); + case SNDRV_PCM_TRIGGER_SUSPEND: + return uni_reader_suspend(reader); + case SNDRV_PCM_TRIGGER_RESUME: + return uni_reader_resume(reader); + default: + return -EINVAL; + } +} + +static void uni_reader_close(struct uniperif *reader) +{ + if (reader->state != UNIPERIF_STATE_STOPPED) { + /* Stop the reader */ + uni_reader_stop(reader); + } +} + +/* + * Platform driver routines + */ + +static int uni_reader_parse_dt(struct platform_device *pdev, + struct device_node *pnode, + struct uniperif *reader) +{ + struct uniperif_info *info; + + /* Allocate memory for the info structure */ + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!pnode) { + dev_err(&pdev->dev, "%s: invalid pnode", __func__); + return -EINVAL; + } + of_property_read_u32(pnode, "version", &info->ver); + + /* Save the info structure */ + reader->info = info; + + return 0; +} + +const struct uniperif_ops uni_reader_ops = { + .close = uni_reader_close, + .prepare = uni_reader_prepare, + .trigger = uni_reader_trigger +}; + +int uni_reader_init(struct platform_device *pdev, struct device_node *node, + struct uniperif **uni_reader, int idx) +{ + struct uniperif *reader; + int ret = 0; + + reader = devm_kzalloc(&pdev->dev, sizeof(*reader), GFP_KERNEL); + if (!reader) + return -ENOMEM; + + reader->dev = &pdev->dev; + reader->state = UNIPERIF_STATE_STOPPED; + reader->hw = &uni_reader_pcm_hw; + reader->ops = &uni_reader_ops; + ret = uni_reader_parse_dt(pdev, node, reader); + + if (ret < 0) { + dev_err(reader->dev, "Failed to parse DeviceTree"); + return ret; + } + + /* Get resources */ + + reader->mem_region = platform_get_resource(pdev, IORESOURCE_MEM, idx); + + if (!reader->mem_region) { + dev_err(&pdev->dev, "Failed to get memory resource"); + return -ENODEV; + } + + reader->base = devm_ioremap_resource(&pdev->dev, + reader->mem_region); + + if (!reader->base) { + dev_err(&pdev->dev, "Failed to ioremap memory region"); + return -ENXIO; + } + + reader->fifo_phys_address = reader->mem_region->start + + UNIPERIF_FIFO_DATA_OFFSET(reader); + + reader->irq = platform_get_irq(pdev, idx); + + if (reader->irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ resource"); + return -ENXIO; + } + + ret = devm_request_irq(&pdev->dev, reader->irq, + uni_reader_irq_handler, IRQF_SHARED, + dev_name(&pdev->dev), reader); + + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request IRQ"); + return -EBUSY; + } + + /* + * request_irq() enables the interrupt immediately; as it is + * lethal in concurrent audio environment, we want to have + * it disabled for most of the time... + */ + disable_irq(reader->irq); + + *uni_reader = reader; + + /* Ensure that disable by default */ + SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(reader); + SET_UNIPERIF_CTRL_ROUNDING_OFF(reader); + SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(reader); + SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(reader); + + return 0; +} +EXPORT_SYMBOL_GPL(uni_reader_init); + +int uni_reader_remove(struct platform_device *pdev) +{ + /* Nothing to do */ + return 0; +} +EXPORT_SYMBOL_GPL(uni_reader_remove);
On Mon, May 18, 2015 at 02:12:51PM +0200, Arnaud Pouliquen wrote:
Add code to manage Uniperipheral reader IP instances. These DAIs are dedicated to capture and support I2S and IEC mode.
Basically the same things apply here as apply to the playback patch. This is still making me think there should be more code sharing...
Asoc Platform driver that manages uniperipheral DAIs and associated PCM stream.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/sti/sti_platform.c | 642 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 642 insertions(+) create mode 100644 sound/soc/sti/sti_platform.c
diff --git a/sound/soc/sti/sti_platform.c b/sound/soc/sti/sti_platform.c new file mode 100644 index 0000000..66466e8 --- /dev/null +++ b/sound/soc/sti/sti_platform.c @@ -0,0 +1,642 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen arnaud.pouliquen@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/module.h> + +#include <sound/dmaengine_pcm.h> + +#include "uniperif.h" + +/* + * Max buffering use case identified: + * 3 periods of 2048 frames @ 192kHz, 32 bits, 10 ch + */ +#define STI_PLATFORM_PERIODS_BYTES_MAX 196608 +#define STI_PLATFORM_PERIODS_MAX 3 +#define STI_PLATFORM_BUFFER_BYTES_MAX (STI_PLATFORM_PERIODS_BYTES_MAX * \ + STI_PLATFORM_PERIODS_MAX) + +#define priv_to_dai_data(priv, i) ((priv)->dai_data + i) + +struct sti_platform_dai { + int stream; + int (*init)(struct platform_device *pdev, struct device_node *node, + struct uniperif **uni, int idx); + int (*remove)(struct platform_device *pdev); + struct uniperif *uni; + struct snd_dmaengine_dai_dma_data dma_data; +}; + +struct sti_platform_dai uni_player = { + .stream = SNDRV_PCM_STREAM_PLAYBACK, + .init = uni_player_init, + .remove = uni_player_remove, +}; + +struct sti_platform_dai uni_reader = { + .stream = SNDRV_PCM_STREAM_CAPTURE, + .init = uni_reader_init, + .remove = uni_reader_remove, +}; + +struct sti_platform_data { + struct platform_device *pdev; + struct snd_soc_dai_driver *dai; + struct sti_platform_dai dai_data[]; /* dynamically allocated */ +}; + +struct sti_pcm_dma_params { + struct dma_chan *dma_channel; + dma_cookie_t dma_cookie; + struct dma_slave_config slave_config; +}; + +/* + * sti_platform_dai_create_ctrl + * This function is used to create Ctrl associated to DAI but also pcm device. + * Request is done by front end to associate ctrl with pcm device id + */ +int sti_platform_dai_create_ctrl(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + struct uniperif *uni = dai_data->uni; + struct snd_kcontrol_new *ctrl; + int i, ret = 0; + + for (i = 0; i < uni->num_ctrls; i++) { + ctrl = &uni->snd_ctrls[i]; + ctrl->index = rtd->pcm->device; + ctrl->device = rtd->pcm->device; + + ret = snd_ctl_add(dai->component->card->snd_card, + snd_ctl_new1(ctrl, uni)); + if (ret < 0) { + dev_err(dai->dev, "%s: Failed to add %s: %d\n", + __func__, ctrl->name, ret); + return ret; + } + } + + return 0; +} + +/* + * DAI + */ + +static int sti_platform_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + struct uniperif *uni = dai_data->uni; + + if (uni->ops->open) + return uni->ops->open(uni); + return 0; +} + +static void sti_platform_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + struct uniperif *uni = dai_data->uni; + + if (uni->ops->close) + uni->ops->close(uni); +} + +static int sti_platform_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + struct uniperif *uni = dai_data->uni; + + if (uni->ops->prepare) + return uni->ops->prepare(uni, substream->runtime); + + return 0; +} + +static int sti_platform_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + struct uniperif *uni = dai_data->uni; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (uni->ops->trigger) + return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_START); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (uni->ops->trigger) + return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_STOP); + default: + return -EINVAL; + } + + return 0; +} + +static int sti_platform_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + struct snd_dmaengine_dai_dma_data *dma_data = &dai_data->dma_data; + int transfer_size; + + transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES; + + dma_data = snd_soc_dai_get_dma_data(dai, substream); + dma_data->maxburst = transfer_size; + + return 0; +} + +static int sti_platform_dai_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + + dai_data->uni->daifmt = fmt; + return 0; +} + +static int sti_platform_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, + int div) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + + dai_data->uni->clk_div = div; + + return 0; +} + +static int sti_platform_dai_suspend(struct snd_soc_dai *dai) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + struct uniperif *uni = dai_data->uni; + + if (uni->ops->trigger) + return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_SUSPEND); + + return 0; +} + +static int sti_platform_dai_resume(struct snd_soc_dai *dai) +{ + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id); + struct uniperif *uni = dai_data->uni; + + if (uni->ops->trigger) + return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_RESUME); + + return 0; +} + +static int sti_platform_dai_probe(struct snd_soc_dai *dai) +{ + struct sti_platform_data *ptf_data = snd_soc_dai_get_drvdata(dai); + + if (ptf_data->dai_data[dai->id].stream == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = &ptf_data->dai_data[dai->id].dma_data; + else + dai->capture_dma_data = &ptf_data->dai_data[dai->id].dma_data; + + return 0; +} + +static struct snd_soc_dai_ops sti_platform_dai_ops[] = { + { + .startup = sti_platform_dai_startup, + .shutdown = sti_platform_dai_shutdown, + .prepare = sti_platform_dai_prepare, + .trigger = sti_platform_dai_trigger, + .hw_params = sti_platform_dai_hw_params, + .set_fmt = sti_platform_dai_set_fmt, + .set_clkdiv = sti_platform_dai_set_clkdiv, + } +}; + +static const struct snd_soc_dai_driver sti_platform_dai_template = { + .probe = sti_platform_dai_probe, + .ops = sti_platform_dai_ops, + .suspend = sti_platform_dai_suspend, + .resume = sti_platform_dai_resume +}; + +static const struct snd_soc_component_driver sti_platform_dai_component = { + .name = "sti_cpu_dai", +}; + +/* + * PCM + */ + +static void sti_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + + snd_pcm_period_elapsed(substream); +} + +static int sti_pcm_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sti_pcm_dma_params *params; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, + rtd->cpu_dai->id); + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + ret = snd_soc_set_runtime_hwparams(substream, dai_data->uni->hw); + if (ret < 0) { + dev_err(rtd->dev, "error on FE hw_constraint\n"); + return ret; + } + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(rtd->dev, "Error: pcm hw constraints failed (%d)\n", + ret); + return ret; + } + + /* update private data */ + runtime->private_data = params; + + return ret; +} + +static int sti_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sti_pcm_dma_params *dma_params = runtime->private_data; + int size, ret = 0; + + size = params_buffer_bytes(params); + + /* Use PCM Lib */ + ret = snd_pcm_lib_malloc_pages(substream, size); + if (ret < 0) { + dev_err(rtd->dev, "Can't allocate pages!\n"); + return -ENOMEM; + } + + return snd_dmaengine_pcm_prepare_slave_config(substream, params, + &dma_params->slave_config); +} + +static int sti_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sti_pcm_dma_params *params = runtime->private_data; + struct sti_platform_data *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct sti_platform_dai *dai_data = priv_to_dai_data(priv, + rtd->cpu_dai->id); + int ret; + char prop[5]; + + if (!params->dma_channel) { + if (dai_data->stream == SNDRV_PCM_STREAM_PLAYBACK) + snprintf(prop, sizeof(prop), "tx-%d", rtd->cpu_dai->id); + else + snprintf(prop, sizeof(prop), "rx-%d", rtd->cpu_dai->id); + + params->dma_channel = + dma_request_slave_channel(rtd->platform->dev, prop); + if (!params->dma_channel) { + dev_err(rtd->dev, "Failed to request DMA channel"); + return -ENODEV; + } + } + ret = dmaengine_slave_config(params->dma_channel, + ¶ms->slave_config); + if (ret) + dev_err(rtd->dev, "Failed to configure DMA channel"); + + return ret; +} + +static int sti_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sti_pcm_dma_params *params = runtime->private_data; + enum dma_transfer_direction direction; + struct dma_async_tx_descriptor *desc; + unsigned long flags = DMA_CTRL_ACK; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + direction = snd_pcm_substream_to_dma_direction(substream); + + desc = dmaengine_prep_dma_cyclic( + params->dma_channel, runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + direction, flags); + if (!desc) { + dev_err(rtd->dev, "Failed to prepare DMA descriptor"); + return -ENOMEM; + } + + /* Set the dma callback */ + desc->callback = sti_dma_complete; + desc->callback_param = substream; + + /* Submit dma descriptor */ + params->dma_cookie = dmaengine_submit(desc); + dma_async_issue_pending(params->dma_channel); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (params->dma_channel) + ret = dmaengine_terminate_all(params->dma_channel); + break; + default: + dev_err(rtd->dev, "%s: ERROR: Invalid command in pcm trigger!\n", + __func__); + return -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t sti_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sti_pcm_dma_params *prtd = runtime->private_data; + struct dma_tx_state state; + enum dma_status status; + unsigned int buf_size; + unsigned int pos = 0; + + status = dmaengine_tx_status(prtd->dma_channel, prtd->dma_cookie, + &state); + if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) { + buf_size = snd_pcm_lib_buffer_bytes(substream); + if (state.residue > 0 && state.residue <= buf_size) + pos = buf_size - state.residue; + } + + return bytes_to_frames(substream->runtime, pos); +} + +static int sti_pcm_close(struct snd_pcm_substream *substream) +{ + struct sti_pcm_dma_params *params; + + params = substream->runtime->private_data; + if (params->dma_channel) { + dma_release_channel(params->dma_channel); + params->dma_channel = NULL; + } + kfree(params); + + return 0; +} + +int sti_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + ret = sti_platform_dai_create_ctrl(rtd); + if (ret < 0) + return ret; + else + return snd_pcm_lib_preallocate_pages_for_all( + pcm, SNDRV_DMA_TYPE_DEV, card->dev, + STI_PLATFORM_BUFFER_BYTES_MAX, + STI_PLATFORM_BUFFER_BYTES_MAX); +} + +static struct snd_pcm_ops sti_pcm_ops = { + .open = sti_pcm_open, + .close = sti_pcm_close, + .hw_params = sti_pcm_hw_params, + .prepare = sti_pcm_prepare, + .hw_free = snd_pcm_lib_free_pages, + .trigger = sti_pcm_trigger, + .pointer = sti_pcm_pointer +}; + +static struct snd_soc_platform_driver sti_platform_platform = { + .ops = &sti_pcm_ops, + .pcm_new = sti_pcm_new, +}; + +static int sti_platform_cpu_dai_of(struct device_node *node, + struct sti_platform_data *priv, int idx) +{ + const char *str; + int ret, i; + struct device *dev = &priv->pdev->dev; + struct sti_platform_dai *dai_data = &priv->dai_data[idx]; + struct snd_soc_dai_driver *dai = &priv->dai[idx]; + struct snd_soc_pcm_stream *stream; + struct uniperif *uni; + struct { + char *name; + struct sti_platform_dai *val; + } of_fmt_table[] = { + { "rx", &uni_reader }, + { "tx", &uni_player }, + }; + + *dai = sti_platform_dai_template; + ret = of_property_read_string(node, "dai-name", &str); + if (ret < 0) { + dev_err(dev, "%s: dai name missing.\n", __func__); + return -EINVAL; + } + dai->name = str; + + ret = of_property_read_string_index(dev->of_node, "dma-names", + idx, &str); + if (ret == 0) { + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(of_fmt_table); i++) + if (strncmp(str, of_fmt_table[i].name, 2) == 0) { + *dai_data = *of_fmt_table[i].val; + ret = 0; + break; + } + } + if (ret < 0) { + dev_err(dev, "%s: invalid dai-type.\n", __func__); + return -EINVAL; + } + + /* Initialise uniperif */ + if (!dai_data->init) { + dev_err(dev, "%s: context data missing.\n", __func__); + return -EINVAL; + } + + ret = dai_data->init(priv->pdev, node, + &dai_data->uni, idx); + if (ret < 0) { + dev_err(dev, "%s: ERROR: Fail to init uniperif ( %d)!\n", + __func__, ret); + return ret; + } + + uni = dai_data->uni; + + if (dai_data->stream == SNDRV_PCM_STREAM_PLAYBACK) + stream = &dai->playback; + else + stream = &dai->capture; + + stream->stream_name = dai->name; + stream->channels_min = uni->hw->channels_min; + stream->channels_max = uni->hw->channels_max; + stream->rates = uni->hw->rates; + stream->formats = uni->hw->formats; + + /* DMA settings*/ + dai_data->dma_data.addr = uni->fifo_phys_address; + dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + return 0; +} + +static int sti_platform_engine_probe(struct platform_device *pdev) +{ + /* + * Driver can not use snd_dmaengine_pcm_register. + * Reason is that DMA needs to load a firmware. + * This firmware is loaded on request_channel from file system. + * So can not be done in probe while alsa card enumerated before + * file system is mounted + */ + + struct sti_platform_data *priv; + struct device_node *node = pdev->dev.of_node; + int num_dais, ret; + + /* Get the number of DAI links */ + if (node && of_get_child_by_name(node, "cpu-dai")) + num_dais = of_get_child_count(node); + else + num_dais = 1; + + /* Allocate the private data and the CPU_DAI array */ + priv = devm_kzalloc(&pdev->dev, + sizeof(*priv) + + sizeof(struct sti_platform_dai) * num_dais, + GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai) * num_dais, + GFP_KERNEL); + if (!priv->dai) + return -ENOMEM; + + priv->pdev = pdev; + + if (of_get_child_by_name(node, "cpu-dai")) { + struct device_node *np = NULL; + int i = 0; + + for_each_child_of_node(node, np) { + ret = sti_platform_cpu_dai_of(np, priv, i); + if (ret < 0) { + of_node_put(np); + return ret; + } + i++; + } + } + + dev_set_drvdata(&pdev->dev, priv); + + ret = snd_soc_register_component(&pdev->dev, + &sti_platform_dai_component, + priv->dai, num_dais); + if (ret < 0) + return ret; + + return snd_soc_register_platform(&pdev->dev, &sti_platform_platform); +} + +static int sti_platform_engine_remove(struct platform_device *pdev) +{ + struct sti_platform_data *priv = dev_get_drvdata(&pdev->dev); + struct sti_platform_dai *dai_data = priv->dai_data; + struct device_node *node = pdev->dev.of_node; + int num_dais, idx; + + /* Get the number of DAI links */ + if (node && of_get_child_by_name(node, "cpu-dai")) + num_dais = of_get_child_count(node); + else + num_dais = 1; + + for (idx = 0; idx < num_dais; idx++) { + dai_data->remove(pdev); + dai_data++; + } + + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static const struct of_device_id snd_soc_sti_match[] = { + { .compatible = "st,sti-audio-platform", }, + {}, +}; + +static struct platform_driver sti_platform_driver = { + .driver = { + .name = "sti-audio-platform", + .owner = THIS_MODULE, + .of_match_table = snd_soc_sti_match, + }, + .probe = sti_platform_engine_probe, + .remove = sti_platform_engine_remove +}; +module_platform_driver(sti_platform_driver); + +MODULE_DESCRIPTION("audio soc platform driver for STI platforms"); +MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_LICENSE("GPL v2");
On Mon, May 18, 2015 at 02:12:52PM +0200, Arnaud Pouliquen wrote:
Asoc Platform driver that manages uniperipheral DAIs and associated
ASoC.
+struct sti_platform_dai {
- int stream;
- int (*init)(struct platform_device *pdev, struct device_node *node,
struct uniperif **uni, int idx);
- int (*remove)(struct platform_device *pdev);
- struct uniperif *uni;
- struct snd_dmaengine_dai_dma_data dma_data;
+};
Again a driver internal abstraction layer...
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (uni->ops->trigger)
return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_START);
Coding style.
- return snd_dmaengine_pcm_prepare_slave_config(substream, params,
&dma_params->slave_config);
You're using dmaengine but at least one of the earlier patches supplied a snd_pcm_hardware - the dmaengine code should be able to figure out constraints from the dmaengine driver in current code, is that causing you problems?
+static int sti_pcm_trigger(struct snd_pcm_substream *substream,
int cmd)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct sti_pcm_dma_params *params = runtime->private_data;
- enum dma_transfer_direction direction;
- struct dma_async_tx_descriptor *desc;
- unsigned long flags = DMA_CTRL_ACK;
- int ret = 0;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- case SNDRV_PCM_TRIGGER_RESUME:
direction = snd_pcm_substream_to_dma_direction(substream);
desc = dmaengine_prep_dma_cyclic(
params->dma_channel, runtime->dma_addr,
snd_pcm_lib_buffer_bytes(substream),
snd_pcm_lib_period_bytes(substream),
direction, flags);
Can you use snd_dmaengine_pcm_trigger?
+static snd_pcm_uframes_t sti_pcm_pointer(struct snd_pcm_substream *substream) +{
snd_dmaengine_pcm_pointer()?
+static int sti_platform_engine_probe(struct platform_device *pdev) +{
- /*
* Driver can not use snd_dmaengine_pcm_register.
* Reason is that DMA needs to load a firmware.
* This firmware is loaded on request_channel from file system.
* So can not be done in probe while alsa card enumerated before
* file system is mounted
*/
This is not the only driver with similar needs, the Intel drivers use firmwares as well (all the DMA is done by the DSP which needs firmware) and even if it were we should be able to arrange for this to work rather than having to open code things. That might mean having the dmaengine driver requesting firmware with _nowait() and then waiting until the firmware appears before registering as a DMA controller for example. We may also be able to have the DMA engine only load the firmware when it's used rather than at probe (that might allow us to keep the DMA controller powered off for longer which would be a power win if possible).
It's not just restricted to audio either.
+static const struct of_device_id snd_soc_sti_match[] = {
- { .compatible = "st,sti-audio-platform", },
- {},
+};
This doesn't seem to represent actual hardware, it seems to be an abstraction layer between dmaengine and your DAI drivers. This is fine and normal but it means the driver shouldn't appear in DT. Take a look at how other drivers like Tegra or the Freescale ones handle this.
On 05/25/2015 02:50 PM, Mark Brown wrote:
On Mon, May 18, 2015 at 02:12:52PM +0200, Arnaud Pouliquen wrote:
- return snd_dmaengine_pcm_prepare_slave_config(substream, params,
&dma_params->slave_config);
You're using dmaengine but at least one of the earlier patches supplied a snd_pcm_hardware - the dmaengine code should be able to figure out constraints from the dmaengine driver in current code, is that causing you problems?
+static int sti_pcm_trigger(struct snd_pcm_substream *substream,
int cmd)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct sti_pcm_dma_params *params = runtime->private_data;
- enum dma_transfer_direction direction;
- struct dma_async_tx_descriptor *desc;
- unsigned long flags = DMA_CTRL_ACK;
- int ret = 0;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- case SNDRV_PCM_TRIGGER_RESUME:
direction = snd_pcm_substream_to_dma_direction(substream);
desc = dmaengine_prep_dma_cyclic(
params->dma_channel, runtime->dma_addr,
snd_pcm_lib_buffer_bytes(substream),
snd_pcm_lib_period_bytes(substream),
direction, flags);
Can you use snd_dmaengine_pcm_trigger?
+static snd_pcm_uframes_t sti_pcm_pointer(struct snd_pcm_substream *substream) +{
snd_dmaengine_pcm_pointer()?
As dmaengine_pcm_runtime_data is static, in this case i need to use also all the other ops function. i tried this before without success. But as i don't remember the reason , I will test it again...
+static int sti_platform_engine_probe(struct platform_device *pdev) +{
- /*
* Driver can not use snd_dmaengine_pcm_register.
* Reason is that DMA needs to load a firmware.
* This firmware is loaded on request_channel from file system.
* So can not be done in probe while alsa card enumerated before
* file system is mounted
*/
This is not the only driver with similar needs, the Intel drivers use firmwares as well (all the DMA is done by the DSP which needs firmware) and even if it were we should be able to arrange for this to work rather than having to open code things. That might mean having the dmaengine driver requesting firmware with _nowait() and then waiting until the firmware appears before registering as a DMA controller for example. We may also be able to have the DMA engine only load the firmware when it's used rather than at probe (that might allow us to keep the DMA controller powered off for longer which would be a power win if possible).
It's not just restricted to audio either.
Move dmaengine_pcm_request_chan from register to open could be a good compromise with (i hope) no impact on drivers that already use soc_generic_dma_engine. This is the implementation already proposed in of pcm_dmaengine.c Another issue i'm facing, is that i have one DMA channel per CPU_DAI instance. As simple card imposes to define CPU_DAIs in one platform device, i need to define specific DMA name per CPU_DAI. What is your recommendation on this point: Do you want that i use snd_dmaengine_pcm_register and propose soc_gebneric_dmaengine driver patches? or simply reuse pcm_dmaengine helper functions as much as possible.
+static const struct of_device_id snd_soc_sti_match[] = {
- { .compatible = "st,sti-audio-platform", },
- {},
+};
This doesn't seem to represent actual hardware, it seems to be an abstraction layer between dmaengine and your DAI drivers. This is fine and normal but it means the driver shouldn't appear in DT. Take a look at how other drivers like Tegra or the Freescale ones handle this.
No sure to full understand your comment... I checked some Tegra and Freescale drivers,look similar...with declaration in DT. Should i rename file and compatible name associated to DAI name as example sti_uniperif.c and .compatible = "st,sti-uniperif"
On 05/27/2015 10:48 AM, Arnaud Pouliquen wrote: [...]
+static int sti_platform_engine_probe(struct platform_device *pdev) +{
- /*
* Driver can not use snd_dmaengine_pcm_register.
* Reason is that DMA needs to load a firmware.
* This firmware is loaded on request_channel from file system.
* So can not be done in probe while alsa card enumerated before
* file system is mounted
*/
This is not the only driver with similar needs, the Intel drivers use firmwares as well (all the DMA is done by the DSP which needs firmware) and even if it were we should be able to arrange for this to work rather than having to open code things. That might mean having the dmaengine driver requesting firmware with _nowait() and then waiting until the firmware appears before registering as a DMA controller for example. We may also be able to have the DMA engine only load the firmware when it's used rather than at probe (that might allow us to keep the DMA controller powered off for longer which would be a power win if possible).
It's not just restricted to audio either.
Move dmaengine_pcm_request_chan from register to open could be a good compromise with (i hope) no impact on drivers that already use soc_generic_dma_engine. This is the implementation already proposed in of pcm_dmaengine.c Another issue i'm facing, is that i have one DMA channel per CPU_DAI instance. As simple card imposes to define CPU_DAIs in one platform device, i need to define specific DMA name per CPU_DAI. What is your recommendation on this point: Do you want that i use snd_dmaengine_pcm_register and propose soc_gebneric_dmaengine driver patches? or simply reuse pcm_dmaengine helper functions as much as possible.
You really want to request the DMA channels in the probe callback of the driver (and also use the the generic-dmaengine-pcm driver). This makes sure that you have proper support for probe deferral, etc. The DMA driver should probably not request the firmware inside the channel_request() callback. As Mark sad this will not only affect audio, but all usecases that involve DMAengine.
Btw. what is the DMA driver you are using, is it already upstream?
- Lars
Define the platform and codec drivers, and how to build them.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/sti/Kconfig | 11 +++++++++++ sound/soc/sti/Makefile | 5 +++++ 4 files changed, 18 insertions(+) create mode 100644 sound/soc/sti/Kconfig create mode 100644 sound/soc/sti/Makefile
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 3ba52da..791f2a6 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -53,6 +53,7 @@ source "sound/soc/samsung/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" +source "sound/soc/sti/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" source "sound/soc/ux500/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 974ba70..30b2d6d 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ +obj-$(CONFIG_SND_SOC) += sti/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ obj-$(CONFIG_SND_SOC) += ux500/ diff --git a/sound/soc/sti/Kconfig b/sound/soc/sti/Kconfig new file mode 100644 index 0000000..c29e219 --- /dev/null +++ b/sound/soc/sti/Kconfig @@ -0,0 +1,11 @@ +# +# STM SoC audio configuration +# +menuconfig SND_SOC_STI + tristate "SoC Audio support for STI System-On-Chip" + depends on SND_SOC + depends on ARCH_STI + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to enable ASoC-support for + any of the STI platforms (e.g. STIH416). diff --git a/sound/soc/sti/Makefile b/sound/soc/sti/Makefile new file mode 100644 index 0000000..de38cce --- /dev/null +++ b/sound/soc/sti/Makefile @@ -0,0 +1,5 @@ +# STI platform support +snd-soc-sti-objs := sti_platform.o uniperif_player.o uniperif_reader.o + +obj-$(CONFIG_SND_SOC_STI) += snd-soc-sti.o +
On Mon, May 18, 2015 at 02:12:53PM +0200, Arnaud Pouliquen wrote:
+menuconfig SND_SOC_STI
- tristate "SoC Audio support for STI System-On-Chip"
- depends on SND_SOC
- depends on ARCH_STI
ARCH_STI || COMPILE_TEST?
Codec part of the STi platform that support codec IPs. This first version does not support HDMI, but only DAC and SPDIF out.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sti-sas.c | 645 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/sti/Kconfig | 6 + 4 files changed, 657 insertions(+) create mode 100644 sound/soc/codecs/sti-sas.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 061c465..f3fe64e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -102,6 +102,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_STA350 if I2C select SND_SOC_STA529 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS + select SND_SOC_STI_SAS select SND_SOC_TAS2552 if I2C select SND_SOC_TAS5086 if I2C select SND_SOC_TFA9879 if I2C @@ -603,6 +604,9 @@ config SND_SOC_STA529 config SND_SOC_STAC9766 tristate
+config SND_SOC_STI_SAS + tristate + config SND_SOC_TAS2552 tristate "Texas Instruments TAS2552 Mono Audio amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index abe2d7e..249ef0d 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -105,6 +105,7 @@ snd-soc-sta32x-objs := sta32x.o snd-soc-sta350-objs := sta350.o snd-soc-sta529-objs := sta529.o snd-soc-stac9766-objs := stac9766.o +snd-soc-sti-sas-objs := sti-sas.o snd-soc-tas5086-objs := tas5086.o snd-soc-tfa9879-objs := tfa9879.o snd-soc-tlv320aic23-objs := tlv320aic23.o @@ -286,6 +287,7 @@ obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o obj-$(CONFIG_SND_SOC_STA350) += snd-soc-sta350.o obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o +obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o diff --git a/sound/soc/codecs/sti-sas.c b/sound/soc/codecs/sti-sas.c new file mode 100644 index 0000000..6cd4c1d --- /dev/null +++ b/sound/soc/codecs/sti-sas.c @@ -0,0 +1,645 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen arnaud.pouliquen@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/mfd/syscon.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +/* chipID supported */ +#define CHIPID_STIH416 0 +#define CHIPID_STIH407 1 + +/* DAC definitions */ + +/* stih416 DAC registers */ +/* sysconf 2517: Audio-DAC-Control */ +#define STIH416_AUDIO_DAC_CTRL 0x00000814 +/* sysconf 2519: Audio-Gue-Control */ +#define STIH416_AUDIO_GLUE_CTRL 0x0000081C + +/* stih407 DAC registers */ +/* sysconf 5041: Audio-Gue-Control */ +#define STIH407_AUDIO_GLUE_CTRL 0x000000A4 +/* sysconf 5042: Audio-DAC-Control */ +#define STIH407_AUDIO_DAC_CTRL 0x000000A8 + +enum { + STI_SAS_DAI_SPDIF_OUT, + STI_SAS_DAI_ANALOG_OUT, +}; + +enum { + BIPHASE_ENABLE, + BIPHASE_IDLE, + STI_SPDIF_MAX_RF +}; + +enum { + STIH416_DAC_MODE, + STIH416_DAC_NOT_STANDBY, + STIH416_DAC_SOFTMUTE, + STIH416_DAC_ANALOG_PWR_DW, + STIH416_DAC_ANALOG_NOT_PWR_DW_BG, + STIH416_DAC_MAX_RF +}; + +enum { + STIH407_DAC_SOFTMUTE, + STIH407_DAC_STANDBY_ANA, + STIH407_DAC_STANDBY, + STIH407_DAC_MAX_RF +}; + +/* DAC configuration fields */ +const struct reg_field sti_sas_dac_stih416[STIH416_DAC_MAX_RF] = { + /*STIH416_DAC_MODE*/ + REG_FIELD(STIH416_AUDIO_DAC_CTRL, 1, 2), + /*STIH416_DAC_NOT_STANDBY */ + REG_FIELD(STIH416_AUDIO_DAC_CTRL, 3, 3), + /*STIH416_DAC_SOFTMUTE*/ + REG_FIELD(STIH416_AUDIO_DAC_CTRL, 4, 4), + /*STIH416_DAC_ANALOG_PWR_DW*/ + REG_FIELD(STIH416_AUDIO_DAC_CTRL, 5, 5), + /*STIH416_DAC_ANALOG_NOT_PWR_DW_BG*/ + REG_FIELD(STIH416_AUDIO_DAC_CTRL, 6, 6), +}; + +const struct reg_field sti_sas_dac_stih407[STIH407_DAC_MAX_RF] = { + /*STIH407_DAC_SOFTMUTE*/ + REG_FIELD(STIH407_AUDIO_DAC_CTRL, 0, 0), + /*STIH407_DAC_STANDBY_ANA*/ + REG_FIELD(STIH407_AUDIO_DAC_CTRL, 1, 1), + /*STIH407_DAC_STANDBY */ + REG_FIELD(STIH407_AUDIO_DAC_CTRL, 2, 2), +}; + +/* SPDIF configuration fields */ +const struct reg_field sti_sas_spdif_stih416[STI_SPDIF_MAX_RF] = { + /*BIPHASE_ENABLE */ + REG_FIELD(STIH416_AUDIO_GLUE_CTRL, 6, 6), + /*BIPHASE_IDLE*/ + REG_FIELD(STIH416_AUDIO_GLUE_CTRL, 7, 7), +}; + +const struct reg_field sti_sas_spdif_stih407[STI_SPDIF_MAX_RF] = { + /*BIPHASE_ENABLE */ + REG_FIELD(STIH407_AUDIO_GLUE_CTRL, 6, 6), + /*BIPHASE_IDLE*/ + REG_FIELD(STIH407_AUDIO_GLUE_CTRL, 7, 7), +}; + +struct sti_dac_audio { + struct regmap *regmap; + struct regmap_field **field; + struct reset_control *rst; +}; + +struct sti_spdif_audio { + struct regmap *regmap; + struct regmap_field **field; +}; + +const int sti_sas_dai_clk_div[]; + +/* device data structure */ +struct sti_sas_dev_data { + const int chipid; /* IC version */ + const struct reg_field *dac; /* DAC register description*/ + const struct snd_soc_dai_ops *dac_ops; /* DAC function callbacks */ + const struct reg_field *spdif; /* SPDIFF register description */ + const struct snd_soc_dapm_widget *dapm_widgets; /* dapms declaration */ + const int num_dapm_widgets; /* dapms declaration */ +}; + +/* driver data structure */ +struct sti_sas_data { + struct device *dev; + const struct sti_sas_dev_data *dev_data; + struct sti_dac_audio dac; + struct sti_spdif_audio spdif; +}; + +static int sti_sas_map_sas_registers(struct sti_sas_data *data) +{ + struct sti_spdif_audio *spdif = &data->spdif; + const struct reg_field *spdif_reg = data->dev_data->spdif; + struct sti_dac_audio *dac = &data->dac; + const struct reg_field *dac_reg = data->dev_data->dac; + int i, max_rf; + + /* Allocate device structure */ + spdif->field = devm_kzalloc(data->dev, + sizeof(struct regmap_field *) * STI_SPDIF_MAX_RF, + GFP_KERNEL); + if (!spdif->field) + return -ENOMEM; + + if (data->dev_data->chipid == CHIPID_STIH407) + max_rf = STIH407_DAC_MAX_RF; + else + max_rf = STIH416_DAC_MAX_RF; + dac->field = devm_kzalloc(data->dev, + sizeof(struct regmap_field *) * max_rf, + GFP_KERNEL); + if (!dac->field) + return -ENOMEM; + + /* Get spdif regmap fields */ + for (i = 0; i < STI_SPDIF_MAX_RF; i++) + spdif->field[i] = regmap_field_alloc(spdif->regmap, + spdif_reg[i]); + /* Get DAC regmap fields */ + for (i = 0; i < max_rf; i++) + dac->field[i] = regmap_field_alloc(dac->regmap, dac_reg[i]); + + return 0; +} + +static int sti_sas_init_sas_registers(struct device *dev, + struct sti_sas_data *data) +{ + struct sti_spdif_audio *spdif = &data->spdif; + struct sti_dac_audio *dac = &data->dac; + int ret; + /* + * DAC and SPDIF are activated by default + * put them in IDLE to save power + */ + + /* Initialise bi-phase formatter to disabled */ + ret = regmap_field_write(spdif->field[BIPHASE_ENABLE], 0); + + if (!ret) + /* Initialise bi-phase formatter idle value to 0 */ + ret = regmap_field_write(spdif->field[BIPHASE_IDLE], 0); + if (ret < 0) { + dev_err(dev, "Failed to update SPDIF registers"); + return ret; + } + + /* Init DAC configuration */ + if (data->dev_data->chipid == CHIPID_STIH407) { + /* init configuration */ + ret = regmap_field_write( + dac->field[STIH407_DAC_STANDBY], 1); + + if (!ret) + ret = regmap_field_write( + dac->field[STIH407_DAC_STANDBY_ANA], 1); + if (!ret) + ret = regmap_field_write( + dac->field[STIH407_DAC_SOFTMUTE], 1); + } else if (data->dev_data->chipid == CHIPID_STIH416) { + ret = regmap_field_write( + dac->field[STIH416_DAC_NOT_STANDBY], 0); + if (!ret) + ret = regmap_field_write( + dac->field[STIH416_DAC_ANALOG_PWR_DW], 0); + if (!ret) + ret = regmap_field_write( + dac->field[STIH416_DAC_ANALOG_NOT_PWR_DW_BG], + 0); + if (!ret) + ret = regmap_field_write( + dac->field[STIH416_DAC_SOFTMUTE], 1); + } + + if (ret < 0) { + dev_err(dev, "Failed to update DAC registers"); + return ret; + } + + return ret; +} + +/* + * DAC + */ +static int sti_sas_dac_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + /* Sanity check only */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_err(dai->codec->dev, + "%s: ERROR: Unsupporter master mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + return 0; +} + +static int stih416_dac_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev); + struct sti_dac_audio *dac = &drvdata->dac; + + /* Get reset control */ + dac->rst = devm_reset_control_get(codec->dev, "dac_rst"); + if (IS_ERR(dac->rst)) { + dev_err(dai->codec->dev, + "%s: ERROR: DAC reset control not defined (%d)!\n", + __func__, (int)dac->rst); + dac->rst = NULL; + return -EFAULT; + } + /* Put the DAC into reset */ + reset_control_assert(dac->rst); + + return 0; +} + +static int stih416_sas_dac_supply(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev); + struct sti_dac_audio *dac = &drvdata->dac; + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Take the DAC out of reset */ + reset_control_deassert(dac->rst); + + /* Enable analog */ + ret = regmap_field_write( + dac->field[STIH416_DAC_ANALOG_NOT_PWR_DW_BG], 1); + if (!ret) + ret = regmap_field_write( + dac->field[STIH416_DAC_ANALOG_PWR_DW], 1); + /* Disable standby */ + if (!ret) + ret = regmap_field_write( + dac->field[STIH416_DAC_NOT_STANDBY], 1); + + break; + + case SND_SOC_DAPM_POST_PMD: + + /* Enable standby */ + ret = regmap_field_write( + dac->field[STIH416_DAC_NOT_STANDBY], 0); + /* Disable analog */ + if (!ret) + ret = regmap_field_write( + dac->field[STIH416_DAC_ANALOG_PWR_DW], 0); + if (!ret) + ret = regmap_field_write( + dac->field[STIH416_DAC_ANALOG_NOT_PWR_DW_BG], + 0); + /* Put the DAC into reset */ + reset_control_assert(dac->rst); + break; + } + + return ret; +} + +static int stih407_sas_dac_supply(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev); + struct sti_dac_audio *dac = &drvdata->dac; + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable analog */ + ret = regmap_field_write(dac->field[STIH407_DAC_STANDBY_ANA], + 0); + /* Disable standby */ + if (!ret) + ret = regmap_field_write( + dac->field[STIH407_DAC_STANDBY], 0); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Enable standby */ + ret = regmap_field_write(dac->field[STIH407_DAC_STANDBY], 1); + /* Disable analog */ + if (!ret) + ret = regmap_field_write( + dac->field[STIH407_DAC_STANDBY_ANA], 1); + break; + } + + return ret; +} + +const struct snd_soc_dapm_widget stih416_sas_dapm_widgets[] = { + SND_SOC_DAPM_DAC_E("DAC enable", "dac_p", SND_SOC_NOPM, 0, 0, + stih416_sas_dac_supply, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUTPUT("DAC Output"), +}; + +const struct snd_soc_dapm_widget stih407_sas_dapm_widgets[] = { + SND_SOC_DAPM_DAC_E("DAC enable", "dac_p", SND_SOC_NOPM, 0, 0, + stih407_sas_dac_supply, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUTPUT("DAC Output"), +}; + +const struct snd_soc_dapm_route sti_sas_route[] = { + {"DAC Output", NULL, "DAC enable"}, +}; + +static int stih416_sas_dac_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + struct snd_soc_codec *codec = dai->codec; + struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev); + struct sti_dac_audio *dac = &drvdata->dac; + + if (mute) + return regmap_field_write( + dac->field[STIH416_DAC_SOFTMUTE], 1); + else + return regmap_field_write( + dac->field[STIH416_DAC_SOFTMUTE], 0); +} + +static int stih407_sas_dac_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + struct snd_soc_codec *codec = dai->codec; + struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev); + struct sti_dac_audio *dac = &drvdata->dac; + + if (mute) + return regmap_field_write( + dac->field[STIH407_DAC_SOFTMUTE], 1); + else + return regmap_field_write( + dac->field[STIH407_DAC_SOFTMUTE], 0); +} + +/* + * SPDIF + */ +static int sti_sas_spdif_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_err(dai->codec->dev, + "%s: ERROR: Unsupporter master mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + return 0; +} + +/* + * sti_sas_spdif_trigger: + * Trigger function is used to ensure that BiPhase Formater is disabled + * before CPU dai is stopped. + * This is mandatory to avoid that BPF is stalled + */ +static int sti_sas_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev); + struct sti_spdif_audio *spdif = &drvdata->spdif; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + return regmap_field_write(spdif->field[BIPHASE_ENABLE], + 1); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + return regmap_field_write(spdif->field[BIPHASE_ENABLE], + 0); + break; + default: + return -EINVAL; + } +} + +/* + * CODEC DAIS + */ + +/* + * sti_sas_hw_params: + * Request MCLK-FS clocks division to CPU_DAI based on requested rate + */ +static int sti_sas_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int div; + + div = sti_sas_dai_clk_div[dai->id]; + if (cpu_dai->driver->ops->set_clkdiv) + return cpu_dai->driver->ops->set_clkdiv(cpu_dai, + SND_SOC_CLOCK_OUT, div); + dev_warn(codec->dev, "WARN: CPU DAI not support sysclk div"); + + return 0; +} + +const struct snd_soc_dai_ops stih416_dac_ops = { + .set_fmt = sti_sas_dac_set_fmt, + .mute_stream = stih416_sas_dac_mute, + .hw_params = sti_sas_hw_params +}; + +const struct snd_soc_dai_ops stih407_dac_ops = { + .set_fmt = sti_sas_dac_set_fmt, + .mute_stream = stih407_sas_dac_mute, + .hw_params = sti_sas_hw_params +}; + +const struct sti_sas_dev_data stih416_data = { + .chipid = CHIPID_STIH416, + .dac = sti_sas_dac_stih416, + .dac_ops = &stih416_dac_ops, + .spdif = sti_sas_spdif_stih416, + .dapm_widgets = stih416_sas_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(stih416_sas_dapm_widgets) +}; + +const struct sti_sas_dev_data stih407_data = { + .chipid = CHIPID_STIH407, + .dac = sti_sas_dac_stih407, + .dac_ops = &stih407_dac_ops, + .spdif = sti_sas_spdif_stih407, + .dapm_widgets = stih407_sas_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(stih407_sas_dapm_widgets) +}; + +static struct snd_soc_dai_driver sti_sas_dai[] = { + { + .name = "sas-dai-spdif-out", + .id = STI_SAS_DAI_SPDIF_OUT, + .playback = { + .stream_name = "spdif_p", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .set_fmt = sti_sas_spdif_set_fmt, + .trigger = sti_sas_spdif_trigger, + .hw_params = sti_sas_hw_params, + } + }, + }, + { + .name = "sas-dai-dac", + .id = STI_SAS_DAI_ANALOG_OUT, + .playback = { + .stream_name = "dac_p", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + }, +}; + +const int sti_sas_dai_clk_div[ARRAY_SIZE(sti_sas_dai)] = { + 128, /* spdif out */ + 256, /* dac */ +}; + +#ifdef CONFIG_PM_SLEEP +static int sti_sas_resume(struct snd_soc_codec *codec) +{ + struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev); + + return sti_sas_init_sas_registers(codec->dev, drvdata); +} +#else +#define sti_sas_resume NULL +#endif + +static int sti_sas_codec_probe(struct snd_soc_codec *codec) +{ + struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev); + int ret; + + ret = sti_sas_init_sas_registers(codec->dev, drvdata); + + return ret; +} + +static struct snd_soc_codec_driver sti_sas_driver = { + .probe = sti_sas_codec_probe, + .resume = sti_sas_resume, + .dapm_routes = sti_sas_route, + .num_dapm_routes = ARRAY_SIZE(sti_sas_route), +}; + +static const struct of_device_id sti_sas_dev_match[] = { + { + .compatible = "st,stih416-sas-codec", + .data = &stih416_data, + }, + { + .compatible = "st,stih407-sas-codec", + .data = &stih407_data, + }, + {}, +}; + +static int sti_sas_driver_probe(struct platform_device *pdev) +{ + struct device_node *pnode = pdev->dev.of_node; + int ret = 0; + struct sti_sas_data *drvdata; + + /* Allocate device structure */ + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct sti_sas_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + /* Populate data structure depending on compatibility */ + if (!of_match_node(sti_sas_dev_match, pnode)->data) { + dev_err(&pdev->dev, "data associated to device is missing"); + return -EINVAL; + } + + drvdata->dev_data = of_match_node(sti_sas_dev_match, pnode)->data; + + /* Initialise device structure */ + drvdata->dev = &pdev->dev; + + /* Request the DAC & SPDIF registers memory region */ + drvdata->dac.regmap = + syscon_regmap_lookup_by_phandle(pnode, "st,syscfg"); + if (!drvdata->dac.regmap) { + dev_err(&pdev->dev, "audio registers not enabled\n"); + return -EFAULT; + } + drvdata->spdif.regmap = drvdata->dac.regmap; + + ret = sti_sas_map_sas_registers(drvdata); + if (ret < 0) + return ret; + + /* Set DAC dai probe */ + if (drvdata->dev_data->chipid == CHIPID_STIH416) + sti_sas_dai[STI_SAS_DAI_ANALOG_OUT].probe = stih416_dac_probe; + + sti_sas_dai[STI_SAS_DAI_ANALOG_OUT].ops = drvdata->dev_data->dac_ops; + + /* Set dapms*/ + sti_sas_driver.dapm_widgets = drvdata->dev_data->dapm_widgets; + sti_sas_driver.num_dapm_widgets = drvdata->dev_data->num_dapm_widgets; + + /* Store context */ + dev_set_drvdata(&pdev->dev, drvdata); + + return snd_soc_register_codec(&pdev->dev, &sti_sas_driver, + sti_sas_dai, + ARRAY_SIZE(sti_sas_dai)); +} + +static int sti_sas_driver_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static struct platform_driver sti_sas_platform_driver = { + .driver = { + .name = "sti-sas-codec", + .owner = THIS_MODULE, + .of_match_table = sti_sas_dev_match, + }, + .probe = sti_sas_driver_probe, + .remove = sti_sas_driver_remove, +}; + +module_platform_driver(sti_sas_platform_driver); + +MODULE_DESCRIPTION("audio codec for STMicroelectronics sti platforms"); +MODULE_AUTHOR("Arnaud.pouliquen@st.com"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sti/Kconfig b/sound/soc/sti/Kconfig index c29e219..af5d6fd 100644 --- a/sound/soc/sti/Kconfig +++ b/sound/soc/sti/Kconfig @@ -9,3 +9,9 @@ menuconfig SND_SOC_STI help Say Y if you want to enable ASoC-support for any of the STI platforms (e.g. STIH416). + +config SND_SOC_STI_SAS + tristate "codec Audio support for STI SAS codec" + depends on SND_SOC_STI + help + Say Y if you want to include STI SAS audio codec support \ No newline at end of file
On Mon, May 18, 2015 at 02:12:54PM +0200, Arnaud Pouliquen wrote:
- if (data->dev_data->chipid == CHIPID_STIH407)
max_rf = STIH407_DAC_MAX_RF;
- else
max_rf = STIH416_DAC_MAX_RF;
switch statements for selecting chip versions here and elsewhere please, that way new variants are easier to handle.
- dac->field = devm_kzalloc(data->dev,
sizeof(struct regmap_field *) * max_rf,
GFP_KERNEL);
devm_kcalloc().
+static int stih407_sas_dac_supply(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event) {
- struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
- struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev);
- struct sti_dac_audio *dac = &drvdata->dac;
- int ret = 0;
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
/* Enable analog */
ret = regmap_field_write(dac->field[STIH407_DAC_STANDBY_ANA],
0);
/* Disable standby */
if (!ret)
ret = regmap_field_write(
dac->field[STIH407_DAC_STANDBY], 0);
break;
These event functions all look very similar and I can't help but think that they look awfully like the sort of register write sequences that DAPM normally generates anyway. Is there any great reason for not doing these by registering multiple widgets with routes between them rather than with custom code?
+static int sti_sas_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct snd_soc_codec *codec = dai->codec;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- int div;
- div = sti_sas_dai_clk_div[dai->id];
- if (cpu_dai->driver->ops->set_clkdiv)
return cpu_dai->driver->ops->set_clkdiv(cpu_dai,
SND_SOC_CLOCK_OUT, div);
- dev_warn(codec->dev, "WARN: CPU DAI not support sysclk div");
This is worrying, we shouldn't be peering inside the CPU DAI like this. I'd expect this to either be done autonomously by the CPU DAI or handled in a machine driver.
+config SND_SOC_STI_SAS
- tristate "codec Audio support for STI SAS codec"
- depends on SND_SOC_STI
- help
Say Y if you want to include STI SAS audio codec support
This appears to duplicate the CODEC Kconfig in the CODECs directory - I'd expect it to be there rather than in the STI directory given that that is where the code is.
On 05/25/2015 03:01 PM, Mark Brown wrote:
+static int stih407_sas_dac_supply(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event) {
- struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
- struct sti_sas_data *drvdata = dev_get_drvdata(codec->dev);
- struct sti_dac_audio *dac = &drvdata->dac;
- int ret = 0;
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
/* Enable analog */
ret = regmap_field_write(dac->field[STIH407_DAC_STANDBY_ANA],
0);
/* Disable standby */
if (!ret)
ret = regmap_field_write(
dac->field[STIH407_DAC_STANDBY], 0);
break;
These event functions all look very similar and I can't help but think that they look awfully like the sort of register write sequences that DAPM normally generates anyway. Is there any great reason for not doing these by registering multiple widgets with routes between them rather than with custom code?
I need to respect the sequence for power up /power down. for sure I could manage it using 3 DAPMs with routes, but how to ensure sequence? Moreover, i will need to implement a cpu dai DAPM to manage uniperipheral clock. This clock need to be enabled/disabled before/after DAC to avoid plop. So would prefer to manage the DAC sequence in a single function...
+static int sti_sas_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct snd_soc_codec *codec = dai->codec;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- int div;
- div = sti_sas_dai_clk_div[dai->id];
- if (cpu_dai->driver->ops->set_clkdiv)
return cpu_dai->driver->ops->set_clkdiv(cpu_dai,
SND_SOC_CLOCK_OUT, div);
- dev_warn(codec->dev, "WARN: CPU DAI not support sysclk div");
This is worrying, we shouldn't be peering inside the CPU DAI like this. I'd expect this to either be done autonomously by the CPU DAI or handled in a machine driver.
I think i misunderstand your remark in V1...but i still not understand how you want that i implement it, if i can't neither use snd_soc_dai_set_clkdiv (except implement it in simple_card). Please could you precise how you would like that i implement the feature?
I'm kind surprise that nobody else have this kind of setup. I know at least one codec that should need this kind of service: AK4628. For this codec, depending on runtime frequency, a division value should be apply between MCLK and SCLK...
On Mon, Jun 01, 2015 at 07:41:54PM +0200, Arnaud Pouliquen wrote:
On 05/25/2015 03:01 PM, Mark Brown wrote:
These event functions all look very similar and I can't help but think that they look awfully like the sort of register write sequences that DAPM normally generates anyway. Is there any great reason for not doing these by registering multiple widgets with routes between them rather than with custom code?
I need to respect the sequence for power up /power down. for sure I could manage it using 3 DAPMs with routes, but how to ensure sequence?
There's a defined ordering for handling widgets of different types wich probably suffices.
Moreover, i will need to implement a cpu dai DAPM to manage uniperipheral clock. This clock need to be enabled/disabled before/after DAC to avoid plop. So would prefer to manage the DAC sequence in a single function...
We already handle clock and supply widgets very early and late in the sequence for precisely this reason.
- div = sti_sas_dai_clk_div[dai->id];
- if (cpu_dai->driver->ops->set_clkdiv)
return cpu_dai->driver->ops->set_clkdiv(cpu_dai,
SND_SOC_CLOCK_OUT, div);
- dev_warn(codec->dev, "WARN: CPU DAI not support sysclk div");
This is worrying, we shouldn't be peering inside the CPU DAI like this. I'd expect this to either be done autonomously by the CPU DAI or handled in a machine driver.
I think i misunderstand your remark in V1...but i still not understand how you want that i implement it, if i can't neither use snd_soc_dai_set_clkdiv (except implement it in simple_card). Please could you precise how you would like that i implement the feature?
I don't know exactly what "this feature" is so it's hard to be sure what the above is supposed to do but the fact that you're having one driver for your platform set a clock divider in a completely different driver for that platform should be an enormous red flag - at a bare minimum whatever you're doing here doesn't seem like it should be done in this other driver.
I'm kind surprise that nobody else have this kind of setup. I know at least one codec that should need this kind of service: AK4628. For this codec, depending on runtime frequency, a division value should be apply between MCLK and SCLK...
We already have a _set_bclk_ratio() API if that's what you're looking for? It's not something that your CPU drivers should ever be using, though - it's something for machine drivers. Your CPU drivers can't know what configuration a particular machine is going to require. I don't know what SCLK is here, do you mean LRCLK?
Add capability to adjust player clock, for clocks drift management.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/sti/uniperif.h | 2 +- sound/soc/sti/uniperif_player.c | 149 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 2 deletions(-)
diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index c18584c..14f5595 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1196,7 +1196,7 @@ struct uniperif {
/* Specific to IEC958 player */ struct uniperif_iec958_settings stream_settings; - spinlock_t lock; /* lock on resource updated by alsa controls */ + spinlock_t lock; /* lock on resource updated by alsa stream or controls */
/*alsa ctrl*/ struct snd_kcontrol_new *snd_ctrls; diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index b41cc82..d8f83af 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -40,6 +40,9 @@ (UNIPERIF_PLAYER_TYPE_IS_HDMI(p) || \ UNIPERIF_PLAYER_TYPE_IS_SPDIF(p))
+#define UNIPERIF_PLAYER_CLK_ADJ_MIN -999999 +#define UNIPERIF_PLAYER_CLK_ADJ_MAX 1000000 + /* * Note: snd_pcm_hardware is linked to DMA controller but is declared here to * integrate DAI_CPU capability in term of rate and supported channels @@ -181,6 +184,70 @@ static irqreturn_t uni_player_irq_handler(int irq, void *dev_id) return ret; }
+int uni_player_clk_set_rate(struct uniperif *player, unsigned long rate) +{ + int rate_adjusted, rate_achieved, delta, ret; + int adjustment = player->clk_adj; + + /* + * a + * F = f + --------- * f = f + d + * 1000000 + * + * a + * d = --------- * f + * 1000000 + * + * where: + * f - nominal rate + * a - adjustment in ppm (parts per milion) + * F - rate to be set in synthesizer + * d - delta (difference) between f and F + */ + if (adjustment < 0) { + /* div64_64 operates on unsigned values... */ + delta = -1; + adjustment = -adjustment; + } else { + delta = 1; + } + /* 500000 ppm is 0.5, which is used to round up values */ + delta *= (int)div64_u64((uint64_t)rate * + (uint64_t)adjustment + 500000, 1000000); + rate_adjusted = rate + delta; + + /* Adjusted rate should never be == 0 */ + if (!rate_adjusted) + return -EINVAL; + + ret = clk_set_rate(player->clk, rate_adjusted); + if (ret < 0) + return ret; + + rate_achieved = clk_get_rate(player->clk); + if (!rate_achieved) + /* If value is 0 means that clock or parent not valid */ + return -EINVAL; + + /* + * Using ALSA's adjustment control, we can modify the rate to be up + * to twice as much as requested, but no more + */ + delta = rate_achieved - rate; + if (delta < 0) { + /* div64_64 operates on unsigned values... */ + delta = -delta; + adjustment = -1; + } else { + adjustment = 1; + } + /* Frequency/2 is added to round up result */ + adjustment *= (int)div64_u64((uint64_t)delta * 1000000 + rate / 2, + rate); + player->clk_adj = adjustment; + return 0; +} + static void uni_player_set_channel_status(struct uniperif *player, struct snd_pcm_runtime *runtime) { @@ -512,6 +579,74 @@ static int uni_player_prepare_pcm(struct uniperif *player, return 0; }
+/* + * uniperif rate adjustement control + */ +static int snd_sti_clk_adjustment_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = UNIPERIF_PLAYER_CLK_ADJ_MIN; + uinfo->value.integer.max = UNIPERIF_PLAYER_CLK_ADJ_MAX; + uinfo->value.integer.step = 1; + + return 0; +} + +static int snd_sti_clk_adjustment_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uniperif *player = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = player->clk_adj; + + return 0; +} + +static int snd_sti_clk_adjustment_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uniperif *player = snd_kcontrol_chip(kcontrol); + int ret = 0; + + if ((ucontrol->value.integer.value[0] < UNIPERIF_PLAYER_CLK_ADJ_MIN) || + (ucontrol->value.integer.value[0] > UNIPERIF_PLAYER_CLK_ADJ_MAX)) + return -EINVAL; + + spin_lock(&player->lock); + player->clk_adj = ucontrol->value.integer.value[0]; + + if (player->rate) + ret = uni_player_clk_set_rate(player, + player->rate * player->clk_div); + spin_unlock(&player->lock); + + return ret; +} + +static struct snd_kcontrol_new uni_player_clk_adj_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Playback Oversampling Freq. Adjustment", + .info = snd_sti_clk_adjustment_info, + .get = snd_sti_clk_adjustment_get, + .put = snd_sti_clk_adjustment_put, +}; + +static struct snd_kcontrol_new *snd_sti_ctl[] = { + &uni_player_clk_adj_ctl, +}; + +static int uni_player_open(struct uniperif *player) +{ + spin_lock(&player->lock); + player->rate = 0; + player->clk_adj = 0; + spin_unlock(&player->lock); + + return 0; +} + static int uni_player_prepare(struct uniperif *player, struct snd_pcm_runtime *runtime) { @@ -605,12 +740,18 @@ static int uni_player_prepare(struct uniperif *player, }
/* Set clock rate */ - ret = clk_set_rate(player->clk, runtime->rate * player->clk_div); + spin_lock(&player->lock); + ret = uni_player_clk_set_rate(player, + runtime->rate * player->clk_div); if (ret) { dev_err(player->dev, "Failed to set clock rate"); + spin_unlock(&player->lock); return ret; }
+ player->rate = runtime->rate; + spin_unlock(&player->lock); + SET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(player, 0);
/* Reset uniperipheral player */ @@ -860,6 +1001,7 @@ static int uni_player_parse_dt(struct platform_device *pdev, }
const struct uniperif_ops uni_player_ops = { + .open = uni_player_open, .close = uni_player_close, .prepare = uni_player_prepare, .trigger = uni_player_trigger @@ -964,6 +1106,8 @@ int uni_player_init(struct platform_device *pdev, struct device_node *node,
*uni_player = player;
+ spin_lock_init(&player->lock); + /* Ensure that disabled by default */ SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(player); SET_UNIPERIF_CTRL_ROUNDING_OFF(player); @@ -990,6 +1134,9 @@ int uni_player_init(struct platform_device *pdev, struct device_node *node, IEC958_AES4_CON_WORDLEN_24_20; }
+ player->num_ctrls = ARRAY_SIZE(snd_sti_ctl); + player->snd_ctrls = snd_sti_ctl[0]; + return 0; } EXPORT_SYMBOL_GPL(uni_player_init);
Add control to configure IEC60958 settings.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/sti/uniperif_player.c | 73 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-)
diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index d8f83af..36bbbf5 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -259,6 +259,7 @@ static void uni_player_set_channel_status(struct uniperif *player, * sampling frequency. If no sample rate is already specified, then * set one. */ + spin_lock(&player->lock); if (runtime && (player->stream_settings.iec958.status[3] == IEC958_AES3_CON_FS_NOTID)) { switch (runtime->rate) { @@ -340,6 +341,7 @@ static void uni_player_set_channel_status(struct uniperif *player, player->stream_settings.iec958.status[3 + (n * 4)] << 24; SET_UNIPERIF_CHANNEL_STA_REGN(player, n, status); } + spin_unlock(&player->lock);
/* Update the channel status */ if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) @@ -580,6 +582,59 @@ static int uni_player_prepare_pcm(struct uniperif *player, }
/* + * ALSA uniperipheral iec958 controls + */ +static int uni_player_ctl_iec958_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int uni_player_ctl_iec958_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uniperif *player = snd_kcontrol_chip(kcontrol); + struct snd_aes_iec958 *iec958 = &player->stream_settings.iec958; + + spin_lock(&player->lock); + ucontrol->value.iec958.status[0] = iec958->status[0]; + ucontrol->value.iec958.status[1] = iec958->status[1]; + ucontrol->value.iec958.status[2] = iec958->status[2]; + ucontrol->value.iec958.status[3] = iec958->status[3]; + spin_unlock(&player->lock); + return 0; +} + +static int uni_player_ctl_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uniperif *player = snd_kcontrol_chip(kcontrol); + struct snd_aes_iec958 *iec958 = &player->stream_settings.iec958; + + spin_lock(&player->lock); + iec958->status[0] = ucontrol->value.iec958.status[0]; + iec958->status[1] = ucontrol->value.iec958.status[1]; + iec958->status[2] = ucontrol->value.iec958.status[2]; + iec958->status[3] = ucontrol->value.iec958.status[3]; + spin_unlock(&player->lock); + + uni_player_set_channel_status(player, NULL); + + return 0; +} + +static struct snd_kcontrol_new uni_player_iec958_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = uni_player_ctl_iec958_info, + .get = uni_player_ctl_iec958_get, + .put = uni_player_ctl_iec958_put, +}; + +/* * uniperif rate adjustement control */ static int snd_sti_clk_adjustment_info(struct snd_kcontrol *kcontrol, @@ -599,7 +654,9 @@ static int snd_sti_clk_adjustment_get(struct snd_kcontrol *kcontrol, { struct uniperif *player = snd_kcontrol_chip(kcontrol);
+ spin_lock(&player->lock); ucontrol->value.integer.value[0] = player->clk_adj; + spin_unlock(&player->lock);
return 0; } @@ -633,7 +690,12 @@ static struct snd_kcontrol_new uni_player_clk_adj_ctl = { .put = snd_sti_clk_adjustment_put, };
-static struct snd_kcontrol_new *snd_sti_ctl[] = { +static struct snd_kcontrol_new *snd_sti_pcm_ctl[] = { + &uni_player_clk_adj_ctl, +}; + +static struct snd_kcontrol_new *snd_sti_iec_ctl[] = { + &uni_player_iec958_ctl, &uni_player_clk_adj_ctl, };
@@ -1132,10 +1194,13 @@ int uni_player_init(struct platform_device *pdev, struct device_node *node, player->stream_settings.iec958.status[4] = IEC958_AES4_CON_MAX_WORDLEN_24 | IEC958_AES4_CON_WORDLEN_24_20; - }
- player->num_ctrls = ARRAY_SIZE(snd_sti_ctl); - player->snd_ctrls = snd_sti_ctl[0]; + player->num_ctrls = ARRAY_SIZE(snd_sti_iec_ctl); + player->snd_ctrls = snd_sti_iec_ctl[0]; + } else { + player->num_ctrls = ARRAY_SIZE(snd_sti_pcm_ctl); + player->snd_ctrls = snd_sti_pcm_ctl[0]; + }
return 0; }
On Mon, May 18, 2015 at 02:12:56PM +0200, Arnaud Pouliquen wrote:
- spin_lock(&player->lock);
This is basically fine but a spinlock seems very heavy here - why not a mutex (and why not something just for the IEC bits rather than the big lock for the entire device)?
participants (3)
-
Arnaud Pouliquen
-
Lars-Peter Clausen
-
Mark Brown