| b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame] | 1 | From 1372d5148783da0f382be29705de8b742dcc9d2a Mon Sep 17 00:00:00 2001 |
| 2 | From: Gordon Garrity <gordon@iqaudio.com> |
| 3 | Date: Sat, 8 Mar 2014 16:56:57 +0000 |
| 4 | Subject: [PATCH] Add IQaudIO Sound Card support for Raspberry Pi |
| 5 | |
| 6 | Set a limit of 0dB on Digital Volume Control |
| 7 | |
| 8 | The main volume control in the PCM512x DAC has a range up to |
| 9 | +24dB. This is dangerously loud and can potentially cause massive |
| 10 | clipping in the output stages. Therefore this sets a sensible |
| 11 | limit of 0dB for this control. |
| 12 | |
| 13 | Allow up to 24dB digital gain to be applied when using IQAudIO DAC+ |
| 14 | |
| 15 | 24db_digital_gain DT param can be used to specify that PCM512x |
| 16 | codec "Digital" volume control should not be limited to 0dB gain, |
| 17 | and if specified will allow the full 24dB gain. |
| 18 | |
| 19 | Modify IQAudIO DAC+ ASoC driver to set card/dai config from dt |
| 20 | |
| 21 | Add the ability to set the card name, dai name and dai stream name, from |
| 22 | dt config. |
| 23 | |
| 24 | Signed-off-by: DigitalDreamtime <clive.messer@digitaldreamtime.co.uk> |
| 25 | |
| 26 | IQaudIO: auto-mute for AMP+ and DigiAMP+ |
| 27 | |
| 28 | IQAudIO amplifier mute via GPIO22. Add dt params for "one-shot" unmute |
| 29 | and auto mute. |
| 30 | |
| 31 | Revision 2, auto mute implementing HiassofT suggestion to mute/unmute |
| 32 | using set_bias_level, rather than startup/shutdown.... |
| 33 | "By default DAPM waits 5 seconds (pmdown_time) before shutting down |
| 34 | playback streams so a close/stop immediately followed by open/start |
| 35 | doesn't trigger an amp mute+unmute." |
| 36 | |
| 37 | Tested on both AMP+ (via DAC+) and DigiAMP+, with both options... |
| 38 | |
| 39 | dtoverlay=iqaudio-dacplus,unmute_amp |
| 40 | "one-shot" unmute when kernel module loads. |
| 41 | |
| 42 | dtoverlay=iqaudio-dacplus,auto_mute_amp |
| 43 | Unmute amp when ALSA device opened by a client. Mute, with 5 second delay |
| 44 | when ALSA device closed. (Re-opening the device within the 5 second close |
| 45 | window, will cancel mute.) |
| 46 | |
| 47 | Revision 4, using gpiod. |
| 48 | |
| 49 | Revision 5, clean-up formatting before adding mute code. |
| 50 | - Convert tab plus 4 space formatting to 2x tab |
| 51 | - Remove '// NOT USED' commented code |
| 52 | |
| 53 | Revision 6, don't attempt to "one-shot" unmute amp, unless card is |
| 54 | successfully registered. |
| 55 | |
| 56 | Signed-off-by: DigitalDreamtime <clive.messer@digitaldreamtime.co.uk> |
| 57 | |
| 58 | ASoC: iqaudio-dac: fix S24_LE format |
| 59 | |
| 60 | Remove set_bclk_ratio call so 24-bit data is transmitted in |
| 61 | 24 bclk cycles. |
| 62 | |
| 63 | Signed-off-by: Matthias Reichl <hias@horus.com> |
| 64 | |
| 65 | ASoC: iqaudio-dac: use modern dai_link style |
| 66 | |
| 67 | Signed-off-by: Matthias Reichl <hias@horus.com> |
| 68 | --- |
| 69 | sound/soc/bcm/iqaudio-dac.c | 223 ++++++++++++++++++++++++++++++++++++ |
| 70 | 1 file changed, 223 insertions(+) |
| 71 | create mode 100644 sound/soc/bcm/iqaudio-dac.c |
| 72 | |
| 73 | --- /dev/null |
| 74 | +++ b/sound/soc/bcm/iqaudio-dac.c |
| 75 | @@ -0,0 +1,223 @@ |
| 76 | +/* |
| 77 | + * ASoC Driver for IQaudIO DAC |
| 78 | + * |
| 79 | + * Author: Florian Meier <florian.meier@koalo.de> |
| 80 | + * Copyright 2013 |
| 81 | + * |
| 82 | + * This program is free software; you can redistribute it and/or |
| 83 | + * modify it under the terms of the GNU General Public License |
| 84 | + * version 2 as published by the Free Software Foundation. |
| 85 | + * |
| 86 | + * This program is distributed in the hope that it will be useful, but |
| 87 | + * WITHOUT ANY WARRANTY; without even the implied warranty of |
| 88 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 89 | + * General Public License for more details. |
| 90 | + */ |
| 91 | + |
| 92 | +#include <linux/module.h> |
| 93 | +#include <linux/gpio/consumer.h> |
| 94 | +#include <linux/platform_device.h> |
| 95 | + |
| 96 | +#include <sound/core.h> |
| 97 | +#include <sound/pcm.h> |
| 98 | +#include <sound/pcm_params.h> |
| 99 | +#include <sound/soc.h> |
| 100 | +#include <sound/jack.h> |
| 101 | + |
| 102 | +static bool digital_gain_0db_limit = true; |
| 103 | + |
| 104 | +static struct gpio_desc *mute_gpio; |
| 105 | + |
| 106 | +static int snd_rpi_iqaudio_dac_init(struct snd_soc_pcm_runtime *rtd) |
| 107 | +{ |
| 108 | + if (digital_gain_0db_limit) |
| 109 | + { |
| 110 | + int ret; |
| 111 | + struct snd_soc_card *card = rtd->card; |
| 112 | + |
| 113 | + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); |
| 114 | + if (ret < 0) |
| 115 | + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); |
| 116 | + } |
| 117 | + |
| 118 | + return 0; |
| 119 | +} |
| 120 | + |
| 121 | +static void snd_rpi_iqaudio_gpio_mute(struct snd_soc_card *card) |
| 122 | +{ |
| 123 | + if (mute_gpio) { |
| 124 | + dev_info(card->dev, "%s: muting amp using GPIO22\n", |
| 125 | + __func__); |
| 126 | + gpiod_set_value_cansleep(mute_gpio, 0); |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +static void snd_rpi_iqaudio_gpio_unmute(struct snd_soc_card *card) |
| 131 | +{ |
| 132 | + if (mute_gpio) { |
| 133 | + dev_info(card->dev, "%s: un-muting amp using GPIO22\n", |
| 134 | + __func__); |
| 135 | + gpiod_set_value_cansleep(mute_gpio, 1); |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +static int snd_rpi_iqaudio_set_bias_level(struct snd_soc_card *card, |
| 140 | + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) |
| 141 | +{ |
| 142 | + struct snd_soc_pcm_runtime *rtd; |
| 143 | + struct snd_soc_dai *codec_dai; |
| 144 | + |
| 145 | + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); |
| 146 | + codec_dai = rtd->codec_dai; |
| 147 | + |
| 148 | + if (dapm->dev != codec_dai->dev) |
| 149 | + return 0; |
| 150 | + |
| 151 | + switch (level) { |
| 152 | + case SND_SOC_BIAS_PREPARE: |
| 153 | + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) |
| 154 | + break; |
| 155 | + |
| 156 | + /* UNMUTE AMP */ |
| 157 | + snd_rpi_iqaudio_gpio_unmute(card); |
| 158 | + |
| 159 | + break; |
| 160 | + case SND_SOC_BIAS_STANDBY: |
| 161 | + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) |
| 162 | + break; |
| 163 | + |
| 164 | + /* MUTE AMP */ |
| 165 | + snd_rpi_iqaudio_gpio_mute(card); |
| 166 | + |
| 167 | + break; |
| 168 | + default: |
| 169 | + break; |
| 170 | + } |
| 171 | + |
| 172 | + return 0; |
| 173 | +} |
| 174 | + |
| 175 | +SND_SOC_DAILINK_DEFS(hifi, |
| 176 | + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), |
| 177 | + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")), |
| 178 | + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); |
| 179 | + |
| 180 | +static struct snd_soc_dai_link snd_rpi_iqaudio_dac_dai[] = { |
| 181 | +{ |
| 182 | + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
| 183 | + SND_SOC_DAIFMT_CBS_CFS, |
| 184 | + .init = snd_rpi_iqaudio_dac_init, |
| 185 | + SND_SOC_DAILINK_REG(hifi), |
| 186 | +}, |
| 187 | +}; |
| 188 | + |
| 189 | +/* audio machine driver */ |
| 190 | +static struct snd_soc_card snd_rpi_iqaudio_dac = { |
| 191 | + .owner = THIS_MODULE, |
| 192 | + .dai_link = snd_rpi_iqaudio_dac_dai, |
| 193 | + .num_links = ARRAY_SIZE(snd_rpi_iqaudio_dac_dai), |
| 194 | +}; |
| 195 | + |
| 196 | +static int snd_rpi_iqaudio_dac_probe(struct platform_device *pdev) |
| 197 | +{ |
| 198 | + int ret = 0; |
| 199 | + bool gpio_unmute = false; |
| 200 | + |
| 201 | + snd_rpi_iqaudio_dac.dev = &pdev->dev; |
| 202 | + |
| 203 | + if (pdev->dev.of_node) { |
| 204 | + struct device_node *i2s_node; |
| 205 | + struct snd_soc_card *card = &snd_rpi_iqaudio_dac; |
| 206 | + struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_dac_dai[0]; |
| 207 | + bool auto_gpio_mute = false; |
| 208 | + |
| 209 | + i2s_node = of_parse_phandle(pdev->dev.of_node, |
| 210 | + "i2s-controller", 0); |
| 211 | + if (i2s_node) { |
| 212 | + dai->cpus->dai_name = NULL; |
| 213 | + dai->cpus->of_node = i2s_node; |
| 214 | + dai->platforms->name = NULL; |
| 215 | + dai->platforms->of_node = i2s_node; |
| 216 | + } |
| 217 | + |
| 218 | + digital_gain_0db_limit = !of_property_read_bool( |
| 219 | + pdev->dev.of_node, "iqaudio,24db_digital_gain"); |
| 220 | + |
| 221 | + if (of_property_read_string(pdev->dev.of_node, "card_name", |
| 222 | + &card->name)) |
| 223 | + card->name = "IQaudIODAC"; |
| 224 | + |
| 225 | + if (of_property_read_string(pdev->dev.of_node, "dai_name", |
| 226 | + &dai->name)) |
| 227 | + dai->name = "IQaudIO DAC"; |
| 228 | + |
| 229 | + if (of_property_read_string(pdev->dev.of_node, |
| 230 | + "dai_stream_name", &dai->stream_name)) |
| 231 | + dai->stream_name = "IQaudIO DAC HiFi"; |
| 232 | + |
| 233 | + /* gpio_unmute - one time unmute amp using GPIO */ |
| 234 | + gpio_unmute = of_property_read_bool(pdev->dev.of_node, |
| 235 | + "iqaudio-dac,unmute-amp"); |
| 236 | + |
| 237 | + /* auto_gpio_mute - mute/unmute amp using GPIO */ |
| 238 | + auto_gpio_mute = of_property_read_bool(pdev->dev.of_node, |
| 239 | + "iqaudio-dac,auto-mute-amp"); |
| 240 | + |
| 241 | + if (auto_gpio_mute || gpio_unmute) { |
| 242 | + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", |
| 243 | + GPIOD_OUT_LOW); |
| 244 | + if (IS_ERR(mute_gpio)) { |
| 245 | + ret = PTR_ERR(mute_gpio); |
| 246 | + dev_err(&pdev->dev, |
| 247 | + "Failed to get mute gpio: %d\n", ret); |
| 248 | + return ret; |
| 249 | + } |
| 250 | + |
| 251 | + if (auto_gpio_mute && mute_gpio) |
| 252 | + snd_rpi_iqaudio_dac.set_bias_level = |
| 253 | + snd_rpi_iqaudio_set_bias_level; |
| 254 | + } |
| 255 | + } |
| 256 | + |
| 257 | + ret = snd_soc_register_card(&snd_rpi_iqaudio_dac); |
| 258 | + if (ret) { |
| 259 | + if (ret != -EPROBE_DEFER) |
| 260 | + dev_err(&pdev->dev, |
| 261 | + "snd_soc_register_card() failed: %d\n", ret); |
| 262 | + return ret; |
| 263 | + } |
| 264 | + |
| 265 | + if (gpio_unmute && mute_gpio) |
| 266 | + snd_rpi_iqaudio_gpio_unmute(&snd_rpi_iqaudio_dac); |
| 267 | + |
| 268 | + return 0; |
| 269 | +} |
| 270 | + |
| 271 | +static int snd_rpi_iqaudio_dac_remove(struct platform_device *pdev) |
| 272 | +{ |
| 273 | + snd_rpi_iqaudio_gpio_mute(&snd_rpi_iqaudio_dac); |
| 274 | + |
| 275 | + return snd_soc_unregister_card(&snd_rpi_iqaudio_dac); |
| 276 | +} |
| 277 | + |
| 278 | +static const struct of_device_id iqaudio_of_match[] = { |
| 279 | + { .compatible = "iqaudio,iqaudio-dac", }, |
| 280 | + {}, |
| 281 | +}; |
| 282 | +MODULE_DEVICE_TABLE(of, iqaudio_of_match); |
| 283 | + |
| 284 | +static struct platform_driver snd_rpi_iqaudio_dac_driver = { |
| 285 | + .driver = { |
| 286 | + .name = "snd-rpi-iqaudio-dac", |
| 287 | + .owner = THIS_MODULE, |
| 288 | + .of_match_table = iqaudio_of_match, |
| 289 | + }, |
| 290 | + .probe = snd_rpi_iqaudio_dac_probe, |
| 291 | + .remove = snd_rpi_iqaudio_dac_remove, |
| 292 | +}; |
| 293 | + |
| 294 | +module_platform_driver(snd_rpi_iqaudio_dac_driver); |
| 295 | + |
| 296 | +MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>"); |
| 297 | +MODULE_DESCRIPTION("ASoC Driver for IQAudio DAC"); |
| 298 | +MODULE_LICENSE("GPL v2"); |