summaryrefslogtreecommitdiff
path: root/sound/soc/stm/stm32_sai.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2017-11-13 15:45:57 +0100
committerTakashi Iwai <tiwai@suse.de>2017-11-13 15:45:57 +0100
commit76727c2c3bf4a5e58dff8cca23d0147ba08fb2c8 (patch)
treec84c07b9deac4425190777a962f6788d355a0dd1 /sound/soc/stm/stm32_sai.c
parentc429bda21ffafb28f02fb2eb4055b4ab6879ed58 (diff)
parentdf6a3e245541ac61cc99f2887437e0a43dd08f2e (diff)
Merge tag 'asoc-v4.15' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into for-linus
ASoC: Updates for v4.15 The biggest thing this release has been the conversion of the AC98 bus to the driver model, that's been a long time coming so thanks to Robert Jarzmik for his dedication there. Due to there being some AC97 MFD there's a few fairly large changes in input and the MFD layer, mainly to the wm97xx driver. There's also some drivers/drm changes to support the new AMD Stoney platform, these are shared with the DRM subsystem and should be being merged via both. Within the subsystem the overwhelming bulk of the changes is in the Intel drivers which continue to need lots of cleanups and fixes, this release they've also gained support for their open source firmware. There's also some large changs in the core as Morimoto-san continues to mirror operations into the component level in preparation for conversion of drivers to that. - The AC97 bus has finally caught up with the driver model thanks to some dedicated and persistent work from Robert Jarzmik. - Continued work from Morimoto-san on moving us towards being able to use components for everything. - Lots of cleanups for the Intel platform code, including support for their open source audio firmware. - Support for scaling MCLK with sample rate in simple-card. - Support for AMD Stoney platform.
Diffstat (limited to 'sound/soc/stm/stm32_sai.c')
-rw-r--r--sound/soc/stm/stm32_sai.c162
1 files changed, 156 insertions, 6 deletions
diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c
index 1258bef4dcb3..d6f71a3406e9 100644
--- a/sound/soc/stm/stm32_sai.c
+++ b/sound/soc/stm/stm32_sai.c
@@ -16,6 +16,7 @@
* details.
*/
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/module.h>
@@ -27,6 +28,16 @@
#include "stm32_sai.h"
+static LIST_HEAD(sync_providers);
+static DEFINE_MUTEX(sync_mutex);
+
+struct sync_provider {
+ struct list_head link;
+ struct device_node *node;
+ int (*sync_conf)(void *data, int synco);
+ void *data;
+};
+
static const struct stm32_sai_conf stm32_sai_conf_f4 = {
.version = SAI_STM32F4,
};
@@ -41,23 +52,143 @@ static const struct of_device_id stm32_sai_ids[] = {
{}
};
+static int stm32_sai_sync_conf_client(struct stm32_sai_data *sai, int synci)
+{
+ int ret;
+
+ /* Enable peripheral clock to allow GCR register access */
+ ret = clk_prepare_enable(sai->pclk);
+ if (ret) {
+ dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ writel_relaxed(FIELD_PREP(SAI_GCR_SYNCIN_MASK, (synci - 1)), sai->base);
+
+ clk_disable_unprepare(sai->pclk);
+
+ return 0;
+}
+
+static int stm32_sai_sync_conf_provider(void *data, int synco)
+{
+ struct stm32_sai_data *sai = (struct stm32_sai_data *)data;
+ u32 prev_synco;
+ int ret;
+
+ /* Enable peripheral clock to allow GCR register access */
+ ret = clk_prepare_enable(sai->pclk);
+ if (ret) {
+ dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(&sai->pdev->dev, "Set %s%s as synchro provider\n",
+ sai->pdev->dev.of_node->name,
+ synco == STM_SAI_SYNC_OUT_A ? "A" : "B");
+
+ prev_synco = FIELD_GET(SAI_GCR_SYNCOUT_MASK, readl_relaxed(sai->base));
+ if (prev_synco != STM_SAI_SYNC_OUT_NONE && synco != prev_synco) {
+ dev_err(&sai->pdev->dev, "%s%s already set as sync provider\n",
+ sai->pdev->dev.of_node->name,
+ prev_synco == STM_SAI_SYNC_OUT_A ? "A" : "B");
+ clk_disable_unprepare(sai->pclk);
+ return -EINVAL;
+ }
+
+ writel_relaxed(FIELD_PREP(SAI_GCR_SYNCOUT_MASK, synco), sai->base);
+
+ clk_disable_unprepare(sai->pclk);
+
+ return 0;
+}
+
+static int stm32_sai_set_sync_provider(struct device_node *np, int synco)
+{
+ struct sync_provider *provider;
+ int ret;
+
+ mutex_lock(&sync_mutex);
+ list_for_each_entry(provider, &sync_providers, link) {
+ if (provider->node == np) {
+ ret = provider->sync_conf(provider->data, synco);
+ mutex_unlock(&sync_mutex);
+ return ret;
+ }
+ }
+ mutex_unlock(&sync_mutex);
+
+ /* SAI sync provider not found */
+ return -ENODEV;
+}
+
+static int stm32_sai_set_sync(struct stm32_sai_data *sai,
+ struct device_node *np_provider,
+ int synco, int synci)
+{
+ int ret;
+
+ /* Configure sync client */
+ stm32_sai_sync_conf_client(sai, synci);
+
+ /* Configure sync provider */
+ ret = stm32_sai_set_sync_provider(np_provider, synco);
+
+ return ret;
+}
+
+static int stm32_sai_sync_add_provider(struct platform_device *pdev,
+ void *data)
+{
+ struct sync_provider *sp;
+
+ sp = devm_kzalloc(&pdev->dev, sizeof(*sp), GFP_KERNEL);
+ if (!sp)
+ return -ENOMEM;
+
+ sp->node = of_node_get(pdev->dev.of_node);
+ sp->data = data;
+ sp->sync_conf = &stm32_sai_sync_conf_provider;
+
+ mutex_lock(&sync_mutex);
+ list_add(&sp->link, &sync_providers);
+ mutex_unlock(&sync_mutex);
+
+ return 0;
+}
+
+static void stm32_sai_sync_del_provider(struct device_node *np)
+{
+ struct sync_provider *sp;
+
+ mutex_lock(&sync_mutex);
+ list_for_each_entry(sp, &sync_providers, link) {
+ if (sp->node == np) {
+ list_del(&sp->link);
+ of_node_put(sp->node);
+ break;
+ }
+ }
+ mutex_unlock(&sync_mutex);
+}
+
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;
+ int ret;
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);
+ sai->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(sai->base))
+ return PTR_ERR(sai->base);
of_id = of_match_device(stm32_sai_ids, &pdev->dev);
if (of_id)
@@ -65,6 +196,14 @@ static int stm32_sai_probe(struct platform_device *pdev)
else
return -EINVAL;
+ if (!STM_SAI_IS_F4(sai)) {
+ sai->pclk = devm_clk_get(&pdev->dev, "pclk");
+ if (IS_ERR(sai->pclk)) {
+ dev_err(&pdev->dev, "missing bus clock pclk\n");
+ return PTR_ERR(sai->pclk);
+ }
+ }
+
sai->clk_x8k = devm_clk_get(&pdev->dev, "x8k");
if (IS_ERR(sai->clk_x8k)) {
dev_err(&pdev->dev, "missing x8k parent clock\n");
@@ -85,23 +224,34 @@ static int stm32_sai_probe(struct platform_device *pdev)
}
/* reset */
- rst = reset_control_get_exclusive(&pdev->dev, NULL);
+ rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (!IS_ERR(rst)) {
reset_control_assert(rst);
udelay(2);
reset_control_deassert(rst);
}
+ ret = stm32_sai_sync_add_provider(pdev, sai);
+ if (ret < 0)
+ return ret;
+ sai->set_sync = &stm32_sai_set_sync;
+
sai->pdev = pdev;
platform_set_drvdata(pdev, sai);
- return of_platform_populate(np, NULL, NULL, &pdev->dev);
+ ret = of_platform_populate(np, NULL, NULL, &pdev->dev);
+ if (ret < 0)
+ stm32_sai_sync_del_provider(np);
+
+ return ret;
}
static int stm32_sai_remove(struct platform_device *pdev)
{
of_platform_depopulate(&pdev->dev);
+ stm32_sai_sync_del_provider(pdev->dev.of_node);
+
return 0;
}