| From 759d6a02f75342beff69ec1c07389a151a1cacd2 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> | 
 |  | 
 | ASoC: rpi-cirrus: use modern dai_link style | 
 |  | 
 | Signed-off-by: Matthias Reichl <hias@horus.com> | 
 | --- | 
 |  sound/soc/bcm/rpi-cirrus.c | 1035 ++++++++++++++++++++++++++++++++++++ | 
 |  1 file changed, 1035 insertions(+) | 
 |  create mode 100644 sound/soc/bcm/rpi-cirrus.c | 
 |  | 
 | --- /dev/null | 
 | +++ b/sound/soc/bcm/rpi-cirrus.c | 
 | @@ -0,0 +1,1035 @@ | 
 | +/* | 
 | + * 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, | 
 | +}; | 
 | + | 
 | +SND_SOC_DAILINK_DEFS(wm5102, | 
 | +     DAILINK_COMP_ARRAY(COMP_EMPTY()), | 
 | +     DAILINK_COMP_ARRAY(COMP_CODEC("wm5102-codec", "wm5102-aif1")), | 
 | +     DAILINK_COMP_ARRAY(COMP_EMPTY())); | 
 | + | 
 | +SND_SOC_DAILINK_DEFS(wm8804, | 
 | +     DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif2")), | 
 | +     DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif"))); | 
 | + | 
 | +static struct snd_soc_dai_link rpi_cirrus_dai[] = { | 
 | +	[DAI_WM5102] = { | 
 | +		.name		= "WM5102", | 
 | +		.stream_name	= "WM5102 AiFi", | 
 | +		.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, | 
 | +		SND_SOC_DAILINK_REG(wm5102), | 
 | +	}, | 
 | +	[DAI_WM8804] = { | 
 | +		.name		= "WM5102 SPDIF", | 
 | +		.stream_name	= "SPDIF Tx/Rx", | 
 | +		.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, | 
 | +		SND_SOC_DAILINK_REG(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].cpus->of_node = i2s_node; | 
 | +	rpi_cirrus_dai[DAI_WM5102].platforms->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"); |