From e85a709974db40779f5942ed81e9262c62179863 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 8 Dec 2016 23:01:36 +0530 Subject: [PATCH 01/24] =?UTF-8?q?ASoC:=20samsung:=20smdk=5Fwm8580:=20remov?= =?UTF-8?q?e=20unused=20=E2=80=98bfs=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In smdk_hw_params(), 'bfs' is initialized and assigned bits based on params_width, but never used. We could have removed the whole switch case but then driver might be relying on checking bits, so I have kept the case for now. sound/soc/samsung/smdk_wm8580.c: In function ‘smdk_hw_params’: sound/soc/samsung/smdk_wm8580.c:35:6: warning: variable ‘bfs’ set but not used [-Wunused-but-set-variable] int bfs, rfs, ret; Signed-off-by: Vinod Koul Signed-off-by: Mark Brown --- sound/soc/samsung/smdk_wm8580.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c index de724ce7b955..6e4dfa7e2c89 100644 --- a/sound/soc/samsung/smdk_wm8580.c +++ b/sound/soc/samsung/smdk_wm8580.c @@ -32,14 +32,11 @@ static int smdk_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; unsigned int pll_out; - int bfs, rfs, ret; + int rfs, ret; switch (params_width(params)) { case 8: - bfs = 16; - break; case 16: - bfs = 32; break; default: return -EINVAL; From c7f87f96e384b7ecc41a6c0c8c397e095284ede0 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 9 Dec 2016 16:56:24 +0800 Subject: [PATCH 02/24] ASoC: rt5665: Make SND_SOC_RT5665 entry sort in Kconfig and Makefile Signed-off-by: Axel Lin Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 10 +++++----- sound/soc/codecs/Makefile | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 9e1718a8cb1c..cfc108e5e5ec 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -117,8 +117,8 @@ config SND_SOC_ALL_CODECS select SND_SOC_RT5651 if I2C select SND_SOC_RT5659 if I2C select SND_SOC_RT5660 if I2C - select SND_SOC_RT5665 if I2C select SND_SOC_RT5663 if I2C + select SND_SOC_RT5665 if I2C select SND_SOC_RT5670 if I2C select SND_SOC_RT5677 if I2C && SPI_MASTER select SND_SOC_SGTL5000 if I2C @@ -668,8 +668,8 @@ config SND_SOC_RL6231 default y if SND_SOC_RT5651=y default y if SND_SOC_RT5659=y default y if SND_SOC_RT5660=y - default y if SND_SOC_RT5665=y default y if SND_SOC_RT5663=y + default y if SND_SOC_RT5665=y default y if SND_SOC_RT5670=y default y if SND_SOC_RT5677=y default m if SND_SOC_RT5514=m @@ -679,8 +679,8 @@ config SND_SOC_RL6231 default m if SND_SOC_RT5651=m default m if SND_SOC_RT5659=m default m if SND_SOC_RT5660=m - default m if SND_SOC_RT5665=m default m if SND_SOC_RT5663=m + default m if SND_SOC_RT5665=m default m if SND_SOC_RT5670=m default m if SND_SOC_RT5677=m @@ -728,10 +728,10 @@ config SND_SOC_RT5659 config SND_SOC_RT5660 tristate -config SND_SOC_RT5665 +config SND_SOC_RT5663 tristate -config SND_SOC_RT5663 +config SND_SOC_RT5665 tristate config SND_SOC_RT5670 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 7e1dad79610b..2624c7324d4a 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -118,8 +118,8 @@ snd-soc-rt5645-objs := rt5645.o snd-soc-rt5651-objs := rt5651.o snd-soc-rt5659-objs := rt5659.o snd-soc-rt5660-objs := rt5660.o -snd-soc-rt5665-objs := rt5665.o snd-soc-rt5663-objs := rt5663.o +snd-soc-rt5665-objs := rt5665.o snd-soc-rt5670-objs := rt5670.o snd-soc-rt5677-objs := rt5677.o snd-soc-rt5677-spi-objs := rt5677-spi.o @@ -346,8 +346,8 @@ obj-$(CONFIG_SND_SOC_RT5645) += snd-soc-rt5645.o obj-$(CONFIG_SND_SOC_RT5651) += snd-soc-rt5651.o obj-$(CONFIG_SND_SOC_RT5659) += snd-soc-rt5659.o obj-$(CONFIG_SND_SOC_RT5660) += snd-soc-rt5660.o -obj-$(CONFIG_SND_SOC_RT5665) += snd-soc-rt5665.o obj-$(CONFIG_SND_SOC_RT5663) += snd-soc-rt5663.o +obj-$(CONFIG_SND_SOC_RT5665) += snd-soc-rt5665.o obj-$(CONFIG_SND_SOC_RT5670) += snd-soc-rt5670.o obj-$(CONFIG_SND_SOC_RT5677) += snd-soc-rt5677.o obj-$(CONFIG_SND_SOC_RT5677_SPI) += snd-soc-rt5677-spi.o From 5d079fdc12ffe1f939890035f5172374b5c0f2be Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Fri, 9 Dec 2016 19:12:50 +0100 Subject: [PATCH 03/24] ASoC: samsung: include gpio consumer.h Fix the following build errors on X86_32 !GPIOLIB sound/soc/samsung/tm2_wm5110.c:220:3: error: implicit declaration of function 'gpiod_set_value_cansleep' [-Werror=implicit-function-declaration] sound/soc/samsung/tm2_wm5110.c:438:24: error: implicit declaration of function 'devm_gpiod_get' [-Werror=implicit-function-declaration] Reviewed-by: Krzysztof Kozlowski Signed-off-by: Fabian Frederick Signed-off-by: Mark Brown --- sound/soc/samsung/tm2_wm5110.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/samsung/tm2_wm5110.c b/sound/soc/samsung/tm2_wm5110.c index 5cdf7d19b87f..24cc9d63ce87 100644 --- a/sound/soc/samsung/tm2_wm5110.c +++ b/sound/soc/samsung/tm2_wm5110.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include From 0223f500aa39a2b6df00af212da736232705be3e Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Fri, 9 Dec 2016 19:13:26 +0100 Subject: [PATCH 04/24] ASoC: samsung: add GPIOLIB dependency Both SND_SOC_SMARTQ and SND_SOC_SAMSUNG_TM2_WM5110 use gpio/consumer.h This patch adds GPIOLIB || COMPILE_TEST to Kconfig entries to fix runtime dependency. See commit 638f958baeaf ("extcon: Allow compile test of GPIO consumers if !GPIOLIB") for similar problem and explanations. Reviewed-by: Krzysztof Kozlowski Reported-by: Krzysztof Kozlowski Signed-off-by: Fabian Frederick Signed-off-by: Mark Brown --- sound/soc/samsung/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 7c423151ef7d..f1f1d7959a1b 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -111,6 +111,7 @@ config SND_SOC_SAMSUNG_RX1950_UDA1380 config SND_SOC_SMARTQ tristate "SoC I2S Audio support for SmartQ board" depends on MACH_SMARTQ || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST depends on I2C select SND_SAMSUNG_I2S select SND_SOC_WM8750 @@ -193,6 +194,7 @@ config SND_SOC_ARNDALE_RT5631_ALC5631 config SND_SOC_SAMSUNG_TM2_WM5110 tristate "SoC I2S Audio support for WM5110 on TM2 board" depends on SND_SOC_SAMSUNG && MFD_ARIZONA && I2C && SPI_MASTER + depends on GPIOLIB || COMPILE_TEST select SND_SOC_MAX98504 select SND_SOC_WM5110 select SND_SAMSUNG_I2S From 409c69be433b819c924a8d1c457a835bc6d51700 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 10 Dec 2016 11:51:11 +0200 Subject: [PATCH 05/24] ASoC: samsung: Remove tests of member address The driver was checking for non-NULL address of struct's members: - s3c_audio_pdata->type (union), - s3c_audio_pdata->type.i2s (embedded struct). This is pointless as these will be always non-NULL. The 's3c_audio_pdata' is always initialized in static memory so it will be zeroed. Additionally the 'type' member was an union with only one member. It is safe to reorganize the structures to get rid of useless union and checks for addresses to fix the coccinelle warning: >> sound/soc/samsung/i2s.c:1270:2-4: ERROR: test of a variable/field address Reported-by: kbuild test robot Signed-off-by: Krzysztof Kozlowski Reviewed-by: Bartlomiej Zolnierkiewicz Signed-off-by: Mark Brown --- arch/arm/mach-s3c64xx/dev-audio.c | 4 +--- include/linux/platform_data/asoc-s3c.h | 6 ++---- sound/soc/samsung/i2s.c | 10 ++-------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/arch/arm/mach-s3c64xx/dev-audio.c b/arch/arm/mach-s3c64xx/dev-audio.c index b57783371d52..247dcc0b691e 100644 --- a/arch/arm/mach-s3c64xx/dev-audio.c +++ b/arch/arm/mach-s3c64xx/dev-audio.c @@ -106,9 +106,7 @@ static struct s3c_audio_pdata i2sv4_pdata = { .dma_playback = DMACH_HSI_I2SV40_TX, .dma_capture = DMACH_HSI_I2SV40_RX, .type = { - .i2s = { - .quirks = QUIRK_PRI_6CHAN, - }, + .quirks = QUIRK_PRI_6CHAN, }, }; diff --git a/include/linux/platform_data/asoc-s3c.h b/include/linux/platform_data/asoc-s3c.h index 15bf56ee8af7..90641a5daaf0 100644 --- a/include/linux/platform_data/asoc-s3c.h +++ b/include/linux/platform_data/asoc-s3c.h @@ -18,7 +18,7 @@ extern void s3c64xx_ac97_setup_gpio(int); -struct samsung_i2s { +struct samsung_i2s_type { /* If the Primary DAI has 5.1 Channels */ #define QUIRK_PRI_6CHAN (1 << 0) /* If the I2S block has a Stereo Overlay Channel */ @@ -47,7 +47,5 @@ struct s3c_audio_pdata { void *dma_capture; void *dma_play_sec; void *dma_capture_mic; - union { - struct samsung_i2s i2s; - } type; + struct samsung_i2s_type type; }; diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index e00974bc5616..d55326289a4a 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1218,7 +1218,6 @@ static int samsung_i2s_probe(struct platform_device *pdev) { struct i2s_dai *pri_dai, *sec_dai = NULL; struct s3c_audio_pdata *i2s_pdata = pdev->dev.platform_data; - struct samsung_i2s *i2s_cfg = NULL; struct resource *res; u32 regs_base, quirks = 0, idma_addr = 0; struct device_node *np = pdev->dev.of_node; @@ -1267,13 +1266,8 @@ static int samsung_i2s_probe(struct platform_device *pdev) pri_dai->dma_capture.filter_data = i2s_pdata->dma_capture; pri_dai->filter = i2s_pdata->dma_filter; - if (&i2s_pdata->type) - i2s_cfg = &i2s_pdata->type.i2s; - - if (i2s_cfg) { - quirks = i2s_cfg->quirks; - idma_addr = i2s_cfg->idma_addr; - } + quirks = i2s_pdata->type.quirks; + idma_addr = i2s_pdata->type.idma_addr; } else { quirks = i2s_dai_data->quirks; if (of_property_read_u32(np, "samsung,idma-addr", From df3b5733496f7c375fcb200a5a82b7d89d75cfd1 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Thu, 15 Dec 2016 16:23:02 +0100 Subject: [PATCH 06/24] ASoC: rt5677: Remove unneeded linux/miscdevice.h include sound/soc/codecs/rt5677-spi.c does not use any miscdevice so this patch remove this unnecessary inclusion. Signed-off-by: Corentin Labbe Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677-spi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index ebd0f7c5ad3b..bd51f3655ee3 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include From be2c92eb64023e294d6bb9232578963670bb121b Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Thu, 29 Dec 2016 12:34:03 +0100 Subject: [PATCH 07/24] ASoC: samsung: i2s: Remove virtual device for secondary DAI For some unknown (maybe historical?) reasons support for secondary I2S DAI was implemented by adding additional virtual platform device, which was then probed again with the main I2S driver. This pattern is really hard to follow and provides no benefits, so lets remove this hack and register both DAIs during linear probe of Exynos I2S controller driver. Signed-off-by: Marek Szyprowski Signed-off-by: Mark Brown --- sound/soc/samsung/i2s.c | 102 ++++++++++------------------------------ 1 file changed, 26 insertions(+), 76 deletions(-) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index d55326289a4a..10b19a4afe86 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -34,11 +34,6 @@ #define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) -enum samsung_dai_type { - TYPE_PRI, - TYPE_SEC, -}; - struct samsung_i2s_variant_regs { unsigned int bfs_off; unsigned int rfs_off; @@ -54,7 +49,6 @@ struct samsung_i2s_variant_regs { }; struct samsung_i2s_dai_data { - int dai_type; u32 quirks; const struct samsung_i2s_variant_regs *i2s_variant_regs; }; @@ -1066,7 +1060,6 @@ static const struct snd_soc_component_driver samsung_i2s_component = { static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) { struct i2s_dai *i2s; - int ret; i2s = devm_kzalloc(&pdev->dev, sizeof(struct i2s_dai), GFP_KERNEL); if (i2s == NULL) @@ -1091,28 +1084,10 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) i2s->i2s_dai_drv.capture.channels_max = 2; i2s->i2s_dai_drv.capture.rates = SAMSUNG_I2S_RATES; i2s->i2s_dai_drv.capture.formats = SAMSUNG_I2S_FMTS; - dev_set_drvdata(&i2s->pdev->dev, i2s); - } else { /* Create a new platform_device for Secondary */ - i2s->pdev = platform_device_alloc("samsung-i2s-sec", -1); - if (!i2s->pdev) - return NULL; - - i2s->pdev->dev.parent = &pdev->dev; - - platform_set_drvdata(i2s->pdev, i2s); - ret = platform_device_add(i2s->pdev); - if (ret < 0) - return NULL; } - return i2s; } -static void i2s_free_sec_dai(struct i2s_dai *i2s) -{ - platform_device_del(i2s->pdev); -} - #ifdef CONFIG_PM static int i2s_runtime_suspend(struct device *dev) { @@ -1230,22 +1205,6 @@ static int samsung_i2s_probe(struct platform_device *pdev) i2s_dai_data = (struct samsung_i2s_dai_data *) platform_get_device_id(pdev)->driver_data; - /* Call during the secondary interface registration */ - if (i2s_dai_data->dai_type == TYPE_SEC) { - sec_dai = dev_get_drvdata(&pdev->dev); - if (!sec_dai) { - dev_err(&pdev->dev, "Unable to get drvdata\n"); - return -EFAULT; - } - ret = samsung_asoc_dma_platform_register(&pdev->dev, - sec_dai->filter, "tx-sec", NULL); - if (ret != 0) - return ret; - - return devm_snd_soc_register_component(&sec_dai->pdev->dev, - &samsung_i2s_component, - &sec_dai->i2s_dai_drv, 1); - } pri_dai = i2s_alloc_dai(pdev, false); if (!pri_dai) { @@ -1312,6 +1271,12 @@ static int samsung_i2s_probe(struct platform_device *pdev) if (ret < 0) goto err_disable_clk; + ret = devm_snd_soc_register_component(&pdev->dev, + &samsung_i2s_component, + &pri_dai->i2s_dai_drv, 1); + if (ret < 0) + goto err_disable_clk; + if (quirks & QUIRK_SEC_DAI) { sec_dai = i2s_alloc_dai(pdev, true); if (!sec_dai) { @@ -1336,6 +1301,17 @@ static int samsung_i2s_probe(struct platform_device *pdev) sec_dai->idma_playback.addr = idma_addr; sec_dai->pri_dai = pri_dai; pri_dai->sec_dai = sec_dai; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, + sec_dai->filter, "tx-sec", NULL); + if (ret < 0) + goto err_disable_clk; + + ret = devm_snd_soc_register_component(&pdev->dev, + &samsung_i2s_component, + &sec_dai->i2s_dai_drv, 1); + if (ret < 0) + goto err_disable_clk; } if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) { @@ -1344,11 +1320,7 @@ static int samsung_i2s_probe(struct platform_device *pdev) goto err_disable_clk; } - ret = devm_snd_soc_register_component(&pri_dai->pdev->dev, - &samsung_i2s_component, - &pri_dai->i2s_dai_drv, 1); - if (ret < 0) - goto err_free_dai; + dev_set_drvdata(&pdev->dev, pri_dai); pm_runtime_enable(&pdev->dev); @@ -1358,9 +1330,6 @@ static int samsung_i2s_probe(struct platform_device *pdev) return 0; pm_runtime_disable(&pdev->dev); -err_free_dai: - if (sec_dai) - i2s_free_sec_dai(sec_dai); err_disable_clk: clk_disable_unprepare(pri_dai->clk); return ret; @@ -1368,25 +1337,18 @@ err_disable_clk: static int samsung_i2s_remove(struct platform_device *pdev) { - struct i2s_dai *i2s, *other; + struct i2s_dai *pri_dai, *sec_dai; - i2s = dev_get_drvdata(&pdev->dev); - other = get_other_dai(i2s); + pri_dai = dev_get_drvdata(&pdev->dev); + sec_dai = pri_dai->sec_dai; - if (other) { - other->pri_dai = NULL; - other->sec_dai = NULL; - } else { - pm_runtime_disable(&pdev->dev); - } + pri_dai->sec_dai = NULL; + sec_dai->pri_dai = NULL; - if (!is_secondary(i2s)) { - i2s_unregister_clock_provider(pdev); - clk_disable_unprepare(i2s->clk); - } + pm_runtime_disable(&pdev->dev); - i2s->pri_dai = NULL; - i2s->sec_dai = NULL; + i2s_unregister_clock_provider(pdev); + clk_disable_unprepare(pri_dai->clk); return 0; } @@ -1448,49 +1410,37 @@ static const struct samsung_i2s_variant_regs i2sv5_i2s1_regs = { }; static const struct samsung_i2s_dai_data i2sv3_dai_type = { - .dai_type = TYPE_PRI, .quirks = QUIRK_NO_MUXPSR, .i2s_variant_regs = &i2sv3_regs, }; static const struct samsung_i2s_dai_data i2sv5_dai_type = { - .dai_type = TYPE_PRI, .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_IDMA, .i2s_variant_regs = &i2sv3_regs, }; static const struct samsung_i2s_dai_data i2sv6_dai_type = { - .dai_type = TYPE_PRI, .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM | QUIRK_SUPPORTS_IDMA, .i2s_variant_regs = &i2sv6_regs, }; static const struct samsung_i2s_dai_data i2sv7_dai_type = { - .dai_type = TYPE_PRI, .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM, .i2s_variant_regs = &i2sv7_regs, }; static const struct samsung_i2s_dai_data i2sv5_dai_type_i2s1 = { - .dai_type = TYPE_PRI, .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR, .i2s_variant_regs = &i2sv5_i2s1_regs, }; -static const struct samsung_i2s_dai_data samsung_dai_type_sec = { - .dai_type = TYPE_SEC, -}; - static const struct platform_device_id samsung_i2s_driver_ids[] = { { .name = "samsung-i2s", .driver_data = (kernel_ulong_t)&i2sv3_dai_type, - }, { - .name = "samsung-i2s-sec", - .driver_data = (kernel_ulong_t)&samsung_dai_type_sec, }, {}, }; From dc938ddb56283a0b71d987e7ecd4be90390985d6 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Thu, 29 Dec 2016 12:34:04 +0100 Subject: [PATCH 08/24] ASoC: samsung: i2s: Ensure proper runtime PM state of I2S device This patch adds calls to pm_runtime_get/put to ensure that any access to I2S registers is done with proper (active) runtime PM state of I2S device. Till now the driver enabled runtime PM, but didn't manage the state during driver operation. The driver worked fine only because the runtime PM callbacks managed device clock, which was enabled all the time because of the additional enable call in the driver's probe function. Signed-off-by: Marek Szyprowski Signed-off-by: Mark Brown --- sound/soc/samsung/i2s.c | 54 +++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 10b19a4afe86..8d8965e7107c 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -477,6 +477,9 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, unsigned int rsrc_mask = 1 << i2s_regs->rclksrc_off; u32 mod, mask, val = 0; unsigned long flags; + int ret = 0; + + pm_runtime_get_sync(dai->dev); spin_lock_irqsave(i2s->lock, flags); mod = readl(i2s->addr + I2SMOD); @@ -501,7 +504,8 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, && (mod & cdcon_mask))))) { dev_err(&i2s->pdev->dev, "%s:%d Other DAI busy\n", __func__, __LINE__); - return -EAGAIN; + ret = -EAGAIN; + goto err; } if (dir == SND_SOC_CLOCK_IN) @@ -529,7 +533,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, } else { i2s->rclk_srcrate = clk_get_rate(i2s->op_clk); - return 0; + goto done; } } @@ -540,8 +544,10 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, i2s->op_clk = clk_get(&i2s->pdev->dev, "i2s_opclk0"); - if (WARN_ON(IS_ERR(i2s->op_clk))) - return PTR_ERR(i2s->op_clk); + if (WARN_ON(IS_ERR(i2s->op_clk))) { + ret = PTR_ERR(i2s->op_clk); + goto err; + } clk_prepare_enable(i2s->op_clk); i2s->rclk_srcrate = clk_get_rate(i2s->op_clk); @@ -555,12 +561,13 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, || (clk_id && !(mod & rsrc_mask))) { dev_err(&i2s->pdev->dev, "%s:%d Other DAI busy\n", __func__, __LINE__); - return -EAGAIN; + ret = -EAGAIN; + goto err; } else { /* Call can't be on the active DAI */ i2s->op_clk = other->op_clk; i2s->rclk_srcrate = other->rclk_srcrate; - return 0; + goto done; } if (clk_id == 1) @@ -568,7 +575,8 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, break; default: dev_err(&i2s->pdev->dev, "We don't serve that!\n"); - return -EINVAL; + ret = -EINVAL; + goto err; } spin_lock_irqsave(i2s->lock, flags); @@ -576,8 +584,13 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, mod = (mod & ~mask) | val; writel(mod, i2s->addr + I2SMOD); spin_unlock_irqrestore(i2s->lock, flags); +done: + pm_runtime_put(dai->dev); return 0; +err: + pm_runtime_put(dai->dev); + return ret; } static int i2s_set_fmt(struct snd_soc_dai *dai, @@ -646,6 +659,7 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, return -EINVAL; } + pm_runtime_get_sync(dai->dev); spin_lock_irqsave(i2s->lock, flags); mod = readl(i2s->addr + I2SMOD); /* @@ -655,6 +669,7 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, if (any_active(i2s) && ((mod & (sdf_mask | lrp_rlow | mod_slave)) != tmp)) { spin_unlock_irqrestore(i2s->lock, flags); + pm_runtime_put(dai->dev); dev_err(&i2s->pdev->dev, "%s:%d Other DAI busy\n", __func__, __LINE__); return -EAGAIN; @@ -664,6 +679,7 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, mod |= tmp; writel(mod, i2s->addr + I2SMOD); spin_unlock_irqrestore(i2s->lock, flags); + pm_runtime_put(dai->dev); return 0; } @@ -675,6 +691,8 @@ static int i2s_hw_params(struct snd_pcm_substream *substream, u32 mod, mask = 0, val = 0; unsigned long flags; + WARN_ON(!pm_runtime_active(dai->dev)); + if (!is_secondary(i2s)) mask |= (MOD_DC2_EN | MOD_DC1_EN); @@ -763,6 +781,8 @@ static int i2s_startup(struct snd_pcm_substream *substream, struct i2s_dai *other = get_other_dai(i2s); unsigned long flags; + pm_runtime_get_sync(dai->dev); + spin_lock_irqsave(&lock, flags); i2s->mode |= DAI_OPENED; @@ -800,6 +820,8 @@ static void i2s_shutdown(struct snd_pcm_substream *substream, i2s->bfs = 0; spin_unlock_irqrestore(&lock, flags); + + pm_runtime_put(dai->dev); } static int config_setup(struct i2s_dai *i2s) @@ -874,6 +896,7 @@ static int i2s_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pm_runtime_get_sync(dai->dev); spin_lock_irqsave(i2s->lock, flags); if (config_setup(i2s)) { @@ -902,6 +925,7 @@ static int i2s_trigger(struct snd_pcm_substream *substream, } spin_unlock_irqrestore(i2s->lock, flags); + pm_runtime_put(dai->dev); break; } @@ -916,13 +940,16 @@ static int i2s_set_clkdiv(struct snd_soc_dai *dai, switch (div_id) { case SAMSUNG_I2S_DIV_BCLK: + pm_runtime_get_sync(dai->dev); if ((any_active(i2s) && div && (get_bfs(i2s) != div)) || (other && other->bfs && (other->bfs != div))) { + pm_runtime_put(dai->dev); dev_err(&i2s->pdev->dev, "%s:%d Other DAI busy\n", __func__, __LINE__); return -EAGAIN; } i2s->bfs = div; + pm_runtime_put(dai->dev); break; default: dev_err(&i2s->pdev->dev, @@ -941,6 +968,8 @@ i2s_delay(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) snd_pcm_sframes_t delay; const struct samsung_i2s_variant_regs *i2s_regs = i2s->variant_regs; + WARN_ON(!pm_runtime_active(dai->dev)); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) delay = FIC_RXCOUNT(reg); else if (is_secondary(i2s)) @@ -984,6 +1013,8 @@ static int samsung_i2s_dai_probe(struct snd_soc_dai *dai) struct i2s_dai *other = get_other_dai(i2s); unsigned long flags; + pm_runtime_get_sync(dai->dev); + if (is_secondary(i2s)) { /* If this is probe on the secondary DAI */ snd_soc_dai_init_dma_data(dai, &other->sec_dai->dma_playback, NULL); @@ -1016,6 +1047,7 @@ static int samsung_i2s_dai_probe(struct snd_soc_dai *dai) if (!is_opened(other)) i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK, 0, SND_SOC_CLOCK_IN); + pm_runtime_put(dai->dev); return 0; } @@ -1025,6 +1057,8 @@ static int samsung_i2s_dai_remove(struct snd_soc_dai *dai) struct i2s_dai *i2s = snd_soc_dai_get_drvdata(dai); unsigned long flags; + pm_runtime_get_sync(dai->dev); + if (!is_secondary(i2s)) { if (i2s->quirks & QUIRK_NEED_RSTCLR) { spin_lock_irqsave(i2s->lock, flags); @@ -1033,6 +1067,8 @@ static int samsung_i2s_dai_remove(struct snd_soc_dai *dai) } } + pm_runtime_put(dai->dev); + return 0; } @@ -1322,7 +1358,7 @@ static int samsung_i2s_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, pri_dai); - + pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); ret = i2s_register_clock_provider(pdev); @@ -1345,10 +1381,12 @@ static int samsung_i2s_remove(struct platform_device *pdev) pri_dai->sec_dai = NULL; sec_dai->pri_dai = NULL; + pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); i2s_unregister_clock_provider(pdev); clk_disable_unprepare(pri_dai->clk); + pm_runtime_put_noidle(&pdev->dev); return 0; } From e7e52dfc68a2160570c7ec51415e391961160edb Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Thu, 29 Dec 2016 12:34:05 +0100 Subject: [PATCH 09/24] ASoC: samsung: i2s: Move saving and restoring regs to runtime pm operations This patch moves saving and restoring I2S registers to runtime PM operations, what prepares the driver to operate with audio power domain. When support for audio power domain is enabled and the domain is being turned off, the I2S module will loose its context (registers), so runtime callbacks have to handle it. System sleep suspend/resume operation are implemented on top of runtime PM operations with generic pm_runtime_force_suspend/resume helpers. Signed-off-by: Marek Szyprowski Signed-off-by: Mark Brown --- sound/soc/samsung/i2s.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 8d8965e7107c..df3fae862665 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -983,24 +983,12 @@ i2s_delay(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) #ifdef CONFIG_PM static int i2s_suspend(struct snd_soc_dai *dai) { - struct i2s_dai *i2s = to_info(dai); - - i2s->suspend_i2smod = readl(i2s->addr + I2SMOD); - i2s->suspend_i2scon = readl(i2s->addr + I2SCON); - i2s->suspend_i2spsr = readl(i2s->addr + I2SPSR); - - return 0; + return pm_runtime_force_suspend(dai->dev); } static int i2s_resume(struct snd_soc_dai *dai) { - struct i2s_dai *i2s = to_info(dai); - - writel(i2s->suspend_i2scon, i2s->addr + I2SCON); - writel(i2s->suspend_i2smod, i2s->addr + I2SMOD); - writel(i2s->suspend_i2spsr, i2s->addr + I2SPSR); - - return 0; + return pm_runtime_force_resume(dai->dev); } #else #define i2s_suspend NULL @@ -1129,6 +1117,10 @@ static int i2s_runtime_suspend(struct device *dev) { struct i2s_dai *i2s = dev_get_drvdata(dev); + i2s->suspend_i2smod = readl(i2s->addr + I2SMOD); + i2s->suspend_i2scon = readl(i2s->addr + I2SCON); + i2s->suspend_i2spsr = readl(i2s->addr + I2SPSR); + clk_disable_unprepare(i2s->clk); return 0; @@ -1140,6 +1132,10 @@ static int i2s_runtime_resume(struct device *dev) clk_prepare_enable(i2s->clk); + writel(i2s->suspend_i2scon, i2s->addr + I2SCON); + writel(i2s->suspend_i2smod, i2s->addr + I2SMOD); + writel(i2s->suspend_i2spsr, i2s->addr + I2SPSR); + return 0; } #endif /* CONFIG_PM */ @@ -1510,6 +1506,8 @@ MODULE_DEVICE_TABLE(of, exynos_i2s_match); static const struct dev_pm_ops samsung_i2s_pm = { SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; static struct platform_driver samsung_i2s_driver = { From afa99da863e8e00efd8ce2f8840ed31d50abb889 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Thu, 29 Dec 2016 12:34:06 +0100 Subject: [PATCH 10/24] ASoC: samsung: i2s: Let runtime PM operations to control op_clk too This patch adds handling of parent operational clock to runtime PM callbacks. This way it is ensured that when I2S module is in runtime suspended state, all its parent clocks are disabled and unprepared. Signed-off-by: Marek Szyprowski Signed-off-by: Mark Brown --- sound/soc/samsung/i2s.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index df3fae862665..b2b9ee4a177a 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -546,6 +546,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, if (WARN_ON(IS_ERR(i2s->op_clk))) { ret = PTR_ERR(i2s->op_clk); + i2s->op_clk = NULL; goto err; } @@ -1121,6 +1122,8 @@ static int i2s_runtime_suspend(struct device *dev) i2s->suspend_i2scon = readl(i2s->addr + I2SCON); i2s->suspend_i2spsr = readl(i2s->addr + I2SPSR); + if (i2s->op_clk) + clk_disable_unprepare(i2s->op_clk); clk_disable_unprepare(i2s->clk); return 0; @@ -1131,6 +1134,8 @@ static int i2s_runtime_resume(struct device *dev) struct i2s_dai *i2s = dev_get_drvdata(dev); clk_prepare_enable(i2s->clk); + if (i2s->op_clk) + clk_prepare_enable(i2s->op_clk); writel(i2s->suspend_i2scon, i2s->addr + I2SCON); writel(i2s->suspend_i2smod, i2s->addr + I2SMOD); From 9b41da80e09128574f09bed8dc5a5fc6f72a8239 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Thu, 29 Dec 2016 12:34:07 +0100 Subject: [PATCH 11/24] ASoC: samsung: i2s: Provide I2S device for registered clocks This patch adds pointer to I2S device to clk_register_* functions. This in the future allow clock framework to ensure proper runtime state of the I2S device during all operations on the clocks provided by I2S module. Signed-off-by: Marek Szyprowski Signed-off-by: Mark Brown --- sound/soc/samsung/i2s.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index b2b9ee4a177a..2a5b92c672fb 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1191,13 +1191,13 @@ static int i2s_register_clock_provider(struct platform_device *pdev) u32 val = readl(i2s->addr + I2SPSR); writel(val | PSR_PSREN, i2s->addr + I2SPSR); - i2s->clk_table[CLK_I2S_RCLK_SRC] = clk_register_mux(NULL, + i2s->clk_table[CLK_I2S_RCLK_SRC] = clk_register_mux(dev, "i2s_rclksrc", p_names, ARRAY_SIZE(p_names), CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, i2s->addr + I2SMOD, reg_info->rclksrc_off, 1, 0, i2s->lock); - i2s->clk_table[CLK_I2S_RCLK_PSR] = clk_register_divider(NULL, + i2s->clk_table[CLK_I2S_RCLK_PSR] = clk_register_divider(dev, "i2s_presc", "i2s_rclksrc", CLK_SET_RATE_PARENT, i2s->addr + I2SPSR, 8, 6, 0, i2s->lock); @@ -1208,7 +1208,7 @@ static int i2s_register_clock_provider(struct platform_device *pdev) of_property_read_string_index(dev->of_node, "clock-output-names", 0, &clk_name[0]); - i2s->clk_table[CLK_I2S_CDCLK] = clk_register_gate(NULL, clk_name[0], + i2s->clk_table[CLK_I2S_CDCLK] = clk_register_gate(dev, clk_name[0], p_names[0], CLK_SET_RATE_PARENT, i2s->addr + I2SMOD, reg_info->cdclkcon_off, CLK_GATE_SET_TO_DISABLE, i2s->lock); From 96e53c41e1f81c9e9d1ce38d3f28b95668b71dcf Mon Sep 17 00:00:00 2001 From: Marcus Cooper Date: Tue, 20 Dec 2016 15:49:13 +0100 Subject: [PATCH 12/24] ASoC: sun4i-spdif: remove legacy dapm components The dapm components are now handled by the ALSA SoC SPDIF DIT driver so can be removed. Signed-off-by: Marcus Cooper Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-spdif.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index 88fbb3a1e660..048de15d6937 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -403,14 +403,6 @@ static struct snd_soc_dai_driver sun4i_spdif_dai = { .name = "spdif", }; -static const struct snd_soc_dapm_widget dit_widgets[] = { - SND_SOC_DAPM_OUTPUT("spdif-out"), -}; - -static const struct snd_soc_dapm_route dit_routes[] = { - { "spdif-out", NULL, "Playback" }, -}; - static const struct of_device_id sun4i_spdif_of_match[] = { { .compatible = "allwinner,sun4i-a10-spdif", }, { .compatible = "allwinner,sun6i-a31-spdif", }, From 7762681a3ada5fca6017e75ea7f9cdac08fc50b9 Mon Sep 17 00:00:00 2001 From: Marcus Cooper Date: Tue, 20 Dec 2016 15:49:14 +0100 Subject: [PATCH 13/24] ASoC: sun4i-spdif: Add quirks to the spdif driver It has been seen that some newer SoCs have a different TX FIFO address and we already have the difference with the A31 requiring a reset. Add a quirks structure so that these can be managed easily. Signed-off-by: Marcus Cooper Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-spdif.c | 36 ++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index 048de15d6937..fec62ee1fc72 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -403,9 +403,29 @@ static struct snd_soc_dai_driver sun4i_spdif_dai = { .name = "spdif", }; +struct sun4i_spdif_quirks { + unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */ + bool has_reset; +}; + +static const struct sun4i_spdif_quirks sun4i_a10_spdif_quirks = { + .reg_dac_txdata = SUN4I_SPDIF_TXFIFO, +}; + +static const struct sun4i_spdif_quirks sun6i_a31_spdif_quirks = { + .reg_dac_txdata = SUN4I_SPDIF_TXFIFO, + .has_reset = true, +}; + static const struct of_device_id sun4i_spdif_of_match[] = { - { .compatible = "allwinner,sun4i-a10-spdif", }, - { .compatible = "allwinner,sun6i-a31-spdif", }, + { + .compatible = "allwinner,sun4i-a10-spdif", + .data = &sun4i_a10_spdif_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-spdif", + .data = &sun6i_a31_spdif_quirks, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sun4i_spdif_of_match); @@ -438,6 +458,7 @@ static int sun4i_spdif_probe(struct platform_device *pdev) { struct sun4i_spdif_dev *host; struct resource *res; + const struct sun4i_spdif_quirks *quirks; int ret; void __iomem *base; @@ -459,6 +480,12 @@ static int sun4i_spdif_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); + quirks = of_device_get_match_data(&pdev->dev); + if (quirks == NULL) { + dev_err(&pdev->dev, "Failed to determine the quirks to use\n"); + return -ENODEV; + } + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, &sun4i_spdif_regmap_config); @@ -476,14 +503,13 @@ static int sun4i_spdif_probe(struct platform_device *pdev) goto err_disable_apb_clk; } - host->dma_params_tx.addr = res->start + SUN4I_SPDIF_TXFIFO; + host->dma_params_tx.addr = res->start + quirks->reg_dac_txdata; host->dma_params_tx.maxburst = 8; host->dma_params_tx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; platform_set_drvdata(pdev, host); - if (of_device_is_compatible(pdev->dev.of_node, - "allwinner,sun6i-a31-spdif")) { + if (quirks->has_reset) { host->rst = devm_reset_control_get_optional(&pdev->dev, NULL); if (IS_ERR(host->rst) && PTR_ERR(host->rst) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; From 19426bdedb72b965db0ebf2106e95e9eeb3b5935 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Sun, 8 Jan 2017 02:57:06 +0800 Subject: [PATCH 14/24] ASoC: sun4i-codec: Add "Right Mixer" to "Line Out Mono Diff." route The mono differential output for "Line Out" downmixes the stereo audio from the mixer, instead of just taking the left channel. Add a route from the "Right Mixer" to "Line Out Source Playback Route" through the "Mono Differential" path, so DAPM doesn't shut down everything if the left channel is muted. Fixes: 0f909f98d7cb ("ASoC: sun4i-codec: Add support for A31 Line Out playback") Signed-off-by: Chen-Yu Tsai Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-codec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c index 848af01692a0..c3aab10fa085 100644 --- a/sound/soc/sunxi/sun4i-codec.c +++ b/sound/soc/sunxi/sun4i-codec.c @@ -1058,6 +1058,7 @@ static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = { { "Line Out Source Playback Route", "Stereo", "Left Mixer" }, { "Line Out Source Playback Route", "Stereo", "Right Mixer" }, { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" }, + { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" }, { "LINEOUT", NULL, "Line Out Source Playback Route" }, /* ADC Routes */ From cb5c978f9a56c459d5f13901efcfe44b97c4182d Mon Sep 17 00:00:00 2001 From: Marcus Cooper Date: Thu, 19 Jan 2017 20:52:57 +0100 Subject: [PATCH 15/24] ASoC: sunxi: Add bindings for sun8i to SPDIF The H3 SoC uses the same SPDIF block as found in earlier SoCs, but the transmit fifo is at a different address. Signed-off-by: Marcus Cooper Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/sunxi,sun4i-spdif.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/sound/sunxi,sun4i-spdif.txt b/Documentation/devicetree/bindings/sound/sunxi,sun4i-spdif.txt index 0230c4d20506..fe0a65e6d629 100644 --- a/Documentation/devicetree/bindings/sound/sunxi,sun4i-spdif.txt +++ b/Documentation/devicetree/bindings/sound/sunxi,sun4i-spdif.txt @@ -10,6 +10,7 @@ Required properties: - compatible : should be one of the following: - "allwinner,sun4i-a10-spdif": for the Allwinner A10 SoC - "allwinner,sun6i-a31-spdif": for the Allwinner A31 SoC + - "allwinner,sun8i-h3-spdif": for the Allwinner H3 SoC - reg : Offset and length of the register set for the device. From 1bd92af877abfeddcc4b83a35482ed4139591acf Mon Sep 17 00:00:00 2001 From: Marcus Cooper Date: Thu, 19 Jan 2017 20:52:58 +0100 Subject: [PATCH 16/24] ASoC: sun4i-spdif: Add support for the H3 SoC The H3 SoC uses the same SPDIF block as found in earlier SoCs, but its TXFIFO is mapped to another address. Signed-off-by: Marcus Cooper Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-spdif.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index fec62ee1fc72..c03cd07a9b19 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -103,6 +103,8 @@ #define SUN4I_SPDIF_ISTA_RXOSTA BIT(1) #define SUN4I_SPDIF_ISTA_RXASTA BIT(0) +#define SUN8I_SPDIF_TXFIFO (0x20) + #define SUN4I_SPDIF_TXCNT (0x24) #define SUN4I_SPDIF_RXCNT (0x28) @@ -417,6 +419,11 @@ static const struct sun4i_spdif_quirks sun6i_a31_spdif_quirks = { .has_reset = true, }; +static const struct sun4i_spdif_quirks sun8i_h3_spdif_quirks = { + .reg_dac_txdata = SUN8I_SPDIF_TXFIFO, + .has_reset = true, +}; + static const struct of_device_id sun4i_spdif_of_match[] = { { .compatible = "allwinner,sun4i-a10-spdif", @@ -426,6 +433,10 @@ static const struct of_device_id sun4i_spdif_of_match[] = { .compatible = "allwinner,sun6i-a31-spdif", .data = &sun6i_a31_spdif_quirks, }, + { + .compatible = "allwinner,sun8i-h3-spdif", + .data = &sun8i_h3_spdif_quirks, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sun4i_spdif_of_match); From e984fd61e860ce3c45e79d69cf214b8cc6cae7d9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 23 Jan 2017 07:29:42 +0000 Subject: [PATCH 17/24] ASoC: simple-card: use devm_get_clk_from_child() Current simple-card-utils is getting clk by of_clk_get(), but didn't call clk_free(). Now we can use devm_get_clk_from_child() for this purpose. Let's use it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown --- include/sound/simple_card_utils.h | 11 ++++++----- sound/soc/generic/simple-card-utils.c | 8 ++++---- sound/soc/generic/simple-card.c | 4 ++-- sound/soc/generic/simple-scu-card.c | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index 64e90ca9ad32..af58d2362975 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -34,11 +34,12 @@ int asoc_simple_card_set_dailink_name(struct device *dev, int asoc_simple_card_parse_card_name(struct snd_soc_card *card, char *prefix); -#define asoc_simple_card_parse_clk_cpu(node, dai_link, simple_dai) \ - asoc_simple_card_parse_clk(node, dai_link->cpu_of_node, simple_dai) -#define asoc_simple_card_parse_clk_codec(node, dai_link, simple_dai) \ - asoc_simple_card_parse_clk(node, dai_link->codec_of_node, simple_dai) -int asoc_simple_card_parse_clk(struct device_node *node, +#define asoc_simple_card_parse_clk_cpu(dev, node, dai_link, simple_dai) \ + asoc_simple_card_parse_clk(dev, node, dai_link->cpu_of_node, simple_dai) +#define asoc_simple_card_parse_clk_codec(dev, node, dai_link, simple_dai) \ + asoc_simple_card_parse_clk(dev, node, dai_link->codec_of_node, simple_dai) +int asoc_simple_card_parse_clk(struct device *dev, + struct device_node *node, struct device_node *dai_of_node, struct asoc_simple_dai *simple_dai); diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index cf026252cd4a..4924575d2e95 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -98,7 +98,8 @@ int asoc_simple_card_parse_card_name(struct snd_soc_card *card, } EXPORT_SYMBOL_GPL(asoc_simple_card_parse_card_name); -int asoc_simple_card_parse_clk(struct device_node *node, +int asoc_simple_card_parse_clk(struct device *dev, + struct device_node *node, struct device_node *dai_of_node, struct asoc_simple_dai *simple_dai) { @@ -111,14 +112,13 @@ int asoc_simple_card_parse_clk(struct device_node *node, * or "system-clock-frequency = " * or device's module clock. */ - clk = of_clk_get(node, 0); + clk = devm_get_clk_from_child(dev, node, NULL); if (!IS_ERR(clk)) { simple_dai->sysclk = clk_get_rate(clk); - simple_dai->clk = clk; } else if (!of_property_read_u32(node, "system-clock-frequency", &val)) { simple_dai->sysclk = val; } else { - clk = of_clk_get(dai_of_node, 0); + clk = devm_get_clk_from_child(dev, dai_of_node, NULL); if (!IS_ERR(clk)) simple_dai->sysclk = clk_get_rate(clk); } diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index a385ff6bfa4b..85b4f1806514 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -278,11 +278,11 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_clk_cpu(cpu, dai_link, cpu_dai); + ret = asoc_simple_card_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_clk_codec(codec, dai_link, codec_dai); + ret = asoc_simple_card_parse_clk_codec(dev, codec, dai_link, codec_dai); if (ret < 0) goto dai_link_of_err; diff --git a/sound/soc/generic/simple-scu-card.c b/sound/soc/generic/simple-scu-card.c index bb86ee042490..308ff4c11a8d 100644 --- a/sound/soc/generic/simple-scu-card.c +++ b/sound/soc/generic/simple-scu-card.c @@ -128,7 +128,7 @@ static int asoc_simple_card_dai_link_of(struct device_node *np, if (ret) return ret; - ret = asoc_simple_card_parse_clk_cpu(np, dai_link, dai_props); + ret = asoc_simple_card_parse_clk_cpu(dev, np, dai_link, dai_props); if (ret < 0) return ret; @@ -153,7 +153,7 @@ static int asoc_simple_card_dai_link_of(struct device_node *np, if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_codec(np, dai_link, dai_props); + ret = asoc_simple_card_parse_clk_codec(dev, np, dai_link, dai_props); if (ret < 0) return ret; From bf14da7e55169964a1e6f35dc9d7428dc9e9013c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Myl=C3=A8ne=20Josserand?= Date: Thu, 2 Feb 2017 10:24:18 +0100 Subject: [PATCH 18/24] ASoC: sun8i-codec-analog: Add amplifier event to fix first delay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When playing a sound for the first time, a short delay, where the audio file is not played, can be noticed. On a second play (right after), the sound is played correctly. If we wait a short time (~5 sec which corresponds to the aplay timeout), the delay is back. This patch fixes it by using an event on headphone amplifier. It allows to keep the amplifier enable while playing a sound. A delay of 700ms allows to wait that the amplifier is powered-up before playing the sound. Signed-off-by: Mylène Josserand Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- sound/soc/sunxi/sun8i-codec-analog.c | 30 ++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c index af02290ebe49..72331332b72e 100644 --- a/sound/soc/sunxi/sun8i-codec-analog.c +++ b/sound/soc/sunxi/sun8i-codec-analog.c @@ -398,11 +398,37 @@ static const struct snd_kcontrol_new sun8i_codec_hp_src[] = { sun8i_codec_hp_src_enum), }; +static int sun8i_headphone_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, + BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), + BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN)); + /* + * Need a delay to have the amplifier up. 700ms seems the best + * compromise between the time to let the amplifier up and the + * time not to feel this delay while playing a sound. + */ + msleep(700); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, + BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), + 0x0); + } + + return 0; +} + static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = { SND_SOC_DAPM_MUX("Headphone Source Playback Route", SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src), - SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL, - SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV_E("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL, + SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0, + sun8i_headphone_amp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL, SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0), SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL, From 36c684936fae7ed97a4816de6006d127d1854a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Myl=C3=A8ne=20Josserand?= Date: Thu, 2 Feb 2017 10:24:17 +0100 Subject: [PATCH 19/24] ASoC: Add sun8i digital audio codec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the sun8i audio codec which handles the digital register of A33 codec. The driver handles only the basic playback from the DAC to headphones. All other features (microphone, capture, etc) will be added later. Signed-off-by: Mylène Josserand Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- sound/soc/sunxi/Kconfig | 11 + sound/soc/sunxi/Makefile | 1 + sound/soc/sunxi/sun8i-codec.c | 498 ++++++++++++++++++++++++++++++++++ 3 files changed, 510 insertions(+) create mode 100644 sound/soc/sunxi/sun8i-codec.c diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index 6c344e16aca4..13a8267f17c7 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -9,6 +9,17 @@ config SND_SUN4I_CODEC Select Y or M to add support for the Codec embedded in the Allwinner A10 and affiliated SoCs. +config SND_SUN8I_CODEC + tristate "Allwinner SUN8I audio codec" + depends on OF + depends on MACH_SUN8I || COMPILE_TEST + select REGMAP_MMIO + help + This option enables the digital part of the internal audio codec for + Allwinner sun8i SoC (and particularly A33). + + Say Y or M if you want to add sun8i digital audio codec support. + config SND_SUN8I_CODEC_ANALOG tristate "Allwinner sun8i Codec Analog Controls Support" depends on MACH_SUN8I || COMPILE_TEST diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile index 241c0df9ca0c..1f1af6271731 100644 --- a/sound/soc/sunxi/Makefile +++ b/sound/soc/sunxi/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o +obj-$(CONFIG_SND_SUN8I_CODEC) += sun8i-codec.o diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c new file mode 100644 index 000000000000..b92bdc8361af --- /dev/null +++ b/sound/soc/sunxi/sun8i-codec.c @@ -0,0 +1,498 @@ +/* + * This driver supports the digital controls for the internal codec + * found in Allwinner's A33 SoCs. + * + * (C) Copyright 2010-2016 + * Reuuimlla Technology Co., Ltd. + * huangxin + * Mylène Josserand + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define SUN8I_SYSCLK_CTL 0x00c +#define SUN8I_SYSCLK_CTL_AIF1CLK_ENA 11 +#define SUN8I_SYSCLK_CTL_AIF1CLK_SRC_PLL 9 +#define SUN8I_SYSCLK_CTL_AIF1CLK_SRC 8 +#define SUN8I_SYSCLK_CTL_SYSCLK_ENA 3 +#define SUN8I_SYSCLK_CTL_SYSCLK_SRC 0 +#define SUN8I_MOD_CLK_ENA 0x010 +#define SUN8I_MOD_CLK_ENA_AIF1 15 +#define SUN8I_MOD_CLK_ENA_DAC 2 +#define SUN8I_MOD_RST_CTL 0x014 +#define SUN8I_MOD_RST_CTL_AIF1 15 +#define SUN8I_MOD_RST_CTL_DAC 2 +#define SUN8I_SYS_SR_CTRL 0x018 +#define SUN8I_SYS_SR_CTRL_AIF1_FS 12 +#define SUN8I_SYS_SR_CTRL_AIF2_FS 8 +#define SUN8I_AIF1CLK_CTRL 0x040 +#define SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD 15 +#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV 14 +#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV 13 +#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV 9 +#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV 6 +#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_16 (1 << 6) +#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ 4 +#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16 (1 << 4) +#define SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT 2 +#define SUN8I_AIF1_DACDAT_CTRL 0x048 +#define SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA 15 +#define SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA 14 +#define SUN8I_DAC_DIG_CTRL 0x120 +#define SUN8I_DAC_DIG_CTRL_ENDA 15 +#define SUN8I_DAC_MXR_SRC 0x130 +#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA0L 15 +#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA1L 14 +#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF2DACL 13 +#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_ADCL 12 +#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA0R 11 +#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA1R 10 +#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF2DACR 9 +#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_ADCR 8 + +#define SUN8I_SYS_SR_CTRL_AIF1_FS_MASK GENMASK(15, 12) +#define SUN8I_SYS_SR_CTRL_AIF2_FS_MASK GENMASK(11, 8) +#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK GENMASK(5, 4) +#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK GENMASK(8, 6) + +struct sun8i_codec { + struct device *dev; + struct regmap *regmap; + struct clk *clk_module; + struct clk *clk_bus; +}; + +static int sun8i_codec_runtime_resume(struct device *dev) +{ + struct sun8i_codec *scodec = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(scodec->clk_module); + if (ret) { + dev_err(dev, "Failed to enable the module clock\n"); + return ret; + } + + ret = clk_prepare_enable(scodec->clk_bus); + if (ret) { + dev_err(dev, "Failed to enable the bus clock\n"); + goto err_disable_modclk; + } + + regcache_cache_only(scodec->regmap, false); + + ret = regcache_sync(scodec->regmap); + if (ret) { + dev_err(dev, "Failed to sync regmap cache\n"); + goto err_disable_clk; + } + + return 0; + +err_disable_clk: + clk_disable_unprepare(scodec->clk_bus); + +err_disable_modclk: + clk_disable_unprepare(scodec->clk_module); + + return ret; +} + +static int sun8i_codec_runtime_suspend(struct device *dev) +{ + struct sun8i_codec *scodec = dev_get_drvdata(dev); + + regcache_cache_only(scodec->regmap, true); + regcache_mark_dirty(scodec->regmap); + + clk_disable_unprepare(scodec->clk_module); + clk_disable_unprepare(scodec->clk_bus); + + return 0; +} + +static int sun8i_codec_get_hw_rate(struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + + switch (rate) { + case 8000: + case 7350: + return 0x0; + case 11025: + return 0x1; + case 12000: + return 0x2; + case 16000: + return 0x3; + case 22050: + return 0x4; + case 24000: + return 0x5; + case 32000: + return 0x6; + case 44100: + return 0x7; + case 48000: + return 0x8; + case 96000: + return 0x9; + case 192000: + return 0xa; + default: + return -EINVAL; + } +} + +static int sun8i_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct sun8i_codec *scodec = snd_soc_codec_get_drvdata(dai->codec); + u32 value; + + /* clock masters */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: /* DAI Slave */ + value = 0x0; /* Codec Master */ + break; + case SND_SOC_DAIFMT_CBM_CFM: /* DAI Master */ + value = 0x1; /* Codec Slave */ + break; + default: + return -EINVAL; + } + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + BIT(SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD), + value << SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD); + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* Normal */ + value = 0x0; + break; + case SND_SOC_DAIFMT_IB_IF: /* Inversion */ + value = 0x1; + break; + default: + return -EINVAL; + } + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + BIT(SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV), + value << SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV); + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + BIT(SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV), + value << SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV); + + /* DAI format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + value = 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + value = 0x1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + value = 0x2; + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + value = 0x3; + break; + default: + return -EINVAL; + } + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + BIT(SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT), + value << SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT); + + return 0; +} + +static int sun8i_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sun8i_codec *scodec = snd_soc_codec_get_drvdata(dai->codec); + int sample_rate; + + /* + * The CPU DAI handles only a sample of 16 bits. Configure the + * codec to handle this type of sample resolution. + */ + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK, + SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16); + + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK, + SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_16); + + sample_rate = sun8i_codec_get_hw_rate(params); + if (sample_rate < 0) + return sample_rate; + + regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL, + SUN8I_SYS_SR_CTRL_AIF1_FS_MASK, + sample_rate << SUN8I_SYS_SR_CTRL_AIF1_FS); + regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL, + SUN8I_SYS_SR_CTRL_AIF2_FS_MASK, + sample_rate << SUN8I_SYS_SR_CTRL_AIF2_FS); + + return 0; +} + +static const struct snd_kcontrol_new sun8i_output_left_mixer_controls[] = { + SOC_DAPM_SINGLE("LSlot 0", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA0L, 1, 0), + SOC_DAPM_SINGLE("LSlot 1", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA1L, 1, 0), + SOC_DAPM_SINGLE("DACL", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF2DACL, 1, 0), + SOC_DAPM_SINGLE("ADCL", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_ADCL, 1, 0), +}; + +static const struct snd_kcontrol_new sun8i_output_right_mixer_controls[] = { + SOC_DAPM_SINGLE("RSlot 0", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA0R, 1, 0), + SOC_DAPM_SINGLE("RSlot 1", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA1R, 1, 0), + SOC_DAPM_SINGLE("DACR", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF2DACR, 1, 0), + SOC_DAPM_SINGLE("ADCR", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_ADCR, 1, 0), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { + /* Digital parts of the DACs */ + SND_SOC_DAPM_SUPPLY("DAC", SUN8I_DAC_DIG_CTRL, SUN8I_DAC_DIG_CTRL_ENDA, + 0, NULL, 0), + + /* Analog DAC */ + SND_SOC_DAPM_DAC("Digital Left DAC", "Playback", SUN8I_AIF1_DACDAT_CTRL, + SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA, 0), + SND_SOC_DAPM_DAC("Digital Right DAC", "Playback", SUN8I_AIF1_DACDAT_CTRL, + SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0), + + /* DAC Mixers */ + SND_SOC_DAPM_MIXER("Left DAC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_output_left_mixer_controls, + ARRAY_SIZE(sun8i_output_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right DAC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_output_right_mixer_controls, + ARRAY_SIZE(sun8i_output_right_mixer_controls)), + + /* Clocks */ + SND_SOC_DAPM_SUPPLY("MODCLK AFI1", SUN8I_MOD_CLK_ENA, + SUN8I_MOD_CLK_ENA_AIF1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MODCLK DAC", SUN8I_MOD_CLK_ENA, + SUN8I_MOD_CLK_ENA_DAC, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF1", SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_AIF1CLK_ENA, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("SYSCLK", SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_SYSCLK_ENA, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("AIF1 PLL", SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_AIF1CLK_SRC_PLL, 0, NULL, 0), + /* Inversion as 0=AIF1, 1=AIF2 */ + SND_SOC_DAPM_SUPPLY("SYSCLK AIF1", SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_SYSCLK_SRC, 1, NULL, 0), + + /* Module reset */ + SND_SOC_DAPM_SUPPLY("RST AIF1", SUN8I_MOD_RST_CTL, + SUN8I_MOD_RST_CTL_AIF1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RST DAC", SUN8I_MOD_RST_CTL, + SUN8I_MOD_RST_CTL_DAC, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("HP"), +}; + +static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { + /* Clock Routes */ + { "AIF1", NULL, "SYSCLK AIF1" }, + { "AIF1 PLL", NULL, "AIF1" }, + { "RST AIF1", NULL, "AIF1 PLL" }, + { "MODCLK AFI1", NULL, "RST AIF1" }, + { "DAC", NULL, "MODCLK AFI1" }, + + { "RST DAC", NULL, "SYSCLK" }, + { "MODCLK DAC", NULL, "RST DAC" }, + { "DAC", NULL, "MODCLK DAC" }, + + /* DAC Routes */ + { "Digital Left DAC", NULL, "DAC" }, + { "Digital Right DAC", NULL, "DAC" }, + + /* DAC Mixer Routes */ + { "Left DAC Mixer", "LSlot 0", "Digital Left DAC"}, + { "Right DAC Mixer", "RSlot 0", "Digital Right DAC"}, + + /* End of route : HP out */ + { "HP", NULL, "Left DAC Mixer" }, + { "HP", NULL, "Right DAC Mixer" }, +}; + +static struct snd_soc_dai_ops sun8i_codec_dai_ops = { + .hw_params = sun8i_codec_hw_params, + .set_fmt = sun8i_set_fmt, +}; + +static struct snd_soc_dai_driver sun8i_codec_dai = { + .name = "sun8i", + /* playback capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + /* pcm operations */ + .ops = &sun8i_codec_dai_ops, +}; + +static struct snd_soc_codec_driver sun8i_soc_codec = { + .component_driver = { + .dapm_widgets = sun8i_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_dapm_widgets), + .dapm_routes = sun8i_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sun8i_codec_dapm_routes), + }, +}; + +static const struct regmap_config sun8i_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN8I_DAC_MXR_SRC, + + .cache_type = REGCACHE_FLAT, +}; + +static int sun8i_codec_probe(struct platform_device *pdev) +{ + struct resource *res_base; + struct sun8i_codec *scodec; + void __iomem *base; + int ret; + + scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL); + if (!scodec) + return -ENOMEM; + + scodec->dev = &pdev->dev; + + scodec->clk_module = devm_clk_get(&pdev->dev, "mod"); + if (IS_ERR(scodec->clk_module)) { + dev_err(&pdev->dev, "Failed to get the module clock\n"); + return PTR_ERR(scodec->clk_module); + } + + scodec->clk_bus = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(scodec->clk_bus)) { + dev_err(&pdev->dev, "Failed to get the bus clock\n"); + return PTR_ERR(scodec->clk_bus); + } + + res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res_base); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed to map the registers\n"); + return PTR_ERR(base); + } + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun8i_codec_regmap_config); + if (IS_ERR(scodec->regmap)) { + dev_err(&pdev->dev, "Failed to create our regmap\n"); + return PTR_ERR(scodec->regmap); + } + + platform_set_drvdata(pdev, scodec); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = sun8i_codec_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &sun8i_soc_codec, + &sun8i_codec_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec\n"); + goto err_suspend; + } + + return ret; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + sun8i_codec_runtime_suspend(&pdev->dev); + +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int sun8i_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct sun8i_codec *scodec = snd_soc_card_get_drvdata(card); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + sun8i_codec_runtime_suspend(&pdev->dev); + + snd_soc_unregister_codec(&pdev->dev); + clk_disable_unprepare(scodec->clk_module); + clk_disable_unprepare(scodec->clk_bus); + + return 0; +} + +static const struct of_device_id sun8i_codec_of_match[] = { + { .compatible = "allwinner,sun8i-a33-codec" }, + {} +}; +MODULE_DEVICE_TABLE(of, sun8i_codec_of_match); + +static const struct dev_pm_ops sun8i_codec_pm_ops = { + SET_RUNTIME_PM_OPS(sun8i_codec_runtime_suspend, + sun8i_codec_runtime_resume, NULL) +}; + +static struct platform_driver sun8i_codec_driver = { + .driver = { + .name = "sun8i-codec", + .of_match_table = sun8i_codec_of_match, + .pm = &sun8i_codec_pm_ops, + }, + .probe = sun8i_codec_probe, + .remove = sun8i_codec_remove, +}; +module_platform_driver(sun8i_codec_driver); + +MODULE_DESCRIPTION("Allwinner A33 (sun8i) codec driver"); +MODULE_AUTHOR("Mylène Josserand "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sun8i-codec"); From 164e372747ce7a8b97459e98ce258b6aa969cb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Myl=C3=A8ne=20Josserand?= Date: Thu, 2 Feb 2017 10:24:19 +0100 Subject: [PATCH 20/24] ASoC: codecs: Add sun8i-a33 binding documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the documentation for dt-binding of the digital audio codec driver and the audio card driver for Sun8i-a33 SoCs. Signed-off-by: Mylène Josserand Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- .../bindings/sound/sun8i-a33-codec.txt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/sun8i-a33-codec.txt diff --git a/Documentation/devicetree/bindings/sound/sun8i-a33-codec.txt b/Documentation/devicetree/bindings/sound/sun8i-a33-codec.txt new file mode 100644 index 000000000000..399b1b4bae22 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/sun8i-a33-codec.txt @@ -0,0 +1,63 @@ +Allwinner SUN8I audio codec +------------------------------------ + +On Sun8i-A33 SoCs, the audio is separated in different parts: + - A DAI driver. It uses the "sun4i-i2s" driver which is + documented here: + Documentation/devicetree/bindings/sound/sun4i-i2s.txt + - An analog part of the codec which is handled as PRCM registers. + See Documentation/devicetree/bindings/sound/sun8i-codec-analog.txt + - An digital part of the codec which is documented in this current + binding documentation. + - And finally, an audio card which links all the above components. + The simple-audio card will be used. + See Documentation/devicetree/bindings/sound/simple-card.txt + +This bindings documentation exposes Sun8i codec (digital part). + +Required properties: +- compatible: must be "allwinner,sun8i-a33-codec" +- reg: must contain the registers location and length +- interrupts: must contain the codec interrupt +- clocks: a list of phandle + clock-specifer pairs, one for each entry + in clock-names. +- clock-names: should contain followings: + - "bus": the parent APB clock for this controller + - "mod": the parent module clock + +Here is an example to add a sound card and the codec binding on sun8i SoCs that +are similar to A33 using simple-card: + + sound { + compatible = "simple-audio-card"; + simple-audio-card,name = "sun8i-a33-audio"; + simple-audio-card,format = "i2s"; + simple-audio-card,frame-master = <&link_codec>; + simple-audio-card,bitclock-master = <&link_codec>; + simple-audio-card,mclk-fs = <512>; + simple-audio-card,aux-devs = <&codec_analog>; + simple-audio-card,routing = + "Left DAC", "Digital Left DAC", + "Right DAC", "Digital Right DAC"; + + simple-audio-card,cpu { + sound-dai = <&dai>; + }; + + link_codec: simple-audio-card,codec { + sound-dai = <&codec>; + }; + + soc@01c00000 { + [...] + + audio-codec@1c22e00 { + #sound-dai-cells = <0>; + compatible = "allwinner,sun8i-a33-codec"; + reg = <0x01c22e00 0x400>; + interrupts = ; + clocks = <&ccu CLK_BUS_CODEC>, <&ccu CLK_AC_DIG>; + clock-names = "bus", "mod"; + }; + }; + From 2ad6f30de7087515a0bc2a718fca6681a57739a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Myl=C3=A8ne=20Josserand?= Date: Thu, 2 Feb 2017 10:24:16 +0100 Subject: [PATCH 21/24] ASoC: sun4i-i2s: Add quirks to handle a31 compatible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some SoCs have a reset line that must be asserted/deasserted. This patch adds a quirk to handle the new compatible "allwinner,sun6i-a31-i2s" which will deassert the reset line on probe function and assert it on remove's one. This new compatible is useful in case of A33 codec driver, for example. Signed-off-by: Mylène Josserand Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-i2s.c | 57 +++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index f24d19526603..268f2bf691b3 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -14,9 +14,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -92,6 +94,7 @@ struct sun4i_i2s { struct clk *bus_clk; struct clk *mod_clk; struct regmap *regmap; + struct reset_control *rst; unsigned int mclk_freq; @@ -651,9 +654,22 @@ static int sun4i_i2s_runtime_suspend(struct device *dev) return 0; } +struct sun4i_i2s_quirks { + bool has_reset; +}; + +static const struct sun4i_i2s_quirks sun4i_a10_i2s_quirks = { + .has_reset = false, +}; + +static const struct sun4i_i2s_quirks sun6i_a31_i2s_quirks = { + .has_reset = true, +}; + static int sun4i_i2s_probe(struct platform_device *pdev) { struct sun4i_i2s *i2s; + const struct sun4i_i2s_quirks *quirks; struct resource *res; void __iomem *regs; int irq, ret; @@ -674,6 +690,12 @@ static int sun4i_i2s_probe(struct platform_device *pdev) return irq; } + quirks = of_device_get_match_data(&pdev->dev); + if (!quirks) { + dev_err(&pdev->dev, "Failed to determine the quirks to use\n"); + return -ENODEV; + } + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); if (IS_ERR(i2s->bus_clk)) { dev_err(&pdev->dev, "Can't get our bus clock\n"); @@ -692,7 +714,24 @@ static int sun4i_i2s_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Can't get our mod clock\n"); return PTR_ERR(i2s->mod_clk); } - + + if (quirks->has_reset) { + i2s->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(i2s->rst)) { + dev_err(&pdev->dev, "Failed to get reset control\n"); + return PTR_ERR(i2s->rst); + } + } + + if (!IS_ERR(i2s->rst)) { + ret = reset_control_deassert(i2s->rst); + if (ret) { + dev_err(&pdev->dev, + "Failed to deassert the reset control\n"); + return -EINVAL; + } + } + i2s->playback_dma_data.addr = res->start + SUN4I_I2S_FIFO_TX_REG; i2s->playback_dma_data.maxburst = 4; @@ -727,23 +766,37 @@ err_suspend: sun4i_i2s_runtime_suspend(&pdev->dev); err_pm_disable: pm_runtime_disable(&pdev->dev); + if (!IS_ERR(i2s->rst)) + reset_control_assert(i2s->rst); return ret; } static int sun4i_i2s_remove(struct platform_device *pdev) { + struct sun4i_i2s *i2s = dev_get_drvdata(&pdev->dev); + snd_dmaengine_pcm_unregister(&pdev->dev); pm_runtime_disable(&pdev->dev); if (!pm_runtime_status_suspended(&pdev->dev)) sun4i_i2s_runtime_suspend(&pdev->dev); + if (!IS_ERR(i2s->rst)) + reset_control_assert(i2s->rst); + return 0; } static const struct of_device_id sun4i_i2s_match[] = { - { .compatible = "allwinner,sun4i-a10-i2s", }, + { + .compatible = "allwinner,sun4i-a10-i2s", + .data = &sun4i_a10_i2s_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-i2s", + .data = &sun6i_a31_i2s_quirks, + }, {} }; MODULE_DEVICE_TABLE(of, sun4i_i2s_match); From e5028a259733ec2324893cb481023ed6cf7e9723 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Wed, 8 Feb 2017 02:30:40 +0800 Subject: [PATCH 22/24] ASoC: sunxi: allow the analog codec driver to be built on ARM64 As the 64-bit Allwinner H5 SoC has the same analog codec part (also the same digital part) as H3, enable the driver to be built on ARM64 Allwinner platform, so that it can be used on H5. Signed-off-by: Icenowy Zheng Acked-by: Maxime Ripard Acked-by: Chen-Yu Tsai Signed-off-by: Mark Brown --- sound/soc/sunxi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index 13a8267f17c7..22408bc2d6ec 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -22,7 +22,7 @@ config SND_SUN8I_CODEC config SND_SUN8I_CODEC_ANALOG tristate "Allwinner sun8i Codec Analog Controls Support" - depends on MACH_SUN8I || COMPILE_TEST + depends on MACH_SUN8I || (ARM64 && ARCH_SUNXI) || COMPILE_TEST select REGMAP help Say Y or M if you want to add support for the analog controls for From f55d404f49194563974f5462f9f4bd7cbc48d9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Myl=C3=A8ne=20Josserand?= Date: Fri, 10 Feb 2017 11:00:46 +0100 Subject: [PATCH 23/24] ASoC: sun4i-i2s: Update binding documentation to include A31 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new compatible for sun4i-i2s driver to handle some SoCs that have a reset line that must be asserted/deasserted. This new compatible, "allwinner,sun6i-a31-i2s", requires the property "resets" which should be a phandle to the reset line. Except these differences, the compatible is identical to previous one which will not handle a reset line. Signed-off-by: Mylène Josserand Acked-by: Maxime Ripard Acked-by: Rob Herring Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/sun4i-i2s.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/sun4i-i2s.txt b/Documentation/devicetree/bindings/sound/sun4i-i2s.txt index 7b526ec64991..f4adc58f82ba 100644 --- a/Documentation/devicetree/bindings/sound/sun4i-i2s.txt +++ b/Documentation/devicetree/bindings/sound/sun4i-i2s.txt @@ -7,6 +7,7 @@ Required properties: - compatible: should be one of the followings - "allwinner,sun4i-a10-i2s" + - "allwinner,sun6i-a31-i2s" - reg: physical base address of the controller and length of memory mapped region. - interrupts: should contain the I2S interrupt. @@ -19,6 +20,10 @@ Required properties: - "mod" : module clock for the I2S controller - #sound-dai-cells : Must be equal to 0 +Required properties for the following compatibles: + - "allwinner,sun6i-a31-i2s" +- resets: phandle to the reset line for this codec + Example: i2s0: i2s@01c22400 { From c97c4604c008f3d489cc3201de80e313aeb501d6 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Mon, 6 Feb 2017 15:22:24 +0000 Subject: [PATCH 24/24] ASoC: sun4i-spdif: drop unnessary snd_soc_unregister_component() It's not necessary to unregister a component registered with devm_snd_soc_register_component(). Also removed pointness clk_disable_unprepare() from error path and snd_soc_unregister_platform() from the remove. Fixes: f8260afa444b ("ASoC: sunxi: Add support for the SPDIF block") Signed-off-by: Wei Yongjun Acked-by: Maxime Ripard Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-spdif.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index c03cd07a9b19..eaefd07a5ed0 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -510,8 +510,7 @@ static int sun4i_spdif_probe(struct platform_device *pdev) host->spdif_clk = devm_clk_get(&pdev->dev, "spdif"); if (IS_ERR(host->spdif_clk)) { dev_err(&pdev->dev, "failed to get a spdif clock.\n"); - ret = PTR_ERR(host->spdif_clk); - goto err_disable_apb_clk; + return PTR_ERR(host->spdif_clk); } host->dma_params_tx.addr = res->start + quirks->reg_dac_txdata; @@ -525,7 +524,7 @@ static int sun4i_spdif_probe(struct platform_device *pdev) if (IS_ERR(host->rst) && PTR_ERR(host->rst) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; dev_err(&pdev->dev, "Failed to get reset: %d\n", ret); - goto err_disable_apb_clk; + return ret; } if (!IS_ERR(host->rst)) reset_control_deassert(host->rst); @@ -534,7 +533,7 @@ static int sun4i_spdif_probe(struct platform_device *pdev) ret = devm_snd_soc_register_component(&pdev->dev, &sun4i_spdif_component, &sun4i_spdif_dai, 1); if (ret) - goto err_disable_apb_clk; + return ret; pm_runtime_enable(&pdev->dev); if (!pm_runtime_enabled(&pdev->dev)) { @@ -552,9 +551,6 @@ err_suspend: sun4i_spdif_runtime_suspend(&pdev->dev); err_unregister: pm_runtime_disable(&pdev->dev); - snd_soc_unregister_component(&pdev->dev); -err_disable_apb_clk: - clk_disable_unprepare(host->apb_clk); return ret; } @@ -564,9 +560,6 @@ static int sun4i_spdif_remove(struct platform_device *pdev) if (!pm_runtime_status_suspended(&pdev->dev)) sun4i_spdif_runtime_suspend(&pdev->dev); - snd_soc_unregister_platform(&pdev->dev); - snd_soc_unregister_component(&pdev->dev); - return 0; }