From c69fa366d7569c5b98b1438b9354e0f296d72c78 Mon Sep 17 00:00:00 2001 From: Joerg Schambacher Date: Fri, 29 Jan 2021 08:26:44 +0100 Subject: [PATCH 1/2] Adds the DT-overlays to support Hifiberry AMP100 Adds new DT-overlay to control AMP100. Signed-off-by: Joerg Schambacher --- arch/arm/boot/dts/overlays/Makefile | 1 + arch/arm/boot/dts/overlays/README | 30 ++++++++- .../dts/overlays/hifiberry-amp100-overlay.dts | 64 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile index d8a3677b583e28..b6abddcb26158b 100644 --- a/arch/arm/boot/dts/overlays/Makefile +++ b/arch/arm/boot/dts/overlays/Makefile @@ -60,6 +60,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ hd44780-lcd.dtbo \ hdmi-backlight-hwhack-gpio.dtbo \ hifiberry-amp.dtbo \ + hifiberry-amp100.dtbo \ hifiberry-dac.dtbo \ hifiberry-dacplus.dtbo \ hifiberry-dacplusadc.dtbo \ diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README index 5252b1e67326a7..f3d6bf57f40b80 100644 --- a/arch/arm/boot/dts/overlays/README +++ b/arch/arm/boot/dts/overlays/README @@ -1045,8 +1045,36 @@ Load: dtoverlay=hifiberry-amp Params: +Name: hifiberry-amp100 +Info: Configures the HifiBerry AMP100 audio card +Load: dtoverlay=hifiberry-amp100,= +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=hifiberry-amp100,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + slave Force DAC+ Pro into slave mode, using Pi as + master for bit clock and frame clock. + leds_off If set to 'true' the onboard indicator LEDs + are switched off at all times. + auto_mute If set to 'true' the amplifier is automatically + muted when the DAC is not playing. + mute_ext_ctl The amplifier's HW mute control is enabled + in ALSA mixer and set to . + Will be overwritten by ALSA user settings. + + Name: hifiberry-dac -Info: Configures the HifiBerry DAC audio card +Info: Configures the HifiBerry DAC audio cards Load: dtoverlay=hifiberry-dac Params: diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts new file mode 100644 index 00000000000000..ebdef55d61109d --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts @@ -0,0 +1,64 @@ +// Definitions for HiFiBerry AMP100 +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + }; + }; + + fragment@1 { + target = <&i2s>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + clocks = <&dacpro_osc>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + hifiberry_dacplus: __overlay__ { + compatible = "hifiberry,hifiberry-dacplus"; + i2s-controller = <&i2s>; + status = "okay"; + mute-gpio = <&gpio 4 0>; + reset-gpio = <&gpio 17 0x11>; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&hifiberry_dacplus>,"hifiberry,24db_digital_gain?"; + slave = <&hifiberry_dacplus>,"hifiberry-dacplus,slave?"; + leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?"; + mute_ext_ctl = <&hifiberry_dacplus>,"hifiberry-dacplus,mute_ext_ctl:0"; + auto_mute = <&hifiberry_dacplus>,"hifiberry-dacplus,auto_mute?"; + }; +}; From b6fca0938cafeec4c58bb41f43d9debcff96e217 Mon Sep 17 00:00:00 2001 From: Joerg Schambacher Date: Fri, 29 Jan 2021 16:16:39 +0100 Subject: [PATCH 2/2] Enhances the Hifiberry DAC+ driver for Hifiberry AMP100 support Adds the necessary GPIO handling and ALSA mixer extensions. Also fixes a problem with the PLL/CLK control when switching sample rates. Thanks to Clive Messer for the support! Signed-off-by: Joerg Schambacher --- sound/soc/bcm/hifiberry_dacplus.c | 124 ++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 13 deletions(-) diff --git a/sound/soc/bcm/hifiberry_dacplus.c b/sound/soc/bcm/hifiberry_dacplus.c index 2f1b1ef4191a58..66a4198a1a3040 100644 --- a/sound/soc/bcm/hifiberry_dacplus.c +++ b/sound/soc/bcm/hifiberry_dacplus.c @@ -1,10 +1,10 @@ /* - * ASoC Driver for HiFiBerry DAC+ / DAC Pro + * ASoC Driver for HiFiBerry DAC+ / DAC Pro / AMP100 * * Author: Daniel Matuschek, Stuart MacLean * Copyright 2014-2015 * based on code by Florian Meier - * Headphone added by Joerg Schambacher, joerg@i2audio.com + * Headphone/AMP100 Joerg Schambacher * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,6 +17,8 @@ */ #include +#include +#include <../drivers/gpio/gpiolib.h> #include #include #include @@ -53,6 +55,47 @@ static bool slave; static bool snd_rpi_hifiberry_is_dacpro; static bool digital_gain_0db_limit = true; static bool leds_off; +static bool auto_mute; +static int mute_ext_ctl; +static int mute_ext; +static struct gpio_desc *snd_mute_gpio; +static struct gpio_desc *snd_reset_gpio; +static struct snd_soc_card snd_rpi_hifiberry_dacplus; + +static int snd_rpi_hifiberry_dacplus_mute_set(int mute) +{ + gpiod_set_value_cansleep(snd_mute_gpio, mute); + return 1; +} + +static int snd_rpi_hifiberry_dacplus_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = mute_ext; + + return 0; +} + +static int snd_rpi_hifiberry_dacplus_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (mute_ext == ucontrol->value.integer.value[0]) + return 0; + + mute_ext = ucontrol->value.integer.value[0]; + + return snd_rpi_hifiberry_dacplus_mute_set(mute_ext); +} + +static const char * const mute_text[] = {"Play", "Mute"}; +static const struct soc_enum hb_dacplus_opt_mute_enum = + SOC_ENUM_SINGLE_EXT(2, mute_text); + +static const struct snd_kcontrol_new hb_dacplus_opt_mute_controls[] = { + SOC_ENUM_EXT("Mute(ext)", hb_dacplus_opt_mute_enum, + snd_rpi_hifiberry_dacplus_mute_get, + snd_rpi_hifiberry_dacplus_mute_put), +}; static void snd_rpi_hifiberry_dacplus_select_clk(struct snd_soc_component *component, int clk_id) @@ -68,6 +111,7 @@ static void snd_rpi_hifiberry_dacplus_select_clk(struct snd_soc_component *compo snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04); break; } + usleep_range(2000, 2100); } static void snd_rpi_hifiberry_dacplus_clk_gpio(struct snd_soc_component *component) @@ -85,13 +129,6 @@ static bool snd_rpi_hifiberry_dacplus_is_sclk(struct snd_soc_component *componen return (!(sck & 0x40)); } -static bool snd_rpi_hifiberry_dacplus_is_sclk_sleep( - struct snd_soc_component *component) -{ - msleep(2); - return snd_rpi_hifiberry_dacplus_is_sclk(component); -} - static bool snd_rpi_hifiberry_dacplus_is_pro_card(struct snd_soc_component *component) { bool isClk44EN, isClk48En, isNoClk; @@ -99,13 +136,13 @@ static bool snd_rpi_hifiberry_dacplus_is_pro_card(struct snd_soc_component *comp snd_rpi_hifiberry_dacplus_clk_gpio(component); snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK44EN); - isClk44EN = snd_rpi_hifiberry_dacplus_is_sclk_sleep(component); + isClk44EN = snd_rpi_hifiberry_dacplus_is_sclk(component); snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK); - isNoClk = snd_rpi_hifiberry_dacplus_is_sclk_sleep(component); + isNoClk = snd_rpi_hifiberry_dacplus_is_sclk(component); snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK48EN); - isClk48En = snd_rpi_hifiberry_dacplus_is_sclk_sleep(component); + isClk48En = snd_rpi_hifiberry_dacplus_is_sclk(component); return (isClk44EN && isClk48En && !isNoClk); } @@ -149,6 +186,7 @@ static int snd_rpi_hifiberry_dacplus_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_component *component = rtd->codec_dai->component; struct pcm512x_priv *priv; + struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus; if (slave) snd_rpi_hifiberry_is_dacpro = false; @@ -187,6 +225,20 @@ static int snd_rpi_hifiberry_dacplus_init(struct snd_soc_pcm_runtime *rtd) if (ret < 0) dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); } + if (snd_reset_gpio) { + gpiod_set_value_cansleep(snd_reset_gpio, 0); + msleep(1); + gpiod_set_value_cansleep(snd_reset_gpio, 1); + msleep(1); + gpiod_set_value_cansleep(snd_reset_gpio, 0); + } + + if (mute_ext_ctl) + snd_soc_add_card_controls(card, hb_dacplus_opt_mute_controls, + ARRAY_SIZE(hb_dacplus_opt_mute_controls)); + + if (snd_mute_gpio) + gpiod_set_value_cansleep(snd_mute_gpio, mute_ext); return 0; } @@ -254,6 +306,8 @@ static int snd_rpi_hifiberry_dacplus_startup( struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_component *component = rtd->codec_dai->component; + if (auto_mute) + gpiod_set_value_cansleep(snd_mute_gpio, 0); if (leds_off) return 0; snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); @@ -267,6 +321,8 @@ static void snd_rpi_hifiberry_dacplus_shutdown( struct snd_soc_component *component = rtd->codec_dai->component; snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + if (auto_mute) + gpiod_set_value_cansleep(snd_mute_gpio, 1); } /* machine stream operations */ @@ -342,6 +398,8 @@ static int snd_rpi_hifiberry_dacplus_probe(struct platform_device *pdev) struct device_node *tpa_node; struct property *tpa_prop; struct of_changeset ocs; + struct property *pp; + int tmp; /* probe for head phone amp */ ret = hb_hp_detect(); @@ -396,6 +454,39 @@ static int snd_rpi_hifiberry_dacplus_probe(struct platform_device *pdev) "hifiberry-dacplus,slave"); leds_off = of_property_read_bool(pdev->dev.of_node, "hifiberry-dacplus,leds_off"); + auto_mute = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplus,auto_mute"); + + /* + * check for HW MUTE as defined in DT-overlay + * active high, therefore default to HIGH to MUTE + */ + snd_mute_gpio = devm_gpiod_get_optional(&pdev->dev, + "mute", GPIOD_OUT_HIGH); + if (IS_ERR(snd_mute_gpio)) { + dev_err(&pdev->dev, "Can't allocate GPIO (HW-MUTE)"); + return PTR_ERR(snd_mute_gpio); + } + + /* add ALSA control if requested in DT-overlay (AMP100) */ + pp = of_find_property(pdev->dev.of_node, + "hifiberry-dacplus,mute_ext_ctl", &tmp); + if (pp) { + if (!of_property_read_u32(pdev->dev.of_node, + "hifiberry-dacplus,mute_ext_ctl", &mute_ext)) { + /* ALSA control will be used */ + mute_ext_ctl = 1; + } + } + + /* check for HW RESET (AMP100) */ + snd_reset_gpio = devm_gpiod_get_optional(&pdev->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(snd_reset_gpio)) { + dev_err(&pdev->dev, "Can't allocate GPIO (HW-RESET)"); + return PTR_ERR(snd_reset_gpio); + } + } ret = devm_snd_soc_register_card(&pdev->dev, @@ -403,7 +494,14 @@ static int snd_rpi_hifiberry_dacplus_probe(struct platform_device *pdev) if (ret && ret != -EPROBE_DEFER) dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret); - + if (!ret) { + if (snd_mute_gpio) + dev_info(&pdev->dev, "GPIO%i for HW-MUTE selected", + gpio_chip_hwgpio(snd_mute_gpio)); + if (snd_reset_gpio) + dev_info(&pdev->dev, "GPIO%i for HW-RESET selected", + gpio_chip_hwgpio(snd_reset_gpio)); + } return ret; }