]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
Merge remote-tracking branches 'asoc/topic/sta529', 'asoc/topic/sti', 'asoc/topic...
authorMark Brown <broonie@kernel.org>
Sun, 30 Apr 2017 13:16:31 +0000 (22:16 +0900)
committerMark Brown <broonie@kernel.org>
Sun, 30 Apr 2017 13:16:31 +0000 (22:16 +0900)
14 files changed:
Documentation/devicetree/bindings/sound/st,stm32-sai.txt [new file with mode: 0644]
Documentation/devicetree/bindings/sound/tas2552.txt
sound/soc/Kconfig
sound/soc/Makefile
sound/soc/codecs/sta529.c
sound/soc/codecs/tas2552.c
sound/soc/sti/uniperif_player.c
sound/soc/stm/Kconfig [new file with mode: 0644]
sound/soc/stm/Makefile [new file with mode: 0644]
sound/soc/stm/stm32_sai.c [new file with mode: 0644]
sound/soc/stm/stm32_sai.h [new file with mode: 0644]
sound/soc/stm/stm32_sai_sub.c [new file with mode: 0644]
sound/soc/sunxi/sun8i-codec-analog.c
sound/soc/sunxi/sun8i-codec.c

diff --git a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt
new file mode 100644 (file)
index 0000000..c59a3d7
--- /dev/null
@@ -0,0 +1,89 @@
+STMicroelectronics STM32 Serial Audio Interface (SAI).
+
+The SAI interface (Serial Audio Interface) offers a wide set of audio protocols
+as I2S standards, LSB or MSB-justified, PCM/DSP, TDM, and AC'97.
+The SAI contains two independent audio sub-blocks. Each sub-block has
+its own clock generator and I/O lines controller.
+
+Required properties:
+  - compatible: Should be "st,stm32f4-sai"
+  - reg: Base address and size of SAI common register set.
+  - clocks: Must contain phandle and clock specifier pairs for each entry
+       in clock-names.
+  - clock-names: Must contain "x8k" and "x11k"
+       "x8k": SAI parent clock for sampling rates multiple of 8kHz.
+       "x11k": SAI parent clock for sampling rates multiple of 11.025kHz.
+  - interrupts: cpu DAI interrupt line shared by SAI sub-blocks
+
+Optional properties:
+  - resets: Reference to a reset controller asserting the SAI
+
+SAI subnodes:
+Two subnodes corresponding to SAI sub-block instances A et B can be defined.
+Subnode can be omitted for unsused sub-block.
+
+SAI subnodes required properties:
+  - compatible: Should be "st,stm32-sai-sub-a" or "st,stm32-sai-sub-b"
+       for SAI sub-block A or B respectively.
+  - reg: Base address and size of SAI sub-block register set.
+  - clocks: Must contain one phandle and clock specifier pair
+       for sai_ck which feeds the internal clock generator.
+  - clock-names: Must contain "sai_ck".
+  - dmas: see Documentation/devicetree/bindings/dma/stm32-dma.txt
+  - dma-names: identifier string for each DMA request line
+       "tx": if sai sub-block is configured as playback DAI
+       "rx": if sai sub-block is configured as capture DAI
+  - pinctrl-names: should contain only value "default"
+  - pinctrl-0: see Documentation/devicetree/bindings/pinctrl/pinctrl-stm32.txt
+
+Example:
+sound_card {
+       compatible = "audio-graph-card";
+       dais = <&sai1b_port>;
+};
+
+sai1: sai1@40015800 {
+       compatible = "st,stm32f4-sai";
+       #address-cells = <1>;
+       #size-cells = <1>;
+       ranges;
+       reg = <0x40015800 0x4>;
+       clocks = <&rcc 1 CLK_SAIQ_PDIV>, <&rcc 1 CLK_I2SQ_PDIV>;
+       clock-names = "x8k", "x11k";
+       interrupts = <87>;
+
+       sai1b: audio-controller@40015824 {
+               #sound-dai-cells = <0>;
+               compatible = "st,stm32-sai-sub-b";
+               reg = <0x40015824 0x1C>;
+               clocks = <&rcc 1 CLK_SAI2>;
+               clock-names = "sai_ck";
+               dmas = <&dma2 5 0 0x400 0x0>;
+               dma-names = "tx";
+               pinctrl-names = "default";
+               pinctrl-0 = <&pinctrl_sai1b>;
+
+               ports {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       sai1b_port: port@0 {
+                               reg = <0>;
+                               cpu_endpoint: endpoint {
+                                       remote-endpoint = <&codec_endpoint>;
+                                       audio-graph-card,format = "i2s";
+                                       audio-graph-card,bitclock-master = <&codec_endpoint>;
+                                       audio-graph-card,frame-master = <&codec_endpoint>;
+                               };
+                       };
+               };
+       };
+};
+
+audio-codec {
+       codec_port: port {
+               codec_endpoint: endpoint {
+                       remote-endpoint = <&cpu_endpoint>;
+               };
+       };
+};
index c49992c0b62acb97f9d6bb11ad41812747242f84..2d71eb05c1d384c72e166d02a46fc2d695cea382 100644 (file)
@@ -5,7 +5,8 @@ The tas2552 serial control bus communicates through I2C protocols
 Required properties:
        - compatible - One of:
                "ti,tas2552" - TAS2552
-       - reg -  I2C slave address
+       - reg -  I2C slave address: it can be 0x40 if ADDR pin is 0
+                                   or 0x41 if ADDR pin is 1.
        - supply-*: Required supply regulators are:
                "vbat"          battery voltage
                "iovdd"         I/O Voltage
@@ -14,17 +15,20 @@ Required properties:
 Optional properties:
        - enable-gpio - gpio pin to enable/disable the device
 
-tas2552 can receive it's reference clock via MCLK, BCLK, IVCLKIN pin or use the
+tas2552 can receive its reference clock via MCLK, BCLK, IVCLKIN pin or use the
 internal 1.8MHz. This CLKIN is used by the PLL. In addition to PLL, the PDM
 reference clock is also selectable: PLL, IVCLKIN, BCLK or MCLK.
 For system integration the dt-bindings/sound/tas2552.h header file provides
-defined values to selct and configure the PLL and PDM reference clocks.
+defined values to select and configure the PLL and PDM reference clocks.
 
 Example:
 
 tas2552: tas2552@41 {
        compatible = "ti,tas2552";
        reg = <0x41>;
+       vbat-supply = <&reg_vbat>;
+       iovdd-supply = <&reg_iovdd>;
+       avdd-supply = <&reg_avdd>;
        enable-gpio = <&gpio4 2 GPIO_ACTIVE_HIGH>;
 };
 
index 9df9658b552b8d91e124d2eba0b785db0a5c4edb..c0abad2067e14a07feb3eee54209a3467517040c 100644 (file)
@@ -64,6 +64,7 @@ source "sound/soc/sh/Kconfig"
 source "sound/soc/sirf/Kconfig"
 source "sound/soc/spear/Kconfig"
 source "sound/soc/sti/Kconfig"
+source "sound/soc/stm/Kconfig"
 source "sound/soc/sunxi/Kconfig"
 source "sound/soc/tegra/Kconfig"
 source "sound/soc/txx9/Kconfig"
index 2f6aabb8b4c3437456e3c35f060d50d26c153050..39c27a58158db38c3f4db2a72af842747f7ef830 100644 (file)
@@ -44,6 +44,7 @@ obj-$(CONFIG_SND_SOC) += sh/
 obj-$(CONFIG_SND_SOC)  += sirf/
 obj-$(CONFIG_SND_SOC)  += spear/
 obj-$(CONFIG_SND_SOC)  += sti/
+obj-$(CONFIG_SND_SOC)  += stm/
 obj-$(CONFIG_SND_SOC)  += sunxi/
 obj-$(CONFIG_SND_SOC)  += tegra/
 obj-$(CONFIG_SND_SOC)  += txx9/
index d4b384e4b2664cf8b080ba5271fef8bcce0df5e3..660734359bf3c174577e9363a34cf066e4bd0f3b 100644 (file)
@@ -375,9 +375,16 @@ static const struct i2c_device_id sta529_i2c_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, sta529_i2c_id);
 
+static const struct of_device_id sta529_of_match[] = {
+       { .compatible = "st,sta529", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, sta529_of_match);
+
 static struct i2c_driver sta529_i2c_driver = {
        .driver = {
                .name = "sta529",
+               .of_match_table = sta529_of_match,
        },
        .probe          = sta529_i2c_probe,
        .remove         = sta529_i2c_remove,
index baf455e8c2f7b038b0883e43184d12e99c46cfb7..8840f72f3c4ad7f2a2b5f8bcb0cb54745d8dfbf1 100644 (file)
@@ -611,7 +611,7 @@ probe_fail:
 
        regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies),
                                        tas2552->supplies);
-       return -EIO;
+       return ret;
 }
 
 static int tas2552_codec_remove(struct snd_soc_codec *codec)
@@ -637,7 +637,7 @@ static int tas2552_suspend(struct snd_soc_codec *codec)
        if (ret != 0)
                dev_err(codec->dev, "Failed to disable supplies: %d\n",
                        ret);
-       return 0;
+       return ret;
 }
 
 static int tas2552_resume(struct snd_soc_codec *codec)
@@ -653,7 +653,7 @@ static int tas2552_resume(struct snd_soc_codec *codec)
                        ret);
        }
 
-       return 0;
+       return ret;
 }
 #else
 #define tas2552_suspend NULL
index d7e8dd46d2cc40ba2c937a4ea627f4859fe1f478..d8b6936e544e3c9e42444130f9087dc31be73357 100644 (file)
@@ -1074,7 +1074,7 @@ int uni_player_init(struct platform_device *pdev,
        player->clk = of_clk_get(pdev->dev.of_node, 0);
        if (IS_ERR(player->clk)) {
                dev_err(player->dev, "Failed to get clock\n");
-               ret = PTR_ERR(player->clk);
+               return PTR_ERR(player->clk);
        }
 
        /* Select the frequency synthesizer clock */
diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig
new file mode 100644 (file)
index 0000000..972970f
--- /dev/null
@@ -0,0 +1,8 @@
+menuconfig SND_SOC_STM32
+       tristate "STMicroelectronics STM32 SOC audio support"
+       depends on ARCH_STM32 || COMPILE_TEST
+       depends on SND_SOC
+       select SND_SOC_GENERIC_DMAENGINE_PCM
+       select REGMAP_MMIO
+       help
+         Say Y if you want to enable ASoC-support for STM32
diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile
new file mode 100644 (file)
index 0000000..e466a47
--- /dev/null
@@ -0,0 +1,6 @@
+# SAI
+snd-soc-stm32-sai-sub-objs := stm32_sai_sub.o
+obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai-sub.o
+
+snd-soc-stm32-sai-objs := stm32_sai.o
+obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai.o
diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c
new file mode 100644 (file)
index 0000000..2a27a26
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * STM32 ALSA SoC Digital Audio Interface (SAI) driver.
+ *
+ * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
+ * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics.
+ *
+ * License terms: GPL V2.0.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/reset.h>
+
+#include <sound/dmaengine_pcm.h>
+#include <sound/core.h>
+
+#include "stm32_sai.h"
+
+static const struct of_device_id stm32_sai_ids[] = {
+       { .compatible = "st,stm32f4-sai", .data = (void *)SAI_STM32F4 },
+       {}
+};
+
+static int stm32_sai_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct stm32_sai_data *sai;
+       struct reset_control *rst;
+       struct resource *res;
+       void __iomem *base;
+       const struct of_device_id *of_id;
+
+       sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+       if (!sai)
+               return -ENOMEM;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(base))
+               return PTR_ERR(base);
+
+       of_id = of_match_device(stm32_sai_ids, &pdev->dev);
+       if (of_id)
+               sai->version = (enum stm32_sai_version)of_id->data;
+       else
+               return -EINVAL;
+
+       sai->clk_x8k = devm_clk_get(&pdev->dev, "x8k");
+       if (IS_ERR(sai->clk_x8k)) {
+               dev_err(&pdev->dev, "missing x8k parent clock\n");
+               return PTR_ERR(sai->clk_x8k);
+       }
+
+       sai->clk_x11k = devm_clk_get(&pdev->dev, "x11k");
+       if (IS_ERR(sai->clk_x11k)) {
+               dev_err(&pdev->dev, "missing x11k parent clock\n");
+               return PTR_ERR(sai->clk_x11k);
+       }
+
+       /* init irqs */
+       sai->irq = platform_get_irq(pdev, 0);
+       if (sai->irq < 0) {
+               dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+               return sai->irq;
+       }
+
+       /* reset */
+       rst = reset_control_get(&pdev->dev, NULL);
+       if (!IS_ERR(rst)) {
+               reset_control_assert(rst);
+               udelay(2);
+               reset_control_deassert(rst);
+       }
+
+       sai->pdev = pdev;
+       platform_set_drvdata(pdev, sai);
+
+       return of_platform_populate(np, NULL, NULL, &pdev->dev);
+}
+
+static int stm32_sai_remove(struct platform_device *pdev)
+{
+       of_platform_depopulate(&pdev->dev);
+
+       return 0;
+}
+
+MODULE_DEVICE_TABLE(of, stm32_sai_ids);
+
+static struct platform_driver stm32_sai_driver = {
+       .driver = {
+               .name = "st,stm32-sai",
+               .of_match_table = stm32_sai_ids,
+       },
+       .probe = stm32_sai_probe,
+       .remove = stm32_sai_remove,
+};
+
+module_platform_driver(stm32_sai_driver);
+
+MODULE_DESCRIPTION("STM32 Soc SAI Interface");
+MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>");
+MODULE_ALIAS("platform:st,stm32-sai");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h
new file mode 100644 (file)
index 0000000..a801fda
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * STM32 ALSA SoC Digital Audio Interface (SAI) driver.
+ *
+ * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
+ * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics.
+ *
+ * License terms: GPL V2.0.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ */
+
+/******************** SAI Register Map **************************************/
+
+/* common register */
+#define STM_SAI_GCR            0x00
+
+/* Sub-block A&B registers offsets, relative to A&B sub-block addresses */
+#define STM_SAI_CR1_REGX       0x00    /* A offset: 0x04. B offset: 0x24 */
+#define STM_SAI_CR2_REGX       0x04
+#define STM_SAI_FRCR_REGX      0x08
+#define STM_SAI_SLOTR_REGX     0x0C
+#define STM_SAI_IMR_REGX       0x10
+#define STM_SAI_SR_REGX                0x14
+#define STM_SAI_CLRFR_REGX     0x18
+#define STM_SAI_DR_REGX                0x1C
+
+/******************** Bit definition for SAI_GCR register *******************/
+#define SAI_GCR_SYNCIN_SHIFT   0
+#define SAI_GCR_SYNCIN_MASK    GENMASK(1, SAI_GCR_SYNCIN_SHIFT)
+#define SAI_GCR_SYNCIN_SET(x)  ((x) << SAI_GCR_SYNCIN_SHIFT)
+
+#define SAI_GCR_SYNCOUT_SHIFT  4
+#define SAI_GCR_SYNCOUT_MASK   GENMASK(5, SAI_GCR_SYNCOUT_SHIFT)
+#define SAI_GCR_SYNCOUT_SET(x) ((x) << SAI_GCR_SYNCOUT_SHIFT)
+
+/******************* Bit definition for SAI_XCR1 register *******************/
+#define SAI_XCR1_RX_TX_SHIFT   0
+#define SAI_XCR1_RX_TX         BIT(SAI_XCR1_RX_TX_SHIFT)
+#define SAI_XCR1_SLAVE_SHIFT   1
+#define SAI_XCR1_SLAVE         BIT(SAI_XCR1_SLAVE_SHIFT)
+
+#define SAI_XCR1_PRTCFG_SHIFT  2
+#define SAI_XCR1_PRTCFG_MASK   GENMASK(3, SAI_XCR1_PRTCFG_SHIFT)
+#define SAI_XCR1_PRTCFG_SET(x) ((x) << SAI_XCR1_PRTCFG_SHIFT)
+
+#define SAI_XCR1_DS_SHIFT      5
+#define SAI_XCR1_DS_MASK       GENMASK(7, SAI_XCR1_DS_SHIFT)
+#define SAI_XCR1_DS_SET(x)     ((x) << SAI_XCR1_DS_SHIFT)
+
+#define SAI_XCR1_LSBFIRST_SHIFT        8
+#define SAI_XCR1_LSBFIRST      BIT(SAI_XCR1_LSBFIRST_SHIFT)
+#define SAI_XCR1_CKSTR_SHIFT   9
+#define SAI_XCR1_CKSTR         BIT(SAI_XCR1_CKSTR_SHIFT)
+
+#define SAI_XCR1_SYNCEN_SHIFT  10
+#define SAI_XCR1_SYNCEN_MASK   GENMASK(11, SAI_XCR1_SYNCEN_SHIFT)
+#define SAI_XCR1_SYNCEN_SET(x) ((x) << SAI_XCR1_SYNCEN_SHIFT)
+
+#define SAI_XCR1_MONO_SHIFT    12
+#define SAI_XCR1_MONO          BIT(SAI_XCR1_MONO_SHIFT)
+#define SAI_XCR1_OUTDRIV_SHIFT 13
+#define SAI_XCR1_OUTDRIV       BIT(SAI_XCR1_OUTDRIV_SHIFT)
+#define SAI_XCR1_SAIEN_SHIFT   16
+#define SAI_XCR1_SAIEN         BIT(SAI_XCR1_SAIEN_SHIFT)
+#define SAI_XCR1_DMAEN_SHIFT   17
+#define SAI_XCR1_DMAEN         BIT(SAI_XCR1_DMAEN_SHIFT)
+#define SAI_XCR1_NODIV_SHIFT   19
+#define SAI_XCR1_NODIV         BIT(SAI_XCR1_NODIV_SHIFT)
+
+#define SAI_XCR1_MCKDIV_SHIFT  20
+#define SAI_XCR1_MCKDIV_WIDTH  4
+#define SAI_XCR1_MCKDIV_MASK   GENMASK(24, SAI_XCR1_MCKDIV_SHIFT)
+#define SAI_XCR1_MCKDIV_SET(x) ((x) << SAI_XCR1_MCKDIV_SHIFT)
+#define SAI_XCR1_MCKDIV_MAX    ((1 << SAI_XCR1_MCKDIV_WIDTH) - 1)
+
+#define SAI_XCR1_OSR_SHIFT     26
+#define SAI_XCR1_OSR           BIT(SAI_XCR1_OSR_SHIFT)
+
+/******************* Bit definition for SAI_XCR2 register *******************/
+#define SAI_XCR2_FTH_SHIFT     0
+#define SAI_XCR2_FTH_MASK      GENMASK(2, SAI_XCR2_FTH_SHIFT)
+#define SAI_XCR2_FTH_SET(x)    ((x) << SAI_XCR2_FTH_SHIFT)
+
+#define SAI_XCR2_FFLUSH_SHIFT  3
+#define SAI_XCR2_FFLUSH                BIT(SAI_XCR2_FFLUSH_SHIFT)
+#define SAI_XCR2_TRIS_SHIFT    4
+#define SAI_XCR2_TRIS          BIT(SAI_XCR2_TRIS_SHIFT)
+#define SAI_XCR2_MUTE_SHIFT    5
+#define SAI_XCR2_MUTE          BIT(SAI_XCR2_MUTE_SHIFT)
+#define SAI_XCR2_MUTEVAL_SHIFT 6
+#define SAI_XCR2_MUTEVAL       BIT(SAI_XCR2_MUTEVAL_SHIFT)
+
+#define SAI_XCR2_MUTECNT_SHIFT 7
+#define SAI_XCR2_MUTECNT_MASK  GENMASK(12, SAI_XCR2_MUTECNT_SHIFT)
+#define SAI_XCR2_MUTECNT_SET(x)        ((x) << SAI_XCR2_MUTECNT_SHIFT)
+
+#define SAI_XCR2_CPL_SHIFT     13
+#define SAI_XCR2_CPL           BIT(SAI_XCR2_CPL_SHIFT)
+
+#define SAI_XCR2_COMP_SHIFT    14
+#define SAI_XCR2_COMP_MASK     GENMASK(15, SAI_XCR2_COMP_SHIFT)
+#define SAI_XCR2_COMP_SET(x)   ((x) << SAI_XCR2_COMP_SHIFT)
+
+/****************** Bit definition for SAI_XFRCR register *******************/
+#define SAI_XFRCR_FRL_SHIFT    0
+#define SAI_XFRCR_FRL_MASK     GENMASK(7, SAI_XFRCR_FRL_SHIFT)
+#define SAI_XFRCR_FRL_SET(x)   ((x) << SAI_XFRCR_FRL_SHIFT)
+
+#define SAI_XFRCR_FSALL_SHIFT  8
+#define SAI_XFRCR_FSALL_MASK   GENMASK(14, SAI_XFRCR_FSALL_SHIFT)
+#define SAI_XFRCR_FSALL_SET(x) ((x) << SAI_XFRCR_FSALL_SHIFT)
+
+#define SAI_XFRCR_FSDEF_SHIFT  16
+#define SAI_XFRCR_FSDEF                BIT(SAI_XFRCR_FSDEF_SHIFT)
+#define SAI_XFRCR_FSPOL_SHIFT  17
+#define SAI_XFRCR_FSPOL                BIT(SAI_XFRCR_FSPOL_SHIFT)
+#define SAI_XFRCR_FSOFF_SHIFT  18
+#define SAI_XFRCR_FSOFF                BIT(SAI_XFRCR_FSOFF_SHIFT)
+
+/****************** Bit definition for SAI_XSLOTR register ******************/
+
+#define SAI_XSLOTR_FBOFF_SHIFT 0
+#define SAI_XSLOTR_FBOFF_MASK  GENMASK(4, SAI_XSLOTR_FBOFF_SHIFT)
+#define SAI_XSLOTR_FBOFF_SET(x)        ((x) << SAI_XSLOTR_FBOFF_SHIFT)
+
+#define SAI_XSLOTR_SLOTSZ_SHIFT        6
+#define SAI_XSLOTR_SLOTSZ_MASK GENMASK(7, SAI_XSLOTR_SLOTSZ_SHIFT)
+#define SAI_XSLOTR_SLOTSZ_SET(x)       ((x) << SAI_XSLOTR_SLOTSZ_SHIFT)
+
+#define SAI_XSLOTR_NBSLOT_SHIFT 8
+#define SAI_XSLOTR_NBSLOT_MASK GENMASK(11, SAI_XSLOTR_NBSLOT_SHIFT)
+#define SAI_XSLOTR_NBSLOT_SET(x) ((x) << SAI_XSLOTR_NBSLOT_SHIFT)
+
+#define SAI_XSLOTR_SLOTEN_SHIFT        16
+#define SAI_XSLOTR_SLOTEN_WIDTH        16
+#define SAI_XSLOTR_SLOTEN_MASK GENMASK(31, SAI_XSLOTR_SLOTEN_SHIFT)
+#define SAI_XSLOTR_SLOTEN_SET(x) ((x) << SAI_XSLOTR_SLOTEN_SHIFT)
+
+/******************* Bit definition for SAI_XIMR register *******************/
+#define SAI_XIMR_OVRUDRIE      BIT(0)
+#define SAI_XIMR_MUTEDETIE     BIT(1)
+#define SAI_XIMR_WCKCFGIE      BIT(2)
+#define SAI_XIMR_FREQIE                BIT(3)
+#define SAI_XIMR_CNRDYIE       BIT(4)
+#define SAI_XIMR_AFSDETIE      BIT(5)
+#define SAI_XIMR_LFSDETIE      BIT(6)
+
+#define SAI_XIMR_SHIFT 0
+#define SAI_XIMR_MASK          GENMASK(6, SAI_XIMR_SHIFT)
+
+/******************** Bit definition for SAI_XSR register *******************/
+#define SAI_XSR_OVRUDR         BIT(0)
+#define SAI_XSR_MUTEDET                BIT(1)
+#define SAI_XSR_WCKCFG         BIT(2)
+#define SAI_XSR_FREQ           BIT(3)
+#define SAI_XSR_CNRDY          BIT(4)
+#define SAI_XSR_AFSDET         BIT(5)
+#define SAI_XSR_LFSDET         BIT(6)
+
+#define SAI_XSR_SHIFT  0
+#define SAI_XSR_MASK           GENMASK(6, SAI_XSR_SHIFT)
+
+/****************** Bit definition for SAI_XCLRFR register ******************/
+#define SAI_XCLRFR_COVRUDR     BIT(0)
+#define SAI_XCLRFR_CMUTEDET    BIT(1)
+#define SAI_XCLRFR_CWCKCFG     BIT(2)
+#define SAI_XCLRFR_CFREQ       BIT(3)
+#define SAI_XCLRFR_CCNRDY      BIT(4)
+#define SAI_XCLRFR_CAFSDET     BIT(5)
+#define SAI_XCLRFR_CLFSDET     BIT(6)
+
+#define SAI_XCLRFR_SHIFT       0
+#define SAI_XCLRFR_MASK                GENMASK(6, SAI_XCLRFR_SHIFT)
+
+enum stm32_sai_version {
+       SAI_STM32F4
+};
+
+/**
+ * struct stm32_sai_data - private data of SAI instance driver
+ * @pdev: device data pointer
+ * @clk_x8k: SAI parent clock for sampling frequencies multiple of 8kHz
+ * @clk_x11k: SAI parent clock for sampling frequencies multiple of 11kHz
+ * @version: SOC version
+ * @irq: SAI interrupt line
+ */
+struct stm32_sai_data {
+       struct platform_device *pdev;
+       struct clk *clk_x8k;
+       struct clk *clk_x11k;
+       int version;
+       int irq;
+};
diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c
new file mode 100644 (file)
index 0000000..ae4706c
--- /dev/null
@@ -0,0 +1,884 @@
+/*
+ * STM32 ALSA SoC Digital Audio Interface (SAI) driver.
+ *
+ * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
+ * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics.
+ *
+ * License terms: GPL V2.0.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "stm32_sai.h"
+
+#define SAI_FREE_PROTOCOL      0x0
+
+#define SAI_SLOT_SIZE_AUTO     0x0
+#define SAI_SLOT_SIZE_16       0x1
+#define SAI_SLOT_SIZE_32       0x2
+
+#define SAI_DATASIZE_8         0x2
+#define SAI_DATASIZE_10                0x3
+#define SAI_DATASIZE_16                0x4
+#define SAI_DATASIZE_20                0x5
+#define SAI_DATASIZE_24                0x6
+#define SAI_DATASIZE_32                0x7
+
+#define STM_SAI_FIFO_SIZE      8
+#define STM_SAI_DAI_NAME_SIZE  15
+
+#define STM_SAI_IS_PLAYBACK(ip)        ((ip)->dir == SNDRV_PCM_STREAM_PLAYBACK)
+#define STM_SAI_IS_CAPTURE(ip) ((ip)->dir == SNDRV_PCM_STREAM_CAPTURE)
+
+#define STM_SAI_A_ID           0x0
+#define STM_SAI_B_ID           0x1
+
+#define STM_SAI_BLOCK_NAME(x)  (((x)->id == STM_SAI_A_ID) ? "A" : "B")
+
+/**
+ * struct stm32_sai_sub_data - private data of SAI sub block (block A or B)
+ * @pdev: device data pointer
+ * @regmap: SAI register map pointer
+ * @dma_params: dma configuration data for rx or tx channel
+ * @cpu_dai_drv: DAI driver data pointer
+ * @cpu_dai: DAI runtime data pointer
+ * @substream: PCM substream data pointer
+ * @pdata: SAI block parent data pointer
+ * @sai_ck: kernel clock feeding the SAI clock generator
+ * @phys_addr: SAI registers physical base address
+ * @mclk_rate: SAI block master clock frequency (Hz). set at init
+ * @id: SAI sub block id corresponding to sub-block A or B
+ * @dir: SAI block direction (playback or capture). set at init
+ * @master: SAI block mode flag. (true=master, false=slave) set at init
+ * @fmt: SAI block format. relevant only for custom protocols. set at init
+ * @sync: SAI block synchronization mode. (none, internal or external)
+ * @fs_length: frame synchronization length. depends on protocol settings
+ * @slots: rx or tx slot number
+ * @slot_width: rx or tx slot width in bits
+ * @slot_mask: rx or tx active slots mask. set at init or at runtime
+ * @data_size: PCM data width. corresponds to PCM substream width.
+ */
+struct stm32_sai_sub_data {
+       struct platform_device *pdev;
+       struct regmap *regmap;
+       struct snd_dmaengine_dai_dma_data dma_params;
+       struct snd_soc_dai_driver *cpu_dai_drv;
+       struct snd_soc_dai *cpu_dai;
+       struct snd_pcm_substream *substream;
+       struct stm32_sai_data *pdata;
+       struct clk *sai_ck;
+       dma_addr_t phys_addr;
+       unsigned int mclk_rate;
+       unsigned int id;
+       int dir;
+       bool master;
+       int fmt;
+       int sync;
+       int fs_length;
+       int slots;
+       int slot_width;
+       int slot_mask;
+       int data_size;
+};
+
+enum stm32_sai_fifo_th {
+       STM_SAI_FIFO_TH_EMPTY,
+       STM_SAI_FIFO_TH_QUARTER,
+       STM_SAI_FIFO_TH_HALF,
+       STM_SAI_FIFO_TH_3_QUARTER,
+       STM_SAI_FIFO_TH_FULL,
+};
+
+static bool stm32_sai_sub_readable_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case STM_SAI_CR1_REGX:
+       case STM_SAI_CR2_REGX:
+       case STM_SAI_FRCR_REGX:
+       case STM_SAI_SLOTR_REGX:
+       case STM_SAI_IMR_REGX:
+       case STM_SAI_SR_REGX:
+       case STM_SAI_CLRFR_REGX:
+       case STM_SAI_DR_REGX:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static bool stm32_sai_sub_volatile_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case STM_SAI_DR_REGX:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case STM_SAI_CR1_REGX:
+       case STM_SAI_CR2_REGX:
+       case STM_SAI_FRCR_REGX:
+       case STM_SAI_SLOTR_REGX:
+       case STM_SAI_IMR_REGX:
+       case STM_SAI_SR_REGX:
+       case STM_SAI_CLRFR_REGX:
+       case STM_SAI_DR_REGX:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static const struct regmap_config stm32_sai_sub_regmap_config = {
+       .reg_bits = 32,
+       .reg_stride = 4,
+       .val_bits = 32,
+       .max_register = STM_SAI_DR_REGX,
+       .readable_reg = stm32_sai_sub_readable_reg,
+       .volatile_reg = stm32_sai_sub_volatile_reg,
+       .writeable_reg = stm32_sai_sub_writeable_reg,
+       .fast_io = true,
+};
+
+static irqreturn_t stm32_sai_isr(int irq, void *devid)
+{
+       struct stm32_sai_sub_data *sai = (struct stm32_sai_sub_data *)devid;
+       struct snd_pcm_substream *substream = sai->substream;
+       struct platform_device *pdev = sai->pdev;
+       unsigned int sr, imr, flags;
+       snd_pcm_state_t status = SNDRV_PCM_STATE_RUNNING;
+
+       regmap_read(sai->regmap, STM_SAI_IMR_REGX, &imr);
+       regmap_read(sai->regmap, STM_SAI_SR_REGX, &sr);
+
+       flags = sr & imr;
+       if (!flags)
+               return IRQ_NONE;
+
+       regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK,
+                          SAI_XCLRFR_MASK);
+
+       if (flags & SAI_XIMR_OVRUDRIE) {
+               dev_err(&pdev->dev, "IT %s\n",
+                       STM_SAI_IS_PLAYBACK(sai) ? "underrun" : "overrun");
+               status = SNDRV_PCM_STATE_XRUN;
+       }
+
+       if (flags & SAI_XIMR_MUTEDETIE)
+               dev_dbg(&pdev->dev, "IT mute detected\n");
+
+       if (flags & SAI_XIMR_WCKCFGIE) {
+               dev_err(&pdev->dev, "IT wrong clock configuration\n");
+               status = SNDRV_PCM_STATE_DISCONNECTED;
+       }
+
+       if (flags & SAI_XIMR_CNRDYIE)
+               dev_warn(&pdev->dev, "IT Codec not ready\n");
+
+       if (flags & SAI_XIMR_AFSDETIE) {
+               dev_warn(&pdev->dev, "IT Anticipated frame synchro\n");
+               status = SNDRV_PCM_STATE_XRUN;
+       }
+
+       if (flags & SAI_XIMR_LFSDETIE) {
+               dev_warn(&pdev->dev, "IT Late frame synchro\n");
+               status = SNDRV_PCM_STATE_XRUN;
+       }
+
+       if (status != SNDRV_PCM_STATE_RUNNING) {
+               snd_pcm_stream_lock(substream);
+               snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+               snd_pcm_stream_unlock(substream);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai,
+                               int clk_id, unsigned int freq, int dir)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+
+       if ((dir == SND_SOC_CLOCK_OUT) && sai->master) {
+               sai->mclk_rate = freq;
+               dev_dbg(cpu_dai->dev, "SAI MCLK frequency is %uHz\n", freq);
+       }
+
+       return 0;
+}
+
+static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
+                                     u32 rx_mask, int slots, int slot_width)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int slotr, slotr_mask, slot_size;
+
+       dev_dbg(cpu_dai->dev, "masks tx/rx:%#x/%#x, slots:%d, width:%d\n",
+               tx_mask, rx_mask, slots, slot_width);
+
+       switch (slot_width) {
+       case 16:
+               slot_size = SAI_SLOT_SIZE_16;
+               break;
+       case 32:
+               slot_size = SAI_SLOT_SIZE_32;
+               break;
+       default:
+               slot_size = SAI_SLOT_SIZE_AUTO;
+               break;
+       }
+
+       slotr = SAI_XSLOTR_SLOTSZ_SET(slot_size) |
+               SAI_XSLOTR_NBSLOT_SET(slots - 1);
+       slotr_mask = SAI_XSLOTR_SLOTSZ_MASK | SAI_XSLOTR_NBSLOT_MASK;
+
+       /* tx/rx mask set in machine init, if slot number defined in DT */
+       if (STM_SAI_IS_PLAYBACK(sai)) {
+               sai->slot_mask = tx_mask;
+               slotr |= SAI_XSLOTR_SLOTEN_SET(tx_mask);
+       }
+
+       if (STM_SAI_IS_CAPTURE(sai)) {
+               sai->slot_mask = rx_mask;
+               slotr |= SAI_XSLOTR_SLOTEN_SET(rx_mask);
+       }
+
+       slotr_mask |= SAI_XSLOTR_SLOTEN_MASK;
+
+       regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, slotr_mask, slotr);
+
+       sai->slot_width = slot_width;
+       sai->slots = slots;
+
+       return 0;
+}
+
+static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int cr1 = 0, frcr = 0;
+       int cr1_mask = 0, frcr_mask = 0;
+       int ret;
+
+       dev_dbg(cpu_dai->dev, "fmt %x\n", fmt);
+
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       /* SCK active high for all protocols */
+       case SND_SOC_DAIFMT_I2S:
+               cr1 |= SAI_XCR1_CKSTR;
+               frcr |= SAI_XFRCR_FSOFF | SAI_XFRCR_FSDEF;
+               break;
+       /* Left justified */
+       case SND_SOC_DAIFMT_MSB:
+               frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF;
+               break;
+       /* Right justified */
+       case SND_SOC_DAIFMT_LSB:
+               frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF;
+               break;
+       case SND_SOC_DAIFMT_DSP_A:
+               frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF;
+               break;
+       case SND_SOC_DAIFMT_DSP_B:
+               frcr |= SAI_XFRCR_FSPOL;
+               break;
+       default:
+               dev_err(cpu_dai->dev, "Unsupported protocol %#x\n",
+                       fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+               return -EINVAL;
+       }
+
+       cr1_mask |= SAI_XCR1_PRTCFG_MASK | SAI_XCR1_CKSTR;
+       frcr_mask |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF |
+                    SAI_XFRCR_FSDEF;
+
+       /* DAI clock strobing. Invert setting previously set */
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_NB_NF:
+               break;
+       case SND_SOC_DAIFMT_IB_NF:
+               cr1 ^= SAI_XCR1_CKSTR;
+               break;
+       case SND_SOC_DAIFMT_NB_IF:
+               frcr ^= SAI_XFRCR_FSPOL;
+               break;
+       case SND_SOC_DAIFMT_IB_IF:
+               /* Invert fs & sck */
+               cr1 ^= SAI_XCR1_CKSTR;
+               frcr ^= SAI_XFRCR_FSPOL;
+               break;
+       default:
+               dev_err(cpu_dai->dev, "Unsupported strobing %#x\n",
+                       fmt & SND_SOC_DAIFMT_INV_MASK);
+               return -EINVAL;
+       }
+       cr1_mask |= SAI_XCR1_CKSTR;
+       frcr_mask |= SAI_XFRCR_FSPOL;
+
+       regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr);
+
+       /* DAI clock master masks */
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBM_CFM:
+               /* codec is master */
+               cr1 |= SAI_XCR1_SLAVE;
+               sai->master = false;
+               break;
+       case SND_SOC_DAIFMT_CBS_CFS:
+               sai->master = true;
+               break;
+       default:
+               dev_err(cpu_dai->dev, "Unsupported mode %#x\n",
+                       fmt & SND_SOC_DAIFMT_MASTER_MASK);
+               return -EINVAL;
+       }
+       cr1_mask |= SAI_XCR1_SLAVE;
+
+       ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
+       if (ret < 0) {
+               dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+               return ret;
+       }
+
+       sai->fmt = fmt;
+
+       return 0;
+}
+
+static int stm32_sai_startup(struct snd_pcm_substream *substream,
+                            struct snd_soc_dai *cpu_dai)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int imr, cr2, ret;
+
+       sai->substream = substream;
+
+       ret = clk_prepare_enable(sai->sai_ck);
+       if (ret < 0) {
+               dev_err(cpu_dai->dev, "failed to enable clock: %d\n", ret);
+               return ret;
+       }
+
+       /* Enable ITs */
+       regmap_update_bits(sai->regmap, STM_SAI_SR_REGX,
+                          SAI_XSR_MASK, (unsigned int)~SAI_XSR_MASK);
+
+       regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX,
+                          SAI_XCLRFR_MASK, SAI_XCLRFR_MASK);
+
+       imr = SAI_XIMR_OVRUDRIE;
+       if (STM_SAI_IS_CAPTURE(sai)) {
+               regmap_read(sai->regmap, STM_SAI_CR2_REGX, &cr2);
+               if (cr2 & SAI_XCR2_MUTECNT_MASK)
+                       imr |= SAI_XIMR_MUTEDETIE;
+       }
+
+       if (sai->master)
+               imr |= SAI_XIMR_WCKCFGIE;
+       else
+               imr |= SAI_XIMR_AFSDETIE | SAI_XIMR_LFSDETIE;
+
+       regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX,
+                          SAI_XIMR_MASK, imr);
+
+       return 0;
+}
+
+static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai,
+                               struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *params)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int cr1, cr1_mask, ret;
+       int fth = STM_SAI_FIFO_TH_HALF;
+
+       /* FIFO config */
+       regmap_update_bits(sai->regmap, STM_SAI_CR2_REGX,
+                          SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK,
+                          SAI_XCR2_FFLUSH | SAI_XCR2_FTH_SET(fth));
+
+       /* Mode, data format and channel config */
+       cr1 = SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL);
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S8:
+               cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_8);
+               break;
+       case SNDRV_PCM_FORMAT_S16_LE:
+               cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_16);
+               break;
+       case SNDRV_PCM_FORMAT_S32_LE:
+               cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_32);
+               break;
+       default:
+               dev_err(cpu_dai->dev, "Data format not supported");
+               return -EINVAL;
+       }
+       cr1_mask = SAI_XCR1_DS_MASK | SAI_XCR1_PRTCFG_MASK;
+
+       cr1_mask |= SAI_XCR1_RX_TX;
+       if (STM_SAI_IS_CAPTURE(sai))
+               cr1 |= SAI_XCR1_RX_TX;
+
+       cr1_mask |= SAI_XCR1_MONO;
+       if ((sai->slots == 2) && (params_channels(params) == 1))
+               cr1 |= SAI_XCR1_MONO;
+
+       ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
+       if (ret < 0) {
+               dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+               return ret;
+       }
+
+       /* DMA config */
+       sai->dma_params.maxburst = STM_SAI_FIFO_SIZE * fth / sizeof(u32);
+       snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)&sai->dma_params);
+
+       return 0;
+}
+
+static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int slotr, slot_sz;
+
+       regmap_read(sai->regmap, STM_SAI_SLOTR_REGX, &slotr);
+
+       /*
+        * If SLOTSZ is set to auto in SLOTR, align slot width on data size
+        * By default slot width = data size, if not forced from DT
+        */
+       slot_sz = slotr & SAI_XSLOTR_SLOTSZ_MASK;
+       if (slot_sz == SAI_XSLOTR_SLOTSZ_SET(SAI_SLOT_SIZE_AUTO))
+               sai->slot_width = sai->data_size;
+
+       if (sai->slot_width < sai->data_size) {
+               dev_err(cpu_dai->dev,
+                       "Data size %d larger than slot width\n",
+                       sai->data_size);
+               return -EINVAL;
+       }
+
+       /* Slot number is set to 2, if not specified in DT */
+       if (!sai->slots)
+               sai->slots = 2;
+
+       /* The number of slots in the audio frame is equal to NBSLOT[3:0] + 1*/
+       regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX,
+                          SAI_XSLOTR_NBSLOT_MASK,
+                          SAI_XSLOTR_NBSLOT_SET((sai->slots - 1)));
+
+       /* Set default slots mask if not already set from DT */
+       if (!(slotr & SAI_XSLOTR_SLOTEN_MASK)) {
+               sai->slot_mask = (1 << sai->slots) - 1;
+               regmap_update_bits(sai->regmap,
+                                  STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK,
+                                  SAI_XSLOTR_SLOTEN_SET(sai->slot_mask));
+       }
+
+       dev_dbg(cpu_dai->dev, "slots %d, slot width %d\n",
+               sai->slots, sai->slot_width);
+
+       return 0;
+}
+
+static void stm32_sai_set_frame(struct snd_soc_dai *cpu_dai)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int fs_active, offset, format;
+       int frcr, frcr_mask;
+
+       format = sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+       sai->fs_length = sai->slot_width * sai->slots;
+
+       fs_active = sai->fs_length / 2;
+       if ((format == SND_SOC_DAIFMT_DSP_A) ||
+           (format == SND_SOC_DAIFMT_DSP_B))
+               fs_active = 1;
+
+       frcr = SAI_XFRCR_FRL_SET((sai->fs_length - 1));
+       frcr |= SAI_XFRCR_FSALL_SET((fs_active - 1));
+       frcr_mask = SAI_XFRCR_FRL_MASK | SAI_XFRCR_FSALL_MASK;
+
+       dev_dbg(cpu_dai->dev, "frame length %d, frame active %d\n",
+               sai->fs_length, fs_active);
+
+       regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr);
+
+       if ((sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_LSB) {
+               offset = sai->slot_width - sai->data_size;
+
+               regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX,
+                                  SAI_XSLOTR_FBOFF_MASK,
+                                  SAI_XSLOTR_FBOFF_SET(offset));
+       }
+}
+
+static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai,
+                                    struct snd_pcm_hw_params *params)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int cr1, mask, div = 0;
+       int sai_clk_rate, ret;
+
+       if (!sai->mclk_rate) {
+               dev_err(cpu_dai->dev, "Mclk rate is null\n");
+               return -EINVAL;
+       }
+
+       if (!(params_rate(params) % 11025))
+               clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k);
+       else
+               clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k);
+       sai_clk_rate = clk_get_rate(sai->sai_ck);
+
+       /*
+        * mclk_rate = 256 * fs
+        * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate
+        * MCKDIV = sai_ck / (2 * mclk_rate) otherwise
+        */
+       if (2 * sai_clk_rate >= 3 * sai->mclk_rate)
+               div = DIV_ROUND_CLOSEST(sai_clk_rate, 2 * sai->mclk_rate);
+
+       if (div > SAI_XCR1_MCKDIV_MAX) {
+               dev_err(cpu_dai->dev, "Divider %d out of range\n", div);
+               return -EINVAL;
+       }
+       dev_dbg(cpu_dai->dev, "SAI clock %d, divider %d\n", sai_clk_rate, div);
+
+       mask = SAI_XCR1_MCKDIV_MASK;
+       cr1 = SAI_XCR1_MCKDIV_SET(div);
+       ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, mask, cr1);
+       if (ret < 0) {
+               dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int stm32_sai_hw_params(struct snd_pcm_substream *substream,
+                              struct snd_pcm_hw_params *params,
+                              struct snd_soc_dai *cpu_dai)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int ret;
+
+       sai->data_size = params_width(params);
+
+       ret = stm32_sai_set_slots(cpu_dai);
+       if (ret < 0)
+               return ret;
+       stm32_sai_set_frame(cpu_dai);
+
+       ret = stm32_sai_set_config(cpu_dai, substream, params);
+       if (ret)
+               return ret;
+
+       if (sai->master)
+               ret = stm32_sai_configure_clock(cpu_dai, params);
+
+       return ret;
+}
+
+static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd,
+                            struct snd_soc_dai *cpu_dai)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       int ret;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               dev_dbg(cpu_dai->dev, "Enable DMA and SAI\n");
+
+               regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
+                                  SAI_XCR1_DMAEN, SAI_XCR1_DMAEN);
+
+               /* Enable SAI */
+               ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
+                                        SAI_XCR1_SAIEN, SAI_XCR1_SAIEN);
+               if (ret < 0)
+                       dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+               break;
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+       case SNDRV_PCM_TRIGGER_STOP:
+               dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n");
+
+               regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
+                                  SAI_XCR1_DMAEN,
+                                  (unsigned int)~SAI_XCR1_DMAEN);
+
+               ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
+                                        SAI_XCR1_SAIEN,
+                                        (unsigned int)~SAI_XCR1_SAIEN);
+               if (ret < 0)
+                       dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return ret;
+}
+
+static void stm32_sai_shutdown(struct snd_pcm_substream *substream,
+                              struct snd_soc_dai *cpu_dai)
+{
+       struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
+
+       regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0);
+
+       clk_disable_unprepare(sai->sai_ck);
+       sai->substream = NULL;
+}
+
+static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+       struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev);
+
+       sai->dma_params.addr = (dma_addr_t)(sai->phys_addr + STM_SAI_DR_REGX);
+       sai->dma_params.maxburst = 1;
+       /* Buswidth will be set by framework at runtime */
+       sai->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+
+       if (STM_SAI_IS_PLAYBACK(sai))
+               snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params, NULL);
+       else
+               snd_soc_dai_init_dma_data(cpu_dai, NULL, &sai->dma_params);
+
+       return 0;
+}
+
+static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = {
+       .set_sysclk     = stm32_sai_set_sysclk,
+       .set_fmt        = stm32_sai_set_dai_fmt,
+       .set_tdm_slot   = stm32_sai_set_dai_tdm_slot,
+       .startup        = stm32_sai_startup,
+       .hw_params      = stm32_sai_hw_params,
+       .trigger        = stm32_sai_trigger,
+       .shutdown       = stm32_sai_shutdown,
+};
+
+static const struct snd_pcm_hardware stm32_sai_pcm_hw = {
+       .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP,
+       .buffer_bytes_max = 8 * PAGE_SIZE,
+       .period_bytes_min = 1024, /* 5ms at 48kHz */
+       .period_bytes_max = PAGE_SIZE,
+       .periods_min = 2,
+       .periods_max = 8,
+};
+
+static struct snd_soc_dai_driver stm32_sai_playback_dai[] = {
+{
+               .probe = stm32_sai_dai_probe,
+               .id = 1, /* avoid call to fmt_single_name() */
+               .playback = {
+                       .channels_min = 1,
+                       .channels_max = 2,
+                       .rate_min = 8000,
+                       .rate_max = 192000,
+                       .rates = SNDRV_PCM_RATE_CONTINUOUS,
+                       /* DMA does not support 24 bits transfers */
+                       .formats =
+                               SNDRV_PCM_FMTBIT_S8 |
+                               SNDRV_PCM_FMTBIT_S16_LE |
+                               SNDRV_PCM_FMTBIT_S32_LE,
+               },
+               .ops = &stm32_sai_pcm_dai_ops,
+       }
+};
+
+static struct snd_soc_dai_driver stm32_sai_capture_dai[] = {
+{
+               .probe = stm32_sai_dai_probe,
+               .id = 1, /* avoid call to fmt_single_name() */
+               .capture = {
+                       .channels_min = 1,
+                       .channels_max = 2,
+                       .rate_min = 8000,
+                       .rate_max = 192000,
+                       .rates = SNDRV_PCM_RATE_CONTINUOUS,
+                       /* DMA does not support 24 bits transfers */
+                       .formats =
+                               SNDRV_PCM_FMTBIT_S8 |
+                               SNDRV_PCM_FMTBIT_S16_LE |
+                               SNDRV_PCM_FMTBIT_S32_LE,
+               },
+               .ops = &stm32_sai_pcm_dai_ops,
+       }
+};
+
+static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = {
+       .pcm_hardware   = &stm32_sai_pcm_hw,
+       .prepare_slave_config   = snd_dmaengine_pcm_prepare_slave_config,
+};
+
+static const struct snd_soc_component_driver stm32_component = {
+       .name = "stm32-sai",
+};
+
+static const struct of_device_id stm32_sai_sub_ids[] = {
+       { .compatible = "st,stm32-sai-sub-a",
+         .data = (void *)STM_SAI_A_ID},
+       { .compatible = "st,stm32-sai-sub-b",
+         .data = (void *)STM_SAI_B_ID},
+       {}
+};
+MODULE_DEVICE_TABLE(of, stm32_sai_sub_ids);
+
+static int stm32_sai_sub_parse_of(struct platform_device *pdev,
+                                 struct stm32_sai_sub_data *sai)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct resource *res;
+       void __iomem *base;
+
+       if (!np)
+               return -ENODEV;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+       dev_err(&pdev->dev, "res %pr\n", res);
+
+       base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(base))
+               return PTR_ERR(base);
+
+       sai->phys_addr = res->start;
+       sai->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+                                           &stm32_sai_sub_regmap_config);
+
+       /* Get direction property */
+       if (of_property_match_string(np, "dma-names", "tx") >= 0) {
+               sai->dir = SNDRV_PCM_STREAM_PLAYBACK;
+       } else if (of_property_match_string(np, "dma-names", "rx") >= 0) {
+               sai->dir = SNDRV_PCM_STREAM_CAPTURE;
+       } else {
+               dev_err(&pdev->dev, "Unsupported direction\n");
+               return -EINVAL;
+       }
+
+       sai->sai_ck = devm_clk_get(&pdev->dev, "sai_ck");
+       if (IS_ERR(sai->sai_ck)) {
+               dev_err(&pdev->dev, "missing kernel clock sai_ck\n");
+               return PTR_ERR(sai->sai_ck);
+       }
+
+       return 0;
+}
+
+static int stm32_sai_sub_dais_init(struct platform_device *pdev,
+                                  struct stm32_sai_sub_data *sai)
+{
+       sai->cpu_dai_drv = devm_kzalloc(&pdev->dev,
+                                       sizeof(struct snd_soc_dai_driver),
+                                       GFP_KERNEL);
+       if (!sai->cpu_dai_drv)
+               return -ENOMEM;
+
+       sai->cpu_dai_drv->name = dev_name(&pdev->dev);
+       if (STM_SAI_IS_PLAYBACK(sai)) {
+               memcpy(sai->cpu_dai_drv, &stm32_sai_playback_dai,
+                      sizeof(stm32_sai_playback_dai));
+               sai->cpu_dai_drv->playback.stream_name = sai->cpu_dai_drv->name;
+       } else {
+               memcpy(sai->cpu_dai_drv, &stm32_sai_capture_dai,
+                      sizeof(stm32_sai_capture_dai));
+               sai->cpu_dai_drv->capture.stream_name = sai->cpu_dai_drv->name;
+       }
+
+       return 0;
+}
+
+static int stm32_sai_sub_probe(struct platform_device *pdev)
+{
+       struct stm32_sai_sub_data *sai;
+       const struct of_device_id *of_id;
+       int ret;
+
+       sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+       if (!sai)
+               return -ENOMEM;
+
+       of_id = of_match_device(stm32_sai_sub_ids, &pdev->dev);
+       if (!of_id)
+               return -EINVAL;
+       sai->id = (uintptr_t)of_id->data;
+
+       sai->pdev = pdev;
+       platform_set_drvdata(pdev, sai);
+
+       sai->pdata = dev_get_drvdata(pdev->dev.parent);
+       if (!sai->pdata) {
+               dev_err(&pdev->dev, "Parent device data not available\n");
+               return -EINVAL;
+       }
+
+       ret = stm32_sai_sub_parse_of(pdev, sai);
+       if (ret)
+               return ret;
+
+       ret = stm32_sai_sub_dais_init(pdev, sai);
+       if (ret)
+               return ret;
+
+       ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr,
+                              IRQF_SHARED, dev_name(&pdev->dev), sai);
+       if (ret) {
+               dev_err(&pdev->dev, "irq request returned %d\n", ret);
+               return ret;
+       }
+
+       ret = devm_snd_soc_register_component(&pdev->dev, &stm32_component,
+                                             sai->cpu_dai_drv, 1);
+       if (ret)
+               return ret;
+
+       ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
+                                             &stm32_sai_pcm_config, 0);
+       if (ret) {
+               dev_err(&pdev->dev, "could not register pcm dma\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static struct platform_driver stm32_sai_sub_driver = {
+       .driver = {
+               .name = "st,stm32-sai-sub",
+               .of_match_table = stm32_sai_sub_ids,
+       },
+       .probe = stm32_sai_sub_probe,
+};
+
+module_platform_driver(stm32_sai_sub_driver);
+
+MODULE_DESCRIPTION("STM32 Soc SAI sub-block Interface");
+MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>");
+MODULE_ALIAS("platform:st,stm32-sai-sub");
+MODULE_LICENSE("GPL v2");
index 72331332b72ee94a4f9d5a268bc2ef9b94ad278f..6c17c99c2c8dac2ebf2ceb2670d9d47419942163 100644 (file)
@@ -252,24 +252,15 @@ static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale,
 );
 
 static const struct snd_kcontrol_new sun8i_codec_common_controls[] = {
-       /* Mixer pre-gains */
-       SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL,
-                      SUN8I_ADDA_LINEIN_GCTRL_LINEING,
-                      0x7, 0, sun8i_codec_out_mixer_pregain_scale),
+       /* Mixer pre-gain */
        SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL,
                       SUN8I_ADDA_MICIN_GCTRL_MIC1G,
                       0x7, 0, sun8i_codec_out_mixer_pregain_scale),
-       SOC_SINGLE_TLV("Mic2 Playback Volume",
-                      SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G,
-                      0x7, 0, sun8i_codec_out_mixer_pregain_scale),
 
-       /* Microphone Amp boost gains */
+       /* Microphone Amp boost gain */
        SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
                       SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0,
                       sun8i_codec_mic_gain_scale),
-       SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL,
-                      SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0,
-                      sun8i_codec_mic_gain_scale),
 
        /* ADC */
        SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN,
@@ -295,12 +286,8 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = {
         * stream widgets at the card level.
         */
 
-       /* Line In */
-       SND_SOC_DAPM_INPUT("LINEIN"),
-
-       /* Microphone inputs */
+       /* Microphone input */
        SND_SOC_DAPM_INPUT("MIC1"),
-       SND_SOC_DAPM_INPUT("MIC2"),
 
        /* Microphone Bias */
        SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
@@ -310,8 +297,6 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = {
        /* Mic input path */
        SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
                         SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0),
-       SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL,
-                        SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0),
 
        /* Mixers */
        SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC,
@@ -335,35 +320,26 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = {
 static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = {
        /* Microphone Routes */
        { "Mic1 Amplifier", NULL, "MIC1"},
-       { "Mic2 Amplifier", NULL, "MIC2"},
 
        /* Left Mixer Routes */
        { "Left Mixer", "DAC Playback Switch", "Left DAC" },
        { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" },
-       { "Left Mixer", "Line In Playback Switch", "LINEIN" },
        { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
-       { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
 
        /* Right Mixer Routes */
        { "Right Mixer", "DAC Playback Switch", "Right DAC" },
        { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" },
-       { "Right Mixer", "Line In Playback Switch", "LINEIN" },
        { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
-       { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
 
        /* Left ADC Mixer Routes */
        { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" },
        { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" },
-       { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" },
        { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
-       { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
 
        /* Right ADC Mixer Routes */
        { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" },
        { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" },
-       { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" },
        { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
-       { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
 
        /* ADC Routes */
        { "Left ADC", NULL, "Left ADC Mixer" },
@@ -498,6 +474,61 @@ static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt)
        return ret;
 }
 
+/* line in specific controls, widgets and rines */
+static const struct snd_kcontrol_new sun8i_codec_linein_controls[] = {
+       /* Mixer pre-gain */
+       SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL,
+                      SUN8I_ADDA_LINEIN_GCTRL_LINEING,
+                      0x7, 0, sun8i_codec_out_mixer_pregain_scale),
+};
+
+static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = {
+       /* Line input */
+       SND_SOC_DAPM_INPUT("LINEIN"),
+};
+
+static const struct snd_soc_dapm_route sun8i_codec_linein_routes[] = {
+       { "Left Mixer", "Line In Playback Switch", "LINEIN" },
+
+       { "Right Mixer", "Line In Playback Switch", "LINEIN" },
+
+       { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" },
+
+       { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" },
+};
+
+static int sun8i_codec_add_linein(struct snd_soc_component *cmpnt)
+{
+       struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
+       struct device *dev = cmpnt->dev;
+       int ret;
+
+       ret = snd_soc_add_component_controls(cmpnt,
+                                            sun8i_codec_linein_controls,
+                                            ARRAY_SIZE(sun8i_codec_linein_controls));
+       if (ret) {
+               dev_err(dev, "Failed to add Line In controls: %d\n", ret);
+               return ret;
+       }
+
+       ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_linein_widgets,
+                                       ARRAY_SIZE(sun8i_codec_linein_widgets));
+       if (ret) {
+               dev_err(dev, "Failed to add Line In DAPM widgets: %d\n", ret);
+               return ret;
+       }
+
+       ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_linein_routes,
+                                     ARRAY_SIZE(sun8i_codec_linein_routes));
+       if (ret) {
+               dev_err(dev, "Failed to add Line In DAPM routes: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+
 /* line out specific controls, widgets and routes */
 static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale,
        0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1),
@@ -578,19 +609,90 @@ static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt)
        return 0;
 }
 
+/* mic2 specific controls, widgets and routes */
+static const struct snd_kcontrol_new sun8i_codec_mic2_controls[] = {
+       /* Mixer pre-gain */
+       SOC_SINGLE_TLV("Mic2 Playback Volume",
+                      SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G,
+                      0x7, 0, sun8i_codec_out_mixer_pregain_scale),
+
+       /* Microphone Amp boost gain */
+       SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL,
+                      SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0,
+                      sun8i_codec_mic_gain_scale),
+};
+
+static const struct snd_soc_dapm_widget sun8i_codec_mic2_widgets[] = {
+       /* Microphone input */
+       SND_SOC_DAPM_INPUT("MIC2"),
+
+       /* Mic input path */
+       SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL,
+                        SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route sun8i_codec_mic2_routes[] = {
+       { "Mic2 Amplifier", NULL, "MIC2"},
+
+       { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
+
+       { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
+
+       { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
+
+       { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
+};
+
+static int sun8i_codec_add_mic2(struct snd_soc_component *cmpnt)
+{
+       struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
+       struct device *dev = cmpnt->dev;
+       int ret;
+
+       ret = snd_soc_add_component_controls(cmpnt,
+                                            sun8i_codec_mic2_controls,
+                                            ARRAY_SIZE(sun8i_codec_mic2_controls));
+       if (ret) {
+               dev_err(dev, "Failed to add MIC2 controls: %d\n", ret);
+               return ret;
+       }
+
+       ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mic2_widgets,
+                                       ARRAY_SIZE(sun8i_codec_mic2_widgets));
+       if (ret) {
+               dev_err(dev, "Failed to add MIC2 DAPM widgets: %d\n", ret);
+               return ret;
+       }
+
+       ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mic2_routes,
+                                     ARRAY_SIZE(sun8i_codec_mic2_routes));
+       if (ret) {
+               dev_err(dev, "Failed to add MIC2 DAPM routes: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
 struct sun8i_codec_analog_quirks {
        bool has_headphone;
        bool has_hmic;
+       bool has_linein;
        bool has_lineout;
+       bool has_mic2;
 };
 
 static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = {
        .has_headphone  = true,
        .has_hmic       = true,
+       .has_linein     = true,
+       .has_mic2       = true,
 };
 
 static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = {
+       .has_linein     = true,
        .has_lineout    = true,
+       .has_mic2       = true,
 };
 
 static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt)
@@ -620,12 +722,24 @@ static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt)
                        return ret;
        }
 
+       if (quirks->has_linein) {
+               ret = sun8i_codec_add_linein(cmpnt);
+               if (ret)
+                       return ret;
+       }
+
        if (quirks->has_lineout) {
                ret = sun8i_codec_add_lineout(cmpnt);
                if (ret)
                        return ret;
        }
 
+       if (quirks->has_mic2) {
+               ret = sun8i_codec_add_mic2(cmpnt);
+               if (ret)
+                       return ret;
+       }
+
        return 0;
 }
 
index 7527ba29a5a0ea6eb9c6498d8d0293ad4aead18a..5723c3404f6bd6a896aed90b8b37d143de26c075 100644 (file)
@@ -290,12 +290,10 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = {
                            SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0),
 
        /* DAC Mixers */
-       SND_SOC_DAPM_MIXER("Left Digital DAC Mixer", SND_SOC_NOPM, 0, 0,
-                          sun8i_dac_mixer_controls,
-                          ARRAY_SIZE(sun8i_dac_mixer_controls)),
-       SND_SOC_DAPM_MIXER("Right Digital DAC Mixer", SND_SOC_NOPM, 0, 0,
-                          sun8i_dac_mixer_controls,
-                          ARRAY_SIZE(sun8i_dac_mixer_controls)),
+       SOC_MIXER_ARRAY("Left Digital DAC Mixer", SND_SOC_NOPM, 0, 0,
+                       sun8i_dac_mixer_controls),
+       SOC_MIXER_ARRAY("Right Digital DAC Mixer", SND_SOC_NOPM, 0, 0,
+                       sun8i_dac_mixer_controls),
 
        /* Clocks */
        SND_SOC_DAPM_SUPPLY("MODCLK AFI1", SUN8I_MOD_CLK_ENA,