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

ASoC: codecs: Add NeoFidelity NTP8918 codec

The NeoFidelity NTP8918 is a two channel amplifier with mixer and
biquad filters.

Datasheet: https://datasheetspdf.com/pdf-down/N/T/P/NTP8918-NeoFidelity.pdf
Signed-off-by: Igor Prusov <ivprusov@salutedevices.com>
Link: https://patch.msgid.link/20240925-ntp-amps-8918-8835-v3-4-e2459a8191a6@salutedevices.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Igor Prusov and committed by
Mark Brown
2bd61fff 64fbb6bd

+404
+5
sound/soc/codecs/Kconfig
··· 2568 2568 config SND_SOC_NTPFW 2569 2569 tristate 2570 2570 2571 + config SND_SOC_NTP8918 2572 + select SND_SOC_NTPFW 2573 + tristate "NeoFidelity NTP8918 amplifier" 2574 + depends on I2C 2575 + 2571 2576 config SND_SOC_TPA6130A2 2572 2577 tristate "Texas Instruments TPA6130A2 headphone amplifier" 2573 2578 depends on I2C
+2
sound/soc/codecs/Makefile
··· 189 189 snd-soc-nau8822-y := nau8822.o 190 190 snd-soc-nau8824-y := nau8824.o 191 191 snd-soc-nau8825-y := nau8825.o 192 + snd-soc-ntp8918-y := ntp8918.o 192 193 snd-soc-ntpfw-y := ntpfw.o 193 194 snd-soc-hdmi-codec-y := hdmi-codec.o 194 195 snd-soc-pcm1681-y := pcm1681.o ··· 593 592 obj-$(CONFIG_SND_SOC_NAU8822) += snd-soc-nau8822.o 594 593 obj-$(CONFIG_SND_SOC_NAU8824) += snd-soc-nau8824.o 595 594 obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o 595 + obj-$(CONFIG_SND_SOC_NTP8918) += snd-soc-ntp8918.o 596 596 obj-$(CONFIG_SND_SOC_NTPFW) += snd-soc-ntpfw.o 597 597 obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o 598 598 obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o
+397
sound/soc/codecs/ntp8918.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Driver for the NTP8918 Audio Amplifier 4 + * 5 + * Copyright (c) 2024, SaluteDevices. All Rights Reserved. 6 + * 7 + * Author: Igor Prusov <ivprusov@salutedevices.com> 8 + */ 9 + 10 + #include <linux/kernel.h> 11 + #include <linux/clk.h> 12 + #include <linux/reset.h> 13 + #include <linux/i2c.h> 14 + #include <linux/regmap.h> 15 + #include <linux/clk.h> 16 + #include <linux/clk-provider.h> 17 + 18 + #include <sound/initval.h> 19 + #include <sound/core.h> 20 + #include <sound/pcm.h> 21 + #include <sound/pcm_params.h> 22 + #include <sound/soc.h> 23 + #include <sound/soc-component.h> 24 + #include <sound/tlv.h> 25 + 26 + #include "ntpfw.h" 27 + 28 + #define NTP8918_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ 29 + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) 30 + 31 + #define NTP8918_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ 32 + SNDRV_PCM_FMTBIT_S20_3LE | \ 33 + SNDRV_PCM_FMTBIT_S24_LE | \ 34 + SNDRV_PCM_FMTBIT_S32_LE) 35 + 36 + #define NTP8918_INPUT_FMT 0x0 37 + #define NTP8918_INPUT_FMT_MASTER_MODE BIT(0) 38 + #define NTP8918_INPUT_FMT_GSA_MODE BIT(1) 39 + #define NTP8918_GSA_FMT 0x1 40 + #define NTP8918_GSA_BS_MASK GENMASK(3, 2) 41 + #define NTP8918_GSA_BS(x) ((x) << 2) 42 + #define NTP8918_GSA_RIGHT_J BIT(0) 43 + #define NTP8918_GSA_LSB BIT(1) 44 + #define NTP8918_MCLK_FREQ_CTRL 0x2 45 + #define NTP8918_MCLK_FREQ_MCF GENMASK(1, 0) 46 + #define NTP8918_MASTER_VOL 0x0C 47 + #define NTP8918_CHNL_A_VOL 0x17 48 + #define NTP8918_CHNL_B_VOL 0x18 49 + #define NTP8918_SOFT_MUTE 0x33 50 + #define NTP8918_SOFT_MUTE_SM1 BIT(0) 51 + #define NTP8918_SOFT_MUTE_SM2 BIT(1) 52 + #define NTP8918_PWM_SWITCH 0x34 53 + #define NTP8918_PWM_MASK_CTRL0 0x35 54 + #define REG_MAX NTP8918_PWM_MASK_CTRL0 55 + 56 + #define NTP8918_FW_NAME "eq_8918.bin" 57 + #define NTP8918_FW_MAGIC 0x38393138 /* "8918" */ 58 + 59 + struct ntp8918_priv { 60 + struct i2c_client *i2c; 61 + struct clk *bck; 62 + struct reset_control *reset; 63 + unsigned int format; 64 + }; 65 + 66 + static const DECLARE_TLV_DB_SCALE(ntp8918_master_vol_scale, -12550, 50, 0); 67 + 68 + static const struct snd_kcontrol_new ntp8918_vol_control[] = { 69 + SOC_SINGLE_RANGE_TLV("Playback Volume", NTP8918_MASTER_VOL, 0, 70 + 0x04, 0xff, 0, ntp8918_master_vol_scale), 71 + SOC_SINGLE("Playback Switch", NTP8918_PWM_MASK_CTRL0, 1, 1, 1), 72 + }; 73 + 74 + static void ntp8918_reset_gpio(struct ntp8918_priv *ntp8918) 75 + { 76 + /* 77 + * Proper initialization sequence for NTP8918 amplifier requires driving 78 + * /RESET signal low during power up for at least 0.1us. The sequence is, 79 + * according to NTP8918 datasheet, 6.2 Timing Sequence 1: 80 + * Deassert for T2 >= 1ms... 81 + */ 82 + reset_control_deassert(ntp8918->reset); 83 + fsleep(1000); 84 + 85 + /* ...Assert for T3 >= 0.1us... */ 86 + reset_control_assert(ntp8918->reset); 87 + fsleep(1); 88 + 89 + /* ...Deassert, and wait for T4 >= 0.5ms before sound on sequence. */ 90 + reset_control_deassert(ntp8918->reset); 91 + fsleep(500); 92 + } 93 + 94 + static const struct reg_sequence ntp8918_sound_off[] = { 95 + { NTP8918_MASTER_VOL, 0 }, 96 + }; 97 + 98 + static const struct reg_sequence ntp8918_sound_on[] = { 99 + { NTP8918_MASTER_VOL, 0b11 }, 100 + }; 101 + 102 + static int ntp8918_load_firmware(struct ntp8918_priv *ntp8918) 103 + { 104 + int ret; 105 + 106 + ret = ntpfw_load(ntp8918->i2c, NTP8918_FW_NAME, NTP8918_FW_MAGIC); 107 + if (ret == -ENOENT) { 108 + dev_warn_once(&ntp8918->i2c->dev, "Could not find firmware %s\n", 109 + NTP8918_FW_NAME); 110 + return 0; 111 + } 112 + 113 + return ret; 114 + } 115 + 116 + static int ntp8918_snd_suspend(struct snd_soc_component *component) 117 + { 118 + struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component); 119 + 120 + regcache_cache_only(component->regmap, true); 121 + 122 + regmap_multi_reg_write_bypassed(component->regmap, 123 + ntp8918_sound_off, 124 + ARRAY_SIZE(ntp8918_sound_off)); 125 + 126 + /* 127 + * According to NTP8918 datasheet, 6.2 Timing Sequence 1: 128 + * wait after sound off for T6 >= 0.5ms 129 + */ 130 + fsleep(500); 131 + reset_control_assert(ntp8918->reset); 132 + 133 + regcache_mark_dirty(component->regmap); 134 + clk_disable_unprepare(ntp8918->bck); 135 + 136 + return 0; 137 + } 138 + 139 + static int ntp8918_snd_resume(struct snd_soc_component *component) 140 + { 141 + struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component); 142 + int ret; 143 + 144 + ret = clk_prepare_enable(ntp8918->bck); 145 + if (ret) 146 + return ret; 147 + 148 + ntp8918_reset_gpio(ntp8918); 149 + 150 + regmap_multi_reg_write_bypassed(component->regmap, 151 + ntp8918_sound_on, 152 + ARRAY_SIZE(ntp8918_sound_on)); 153 + 154 + ret = ntp8918_load_firmware(ntp8918); 155 + if (ret) { 156 + dev_err(&ntp8918->i2c->dev, "Failed to load firmware\n"); 157 + return ret; 158 + } 159 + 160 + regcache_cache_only(component->regmap, false); 161 + snd_soc_component_cache_sync(component); 162 + 163 + return 0; 164 + } 165 + 166 + static int ntp8918_probe(struct snd_soc_component *component) 167 + { 168 + int ret; 169 + struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component); 170 + struct device *dev = component->dev; 171 + 172 + ret = snd_soc_add_component_controls(component, ntp8918_vol_control, 173 + ARRAY_SIZE(ntp8918_vol_control)); 174 + if (ret) 175 + return dev_err_probe(dev, ret, "Failed to add controls\n"); 176 + 177 + ret = ntp8918_load_firmware(ntp8918); 178 + if (ret) 179 + return dev_err_probe(dev, ret, "Failed to load firmware\n"); 180 + 181 + return 0; 182 + } 183 + 184 + static const struct snd_soc_dapm_widget ntp8918_dapm_widgets[] = { 185 + SND_SOC_DAPM_DAC("AIFIN", "Playback", SND_SOC_NOPM, 0, 0), 186 + 187 + SND_SOC_DAPM_OUTPUT("OUT1"), 188 + SND_SOC_DAPM_OUTPUT("OUT2"), 189 + }; 190 + 191 + static const struct snd_soc_dapm_route ntp8918_dapm_routes[] = { 192 + { "OUT1", NULL, "AIFIN" }, 193 + { "OUT2", NULL, "AIFIN" }, 194 + }; 195 + 196 + static const struct snd_soc_component_driver soc_component_ntp8918 = { 197 + .probe = ntp8918_probe, 198 + .suspend = ntp8918_snd_suspend, 199 + .resume = ntp8918_snd_resume, 200 + .dapm_widgets = ntp8918_dapm_widgets, 201 + .num_dapm_widgets = ARRAY_SIZE(ntp8918_dapm_widgets), 202 + .dapm_routes = ntp8918_dapm_routes, 203 + .num_dapm_routes = ARRAY_SIZE(ntp8918_dapm_routes), 204 + }; 205 + 206 + static int ntp8918_hw_params(struct snd_pcm_substream *substream, 207 + struct snd_pcm_hw_params *params, 208 + struct snd_soc_dai *dai) 209 + { 210 + struct snd_soc_component *component = dai->component; 211 + struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component); 212 + unsigned int input_fmt = 0; 213 + unsigned int gsa_fmt = 0; 214 + unsigned int gsa_fmt_mask; 215 + unsigned int mcf; 216 + int bclk; 217 + int ret; 218 + 219 + bclk = snd_soc_params_to_bclk(params); 220 + switch (bclk) { 221 + case 3072000: 222 + case 2822400: 223 + mcf = 0; 224 + break; 225 + case 6144000: 226 + mcf = 1; 227 + break; 228 + case 2048000: 229 + mcf = 2; 230 + break; 231 + default: 232 + return -EINVAL; 233 + } 234 + 235 + ret = snd_soc_component_update_bits(component, NTP8918_MCLK_FREQ_CTRL, 236 + NTP8918_MCLK_FREQ_MCF, mcf); 237 + if (ret) 238 + return ret; 239 + 240 + switch (ntp8918->format) { 241 + case SND_SOC_DAIFMT_I2S: 242 + break; 243 + case SND_SOC_DAIFMT_RIGHT_J: 244 + input_fmt |= NTP8918_INPUT_FMT_GSA_MODE; 245 + gsa_fmt |= NTP8918_GSA_RIGHT_J; 246 + break; 247 + case SND_SOC_DAIFMT_LEFT_J: 248 + input_fmt |= NTP8918_INPUT_FMT_GSA_MODE; 249 + break; 250 + } 251 + 252 + ret = snd_soc_component_update_bits(component, NTP8918_INPUT_FMT, 253 + NTP8918_INPUT_FMT_MASTER_MODE | 254 + NTP8918_INPUT_FMT_GSA_MODE, 255 + input_fmt); 256 + 257 + if (!(input_fmt & NTP8918_INPUT_FMT_GSA_MODE) || ret < 0) 258 + return ret; 259 + 260 + switch (params_width(params)) { 261 + case 24: 262 + gsa_fmt |= NTP8918_GSA_BS(0); 263 + break; 264 + case 20: 265 + gsa_fmt |= NTP8918_GSA_BS(1); 266 + break; 267 + case 18: 268 + gsa_fmt |= NTP8918_GSA_BS(2); 269 + break; 270 + case 16: 271 + gsa_fmt |= NTP8918_GSA_BS(3); 272 + break; 273 + default: 274 + return -EINVAL; 275 + } 276 + 277 + gsa_fmt_mask = NTP8918_GSA_BS_MASK | 278 + NTP8918_GSA_RIGHT_J | 279 + NTP8918_GSA_LSB; 280 + return snd_soc_component_update_bits(component, NTP8918_GSA_FMT, 281 + gsa_fmt_mask, gsa_fmt); 282 + } 283 + 284 + static int ntp8918_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 285 + { 286 + struct snd_soc_component *component = dai->component; 287 + struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component); 288 + 289 + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 290 + case SND_SOC_DAIFMT_I2S: 291 + case SND_SOC_DAIFMT_RIGHT_J: 292 + case SND_SOC_DAIFMT_LEFT_J: 293 + ntp8918->format = fmt & SND_SOC_DAIFMT_FORMAT_MASK; 294 + break; 295 + default: 296 + return -EINVAL; 297 + } 298 + return 0; 299 + } 300 + 301 + static int ntp8918_digital_mute(struct snd_soc_dai *dai, int mute, int stream) 302 + { 303 + unsigned int mute_mask = NTP8918_SOFT_MUTE_SM1 | 304 + NTP8918_SOFT_MUTE_SM2; 305 + 306 + return snd_soc_component_update_bits(dai->component, NTP8918_SOFT_MUTE, 307 + mute_mask, mute ? mute_mask : 0); 308 + } 309 + 310 + static const struct snd_soc_dai_ops ntp8918_dai_ops = { 311 + .hw_params = ntp8918_hw_params, 312 + .set_fmt = ntp8918_set_fmt, 313 + .mute_stream = ntp8918_digital_mute, 314 + }; 315 + 316 + static struct snd_soc_dai_driver ntp8918_dai = { 317 + .name = "ntp8918-amplifier", 318 + .playback = { 319 + .stream_name = "Playback", 320 + .channels_min = 1, 321 + .channels_max = 2, 322 + .rates = NTP8918_RATES, 323 + .formats = NTP8918_FORMATS, 324 + }, 325 + .ops = &ntp8918_dai_ops, 326 + }; 327 + 328 + static const struct regmap_config ntp8918_regmap = { 329 + .reg_bits = 8, 330 + .val_bits = 8, 331 + .max_register = REG_MAX, 332 + .cache_type = REGCACHE_MAPLE, 333 + }; 334 + 335 + static int ntp8918_i2c_probe(struct i2c_client *i2c) 336 + { 337 + struct ntp8918_priv *ntp8918; 338 + int ret; 339 + struct regmap *regmap; 340 + 341 + ntp8918 = devm_kzalloc(&i2c->dev, sizeof(*ntp8918), GFP_KERNEL); 342 + if (!ntp8918) 343 + return -ENOMEM; 344 + 345 + ntp8918->i2c = i2c; 346 + 347 + ntp8918->reset = devm_reset_control_get_shared(&i2c->dev, NULL); 348 + if (IS_ERR(ntp8918->reset)) 349 + return dev_err_probe(&i2c->dev, PTR_ERR(ntp8918->reset), "Failed to get reset\n"); 350 + 351 + dev_set_drvdata(&i2c->dev, ntp8918); 352 + 353 + ntp8918_reset_gpio(ntp8918); 354 + 355 + regmap = devm_regmap_init_i2c(i2c, &ntp8918_regmap); 356 + if (IS_ERR(regmap)) 357 + return dev_err_probe(&i2c->dev, PTR_ERR(regmap), 358 + "Failed to allocate regmap\n"); 359 + 360 + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_ntp8918, 361 + &ntp8918_dai, 1); 362 + if (ret) 363 + return dev_err_probe(&i2c->dev, ret, 364 + "Failed to register component\n"); 365 + 366 + ntp8918->bck = devm_clk_get_enabled(&i2c->dev, "bck"); 367 + if (IS_ERR(ntp8918->bck)) 368 + return dev_err_probe(&i2c->dev, PTR_ERR(ntp8918->bck), "failed to get bck clock\n"); 369 + 370 + return 0; 371 + } 372 + 373 + static const struct i2c_device_id ntp8918_i2c_id[] = { 374 + { "ntp8918", 0 }, 375 + {} 376 + }; 377 + MODULE_DEVICE_TABLE(i2c, ntp8918_i2c_id); 378 + 379 + static const struct of_device_id ntp8918_of_match[] = { 380 + {.compatible = "neofidelity,ntp8918"}, 381 + {} 382 + }; 383 + MODULE_DEVICE_TABLE(of, ntp8918_of_match); 384 + 385 + static struct i2c_driver ntp8918_i2c_driver = { 386 + .probe = ntp8918_i2c_probe, 387 + .id_table = ntp8918_i2c_id, 388 + .driver = { 389 + .name = "ntp8918", 390 + .of_match_table = ntp8918_of_match, 391 + }, 392 + }; 393 + module_i2c_driver(ntp8918_i2c_driver); 394 + 395 + MODULE_AUTHOR("Igor Prusov <ivprusov@salutedevices.com>"); 396 + MODULE_DESCRIPTION("NTP8918 Audio Amplifier Driver"); 397 + MODULE_LICENSE("GPL");