From 1aebaa90d468875109f0bf37cd89c7cfd9ac69bf Mon Sep 17 00:00:00 2001 From: Joerg Schambacher Date: Fri, 29 Jan 2021 16:16:39 +0100 Subject: [PATCH] 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(-) --- 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_se 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 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 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_ini { struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->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_ini 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_sta struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->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_sh struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->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_pro 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_pro "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_pro 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; }