mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 05:54:26 -04:00 
			
		
		
		
	Right now all brcm2708 patches are extracted from the non-mainline raspberrypi/linux git tree. Many of them are hacks and/or are unneeded in LEDE. Raspberry Pi is getting better and better mainline support so it would be nice to finally start maintaining patches in a cleaner way: 1) Backport patches accepted in upstream tree 2) Start using upstream drivers 3) Pick only these patches that are needed for more complete support Handling above tasks requires grouping patches - ideally using the same prefixes as generic ones. It means we should rename existing patches to use some high prefix. This will allow e.g. use 0xx for backported code. Signed-off-by: Rafał Miłecki <rafal@milecki.pl> Acked-by: Florian Fainelli <f.fainelli@gmail.com> Acked-by: Stijn Tintel <stijn@linux-ipv6.be>
		
			
				
	
	
		
			1239 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			1239 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 01c84e09a331a3b4c29c1cd5ce364c91577c7cea 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>
 | |
| ---
 | |
|  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: <None>
 | |
|  
 | |
|  
 | |
| +Name:   rpi-cirrus-wm5102
 | |
| +Info:   Configures the Cirrus Logic Audio Card
 | |
| +Load:   dtoverlay=rpi-cirrus-wm5102
 | |
| +Params: <None>
 | |
| +
 | |
| +
 | |
|  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 <dt-bindings/pinctrl/bcm2835.h>
 | |
| +#include <dt-bindings/gpio/gpio.h>
 | |
| +#include <dt-bindings/mfd/arizona.h>
 | |
| +
 | |
| +/ {
 | |
| +	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 <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_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 <hias@horus.com>");
 | |
| +MODULE_DESCRIPTION("ASoC driver for Cirrus Logic Audio Card");
 | |
| +MODULE_LICENSE("GPL");
 |