Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

ASoC SSM3515 codec driver

Merge series from Martin Povišer <povik+lin@cutebit.org>:

Analog Devices SSM3515 is a simple speaker amp that Apple is
using in their 2021 iMacs, possibly elsewhere.

+507
+49
Documentation/devicetree/bindings/sound/adi,ssm3515.yaml
··· 1 + # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 + %YAML 1.2 3 + --- 4 + $id: http://devicetree.org/schemas/sound/adi,ssm3515.yaml# 5 + $schema: http://devicetree.org/meta-schemas/core.yaml# 6 + 7 + title: Analog Devices SSM3515 Audio Amplifier 8 + 9 + maintainers: 10 + - Martin Povišer <povik+lin@cutebit.org> 11 + 12 + description: | 13 + SSM3515 is a mono Class-D audio amplifier with digital input. 14 + 15 + https://www.analog.com/media/en/technical-documentation/data-sheets/SSM3515.pdf 16 + 17 + allOf: 18 + - $ref: dai-common.yaml# 19 + 20 + properties: 21 + compatible: 22 + enum: 23 + - adi,ssm3515 24 + 25 + reg: 26 + maxItems: 1 27 + 28 + '#sound-dai-cells': 29 + const: 0 30 + 31 + required: 32 + - compatible 33 + - reg 34 + 35 + unevaluatedProperties: false 36 + 37 + examples: 38 + - | 39 + i2c { 40 + #address-cells = <1>; 41 + #size-cells = <0>; 42 + 43 + codec@14 { 44 + compatible = "adi,ssm3515"; 45 + reg = <0x14>; 46 + #sound-dai-cells = <0>; 47 + sound-name-prefix = "Left Tweeter"; 48 + }; 49 + };
+2
MAINTAINERS
··· 1990 1990 L: asahi@lists.linux.dev 1991 1991 L: alsa-devel@alsa-project.org (moderated for non-subscribers) 1992 1992 S: Maintained 1993 + F: Documentation/devicetree/bindings/sound/adi,ssm3515.yaml 1993 1994 F: Documentation/devicetree/bindings/sound/apple,* 1994 1995 F: sound/soc/apple/* 1995 1996 F: sound/soc/codecs/cs42l83-i2c.c 1997 + F: sound/soc/codecs/ssm3515.c 1996 1998 1997 1999 ARM/ARTPEC MACHINE SUPPORT 1998 2000 M: Jesper Nilsson <jesper.nilsson@axis.com>
+6
sound/soc/codecs/Kconfig
··· 1659 1659 select SND_SOC_SSM2602 1660 1660 select REGMAP_I2C 1661 1661 1662 + config SND_SOC_SSM3515 1663 + tristate "Analog Devices SSM3515 amplifier driver" 1664 + select REGMAP_I2C 1665 + depends on I2C 1666 + depends on OF 1667 + 1662 1668 config SND_SOC_SSM4567 1663 1669 tristate "Analog Devices ssm4567 amplifier driver support" 1664 1670 depends on I2C
+2
sound/soc/codecs/Makefile
··· 257 257 snd-soc-ssm2602-objs := ssm2602.o 258 258 snd-soc-ssm2602-spi-objs := ssm2602-spi.o 259 259 snd-soc-ssm2602-i2c-objs := ssm2602-i2c.o 260 + snd-soc-ssm3515-objs := ssm3515.o 260 261 snd-soc-ssm4567-objs := ssm4567.o 261 262 snd-soc-sta32x-objs := sta32x.o 262 263 snd-soc-sta350-objs := sta350.o ··· 626 625 obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o 627 626 obj-$(CONFIG_SND_SOC_SSM2602_SPI) += snd-soc-ssm2602-spi.o 628 627 obj-$(CONFIG_SND_SOC_SSM2602_I2C) += snd-soc-ssm2602-i2c.o 628 + obj-$(CONFIG_SND_SOC_SSM3515) += snd-soc-ssm3515.o 629 629 obj-$(CONFIG_SND_SOC_SSM4567) += snd-soc-ssm4567.o 630 630 obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o 631 631 obj-$(CONFIG_SND_SOC_STA350) += snd-soc-sta350.o
+448
sound/soc/codecs/ssm3515.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only OR MIT 2 + // 3 + // Analog Devices' SSM3515 audio amp driver 4 + // 5 + // Copyright (C) The Asahi Linux Contributors 6 + 7 + #include <linux/bits.h> 8 + #include <linux/bitfield.h> 9 + #include <linux/device.h> 10 + #include <linux/i2c.h> 11 + #include <linux/module.h> 12 + #include <linux/of.h> 13 + #include <linux/regmap.h> 14 + 15 + #include <sound/pcm.h> 16 + #include <sound/pcm_params.h> 17 + #include <sound/soc.h> 18 + #include <sound/tlv.h> 19 + 20 + 21 + #define SSM3515_PWR 0x00 22 + #define SSM3515_PWR_APWDN_EN BIT(7) 23 + #define SSM3515_PWR_BSNS_PWDN BIT(6) 24 + #define SSM3515_PWR_S_RST BIT(1) 25 + #define SSM3515_PWR_SPWDN BIT(0) 26 + 27 + #define SSM3515_GEC 0x01 28 + #define SSM3515_GEC_EDGE BIT(4) 29 + #define SSM3515_GEC_EDGE_SHIFT 4 30 + #define SSM3515_GEC_ANA_GAIN GENMASK(1, 0) 31 + 32 + #define SSM3515_DAC 0x02 33 + #define SSM3515_DAC_HV BIT(7) 34 + #define SSM3515_DAC_MUTE BIT(6) 35 + #define SSM3515_DAC_HPF BIT(5) 36 + #define SSM3515_DAC_LPM BIT(4) 37 + #define SSM3515_DAC_FS GENMASK(2, 0) 38 + 39 + #define SSM3515_DAC_VOL 0x03 40 + 41 + #define SSM3515_SAI1 0x04 42 + #define SSM3515_SAI1_DAC_POL BIT(7) 43 + #define SSM3515_SAI1_BCLK_POL BIT(6) 44 + #define SSM3515_SAI1_TDM_BCLKS GENMASK(5, 3) 45 + #define SSM3515_SAI1_FSYNC_MODE BIT(2) 46 + #define SSM3515_SAI1_SDATA_FMT BIT(1) 47 + #define SSM3515_SAI1_SAI_MODE BIT(0) 48 + 49 + #define SSM3515_SAI2 0x05 50 + #define SSM3515_SAI2_DATA_WIDTH BIT(7) 51 + #define SSM3515_SAI2_AUTO_SLOT BIT(4) 52 + #define SSM3515_SAI2_TDM_SLOT GENMASK(3, 0) 53 + 54 + #define SSM3515_VBAT_OUT 0x06 55 + 56 + #define SSM3515_STATUS 0x0a 57 + #define SSM3515_STATUS_UVLO_REG BIT(6) 58 + #define SSM3515_STATUS_LIM_EG BIT(5) 59 + #define SSM3515_STATUS_CLIP BIT(4) 60 + #define SSM3515_STATUS_AMP_OC BIT(3) 61 + #define SSM3515_STATUS_OTF BIT(2) 62 + #define SSM3515_STATUS_OTW BIT(1) 63 + #define SSM3515_STATUS_BAT_WARN BIT(0) 64 + 65 + static bool ssm3515_volatile_reg(struct device *dev, unsigned int reg) 66 + { 67 + switch (reg) { 68 + case SSM3515_STATUS: 69 + case SSM3515_VBAT_OUT: 70 + return true; 71 + 72 + default: 73 + return false; 74 + } 75 + } 76 + 77 + static const struct reg_default ssm3515_reg_defaults[] = { 78 + { SSM3515_PWR, 0x81 }, 79 + { SSM3515_GEC, 0x01 }, 80 + { SSM3515_DAC, 0x32 }, 81 + { SSM3515_DAC_VOL, 0x40 }, 82 + { SSM3515_SAI1, 0x11 }, 83 + { SSM3515_SAI2, 0x00 }, 84 + }; 85 + 86 + static const struct regmap_config ssm3515_i2c_regmap = { 87 + .reg_bits = 8, 88 + .val_bits = 8, 89 + .volatile_reg = ssm3515_volatile_reg, 90 + .max_register = 0xb, 91 + .reg_defaults = ssm3515_reg_defaults, 92 + .num_reg_defaults = ARRAY_SIZE(ssm3515_reg_defaults), 93 + .cache_type = REGCACHE_FLAT, 94 + }; 95 + 96 + struct ssm3515_data { 97 + struct device *dev; 98 + struct regmap *regmap; 99 + }; 100 + 101 + // The specced range is -71.25...24.00 dB with step size of 0.375 dB, 102 + // and a mute item below that. This is represented by -71.62...24.00 dB 103 + // with the mute item mapped onto the low end. 104 + static DECLARE_TLV_DB_MINMAX_MUTE(ssm3515_dac_volume, -7162, 2400); 105 + 106 + static const char * const ssm3515_ana_gain_text[] = { 107 + "8.4 V Span", "12.6 V Span", "14 V Span", "15 V Span", 108 + }; 109 + 110 + static SOC_ENUM_SINGLE_DECL(ssm3515_ana_gain_enum, SSM3515_GEC, 111 + __bf_shf(SSM3515_GEC_ANA_GAIN), 112 + ssm3515_ana_gain_text); 113 + 114 + static const struct snd_kcontrol_new ssm3515_snd_controls[] = { 115 + SOC_SINGLE_TLV("DAC Playback Volume", SSM3515_DAC_VOL, 116 + 0, 255, 1, ssm3515_dac_volume), 117 + SOC_SINGLE("Low EMI Mode Switch", SSM3515_GEC, 118 + __bf_shf(SSM3515_GEC_EDGE), 1, 0), 119 + SOC_SINGLE("Soft Volume Ramping Switch", SSM3515_DAC, 120 + __bf_shf(SSM3515_DAC_HV), 1, 1), 121 + SOC_SINGLE("HPF Switch", SSM3515_DAC, 122 + __bf_shf(SSM3515_DAC_HPF), 1, 0), 123 + SOC_SINGLE("DAC Invert Switch", SSM3515_SAI1, 124 + __bf_shf(SSM3515_SAI1_DAC_POL), 1, 0), 125 + SOC_ENUM("DAC Analog Gain Select", ssm3515_ana_gain_enum), 126 + }; 127 + 128 + static void ssm3515_read_faults(struct snd_soc_component *component) 129 + { 130 + int ret; 131 + 132 + ret = snd_soc_component_read(component, SSM3515_STATUS); 133 + if (ret <= 0) { 134 + /* 135 + * If the read was erroneous, ASoC core has printed a message, 136 + * and that's all that's appropriate in handling the error here. 137 + */ 138 + return; 139 + } 140 + 141 + dev_err(component->dev, "device reports:%s%s%s%s%s%s%s\n", 142 + FIELD_GET(SSM3515_STATUS_UVLO_REG, ret) ? " voltage regulator fault" : "", 143 + FIELD_GET(SSM3515_STATUS_LIM_EG, ret) ? " limiter engaged" : "", 144 + FIELD_GET(SSM3515_STATUS_CLIP, ret) ? " clipping detected" : "", 145 + FIELD_GET(SSM3515_STATUS_AMP_OC, ret) ? " amp over-current fault" : "", 146 + FIELD_GET(SSM3515_STATUS_OTF, ret) ? " overtemperature fault" : "", 147 + FIELD_GET(SSM3515_STATUS_OTW, ret) ? " overtemperature warning" : "", 148 + FIELD_GET(SSM3515_STATUS_BAT_WARN, ret) ? " bat voltage low warning" : ""); 149 + } 150 + 151 + static int ssm3515_probe(struct snd_soc_component *component) 152 + { 153 + int ret; 154 + 155 + /* Start out muted */ 156 + ret = snd_soc_component_update_bits(component, SSM3515_DAC, 157 + SSM3515_DAC_MUTE, SSM3515_DAC_MUTE); 158 + if (ret < 0) 159 + return ret; 160 + 161 + /* Disable the 'master power-down' */ 162 + ret = snd_soc_component_update_bits(component, SSM3515_PWR, 163 + SSM3515_PWR_SPWDN, 0); 164 + if (ret < 0) 165 + return ret; 166 + 167 + return 0; 168 + } 169 + 170 + static int ssm3515_mute(struct snd_soc_dai *dai, int mute, int direction) 171 + { 172 + int ret; 173 + 174 + ret = snd_soc_component_update_bits(dai->component, 175 + SSM3515_DAC, 176 + SSM3515_DAC_MUTE, 177 + FIELD_PREP(SSM3515_DAC_MUTE, mute)); 178 + if (ret < 0) 179 + return ret; 180 + return 0; 181 + } 182 + 183 + static int ssm3515_hw_params(struct snd_pcm_substream *substream, 184 + struct snd_pcm_hw_params *params, 185 + struct snd_soc_dai *dai) 186 + { 187 + struct snd_soc_component *component = dai->component; 188 + int ret, rateval; 189 + 190 + switch (params_format(params)) { 191 + case SNDRV_PCM_FORMAT_S16: 192 + case SNDRV_PCM_FORMAT_S24: 193 + ret = snd_soc_component_update_bits(component, 194 + SSM3515_SAI2, SSM3515_SAI2_DATA_WIDTH, 195 + FIELD_PREP(SSM3515_SAI2_DATA_WIDTH, 196 + params_width(params) == 16)); 197 + if (ret < 0) 198 + return ret; 199 + break; 200 + 201 + default: 202 + return -EINVAL; 203 + } 204 + 205 + switch (params_rate(params)) { 206 + case 8000 ... 12000: 207 + rateval = 0; 208 + break; 209 + case 16000 ... 24000: 210 + rateval = 1; 211 + break; 212 + case 32000 ... 48000: 213 + rateval = 2; 214 + break; 215 + case 64000 ... 96000: 216 + rateval = 3; 217 + break; 218 + case 128000 ... 192000: 219 + rateval = 4; 220 + break; 221 + case 48001 ... 63999: /* this is ...72000 but overlaps */ 222 + rateval = 5; 223 + break; 224 + default: 225 + return -EINVAL; 226 + } 227 + 228 + ret = snd_soc_component_update_bits(component, 229 + SSM3515_DAC, SSM3515_DAC_FS, 230 + FIELD_PREP(SSM3515_DAC_FS, rateval)); 231 + if (ret < 0) 232 + return ret; 233 + 234 + return 0; 235 + } 236 + 237 + static int ssm3515_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 238 + { 239 + struct snd_soc_component *component = dai->component; 240 + bool fpol_inv = false; /* non-inverted: frame starts with low-to-high FSYNC */ 241 + int ret; 242 + u8 sai1 = 0; 243 + 244 + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 245 + case SND_SOC_DAIFMT_IB_NF: 246 + case SND_SOC_DAIFMT_IB_IF: 247 + sai1 |= SSM3515_SAI1_BCLK_POL; 248 + break; 249 + } 250 + 251 + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 252 + case SND_SOC_DAIFMT_I2S: 253 + fpol_inv = 1; 254 + sai1 &= ~SSM3515_SAI1_SDATA_FMT; /* 1 bit start delay */ 255 + break; 256 + case SND_SOC_DAIFMT_LEFT_J: 257 + fpol_inv = 0; 258 + sai1 |= SSM3515_SAI1_SDATA_FMT; /* no start delay */ 259 + break; 260 + default: 261 + return -EINVAL; 262 + } 263 + 264 + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 265 + case SND_SOC_DAIFMT_NB_IF: 266 + case SND_SOC_DAIFMT_IB_IF: 267 + fpol_inv ^= 1; 268 + break; 269 + } 270 + 271 + /* Set the serial input to 'TDM mode' */ 272 + sai1 |= SSM3515_SAI1_SAI_MODE; 273 + 274 + if (fpol_inv) { 275 + /* 276 + * We configure the codec in a 'TDM mode', in which the 277 + * FSYNC_MODE bit of SAI1 is supposed to select between 278 + * what the datasheet calls 'Pulsed FSYNC mode' and '50% 279 + * FSYNC mode'. 280 + * 281 + * Experiments suggest that this bit in fact simply selects 282 + * the FSYNC polarity, so go with that. 283 + */ 284 + sai1 |= SSM3515_SAI1_FSYNC_MODE; 285 + } 286 + 287 + ret = snd_soc_component_update_bits(component, SSM3515_SAI1, 288 + SSM3515_SAI1_BCLK_POL | SSM3515_SAI1_SDATA_FMT | 289 + SSM3515_SAI1_SAI_MODE | SSM3515_SAI1_FSYNC_MODE, sai1); 290 + 291 + if (ret < 0) 292 + return ret; 293 + return 0; 294 + } 295 + 296 + static int ssm3515_set_tdm_slot(struct snd_soc_dai *dai, 297 + unsigned int tx_mask, 298 + unsigned int rx_mask, 299 + int slots, int slot_width) 300 + { 301 + struct snd_soc_component *component = dai->component; 302 + int slot, tdm_bclks_val, ret; 303 + 304 + if (tx_mask == 0 || rx_mask != 0) 305 + return -EINVAL; 306 + 307 + slot = __ffs(tx_mask); 308 + 309 + if (tx_mask & ~BIT(slot)) 310 + return -EINVAL; 311 + 312 + switch (slot_width) { 313 + case 16: 314 + tdm_bclks_val = 0; 315 + break; 316 + case 24: 317 + tdm_bclks_val = 1; 318 + break; 319 + case 32: 320 + tdm_bclks_val = 2; 321 + break; 322 + case 48: 323 + tdm_bclks_val = 3; 324 + break; 325 + case 64: 326 + tdm_bclks_val = 4; 327 + break; 328 + default: 329 + return -EINVAL; 330 + } 331 + 332 + ret = snd_soc_component_update_bits(component, SSM3515_SAI1, 333 + SSM3515_SAI1_TDM_BCLKS, 334 + FIELD_PREP(SSM3515_SAI1_TDM_BCLKS, tdm_bclks_val)); 335 + if (ret < 0) 336 + return ret; 337 + 338 + ret = snd_soc_component_update_bits(component, SSM3515_SAI2, 339 + SSM3515_SAI2_TDM_SLOT, 340 + FIELD_PREP(SSM3515_SAI2_TDM_SLOT, slot)); 341 + if (ret < 0) 342 + return ret; 343 + 344 + return 0; 345 + } 346 + 347 + static int ssm3515_hw_free(struct snd_pcm_substream *substream, 348 + struct snd_soc_dai *dai) 349 + { 350 + /* 351 + * We don't get live notification of faults, so at least at 352 + * this time, when playback is over, check if we have tripped 353 + * over anything and if so, log it. 354 + */ 355 + ssm3515_read_faults(dai->component); 356 + return 0; 357 + } 358 + 359 + static const struct snd_soc_dai_ops ssm3515_dai_ops = { 360 + .mute_stream = ssm3515_mute, 361 + .hw_params = ssm3515_hw_params, 362 + .set_fmt = ssm3515_set_fmt, 363 + .set_tdm_slot = ssm3515_set_tdm_slot, 364 + .hw_free = ssm3515_hw_free, 365 + }; 366 + 367 + static struct snd_soc_dai_driver ssm3515_dai_driver = { 368 + .name = "SSM3515 SAI", 369 + .id = 0, 370 + .playback = { 371 + .stream_name = "Playback", 372 + .channels_min = 1, 373 + .channels_max = 1, 374 + .rates = SNDRV_PCM_RATE_CONTINUOUS, 375 + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, 376 + }, 377 + .ops = &ssm3515_dai_ops, 378 + }; 379 + 380 + static const struct snd_soc_dapm_widget ssm3515_dapm_widgets[] = { 381 + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), 382 + SND_SOC_DAPM_OUTPUT("OUT"), 383 + }; 384 + 385 + static const struct snd_soc_dapm_route ssm3515_dapm_routes[] = { 386 + {"OUT", NULL, "DAC"}, 387 + {"DAC", NULL, "Playback"}, 388 + }; 389 + 390 + static const struct snd_soc_component_driver ssm3515_asoc_component = { 391 + .probe = ssm3515_probe, 392 + .controls = ssm3515_snd_controls, 393 + .num_controls = ARRAY_SIZE(ssm3515_snd_controls), 394 + .dapm_widgets = ssm3515_dapm_widgets, 395 + .num_dapm_widgets = ARRAY_SIZE(ssm3515_dapm_widgets), 396 + .dapm_routes = ssm3515_dapm_routes, 397 + .num_dapm_routes = ARRAY_SIZE(ssm3515_dapm_routes), 398 + .endianness = 1, 399 + }; 400 + 401 + static int ssm3515_i2c_probe(struct i2c_client *client) 402 + { 403 + struct ssm3515_data *data; 404 + int ret; 405 + 406 + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); 407 + if (!data) 408 + return -ENOMEM; 409 + 410 + data->dev = &client->dev; 411 + i2c_set_clientdata(client, data); 412 + 413 + data->regmap = devm_regmap_init_i2c(client, &ssm3515_i2c_regmap); 414 + if (IS_ERR(data->regmap)) 415 + return dev_err_probe(data->dev, PTR_ERR(data->regmap), 416 + "initializing register map\n"); 417 + 418 + /* Perform a reset */ 419 + ret = regmap_update_bits(data->regmap, SSM3515_PWR, 420 + SSM3515_PWR_S_RST, SSM3515_PWR_S_RST); 421 + if (ret < 0) 422 + return dev_err_probe(data->dev, ret, 423 + "performing software reset\n"); 424 + regmap_reinit_cache(data->regmap, &ssm3515_i2c_regmap); 425 + 426 + return devm_snd_soc_register_component(data->dev, 427 + &ssm3515_asoc_component, 428 + &ssm3515_dai_driver, 1); 429 + } 430 + 431 + static const struct of_device_id ssm3515_of_match[] = { 432 + { .compatible = "adi,ssm3515" }, 433 + {} 434 + }; 435 + MODULE_DEVICE_TABLE(of, ssm3515_of_match); 436 + 437 + static struct i2c_driver ssm3515_i2c_driver = { 438 + .driver = { 439 + .name = "ssm3515", 440 + .of_match_table = of_match_ptr(ssm3515_of_match), 441 + }, 442 + .probe_new = ssm3515_i2c_probe, 443 + }; 444 + module_i2c_driver(ssm3515_i2c_driver); 445 + 446 + MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>"); 447 + MODULE_DESCRIPTION("ASoC SSM3515 audio amp driver"); 448 + MODULE_LICENSE("Dual MIT/GPL");