diff options
23 files changed, 827 insertions, 165 deletions
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 { 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 = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&ccu CLK_BUS_CODEC>, <&ccu CLK_AC_DIG>; + clock-names = "bus", "mod"; + }; + }; + 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. 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/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/codecs/Kconfig b/sound/soc/codecs/Kconfig index b456d31f7fc1..e49e9da7f1f6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -118,8 +118,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 @@ -671,8 +671,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 @@ -682,8 +682,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 @@ -731,10 +731,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 0a7f4ffe0d78..1796cb987e71 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -119,8 +119,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 @@ -348,8 +348,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 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 <linux/gpio.h> #include <linux/sched.h> #include <linux/uaccess.h> -#include <linux/miscdevice.h> #include <linux/regulator/consumer.h> #include <linux/pm_qos.h> #include <linux/sysfs.h> 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 = <xxx>" * 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; 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 diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 85324e61cbd5..52a47ed292a4 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; }; @@ -483,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); @@ -507,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) @@ -535,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; } } @@ -546,8 +544,11 @@ 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); + i2s->op_clk = NULL; + goto err; + } clk_prepare_enable(i2s->op_clk); i2s->rclk_srcrate = clk_get_rate(i2s->op_clk); @@ -561,12 +562,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) @@ -574,7 +576,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); @@ -582,8 +585,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, @@ -652,6 +660,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); /* @@ -661,6 +670,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; @@ -670,6 +680,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; } @@ -681,6 +692,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); @@ -769,6 +782,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; @@ -806,6 +821,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) @@ -880,6 +897,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)) { @@ -908,6 +926,7 @@ static int i2s_trigger(struct snd_pcm_substream *substream, } spin_unlock_irqrestore(i2s->lock, flags); + pm_runtime_put(dai->dev); break; } @@ -922,13 +941,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, @@ -947,6 +969,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)) @@ -960,24 +984,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 @@ -990,6 +1002,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); @@ -1022,6 +1036,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; } @@ -1031,6 +1046,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); @@ -1039,6 +1056,8 @@ static int samsung_i2s_dai_remove(struct snd_soc_dai *dai) } } + pm_runtime_put(dai->dev); + return 0; } @@ -1066,7 +1085,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,33 +1109,21 @@ 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) { 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); + + if (i2s->op_clk) + clk_disable_unprepare(i2s->op_clk); clk_disable_unprepare(i2s->clk); return 0; @@ -1128,6 +1134,12 @@ 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); + writel(i2s->suspend_i2spsr, i2s->addr + I2SPSR); return 0; } @@ -1179,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); @@ -1196,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); @@ -1218,7 +1230,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; @@ -1231,22 +1242,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) { @@ -1267,13 +1262,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", @@ -1320,6 +1310,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) { @@ -1345,6 +1341,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)) { @@ -1353,13 +1360,9 @@ 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_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); ret = i2s_register_clock_provider(pdev); @@ -1367,9 +1370,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; @@ -1377,25 +1377,20 @@ 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_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); - i2s->pri_dai = NULL; - i2s->sec_dai = NULL; + i2s_unregister_clock_provider(pdev); + clk_disable_unprepare(pri_dai->clk); + pm_runtime_put_noidle(&pdev->dev); return 0; } @@ -1457,49 +1452,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, }, {}, }; @@ -1531,6 +1514,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 = { 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; 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 <linux/clk.h> #include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/of.h> #include <sound/pcm_params.h> diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index 6c344e16aca4..22408bc2d6ec 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -9,9 +9,20 @@ 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 + 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 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/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 */ diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index 4237323ef594..3635bbc72cbc 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -14,9 +14,11 @@ #include <linux/clk.h> #include <linux/dmaengine.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> +#include <linux/reset.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> @@ -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 = 8; @@ -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); diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index 88fbb3a1e660..eaefd07a5ed0 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) @@ -403,17 +405,38 @@ 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"), +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 snd_soc_dapm_route dit_routes[] = { - { "spdif-out", NULL, "Playback" }, +static const struct sun4i_spdif_quirks sun6i_a31_spdif_quirks = { + .reg_dac_txdata = SUN4I_SPDIF_TXFIFO, + .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", }, - { .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, + }, + { + .compatible = "allwinner,sun8i-h3-spdif", + .data = &sun8i_h3_spdif_quirks, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sun4i_spdif_of_match); @@ -446,6 +469,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; @@ -467,6 +491,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); @@ -480,23 +510,21 @@ 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 + 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; 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); @@ -505,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)) { @@ -523,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; } @@ -535,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; } 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, 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. <www.reuuimllatech.com> + * huangxin <huangxin@Reuuimllatech.com> + * Mylène Josserand <mylene.josserand@free-electrons.com> + * + * 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 <linux/module.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#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 <mylene.josserand@free-electrons.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sun8i-codec"); |