[alsa-devel] [PATCH 1/8] regmap: Add support for 24 bit wide register addresses
Since regmap already has support for formatting 24 bit wide values, so adding support for 24 bit wide registers is pretty much straight forward.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- drivers/base/regmap/regmap.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 42d5cb0..26af93a 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -500,6 +500,12 @@ struct regmap *regmap_init(struct device *dev, } break;
+ case 24: + if (reg_endian != REGMAP_ENDIAN_BIG) + goto err_map; + map->format.format_reg = regmap_format_24; + break; + case 32: switch (reg_endian) { case REGMAP_ENDIAN_BIG:
Virtual switches are similar to virtual MUX controls. It does not have a representation in the actual hardware but allows to control the DAPM routing by enabling or disabling a input path into the mixer. This is useful if we want to present a way to the user to for example mute a path despite the absence of a separate mute control in hardware.
More specific this will be used by the ADAU1361 driver where the hardware has a mute control for each of the DAC output paths, but we have to make sure that the path is muted whenever the DAC is disabled. So the mute switches needs to be controlled by DAPM so it can disable them before disabling the DAC. On the other hand we still want to be to mute individual paths. This will be accomplished by using virtual switch controls.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- include/sound/soc-dapm.h | 10 +++++++ sound/soc/soc-dapm.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index e1ef63d..c3d0371 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -257,6 +257,12 @@ struct device; .info = snd_soc_info_volsw, \ .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } +#define SOC_DAPM_SINGLE_VIRT(xname, shift, max) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw_virt, \ + .put = snd_soc_dapm_put_volsw_virt, \ + .private_value = SOC_SINGLE_VALUE(SND_SOC_NOPM, shift, max, 0) } #define SOC_DAPM_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, \ @@ -344,6 +350,10 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int snd_soc_dapm_put_volsw_virt(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_soc_dapm_get_volsw_virt(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 1e36bc8..c3a0f43 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -347,7 +347,10 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert;
- val = soc_widget_read(w, reg); + if (reg >= 0) + val = soc_widget_read(w, reg); + else + val = 0; val = (val >> shift) & mask; if (invert) val = max - val; @@ -2697,6 +2700,79 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw);
/** + * snd_soc_dapm_get_volsw_virt - virtual dapm mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a virtual dapm mixer control. + * + * Returns 0 for success. + */ +int snd_soc_dapm_get_volsw_virt(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int shift = mc->shift; + unsigned int mask = (1 << fls(mc->max)) - 1; + + ucontrol->value.integer.value[0] = (widget->value >> shift) & mask; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw_virt); + +/** + * snd_soc_dapm_put_volsw_virt - virtual dapm mixer set callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a virutal dapm mixer control. + * + * Returns 0 for success. + */ +int snd_soc_dapm_put_volsw_virt(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_card *card = widget->codec->card; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mask = (1 << fls(mc->max)) - 1; + unsigned int shift = mc->shift; + unsigned int val, connect; + int wi; + + val = ucontrol->value.integer.value[0] & mask; + + mask = mask << shift; + val = val << shift; + + if (val) + connect = 1; + else + connect = 0; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + if (val != (widget->value & mask)) { + widget->value &= ~mask; + widget->value |= val; + for (wi = 0; wi < wlist->num_widgets; wi++) { + widget = wlist->widgets[wi]; + soc_dapm_mixer_update_power(widget, kcontrol, connect); + } + } + + mutex_unlock(&card->dapm_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw_virt); + +/** * snd_soc_dapm_get_enum_double - dapm enumerated double mixer get callback * @kcontrol: mixer control * @ucontrol: control element information
On Thu, Jan 10, 2013 at 05:06:11PM +0100, Lars-Peter Clausen wrote:
More specific this will be used by the ADAU1361 driver where the hardware has a mute control for each of the DAC output paths, but we have to make sure that the path is muted whenever the DAC is disabled. So the mute switches needs to be controlled by DAPM so it can disable them before disabling the DAC. On the other hand we still want to be to mute individual paths. This will be accomplished by using virtual switch controls.
This doesn't sound like it does quite what you want - it means you don't actually end up with a mute control, only a power control (which is also going to affect everything else in the path). I'd expect to see something like this implemented by having a control that has a specified value forced in the register while the control is enabled (kind of the opposite of a supply). This is a fairly common need for older parts though it's unusual to see it on a new device.
On 01/11/2013 01:19 PM, Mark Brown wrote:
On Thu, Jan 10, 2013 at 05:06:11PM +0100, Lars-Peter Clausen wrote:
More specific this will be used by the ADAU1361 driver where the hardware has a mute control for each of the DAC output paths, but we have to make sure that the path is muted whenever the DAC is disabled. So the mute switches needs to be controlled by DAPM so it can disable them before disabling the DAC. On the other hand we still want to be to mute individual paths. This will be accomplished by using virtual switch controls.
This doesn't sound like it does quite what you want - it means you don't actually end up with a mute control, only a power control (which is also going to affect everything else in the path). I'd expect to see something like this implemented by having a control that has a specified value forced in the register while the control is enabled (kind of the opposite of a supply). This is a fairly common need for older parts though it's unusual to see it on a new device.
I think I want the opposite of what you just described. I want to be able to overwrite the control setting based on the power state.
The hardware setup looks a bit like this. +------------+ /---- [Mute] ---- | Left Mixer | .... +----------+ / +------------+ | Left DAC | -< +----------+ \ +-------------+ ---- [Mute] ---- | Right Mixer | ... +-------------+
Same for the Right DAC. The DAC output goes to GND when the DAC is disabled. If the mute control is enabled its output goes to midscale otherwise. So whenever the DAC is disabled we need to make sure that all mute controls after the DAC output are also enabled to avoid pop noises. Still we want to be able to enable a mute control even if the DAC is enabled. For example to route only the left DAC to the left mixer and the right DAC to the right mixer.
In ASoC I modeled this by letting DAPM take care of mute controls. E.g. the mute control gets disabled if there is an active path from the DAC through the mixer to one of the outputs and gets disabled otherwise. This makes sure that when the DAC is powered down (when there is no active path from the DAC to any of the outputs) each mute control is also enabled. The virtual switches now allow disable a path from the DAC to the mixer, which in turn will cause DAPM to enable the mute control. This is similar to how the virtual enums already work.
- Lars
On Fri, Jan 11, 2013 at 01:38:55PM +0100, Lars-Peter Clausen wrote:
On 01/11/2013 01:19 PM, Mark Brown wrote:
going to affect everything else in the path). I'd expect to see something like this implemented by having a control that has a specified value forced in the register while the control is enabled (kind of the opposite of a supply). This is a fairly common need for older parts though it's unusual to see it on a new device.
I think I want the opposite of what you just described. I want to be able to overwrite the control setting based on the power state.
Right, just a thinko though - you see the point though.
In ASoC I modeled this by letting DAPM take care of mute controls. E.g. the mute control gets disabled if there is an active path from the DAC through the mixer to one of the outputs and gets disabled otherwise. This makes sure that when the DAC is powered down (when there is no active path from the DAC to any of the outputs) each mute control is also enabled. The virtual switches now allow disable a path from the DAC to the mixer, which in turn will cause DAPM to enable the mute control. This is similar to how the virtual enums already work.
Virtual enums do actually end up routing, that's not what a mute control usually does. You can do the above in the manner I suggested, just have the register forced to a particular value when the DAC is disabled.
On 01/11/2013 01:45 PM, Mark Brown wrote:
On Fri, Jan 11, 2013 at 01:38:55PM +0100, Lars-Peter Clausen wrote:
On 01/11/2013 01:19 PM, Mark Brown wrote:
going to affect everything else in the path). I'd expect to see something like this implemented by having a control that has a specified value forced in the register while the control is enabled (kind of the opposite of a supply). This is a fairly common need for older parts though it's unusual to see it on a new device.
I think I want the opposite of what you just described. I want to be able to overwrite the control setting based on the power state.
Right, just a thinko though - you see the point though.
In ASoC I modeled this by letting DAPM take care of mute controls. E.g. the mute control gets disabled if there is an active path from the DAC through the mixer to one of the outputs and gets disabled otherwise. This makes sure that when the DAC is powered down (when there is no active path from the DAC to any of the outputs) each mute control is also enabled. The virtual switches now allow disable a path from the DAC to the mixer, which in turn will cause DAPM to enable the mute control. This is similar to how the virtual enums already work.
Virtual enums do actually end up routing, that's not what a mute control usually does. You can do the above in the manner I suggested, just have the register forced to a particular value when the DAC is disabled.
Well, that's what the code does. The alternative is to implement more or less in the driver. Have custom put/get callbacks for the controls, which write to a shadow register, only if the DAC is enabled the shadow value gets written to the real register. And listen to the DAC powerdown/powerup events, when it is powered down enable all hw mutes, when it is enabled restore the shadow register value. The virtual control more or less implements it in a generic manner so it does not have to be implemented by each driver which needs this on its own.
- Lars
On Fri, Jan 11, 2013 at 01:56:17PM +0100, Lars-Peter Clausen wrote:
On 01/11/2013 01:45 PM, Mark Brown wrote:
Virtual enums do actually end up routing, that's not what a mute control usually does. You can do the above in the manner I suggested, just have the register forced to a particular value when the DAC is disabled.
Well, that's what the code does. The alternative is to implement more or
No, it's not (at least not according to the changelog). It implements a totally separate virtual control.
less in the driver. Have custom put/get callbacks for the controls, which write to a shadow register, only if the DAC is enabled the shadow value gets written to the real register. And listen to the DAC powerdown/powerup events, when it is powered down enable all hw mutes, when it is enabled
What makes you say that this must be open coded in the driver? We can modify the framework code...
restore the shadow register value. The virtual control more or less implements it in a generic manner so it does not have to be implemented by each driver which needs this on its own.
I'm having a really hard time seeing the equivalence here.
On 01/11/2013 02:57 PM, Mark Brown wrote:
On Fri, Jan 11, 2013 at 01:56:17PM +0100, Lars-Peter Clausen wrote:
On 01/11/2013 01:45 PM, Mark Brown wrote:
Virtual enums do actually end up routing, that's not what a mute control usually does. You can do the above in the manner I suggested, just have the register forced to a particular value when the DAC is disabled.
Well, that's what the code does. The alternative is to implement more or
No, it's not (at least not according to the changelog). It implements a totally separate virtual control.
What do you mean by 'separate'? Maybe this is was causes the confusion. Do you mean a separate DAPM control, like DAPM_SWITCH? That's not what the patch does it creates a virtual kcontrol, which can be used to control a mixers input. It's the pendant to SOC_DAPM_ENUM_VIRT not to SND_SOC_DAPM_VIRT_MUX. I realize that the commit message may have been a bit ambiguous in that regard.
- Lars
On 01/11/2013 04:05 PM, Lars-Peter Clausen wrote:
On 01/11/2013 02:57 PM, Mark Brown wrote:
On Fri, Jan 11, 2013 at 01:56:17PM +0100, Lars-Peter Clausen wrote:
On 01/11/2013 01:45 PM, Mark Brown wrote:
Virtual enums do actually end up routing, that's not what a mute control usually does. You can do the above in the manner I suggested, just have the register forced to a particular value when the DAC is disabled.
Well, that's what the code does. The alternative is to implement more or
No, it's not (at least not according to the changelog). It implements a totally separate virtual control.
What do you mean by 'separate'? Maybe this is was causes the confusion. Do you mean a separate DAPM control, like DAPM_SWITCH? That's not what the patch does it creates a virtual kcontrol, which can be used to control a mixers input. It's the pendant to SOC_DAPM_ENUM_VIRT not to
s/pendant/counterpart/ apparently this is a false friend
SND_SOC_DAPM_VIRT_MUX. I realize that the commit message may have been a bit ambiguous in that regard.
- Lars
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Fri, Jan 11, 2013 at 04:05:53PM +0100, Lars-Peter Clausen wrote:
What do you mean by 'separate'? Maybe this is was causes the confusion. Do you mean a separate DAPM control, like DAPM_SWITCH? That's not what the patch does it creates a virtual kcontrol, which can be used to control a mixers input. It's the pendant to SOC_DAPM_ENUM_VIRT not to
The issue I'm seeing is that it sounds like this isn't joined up with the mute control (as it's a virtual control).
SND_SOC_DAPM_VIRT_MUX. I realize that the commit message may have been a bit ambiguous in that regard.
It's the virtual bit that's concerning me here so I don't think that's really it.
On Thu, Jan 10, 2013 at 05:06:11PM +0100, Lars-Peter Clausen wrote:
Virtual switches are similar to virtual MUX controls. It does not have a representation in the actual hardware but allows to control the DAPM routing by enabling or disabling a input path into the mixer. This is useful if we want to present a way to the user to for example mute a path despite the absence of a separate mute control in hardware.
So, following IRC discussion it seems like the main issue here is that the changelog is the main issue here - this is actually for moving the writes done when controlling a DAPM mixer input rather than adding new widgets to control power on/off of paths.
The ADAU1X61 and ADAU1X81 are very similar in the digital domain of the CODECs, but are quite different in the analog domain. This patch adds support for the common parts of the ADAU161 and ADAU181 CODECs.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- include/linux/platform_data/adau17x1.h | 23 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/adau17x1.c | 895 +++++++++++++++++++++++++++++++++ sound/soc/codecs/adau17x1.h | 149 ++++++ 5 files changed, 1073 insertions(+) create mode 100644 include/linux/platform_data/adau17x1.h create mode 100644 sound/soc/codecs/adau17x1.c create mode 100644 sound/soc/codecs/adau17x1.h
diff --git a/include/linux/platform_data/adau17x1.h b/include/linux/platform_data/adau17x1.h new file mode 100644 index 0000000..fdf419a --- /dev/null +++ b/include/linux/platform_data/adau17x1.h @@ -0,0 +1,23 @@ +/* + * Driver for ADAU1761/ADAU1461/ADAU1761/ADAU1961/ADAU1781/ADAU1781 codecs + * + * Copyright 2011-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen lars@metafoo.de + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __LINUX_PLATFORM_DATA_ADAU17X1_H__ +#define __LINUX_PLATFORM_DATA_ADAU17X1_H__ + +/** + * enum adau17x1_micbias_voltage - Microphone bias voltage + * @ADAU17X1_MICBIAS_0_90_AVDD: 0.9 * AVDD + * @ADAU17X1_MICBIAS_0_65_AVDD: 0.65 * AVDD + */ +enum adau17x1_micbias_voltage { + ADAU17X1_MICBIAS_0_90_AVDD = 0, + ADAU17X1_MICBIAS_0_65_AVDD = 1, +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3a84782..d7dbcb3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -181,6 +181,10 @@ config SND_SOC_ADAU1701 config SND_SOC_ADAU1373 tristate
+config SND_SOC_ADAU17X1 + select SND_SOC_SIGMADSP + tristate + config SND_SOC_ADAV80X tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index f6e8e36..d67528f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -7,6 +7,7 @@ snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o snd-soc-adau1701-objs := adau1701.o snd-soc-adau1373-objs := adau1373.o +snd-soc-adau17x1-objs := adau17x1.o snd-soc-adav80x-objs := adav80x.o snd-soc-ads117x-objs := ads117x.o snd-soc-ak4104-objs := ak4104.o @@ -129,6 +130,7 @@ obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o +obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c new file mode 100644 index 0000000..ccf5d7e --- /dev/null +++ b/sound/soc/codecs/adau17x1.c @@ -0,0 +1,895 @@ +/* + * Common code for ADAU1X61 and ADAU1X81 codecs + * + * Copyright 2011-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen lars@metafoo.de + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <linux/gcd.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/regmap.h> + +#include "sigmadsp.h" +#include "adau17x1.h" + +static const char * const adau17x1_capture_mixer_boost_text[] = { + "Normal operation", "Boost Level 1", "Boost Level 2", "Boost Level 3", +}; + +static const SOC_ENUM_SINGLE_DECL(adau17x1_capture_boost_enum, + ADAU17X1_REC_POWER_MGMT, 5, adau17x1_capture_mixer_boost_text); + +static const char * const adau17x1_mic_bias_mode_text[] = { + "Normal operation", "High performance", +}; + +static SOC_ENUM_SINGLE_DECL(adau17x1_mic_bias_mode_enum, + ADAU17X1_MICBIAS, 3, adau17x1_mic_bias_mode_text); + +static const char * const adau17x1_mono_stereo_text[] = { + "Stereo", + "Mono Left Channel (L+R)", + "Mono Right Channel (L+R)", + "Mono (L+R)", +}; + +static const SOC_ENUM_SINGLE_DECL(adau17x1_dac_mode_enum, + ADAU17X1_DAC_CONTROL0, 6, adau17x1_mono_stereo_text); + +static const DECLARE_TLV_DB_MINMAX(adau17x1_digital_tlv, -9563, 0); + +static const struct snd_kcontrol_new adau17x1_controls[] = { + SOC_DOUBLE_R_TLV("Digital Capture Volume", + ADAU17X1_LEFT_INPUT_DIGITAL_VOL, + ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, + 0, 0xff, 1, adau17x1_digital_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume", ADAU17X1_DAC_CONTROL1, + ADAU17X1_DAC_CONTROL2, 0, 0xff, 1, adau17x1_digital_tlv), + + SOC_SINGLE("ADC High Pass Filter Switch", ADAU17X1_ADC_CONTROL, + 5, 1, 0), + SOC_SINGLE("Playback De-emphasis Switch", ADAU17X1_DAC_CONTROL0, + 2, 1, 0), + + SOC_ENUM("Capture Boost", adau17x1_capture_boost_enum), + + SOC_ENUM("Mic Bias Mode", adau17x1_mic_bias_mode_enum), + + SOC_ENUM("DAC Mono Stereo", adau17x1_dac_mode_enum), +}; + +static int adau17x1_pll_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct adau *adau = snd_soc_codec_get_drvdata(w->codec); + int ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) { + adau->pll_regs[5] = 1; + } else { + adau->pll_regs[5] = 0; + /* Bypass the PLL when disabled, otherwise registers will become + * inaccessible. */ + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL, 0); + } + + /* The PLL register is 6 bytes long and can only be written at once. */ + ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, + adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + mdelay(5); + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL, + ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL); + } + + return 0; +} + +static const struct snd_soc_dapm_widget adau17x1_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("PLL", 3, SND_SOC_NOPM, 0, 0, adau17x1_pll_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("AIFCLK", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("MICBIAS", ADAU17X1_MICBIAS, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Left Playback Enable", ADAU17X1_PLAY_POWER_MGMT, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right Playback Enable", ADAU17X1_PLAY_POWER_MGMT, + 1, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Left Dec Filter Enable", ADAU17X1_ADC_CONTROL, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right Dec Filter Enable", ADAU17X1_ADC_CONTROL, + 1, 0, NULL, 0), + + SND_SOC_DAPM_DAC("Left DAC", NULL, ADAU17X1_DAC_CONTROL0, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", NULL, ADAU17X1_DAC_CONTROL0, 1, 0), + SND_SOC_DAPM_ADC("Left ADC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("Right ADC", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFIN", "Playback", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route adau17x1_dapm_routes[] = { + { "Left ADC", NULL, "SYSCLK" }, + { "Right ADC", NULL, "SYSCLK" }, + { "Left DAC", NULL, "SYSCLK" }, + { "Right DAC", NULL, "SYSCLK" }, + { "AIFOUT", NULL, "SYSCLK" }, + { "AIFIN", NULL, "SYSCLK" }, + + { "Left ADC", NULL, "Left Dec Filter Enable" }, + { "Right ADC", NULL, "Right Dec Filter Enable" }, + + { "AIFOUT", NULL, "AIFCLK" }, + { "AIFIN", NULL, "AIFCLK" }, +}; + +static const struct snd_soc_dapm_route adau17x1_dapm_pll_route = { + "SYSCLK", NULL, "PLL", +}; + +static const char * const adau17x1_dac_mux_text[] = { + "AIFIN", + "DSP", +}; + +int adau17x1_dsp_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *w = wlist->widgets[0]; + struct adau *adau = snd_soc_codec_get_drvdata(w->codec); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val, change; + struct snd_soc_dapm_update update; + + if (ucontrol->value.enumerated.item[0] >= e->max) + return -EINVAL; + + switch (ucontrol->value.enumerated.item[0]) { + case 0: + switch (e->reg) { + case ADAU17X1_SERIAL_INPUT_ROUTE: + val = (adau->tdm_dac_slot * 2) + 1; + break; + case ADAU17X1_SERIAL_OUTPUT_ROUTE: + val = (adau->tdm_dac_slot * 2) + 1; + break; + default: + val = 0; + break; + } + break; + default: + val = 0; + break; + } + + change = snd_soc_test_bits(w->codec, e->reg, 0xff, val); + if (change) { + update.kcontrol = kcontrol; + update.widget = w; + update.reg = e->reg; + update.mask = 0xff; + update.val = val; + w->dapm->update = &update; + + snd_soc_dapm_mux_update_power(w, kcontrol, + ucontrol->value.enumerated.item[0], e); + + w->dapm->update = NULL; + } + + return change; +} +EXPORT_SYMBOL_GPL(adau17x1_dsp_mux_enum_put); + +int adau17x1_dsp_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val; + + val = snd_soc_read(widget->codec, e->reg); + ucontrol->value.enumerated.item[0] = val == 0; + + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_dsp_mux_enum_get); + +static const SOC_ENUM_SINGLE_DECL(adau17x1_dac_mux_enum, + ADAU17X1_SERIAL_INPUT_ROUTE, 0, + adau17x1_dac_mux_text); + +static const struct snd_kcontrol_new adau17x1_dac_mux = + ADAU17X1_DSP_MUX_ENUM("DAC Playback Mux", adau17x1_dac_mux_enum); + +static const struct snd_soc_dapm_widget adau17x1_dsp_dapm_widgets[] = { + SND_SOC_DAPM_PGA("DSP", ADAU17X1_DSP_RUN, 0, 0, NULL, 0), + SND_SOC_DAPM_SIGGEN("DSP Siggen"), + + SND_SOC_DAPM_VIRT_MUX("DAC Playback Mux", SND_SOC_NOPM, 0, 0, + &adau17x1_dac_mux), + +}; + +static const struct snd_soc_dapm_route adau17x1_dsp_dapm_routes[] = { + { "DAC Playback Mux", "DSP", "DSP" }, + { "DAC Playback Mux", "AIFIN", "AIFIN" }, + + { "Left DAC", NULL, "DAC Playback Mux" }, + { "Right DAC", NULL, "DAC Playback Mux" }, + + { "AIFOUT Capture Mux", "DSP", "DSP" }, + + { "AIFOUT", NULL, "AIFOUT Capture Mux" }, + + { "DSP", NULL, "DSP Siggen" }, +}; + +static const struct snd_soc_dapm_route adau17x1_no_dsp_dapm_routes[] = { + { "Left DAC", NULL, "AIFIN" }, + { "Right DAC", NULL, "AIFIN" }, +}; + +static void adau17x1_check_aifclk(struct snd_soc_codec *codec) +{ + struct adau *adau = snd_soc_codec_get_drvdata(codec); + + /* If we are in master mode we need to generate bit- and frameclock, + * regardless of whether there is an active path or not */ + if (codec->active && adau->master) + snd_soc_dapm_force_enable_pin(&codec->dapm, "AIFCLK"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "AIFCLK"); + snd_soc_dapm_sync(&codec->dapm); +} + +bool adau17x1_has_dsp(struct adau *adau) +{ + switch (adau->type) { + case ADAU1761: + case ADAU1381: + case ADAU1781: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(adau17x1_has_dsp); + +static int adau17x1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct adau *adau = snd_soc_codec_get_drvdata(codec); + unsigned int val, div, dsp_div; + unsigned int freq; + + if (adau->clk_src == ADAU17X1_CLK_SRC_PLL) + freq = adau->pll_freq; + else + freq = adau->sysclk / adau->sysclk_div; + + if (freq % params_rate(params) != 0) + return -EINVAL; + + switch (freq / params_rate(params)) { + case 1024: /* fs */ + div = 0; + dsp_div = 1; + break; + case 6144: /* fs / 6 */ + div = 1; + dsp_div = 6; + break; + case 4096: /* fs / 4 */ + div = 2; + dsp_div = 5; + break; + case 3072: /* fs / 3 */ + div = 3; + dsp_div = 4; + break; + case 2048: /* fs / 2 */ + div = 4; + dsp_div = 3; + break; + case 1536: /* fs / 1.5 */ + div = 5; + dsp_div = 2; + break; + case 512: /* fs / 0.5 */ + div = 6; + dsp_div = 0; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER0, 7, div); + if (adau17x1_has_dsp(adau)) { + regmap_write(adau->regmap, ADAU17X1_SERIAL_SAMPLING_RATE, div); + regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, dsp_div); + } + + adau17x1_check_aifclk(codec); + + if (adau->dai_fmt != SND_SOC_DAIFMT_RIGHT_J) + return 0; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = ADAU17X1_SERIAL_PORT1_DELAY16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = ADAU17X1_SERIAL_PORT1_DELAY8; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val = ADAU17X1_SERIAL_PORT1_DELAY0; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT1, + ADAU17X1_SERIAL_PORT1_DELAY_MASK, val); +} + +static int adau17x1_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + adau17x1_check_aifclk(dai->codec); + + return 0; +} + +static void adau17x1_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + adau17x1_check_aifclk(dai->codec); +} + +static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = dai->codec; + struct adau *adau = snd_soc_codec_get_drvdata(codec); + unsigned int div; + unsigned int r, n, m, i, j; + + if (freq_in < 8000000 || freq_in > 27000000) + return -EINVAL; + + if (!freq_out) { + r = 0; + n = 0; + m = 0; + div = 0; + } else { + if (freq_out % freq_in != 0) { + div = DIV_ROUND_UP(freq_in, 13500000); + freq_in /= div; + r = freq_out / freq_in; + i = freq_out % freq_in; + j = gcd(i, freq_in); + n = i / j; + m = freq_in / j; + div--; + } else { + r = freq_out / freq_in; + n = 0; + m = 0; + div = 0; + } + if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) + return -EINVAL; + } + + adau->pll_regs[0] = m >> 8; + adau->pll_regs[1] = m & 0xff; + adau->pll_regs[2] = n >> 8; + adau->pll_regs[3] = n & 0xff; + adau->pll_regs[4] = (r << 3) | (div << 1); + if (m != 0) + adau->pll_regs[4] |= 1; /* Fractional mode */ + adau->pll_regs[5] = 0; + + adau->pll_freq = freq_out; + + return 0; +} + +static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); + struct snd_soc_dapm_context *dapm = &dai->codec->dapm; + + switch (clk_id) { + case ADAU17X1_CLK_SRC_MCLK: + case ADAU17X1_CLK_SRC_PLL: + break; + default: + return -EINVAL; + } + + adau->sysclk = freq; + + if (adau->clk_src != clk_id) { + if (clk_id == ADAU17X1_CLK_SRC_PLL) { + snd_soc_dapm_add_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } else { + snd_soc_dapm_del_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } + } + + adau->clk_src = clk_id; + + return 0; +} + +static int adau17x1_set_dai_clkdiv(struct snd_soc_dai *dai, int div_id, int div) +{ + struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); + + switch (div) { + case 1: + case 2: + case 3: + case 4: + break; + default: + return -EINVAL; + } + + adau->sysclk_div = div; + + return regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_INFREQ_MASK, (div - 1) << 1); +} + +static int adau17x1_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); + unsigned int ctrl0, ctrl1; + int lrclk_pol; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl0 = ADAU17X1_SERIAL_PORT0_MASTER; + adau->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ctrl0 = 0; + adau->master = false; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + lrclk_pol = 0; + ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY1; + break; + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + lrclk_pol = 1; + ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY0; + break; + case SND_SOC_DAIFMT_DSP_A: + lrclk_pol = 1; + ctrl0 |= ADAU17X1_SERIAL_PORT0_PULSE_MODE; + ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY1; + break; + case SND_SOC_DAIFMT_DSP_B: + lrclk_pol = 1; + ctrl0 |= ADAU17X1_SERIAL_PORT0_PULSE_MODE; + ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl0 |= ADAU17X1_SERIAL_PORT0_BCLK_POL; + break; + case SND_SOC_DAIFMT_NB_IF: + lrclk_pol = !lrclk_pol; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl0 |= ADAU17X1_SERIAL_PORT0_BCLK_POL; + lrclk_pol = !lrclk_pol; + break; + default: + return -EINVAL; + } + + if (lrclk_pol) + ctrl0 |= ADAU17X1_SERIAL_PORT0_LRCLK_POL; + + regmap_write(adau->regmap, ADAU17X1_SERIAL_PORT0, ctrl0); + regmap_write(adau->regmap, ADAU17X1_SERIAL_PORT1, ctrl1); + + adau->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + return 0; +} + +static int adau17x1_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); + unsigned int ser_ctrl0, ser_ctrl1; + unsigned int conv_ctrl0, conv_ctrl1; + + /* I2S mode */ + if (slots == 0) { + slots = 2; + rx_mask = 3; + tx_mask = 3; + slot_width = 32; + } + + switch (slots) { + case 2: + ser_ctrl0 = ADUA_SERIAL_PORT0_STEREO; + break; + case 4: + ser_ctrl0 = ADUA_SERIAL_PORT0_TDM4; + break; + case 8: + if (adau->type == ADAU1361) + return -EINVAL; + + ser_ctrl0 = ADUA_SERIAL_PORT0_TDM8; + break; + default: + return -EINVAL; + } + + switch (slot_width * slots) { + case 32: + if (adau->type == ADAU1761) + return -EINVAL; + + ser_ctrl1 = ADUA_SERIAL_PORT1_BCLK32; + break; + case 64: + ser_ctrl1 = ADUA_SERIAL_PORT1_BCLK64; + break; + case 48: + ser_ctrl1 = ADUA_SERIAL_PORT1_BCLK48; + break; + case 128: + ser_ctrl1 = ADUA_SERIAL_PORT1_BCLK128; + break; + case 256: + if (adau->type == ADAU1361) + return -EINVAL; + + ser_ctrl1 = ADUA_SERIAL_PORT1_BCLK256; + break; + default: + return -EINVAL; + } + + switch (rx_mask) { + case 0x03: + conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(1); + adau->tdm_adc_slot = 0; + break; + case 0x0c: + conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(2); + adau->tdm_adc_slot = 1; + break; + case 0x30: + conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(3); + adau->tdm_adc_slot = 2; + break; + case 0xc0: + conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(4); + adau->tdm_adc_slot = 3; + break; + default: + return -EINVAL; + } + + switch (tx_mask) { + case 0x03: + conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(1); + adau->tdm_dac_slot = 0; + break; + case 0x0c: + conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(2); + adau->tdm_dac_slot = 1; + break; + case 0x30: + conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(3); + adau->tdm_dac_slot = 2; + break; + case 0xc0: + conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(4); + adau->tdm_dac_slot = 3; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER0, + ADAU17X1_CONVERTER0_DAC_PAIR_MASK, conv_ctrl0); + regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER1, + ADAU17X1_CONVERTER1_ADC_PAIR_MASK, conv_ctrl1); + regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT0, + ADAU17X1_SERIAL_PORT0_TDM_MASK, ser_ctrl0); + regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT1, + ADAU17X1_SERIAL_PORT1_BCLK_MASK, ser_ctrl1); + + if (adau17x1_has_dsp(adau)) { + if (adau->dsp_playback_bypass) { + regmap_write(adau->regmap, ADAU17X1_SERIAL_INPUT_ROUTE, + (adau->tdm_dac_slot * 2) + 1); + } + if (adau->dsp_capture_bypass) { + regmap_write(adau->regmap, ADAU17X1_SERIAL_OUTPUT_ROUTE, + (adau->tdm_adc_slot * 2) + 1); + } + } + + return 0; +} + +const struct snd_soc_dai_ops adau17x1_dai_ops = { + .hw_params = adau17x1_hw_params, + .set_sysclk = adau17x1_set_dai_sysclk, + .set_fmt = adau17x1_set_dai_fmt, + .set_pll = adau17x1_set_dai_pll, + .set_clkdiv = adau17x1_set_dai_clkdiv, + .set_tdm_slot = adau17x1_set_dai_tdm_slot, + .startup = adau17x1_dai_startup, + .shutdown = adau17x1_dai_shutdown, +}; +EXPORT_SYMBOL_GPL(adau17x1_dai_ops); + +int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec, + enum adau17x1_micbias_voltage micbias) +{ + struct adau *adau = snd_soc_codec_get_drvdata(codec); + + switch (micbias) { + case ADAU17X1_MICBIAS_0_90_AVDD: + case ADAU17X1_MICBIAS_0_65_AVDD: + break; + default: + return -EINVAL; + } + + regmap_write(adau->regmap, ADAU17X1_MICBIAS, micbias << 2); + + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_set_micbias_voltage); + +bool adau17x1_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU17X1_CLOCK_CONTROL: + case ADAU17X1_PLL_CONTROL: + case ADAU17X1_REC_POWER_MGMT: + case ADAU17X1_MICBIAS: + case ADAU17X1_SERIAL_PORT0: + case ADAU17X1_SERIAL_PORT1: + case ADAU17X1_CONVERTER0: + case ADAU17X1_CONVERTER1: + case ADAU17X1_LEFT_INPUT_DIGITAL_VOL: + case ADAU17X1_RIGHT_INPUT_DIGITAL_VOL: + case ADAU17X1_ADC_CONTROL: + case ADAU17X1_PLAY_POWER_MGMT: + case ADAU17X1_DAC_CONTROL0: + case ADAU17X1_DAC_CONTROL1: + case ADAU17X1_DAC_CONTROL2: + case ADAU17X1_SERIAL_PORT_PAD: + case ADAU17X1_CONTROL_PORT_PAD0: + case ADAU17X1_CONTROL_PORT_PAD1: + case ADAU17X1_DSP_SAMPLING_RATE: + case ADAU17X1_SERIAL_INPUT_ROUTE: + case ADAU17X1_SERIAL_OUTPUT_ROUTE: + case ADAU17X1_DSP_ENABLE: + case ADAU17X1_DSP_RUN: + case ADAU17X1_SERIAL_SAMPLING_RATE: + return true; + default: + break; + } + return false; +} +EXPORT_SYMBOL_GPL(adau17x1_readable_register); + +bool adau17x1_volatile_register(struct device *dev, unsigned int reg) +{ + /* SigmaDSP parameter and program memory */ + if (reg < 0x4000) + return true; + + switch (reg) { + /* The PLL register is 6 bytes long */ + case ADAU17X1_PLL_CONTROL: + case ADAU17X1_PLL_CONTROL + 1: + case ADAU17X1_PLL_CONTROL + 2: + case ADAU17X1_PLL_CONTROL + 3: + case ADAU17X1_PLL_CONTROL + 4: + case ADAU17X1_PLL_CONTROL + 5: + return true; + default: + break; + } + + return false; +} +EXPORT_SYMBOL_GPL(adau17x1_volatile_register); + +int adau17x1_load_firmware(struct adau *adau, struct device *dev, + const char *firmware) +{ + int ret; + int dspsr; + + ret = regmap_read(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, &dspsr); + if (ret) + return ret; + + regmap_write(adau->regmap, ADAU17X1_DSP_ENABLE, 1); + regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, 0xf); + + ret = process_sigma_firmware_regmap(dev, adau->regmap, firmware); + if (ret) { + regmap_write(adau->regmap, ADAU17X1_DSP_ENABLE, 0); + return ret; + } + regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, dspsr); + + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_load_firmware); + +int adau17x1_probe(struct snd_soc_codec *codec) +{ + struct adau *adau = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = snd_soc_codec_set_cache_io(codec, 0, 0, SND_SOC_REGMAP); + if (ret) + return ret; + + codec->driver->set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = snd_soc_add_codec_controls(codec, adau17x1_controls, + ARRAY_SIZE(adau17x1_controls)); + if (ret) + return ret; + ret = snd_soc_dapm_new_controls(&codec->dapm, adau17x1_dapm_widgets, + ARRAY_SIZE(adau17x1_dapm_widgets)); + if (ret) + return ret; + + if (adau17x1_has_dsp(adau)) { + ret = snd_soc_dapm_new_controls(&codec->dapm, + adau17x1_dsp_dapm_widgets, + ARRAY_SIZE(adau17x1_dsp_dapm_widgets)); + } + return ret; +} +EXPORT_SYMBOL_GPL(adau17x1_probe); + +int adau17x1_add_routes(struct snd_soc_codec *codec) +{ + struct adau *adau = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = snd_soc_dapm_add_routes(&codec->dapm, adau17x1_dapm_routes, + ARRAY_SIZE(adau17x1_dapm_routes)); + if (ret) + return ret; + + if (adau17x1_has_dsp(adau)) { + ret = snd_soc_dapm_add_routes(&codec->dapm, + adau17x1_dsp_dapm_routes, + ARRAY_SIZE(adau17x1_dsp_dapm_routes)); + } else { + ret = snd_soc_dapm_add_routes(&codec->dapm, + adau17x1_no_dsp_dapm_routes, + ARRAY_SIZE(adau17x1_no_dsp_dapm_routes)); + } + return ret; +} + +#if IS_ENABLED(CONFIG_SPI_MASTER) +static void adau17x1_spi_mode(struct device *dev) +{ + /* To get the device into SPI mode CLATCH has to be pulled low three + * times. Do this by issuing three dummy reads. */ + spi_w8r8(to_spi_device(dev), 0x00); + spi_w8r8(to_spi_device(dev), 0x00); + spi_w8r8(to_spi_device(dev), 0x00); +} +#else +static inline void adau17x1_spi_mode(struct device *dev) {} +#endif + +int adau17x1_suspend(struct snd_soc_codec *codec) +{ + codec->driver->set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_suspend); + +int adau17x1_resume(struct snd_soc_codec *codec) +{ + struct adau *adau = snd_soc_codec_get_drvdata(codec); + + if (adau->control_type == SND_SOC_SPI) + adau17x1_spi_mode(codec->dev); + + codec->driver->set_bias_level(codec, SND_SOC_BIAS_STANDBY); + regcache_sync(adau->regmap); + + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_resume); + +int adau17x1_bus_probe(struct device *dev, struct regmap *regmap, + enum adau17x1_type type, enum snd_soc_control_type control_type) +{ + struct adau *adau; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + adau = devm_kzalloc(dev, sizeof(*adau), GFP_KERNEL); + if (!adau) + return -ENOMEM; + + adau->regmap = regmap; + adau->control_type = control_type; + adau->type = type; + adau->sysclk_div = 1; + + dev_set_drvdata(dev, adau); + + if (control_type == SND_SOC_SPI) + adau17x1_spi_mode(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_bus_probe); + +MODULE_DESCRIPTION("ASoC ADAU1X61/ADAU1X81 common code"); +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau17x1.h b/sound/soc/codecs/adau17x1.h new file mode 100644 index 0000000..a034800 --- /dev/null +++ b/sound/soc/codecs/adau17x1.h @@ -0,0 +1,149 @@ +#ifndef __ADAU17X1_H__ +#define __ADAU17X1_H__ + +#include <linux/regmap.h> +#include <linux/platform_data/adau17x1.h> + +enum adau17x1_type { + ADAU1361, + ADAU1761, + ADAU1381, + ADAU1781, +}; + +enum adau17x1_pll { + ADAU17X1_PLL, +}; + +enum adau17x1_pll_src { + ADAU17X1_PLL_SRC_MCLK, +}; + +enum adau17x1_clk_src { + ADAU17X1_CLK_SRC_MCLK, + ADAU17X1_CLK_SRC_PLL, +}; + +struct adau { + unsigned int sysclk; + unsigned int sysclk_div; + unsigned int pll_freq; + + enum adau17x1_clk_src clk_src; + enum adau17x1_type type; + enum snd_soc_control_type control_type; + + unsigned int dai_fmt; + + uint8_t pll_regs[6]; + + bool master; + + unsigned int tdm_dac_slot; + unsigned int tdm_adc_slot; + + bool dsp_playback_bypass; + bool dsp_capture_bypass; + + struct regmap *regmap; +}; + +int adau17x1_probe(struct snd_soc_codec *codec); +int adau17x1_add_routes(struct snd_soc_codec *codec); +int adau17x1_bus_probe(struct device *dev, + struct regmap *regmap, enum adau17x1_type type, + enum snd_soc_control_type control_type); +int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec, + enum adau17x1_micbias_voltage micbias); +bool adau17x1_readable_register(struct device *dev, unsigned int reg); +bool adau17x1_volatile_register(struct device *dev, unsigned int reg); +int adau17x1_suspend(struct snd_soc_codec *codec); +int adau17x1_resume(struct snd_soc_codec *codec); + +extern const struct snd_soc_dai_ops adau17x1_dai_ops; + +int adau17x1_load_firmware(struct adau *adau, struct device *dev, + const char *firmware); +bool adau17x1_has_dsp(struct adau *adau); +int adau17x1_dsp_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int adau17x1_dsp_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +#define ADAU17X1_DSP_MUX_ENUM(xname, xenum) \ + SOC_DAPM_ENUM_EXT(xname, xenum, \ + adau17x1_dsp_mux_enum_get, adau17x1_dsp_mux_enum_put) + +#define DECLARE_ADAU17X1_AIFOUT_MUX(name, text) \ +static const char * const adau17x1_ ## name ## _aifout_text[] = { \ + text, \ + "DSP", \ +}; \ +static const SOC_ENUM_SINGLE_DECL(adau17x1_ ## name ## _aifout_mux_enum, \ + ADAU17X1_SERIAL_OUTPUT_ROUTE, 0, adau17x1_ ## name ## _aifout_text); \ +static const struct snd_kcontrol_new adau17x1_ ## name ## _aifout_mux = \ + ADAU17X1_DSP_MUX_ENUM("AIFOUT Capture Mux", \ + adau17x1_ ## name ## _aifout_mux_enum); \ +static const struct snd_soc_dapm_widget adau17x1_ ## name ## _dsp_widgets[] = { \ + SND_SOC_DAPM_VIRT_MUX("AIFOUT Capture Mux", SND_SOC_NOPM, 0, 0, \ + &adau17x1_ ## name ## _aifout_mux), \ +} + +#define ADAU17X1_CLOCK_CONTROL 0x4000 +#define ADAU17X1_PLL_CONTROL 0x4002 +#define ADAU17X1_REC_POWER_MGMT 0x4009 +#define ADAU17X1_MICBIAS 0x4010 +#define ADAU17X1_SERIAL_PORT0 0x4015 +#define ADAU17X1_SERIAL_PORT1 0x4016 +#define ADAU17X1_CONVERTER0 0x4017 +#define ADAU17X1_CONVERTER1 0x4018 +#define ADAU17X1_LEFT_INPUT_DIGITAL_VOL 0x401a +#define ADAU17X1_RIGHT_INPUT_DIGITAL_VOL 0x401b +#define ADAU17X1_ADC_CONTROL 0x4019 +#define ADAU17X1_PLAY_POWER_MGMT 0x4029 +#define ADAU17X1_DAC_CONTROL0 0x402a +#define ADAU17X1_DAC_CONTROL1 0x402b +#define ADAU17X1_DAC_CONTROL2 0x402c +#define ADAU17X1_SERIAL_PORT_PAD 0x402d +#define ADAU17X1_CONTROL_PORT_PAD0 0x402f +#define ADAU17X1_CONTROL_PORT_PAD1 0x4030 +#define ADAU17X1_DSP_SAMPLING_RATE 0x40eb +#define ADAU17X1_SERIAL_INPUT_ROUTE 0x40f2 +#define ADAU17X1_SERIAL_OUTPUT_ROUTE 0x40f3 +#define ADAU17X1_DSP_ENABLE 0x40f5 +#define ADAU17X1_DSP_RUN 0x40f6 +#define ADAU17X1_SERIAL_SAMPLING_RATE 0x40f8 + +#define ADAU17X1_SERIAL_PORT0_BCLK_POL BIT(4) +#define ADAU17X1_SERIAL_PORT0_LRCLK_POL BIT(3) +#define ADAU17X1_SERIAL_PORT0_MASTER BIT(0) + +#define ADAU17X1_SERIAL_PORT1_DELAY1 0x00 +#define ADAU17X1_SERIAL_PORT1_DELAY0 0x01 +#define ADAU17X1_SERIAL_PORT1_DELAY8 0x02 +#define ADAU17X1_SERIAL_PORT1_DELAY16 0x03 +#define ADAU17X1_SERIAL_PORT1_DELAY_MASK 0x03 + +#define ADAU17X1_CLOCK_CONTROL_INFREQ_MASK 0x6 +#define ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL BIT(3) +#define ADAU17X1_CLOCK_CONTROL_SYSCLK_EN BIT(0) + +#define ADUA_SERIAL_PORT1_BCLK32 (0x0 << 5) +#define ADUA_SERIAL_PORT1_BCLK48 (0x1 << 5) +#define ADUA_SERIAL_PORT1_BCLK64 (0x2 << 5) +#define ADUA_SERIAL_PORT1_BCLK128 (0x3 << 5) +#define ADUA_SERIAL_PORT1_BCLK256 (0x4 << 5) +#define ADAU17X1_SERIAL_PORT1_BCLK_MASK (0x7 << 5) + +#define ADUA_SERIAL_PORT0_STEREO (0x0 << 1) +#define ADUA_SERIAL_PORT0_TDM4 (0x1 << 1) +#define ADUA_SERIAL_PORT0_TDM8 (0x2 << 1) +#define ADAU17X1_SERIAL_PORT0_TDM_MASK (0x3 << 1) +#define ADAU17X1_SERIAL_PORT0_PULSE_MODE BIT(5) + +#define ADAU17X1_CONVERTER0_DAC_PAIR(x) (((x) - 1) << 5) +#define ADAU17X1_CONVERTER0_DAC_PAIR_MASK (0x3 << 5) +#define ADAU17X1_CONVERTER1_ADC_PAIR(x) ((x) - 1) +#define ADAU17X1_CONVERTER1_ADC_PAIR_MASK 0x3 + +#endif
This patch adds support for the Analog Devices ADAU1361 and ADAU1761 CODECs.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- include/linux/platform_data/adau17x1.h | 67 +++ sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/adau1761.c | 1000 ++++++++++++++++++++++++++++++++ 4 files changed, 1074 insertions(+) create mode 100644 sound/soc/codecs/adau1761.c
diff --git a/include/linux/platform_data/adau17x1.h b/include/linux/platform_data/adau17x1.h index fdf419a..5f559db 100644 --- a/include/linux/platform_data/adau17x1.h +++ b/include/linux/platform_data/adau17x1.h @@ -20,4 +20,71 @@ enum adau17x1_micbias_voltage { ADAU17X1_MICBIAS_0_65_AVDD = 1, };
+/** + * enum adau1761_digmic_jackdet_pin_mode - Configuration of the JACKDET/MICIN pin + * @ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE: Disable the pin + * @ADAU1761_DIGMIC_JACKDET_PIN_MODE_DIGMIC: Configure the pin for usage as + * digital microphone input. + * @ADAU1761_DIGMIC_JACKDET_PIN_MODE_JACKDETECT: Configure the pin for jack + * insertion detection. +*/ +enum adau1761_digmic_jackdet_pin_mode { + ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE, + ADAU1761_DIGMIC_JACKDET_PIN_MODE_DIGMIC, + ADAU1761_DIGMIC_JACKDET_PIN_MODE_JACKDETECT, +}; + +/** + * adau1761_jackdetect_debounce_time - Jack insertion detection debounce time + * @ADAU1761_JACKDETECT_DEBOUNCE_5MS: 5 milliseconds + * @ADAU1761_JACKDETECT_DEBOUNCE_10MS: 10 milliseconds + * @ADAU1761_JACKDETECT_DEBOUNCE_20MS: 20 milliseconds + * @ADAU1761_JACKDETECT_DEBOUNCE_40MS: 40 milliseconds + */ +enum adau1761_jackdetect_debounce_time { + ADAU1761_JACKDETECT_DEBOUNCE_5MS = 0, + ADAU1761_JACKDETECT_DEBOUNCE_10MS = 1, + ADAU1761_JACKDETECT_DEBOUNCE_20MS = 2, + ADAU1761_JACKDETECT_DEBOUNCE_40MS = 3, +}; + +/** + * enum adau1761_output_mode - Output mode configuration + * @ADAU1761_OUTPUT_MODE_HEADPHONE: Headphone output + * @ADAU1761_OUTPUT_MODE_HEADPHONE_CAPLESS: Capless headphone output + * @ADAU1761_OUTPUT_MODE_LINE: Line output + */ +enum adau1761_output_mode { + ADAU1761_OUTPUT_MODE_HEADPHONE, + ADAU1761_OUTPUT_MODE_HEADPHONE_CAPLESS, + ADAU1761_OUTPUT_MODE_LINE, +}; + +/** + * struct adau1761_platform_data - ADAU1761 Codec driver platform data + * @input_differential: If true the input pins will be configured in + * differential mode. + * @lineout_mode: Output mode for the LOUT/ROUT pins + * @headphone_mode: Output mode for the LHP/RHP pins + * @digmic_jackdetect_pin_mode: JACKDET/MICIN pin configuration + * @jackdetect_debounce_time: Jack insertion detection debounce time. + * Note: This value will only be used, if the JACKDET/MICIN pin is + * configured for jack insertion detection. + * @jackdetect_active_low: If true the jack insertion detection is active + * low. Othwise it will be active high. + * @micbias_voltage: Microphone voltage bias + */ +struct adau1761_platform_data { + bool input_differential; + enum adau1761_output_mode lineout_mode; + enum adau1761_output_mode headphone_mode; + + enum adau1761_digmic_jackdet_pin_mode digmic_jackdetect_pin_mode; + + enum adau1761_jackdetect_debounce_time jackdetect_debounce_time; + bool jackdetect_active_low; + + enum adau17x1_micbias_voltage micbias_voltage; +}; + #endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index d7dbcb3..dd11f00 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -19,6 +19,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_AD73311 select SND_SOC_ADAU1373 if I2C + select SND_SOC_ADAU1761 if SND_SOC_I2C_AND_SPI select SND_SOC_ADAV80X select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER @@ -185,6 +186,10 @@ config SND_SOC_ADAU17X1 select SND_SOC_SIGMADSP tristate
+config SND_SOC_ADAU1761 + select SND_SOC_ADAU17X1 + tristate + config SND_SOC_ADAV80X tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index d67528f..60e30f8 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -8,6 +8,7 @@ snd-soc-ad73311-objs := ad73311.o snd-soc-adau1701-objs := adau1701.o snd-soc-adau1373-objs := adau1373.o snd-soc-adau17x1-objs := adau17x1.o +snd-soc-adau1761-objs := adau1761.o snd-soc-adav80x-objs := adav80x.o snd-soc-ads117x-objs := ads117x.o snd-soc-ak4104-objs := ak4104.o @@ -131,6 +132,7 @@ obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o +obj-$(CONFIG_SND_SOC_ADAU1761) += snd-soc-adau1761.o obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o diff --git a/sound/soc/codecs/adau1761.c b/sound/soc/codecs/adau1761.c new file mode 100644 index 0000000..47e53da --- /dev/null +++ b/sound/soc/codecs/adau1761.c @@ -0,0 +1,1000 @@ +/* + * Driver for ADAU1761/ADAU1461/ADAU1761/ADAU1961 codec + * + * Copyright 2011-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen lars@metafoo.de + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <linux/platform_data/adau17x1.h> + +#include "adau17x1.h" + +#define ADAU1761_DIGMIC_JACKDETECT 0x4008 +#define ADAU1761_REC_MIXER_LEFT0 0x400a +#define ADAU1761_REC_MIXER_LEFT1 0x400b +#define ADAU1761_REC_MIXER_RIGHT0 0x400c +#define ADAU1761_REC_MIXER_RIGHT1 0x400d +#define ADAU1761_LEFT_DIFF_INPUT_VOL 0x400e +#define ADAU1761_RIGHT_DIFF_INPUT_VOL 0x400f +#define ADAU1761_PLAY_LR_MIXER_LEFT 0x4020 +#define ADAU1761_PLAY_MIXER_LEFT0 0x401c +#define ADAU1761_PLAY_MIXER_LEFT1 0x401d +#define ADAU1761_PLAY_MIXER_RIGHT0 0x401e +#define ADAU1761_PLAY_MIXER_RIGHT1 0x401f +#define ADAU1761_PLAY_LR_MIXER_RIGHT 0x4021 +#define ADAU1761_PLAY_MIXER_MONO 0x4022 +#define ADAU1761_PLAY_HP_LEFT_VOL 0x4023 +#define ADAU1761_PLAY_HP_RIGHT_VOL 0x4024 +#define ADAU1761_PLAY_LINE_LEFT_VOL 0x4025 +#define ADAU1761_PLAY_LINE_RIGHT_VOL 0x4026 +#define ADAU1761_PLAY_MONO_OUTPUT_VOL 0x4027 +#define ADAU1761_POP_CLICK_SUPPRESS 0x4028 +#define ADAU1761_JACK_DETECT_PIN 0x4031 +#define ADAU1761_DEJITTER 0x4036 +#define ADAU1761_CLK_ENABLE0 0x40f9 +#define ADAU1761_CLK_ENABLE1 0x40fa + +#define ADAU1761_DIGMIC_JACKDETECT_ACTIVE_LOW BIT(0) +#define ADAU1761_DIGMIC_JACKDETECT_DIGMIC BIT(5) + +#define ADAU1761_DIFF_INPUT_VOL_LDEN 0x01 + +#define ADAU1761_FIRMWARE "adau1761.bin" + +static const struct reg_default adau1761_reg_defaults[] = { + { ADAU1761_DEJITTER, 0x03 }, + { ADAU1761_DIGMIC_JACKDETECT, 0x00 }, + { ADAU1761_REC_MIXER_LEFT0, 0x00 }, + { ADAU1761_REC_MIXER_LEFT1, 0x00 }, + { ADAU1761_REC_MIXER_RIGHT0, 0x00 }, + { ADAU1761_REC_MIXER_RIGHT1, 0x00 }, + { ADAU1761_LEFT_DIFF_INPUT_VOL, 0x00 }, + { ADAU1761_RIGHT_DIFF_INPUT_VOL, 0x00 }, + { ADAU1761_PLAY_LR_MIXER_LEFT, 0x00 }, + { ADAU1761_PLAY_MIXER_LEFT0, 0x00 }, + { ADAU1761_PLAY_MIXER_LEFT1, 0x00 }, + { ADAU1761_PLAY_MIXER_RIGHT0, 0x00 }, + { ADAU1761_PLAY_MIXER_RIGHT1, 0x00 }, + { ADAU1761_PLAY_LR_MIXER_RIGHT, 0x00 }, + { ADAU1761_PLAY_MIXER_MONO, 0x00 }, + { ADAU1761_PLAY_HP_LEFT_VOL, 0x00 }, + { ADAU1761_PLAY_HP_RIGHT_VOL, 0x00 }, + { ADAU1761_PLAY_LINE_LEFT_VOL, 0x00 }, + { ADAU1761_PLAY_LINE_RIGHT_VOL, 0x00 }, + { ADAU1761_PLAY_MONO_OUTPUT_VOL, 0x00 }, + { ADAU1761_POP_CLICK_SUPPRESS, 0x00 }, + { ADAU1761_JACK_DETECT_PIN, 0x00 }, + { ADAU1761_CLK_ENABLE0, 0x00 }, + { ADAU1761_CLK_ENABLE1, 0x00 }, + { ADAU17X1_CLOCK_CONTROL, 0x00 }, + { ADAU17X1_PLL_CONTROL, 0x00 }, + { ADAU17X1_REC_POWER_MGMT, 0x00 }, + { ADAU17X1_MICBIAS, 0x00 }, + { ADAU17X1_SERIAL_PORT0, 0x00 }, + { ADAU17X1_SERIAL_PORT1, 0x00 }, + { ADAU17X1_CONVERTER0, 0x00 }, + { ADAU17X1_CONVERTER1, 0x00 }, + { ADAU17X1_LEFT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU17X1_ADC_CONTROL, 0x00 }, + { ADAU17X1_PLAY_POWER_MGMT, 0x00 }, + { ADAU17X1_DAC_CONTROL0, 0x00 }, + { ADAU17X1_DAC_CONTROL1, 0x00 }, + { ADAU17X1_DAC_CONTROL2, 0x00 }, + { ADAU17X1_SERIAL_PORT_PAD, 0x00 }, + { ADAU17X1_CONTROL_PORT_PAD0, 0x00 }, + { ADAU17X1_CONTROL_PORT_PAD1, 0x00 }, + { ADAU17X1_DSP_SAMPLING_RATE, 0x01 }, + { ADAU17X1_SERIAL_INPUT_ROUTE, 0x00 }, + { ADAU17X1_SERIAL_OUTPUT_ROUTE, 0x00 }, + { ADAU17X1_DSP_ENABLE, 0x00 }, + { ADAU17X1_DSP_RUN, 0x00 }, + { ADAU17X1_SERIAL_SAMPLING_RATE, 0x00 }, +}; + +static const DECLARE_TLV_DB_SCALE(adau1761_sing_in_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(adau1761_diff_in_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(adau1761_out_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(adau1761_sidetone_tlv, -1800, 300, 1); +static const DECLARE_TLV_DB_SCALE(adau1761_boost_tlv, -600, 600, 1); +static const DECLARE_TLV_DB_SCALE(adau1761_pga_boost_tlv, -2000, 2000, 1); + +static const unsigned int adau1761_bias_select_values[] = { + 0, 2, 3, +}; + +static const char * const adau1761_bias_select_text[] = { + "Normal operation", "Enhanced performance", "Power saving", +}; + +static const char * const adau1761_bias_select_extreme_text[] = { + "Normal operation", "Extreme power saving", "Enhanced performance", + "Power saving", +}; + +static const SOC_ENUM_SINGLE_DECL(adau1761_adc_bias_enum, + ADAU17X1_REC_POWER_MGMT, 3, adau1761_bias_select_extreme_text); +static const SOC_ENUM_SINGLE_DECL(adau1761_hp_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 6, adau1761_bias_select_extreme_text); +static const SOC_ENUM_SINGLE_DECL(adau1761_dac_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 4, adau1761_bias_select_extreme_text); +static const SOC_VALUE_ENUM_SINGLE_DECL(adau1761_playback_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 2, 0x3, adau1761_bias_select_text, + adau1761_bias_select_values); +static const SOC_VALUE_ENUM_SINGLE_DECL(adau1761_capture_bias_enum, + ADAU17X1_REC_POWER_MGMT, 1, 0x3, adau1761_bias_select_text, + adau1761_bias_select_values); + +static const struct snd_kcontrol_new adau1761_jack_detect_controls[] = { + SOC_SINGLE("Jack Detect Switch", 4, 1, 0, ADAU1761_DIGMIC_JACKDETECT), +}; + +static const struct snd_kcontrol_new adau1761_differential_mode_controls[] = { + SOC_DOUBLE_R_TLV("Capture Volume", ADAU1761_LEFT_DIFF_INPUT_VOL, + ADAU1761_RIGHT_DIFF_INPUT_VOL, 2, 0x3f, 0, + adau1761_diff_in_tlv), + SOC_DOUBLE_R("Capture Switch", ADAU1761_LEFT_DIFF_INPUT_VOL, + ADAU1761_RIGHT_DIFF_INPUT_VOL, 1, 1, 0), + + SOC_DOUBLE_R_TLV("PGA Boost Capture Volume", ADAU1761_REC_MIXER_LEFT1, + ADAU1761_REC_MIXER_RIGHT1, 3, 2, 0, adau1761_pga_boost_tlv), +}; + +static const struct snd_kcontrol_new adau1761_single_mode_controls[] = { + SOC_SINGLE_TLV("Input 1 Capture Volume", ADAU1761_REC_MIXER_LEFT0, + 4, 7, 0, adau1761_sing_in_tlv), + SOC_SINGLE_TLV("Input 2 Capture Volume", ADAU1761_REC_MIXER_LEFT0, + 1, 7, 0, adau1761_sing_in_tlv), + SOC_SINGLE_TLV("Input 3 Capture Volume", ADAU1761_REC_MIXER_RIGHT0, + 4, 7, 0, adau1761_sing_in_tlv), + SOC_SINGLE_TLV("Input 4 Capture Volume", ADAU1761_REC_MIXER_RIGHT0, + 1, 7, 0, adau1761_sing_in_tlv), +}; + +static const struct snd_kcontrol_new adau1761_controls[] = { + SOC_DOUBLE_R_TLV("Aux Capture Volume", ADAU1761_REC_MIXER_LEFT1, + ADAU1761_REC_MIXER_RIGHT1, 0, 7, 0, adau1761_sing_in_tlv), + + SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1761_PLAY_HP_LEFT_VOL, + ADAU1761_PLAY_HP_RIGHT_VOL, 2, 0x3f, 0, adau1761_out_tlv), + SOC_DOUBLE_R("Headphone Playback Switch", ADAU1761_PLAY_HP_LEFT_VOL, + ADAU1761_PLAY_HP_RIGHT_VOL, 1, 1, 0), + SOC_DOUBLE_R_TLV("Lineout Playback Volume", ADAU1761_PLAY_LINE_LEFT_VOL, + ADAU1761_PLAY_LINE_RIGHT_VOL, 2, 0x3f, 0, adau1761_out_tlv), + SOC_DOUBLE_R("Lineout Playback Switch", ADAU1761_PLAY_LINE_LEFT_VOL, + ADAU1761_PLAY_LINE_RIGHT_VOL, 1, 1, 0), + + SOC_ENUM("ADC Bias", adau1761_adc_bias_enum), + SOC_ENUM("DAC Bias", adau1761_dac_bias_enum), + SOC_VALUE_ENUM("Capture Bias", adau1761_capture_bias_enum), + SOC_VALUE_ENUM("Playback Bias", adau1761_playback_bias_enum), + SOC_ENUM("Headphone Bias", adau1761_hp_bias_enum), +}; + +static const struct snd_kcontrol_new adau1761_mono_controls[] = { + SOC_SINGLE_TLV("Mono Playback Volume", ADAU1761_PLAY_MONO_OUTPUT_VOL, + 2, 0x3f, 0, adau1761_out_tlv), + SOC_SINGLE("Mono Playback Switch", ADAU1761_PLAY_MONO_OUTPUT_VOL, + 1, 1, 0), +}; + +static const struct snd_kcontrol_new adau1761_left_mixer_controls[] = { + SOC_DAPM_SINGLE_VIRT("Left DAC Switch", 0, 1), + SOC_DAPM_SINGLE_VIRT("Right DAC Switch", 1, 1), + SOC_DAPM_SINGLE_TLV("Aux Bypass Volume", + ADAU1761_PLAY_MIXER_LEFT0, 1, 8, 0, adau1761_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Right Bypass Volume", + ADAU1761_PLAY_MIXER_LEFT1, 4, 8, 0, adau1761_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Left Bypass Volume", + ADAU1761_PLAY_MIXER_LEFT1, 0, 8, 0, adau1761_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1761_right_mixer_controls[] = { + SOC_DAPM_SINGLE_VIRT("Left DAC Switch", 0, 1), + SOC_DAPM_SINGLE_VIRT("Right DAC Switch", 1, 1), + SOC_DAPM_SINGLE_TLV("Aux Bypass Volume", + ADAU1761_PLAY_MIXER_RIGHT0, 1, 8, 0, adau1761_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Right Bypass Volume", + ADAU1761_PLAY_MIXER_RIGHT1, 4, 8, 0, adau1761_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Left Bypass Volume", + ADAU1761_PLAY_MIXER_RIGHT1, 0, 8, 0, adau1761_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1761_left_lr_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Left Volume", + ADAU1761_PLAY_LR_MIXER_LEFT, 1, 2, 0, adau1761_boost_tlv), + SOC_DAPM_SINGLE_TLV("Right Volume", + ADAU1761_PLAY_LR_MIXER_LEFT, 3, 2, 0, adau1761_boost_tlv), +}; + +static const struct snd_kcontrol_new adau1761_right_lr_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Left Volume", + ADAU1761_PLAY_LR_MIXER_RIGHT, 1, 2, 0, adau1761_boost_tlv), + SOC_DAPM_SINGLE_TLV("Right Volume", + ADAU1761_PLAY_LR_MIXER_RIGHT, 3, 2, 0, adau1761_boost_tlv), +}; + +static const char * const adau1761_input_mux_text[] = { + "ADC", "DMIC", +}; + +static const SOC_ENUM_SINGLE_DECL(adau1761_input_mux_enum, + ADAU17X1_ADC_CONTROL, 2, adau1761_input_mux_text); + +static const struct snd_kcontrol_new adau1761_input_mux_control = + SOC_DAPM_ENUM("Input Select", adau1761_input_mux_enum); + +static int adau1761_dejitter_fixup(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct adau *adau = snd_soc_codec_get_drvdata(w->codec); + + /* After any power changes have been made the dejitter circuit + * has to be reinitialized. */ + regmap_write(adau->regmap, ADAU1761_DEJITTER, 0); + if (!adau->master) + regmap_write(adau->regmap, ADAU1761_DEJITTER, 3); + + return 0; +} + +static const struct snd_soc_dapm_widget adau1x61_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Input Mixer", ADAU1761_REC_MIXER_LEFT0, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("Right Input Mixer", ADAU1761_REC_MIXER_RIGHT0, 0, 0, + NULL, 0), + + /* To avoid clicks and pops the DAC outputs need to be muted when DACs + * are disabled. This is why we insert these extra widgets here. The + * virtual DAC switches of the playback mixers control whether they get + * enabled when the DACs are active. */ + SND_SOC_DAPM_MIXER("Left Playback Mixer Left DAC Mute", + ADAU1761_PLAY_MIXER_LEFT0, 5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Left Playback Mixer Right DAC Mute", + ADAU1761_PLAY_MIXER_LEFT0, 6, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Right Playback Mixer Left DAC Mute", + ADAU1761_PLAY_MIXER_RIGHT0, 5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Right Playback Mixer Right DAC Mute", + ADAU1761_PLAY_MIXER_RIGHT0, 6, 0, NULL, 0), + + SOC_MIXER_ARRAY("Left Playback Mixer", ADAU1761_PLAY_MIXER_LEFT0, + 0, 0, adau1761_left_mixer_controls), + SOC_MIXER_ARRAY("Right Playback Mixer", ADAU1761_PLAY_MIXER_RIGHT0, + 0, 0, adau1761_right_mixer_controls), + SOC_MIXER_ARRAY("Left LR Playback Mixer", ADAU1761_PLAY_LR_MIXER_LEFT, + 0, 0, adau1761_left_lr_mixer_controls), + SOC_MIXER_ARRAY("Right LR Playback Mixer", ADAU1761_PLAY_LR_MIXER_RIGHT, + 0, 0, adau1761_right_lr_mixer_controls), + + SND_SOC_DAPM_SUPPLY("Headphone", ADAU1761_PLAY_HP_LEFT_VOL, + 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("SYSCLK", 2, SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_POST("Dejitter fixup", adau1761_dejitter_fixup), + + SND_SOC_DAPM_INPUT("LAUX"), + SND_SOC_DAPM_INPUT("RAUX"), + SND_SOC_DAPM_INPUT("LINP"), + SND_SOC_DAPM_INPUT("LINN"), + SND_SOC_DAPM_INPUT("RINP"), + SND_SOC_DAPM_INPUT("RINN"), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("LHP"), + SND_SOC_DAPM_OUTPUT("RHP"), +}; + +static const struct snd_soc_dapm_widget adau1761_mono_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Mono Playback Mixer", ADAU1761_PLAY_MIXER_MONO, + 0, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("MONOOUT"), +}; + +static const struct snd_soc_dapm_widget adau1761_capless_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("Headphone VGND", 1, ADAU1761_PLAY_MIXER_MONO, + 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route adau1x61_dapm_routes[] = { + { "Left Input Mixer", NULL, "LINP" }, + { "Left Input Mixer", NULL, "LINN" }, + { "Left Input Mixer", NULL, "LAUX" }, + + { "Right Input Mixer", NULL, "RINP" }, + { "Right Input Mixer", NULL, "RINN" }, + { "Right Input Mixer", NULL, "RAUX" }, + + { "Left Playback Mixer", NULL, "Left Playback Enable"}, + { "Right Playback Mixer", NULL, "Right Playback Enable"}, + { "Left LR Playback Mixer", NULL, "Left Playback Enable"}, + { "Right LR Playback Mixer", NULL, "Right Playback Enable"}, + + { "Left Playback Mixer Left DAC Mute", NULL, "Left DAC" }, + { "Left Playback Mixer Right DAC Mute", NULL, "Right DAC" }, + { "Right Playback Mixer Left DAC Mute", NULL, "Left DAC" }, + { "Right Playback Mixer Right DAC Mute", NULL, "Right DAC" }, + + { "Left Playback Mixer", "Left DAC Switch", + "Left Playback Mixer Left DAC Mute" }, + { "Left Playback Mixer", "Right DAC Switch", + "Left Playback Mixer Right DAC Mute" }, + + { "Right Playback Mixer", "Left DAC Switch", + "Right Playback Mixer Left DAC Mute" }, + { "Right Playback Mixer", "Right DAC Switch", + "Right Playback Mixer Right DAC Mute" }, + + { "Left LR Playback Mixer", "Left Volume", "Left Playback Mixer" }, + { "Left LR Playback Mixer", "Right Volume", "Right Playback Mixer" }, + + { "Right LR Playback Mixer", "Left Volume", "Left Playback Mixer" }, + { "Right LR Playback Mixer", "Right Volume", "Right Playback Mixer" }, + + { "Left ADC", NULL, "Left Input Mixer" }, + { "Right ADC", NULL, "Right Input Mixer" }, + + { "LHP", NULL, "Left Playback Mixer" }, + { "RHP", NULL, "Right Playback Mixer" }, + + { "LHP", NULL, "Headphone" }, + { "RHP", NULL, "Headphone" }, + + { "LOUT", NULL, "Left LR Playback Mixer" }, + { "ROUT", NULL, "Right LR Playback Mixer" }, + + { "Left Playback Mixer", "Aux Bypass Volume", "LAUX" }, + { "Left Playback Mixer", "Left Bypass Volume", "Left Input Mixer" }, + { "Left Playback Mixer", "Right Bypass Volume", "Right Input Mixer" }, + { "Right Playback Mixer", "Aux Bypass Volume", "RAUX" }, + { "Right Playback Mixer", "Left Bypass Volume", "Left Input Mixer" }, + { "Right Playback Mixer", "Right Bypass Volume", "Right Input Mixer" }, +}; + +static const struct snd_soc_dapm_route adau1761_mono_dapm_routes[] = { + { "Mono Playback Mixer", NULL, "Left Playback Mixer" }, + { "Mono Playback Mixer", NULL, "Right Playback Mixer" }, + + { "MONOOUT", NULL, "Mono Playback Mixer" }, +}; + +static const struct snd_soc_dapm_route adau1761_capless_dapm_routes[] = { + { "Headphone", NULL, "Headphone VGND" }, +}; + +static const struct snd_soc_dapm_widget adau1761_dmic_widgets[] = { + SND_SOC_DAPM_MUX("Input Select", SND_SOC_NOPM, 0, 0, + &adau1761_input_mux_control), + + SND_SOC_DAPM_INPUT("DMIC"), +}; + +static const struct snd_soc_dapm_route adau1761_dmic_routes[] = { + { "Input Select", "ADC", "Left ADC" }, + { "Input Select", "ADC", "Right ADC" }, + { "Input Select", "DMIC", "DMIC" }, + + { "DMIC", NULL, "Left Dec Filter Enable" }, + { "DMIC", NULL, "Right Dec Filter Enable" }, +}; + +/* + * We have to handle 4 different cases, which all require different routes and + * widgets: + * - DSP, DMIC + * - DSP, no DMIC + * - no DSP, DMIC + * - no DSP, no DMIC + */ + +DECLARE_ADAU17X1_AIFOUT_MUX(dmic, "ADC/DMIC"); +DECLARE_ADAU17X1_AIFOUT_MUX(no_dmic, "ADC"); + +static const struct snd_soc_dapm_route adau1761_dmic_no_dsp_routes[] = { + { "AIFOUT", NULL, "Input Select" }, +}; + +static const struct snd_soc_dapm_route adau1761_dmic_dsp_routes[] = { + { "DSP", NULL, "Input Select" }, + { "AIFOUT Capture Mux", "ADC/DMIC", "Input Select" }, +}; + +static const struct snd_soc_dapm_route adau1761_no_dmic_no_dsp_routes[] = { + { "AIFOUT", NULL, "Left ADC" }, + { "AIFOUT", NULL, "Right ADC" }, +}; + +static const struct snd_soc_dapm_route adau1761_no_dmic_dsp_routes[] = { + { "DSP", NULL, "Left ADC" }, + { "DSP", NULL, "Right ADC" }, + { "AIFOUT Capture Mux", "ADC", "Left ADC" }, + { "AIFOUT Capture Mux", "ADC", "Right ADC" }, +}; + +static const struct snd_soc_dapm_widget adau1761_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("Serial Port Clock", ADAU1761_CLK_ENABLE0, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Serial Input Routing Clock", ADAU1761_CLK_ENABLE0, + 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Serial Output Routing Clock", ADAU1761_CLK_ENABLE0, + 3, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Decimator Resync Clock", ADAU1761_CLK_ENABLE0, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Interpolator Resync Clock", ADAU1761_CLK_ENABLE0, + 2, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Slew Clock", ADAU1761_CLK_ENABLE0, 6, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("Digital Clock 0", 1, ADAU1761_CLK_ENABLE1, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("Digital Clock 1", 1, ADAU1761_CLK_ENABLE1, + 1, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route adau1761_dapm_routes[] = { + { "Left ADC", NULL, "Digital Clock 0", }, + { "Right ADC", NULL, "Digital Clock 0", }, + { "Left DAC", NULL, "Digital Clock 0", }, + { "Right DAC", NULL, "Digital Clock 0", }, + + { "AIFCLK", NULL, "Digital Clock 1" }, + + { "AIFIN", NULL, "Serial Port Clock" }, + { "AIFOUT", NULL, "Serial Port Clock" }, + { "AIFIN", NULL, "Serial Input Routing Clock" }, + { "AIFOUT", NULL, "Serial Output Routing Clock" }, + + { "AIFIN", NULL, "Decimator Resync Clock" }, + { "AIFOUT", NULL, "Interpolator Resync Clock" }, + + { "DSP", NULL, "Decimator Resync Clock" }, + { "DSP", NULL, "Interpolator Resync Clock" }, + { "DSP", NULL, "Digital Clock 0" }, + + { "Slew Clock", NULL, "Digital Clock 0" }, + { "Right Playback Mixer", NULL, "Slew Clock" }, + { "Left Playback Mixer", NULL, "Slew Clock" }, + + { "Digital Clock 0", NULL, "SYSCLK" }, + { "Digital Clock 1", NULL, "SYSCLK" }, + + { "AIFOUT", NULL, "Decimator Resync Clock" }, +}; + +static int adau1761_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct adau *adau = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, 0); + break; + + } + codec->dapm.bias_level = level; + return 0; +} + +static enum adau1761_output_mode adau1761_get_lineout_mode( + struct snd_soc_codec *codec) +{ + struct adau1761_platform_data *pdata = codec->dev->platform_data; + + if (pdata) + return pdata->lineout_mode; + + return ADAU1761_OUTPUT_MODE_LINE; +} + +static int adau1761_setup_digmic_jackdetect(struct snd_soc_codec *codec) +{ + struct adau1761_platform_data *pdata = codec->dev->platform_data; + struct adau *adau = snd_soc_codec_get_drvdata(codec); + enum adau1761_digmic_jackdet_pin_mode mode; + const struct snd_soc_dapm_widget *widgets = NULL; + const struct snd_soc_dapm_route *routes = NULL; + unsigned int num_widgets = 0; + unsigned int num_routes = 0; + unsigned int val = 0; + int ret; + + if (pdata) + mode = pdata->digmic_jackdetect_pin_mode; + else + mode = ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE; + mode = ADAU1761_DIGMIC_JACKDET_PIN_MODE_DIGMIC; + + switch (mode) { + case ADAU1761_DIGMIC_JACKDET_PIN_MODE_JACKDETECT: + switch (pdata->jackdetect_debounce_time) { + case ADAU1761_JACKDETECT_DEBOUNCE_5MS: + case ADAU1761_JACKDETECT_DEBOUNCE_10MS: + case ADAU1761_JACKDETECT_DEBOUNCE_20MS: + case ADAU1761_JACKDETECT_DEBOUNCE_40MS: + val |= pdata->jackdetect_debounce_time << 6; + break; + default: + return -EINVAL; + } + if (pdata->jackdetect_active_low) + val |= ADAU1761_DIGMIC_JACKDETECT_ACTIVE_LOW; + + ret = snd_soc_add_codec_controls(codec, + adau1761_jack_detect_controls, + ARRAY_SIZE(adau1761_jack_detect_controls)); + if (ret) + return ret; + case ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE: /* fallthrough */ + if (adau17x1_has_dsp(adau)) { + routes = adau1761_no_dmic_dsp_routes; + num_routes = ARRAY_SIZE(adau1761_no_dmic_dsp_routes); + ret = snd_soc_dapm_new_controls(&codec->dapm, + adau17x1_no_dmic_dsp_widgets, + ARRAY_SIZE(adau17x1_no_dmic_dsp_widgets)); + if (ret) + return ret; + + } else { + routes = adau1761_no_dmic_no_dsp_routes; + num_routes = ARRAY_SIZE(adau1761_no_dmic_no_dsp_routes); + } + break; + case ADAU1761_DIGMIC_JACKDET_PIN_MODE_DIGMIC: + ret = snd_soc_dapm_new_controls(&codec->dapm, + adau1761_dmic_widgets, + ARRAY_SIZE(adau1761_dmic_widgets)); + if (ret) + return ret; + + if (adau17x1_has_dsp(adau)) { + routes = adau1761_dmic_dsp_routes; + num_routes = ARRAY_SIZE(adau1761_dmic_dsp_routes); + widgets = adau17x1_dmic_dsp_widgets; + num_widgets = ARRAY_SIZE(adau17x1_dmic_dsp_widgets); + } else { + routes = adau1761_dmic_no_dsp_routes; + num_routes = ARRAY_SIZE(adau1761_dmic_no_dsp_routes); + } + + ret = snd_soc_dapm_new_controls(&codec->dapm, widgets, + num_widgets); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(&codec->dapm, + adau1761_dmic_routes, + ARRAY_SIZE(adau1761_dmic_routes)); + if (ret) + return ret; + + val |= ADAU1761_DIGMIC_JACKDETECT_DIGMIC; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dapm_add_routes(&codec->dapm, routes, num_routes); + if (ret) + return ret; + + regmap_write(adau->regmap, ADAU1761_DIGMIC_JACKDETECT, val); + + return 0; +} + +static int adau1761_setup_headphone_mode(struct snd_soc_codec *codec) +{ + struct adau *adau = snd_soc_codec_get_drvdata(codec); + struct adau1761_platform_data *pdata = codec->dev->platform_data; + enum adau1761_output_mode mode; + int ret; + + if (pdata) + mode = pdata->headphone_mode; + else + mode = ADAU1761_OUTPUT_MODE_HEADPHONE; + + switch (mode) { + case ADAU1761_OUTPUT_MODE_LINE: + break; + case ADAU1761_OUTPUT_MODE_HEADPHONE_CAPLESS: + regmap_update_bits(adau->regmap, ADAU1761_PLAY_MONO_OUTPUT_VOL, + 3, 3); + case ADAU1761_OUTPUT_MODE_HEADPHONE: /* fallthrough */ + regmap_update_bits(adau->regmap, ADAU1761_PLAY_HP_RIGHT_VOL, + 1, 1); + break; + default: + return -EINVAL; + } + + if (mode == ADAU1761_OUTPUT_MODE_HEADPHONE_CAPLESS) { + ret = snd_soc_dapm_new_controls(&codec->dapm, + adau1761_capless_dapm_widgets, + ARRAY_SIZE(adau1761_capless_dapm_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(&codec->dapm, + adau1761_capless_dapm_routes, + ARRAY_SIZE(adau1761_capless_dapm_routes)); + } else { + ret = snd_soc_dapm_new_controls(&codec->dapm, + adau1761_mono_dapm_widgets, + ARRAY_SIZE(adau1761_mono_dapm_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(&codec->dapm, + adau1761_mono_dapm_routes, + ARRAY_SIZE(adau1761_mono_dapm_routes)); + } + + return ret; +} + +static bool adau1761_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU1761_DIGMIC_JACKDETECT: + case ADAU1761_REC_MIXER_LEFT0: + case ADAU1761_REC_MIXER_LEFT1: + case ADAU1761_REC_MIXER_RIGHT0: + case ADAU1761_REC_MIXER_RIGHT1: + case ADAU1761_LEFT_DIFF_INPUT_VOL: + case ADAU1761_RIGHT_DIFF_INPUT_VOL: + case ADAU1761_PLAY_LR_MIXER_LEFT: + case ADAU1761_PLAY_MIXER_LEFT0: + case ADAU1761_PLAY_MIXER_LEFT1: + case ADAU1761_PLAY_MIXER_RIGHT0: + case ADAU1761_PLAY_MIXER_RIGHT1: + case ADAU1761_PLAY_LR_MIXER_RIGHT: + case ADAU1761_PLAY_MIXER_MONO: + case ADAU1761_PLAY_HP_LEFT_VOL: + case ADAU1761_PLAY_HP_RIGHT_VOL: + case ADAU1761_PLAY_LINE_LEFT_VOL: + case ADAU1761_PLAY_LINE_RIGHT_VOL: + case ADAU1761_PLAY_MONO_OUTPUT_VOL: + case ADAU1761_POP_CLICK_SUPPRESS: + case ADAU1761_JACK_DETECT_PIN: + case ADAU1761_DEJITTER: + case ADAU1761_CLK_ENABLE0: + case ADAU1761_CLK_ENABLE1: + return true; + default: + break; + } + + return adau17x1_readable_register(dev, reg); +} + +static int adau1761_probe(struct snd_soc_codec *codec) +{ + struct adau1761_platform_data *pdata = codec->dev->platform_data; + struct adau *adau = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = adau17x1_probe(codec); + if (ret < 0) + return ret; + + if (pdata && pdata->input_differential) { + regmap_update_bits(adau->regmap, ADAU1761_LEFT_DIFF_INPUT_VOL, + ADAU1761_DIFF_INPUT_VOL_LDEN, + ADAU1761_DIFF_INPUT_VOL_LDEN); + regmap_update_bits(adau->regmap, ADAU1761_RIGHT_DIFF_INPUT_VOL, + ADAU1761_DIFF_INPUT_VOL_LDEN, + ADAU1761_DIFF_INPUT_VOL_LDEN); + ret = snd_soc_add_codec_controls(codec, + adau1761_differential_mode_controls, + ARRAY_SIZE(adau1761_differential_mode_controls)); + if (ret) + return ret; + } else { + ret = snd_soc_add_codec_controls(codec, + adau1761_single_mode_controls, + ARRAY_SIZE(adau1761_single_mode_controls)); + if (ret) + return ret; + } + + switch (adau1761_get_lineout_mode(codec)) { + case ADAU1761_OUTPUT_MODE_LINE: + break; + case ADAU1761_OUTPUT_MODE_HEADPHONE: + regmap_update_bits(adau->regmap, ADAU1761_PLAY_LINE_LEFT_VOL, + 1, 1); + regmap_update_bits(adau->regmap, ADAU1761_PLAY_LINE_RIGHT_VOL, + 1, 1); + break; + default: + return -EINVAL; + } + + ret = adau1761_setup_headphone_mode(codec); + if (ret) + return ret; + + ret = adau1761_setup_digmic_jackdetect(codec); + if (ret) + return ret; + + ret = adau17x1_add_routes(codec); + if (ret < 0) + return ret; + + if (adau->type == ADAU1761) { + ret = snd_soc_dapm_new_controls(&codec->dapm, + adau1761_dapm_widgets, + ARRAY_SIZE(adau1761_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(&codec->dapm, + adau1761_dapm_routes, + ARRAY_SIZE(adau1761_dapm_routes)); + if (ret) + return ret; + + ret = adau17x1_load_firmware(adau, codec->dev, + ADAU1761_FIRMWARE); + if (ret) + dev_warn(codec->dev, "Failed to firmware\n"); + } + + return 0; +} + +static struct snd_soc_codec_driver adau1761_codec_driver = { + .probe = adau1761_probe, + .remove = adau17x1_suspend, + .suspend = adau17x1_suspend, + .resume = adau17x1_resume, + .set_bias_level = adau1761_set_bias_level, + + .controls = adau1761_controls, + .num_controls = ARRAY_SIZE(adau1761_controls), + .dapm_widgets = adau1x61_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1x61_dapm_widgets), + .dapm_routes = adau1x61_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1x61_dapm_routes), +}; + +#define ADAU1761_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver adau1361_dai_driver = { + .name = "adau-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1761_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1761_FORMATS, + }, + .ops = &adau17x1_dai_ops, +}; + +static struct snd_soc_dai_driver adau1761_dai_driver = { + .name = "adau-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1761_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1761_FORMATS, + }, + .ops = &adau17x1_dai_ops, +}; + +static int adau1761_bus_probe(struct device *dev, + struct regmap *regmap, enum adau17x1_type type, + enum snd_soc_control_type control_type) +{ + struct snd_soc_dai_driver *dai_drv; + int ret; + + ret = adau17x1_bus_probe(dev, regmap, type, control_type); + if (ret) + return ret; + + if (type == ADAU1361) + dai_drv = &adau1361_dai_driver; + else + dai_drv = &adau1761_dai_driver; + + return snd_soc_register_codec(dev, &adau1761_codec_driver, dai_drv, 1); +} + +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static const struct regmap_config adau1761_spi_regmap_config = { + .val_bits = 8, + .reg_bits = 24, + .read_flag_mask = 0x01, + .max_register = 0x40fa, + .reg_defaults = adau1761_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1761_reg_defaults), + .readable_reg = adau1761_readable_register, + .volatile_reg = adau17x1_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int adau1761_spi_probe(struct spi_device *spi) +{ + enum adau17x1_type type = spi_get_device_id(spi)->driver_data; + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &adau1761_spi_regmap_config); + + return adau1761_bus_probe(&spi->dev, regmap, type, SND_SOC_SPI); +} + +static int adau1761_spi_remove(struct spi_device *spi) +{ + snd_soc_unregister_codec(&spi->dev); + return 0; +} + +static const struct spi_device_id adau1761_spi_id[] = { + { "adau1361", ADAU1361 }, + { "adau1461", ADAU1761 }, + { "adau1761", ADAU1761 }, + { "adau1961", ADAU1361 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adau1761_spi_id); + +static struct spi_driver adau1761_spi_driver = { + .driver = { + .name = "adau1761", + .owner = THIS_MODULE, + }, + .probe = adau1761_spi_probe, + .remove = adau1761_spi_remove, + .id_table = adau1761_spi_id, +}; + +static int __init adau1761_spi_register_driver(void) +{ + return spi_register_driver(&adau1761_spi_driver); +} + +static void adau1761_spi_unregister_driver(void) +{ + spi_unregister_driver(&adau1761_spi_driver); +} + +#else +static int adau1761_spi_register_driver(void) { return 0; } +static void adau1761_spi_unregister_driver(void) {} +#endif + +#if IS_ENABLED(CONFIG_I2C) + +static const struct regmap_config adau1761_i2c_regmap_config = { + .val_bits = 8, + .reg_bits = 16, + .max_register = 0x40fa, + .reg_defaults = adau1761_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1761_reg_defaults), + .readable_reg = adau1761_readable_register, + .volatile_reg = adau17x1_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int adau1761_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + enum adau17x1_type type = id->driver_data; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &adau1761_i2c_regmap_config); + + return adau1761_bus_probe(&client->dev, regmap, type, SND_SOC_I2C); +} + +static int adau1761_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id adau1761_i2c_id[] = { + { "adau1361", ADAU1361 }, + { "adau1461", ADAU1761 }, + { "adau1761", ADAU1761 }, + { "adau1961", ADAU1361 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adau1761_i2c_id); + +static struct i2c_driver adau1761_i2c_driver = { + .driver = { + .name = "adau1761", + .owner = THIS_MODULE, + }, + .probe = adau1761_i2c_probe, + .remove = adau1761_i2c_remove, + .id_table = adau1761_i2c_id, +}; + +static int __init adau1761_i2c_register_driver(void) +{ + return i2c_add_driver(&adau1761_i2c_driver); +} + +static void __exit adau1761_i2c_unregister_driver(void) +{ + i2c_del_driver(&adau1761_i2c_driver); +} + +#else +static int adau1761_i2c_register_driver(void) { return 0; } +static void adau1761_i2c_unregister_driver(void) {} +#endif + +static int __init adau1761_init(void) +{ + int ret; + + ret = adau1761_spi_register_driver(); + if (ret) + return ret; + + ret = adau1761_i2c_register_driver(); + if (ret) + adau1761_spi_unregister_driver(); + + return ret; +} +module_init(adau1761_init); + +static void __exit adau1761_exit(void) +{ + adau1761_i2c_unregister_driver(); + adau1761_spi_unregister_driver(); +} +module_exit(adau1761_exit); + +MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC driver"); +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_LICENSE("GPL");
This patch adds support for the Analog Devices ADAU1381 and ADAU1781 audio CODECs.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- include/linux/platform_data/adau17x1.h | 19 + sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/adau1781.c | 673 +++++++++++++++++++++++++++++++++ 4 files changed, 699 insertions(+) create mode 100644 sound/soc/codecs/adau1781.c
diff --git a/include/linux/platform_data/adau17x1.h b/include/linux/platform_data/adau17x1.h index 5f559db..b890507 100644 --- a/include/linux/platform_data/adau17x1.h +++ b/include/linux/platform_data/adau17x1.h @@ -87,4 +87,23 @@ struct adau1761_platform_data { enum adau17x1_micbias_voltage micbias_voltage; };
+/** + * struct adau1781_platform_data - ADAU1781 Codec driver platform data + * @left_input_differential: If true configure the left input as + differential input. + * @right_input_differential: If true configure the right input as + * differntial input. + * @use_dmic: If true configure the MIC pins as digital + * microphone pins instead of analog microphone pins. + * @micbias_voltage: Microphone voltage bias + */ +struct adau1781_platform_data { + bool left_input_differential; + bool right_input_differential; + + bool use_dmic; + + enum adau17x1_micbias_voltage micbias_voltage; +}; + #endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index dd11f00..0181e99 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -20,6 +20,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AD73311 select SND_SOC_ADAU1373 if I2C select SND_SOC_ADAU1761 if SND_SOC_I2C_AND_SPI + select SND_SOC_ADAU1781 if SND_SOC_I2C_AND_SPI select SND_SOC_ADAV80X select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER @@ -190,6 +191,10 @@ config SND_SOC_ADAU1761 select SND_SOC_ADAU17X1 tristate
+config SND_SOC_ADAU1781 + select SND_SOC_ADAU17X1 + tristate + config SND_SOC_ADAV80X tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 60e30f8..b0a4c58 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -9,6 +9,7 @@ snd-soc-adau1701-objs := adau1701.o snd-soc-adau1373-objs := adau1373.o snd-soc-adau17x1-objs := adau17x1.o snd-soc-adau1761-objs := adau1761.o +snd-soc-adau1781-objs := adau1781.o snd-soc-adav80x-objs := adav80x.o snd-soc-ads117x-objs := ads117x.o snd-soc-ak4104-objs := ak4104.o @@ -133,6 +134,7 @@ obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o obj-$(CONFIG_SND_SOC_ADAU1761) += snd-soc-adau1761.o +obj-$(CONFIG_SND_SOC_ADAU1781) += snd-soc-adau1781.o obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o diff --git a/sound/soc/codecs/adau1781.c b/sound/soc/codecs/adau1781.c new file mode 100644 index 0000000..411e4e6 --- /dev/null +++ b/sound/soc/codecs/adau1781.c @@ -0,0 +1,673 @@ +/* + * Driver for ADAU1781/ADAU1781 codec + * + * Copyright 2011-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen lars@metafoo.de + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <linux/platform_data/adau17x1.h> + +#include "adau17x1.h" + +#define ADAU1781_DMIC_BEEP_CTRL 0x4008 +#define ADAU1781_LEFT_PGA 0x400e +#define ADAU1781_RIGHT_PGA 0x400f +#define ADAU1781_LEFT_PLAYBACK_MIXER 0x401c +#define ADAU1781_RIGHT_PLAYBACK_MIXER 0x401e +#define ADAU1781_MONO_PLAYBACK_MIXER 0x401f +#define ADAU1781_LEFT_LINEOUT 0x4025 +#define ADAU1781_RIGHT_LINEOUT 0x4026 +#define ADAU1781_SPEAKER 0x4027 +#define ADAU1781_BEEP_ZC 0x4028 +#define ADAU1781_DEJITTER 0x4032 +#define ADAU1781_DIG_PWDN0 0x4080 +#define ADAU1781_DIG_PWDN1 0x4081 + +#define ADAU1781_INPUT_DIFFERNTIAL BIT(3) + +#define ADAU1381_FIRMWARE "adau1381.bin" +#define ADAU1781_FIRMWARE "adau1781.bin" + +static const struct reg_default adau1781_reg_defaults[] = { + { ADAU1781_DMIC_BEEP_CTRL, 0x00 }, + { ADAU1781_LEFT_PGA, 0xc7 }, + { ADAU1781_RIGHT_PGA, 0xc7 }, + { ADAU1781_LEFT_PLAYBACK_MIXER, 0x00 }, + { ADAU1781_RIGHT_PLAYBACK_MIXER, 0x00 }, + { ADAU1781_MONO_PLAYBACK_MIXER, 0x00 }, + { ADAU1781_LEFT_LINEOUT, 0x00 }, + { ADAU1781_RIGHT_LINEOUT, 0x00 }, + { ADAU1781_SPEAKER, 0x00 }, + { ADAU1781_BEEP_ZC, 0x19 }, + { ADAU1781_DEJITTER, 0x60 }, + { ADAU1781_DIG_PWDN1, 0x0c }, + { ADAU1781_DIG_PWDN1, 0x00 }, + { ADAU17X1_CLOCK_CONTROL, 0x00 }, + { ADAU17X1_PLL_CONTROL, 0x00 }, + { ADAU17X1_REC_POWER_MGMT, 0x00 }, + { ADAU17X1_MICBIAS, 0x04 }, + { ADAU17X1_SERIAL_PORT0, 0x00 }, + { ADAU17X1_SERIAL_PORT1, 0x00 }, + { ADAU17X1_CONVERTER0, 0x00 }, + { ADAU17X1_CONVERTER1, 0x00 }, + { ADAU17X1_LEFT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU17X1_ADC_CONTROL, 0x00 }, + { ADAU17X1_PLAY_POWER_MGMT, 0x00 }, + { ADAU17X1_DAC_CONTROL0, 0x00 }, + { ADAU17X1_DAC_CONTROL1, 0x00 }, + { ADAU17X1_DAC_CONTROL2, 0x00 }, + { ADAU17X1_SERIAL_PORT_PAD, 0x00 }, + { ADAU17X1_CONTROL_PORT_PAD0, 0x00 }, + { ADAU17X1_CONTROL_PORT_PAD1, 0x00 }, + { ADAU17X1_DSP_SAMPLING_RATE, 0x01 }, + { ADAU17X1_SERIAL_INPUT_ROUTE, 0x00 }, + { ADAU17X1_SERIAL_OUTPUT_ROUTE, 0x00 }, + { ADAU17X1_DSP_ENABLE, 0x00 }, + { ADAU17X1_DSP_RUN, 0x00 }, + { ADAU17X1_SERIAL_SAMPLING_RATE, 0x00 }, +}; + +static const DECLARE_TLV_DB_SCALE(adau1781_speaker_tlv, 0, 200, 0); + +static const unsigned int adau1781_pga_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 1, TLV_DB_SCALE_ITEM(0, 600, 0), + 2, 3, TLV_DB_SCALE_ITEM(1000, 400, 0), + 4, 4, TLV_DB_SCALE_ITEM(1700, 0, 0), + 5, 7, TLV_DB_SCALE_ITEM(2000, 600, 0), +}; + +static const unsigned int adau1781_beep_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 1, TLV_DB_SCALE_ITEM(0, 600, 0), + 2, 3, TLV_DB_SCALE_ITEM(1000, 400, 0), + 4, 4, TLV_DB_SCALE_ITEM(-2300, 0, 0), + 5, 7, TLV_DB_SCALE_ITEM(2000, 600, 0), +}; + +static const DECLARE_TLV_DB_SCALE(adau1781_sidetone_tlv, -1800, 300, 1); + +static const char * const adau1781_speaker_bias_select_text[] = { + "Normal operation", "Power saving", "Enhanced performance", +}; + +static const char * const adau1781_bias_select_text[] = { + "Normal operation", "Extreme power saving", "Power saving", + "Enhanced performance", +}; + +static const SOC_ENUM_SINGLE_DECL(adau1781_adc_bias_enum, + ADAU17X1_REC_POWER_MGMT, 3, adau1781_bias_select_text); +static const SOC_ENUM_SINGLE_DECL(adau1781_speaker_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 6, adau1781_speaker_bias_select_text); +static const SOC_ENUM_SINGLE_DECL(adau1781_dac_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 4, adau1781_bias_select_text); +static const SOC_ENUM_SINGLE_DECL(adau1781_playback_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 2, adau1781_bias_select_text); +static const SOC_ENUM_SINGLE_DECL(adau1781_capture_bias_enum, + ADAU17X1_REC_POWER_MGMT, 1, adau1781_bias_select_text); + +static const struct snd_kcontrol_new adau1781_controls[] = { + SOC_SINGLE_TLV("Beep Capture Volume", ADAU1781_DMIC_BEEP_CTRL, 0, 7, 0, + adau1781_beep_tlv), + SOC_DOUBLE_R_TLV("PGA Capture Volume", ADAU1781_LEFT_PGA, + ADAU1781_RIGHT_PGA, 5, 7, 0, adau1781_pga_tlv), + SOC_DOUBLE_R("PGA Capture Switch", ADAU1781_LEFT_PGA, + ADAU1781_RIGHT_PGA, 1, 1, 0), + + SOC_DOUBLE_R("Lineout Playback Switch", ADAU1781_LEFT_LINEOUT, + ADAU1781_RIGHT_LINEOUT, 1, 1, 0), + SOC_SINGLE("Beep ZC Switch", ADAU1781_BEEP_ZC, 0, 1, 0), + + SOC_SINGLE("Mono Playback Switch", ADAU1781_MONO_PLAYBACK_MIXER, + 0, 1, 0), + SOC_SINGLE_TLV("Mono Playback Volume", ADAU1781_SPEAKER, 6, 3, 0, + adau1781_speaker_tlv), + + SOC_ENUM("ADC Bias", adau1781_adc_bias_enum), + SOC_ENUM("DAC Bias", adau1781_dac_bias_enum), + SOC_ENUM("Capture Bias", adau1781_capture_bias_enum), + SOC_ENUM("Playback Bias", adau1781_playback_bias_enum), + SOC_ENUM("Speaker Bias", adau1781_speaker_bias_enum), +}; + +static const struct snd_kcontrol_new adau1781_beep_mixer_controls[] = { + SOC_DAPM_SINGLE("Beep Capture Switch", ADAU1781_DMIC_BEEP_CTRL, + 3, 1, 0), +}; + +static const struct snd_kcontrol_new adau1781_left_mixer_controls[] = { + SOC_DAPM_SINGLE("Switch", ADAU1781_LEFT_PLAYBACK_MIXER, 5, 1, 0), + SOC_DAPM_SINGLE_TLV("Beep Playback Volume", + ADAU1781_LEFT_PLAYBACK_MIXER, 1, 8, 0, adau1781_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1781_right_mixer_controls[] = { + SOC_DAPM_SINGLE("Switch", ADAU1781_RIGHT_PLAYBACK_MIXER, 6, 1, 0), + SOC_DAPM_SINGLE_TLV("Beep Playback Volume", + ADAU1781_LEFT_PLAYBACK_MIXER, 1, 8, 0, adau1781_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1781_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Switch", ADAU1781_MONO_PLAYBACK_MIXER, 7, 1, 0), + SOC_DAPM_SINGLE("Right Switch", ADAU1781_MONO_PLAYBACK_MIXER, 6, 1, 0), + SOC_DAPM_SINGLE_TLV("Beep Playback Volume", + ADAU1781_MONO_PLAYBACK_MIXER, 2, 8, 0, adau1781_sidetone_tlv), +}; + +static int adau1781_dejitter_fixup(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct adau *adau = snd_soc_codec_get_drvdata(codec); + + /* After any power changes have been made the dejitter circuit + * has to be reinitialized. */ + regmap_write(adau->regmap, ADAU1781_DEJITTER, 0); + if (!adau->master) + regmap_write(adau->regmap, ADAU1781_DEJITTER, 5); + + return 0; +} + +static const struct snd_soc_dapm_widget adau1781_dapm_widgets[] = { + SND_SOC_DAPM_PGA("Left PGA", ADAU1781_LEFT_PGA, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", ADAU1781_RIGHT_PGA, 0, 0, NULL, 0), + + SND_SOC_DAPM_OUT_DRV("Speaker", ADAU1781_SPEAKER, 0, 0, NULL, 0), + + SOC_MIXER_NAMED_CTL_ARRAY("Beep Mixer", ADAU17X1_MICBIAS, 4, 0, + adau1781_beep_mixer_controls), + + SOC_MIXER_ARRAY("Left Lineout Mixer", SND_SOC_NOPM, 0, 0, + adau1781_left_mixer_controls), + SOC_MIXER_ARRAY("Right Lineout Mixer", SND_SOC_NOPM, 0, 0, + adau1781_right_mixer_controls), + SOC_MIXER_ARRAY("Mono Mixer", SND_SOC_NOPM, 0, 0, + adau1781_mono_mixer_controls), + + SND_SOC_DAPM_SUPPLY("Serial Input Routing", ADAU1781_DIG_PWDN0, + 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Serial Output Routing", ADAU1781_DIG_PWDN0, + 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Clock Domain Transfer", ADAU1781_DIG_PWDN0, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Serial Ports", ADAU1781_DIG_PWDN0, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Engine", ADAU1781_DIG_PWDN0, 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Engine", ADAU1781_DIG_PWDN1, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Digital Mic", ADAU1781_DIG_PWDN1, 1, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Sound Engine", ADAU1781_DIG_PWDN0, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("SYSCLK", 1, ADAU1781_DIG_PWDN0, 1, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Zero Crossing Detector", ADAU1781_DIG_PWDN1, 2, 0, + NULL, 0), + + SND_SOC_DAPM_POST("Dejitter fixup", adau1781_dejitter_fixup), + + SND_SOC_DAPM_INPUT("BEEP"), + + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), + SND_SOC_DAPM_OUTPUT("SP"), + SND_SOC_DAPM_INPUT("LMIC"), + SND_SOC_DAPM_INPUT("RMIC"), +}; + +static const struct snd_soc_dapm_route adau1781_dapm_routes[] = { + { "Left Lineout Mixer", NULL, "Left Playback Enable" }, + { "Right Lineout Mixer", NULL, "Right Playback Enable" }, + + { "Left Lineout Mixer", "Beep Playback Volume", "Beep Mixer" }, + { "Left Lineout Mixer", "Switch", "Left DAC" }, + + { "Right Lineout Mixer", "Beep Playback Volume", "Beep Mixer" }, + { "Right Lineout Mixer", "Switch", "Right DAC" }, + + { "Mono Mixer", "Beep Playback Volume", "Beep Mixer" }, + { "Mono Mixer", "Right Switch", "Right DAC" }, + { "Mono Mixer", "Left Switch", "Left DAC" }, + { "Speaker", NULL, "Mono Mixer" }, + + { "Mono Mixer", NULL, "SYSCLK" }, + { "Left Lineout Mixer", NULL, "SYSCLK" }, + { "Left Lineout Mixer", NULL, "SYSCLK" }, + + { "Beep Mixer", "Beep Capture Switch", "BEEP" }, + { "Beep Mixer", NULL, "Zero Crossing Detector" }, + + { "Left DAC", NULL, "DAC Engine" }, + { "Right DAC", NULL, "DAC Engine" }, + + { "Sound Engine", NULL, "SYSCLK" }, + { "DSP", NULL, "Sound Engine" }, + + { "AIFCLK", NULL, "SYSCLK" }, + + { "AIFIN", NULL, "Serial Input Routing" }, + { "AIFIN", NULL, "Serial Ports" }, + { "AIFIN", NULL, "Clock Domain Transfer" }, + { "AIFOUT", NULL, "Serial Output Routing" }, + { "AIFOUT", NULL, "Serial Ports" }, + { "AIFOUT", NULL, "Clock Domain Transfer" }, + + { "AOUTL", NULL, "Left Lineout Mixer" }, + { "AOUTR", NULL, "Right Lineout Mixer" }, + { "SP", NULL, "Speaker" }, +}; + + +DECLARE_ADAU17X1_AIFOUT_MUX(adc, "ADC"); + +static const struct snd_soc_dapm_route adau1781_adc_dapm_routes[] = { + { "Left PGA", NULL, "LMIC" }, + { "Right PGA", NULL, "RMIC" }, + { "Left ADC", NULL, "Left PGA" }, + { "Right ADC", NULL, "Right PGA" }, + + { "Left ADC", NULL, "ADC Engine" }, + { "Right ADC", NULL, "ADC Engine" }, + + { "AIFOUT Capture Mux", "ADC", "Left ADC" }, + { "AIFOUT Capture Mux", "ADC", "Right ADC" }, + + { "DSP", NULL, "Left ADC" }, + { "DSP", NULL, "Right ADC" }, +}; + +static const char * const adau1781_dmic_select_text[] = { + "DMIC1", "DMIC2", +}; + +static const SOC_ENUM_SINGLE_DECL(adau1781_dmic_select_enum, 0, 0, + adau1781_dmic_select_text); + +static const struct snd_kcontrol_new adau1781_dmic_mux = + SOC_DAPM_ENUM_VIRT("DMIC Select", adau1781_dmic_select_enum); + +static const char * const adau1781_dmic_aifout_mux_text[] = { + "DMIC", + "DSP", +}; + +static const SOC_ENUM_SINGLE_DECL(adau1781_dmic_aifout_mux_enum, + ADAU17X1_SERIAL_OUTPUT_ROUTE, 0, adau1781_dmic_aifout_mux_text); + +static const struct snd_kcontrol_new adau1781_dmic_aifout_mux = + ADAU17X1_DSP_MUX_ENUM("AIFOUT Capture Mux", + adau1781_dmic_aifout_mux_enum); + +static const struct snd_soc_dapm_widget adau1781_dmic_dapm_widgets[] = { + SND_SOC_DAPM_VIRT_MUX("DMIC Select", SND_SOC_NOPM, 0, 0, + &adau1781_dmic_mux), + + SND_SOC_DAPM_VIRT_MUX("AIFOUT Capture Mux", SND_SOC_NOPM, 0, 0, + &adau1781_dmic_aifout_mux), + + SND_SOC_DAPM_ADC("DMIC1", NULL, ADAU1781_DMIC_BEEP_CTRL, 4, 0), + SND_SOC_DAPM_ADC("DMIC2", NULL, ADAU1781_DMIC_BEEP_CTRL, 5, 0), +}; + +static const struct snd_soc_dapm_route adau1781_dmic_dapm_routes[] = { + { "DMIC1", NULL, "LMIC" }, + { "DMIC2", NULL, "RMIC" }, + + { "DMIC1", NULL, "Digital Mic" }, + { "DMIC2", NULL, "Digital Mic" }, + { "DMIC1", NULL, "ADC Engine" }, + { "DMIC2", NULL, "ADC Engine" }, + + { "DMIC Select", "DMIC1", "DMIC1" }, + { "DMIC Select", "DMIC2", "DMIC2" }, + + { "AIFOUT Capture Mux", "DMIC", "DMIC Select" }, + { "DSP", NULL, "DMIC Select" }, +}; + +static int adau1781_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct adau *adau = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN); + + /* Precharge */ + regmap_update_bits(adau->regmap, ADAU1781_DIG_PWDN1, 0x8, 0x8); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(adau->regmap, ADAU1781_DIG_PWDN1, 0xc, 0x0); + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, 0); + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +static bool adau1781_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU1781_DMIC_BEEP_CTRL: + case ADAU1781_LEFT_PGA: + case ADAU1781_RIGHT_PGA: + case ADAU1781_LEFT_PLAYBACK_MIXER: + case ADAU1781_RIGHT_PLAYBACK_MIXER: + case ADAU1781_MONO_PLAYBACK_MIXER: + case ADAU1781_LEFT_LINEOUT: + case ADAU1781_RIGHT_LINEOUT: + case ADAU1781_SPEAKER: + case ADAU1781_BEEP_ZC: + case ADAU1781_DEJITTER: + case ADAU1781_DIG_PWDN0: + case ADAU1781_DIG_PWDN1: + return true; + default: + break; + } + + return adau17x1_readable_register(dev, reg); +} + +static int adau1781_set_input_mode(struct adau *adau, unsigned int reg, + bool differential) +{ + unsigned int val; + + if (differential) + val = ADAU1781_INPUT_DIFFERNTIAL; + else + val = 0; + regmap_update_bits(adau->regmap, reg, ADAU1781_INPUT_DIFFERNTIAL, val); + + return 0; +} + +static int adau1781_probe(struct snd_soc_codec *codec) +{ + struct adau1781_platform_data *pdata = dev_get_platdata(codec->dev); + struct adau *adau = snd_soc_codec_get_drvdata(codec); + const char *firmware; + int ret; + + ret = adau17x1_probe(codec); + if (ret) + return ret; + + if (pdata) { + ret = adau1781_set_input_mode(adau, ADAU1781_LEFT_PGA, + pdata->left_input_differential); + if (ret) + return ret; + ret = adau1781_set_input_mode(adau, ADAU1781_RIGHT_PGA, + pdata->right_input_differential); + if (ret) + return ret; + } + + if (pdata && pdata->use_dmic) { + ret = snd_soc_dapm_new_controls(&codec->dapm, + adau1781_dmic_dapm_widgets, + ARRAY_SIZE(adau1781_dmic_dapm_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(&codec->dapm, + adau1781_dmic_dapm_routes, + ARRAY_SIZE(adau1781_dmic_dapm_routes)); + if (ret) + return ret; + } else { + ret = snd_soc_dapm_new_controls(&codec->dapm, + adau17x1_adc_dsp_widgets, + ARRAY_SIZE(adau17x1_adc_dsp_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(&codec->dapm, + adau1781_adc_dapm_routes, + ARRAY_SIZE(adau1781_adc_dapm_routes)); + if (ret) + return ret; + } + + switch (adau->type) { + case ADAU1381: + firmware = ADAU1381_FIRMWARE; + break; + case ADAU1781: + firmware = ADAU1781_FIRMWARE; + break; + default: + return -EINVAL; + } + + ret = adau17x1_add_routes(codec); + if (ret < 0) + return ret; + + ret = adau17x1_load_firmware(adau, codec->dev, firmware); + if (ret) + dev_warn(codec->dev, "Failed to load firmware\n"); + + return 0; +} + +static struct snd_soc_codec_driver adau1781_codec_driver = { + .probe = adau1781_probe, + .remove = adau17x1_suspend, + .suspend = adau17x1_suspend, + .resume = adau17x1_resume, + .set_bias_level = adau1781_set_bias_level, + + .controls = adau1781_controls, + .num_controls = ARRAY_SIZE(adau1781_controls), + .dapm_widgets = adau1781_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1781_dapm_widgets), + .dapm_routes = adau1781_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1781_dapm_routes), +}; + +#define ADAU1781_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver adau1781_dai_driver = { + .name = "adau-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1781_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1781_FORMATS, + }, + .ops = &adau17x1_dai_ops, +}; + +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static const struct regmap_config adau1781_spi_regmap_config = { + .val_bits = 8, + .reg_bits = 24, + .read_flag_mask = 0x01, + .max_register = 0x40f8, + .reg_defaults = adau1781_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1781_reg_defaults), + .readable_reg = adau1781_readable_register, + .volatile_reg = adau17x1_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int adau1781_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + enum adau17x1_type type = spi_get_device_id(spi)->driver_data; + int ret; + + regmap = devm_regmap_init_spi(spi, &adau1781_spi_regmap_config); + + ret = adau17x1_bus_probe(&spi->dev, regmap, type, SND_SOC_SPI); + if (ret) + return ret; + + return snd_soc_register_codec(&spi->dev, &adau1781_codec_driver, + &adau1781_dai_driver, 1); +} + +static int adau1781_spi_remove(struct spi_device *spi) +{ + snd_soc_unregister_codec(&spi->dev); + return 0; +} + +static const struct spi_device_id adau1781_spi_id[] = { + { "adau1381", ADAU1381 }, + { "adau1781", ADAU1781 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adau1781_spi_id); + +static struct spi_driver adau1781_spi_driver = { + .driver = { + .name = "adau1781", + .owner = THIS_MODULE, + }, + .probe = adau1781_spi_probe, + .remove = adau1781_spi_remove, + .id_table = adau1781_spi_id, +}; + +static int __init adau1781_spi_register_driver(void) +{ + return spi_register_driver(&adau1781_spi_driver); +} + +static void adau1781_spi_unregister_driver(void) +{ + spi_unregister_driver(&adau1781_spi_driver); +} + +#else +static int adau1781_spi_register_driver(void) { return 0; } +static void adau1781_spi_unregister_driver(void) {} +#endif + +#if IS_ENABLED(CONFIG_I2C) + +static const struct regmap_config adau1781_i2c_regmap_config = { + .val_bits = 8, + .reg_bits = 16, + .max_register = 0x40f8, + .reg_defaults = adau1781_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1781_reg_defaults), + .readable_reg = adau1781_readable_register, + .volatile_reg = adau17x1_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int adau1781_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + enum adau17x1_type type = id->driver_data; + int ret; + + regmap = devm_regmap_init_i2c(client, &adau1781_i2c_regmap_config); + + ret = adau17x1_bus_probe(&client->dev, regmap, type, SND_SOC_I2C); + if (ret) + return ret; + + return snd_soc_register_codec(&client->dev, &adau1781_codec_driver, + &adau1781_dai_driver, 1); +} + +static int adau1781_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id adau1781_i2c_id[] = { + { "adau1381", ADAU1381 }, + { "adau1781", ADAU1781 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adau1781_i2c_id); + +static struct i2c_driver adau1781_i2c_driver = { + .driver = { + .name = "adau1781", + .owner = THIS_MODULE, + }, + .probe = adau1781_i2c_probe, + .remove = adau1781_i2c_remove, + .id_table = adau1781_i2c_id, +}; + +static int __init adau1781_i2c_register_driver(void) +{ + return i2c_add_driver(&adau1781_i2c_driver); +} + +static void __exit adau1781_i2c_unregister_driver(void) +{ + i2c_del_driver(&adau1781_i2c_driver); +} + +#else +static int adau1781_i2c_register_driver(void) { return 0; } +static void adau1781_i2c_unregister_driver(void) {} +#endif + +static int __init adau1781_init(void) +{ + int ret = 0; + + ret = adau1781_spi_register_driver(); + if (ret) + return ret; + + ret = adau1781_i2c_register_driver(); + if (ret) + adau1781_spi_unregister_driver(); + + return ret; +} +module_init(adau1781_init); + +static void __exit adau1781_exit(void) +{ + adau1781_i2c_unregister_driver(); + adau1781_spi_unregister_driver(); +} +module_exit(adau1781_exit); + +MODULE_DESCRIPTION("ASoC ADAU1381/ADAU1781 driver"); +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_LICENSE("GPL");
The core does not modify these fields, so they can be made const. This allows drivers to declare their op tables as const.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- include/sound/soc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index bc56738..c76d024 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -906,8 +906,8 @@ struct snd_soc_dai_link { struct snd_pcm_hw_params *params);
/* machine stream operations */ - struct snd_soc_ops *ops; - struct snd_soc_compr_ops *compr_ops; + const struct snd_soc_ops *ops; + const struct snd_soc_compr_ops *compr_ops; };
struct snd_soc_codec_conf {
On Thu, Jan 10, 2013 at 05:06:15PM +0100, Lars-Peter Clausen wrote:
The core does not modify these fields, so they can be made const. This allows drivers to declare their op tables as const.
Applied, thanks.
Add a machine driver to support the EVAL-ADAU1X61 board connected to a Analog Devices BF5XX evaluation board.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- sound/soc/blackfin/Kconfig | 13 +++ sound/soc/blackfin/Makefile | 2 + sound/soc/blackfin/bfin-eval-adau1x61.c | 151 ++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 sound/soc/blackfin/bfin-eval-adau1x61.c
diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index 16b88f5..e5035d1 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -43,6 +43,19 @@ config SND_SOC_BFIN_EVAL_ADAU1373 Note: This driver assumes that first ADAU1373 DAI is connected to the first SPORT port on the BF5XX board.
+config SND_SOC_BFIN_EVAL_ADAU1X61 + tristate "Support for the EVAL-ADAU1X61 board on Blackfin eval boards" + depends on SND_BF5XX_I2S && I2C + select SND_BF5XX_SOC_I2S + select SND_SOC_ADAU1761 + help + Say Y if you want to add support for the Analog Devices EVAL-ADAU1X61 + board connected to one of the Blackfin evaluation boards like the + BF5XX-STAMP or BF5XX-EZKIT. + + Note: This driver assumes that the ADAU1X61 is connected to the + first SPORT port on the BF5XX board. + config SND_SOC_BFIN_EVAL_ADAV80X tristate "Support for the EVAL-ADAV80X boards on Blackfin eval boards" depends on SND_BF5XX_I2S && (SPI_MASTER || I2C) diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index 6fea1f4..e26b7a9 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -26,6 +26,7 @@ snd-ssm2602-objs := bf5xx-ssm2602.o snd-ad73311-objs := bf5xx-ad73311.o snd-ad193x-objs := bf5xx-ad193x.o snd-soc-bfin-eval-adau1373-objs := bfin-eval-adau1373.o +snd-soc-bfin-eval-adau1x61-objs := bfin-eval-adau1x61.o snd-soc-bfin-eval-adau1701-objs := bfin-eval-adau1701.o snd-soc-bfin-eval-adav80x-objs := bfin-eval-adav80x.o
@@ -35,5 +36,6 @@ obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) += snd-soc-bfin-eval-adau1373.o +obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1X61) += snd-soc-bfin-eval-adau1x61.o obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) += snd-soc-bfin-eval-adau1701.o obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAV80X) += snd-soc-bfin-eval-adav80x.o diff --git a/sound/soc/blackfin/bfin-eval-adau1x61.c b/sound/soc/blackfin/bfin-eval-adau1x61.c new file mode 100644 index 0000000..87adac8 --- /dev/null +++ b/sound/soc/blackfin/bfin-eval-adau1x61.c @@ -0,0 +1,151 @@ +/* + * Machine driver for EVAL-ADAU1x61MINIZ on Analog Devices bfin + * evaluation boards. + * + * Copyright 2011-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen lars@metafoo.de + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "../codecs/adau17x1.h" + +static const struct snd_soc_dapm_widget bfin_eval_adau1x61_dapm_widgets[] = { + SND_SOC_DAPM_LINE("In 1", NULL), + SND_SOC_DAPM_LINE("In 2", NULL), + SND_SOC_DAPM_LINE("In 3-4", NULL), + + SND_SOC_DAPM_LINE("Diff Out L", NULL), + SND_SOC_DAPM_LINE("Diff Out R", NULL), + SND_SOC_DAPM_LINE("Stereo Out", NULL), + SND_SOC_DAPM_HP("Capless HP Out", NULL), +}; + +static const struct snd_soc_dapm_route bfin_eval_adau1x61_dapm_routes[] = { + { "LAUX", NULL, "In 3-4" }, + { "RAUX", NULL, "In 3-4" }, + { "LINP", NULL, "In 1" }, + { "LINN", NULL, "In 1"}, + { "RINP", NULL, "In 2" }, + { "RINN", NULL, "In 2" }, + + { "In 1", NULL, "MICBIAS" }, + { "In 2", NULL, "MICBIAS" }, + + { "Capless HP Out", NULL, "LHP" }, + { "Capless HP Out", NULL, "RHP" }, + { "Diff Out L", NULL, "LOUT" }, + { "Diff Out R", NULL, "ROUT" }, + { "Stereo Out", NULL, "LOUT" }, + { "Stereo Out", NULL, "ROUT" }, +}; + +static int bfin_eval_adau1x61_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_dai *codec_dai = rtd->codec_dai; + int pll_rate; + int ret; + + switch (params_rate(params)) { + case 48000: + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 96000: + pll_rate = 48000 * 1024; + break; + case 44100: + case 7350: + case 11025: + case 14700: + case 22050: + case 29400: + case 88200: + pll_rate = 44100 * 1024; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_pll(codec_dai, ADAU17X1_PLL, + ADAU17X1_PLL_SRC_MCLK, 12288000, pll_rate); + if (ret) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, ADAU17X1_CLK_SRC_PLL, pll_rate, + SND_SOC_CLOCK_IN); + + return ret; +} + +static const struct snd_soc_ops bfin_eval_adau1x61_ops = { + .hw_params = bfin_eval_adau1x61_hw_params, +}; + +static struct snd_soc_dai_link bfin_eval_adau1x61_dai = { + .name = "adau1x61", + .stream_name = "adau1x61", + .cpu_dai_name = "bfin-i2s.0", + .codec_dai_name = "adau-hifi", + .platform_name = "bfin-i2s-pcm-audio", + .codec_name = "adau1761.0-0038", + .ops = &bfin_eval_adau1x61_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, +}; + +static struct snd_soc_card bfin_eval_adau1x61 = { + .name = "bfin-eval-adau1x61", + .driver_name = "eval-adau1x61", + .dai_link = &bfin_eval_adau1x61_dai, + .num_links = 1, + + .dapm_widgets = bfin_eval_adau1x61_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(bfin_eval_adau1x61_dapm_widgets), + .dapm_routes = bfin_eval_adau1x61_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(bfin_eval_adau1x61_dapm_routes), +}; + +static int bfin_eval_adau1x61_probe(struct platform_device *pdev) +{ + bfin_eval_adau1x61.dev = &pdev->dev; + + return snd_soc_register_card(&bfin_eval_adau1x61); +} + +static int bfin_eval_adau1x61_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static struct platform_driver bfin_eval_adau1x61_driver = { + .driver = { + .name = "bfin-eval-adau1x61", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, + .probe = bfin_eval_adau1x61_probe, + .remove = bfin_eval_adau1x61_remove, +}; +module_platform_driver(bfin_eval_adau1x61_driver); + +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_DESCRIPTION("ALSA SoC bfin adau1x61 driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bfin-eval-adau1x61");
Add a machine driver to support the EVAL-ADAU1X81 board connected to a Analog Devices BF5XX evaluation board.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- sound/soc/blackfin/Kconfig | 13 +++ sound/soc/blackfin/Makefile | 2 + sound/soc/blackfin/bfin-eval-adau1x81.c | 139 ++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 sound/soc/blackfin/bfin-eval-adau1x81.c
diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index e5035d1..06cb784 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -56,6 +56,19 @@ config SND_SOC_BFIN_EVAL_ADAU1X61 Note: This driver assumes that the ADAU1X61 is connected to the first SPORT port on the BF5XX board.
+config SND_SOC_BFIN_EVAL_ADAU1X81 + tristate "Support for the EVAL-ADAU1X81 boards on Blackfin eval boards" + depends on SND_BF5XX_I2S && I2C + select SND_BF5XX_SOC_I2S + select SND_SOC_ADAU1781 + help + Say Y if you want to add support for the Analog Devices EVAL-ADAU1X81 + board connected to one of the Blackfin evaluation boards like the + BF5XX-STAMP or BF5XX-EZKIT. + + Note: This driver assumes that the ADAU1X81 is connected to the + first SPORT port on the BF5XX board. + config SND_SOC_BFIN_EVAL_ADAV80X tristate "Support for the EVAL-ADAV80X boards on Blackfin eval boards" depends on SND_BF5XX_I2S && (SPI_MASTER || I2C) diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index e26b7a9..16df746 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -27,6 +27,7 @@ snd-ad73311-objs := bf5xx-ad73311.o snd-ad193x-objs := bf5xx-ad193x.o snd-soc-bfin-eval-adau1373-objs := bfin-eval-adau1373.o snd-soc-bfin-eval-adau1x61-objs := bfin-eval-adau1x61.o +snd-soc-bfin-eval-adau1x81-objs := bfin-eval-adau1x81.o snd-soc-bfin-eval-adau1701-objs := bfin-eval-adau1701.o snd-soc-bfin-eval-adav80x-objs := bfin-eval-adav80x.o
@@ -37,5 +38,6 @@ obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) += snd-soc-bfin-eval-adau1373.o obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1X61) += snd-soc-bfin-eval-adau1x61.o +obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1X81) += snd-soc-bfin-eval-adau1x81.o obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) += snd-soc-bfin-eval-adau1701.o obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAV80X) += snd-soc-bfin-eval-adav80x.o diff --git a/sound/soc/blackfin/bfin-eval-adau1x81.c b/sound/soc/blackfin/bfin-eval-adau1x81.c new file mode 100644 index 0000000..74e3459 --- /dev/null +++ b/sound/soc/blackfin/bfin-eval-adau1x81.c @@ -0,0 +1,139 @@ +/* + * Machine driver for EVAL-ADAU1x81 on Analog Devices bfin + * evaluation boards. + * + * Copyright 2011-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen lars@metafoo.de + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "../codecs/adau17x1.h" + +static const struct snd_soc_dapm_widget bfin_eval_adau1x81_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Stereo In", NULL), + SND_SOC_DAPM_LINE("Beep", NULL), + + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), +}; + +static const struct snd_soc_dapm_route bfin_eval_adau1x81_dapm_routes[] = { + { "BEEP", NULL, "Beep" }, + { "LMIC", NULL, "Stereo In" }, + { "LMIC", NULL, "Stereo In" }, + + { "Headphone", NULL, "AOUTL" }, + { "Headphone", NULL, "AOUTR" }, + { "Speaker", NULL, "SP" }, +}; + +static int bfin_eval_adau1x81_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_dai *codec_dai = rtd->codec_dai; + int pll_rate; + int ret; + + switch (params_rate(params)) { + case 48000: + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 96000: + pll_rate = 48000 * 1024; + break; + case 44100: + case 7350: + case 11025: + case 14700: + case 22050: + case 29400: + case 88200: + pll_rate = 44100 * 1024; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_pll(codec_dai, ADAU17X1_PLL, + ADAU17X1_PLL_SRC_MCLK, 12288000, pll_rate); + if (ret) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, ADAU17X1_CLK_SRC_PLL, pll_rate, + SND_SOC_CLOCK_IN); + + return ret; +} + +static const struct snd_soc_ops bfin_eval_adau1x81_ops = { + .hw_params = bfin_eval_adau1x81_hw_params, +}; + +static struct snd_soc_dai_link bfin_eval_adau1x81_dai = { + .name = "adau1x81", + .stream_name = "adau1x81", + .cpu_dai_name = "bfin-i2s.0", + .codec_dai_name = "adau-hifi", + .platform_name = "bfin-i2s-pcm-audio", + .codec_name = "adau1781.0-0038", + .ops = &bfin_eval_adau1x81_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, +}; + +static struct snd_soc_card bfin_eval_adau1x81 = { + .name = "bfin-eval-adau1x81", + .driver_name = "eval-adau1x81", + .dai_link = &bfin_eval_adau1x81_dai, + .num_links = 1, + + .dapm_widgets = bfin_eval_adau1x81_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(bfin_eval_adau1x81_dapm_widgets), + .dapm_routes = bfin_eval_adau1x81_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(bfin_eval_adau1x81_dapm_routes), +}; + +static int bfin_eval_adau1x81_probe(struct platform_device *pdev) +{ + bfin_eval_adau1x81.dev = &pdev->dev; + + return snd_soc_register_card(&bfin_eval_adau1x81); +} + +static int bfin_eval_adau1x81_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static struct platform_driver bfin_eval_adau1x81_driver = { + .driver = { + .name = "bfin-eval-adau1x81", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, + .probe = bfin_eval_adau1x81_probe, + .remove = bfin_eval_adau1x81_remove, +}; +module_platform_driver(bfin_eval_adau1x81_driver); + +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_DESCRIPTION("ALSA SoC bfin adau1x81 driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bfin-eval-adau1x81");
participants (2)
-
Lars-Peter Clausen
-
Mark Brown