aboutsummaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/Kconfig1
-rw-r--r--sound/soc/Makefile1
-rw-r--r--sound/soc/codecs/sta529.c7
-rw-r--r--sound/soc/codecs/tas2552.c6
-rw-r--r--sound/soc/sti/uniperif_player.c2
-rw-r--r--sound/soc/stm/Kconfig8
-rw-r--r--sound/soc/stm/Makefile6
-rw-r--r--sound/soc/stm/stm32_sai.c115
-rw-r--r--sound/soc/stm/stm32_sai.h200
-rw-r--r--sound/soc/stm/stm32_sai_sub.c884
-rw-r--r--sound/soc/sunxi/sun8i-codec-analog.c168
-rw-r--r--sound/soc/sunxi/sun8i-codec.c10
12 files changed, 1371 insertions, 37 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 9df9658b552b..c0abad2067e1 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -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"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 2f6aabb8b4c3..39c27a58158d 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -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/
diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c
index d4b384e4b266..660734359bf3 100644
--- a/sound/soc/codecs/sta529.c
+++ b/sound/soc/codecs/sta529.c
@@ -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,
diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c
index baf455e8c2f7..8840f72f3c4a 100644
--- a/sound/soc/codecs/tas2552.c
+++ b/sound/soc/codecs/tas2552.c
@@ -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
diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c
index d7e8dd46d2cc..d8b6936e544e 100644
--- a/sound/soc/sti/uniperif_player.c
+++ b/sound/soc/sti/uniperif_player.c
@@ -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
index 000000000000..972970f0890a
--- /dev/null
+++ b/sound/soc/stm/Kconfig
@@ -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
index 000000000000..e466a4759698
--- /dev/null
+++ b/sound/soc/stm/Makefile
@@ -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
index 000000000000..2a27a26bf7a1
--- /dev/null
+++ b/sound/soc/stm/stm32_sai.c
@@ -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
index 000000000000..a801fda5066f
--- /dev/null
+++ b/sound/soc/stm/stm32_sai.h
@@ -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
index 000000000000..ae4706ca265b
--- /dev/null
+++ b/sound/soc/stm/stm32_sai_sub.c
@@ -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");
diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c
index 72331332b72e..6c17c99c2c8d 100644
--- a/sound/soc/sunxi/sun8i-codec-analog.c
+++ b/sound/soc/sunxi/sun8i-codec-analog.c
@@ -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;
}
diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c
index 7527ba29a5a0..5723c3404f6b 100644
--- a/sound/soc/sunxi/sun8i-codec.c
+++ b/sound/soc/sunxi/sun8i-codec.c
@@ -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,

Privacy Policy