openwrt/target/linux/bcm27xx/patches-4.19/950-0078-ASoC-Add-driver-for-Cirrus-Logic-Audio-Card.patch
Adrian Schmutzler 7d7aa2fd92 brcm2708: rename target to bcm27xx
This change makes the names of Broadcom targets consistent by using
the common notation based on SoC/CPU ID (which is used internally
anyway), bcmXXXX instead of brcmXXXX.
This is even used for target TITLE in make menuconfig already,
only the short target name used brcm so far.

Despite, since subtargets range from bcm2708 to bcm2711, it seems
appropriate to use bcm27xx instead of bcm2708 (again, as already done
for BOARDNAME).

This also renames the packages brcm2708-userland and brcm2708-gpu-fw.

Signed-off-by: Adrian Schmutzler <freifunk@adrianschmutzler.de>
Acked-by: Álvaro Fernández Rojas <noltari@gmail.com>
2020-02-14 14:10:51 +01:00

1049 lines
28 KiB
Diff

From 2c3e4f06b7e7d34a03e747367c26805fbf89a4ac Mon Sep 17 00:00:00 2001
From: Matthias Reichl <hias@horus.com>
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 <hias@horus.com>
---
sound/soc/bcm/rpi-cirrus.c | 1029 ++++++++++++++++++++++++++++++++++++
1 file changed, 1029 insertions(+)
create mode 100644 sound/soc/bcm/rpi-cirrus.c
--- /dev/null
+++ b/sound/soc/bcm/rpi-cirrus.c
@@ -0,0 +1,1029 @@
+/*
+ * ASoC machine driver for Cirrus Logic Audio Card
+ * (with WM5102 and WM8804 codecs)
+ *
+ * Copyright 2015-2017 Matthias Reichl <hias@horus.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <sound/pcm_params.h>
+
+#include <linux/mfd/arizona/registers.h>
+
+#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_component *wm8804_component =
+ get_wm8804_runtime(card)->codec_dai->component;
+ 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_component_update_bits(wm8804_component,
+ 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_component *wm8804_component =
+ get_wm8804_runtime(card)->codec_dai->component;
+ unsigned int val, mask;
+ int i, ret;
+
+ for (i = 0; i < 4; i++) {
+ ret = snd_soc_component_read(wm8804_component,
+ WM8804_RXCHAN1 + i, &val);
+ if (ret)
+ return ret;
+ mask = (i == 3) ? 0x3f : 0xff;
+ ucontrol->value.iec958.status[i] = val & 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_component *wm8804_component =
+ get_wm8804_runtime(card)->codec_dai->component;
+
+ unsigned int bit = kcontrol->private_value & 0xff;
+ unsigned int reg = (kcontrol->private_value >> 8) & 0xff;
+ unsigned int invert = (kcontrol->private_value >> 16) & 0xff;
+ int ret;
+ unsigned int val;
+ bool flag;
+
+ ret = snd_soc_component_read(wm8804_component, reg, &val);
+ if (ret)
+ return ret;
+
+ flag = val & (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_component *wm8804_component =
+ get_wm8804_runtime(card)->codec_dai->component;
+ unsigned int val;
+ int ret;
+
+ ret = snd_soc_component_read(wm8804_component, WM8804_SPDSTAT, &val);
+ if (ret)
+ return ret;
+
+ ucontrol->value.enumerated.item[0] = (val >> 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_component *wm5102_component) {
+
+ int ret1, ret2;
+
+ ret1 = snd_soc_component_set_pll(wm5102_component,
+ WM5102_FLL1, ARIZONA_FLL_SRC_NONE, 0, 0);
+ ret2 = snd_soc_component_set_pll(wm5102_component,
+ 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_component *wm5102_component, unsigned int clk_freq)
+{
+ int ret = snd_soc_component_set_pll(wm5102_component,
+ 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_component *wm5102_component,
+ unsigned int clk_freq, unsigned int aif2_freq)
+{
+ int ret = snd_soc_component_set_pll(wm5102_component,
+ 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_component_set_pll(wm5102_component,
+ 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_component *wm5102_component =
+ get_wm5102_runtime(card)->codec_dai->component;
+
+ 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_component);
+ if (ret) {
+ dev_err(card->dev, "spdif_rx: failed to clear FLLs\n");
+ goto out;
+ }
+
+ ret = rpi_cirrus_set_fll_refclk(card, wm5102_component,
+ 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_component *wm5102_component =
+ wm5102_runtime->codec_dai->component;
+
+ 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_component, 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_component *wm5102_component =
+ wm5102_runtime->codec_dai->component;
+
+ 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_component))
+ 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_component *wm5102_component = rtd->codec_dai->component;
+ 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_component_set_sysclk(wm5102_component,
+ 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_component)) {
+ dev_err(card->dev, "hw_params: failed to clear FLLs\n");
+ goto out;
+ }
+
+ if (rpi_cirrus_set_fll(card, wm5102_component, 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_component *wm5102_component = rtd->codec_dai->component;
+ 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_component_set_sysclk(wm5102_component,
+ 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_component *component = rtd->codec_dai->component;
+ int ret;
+
+ /* no 32kHz input, derive it from sysclk if needed */
+ snd_soc_component_update_bits(component,
+ ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_SRC_MASK, 2);
+
+ if (rpi_cirrus_clear_flls(rtd->card, component))
+ dev_warn(rtd->card->dev,
+ "init_wm5102: failed to clear FLLs\n");
+
+ ret = snd_soc_component_set_sysclk(component,
+ 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_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_soc_card *card = rtd->card;
+ struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+ unsigned int val, mask;
+ int i, ret;
+
+ for (i = 0; i < 4; i++) {
+ ret = snd_soc_component_read(component,
+ WM8804_SPDTX1 + i, &val);
+ if (ret)
+ return ret;
+ mask = (i == 3) ? 0x3f : 0xff;
+ priv->iec958_status[i] = val & 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 <hias@horus.com>");
+MODULE_DESCRIPTION("ASoC driver for Cirrus Logic Audio Card");
+MODULE_LICENSE("GPL");