From ff28ec6d80edd94accabb06138b6175cf40a51a0 Mon Sep 17 00:00:00 2001 From: j-schambacher Date: Mon, 13 Jul 2026 09:11:37 +0200 Subject: [PATCH 1/4] ASoC: rename hifiberry_studio_dac8x.c to hifiberry_studio.c The driver is going to add support for other HiFiBerry Studio-family cards (e.g. AES/SPDIF variants) that share the same onboard controller/firmware and I2C register protocol as the DAC8x cards. Rename the file and its DAC8x-specific identifiers to the more generic "hifiberry_studio" name in preparation for that, without changing any behaviour. No functional change. Signed-off-by: j-schambacher --- sound/soc/bcm/Kconfig | 2 +- sound/soc/bcm/Makefile | 4 +- ...erry_studio_dac8x.c => hifiberry_studio.c} | 66 +++++++++---------- 3 files changed, 36 insertions(+), 36 deletions(-) rename sound/soc/bcm/{hifiberry_studio_dac8x.c => hifiberry_studio.c} (93%) diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig index 283d2fb26f7782..c31a698be9d7d5 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -102,7 +102,7 @@ config SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP help Say Y or M if you want to add support for HifiBerry DSP-DAC. -config SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X +config SND_BCM2708_SOC_HIFIBERRY_STUDIO tristate "Support for HifiBerry Studio DAC8x soundcards" help Say Y or M if you want to add support for diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile index 0ce245bf8a0a97..f27671f4c0577e 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -24,7 +24,7 @@ snd-soc-hifiberry-dacplushd-objs := hifiberry_dacplushd.o snd-soc-hifiberry-dacplusadc-objs := hifiberry_dacplusadc.o snd-soc-hifiberry-dacplusadcpro-objs := hifiberry_dacplusadcpro.o snd-soc-hifiberry-dacplusdsp-objs := hifiberry_dacplusdsp.o -snd-soc-hifiberry-studio-dac8x-objs := hifiberry_studio_dac8x.o +snd-soc-hifiberry-studio-objs := hifiberry_studio.o snd-soc-justboom-both-objs := justboom-both.o snd-soc-justboom-dac-objs := justboom-dac.o snd-soc-rpi-cirrus-objs := rpi-cirrus.o @@ -59,7 +59,7 @@ obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD) += snd-soc-hifiberry-dacplushd obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC) += snd-soc-hifiberry-dacplusadc.o obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO) += snd-soc-hifiberry-dacplusadcpro.o obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP) += snd-soc-hifiberry-dacplusdsp.o -obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X) += snd-soc-hifiberry-studio-dac8x.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO) += snd-soc-hifiberry-studio.o obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH) += snd-soc-justboom-both.o obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o obj-$(CONFIG_SND_BCM2708_SOC_RPI_CIRRUS) += snd-soc-rpi-cirrus.o diff --git a/sound/soc/bcm/hifiberry_studio_dac8x.c b/sound/soc/bcm/hifiberry_studio.c similarity index 93% rename from sound/soc/bcm/hifiberry_studio_dac8x.c rename to sound/soc/bcm/hifiberry_studio.c index a4553c2d2432ea..0f4fff7d94d7e7 100644 --- a/sound/soc/bcm/hifiberry_studio_dac8x.c +++ b/sound/soc/bcm/hifiberry_studio.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * hifiberry_studio_dac8x.c -- driver for more complex + * hifiberry_studio.c -- driver for more complex * multichannel soundcards with own onboard firmware. * * Copyright (C) 2026 HiFiBerry @@ -124,7 +124,7 @@ #define MASK_32_BIT_SF 0x03 /* struct definition for easier access to firmware registers */ -struct hb_studio_dac8x_regs_t { +struct hb_studio_regs_t { unsigned char firmware_major; unsigned char firmware_minor; unsigned char firmware_subversion; @@ -196,14 +196,14 @@ struct hb_studio_dac8x_regs_t { }; -static struct snd_soc_card snd_rpi_hifiberry_studio_dac8x; +static struct snd_soc_card snd_rpi_hifiberry_studio; static struct i2c_client *hb_uni_i2c_client; struct hb_uni_private { struct regmap *regmap; uuid_t uuid; unsigned int sample_bits; unsigned int current_rate; - struct hb_studio_dac8x_regs_t card_info; + struct hb_studio_regs_t card_info; }; static struct hb_uni_private *priv; @@ -514,7 +514,7 @@ static const struct snd_kcontrol_new adc_controls_single[] = { ENUM_CTL_SINGLE("Clipping Attenuation Capture Volume", hb_uni_rec_enum_ctls[0]), }; -static int snd_rpi_hifiberry_studio_dac8x_hw_params( +static int snd_rpi_hifiberry_studio_hw_params( struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -625,16 +625,16 @@ static int snd_rpi_hifiberry_studio_dac8x_hw_params( return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); } -static const struct snd_soc_ops snd_rpi_hifiberry_studio_dac8x_ops = { - .hw_params = snd_rpi_hifiberry_studio_dac8x_hw_params, +static const struct snd_soc_ops snd_rpi_hifiberry_studio_ops = { + .hw_params = snd_rpi_hifiberry_studio_hw_params, }; -SND_SOC_DAILINK_DEFS(hifiberry_studio_dac8x, +SND_SOC_DAILINK_DEFS(hifiberry_studio, DAILINK_COMP_ARRAY(COMP_EMPTY()), DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")), DAILINK_COMP_ARRAY(COMP_EMPTY())); -static int hifiberry_studio_dac8x_init(struct snd_soc_pcm_runtime *rtd) +static int hifiberry_studio_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); struct snd_soc_card *card = rtd->card; @@ -676,26 +676,26 @@ static int hifiberry_studio_dac8x_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static struct snd_soc_dai_link snd_rpi_hifiberry_studio_dac8x_dai[] = { +static struct snd_soc_dai_link snd_rpi_hifiberry_studio_dai[] = { { .name = "HiFiBerry Studio DAC8x", .stream_name = "HifiBerry Studio HiFi", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, - .init = hifiberry_studio_dac8x_init, - .ops = &snd_rpi_hifiberry_studio_dac8x_ops, - SND_SOC_DAILINK_REG(hifiberry_studio_dac8x), + .init = hifiberry_studio_init, + .ops = &snd_rpi_hifiberry_studio_ops, + SND_SOC_DAILINK_REG(hifiberry_studio), }, }; /* audio machine driver */ -static struct snd_soc_card snd_rpi_hifiberry_studio_dac8x = { +static struct snd_soc_card snd_rpi_hifiberry_studio = { .name = "Hifiberry Studio DAC8x", .driver_name = "HifiberryStudio", .owner = THIS_MODULE, - .dai_link = snd_rpi_hifiberry_studio_dac8x_dai, - .num_links = ARRAY_SIZE(snd_rpi_hifiberry_studio_dac8x_dai), + .dai_link = snd_rpi_hifiberry_studio_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_studio_dai), }; static int hb_uni_read_card_info(struct platform_device *pdev) @@ -777,10 +777,10 @@ static int hb_uni_read_card_info(struct platform_device *pdev) switch (cpu_to_be32(*(unsigned int *)&priv->card_info.uuid)) { case 0x74e7ae95: if (card_is_clk_provider) - snd_rpi_hifiberry_studio_dac8x.name = + snd_rpi_hifiberry_studio.name = "HiFiBerry Studio DAC8x Pro"; else - snd_rpi_hifiberry_studio_dac8x.name = + snd_rpi_hifiberry_studio.name = "HiFiBerry Studio DAC8x"; break; default: @@ -800,7 +800,7 @@ static int hb_uni_add_card_controls(struct platform_device *pdev) { int ret; - ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x, + ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, hb_uni_gen_controls_single, ARRAY_SIZE(hb_uni_gen_controls_single)); if (ret < 0) { @@ -808,7 +808,7 @@ static int hb_uni_add_card_controls(struct platform_device *pdev) "snd_soc_add_card_controls() failed: %d\n", ret); return ret; } - ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x, + ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, hb_uni_play_controls_single, ARRAY_SIZE(hb_uni_play_controls_single) / 9 * (priv->card_info.num_of_output_ch + 1)); @@ -820,7 +820,7 @@ static int hb_uni_add_card_controls(struct platform_device *pdev) /* add optional ADC controls if inputs detected */ if (priv->card_info.num_of_input_ch > 0) { - ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x, + ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, hb_uni_rec_controls_single, ARRAY_SIZE(hb_uni_rec_controls_single) / 8 * priv->card_info.num_of_input_ch); @@ -828,7 +828,7 @@ static int hb_uni_add_card_controls(struct platform_device *pdev) dev_err(&pdev->dev, "snd_soc_add_card_controls() failed: %d\n", ret); } - ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x, + ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, adc_controls_single, ARRAY_SIZE(adc_controls_single)); if (ret < 0) { @@ -877,7 +877,7 @@ static int hb_controller_probe(struct platform_device *pdev) return ret; }; -static int snd_rpi_hifiberry_studio_dac8x_probe(struct platform_device *pdev) +static int snd_rpi_hifiberry_studio_probe(struct platform_device *pdev) { int ret = 0; @@ -886,13 +886,13 @@ static int snd_rpi_hifiberry_studio_dac8x_probe(struct platform_device *pdev) if (ret < 0) return ret; - snd_rpi_hifiberry_studio_dac8x.dev = &pdev->dev; + snd_rpi_hifiberry_studio.dev = &pdev->dev; if (pdev->dev.of_node) { struct device_node *i2s_node; struct snd_soc_dai_link *dai; - dai = &snd_rpi_hifiberry_studio_dac8x_dai[0]; + dai = &snd_rpi_hifiberry_studio_dai[0]; i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0); @@ -905,7 +905,7 @@ static int snd_rpi_hifiberry_studio_dac8x_probe(struct platform_device *pdev) } ret = devm_snd_soc_register_card(&pdev->dev, - &snd_rpi_hifiberry_studio_dac8x); + &snd_rpi_hifiberry_studio); if (ret && ret != -EPROBE_DEFER) dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", ret); @@ -916,22 +916,22 @@ static int snd_rpi_hifiberry_studio_dac8x_probe(struct platform_device *pdev) return ret; } -static const struct of_device_id snd_rpi_hifiberry_studio_dac8x_of_match[] = { +static const struct of_device_id snd_rpi_hifiberry_studio_of_match[] = { { .compatible = "hifiberry,hifiberry-studio-dac8x", }, {}, }; -MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_studio_dac8x_of_match); +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_studio_of_match); -static struct platform_driver snd_rpi_hifiberry_studio_dac8x_driver = { +static struct platform_driver snd_rpi_hifiberry_studio_driver = { .driver = { - .name = "snd-rpi-hifiberry-studio-dac8x", + .name = "snd-rpi-hifiberry-studio", .owner = THIS_MODULE, - .of_match_table = snd_rpi_hifiberry_studio_dac8x_of_match, + .of_match_table = snd_rpi_hifiberry_studio_of_match, }, - .probe = snd_rpi_hifiberry_studio_dac8x_probe, + .probe = snd_rpi_hifiberry_studio_probe, }; -module_platform_driver(snd_rpi_hifiberry_studio_dac8x_driver); +module_platform_driver(snd_rpi_hifiberry_studio_driver); MODULE_AUTHOR("Joerg Schambacher "); MODULE_DESCRIPTION("HiFiBerry Studio DAC8x Soundcard Driver"); From 05611c44fed491e7a46b7f08bf48f021bb2e833b Mon Sep 17 00:00:00 2001 From: j-schambacher Date: Mon, 13 Jul 2026 09:14:22 +0200 Subject: [PATCH 2/4] ASoC: hifiberry_studio: add multi-card and Digi/AES support Generalize the driver to support different cards that share the same onboard controller/firmware and I2C register protocol: the existing DAC8x/ADC8x analog cards and the new Studio Digi/AES card. - Split the onboard controller handling into a separate I2C client driver (compatible "hifiberry,hb-studio-ctrl") that creates a shared regmap and reads firmware/hardware capabilities at probe time. The card type is decoded from the last 4 bytes of the controller's UUID and used by the platform/machine driver. - It now registers different ALSA controls depending on the detected card. - Add AES input-rate detection and locking: capture on an AES card requires DIR/RX mode as the Pi's I2S interface can only run at the same sample rate for either direction. - Add GPIO7 interrupt handling (FS-change / PLL-lost) for Digi/AES cards: the IRQ stops any active playback/capture substreams via snd_pcm_stop() under a spinlock shared with the hw_params/startup/ shutdown paths. The IRQ is only requested for AES-type cards, decided automatically from the detected card type. - Add the "hifiberry,hifiberry-studio" compatible string alongside the existing "hifiberry,hifiberry-studio-dac8x" one, used by card variants that don't need the DAC8x-specific compatible string for backwards compatibility. Signed-off-by: j-schambacher --- sound/soc/bcm/hifiberry_studio.c | 747 +++++++++++++++++++++++-------- 1 file changed, 570 insertions(+), 177 deletions(-) diff --git a/sound/soc/bcm/hifiberry_studio.c b/sound/soc/bcm/hifiberry_studio.c index 0f4fff7d94d7e7..ded1e2b402736f 100644 --- a/sound/soc/bcm/hifiberry_studio.c +++ b/sound/soc/bcm/hifiberry_studio.c @@ -28,6 +28,11 @@ #include #include #include +#include +#include +#include +#include +#include /* register definitions and firmware settings */ #define FIRMWARE_MAJOR 0x00 @@ -60,6 +65,10 @@ #define CARD_NOERR 0x34 #define CARD_CLK_OPTIONS 0x35 #define CARD_CLOCK_MODE 0x36 +#define CARD_CLK_ACT 0x37 +#define CARD_CLK_OVRWR 0x38 +#define CARD_STREAM_STATUS 0x39 +#define CARD_DIR_FS 0x3A #define DAC_STATE 0x40 #define DAC_CLOCK_SOURCE 0x41 #define DAC_SYS_CLK 0x42 @@ -79,7 +88,6 @@ #define VOL_CH6 0x57 #define VOL_CH7 0x58 #define MUTE_OUTPUTS 0x59 -#define ADC_INPUT_MODE 0x70 #define ADC_STATE 0x70 #define ADC_CLOCK_SOURCE 0x71 #define ADC_SYS_CLK 0x72 @@ -123,6 +131,15 @@ #define MASK_24_BIT_SF 0x02 #define MASK_32_BIT_SF 0x03 +/* Card types */ +#define DACADC 0x00 +#define AES 0x01 +#define AMP 0x02 + +/* Stream status */ +#define CAPTURE 0x01 +#define PLAY 0x10 + /* struct definition for easier access to firmware registers */ struct hb_studio_regs_t { unsigned char firmware_major; @@ -150,7 +167,10 @@ struct hb_studio_regs_t { unsigned char card_noerr; // 0x34 unsigned char card_clk_options; // 0x35 unsigned char card_clk_mode; // 0x36 - unsigned char res3[9]; // 0x37 + unsigned char card_clk_act; // 0x37 + unsigned char card_clk_ovrwr; // 0x38 + unsigned char card_stream_status; // 0x39 + unsigned char res3[6]; // 0x3a unsigned char dac_state; // 0x40 unsigned char dac_clock_source; // 0x41 unsigned char dac_sys_clk; // 0x42 @@ -172,7 +192,6 @@ struct hb_studio_regs_t { unsigned char vol_ch7; // 0x58 unsigned char mute_outputs; // 0x59 unsigned char res5[22]; // 0x5a- 0x6f - unsigned char adc_input_mode; // 0x70 unsigned char adc_state; // 0x70 unsigned char adc_clock_source; // 0x71 unsigned char adc_sys_clk; // 0x72 @@ -195,21 +214,27 @@ struct hb_studio_regs_t { unsigned char mute_inputs; // 0x89 }; - static struct snd_soc_card snd_rpi_hifiberry_studio; -static struct i2c_client *hb_uni_i2c_client; -struct hb_uni_private { +static struct i2c_client *hb_studio_i2c_client; +struct hb_studio_private { struct regmap *regmap; uuid_t uuid; unsigned int sample_bits; unsigned int current_rate; + unsigned int allowed_rate; + unsigned int clk_ovrwr; struct hb_studio_regs_t card_info; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + spinlock_t stream_lock; + struct work_struct error_work; + int card_type; }; -static struct hb_uni_private *priv; +static struct hb_studio_private *priv; static bool card_is_clk_provider; -static bool hb_uni_volatile_reg(struct device *dev, unsigned int reg) +static bool hb_studio_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case CARD_BUSY: @@ -217,6 +242,8 @@ static bool hb_uni_volatile_reg(struct device *dev, unsigned int reg) case DAC_STATE: case DAC_CLOCK_SOURCE: case DAC_SYS_CLK: + case CARD_CLK_ACT: + case CARD_DIR_FS: case MASTER_VOL: case VOL_CH0: case VOL_CH1: @@ -240,7 +267,7 @@ static bool hb_uni_volatile_reg(struct device *dev, unsigned int reg) } } -static bool hb_uni_readable_reg(struct device *dev, unsigned int reg) +static bool hb_studio_readable_reg(struct device *dev, unsigned int reg) { switch (reg) { case FIRMWARE_MAJOR: @@ -264,6 +291,8 @@ static bool hb_uni_readable_reg(struct device *dev, unsigned int reg) case CARD_BUSY: case CARD_RESET: case CARD_CLOCK_MODE: + case CARD_CLK_ACT: + case CARD_DIR_FS: case DAC_CLOCK_SOURCE: case DAC_SYS_CLK: case DAC_SAMPLE_FORMAT: @@ -287,13 +316,13 @@ static bool hb_uni_readable_reg(struct device *dev, unsigned int reg) } } -static const struct regmap_config hb_uni_regmap = { +static const struct regmap_config hb_studio_regmap = { .reg_bits = 8, .val_bits = 8, .max_register = 0xff, .cache_type = REGCACHE_RBTREE, - .volatile_reg = hb_uni_volatile_reg, - .readable_reg = hb_uni_readable_reg, + .volatile_reg = hb_studio_volatile_reg, + .readable_reg = hb_studio_readable_reg, }; static const DECLARE_TLV_DB_MINMAX(adc_att_tlv, -600, -300); @@ -311,8 +340,9 @@ static const char * const dac_filter_texts[] = { static const char * const adc_att_texts[] = { "Clip att. off", "-3dB", "-4dB", "-5dB", "-6dB", }; +static const char * const dix_clk_texts[] = {"TX", "RX"}; -struct hb_uni_vol_control_single { +struct hb_studio_vol_control_single { unsigned int reg; unsigned int shift; int min; @@ -321,10 +351,17 @@ struct hb_uni_vol_control_single { const unsigned int *tlv; }; -static int hb_uni_vol_info_single(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) +static const char * const samplerate_texts[] = { + "5512Hz", "8kHz", "11.025kHz", "16kHz", + "22.050kHz", "32kHz", "44.1kHz", "48kHz", "64kHz", + "88.2kHz", "96kHz", "176.4kHz", "192kHz", "352.8kHz", "384kHz", + "na" +}; + +static int hb_studio_vol_info_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { - struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value; + struct hb_studio_vol_control_single *ctl = (void *)kcontrol->private_value; uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; // mono @@ -334,10 +371,10 @@ static int hb_uni_vol_info_single(struct snd_kcontrol *kcontrol, return 0; } -static int hb_uni_vol_get_single(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int hb_studio_vol_get_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { - struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value; + struct hb_studio_vol_control_single *ctl = (void *)kcontrol->private_value; unsigned int val; regmap_read(priv->regmap, ctl->reg, &val); @@ -348,10 +385,10 @@ static int hb_uni_vol_get_single(struct snd_kcontrol *kcontrol, return 0; } -static int hb_uni_vol_put_single(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int hb_studio_vol_put_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { - struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value; + struct hb_studio_vol_control_single *ctl = (void *)kcontrol->private_value; unsigned int val = ucontrol->value.integer.value[0]; unsigned int new; @@ -362,7 +399,7 @@ static int hb_uni_vol_put_single(struct snd_kcontrol *kcontrol, return 0; } -struct hb_uni_enum_control { +struct hb_studio_enum_control { unsigned int reg; unsigned int shift; unsigned int mask; @@ -370,10 +407,10 @@ struct hb_uni_enum_control { unsigned int items; }; -static int hb_uni_enum_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) +static int hb_studio_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { - struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value; + struct hb_studio_enum_control *ctl = (void *)kcontrol->private_value; uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; @@ -389,10 +426,10 @@ static int hb_uni_enum_info(struct snd_kcontrol *kcontrol, return 0; } -static int hb_uni_enum_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int hb_studio_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { - struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value; + struct hb_studio_enum_control *ctl = (void *)kcontrol->private_value; unsigned int val; regmap_read(priv->regmap, ctl->reg, &val); @@ -406,10 +443,10 @@ static int hb_uni_enum_get(struct snd_kcontrol *kcontrol, return 0; } -static int hb_uni_enum_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int hb_studio_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { - struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value; + struct hb_studio_enum_control *ctl = (void *)kcontrol->private_value; unsigned int val = ucontrol->value.enumerated.item[0]; if (val >= ctl->items) @@ -422,34 +459,103 @@ static int hb_uni_enum_put(struct snd_kcontrol *kcontrol, return 0; } +/* + * Read the AES input rate. + * Returns the rate in Hz, or 0 if no valid input is present. + * Works in TX and RX clock modes. + */ +static int hb_studio_input_rate_hz(struct hb_studio_private *p) +{ + unsigned int raw = 0; + int trials = 5; + + regmap_write(p->regmap, CARD_DIR_FS, 0x00); + do { + usleep_range(1000, 2000); + regmap_read(p->regmap, CARD_DIR_FS, &raw); + } while ((raw & 0x80) && --trials); + + switch (raw & 0x0f) { + case 0x08: return 44100; + case 0x09: return 48000; + case 0x0b: return 88200; + case 0x0c: return 96000; + case 0x0e: return 176400; + case 0x0f: return 192000; + default: return 0; + } +} + +static int hb_studio_samplerate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hb_studio_enum_control *ctl = (void *)kcontrol->private_value; + unsigned int idx; + + switch (hb_studio_input_rate_hz(priv)) { + case 44100: + idx = 6; + break; + case 48000: + idx = 7; + break; + case 88200: + idx = 9; + break; + case 96000: + idx = 10; + break; + case 176400: + idx = 11; + break; + case 192000: + idx = 12; + break; + default: + idx = ctl->items - 1; /* na */ + break; + } + + ucontrol->value.enumerated.item[0] = idx; + return 0; +} + #define VOL_CTL_SINGLE(kname, controls, ktlv) {\ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = kname, \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = ktlv, \ - .info = hb_uni_vol_info_single, \ - .get = hb_uni_vol_get_single, \ - .put = hb_uni_vol_put_single, \ + .info = hb_studio_vol_info_single, \ + .get = hb_studio_vol_get_single, \ + .put = hb_studio_vol_put_single, \ .private_value = (unsigned long)&controls, } #define ENUM_CTL_SINGLE(kname, controls) {\ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = kname, \ - .info = hb_uni_enum_info, \ - .get = hb_uni_enum_get, \ - .put = hb_uni_enum_put, \ + .info = hb_studio_enum_info, \ + .get = hb_studio_enum_get, \ + .put = hb_studio_enum_put, \ .private_value = (unsigned long)&controls, } #define ENUM_CTL_SINGLE_RO(kname, controls) {\ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = kname, \ - .info = hb_uni_enum_info, \ - .get = hb_uni_enum_get, \ + .info = hb_studio_enum_info, \ + .get = hb_studio_enum_get, \ .put = NULL, \ .private_value = (unsigned long)&controls, } -static const struct hb_uni_vol_control_single hb_uni_vol_ctls_single[] = { +#define ENUM_CTL_SINGLE_RO_GET(kname, controls, getfn) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = kname, \ + .info = hb_studio_enum_info, \ + .get = getfn, \ + .put = NULL, \ + .private_value = (unsigned long)&controls, } + +static const struct hb_studio_vol_control_single hb_studio_vol_ctls_single[] = { { MASTER_VOL, 0, 0, 254, true, volume_tlv }, { VOL_CH0, 0, 0, 206, true, spkr_tlv }, { VOL_CH1, 0, 0, 206, true, spkr_tlv }, @@ -461,7 +567,7 @@ static const struct hb_uni_vol_control_single hb_uni_vol_ctls_single[] = { { VOL_CH7, 0, 0, 206, true, spkr_tlv }, }; -static const struct hb_uni_vol_control_single hb_uni_gain_ctls_single[] = { +static const struct hb_studio_vol_control_single hb_studio_gain_ctls_single[] = { { GAIN_CH0, 0, 0, 104, false, gain_tlv }, { GAIN_CH1, 0, 0, 104, false, gain_tlv }, { GAIN_CH2, 0, 0, 104, false, gain_tlv }, @@ -472,46 +578,69 @@ static const struct hb_uni_vol_control_single hb_uni_gain_ctls_single[] = { { GAIN_CH7, 0, 0, 104, false, gain_tlv }, }; -static const struct snd_kcontrol_new hb_uni_play_controls_single[] = { - VOL_CTL_SINGLE("Master Playback Volume", hb_uni_vol_ctls_single[0], volume_tlv), - VOL_CTL_SINGLE("Output Ch0 Playback Volume", hb_uni_vol_ctls_single[1], spkr_tlv), - VOL_CTL_SINGLE("Output Ch1 Playback Volume", hb_uni_vol_ctls_single[2], spkr_tlv), - VOL_CTL_SINGLE("Output Ch2 Playback Volume", hb_uni_vol_ctls_single[3], spkr_tlv), - VOL_CTL_SINGLE("Output Ch3 Playback Volume", hb_uni_vol_ctls_single[4], spkr_tlv), - VOL_CTL_SINGLE("Output Ch4 Playback Volume", hb_uni_vol_ctls_single[5], spkr_tlv), - VOL_CTL_SINGLE("Output Ch5 Playback Volume", hb_uni_vol_ctls_single[6], spkr_tlv), - VOL_CTL_SINGLE("Output Ch6 Playback Volume", hb_uni_vol_ctls_single[7], spkr_tlv), - VOL_CTL_SINGLE("Output Ch7 Playback Volume", hb_uni_vol_ctls_single[8], spkr_tlv), +static const struct snd_kcontrol_new hb_studio_play_controls_single[] = { + VOL_CTL_SINGLE("Master Playback Volume", hb_studio_vol_ctls_single[0], volume_tlv), + VOL_CTL_SINGLE("Output Ch0 Playback Volume", hb_studio_vol_ctls_single[1], spkr_tlv), + VOL_CTL_SINGLE("Output Ch1 Playback Volume", hb_studio_vol_ctls_single[2], spkr_tlv), + VOL_CTL_SINGLE("Output Ch2 Playback Volume", hb_studio_vol_ctls_single[3], spkr_tlv), + VOL_CTL_SINGLE("Output Ch3 Playback Volume", hb_studio_vol_ctls_single[4], spkr_tlv), + VOL_CTL_SINGLE("Output Ch4 Playback Volume", hb_studio_vol_ctls_single[5], spkr_tlv), + VOL_CTL_SINGLE("Output Ch5 Playback Volume", hb_studio_vol_ctls_single[6], spkr_tlv), + VOL_CTL_SINGLE("Output Ch6 Playback Volume", hb_studio_vol_ctls_single[7], spkr_tlv), + VOL_CTL_SINGLE("Output Ch7 Playback Volume", hb_studio_vol_ctls_single[8], spkr_tlv), }; -static const struct snd_kcontrol_new hb_uni_rec_controls_single[] = { - VOL_CTL_SINGLE("Input Ch0 Capture Volume", hb_uni_gain_ctls_single[0], gain_tlv), - VOL_CTL_SINGLE("Input Ch1 Capture Volume", hb_uni_gain_ctls_single[1], gain_tlv), - VOL_CTL_SINGLE("Input Ch2 Capture Volume", hb_uni_gain_ctls_single[2], gain_tlv), - VOL_CTL_SINGLE("Input Ch3 Capture Volume", hb_uni_gain_ctls_single[3], gain_tlv), - VOL_CTL_SINGLE("Input Ch4 Capture Volume", hb_uni_gain_ctls_single[4], gain_tlv), - VOL_CTL_SINGLE("Input Ch5 Capture Volume", hb_uni_gain_ctls_single[5], gain_tlv), - VOL_CTL_SINGLE("Input Ch6 Capture Volume", hb_uni_gain_ctls_single[6], gain_tlv), - VOL_CTL_SINGLE("Input Ch7 Capture Volume", hb_uni_gain_ctls_single[7], gain_tlv), +static const struct snd_kcontrol_new hb_studio_rec_controls_single[] = { + VOL_CTL_SINGLE("Input Ch0 Capture Volume", hb_studio_gain_ctls_single[0], gain_tlv), + VOL_CTL_SINGLE("Input Ch1 Capture Volume", hb_studio_gain_ctls_single[1], gain_tlv), + VOL_CTL_SINGLE("Input Ch2 Capture Volume", hb_studio_gain_ctls_single[2], gain_tlv), + VOL_CTL_SINGLE("Input Ch3 Capture Volume", hb_studio_gain_ctls_single[3], gain_tlv), + VOL_CTL_SINGLE("Input Ch4 Capture Volume", hb_studio_gain_ctls_single[4], gain_tlv), + VOL_CTL_SINGLE("Input Ch5 Capture Volume", hb_studio_gain_ctls_single[5], gain_tlv), + VOL_CTL_SINGLE("Input Ch6 Capture Volume", hb_studio_gain_ctls_single[6], gain_tlv), + VOL_CTL_SINGLE("Input Ch7 Capture Volume", hb_studio_gain_ctls_single[7], gain_tlv), }; -static const struct hb_uni_enum_control hb_uni_play_enum_ctls[] = { +static const struct hb_studio_enum_control hb_studio_play_enum_ctls[] = { { DAC_STATE, 0, 0x1, pll_lock_texts, ARRAY_SIZE(pll_lock_texts) }, { DAC_FILTER_SETTING_0, 0, 0x03, dac_filter_texts, ARRAY_SIZE(dac_filter_texts) }, { MUTE_OUTPUTS, 0, 0x01, mute_texts, ARRAY_SIZE(mute_texts) }, }; -static const struct hb_uni_enum_control hb_uni_rec_enum_ctls[] = { +static const struct hb_studio_enum_control hb_studio_rec_enum_ctls[] = { { ADC_CLIPPING_ATT, 0, 0x7, adc_att_texts, ARRAY_SIZE(adc_att_texts) }, + { MUTE_INPUTS, 0, 0x01, mute_texts, ARRAY_SIZE(mute_texts) }, }; -static const struct snd_kcontrol_new hb_uni_gen_controls_single[] = { - ENUM_CTL_SINGLE("DAC Filter", hb_uni_play_enum_ctls[1]), - ENUM_CTL_SINGLE("DAC Mute", hb_uni_play_enum_ctls[2]), +/* ---- Studio Digi / AES (card_type AES) controls ---- */ +static const struct hb_studio_enum_control hb_studio_dix_clk_enum_ctls[] = { + { CARD_CLK_OVRWR, 0, 0x01, dix_clk_texts, ARRAY_SIZE(dix_clk_texts) }, +}; + +static const struct snd_kcontrol_new hb_studio_gen_controls_single[] = { + ENUM_CTL_SINGLE("DAC Filter", hb_studio_play_enum_ctls[1]), + ENUM_CTL_SINGLE("Output Mute", hb_studio_play_enum_ctls[2]), }; static const struct snd_kcontrol_new adc_controls_single[] = { - ENUM_CTL_SINGLE("Clipping Attenuation Capture Volume", hb_uni_rec_enum_ctls[0]), + ENUM_CTL_SINGLE("Clipping Attenuation Capture Volume", hb_studio_rec_enum_ctls[0]), +}; + +static const struct hb_studio_enum_control hb_studio_samplerate_ctl = { + /* DIR FS calculator (0x3A): real input rate, any clock mode */ + .reg = CARD_DIR_FS, + .shift = 0, + .mask = 0x0F, /* rate is stored in lower 4 bits */ + .texts = samplerate_texts, + .items = ARRAY_SIZE(samplerate_texts), +}; + +static const struct snd_kcontrol_new dix_controls_single[] = { + ENUM_CTL_SINGLE("Clock mode", hb_studio_dix_clk_enum_ctls[0]), + ENUM_CTL_SINGLE("Output Mute", hb_studio_play_enum_ctls[2]), + ENUM_CTL_SINGLE("Input Mute", hb_studio_rec_enum_ctls[1]), + ENUM_CTL_SINGLE_RO_GET("Current Sample Rate", hb_studio_samplerate_ctl, + hb_studio_samplerate_get), }; static int snd_rpi_hifiberry_studio_hw_params( @@ -528,10 +657,10 @@ static int snd_rpi_hifiberry_studio_hw_params( priv->sample_bits = snd_pcm_format_width(params_format(params)); priv->sample_bits = priv->sample_bits <= 16 ? 16 : 32; - priv->current_rate = params_rate(params); - dev_info(dev, "using %ibits @ %isps\n", - priv->sample_bits, priv->current_rate); + + dev_info(dev, "requesting %ibits @ %isps\n", + priv->sample_bits, priv->current_rate); /* write requested samplerate and word length back to card */ switch (priv->current_rate) { @@ -582,6 +711,44 @@ static int snd_rpi_hifiberry_studio_hw_params( return -EINVAL; } + /* + * AES clock selection & rate locking: + * - in RX-clock mode every stream (capture or playback) must run at + * the detected (AES) input rate; + * - playback in TX-clock mode is unconstrained (the card is + * the clock master). + */ + if (priv->card_type == AES) { + bool capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + unsigned int mode = 0; + int in_rate; + + if (capture) { + in_rate = hb_studio_input_rate_hz(priv); + if (!in_rate) { + dev_err(dev, "no AES input detected, cannot capture\n"); + return -EINVAL; + } + if (in_rate != (int)priv->current_rate) { + dev_err(dev, "capture rate %u does not match AES input %d Hz\n", + priv->current_rate, in_rate); + return -EINVAL; + } + /* valid input and matching rate: lock the card to the input */ + regmap_write(priv->regmap, CARD_CLK_OVRWR, 0x01); + } else { + regmap_read(priv->regmap, CARD_CLK_OVRWR, &mode); + if (mode == 0x01) { + in_rate = hb_studio_input_rate_hz(priv); + if (!in_rate || in_rate != (int)priv->current_rate) { + dev_err(dev, "playback rate %u does not match AES input %d Hz\n", + priv->current_rate, in_rate); + return -EINVAL; + } + } + } + } + err = regmap_write(priv->regmap, CURRENT_RATE, tmp); if (err < 0) return err; @@ -598,7 +765,7 @@ static int snd_rpi_hifiberry_studio_hw_params( break; default: dev_info(dev, "word length not supported (%u)\n", - priv->sample_bits); + priv->sample_bits); return -EINVAL; } err = regmap_write(priv->regmap, CURRENT_FORMAT, tmp); @@ -607,7 +774,7 @@ static int snd_rpi_hifiberry_studio_hw_params( /* If card provides clocks wait max. ~40ms for PLL */ if (card_is_clk_provider) { - /* trigger card to set new rate and format */ + /* trigger card to set new rate and format */ err = regmap_write(priv->regmap, CARD_CLOCK_MODE, 0x02); if (err < 0) return err; @@ -625,8 +792,40 @@ static int snd_rpi_hifiberry_studio_hw_params( return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); } +static int snd_rpi_hifiberry_studio_startup( + struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + priv->playback_substream = substream; + regmap_update_bits(priv->regmap, CARD_STREAM_STATUS, 0x10, + 0x10); + } else { + priv->capture_substream = substream; + regmap_update_bits(priv->regmap, CARD_STREAM_STATUS, 0x01, + 0x01); + } + + return 0; +} + +static void snd_rpi_hifiberry_studio_shutdown( + struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(priv->regmap, CARD_STREAM_STATUS, 0x10, + 0x00); + priv->playback_substream = NULL; + } else { + regmap_update_bits(priv->regmap, CARD_STREAM_STATUS, 0x01, + 0x00); + priv->capture_substream = NULL; + } +} + static const struct snd_soc_ops snd_rpi_hifiberry_studio_ops = { + .startup = snd_rpi_hifiberry_studio_startup, .hw_params = snd_rpi_hifiberry_studio_hw_params, + .shutdown = snd_rpi_hifiberry_studio_shutdown, }; SND_SOC_DAILINK_DEFS(hifiberry_studio, @@ -634,6 +833,8 @@ SND_SOC_DAILINK_DEFS(hifiberry_studio, DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")), DAILINK_COMP_ARRAY(COMP_EMPTY())); +static void hb_studio_error_work(struct work_struct *work); + static int hifiberry_studio_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); @@ -657,7 +858,6 @@ static int hifiberry_studio_init(struct snd_soc_pcm_runtime *rtd) priv->card_info.supported_rates; codec_dai->driver->capture.channels_max = priv->card_info.num_of_input_ch; - dai->name = "HiFiBerry Studio DAC8x-ADC8x"; dai->stream_name = "HiFiBerry Studio HiFi"; } else { rtd->dai_link->playback_only = 1; // Disable capture @@ -671,14 +871,16 @@ static int hifiberry_studio_init(struct snd_soc_pcm_runtime *rtd) | SND_SOC_DAIFMT_CBP_CFP; } dev_info(card->dev, - "HiFiBerry Studio DAC8x successfully initialized\n"); + "HiFiBerry Studio Soundcard successfully initialized\n"); + spin_lock_init(&priv->stream_lock); + INIT_WORK(&priv->error_work, hb_studio_error_work); return 0; } static struct snd_soc_dai_link snd_rpi_hifiberry_studio_dai[] = { { - .name = "HiFiBerry Studio DAC8x", + .name = "HiFiBerry Studio Soundcard", .stream_name = "HifiBerry Studio HiFi", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | @@ -691,73 +893,111 @@ static struct snd_soc_dai_link snd_rpi_hifiberry_studio_dai[] = { /* audio machine driver */ static struct snd_soc_card snd_rpi_hifiberry_studio = { - .name = "Hifiberry Studio DAC8x", + .name = "Hifiberry Studio Soundcard", .driver_name = "HifiberryStudio", .owner = THIS_MODULE, .dai_link = snd_rpi_hifiberry_studio_dai, .num_links = ARRAY_SIZE(snd_rpi_hifiberry_studio_dai), }; -static int hb_uni_read_card_info(struct platform_device *pdev) +/* + * Read the fixed hardware/firmware identity out of the controller: versions, + * UUID (-> card_type), channel counts, supported rates/formats and the + * CARD_BUSY.. capability block. + */ +static int hb_studio_ctrl_read_info(struct i2c_client *client, + struct hb_studio_private *p) { + u32 uuid_end; int ret; /* read basic card info */ - ret = regmap_bulk_read(priv->regmap, 0x00, &priv->card_info, 0x06); + ret = regmap_bulk_read(p->regmap, 0x00, &p->card_info, 0x06); if (ret) { - dev_err(&pdev->dev, "Failed to read card info: %d\n", ret); - return ret; + dev_err(&client->dev, "Failed to read card info: %d\n", ret); + return ret; } - dev_info(&pdev->dev, "hardware V%d.%d.%d\n", - priv->card_info.hardware_major, - priv->card_info.hardware_minor, - priv->card_info.hardware_subversion - ); + dev_info(&client->dev, "hardware V%d.%d.%d\n", + p->card_info.hardware_major, + p->card_info.hardware_minor, + p->card_info.hardware_subversion); - dev_info(&pdev->dev, "firmware V%d.%d.%d\n", - priv->card_info.firmware_major, - priv->card_info.firmware_minor, - priv->card_info.firmware_subversion - ); + dev_info(&client->dev, "firmware V%d.%d.%d\n", + p->card_info.firmware_major, + p->card_info.firmware_minor, + p->card_info.firmware_subversion); /* read card capabilities */ - ret = regmap_bulk_read(priv->regmap, UUID, &priv->card_info.uuid, 0x20); + ret = regmap_bulk_read(p->regmap, UUID, &p->card_info.uuid, 0x20); if (ret) { - dev_err(&pdev->dev, "Failed to read card info: %d\n", ret); + dev_err(&client->dev, "Failed to read card info: %d\n", ret); return ret; } - dev_info(&pdev->dev, "UUID: %*phN\n", - (int)sizeof(priv->card_info.uuid.b), priv->card_info.uuid.b); - dev_info(&pdev->dev, "%i output channels reported\n", - priv->card_info.num_of_output_ch); - dev_dbg(&pdev->dev, "supported rates %08x\n", - priv->card_info.supported_rates); - dev_dbg(&pdev->dev, "supported formats %08x\n", - priv->card_info.supported_formats); - - if (priv->card_info.num_of_output_ch > 8 || - priv->card_info.num_of_input_ch > 8) { - dev_err(&pdev->dev, "Maximum of 8 channels exceeded!\n"); + dev_info(&client->dev, "UUID: %*phN\n", + (int)sizeof(p->card_info.uuid.b), p->card_info.uuid.b); + dev_info(&client->dev, "%i output channels reported\n", + p->card_info.num_of_output_ch); + dev_dbg(&client->dev, "supported rates %08x\n", + p->card_info.supported_rates); + dev_dbg(&client->dev, "supported formats %08x\n", + p->card_info.supported_formats); + + if (p->card_info.num_of_output_ch > 8 || + p->card_info.num_of_input_ch > 8) { + dev_err(&client->dev, "Maximum of 8 channels exceeded!\n"); return -EINVAL; } - if (priv->card_info.num_of_input_ch > 0) { - dev_info(&pdev->dev, - "Inputs detected: %u channels\n", - priv->card_info.num_of_input_ch); + if (p->card_info.num_of_input_ch > 0) { + dev_info(&client->dev, + "Inputs detected: %u channels\n", + p->card_info.num_of_input_ch); } else { - dev_info(&pdev->dev, "No inputs present, playback only\n"); + dev_info(&client->dev, "No inputs present, playback only\n"); } - ret = regmap_bulk_read(priv->regmap, CARD_BUSY, - &priv->card_info.card_busy, 20); + ret = regmap_bulk_read(p->regmap, CARD_BUSY, + &p->card_info.card_busy, 20); if (ret) { - dev_err(&pdev->dev, "Failed to read card info: %d\n", ret); + dev_err(&client->dev, "Failed to read card info: %d\n", ret); return ret; } + uuid_end = cpu_to_be32(*(u32 *)((u8 *)&p->card_info.uuid + 12)); + dev_info(&client->dev, "Card UUID end %08x\n", uuid_end); + + switch (uuid_end) { + case 0x7c641980: + dev_info(&client->dev, "Card type Analog\n"); + p->card_type = DACADC; + break; + case 0x0eb0104d: + dev_info(&client->dev, "Card type Digital/AES\n"); + p->card_type = AES; + break; + default: + dev_info(&client->dev, "No card type detected, assuming Analog\n"); + p->card_type = DACADC; + break; + } + + return 0; +} + +/* + * Check the clock capability (CARD_CLK_OPTIONS, already read + * by hb_studio_ctrl_read_info()) and verfiy vs. the "clk-provider" + * DT property + */ +static int hb_studio_validate_clk_config(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + + if (np && of_property_read_bool(np, "clk-provider")) + card_is_clk_provider = true; + if (card_is_clk_provider) { if (priv->card_info.card_clk_options & 0x02) { dev_info(&pdev->dev, "Card provides i2s clocks\n"); @@ -774,43 +1014,30 @@ static int hb_uni_read_card_info(struct platform_device *pdev) } } - switch (cpu_to_be32(*(unsigned int *)&priv->card_info.uuid)) { - case 0x74e7ae95: - if (card_is_clk_provider) - snd_rpi_hifiberry_studio.name = - "HiFiBerry Studio DAC8x Pro"; - else - snd_rpi_hifiberry_studio.name = - "HiFiBerry Studio DAC8x"; - break; - default: - break; - } - - - regcache_cache_only(priv->regmap, true); - ret = regmap_bulk_read(priv->regmap, MASTER_VOL, - &priv->card_info.master_vol, MUTE_OUTPUTS - MASTER_VOL); - regcache_cache_only(priv->regmap, false); - return 0; } -static int hb_uni_add_card_controls(struct platform_device *pdev) +/* + * Register the ALSA controls for an analog Studio (DAC/ADC) cards (card_type + * DACADC): DAC filter + output mute, the output volumes (sized to the actual + * output-channel count), and if inputs are present the ADC gains and the + * clipping-attenuation control. + */ +static int hb_studio_add_dacadc_controls(struct platform_device *pdev) { int ret; ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, - hb_uni_gen_controls_single, - ARRAY_SIZE(hb_uni_gen_controls_single)); + hb_studio_gen_controls_single, + ARRAY_SIZE(hb_studio_gen_controls_single)); if (ret < 0) { dev_err(&pdev->dev, "snd_soc_add_card_controls() failed: %d\n", ret); return ret; } ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, - hb_uni_play_controls_single, - ARRAY_SIZE(hb_uni_play_controls_single) / 9 * + hb_studio_play_controls_single, + ARRAY_SIZE(hb_studio_play_controls_single) / 9 * (priv->card_info.num_of_output_ch + 1)); if (ret < 0) { dev_err(&pdev->dev, @@ -821,16 +1048,16 @@ static int hb_uni_add_card_controls(struct platform_device *pdev) /* add optional ADC controls if inputs detected */ if (priv->card_info.num_of_input_ch > 0) { ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, - hb_uni_rec_controls_single, - ARRAY_SIZE(hb_uni_rec_controls_single) / 8 * - priv->card_info.num_of_input_ch); + hb_studio_rec_controls_single, + ARRAY_SIZE(hb_studio_rec_controls_single) / 8 * + priv->card_info.num_of_input_ch); if (ret < 0) { dev_err(&pdev->dev, "snd_soc_add_card_controls() failed: %d\n", ret); } ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, - adc_controls_single, - ARRAY_SIZE(adc_controls_single)); + adc_controls_single, + ARRAY_SIZE(adc_controls_single)); if (ret < 0) { dev_err(&pdev->dev, "snd_soc_add_card_controls() failed: %d\n", ret); @@ -839,53 +1066,189 @@ static int hb_uni_add_card_controls(struct platform_device *pdev) return ret; } -static int hb_controller_probe(struct platform_device *pdev) +/* + * Register the ALSA controls for a Studio Digi/AES card (card_type AES): + * the controls for Clock mode, Current Sample Rate, Input/Output Mute. + */ +static int hb_studio_add_dix_controls(struct platform_device *pdev) { - struct i2c_adapter *adap = i2c_get_adapter(1); - struct device_node *np = pdev->dev.of_node; - int ret; - - if (!adap) - return -EPROBE_DEFER; /* I2C module not yet available */ + int ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio, + dix_controls_single, ARRAY_SIZE(dix_controls_single)); + if (ret < 0) + dev_err(&pdev->dev, + "snd_soc_add_card_controls() failed: %d\n", ret); + return ret; +} - struct i2c_board_info info = { - I2C_BOARD_INFO("hb_controller", 0x10), - }; +/* + * The DAC8x and Digi cards share one controller and this driver; the card + * type is auto-detected from the controller UUID (hb_studio_ctrl_read_info): + * DACADC -> DAC8x DAC/ADC controls (hb_studio_add_dacadc_controls) + * AES -> Digi DIX controls (hb_studio_add_dix_controls) + * Any other/unknown type registers no extra card controls. + */ +static int hb_studio_add_card_controls(struct platform_device *pdev) +{ + switch (priv->card_type) { + case DACADC: + return hb_studio_add_dacadc_controls(pdev); + case AES: + return hb_studio_add_dix_controls(pdev); + default: + return 0; + } +} - hb_uni_i2c_client = i2c_new_client_device(adap, &info); - if (IS_ERR(hb_uni_i2c_client)) - return PTR_ERR(hb_uni_i2c_client); +/* + * I2C client driver for the onboard controller (hb-studio-ctrl @ 0x10). It is + * instantiated from the "hifiberry,hb-studio-ctrl" child node under &i2c1 in + * the DT overlay + */ +static int hb_studio_ctrl_probe(struct i2c_client *client) +{ + struct hb_studio_private *p; + int ret; - priv = devm_kzalloc(&hb_uni_i2c_client->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) + p = devm_kzalloc(&client->dev, sizeof(*p), GFP_KERNEL); + if (!p) return -ENOMEM; - priv->regmap = devm_regmap_init_i2c(hb_uni_i2c_client, &hb_uni_regmap); - if (IS_ERR(priv->regmap)) - return dev_err_probe(&hb_uni_i2c_client->dev, - PTR_ERR(priv->regmap), "Failed to init regmap\n"); + p->regmap = devm_regmap_init_i2c(client, &hb_studio_regmap); + if (IS_ERR(p->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(p->regmap), + "Failed to init regmap\n"); + + i2c_set_clientdata(client, p); + + /* + * Read into the not-yet-published 'p' first. The machine driver polls + * the shared 'priv' pointer and treats non-NULL as "fully ready", so + * 'priv'/'hb_studio_i2c_client' must only be assigned once the read + * below has actually succeeded - probing the two drivers can and does + * happen concurrently, and publishing early let the machine driver + * see a card_info that was still all zeroes. + */ + ret = hb_studio_ctrl_read_info(client, p); + if (ret) { + dev_err(&client->dev, + "Failed to read card info or wrong configuration!\n"); + return ret; + } - if (np && of_property_read_bool(np, "clk-provider")) - card_is_clk_provider = true; + /* fully populated: now safe for the machine driver to consume */ + priv = p; + hb_studio_i2c_client = client; - ret = hb_uni_read_card_info(pdev); - if (ret < 0) { - dev_err(&hb_uni_i2c_client->dev, - "Failed to read card info or wrong configuration!\n"); + return 0; +} + +static void hb_studio_ctrl_remove(struct i2c_client *client) +{ + if (hb_studio_i2c_client == client) { + priv = NULL; + hb_studio_i2c_client = NULL; } +} - return ret; +static const struct i2c_device_id hb_studio_ctrl_id[] = { + { "hb-studio-ctrl", 0 }, + { } }; +MODULE_DEVICE_TABLE(i2c, hb_studio_ctrl_id); + +static const struct of_device_id hb_studio_ctrl_of_match[] = { + { .compatible = "hifiberry,hb-studio-ctrl" }, + { } +}; +MODULE_DEVICE_TABLE(of, hb_studio_ctrl_of_match); + +static struct i2c_driver hb_studio_ctrl_driver = { + .driver = { + .name = "hb-studio-ctrl", + .of_match_table = hb_studio_ctrl_of_match, + .suppress_bind_attrs = true, + }, + .probe = hb_studio_ctrl_probe, + .remove = hb_studio_ctrl_remove, + .id_table = hb_studio_ctrl_id, +}; + +static void hb_studio_error_work(struct work_struct *work) +{ + struct hb_studio_private *p = + container_of(work, struct hb_studio_private, error_work); + unsigned long flags; + struct snd_pcm_substream *play, *capt; + + dev_err(&hb_studio_i2c_client->dev, "PLL lock lost, stopping streams\n"); + + spin_lock_irqsave(&p->stream_lock, flags); + play = p->playback_substream; + capt = p->capture_substream; + + if (play) + snd_pcm_stop(play, SNDRV_PCM_STATE_SUSPENDED); + if (capt) + snd_pcm_stop(capt, SNDRV_PCM_STATE_SUSPENDED); + spin_unlock_irqrestore(&p->stream_lock, flags); +} + +/* FS-change / PLL-lost interrupt: hand off to the error work queue. */ +static irqreturn_t hb_studio_irq_handler(int irq, void *dev_id) +{ + struct hb_studio_private *p = dev_id; + + schedule_work(&p->error_work); + return IRQ_HANDLED; +} static int snd_rpi_hifiberry_studio_probe(struct platform_device *pdev) { + bool no_controls; + int gpio, irq; int ret = 0; - /* probe for controller */ - ret = hb_controller_probe(pdev); + /* wait for the I2C controller driver to have probed and populated priv */ + if (!priv) + return -EPROBE_DEFER; + + ret = hb_studio_validate_clk_config(pdev); if (ret < 0) return ret; + no_controls = of_property_read_bool(pdev->dev.of_node, "no-controls"); + + /* + * The FS-change / PLL-lost interrupt only exists on the Digi/AES + * board's overlay (gpios/interrupts wired to GPIO7); the DAC8x + * overlays don't route it at all, and there's nothing for a purely + * analog card to report here, so only look for it for AES cards. + */ + if (priv->card_type == AES) { + gpio = of_get_named_gpio(pdev->dev.of_node, "gpios", 0); + if (!gpio_is_valid(gpio)) + return dev_err_probe(&pdev->dev, gpio, "Invalid GPIO\n"); + + ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_IN, + "hifiberry-studio-fs-change"); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to request GPIO\n"); + + irq = gpio_to_irq(gpio); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, + hb_studio_irq_handler, NULL, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "hifiberry-studio-fs-change", priv); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to request IRQ\n"); + + dev_info(&pdev->dev, "GPIO interrupt registered on GPIO %d (IRQ %d)\n", + gpio, irq); + } + snd_rpi_hifiberry_studio.dev = &pdev->dev; if (pdev->dev.of_node) { @@ -894,7 +1257,7 @@ static int snd_rpi_hifiberry_studio_probe(struct platform_device *pdev) dai = &snd_rpi_hifiberry_studio_dai[0]; i2s_node = of_parse_phandle(pdev->dev.of_node, - "i2s-controller", 0); + "i2s-controller", 0); if (i2s_node) { dai->cpus->dai_name = NULL; @@ -905,19 +1268,28 @@ static int snd_rpi_hifiberry_studio_probe(struct platform_device *pdev) } ret = devm_snd_soc_register_card(&pdev->dev, - &snd_rpi_hifiberry_studio); - if (ret && ret != -EPROBE_DEFER) - dev_err(&pdev->dev, - "devm_snd_soc_register_card() failed: %d\n", ret); + &snd_rpi_hifiberry_studio); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "devm_snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + if (no_controls) { + dev_info(&pdev->dev, "ALSA controls disabled (no-controls)\n"); + return 0; + } /* as we do not have components use card-controls */ - ret = hb_uni_add_card_controls(pdev); + ret = hb_studio_add_card_controls(pdev); return ret; } static const struct of_device_id snd_rpi_hifiberry_studio_of_match[] = { { .compatible = "hifiberry,hifiberry-studio-dac8x", }, + { .compatible = "hifiberry,hifiberry-studio", }, {}, }; MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_studio_of_match); @@ -931,8 +1303,29 @@ static struct platform_driver snd_rpi_hifiberry_studio_driver = { .probe = snd_rpi_hifiberry_studio_probe, }; -module_platform_driver(snd_rpi_hifiberry_studio_driver); +static int __init hb_studio_driver_init(void) +{ + int ret; + + ret = i2c_add_driver(&hb_studio_ctrl_driver); + if (ret) + return ret; + + ret = platform_driver_register(&snd_rpi_hifiberry_studio_driver); + if (ret) + i2c_del_driver(&hb_studio_ctrl_driver); + + return ret; +} +module_init(hb_studio_driver_init); + +static void __exit hb_studio_driver_exit(void) +{ + platform_driver_unregister(&snd_rpi_hifiberry_studio_driver); + i2c_del_driver(&hb_studio_ctrl_driver); +} +module_exit(hb_studio_driver_exit); MODULE_AUTHOR("Joerg Schambacher "); -MODULE_DESCRIPTION("HiFiBerry Studio DAC8x Soundcard Driver"); +MODULE_DESCRIPTION("HiFiBerry Studio soundcard driver (DAC8x, Digi)"); MODULE_LICENSE("GPL"); From 68380bfcd42d8a7a81f2cf57e48cec4ab3c34ac5 Mon Sep 17 00:00:00 2001 From: j-schambacher Date: Mon, 13 Jul 2026 09:16:54 +0200 Subject: [PATCH 3/4] overlays: hifiberry-studio: add Digi/AES overlay, register controller Add hifiberry-studio-digi-overlay.dts for the Studio Digi/AES card, wired the same way as the DAC8x PRO overlay (i2s1, clk-provider), plus the GPIO7 FS-change/PLL-lost interrupt line the driver now requests automatically for AES-type cards. Both existing DAC8x overlays use the "hb_studio_ctrl" I2C client node (compatible "hifiberry,hb-studio-ctrl") so the driver's separate controller probe can be attached. A "no_controls" can be used to skip registering the ALSA controls for a card. The PRO overlay also switches to the new "hifiberry,hifiberry-studio" compatible string and adds the informational "card-type" property; the base overlay keeps "hifiberry,hifiberry-studio-dac8x" for backwards compatibility. Signed-off-by: j-schambacher --- arch/arm/boot/dts/overlays/Makefile | 1 + arch/arm/boot/dts/overlays/README | 17 +++- .../hifiberry-studio-dac8x-overlay.dts | 12 ++- .../hifiberry-studio-dac8x-pro-overlay.dts | 14 +++- .../hifiberry-studio-digi-overlay.dts | 83 +++++++++++++++++++ 5 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 arch/arm/boot/dts/overlays/hifiberry-studio-digi-overlay.dts diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile index c83c624beafbcd..85596878b6e1a4 100644 --- a/arch/arm/boot/dts/overlays/Makefile +++ b/arch/arm/boot/dts/overlays/Makefile @@ -109,6 +109,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ hifiberry-digi-pro.dtbo \ hifiberry-studio-dac8x.dtbo \ hifiberry-studio-dac8x-pro.dtbo \ + hifiberry-studio-digi.dtbo \ highperi.dtbo \ hy28a.dtbo \ hy28b.dtbo \ diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README index 113e20381266c3..3ce83a441bfacf 100644 --- a/arch/arm/boot/dts/overlays/README +++ b/arch/arm/boot/dts/overlays/README @@ -2127,14 +2127,23 @@ Params: Name: hifiberry-studio-dac8x Info: Configures the HifiBerry Studio DAC8x audio card -Load: dtoverlay=hifiberry-studio-dac8x -Params: +Load: dtoverlay=hifiberry-studio-dac8x,= +Params: no_controls Do not register the ALSA controls for + this card Name: hifiberry-studio-dac8x-pro Info: Configures the HifiBerry Studio DAC8x PRO audio card -Load: dtoverlay=hifiberry-studio-dac8x-pro -Params: +Load: dtoverlay=hifiberry-studio-dac8x-pro,= +Params: no_controls Do not register the ALSA controls for + this card + + +Name: hifiberry-studio-digi +Info: Configures the HifiBerry Studio Digi/AES audio card +Load: dtoverlay=hifiberry-studio-digi,= +Params: no_controls Do not register the ALSA controls for + this card Name: highperi diff --git a/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts index b19b7d359ffd71..80f1a92aa604a9 100644 --- a/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts +++ b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts @@ -10,6 +10,13 @@ target = <&i2c1>; __overlay__ { status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + hb_studio_ctrl: hb_studio_ctrl@10 { + compatible = "hifiberry,hb-studio-ctrl"; + reg = <0x10>; + status = "okay"; + }; }; }; @@ -49,11 +56,14 @@ fragment@4 { target = <&sound>; - __overlay__ { + hifiberry_studio: __overlay__ { compatible = "hifiberry,hifiberry-studio-dac8x"; i2s-controller = <&i2s_clk_producer>; status = "okay"; }; }; + __overrides__ { + no_controls = <&hifiberry_studio>,"no-controls?"; + }; }; diff --git a/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts index fd4f0a3485baeb..f92fef1c3427b9 100644 --- a/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts +++ b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts @@ -10,6 +10,13 @@ target = <&i2c1>; __overlay__ { status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + hb_studio_ctrl: hb_studio_ctrl@10 { + compatible = "hifiberry,hb-studio-ctrl"; + reg = <0x10>; + status = "okay"; + }; }; }; @@ -49,12 +56,15 @@ fragment@4 { target = <&sound>; - __overlay__ { - compatible = "hifiberry,hifiberry-studio-dac8x"; + hifiberry_studio: __overlay__ { + compatible = "hifiberry,hifiberry-studio"; i2s-controller = <&i2s_clk_consumer>; clk-provider; status = "okay"; }; }; + __overrides__ { + no_controls = <&hifiberry_studio>,"no-controls?"; + }; }; diff --git a/arch/arm/boot/dts/overlays/hifiberry-studio-digi-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-studio-digi-overlay.dts new file mode 100644 index 00000000000000..941109b6683da5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-studio-digi-overlay.dts @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +// Definitions for HiFiBerry Studio Digi/AES soundcard +/dts-v1/; +/plugin/; + +#include +#include +#include + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + hb_studio_ctrl: hb_studio_ctrl@10 { + compatible = "hifiberry,hb-studio-ctrl"; + reg = <0x10>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + rp1_i2s1_digi: rp1_i2s1_digi { + function = "i2s1"; + pins = "gpio18", "gpio19", "gpio20", + "gpio21", "gpio22", "gpio23", + "gpio24", "gpio25", "gpio26", + "gpio27"; + bias-disable; + }; + digi_int_pin: digi_int_pin { + pins = "gpio7"; + bias-pull-up; + }; + }; + }; + + fragment@2 { + target = <&i2s_clk_consumer>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&rp1_i2s1_digi>; + status = "okay"; + }; + }; + + fragment@3 { + target-path = "/"; + __overlay__ { + dummy-codec { + #sound-dai-cells = <0>; + compatible = "snd-soc-dummy"; + status = "okay"; + }; + }; + }; + + fragment@4 { + target = <&sound>; + hifiberry_studio: __overlay__ { + compatible = "hifiberry,hifiberry-studio"; + i2s-controller = <&i2s_clk_consumer>; + clk-provider; + pinctrl-names = "default"; + pinctrl-0 = <&digi_int_pin>; + interrupt-parent = <&gpio>; + interrupts = <7 IRQ_TYPE_EDGE_BOTH>; + gpios = <&gpio 7 GPIO_ACTIVE_HIGH>; + status = "okay"; + }; + }; + + __overrides__ { + no_controls = <&hifiberry_studio>,"no-controls?"; + }; +}; From 57efdb6d65a04673e38bedaf57ef0658f69da4c5 Mon Sep 17 00:00:00 2001 From: j-schambacher Date: Mon, 13 Jul 2026 09:17:32 +0200 Subject: [PATCH 4/4] arm64: configs: fix stale HIFIBERRY_STUDIO_DAC8X symbol name The Kconfig symbol backing the HiFiBerry Studio driver was renamed from SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X to SND_BCM2708_SOC_HIFIBERRY_STUDIO as part of generalizing the driver beyond the DAC8x card, but the defconfigs enabling it by default were left referencing the old, now nonexistent symbol name, silently dropping the driver from being built by these defconfigs. Update all three affected defconfigs to the new symbol name. Signed-off-by: j-schambacher --- arch/arm64/configs/bcm2711_defconfig | 2 +- arch/arm64/configs/bcm2711_rt_defconfig | 2 +- arch/arm64/configs/bcm2712_defconfig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig index 3135661790a0e9..e9a14c80fe68ce 100644 --- a/arch/arm64/configs/bcm2711_defconfig +++ b/arch/arm64/configs/bcm2711_defconfig @@ -1160,7 +1160,7 @@ CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m -CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m CONFIG_SND_BCM2708_SOC_PIFI_40=m diff --git a/arch/arm64/configs/bcm2711_rt_defconfig b/arch/arm64/configs/bcm2711_rt_defconfig index a3ee2d5b8c6583..11232df846725e 100644 --- a/arch/arm64/configs/bcm2711_rt_defconfig +++ b/arch/arm64/configs/bcm2711_rt_defconfig @@ -1156,7 +1156,7 @@ CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m -CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m CONFIG_SND_BCM2708_SOC_PIFI_40=m diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig index 174f54e66a327b..c5573cf1bd11c0 100644 --- a/arch/arm64/configs/bcm2712_defconfig +++ b/arch/arm64/configs/bcm2712_defconfig @@ -1162,7 +1162,7 @@ CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m -CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m CONFIG_SND_BCM2708_SOC_PIFI_40=m