From 8729879667d0668f20bd2aba8473879ac1d0216d Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Sun, 22 Jan 2017 12:49:37 +0100 Subject: [PATCH] ASoC: Add driver for Cirrus Logic Audio Card Note: due to problems with deferred probing of regulators the following softdep should be added to a modprobe.d file softdep arizona-spi pre: arizona-ldo1 Signed-off-by: Matthias Reichl --- arch/arm/boot/dts/overlays/Makefile | 1 + arch/arm/boot/dts/overlays/README | 6 + .../dts/overlays/rpi-cirrus-wm5102-overlay.dts | 146 +++ sound/soc/bcm/Kconfig | 9 + sound/soc/bcm/Makefile | 2 + sound/soc/bcm/rpi-cirrus.c | 1003 ++++++++++++++++++++ 6 files changed, 1167 insertions(+) create mode 100644 arch/arm/boot/dts/overlays/rpi-cirrus-wm5102-overlay.dts create mode 100644 sound/soc/bcm/rpi-cirrus.c --- a/arch/arm/boot/dts/overlays/Makefile +++ b/arch/arm/boot/dts/overlays/Makefile @@ -68,6 +68,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ qca7000.dtbo \ raspidac3.dtbo \ rpi-backlight.dtbo \ + rpi-cirrus-wm5102.dtbo \ rpi-dac.dtbo \ rpi-display.dtbo \ rpi-ft5406.dtbo \ --- a/arch/arm/boot/dts/overlays/README +++ b/arch/arm/boot/dts/overlays/README @@ -995,6 +995,12 @@ Load: dtoverlay=rpi-backlight Params: +Name: rpi-cirrus-wm5102 +Info: Configures the Cirrus Logic Audio Card +Load: dtoverlay=rpi-cirrus-wm5102 +Params: + + Name: rpi-dac Info: Configures the RPi DAC audio card Load: dtoverlay=rpi-dac --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-cirrus-wm5102-overlay.dts @@ -0,0 +1,146 @@ +// Definitions for the Cirrus Logic Audio Card +/dts-v1/; +/plugin/; +#include +#include +#include + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&i2s>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + wlf_pins: wlf_pins { + brcm,pins = <17 22 27 8>; + brcm,function = < + BCM2835_FSEL_GPIO_OUT + BCM2835_FSEL_GPIO_OUT + BCM2835_FSEL_GPIO_IN + BCM2835_FSEL_GPIO_OUT + >; + }; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + rpi_cirrus_reg_1v8: rpi_cirrus_reg_1v8 { + compatible = "regulator-fixed"; + regulator-name = "RPi-Cirrus 1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + + wm5102@1{ + compatible = "wlf,wm5102"; + reg = <1>; + + spi-max-frequency = <500000>; + + interrupt-parent = <&gpio>; + interrupts = <27 8>; + interrupt-controller; + #interrupt-cells = <2>; + + gpio-controller; + #gpio-cells = <2>; + + LDOVDD-supply = <&rpi_cirrus_reg_1v8>; + AVDD-supply = <&rpi_cirrus_reg_1v8>; + DBVDD1-supply = <&rpi_cirrus_reg_1v8>; + DBVDD2-supply = <&vdd_3v3_reg>; + DBVDD3-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&rpi_cirrus_reg_1v8>; + SPKVDDL-supply = <&vdd_5v0_reg>; + SPKVDDR-supply = <&vdd_5v0_reg>; + DCVDD-supply = <&arizona_ldo1>; + + wlf,reset = <&gpio 17 GPIO_ACTIVE_HIGH>; + wlf,ldoena = <&gpio 22 GPIO_ACTIVE_HIGH>; + wlf,gpio-defaults = < + ARIZONA_GP_DEFAULT + ARIZONA_GP_DEFAULT + ARIZONA_GP_DEFAULT + ARIZONA_GP_DEFAULT + ARIZONA_GP_DEFAULT + >; + wlf,micd-configs = <0 1 0>; + wlf,dmic-ref = < + ARIZONA_DMIC_MICVDD + ARIZONA_DMIC_MICBIAS2 + ARIZONA_DMIC_MICVDD + ARIZONA_DMIC_MICVDD + >; + wlf,inmode = < + ARIZONA_INMODE_DIFF + ARIZONA_INMODE_DMIC + ARIZONA_INMODE_SE + ARIZONA_INMODE_DIFF + >; + status = "okay"; + + arizona_ldo1: ldo1 { + regulator-name = "LDO1"; + // default constraints as in + // arizona-ldo1.c + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1800000>; + }; + }; + }; + }; + + fragment@4 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + wm8804@3b { + compatible = "wlf,wm8804"; + reg = <0x3b>; + status = "okay"; + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + wlf,reset-gpio = <&gpio 8 GPIO_ACTIVE_HIGH>; + }; + }; + }; + + fragment@5 { + target = <&sound>; + __overlay__ { + compatible = "wlf,rpi-cirrus"; + i2s-controller = <&i2s>; + status = "okay"; + }; + }; +}; --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -45,6 +45,15 @@ config SND_BCM2708_SOC_HIFIBERRY_AMP help Say Y or M if you want to add support for the HifiBerry Amp amplifier board. +config SND_BCM2708_SOC_RPI_CIRRUS + tristate "Support for Cirrus Logic Audio Card" + depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S + select SND_SOC_WM5102 + select SND_SOC_WM8804 + help + Say Y or M if you want to add support for the Wolfson and + Cirrus Logic audio cards. + config SND_BCM2708_SOC_RPI_DAC tristate "Support for RPi-DAC" depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -16,6 +16,7 @@ snd-soc-hifiberry-dacplus-objs := hifibe snd-soc-hifiberry-digi-objs := hifiberry_digi.o snd-soc-justboom-dac-objs := justboom-dac.o snd-soc-justboom-digi-objs := justboom-digi.o +snd-soc-rpi-cirrus-objs := rpi-cirrus.o snd-soc-rpi-dac-objs := rpi-dac.o snd-soc-rpi-proto-objs := rpi-proto.o snd-soc-iqaudio-dac-objs := iqaudio-dac.o @@ -34,6 +35,7 @@ obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_D obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI) += snd-soc-hifiberry-digi.o obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI) += snd-soc-justboom-digi.o +obj-$(CONFIG_SND_BCM2708_SOC_RPI_CIRRUS) += snd-soc-rpi-cirrus.o obj-$(CONFIG_SND_BCM2708_SOC_RPI_DAC) += snd-soc-rpi-dac.o obj-$(CONFIG_SND_BCM2708_SOC_RPI_PROTO) += snd-soc-rpi-proto.o obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC) += snd-soc-iqaudio-dac.o --- /dev/null +++ b/sound/soc/bcm/rpi-cirrus.c @@ -0,0 +1,1003 @@ +/* + * ASoC machine driver for Cirrus Logic Audio Card + * (with WM5102 and WM8804 codecs) + * + * Copyright 2015-2017 Matthias Reichl + * + * Based on rpi-cirrus-sound-pi driver (c) Wolfson / Cirrus Logic Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "../codecs/wm5102.h" +#include "../codecs/wm8804.h" + +#define WM8804_CLKOUT_HZ 12000000 + +#define RPI_CIRRUS_DEFAULT_RATE 44100 +#define WM5102_MAX_SYSCLK_1 49152000 /* max sysclk for 4K family */ +#define WM5102_MAX_SYSCLK_2 45158400 /* max sysclk for 11.025K family */ + +static inline unsigned int calc_sysclk(unsigned int rate) +{ + return (rate % 4000) ? WM5102_MAX_SYSCLK_2 : WM5102_MAX_SYSCLK_1; +} + +enum { + DAI_WM5102 = 0, + DAI_WM8804, +}; + +struct rpi_cirrus_priv { + /* mutex for synchronzing FLL1 access with DAPM */ + struct mutex lock; + unsigned int card_rate; + int sync_path_enable; + int fll1_freq; /* negative means RefClock in spdif rx case */ + + /* track hw params/free for substreams */ + unsigned int params_set; + unsigned int min_rate_idx, max_rate_idx; + unsigned char iec958_status[4]; +}; + +/* helper functions */ +static inline struct snd_soc_pcm_runtime *get_wm5102_runtime( + struct snd_soc_card *card) { + return snd_soc_get_pcm_runtime(card, card->dai_link[DAI_WM5102].name); +} + +static inline struct snd_soc_pcm_runtime *get_wm8804_runtime( + struct snd_soc_card *card) { + return snd_soc_get_pcm_runtime(card, card->dai_link[DAI_WM8804].name); +} + + +struct rate_info { + unsigned int value; + char *text; +}; + +static struct rate_info min_rates[] = { + { 0, "off"}, + { 32000, "32kHz"}, + { 44100, "44.1kHz"} +}; + +#define NUM_MIN_RATES ARRAY_SIZE(min_rates) + +static int rpi_cirrus_min_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_MIN_RATES; + + if (uinfo->value.enumerated.item >= NUM_MIN_RATES) + uinfo->value.enumerated.item = NUM_MIN_RATES - 1; + strcpy(uinfo->value.enumerated.name, + min_rates[uinfo->value.enumerated.item].text); + return 0; +} + +static int rpi_cirrus_min_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = priv->min_rate_idx; + return 0; +} + +static int rpi_cirrus_min_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int changed = 0; + + if (priv->min_rate_idx != ucontrol->value.enumerated.item[0]) { + changed = 1; + priv->min_rate_idx = ucontrol->value.enumerated.item[0]; + } + + return changed; +} + +static struct rate_info max_rates[] = { + { 0, "off"}, + { 48000, "48kHz"}, + { 96000, "96kHz"} +}; + +#define NUM_MAX_RATES ARRAY_SIZE(max_rates) + +static int rpi_cirrus_max_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_MAX_RATES; + if (uinfo->value.enumerated.item >= NUM_MAX_RATES) + uinfo->value.enumerated.item = NUM_MAX_RATES - 1; + strcpy(uinfo->value.enumerated.name, + max_rates[uinfo->value.enumerated.item].text); + return 0; +} + +static int rpi_cirrus_max_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = priv->max_rate_idx; + return 0; +} + +static int rpi_cirrus_max_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int changed = 0; + + if (priv->max_rate_idx != ucontrol->value.enumerated.item[0]) { + changed = 1; + priv->max_rate_idx = ucontrol->value.enumerated.item[0]; + } + + return changed; +} + +static int rpi_cirrus_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int rpi_cirrus_spdif_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int i; + + for (i = 0; i < 4; i++) + ucontrol->value.iec958.status[i] = priv->iec958_status[i]; + + return 0; +} + +static int rpi_cirrus_spdif_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *wm8804_codec = get_wm8804_runtime(card)->codec; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned char *stat = priv->iec958_status; + unsigned char *ctrl_stat = ucontrol->value.iec958.status; + unsigned int mask; + int i, changed = 0; + + for (i = 0; i < 4; i++) { + mask = (i == 3) ? 0x3f : 0xff; + if ((ctrl_stat[i] & mask) != (stat[i] & mask)) { + changed = 1; + stat[i] = ctrl_stat[i] & mask; + snd_soc_update_bits(wm8804_codec, + WM8804_SPDTX1 + i, mask, stat[i]); + } + } + + return changed; +} + +static int rpi_cirrus_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0x3f; + + return 0; +} + +static int rpi_cirrus_spdif_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *wm8804_codec = get_wm8804_runtime(card)->codec; + unsigned int mask; + int i; + + for (i = 0; i < 4; i++) { + mask = (i == 3) ? 0x3f : 0xff; + ucontrol->value.iec958.status[i] = + snd_soc_read(wm8804_codec, WM8804_RXCHAN1 + i) & mask; + } + + return 0; +} + +#define SPDIF_FLAG_CTRL(desc, reg, bit, invert) \ +{ \ + .access = SNDRV_CTL_ELEM_ACCESS_READ \ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) \ + desc " Flag", \ + .info = snd_ctl_boolean_mono_info, \ + .get = rpi_cirrus_spdif_status_flag_get, \ + .private_value = \ + (bit) | ((reg) << 8) | ((invert) << 16) \ +} + +static int rpi_cirrus_spdif_status_flag_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *wm8804_codec = get_wm8804_runtime(card)->codec; + + unsigned int bit = kcontrol->private_value & 0xff; + unsigned int reg = (kcontrol->private_value >> 8) & 0xff; + unsigned int invert = (kcontrol->private_value >> 16) & 0xff; + + bool flag = snd_soc_read(wm8804_codec, reg) & (1 << bit); + + ucontrol->value.integer.value[0] = invert ? !flag : flag; + + return 0; +} + +static const char * const recovered_frequency_texts[] = { + "176.4/192 kHz", + "88.2/96 kHz", + "44.1/48 kHz", + "32 kHz" +}; + +#define NUM_RECOVERED_FREQUENCIES \ + ARRAY_SIZE(recovered_frequency_texts) + +static int rpi_cirrus_recovered_frequency_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_RECOVERED_FREQUENCIES; + if (uinfo->value.enumerated.item >= NUM_RECOVERED_FREQUENCIES) + uinfo->value.enumerated.item = NUM_RECOVERED_FREQUENCIES - 1; + strcpy(uinfo->value.enumerated.name, + recovered_frequency_texts[uinfo->value.enumerated.item]); + return 0; +} + +static int rpi_cirrus_recovered_frequency_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *wm8804_codec = get_wm8804_runtime(card)->codec; + + ucontrol->value.enumerated.item[0] = + (snd_soc_read(wm8804_codec, WM8804_SPDSTAT) >> 4) & 0x03; + return 0; +} + +static const struct snd_kcontrol_new rpi_cirrus_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Min Sample Rate", + .info = rpi_cirrus_min_rate_info, + .get = rpi_cirrus_min_rate_get, + .put = rpi_cirrus_min_rate_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Max Sample Rate", + .info = rpi_cirrus_max_rate_info, + .get = rpi_cirrus_max_rate_get, + .put = rpi_cirrus_max_rate_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_playback_get, + .put = rpi_cirrus_spdif_playback_put, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_capture_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_mask_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) + "Recovered Frequency", + .info = rpi_cirrus_recovered_frequency_info, + .get = rpi_cirrus_recovered_frequency_get, + }, + SPDIF_FLAG_CTRL("Audio", WM8804_SPDSTAT, 0, 1), + SPDIF_FLAG_CTRL("Non-PCM", WM8804_SPDSTAT, 1, 0), + SPDIF_FLAG_CTRL("Copyright", WM8804_SPDSTAT, 2, 1), + SPDIF_FLAG_CTRL("De-Emphasis", WM8804_SPDSTAT, 3, 0), + SPDIF_FLAG_CTRL("Lock", WM8804_SPDSTAT, 6, 1), + SPDIF_FLAG_CTRL("Invalid", WM8804_INTSTAT, 1, 0), + SPDIF_FLAG_CTRL("TransErr", WM8804_INTSTAT, 3, 0), +}; + +static const char * const linein_micbias_texts[] = { + "off", "on", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(linein_micbias_enum, + linein_micbias_texts); + +static const struct snd_kcontrol_new linein_micbias_mux = + SOC_DAPM_ENUM("Route", linein_micbias_enum); + +static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +const struct snd_soc_dapm_widget rpi_cirrus_dapm_widgets[] = { + SND_SOC_DAPM_MIC("DMIC", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_INPUT("Line Input"), + SND_SOC_DAPM_MIC("Line Input with Micbias", NULL), + SND_SOC_DAPM_MUX("Line Input Micbias", SND_SOC_NOPM, 0, 0, + &linein_micbias_mux), + SND_SOC_DAPM_INPUT("dummy SPDIF in"), + SND_SOC_DAPM_PGA_E("dummy SPDIFRX", SND_SOC_NOPM, 0, 0, NULL, 0, + rpi_cirrus_spdif_rx_enable_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_INPUT("Dummy Input"), + SND_SOC_DAPM_OUTPUT("Dummy Output"), +}; + +const struct snd_soc_dapm_route rpi_cirrus_dapm_routes[] = { + { "IN1L", NULL, "Headset Mic" }, + { "IN1R", NULL, "Headset Mic" }, + { "Headset Mic", NULL, "MICBIAS1" }, + + { "IN2L", NULL, "DMIC" }, + { "IN2R", NULL, "DMIC" }, + { "DMIC", NULL, "MICBIAS2" }, + + { "IN3L", NULL, "Line Input Micbias" }, + { "IN3R", NULL, "Line Input Micbias" }, + + { "Line Input Micbias", "off", "Line Input" }, + { "Line Input Micbias", "on", "Line Input with Micbias" }, + + /* Make sure MICVDD is enabled, otherwise we get noise */ + { "Line Input", NULL, "MICVDD" }, + { "Line Input with Micbias", NULL, "MICBIAS3" }, + + /* Dummy routes to check whether SPDIF RX is enabled or not */ + {"dummy SPDIFRX", NULL, "dummy SPDIF in"}, + {"AIFTX", NULL, "dummy SPDIFRX"}, + + /* + * Dummy routes to keep wm5102 from staying off on + * playback/capture if all mixers are off. + */ + { "Dummy Output", NULL, "AIF1RX1" }, + { "Dummy Output", NULL, "AIF1RX2" }, + { "AIF1TX1", NULL, "Dummy Input" }, + { "AIF1TX2", NULL, "Dummy Input" }, +}; + +static int rpi_cirrus_clear_flls(struct snd_soc_card *card, + struct snd_soc_codec *wm5102_codec) { + + int ret1, ret2; + + ret1 = snd_soc_codec_set_pll(wm5102_codec, + WM5102_FLL1, ARIZONA_FLL_SRC_NONE, 0, 0); + ret2 = snd_soc_codec_set_pll(wm5102_codec, + WM5102_FLL1_REFCLK, ARIZONA_FLL_SRC_NONE, 0, 0); + + if (ret1) { + dev_warn(card->dev, + "setting FLL1 to zero failed: %d\n", ret1); + return ret1; + } + if (ret2) { + dev_warn(card->dev, + "setting FLL1_REFCLK to zero failed: %d\n", ret2); + return ret2; + } + return 0; +} + +static int rpi_cirrus_set_fll(struct snd_soc_card *card, + struct snd_soc_codec *wm5102_codec, unsigned int clk_freq) +{ + int ret = snd_soc_codec_set_pll(wm5102_codec, + WM5102_FLL1, + ARIZONA_CLK_SRC_MCLK1, + WM8804_CLKOUT_HZ, + clk_freq); + if (ret) + dev_err(card->dev, "Failed to set FLL1 to %d: %d\n", + clk_freq, ret); + + usleep_range(1000, 2000); + return ret; +} + +static int rpi_cirrus_set_fll_refclk(struct snd_soc_card *card, + struct snd_soc_codec *wm5102_codec, + unsigned int clk_freq, unsigned int aif2_freq) +{ + int ret = snd_soc_codec_set_pll(wm5102_codec, + WM5102_FLL1_REFCLK, + ARIZONA_CLK_SRC_MCLK1, + WM8804_CLKOUT_HZ, + clk_freq); + if (ret) { + dev_err(card->dev, + "Failed to set FLL1_REFCLK to %d: %d\n", + clk_freq, ret); + return ret; + } + + ret = snd_soc_codec_set_pll(wm5102_codec, + WM5102_FLL1, + ARIZONA_CLK_SRC_AIF2BCLK, + aif2_freq, clk_freq); + if (ret) + dev_err(card->dev, + "Failed to set FLL1 with Sync Clock %d to %d: %d\n", + aif2_freq, clk_freq, ret); + + usleep_range(1000, 2000); + return ret; +} + +static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_codec *wm5102_codec = get_wm5102_runtime(card)->codec; + + unsigned int clk_freq, aif2_freq; + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + mutex_lock(&priv->lock); + + /* Enable sync path in case of SPDIF capture use case */ + + clk_freq = calc_sysclk(priv->card_rate); + aif2_freq = 64 * priv->card_rate; + + dev_dbg(card->dev, + "spdif_rx: changing FLL1 to use Ref Clock clk: %d spdif: %d\n", + clk_freq, aif2_freq); + + ret = rpi_cirrus_clear_flls(card, wm5102_codec); + if (ret) { + dev_err(card->dev, "spdif_rx: failed to clear FLLs\n"); + goto out; + } + + ret = rpi_cirrus_set_fll_refclk(card, wm5102_codec, + clk_freq, aif2_freq); + + if (ret) { + dev_err(card->dev, "spdif_rx: failed to set FLLs\n"); + goto out; + } + + /* set to negative to indicate we're doing spdif rx */ + priv->fll1_freq = -clk_freq; + priv->sync_path_enable = 1; + break; + + case SND_SOC_DAPM_POST_PMD: + mutex_lock(&priv->lock); + priv->sync_path_enable = 0; + break; + + default: + return 0; + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int rpi_cirrus_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_codec *wm5102_codec = wm5102_runtime->codec; + + int ret = 0; + unsigned int clk_freq; + + if (dapm->dev != wm5102_runtime->codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_ON) + break; + + mutex_lock(&priv->lock); + + if (!priv->sync_path_enable) { + clk_freq = calc_sysclk(priv->card_rate); + + dev_dbg(card->dev, + "set_bias: changing FLL1 from %d to %d\n", + priv->fll1_freq, clk_freq); + + ret = rpi_cirrus_set_fll(card, wm5102_codec, clk_freq); + if (ret) + dev_err(card->dev, + "set_bias: Failed to set FLL1\n"); + else + priv->fll1_freq = clk_freq; + } + mutex_unlock(&priv->lock); + break; + default: + break; + } + + return ret; +} + +static int rpi_cirrus_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_codec *wm5102_codec = wm5102_runtime->codec; + + if (dapm->dev != wm5102_runtime->codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + mutex_lock(&priv->lock); + + dev_dbg(card->dev, + "set_bias_post: changing FLL1 from %d to off\n", + priv->fll1_freq); + + if (rpi_cirrus_clear_flls(card, wm5102_codec)) + dev_err(card->dev, + "set_bias_post: failed to clear FLLs\n"); + else + priv->fll1_freq = 0; + + mutex_unlock(&priv->lock); + + break; + default: + break; + } + + return 0; +} + +static int rpi_cirrus_set_wm8804_pll(struct snd_soc_card *card, + struct snd_soc_dai *wm8804_dai, unsigned int rate) +{ + int ret; + + /* use 256fs */ + unsigned int clk_freq = rate * 256; + + ret = snd_soc_dai_set_pll(wm8804_dai, 0, 0, + WM8804_CLKOUT_HZ, clk_freq); + if (ret) { + dev_err(card->dev, + "Failed to set WM8804 PLL to %d: %d\n", clk_freq, ret); + return ret; + } + + /* Set MCLK as PLL Output */ + ret = snd_soc_dai_set_sysclk(wm8804_dai, + WM8804_TX_CLKSRC_PLL, clk_freq, 0); + if (ret) { + dev_err(card->dev, + "Failed to set MCLK as PLL Output: %d\n", ret); + return ret; + } + + return ret; +} + +static int rpi_cirrus_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned int min_rate = min_rates[priv->min_rate_idx].value; + unsigned int max_rate = max_rates[priv->max_rate_idx].value; + + if (min_rate || max_rate) { + if (max_rate == 0) + max_rate = UINT_MAX; + + dev_dbg(card->dev, + "startup: limiting rate to %u-%u\n", + min_rate, max_rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, min_rate, max_rate); + } + + return 0; +} + +static struct snd_soc_pcm_stream rpi_cirrus_dai_link2_params = { + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = RPI_CIRRUS_DEFAULT_RATE, + .rate_max = RPI_CIRRUS_DEFAULT_RATE, +}; + +static int rpi_cirrus_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *bcm_i2s_dai = rtd->cpu_dai; + struct snd_soc_codec *wm5102_codec = rtd->codec; + struct snd_soc_dai *wm8804_dai = get_wm8804_runtime(card)->codec_dai; + + int ret; + + unsigned int width = snd_pcm_format_physical_width( + params_format(params)); + unsigned int rate = params_rate(params); + unsigned int clk_freq = calc_sysclk(rate); + + mutex_lock(&priv->lock); + + dev_dbg(card->dev, "hw_params: setting rate to %d\n", rate); + + ret = snd_soc_dai_set_bclk_ratio(bcm_i2s_dai, 2 * width); + if (ret) { + dev_err(card->dev, "set_bclk_ratio failed: %d\n", ret); + goto out; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->codec_dai, 0x03, 0x03, 2, width); + if (ret) { + dev_err(card->dev, "set_tdm_slot failed: %d\n", ret); + goto out; + } + + /* WM8804 supports sample rates from 32k only */ + if (rate >= 32000) { + ret = rpi_cirrus_set_wm8804_pll(card, wm8804_dai, rate); + if (ret) + goto out; + } + + ret = snd_soc_codec_set_sysclk(wm5102_codec, + ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, + clk_freq, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "Failed to set SYSCLK: %d\n", ret); + goto out; + } + + if ((priv->fll1_freq > 0) && (priv->fll1_freq != clk_freq)) { + dev_dbg(card->dev, + "hw_params: changing FLL1 from %d to %d\n", + priv->fll1_freq, clk_freq); + + if (rpi_cirrus_clear_flls(card, wm5102_codec)) { + dev_err(card->dev, "hw_params: failed to clear FLLs\n"); + goto out; + } + + if (rpi_cirrus_set_fll(card, wm5102_codec, clk_freq)) { + dev_err(card->dev, "hw_params: failed to set FLL\n"); + goto out; + } + + priv->fll1_freq = clk_freq; + } + + priv->card_rate = rate; + rpi_cirrus_dai_link2_params.rate_min = rate; + rpi_cirrus_dai_link2_params.rate_max = rate; + + priv->params_set |= 1 << substream->stream; + +out: + mutex_unlock(&priv->lock); + + return ret; +} + +static int rpi_cirrus_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_codec *wm5102_codec = rtd->codec; + int ret; + unsigned int old_params_set = priv->params_set; + + priv->params_set &= ~(1 << substream->stream); + + /* disable sysclk if this was the last open stream */ + if (priv->params_set == 0 && old_params_set) { + dev_dbg(card->dev, + "hw_free: Setting SYSCLK to Zero\n"); + + ret = snd_soc_codec_set_sysclk(wm5102_codec, + ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, + 0, + SND_SOC_CLOCK_IN); + if (ret) + dev_err(card->dev, + "hw_free: Failed to set SYSCLK to Zero: %d\n", + ret); + } + return 0; +} + +static int rpi_cirrus_init_wm5102(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret; + + /* no 32kHz input, derive it from sysclk if needed */ + snd_soc_update_bits(codec, + ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_SRC_MASK, 2); + + if (rpi_cirrus_clear_flls(rtd->card, codec)) + dev_warn(rtd->card->dev, + "init_wm5102: failed to clear FLLs\n"); + + ret = snd_soc_codec_set_sysclk(codec, + ARIZONA_CLK_SYSCLK, ARIZONA_CLK_SRC_FLL1, + 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->card->dev, + "Failed to set SYSCLK to Zero: %d\n", ret); + return ret; + } + + return 0; +} + +static int rpi_cirrus_init_wm8804(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned int mask; + int i, ret; + + for (i = 0; i < 4; i++) { + mask = (i == 3) ? 0x3f : 0xff; + priv->iec958_status[i] = + snd_soc_read(codec, WM8804_SPDTX1 + i) & mask; + } + + /* Setup for 256fs */ + ret = snd_soc_dai_set_clkdiv(codec_dai, + WM8804_MCLK_DIV, WM8804_MCLKDIV_256FS); + if (ret) { + dev_err(card->dev, + "init_wm8804: Failed to set MCLK_DIV to 256fs: %d\n", + ret); + return ret; + } + + /* Output OSC on CLKOUT */ + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8804_CLKOUT_SRC_OSCCLK, WM8804_CLKOUT_HZ, 0); + if (ret) + dev_err(card->dev, + "init_wm8804: Failed to set CLKOUT as OSC Frequency: %d\n", + ret); + + /* Init PLL with default samplerate */ + ret = rpi_cirrus_set_wm8804_pll(card, codec_dai, + RPI_CIRRUS_DEFAULT_RATE); + if (ret) + dev_err(card->dev, + "init_wm8804: Failed to setup PLL for %dHz: %d\n", + RPI_CIRRUS_DEFAULT_RATE, ret); + + return ret; +} + +static struct snd_soc_ops rpi_cirrus_ops = { + .startup = rpi_cirrus_startup, + .hw_params = rpi_cirrus_hw_params, + .hw_free = rpi_cirrus_hw_free, +}; + +static struct snd_soc_dai_link rpi_cirrus_dai[] = { + [DAI_WM5102] = { + .name = "WM5102", + .stream_name = "WM5102 AiFi", + .codec_dai_name = "wm5102-aif1", + .codec_name = "wm5102-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &rpi_cirrus_ops, + .init = rpi_cirrus_init_wm5102, + }, + [DAI_WM8804] = { + .name = "WM5102 SPDIF", + .stream_name = "SPDIF Tx/Rx", + .cpu_dai_name = "wm5102-aif2", + .codec_dai_name = "wm8804-spdif", + .codec_name = "wm8804.1-003b", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .params = &rpi_cirrus_dai_link2_params, + .init = rpi_cirrus_init_wm8804, + }, +}; + + +static int rpi_cirrus_late_probe(struct snd_soc_card *card) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_pcm_runtime *wm8804_runtime = get_wm8804_runtime(card); + int ret; + + dev_dbg(card->dev, "iec958_bits: %02x %02x %02x %02x\n", + priv->iec958_status[0], + priv->iec958_status[1], + priv->iec958_status[2], + priv->iec958_status[3]); + + ret = snd_soc_dai_set_sysclk( + wm5102_runtime->codec_dai, ARIZONA_CLK_SYSCLK, 0, 0); + if (ret) { + dev_err(card->dev, + "Failed to set WM5102 codec dai clk domain: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk( + wm8804_runtime->cpu_dai, ARIZONA_CLK_SYSCLK, 0, 0); + if (ret) + dev_err(card->dev, + "Failed to set WM8804 codec dai clk domain: %d\n", ret); + + return ret; +} + +/* audio machine driver */ +static struct snd_soc_card rpi_cirrus_card = { + .name = "RPi-Cirrus", + .driver_name = "RPiCirrus", + .owner = THIS_MODULE, + .dai_link = rpi_cirrus_dai, + .num_links = ARRAY_SIZE(rpi_cirrus_dai), + .late_probe = rpi_cirrus_late_probe, + .controls = rpi_cirrus_controls, + .num_controls = ARRAY_SIZE(rpi_cirrus_controls), + .dapm_widgets = rpi_cirrus_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rpi_cirrus_dapm_widgets), + .dapm_routes = rpi_cirrus_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rpi_cirrus_dapm_routes), + .set_bias_level = rpi_cirrus_set_bias_level, + .set_bias_level_post = rpi_cirrus_set_bias_level_post, +}; + +static int rpi_cirrus_probe(struct platform_device *pdev) +{ + int ret = 0; + struct rpi_cirrus_priv *priv; + struct device_node *i2s_node; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->min_rate_idx = 1; /* min samplerate 32kHz */ + priv->card_rate = RPI_CIRRUS_DEFAULT_RATE; + + mutex_init(&priv->lock); + + snd_soc_card_set_drvdata(&rpi_cirrus_card, priv); + + if (!pdev->dev.of_node) + return -ENODEV; + + i2s_node = of_parse_phandle( + pdev->dev.of_node, "i2s-controller", 0); + if (!i2s_node) { + dev_err(&pdev->dev, "i2s-controller missing in DT\n"); + return -ENODEV; + } + + rpi_cirrus_dai[DAI_WM5102].cpu_of_node = i2s_node; + rpi_cirrus_dai[DAI_WM5102].platform_of_node = i2s_node; + + rpi_cirrus_card.dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, &rpi_cirrus_card); + if (ret) { + if (ret == -EPROBE_DEFER) + dev_dbg(&pdev->dev, + "register card requested probe deferral\n"); + else + dev_err(&pdev->dev, + "Failed to register card: %d\n", ret); + } + + return ret; +} + +static const struct of_device_id rpi_cirrus_of_match[] = { + { .compatible = "wlf,rpi-cirrus", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rpi_cirrus_of_match); + +static struct platform_driver rpi_cirrus_driver = { + .driver = { + .name = "snd-rpi-cirrus", + .of_match_table = of_match_ptr(rpi_cirrus_of_match), + }, + .probe = rpi_cirrus_probe, +}; + +module_platform_driver(rpi_cirrus_driver); + +MODULE_AUTHOR("Matthias Reichl "); +MODULE_DESCRIPTION("ASoC driver for Cirrus Logic Audio Card"); +MODULE_LICENSE("GPL");