diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index ef16d58b29499d..b33e5e984ef186 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1768,6 +1768,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) {"CSC3557", }, {"INT33FE", }, {"INT3515", }, + {"MAX98390", }, {"TXNW2781", }, /* Non-conforming _HID for Cirrus Logic already released */ {"CLSA0100", }, diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c index db030b0f176a24..b41f0cca10e94e 100644 --- a/drivers/platform/x86/serial-multi-instantiate.c +++ b/drivers/platform/x86/serial-multi-instantiate.c @@ -395,6 +395,17 @@ static const struct smi_node tas2781_hda = { .bus_type = SMI_AUTO_DETECT, }; +static const struct smi_node max98390_hda = { + .instances = { + { "max98390-hda", IRQ_RESOURCE_NONE, 0 }, + { "max98390-hda", IRQ_RESOURCE_NONE, 0 }, + { "max98390-hda", IRQ_RESOURCE_NONE, 0 }, + { "max98390-hda", IRQ_RESOURCE_NONE, 0 }, + {} + }, + .bus_type = SMI_I2C, +}; + /* * Note new device-ids must also be added to ignore_serial_bus_ids in * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). @@ -407,6 +418,7 @@ static const struct acpi_device_id smi_acpi_ids[] = { { "CSC3556", (unsigned long)&cs35l56_hda }, { "CSC3557", (unsigned long)&cs35l57_hda }, { "INT3515", (unsigned long)&int3515_data }, + { "MAX98390", (unsigned long)&max98390_hda }, { "TXNW2781", (unsigned long)&tas2781_hda }, /* Non-conforming _HID for Cirrus Logic already released */ { "CLSA0100", (unsigned long)&cs35l41_hda }, diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index c0a6acd7d42dc0..c44a774672a737 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2989,6 +2989,11 @@ static void cs35l41_fixup_spi_four(struct hda_codec *codec, const struct hda_fix comp_generic_fixup(codec, action, "spi", "CSC3551", "-%s:00-cs35l41-hda.%d", 4); } +static void max98390_fixup_i2c_four(struct hda_codec *codec, const struct hda_fixup *fix, int action) +{ + comp_generic_fixup(codec, action, "i2c", "MAX98390", "-%s:00-max98390-hda.%d", 4); +} + static void alc287_fixup_legion_16achg6_speakers(struct hda_codec *cdc, const struct hda_fixup *fix, int action) { @@ -3678,6 +3683,7 @@ enum { ALC298_FIXUP_SAMSUNG_AMP, ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, + ALC298_FIXUP_SAMSUNG_MAX98390_4_AMPS, ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, @@ -5354,6 +5360,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_samsung_amp_v2_4_amps }, + [ALC298_FIXUP_SAMSUNG_MAX98390_4_AMPS] = { + .type = HDA_FIXUP_FUNC, + .v.func = max98390_fixup_i2c_four + }, [ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -6975,6 +6985,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc832, "Samsung Galaxy Book Flex Alpha (NP730QCJ)", ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET), SND_PCI_QUIRK(0x144d, 0xca03, "Samsung Galaxy Book2 Pro 360 (NP930QED)", ALC298_FIXUP_SAMSUNG_AMP), SND_PCI_QUIRK(0x144d, 0xca06, "Samsung Galaxy Book3 360 (NP730QFG)", ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET), + SND_PCI_QUIRK(0x144d, 0xca07, "Samsung Galaxy Book4 Pro 14-inch (NP940XGK)", ALC298_FIXUP_SAMSUNG_MAX98390_4_AMPS), + SND_PCI_QUIRK(0x144d, 0xc890, "Samsung Galaxy Book4 Pro 16 inch (NP960XGK)", ALC298_FIXUP_SAMSUNG_MAX98390_4_AMPS), + SND_PCI_QUIRK(0x144d, 0xc892, "Samsung Galaxy Book4 Pro 360 (NP960QGK)", ALC298_FIXUP_SAMSUNG_MAX98390_4_AMPS), + SND_PCI_QUIRK(0x144d, 0xc1d8, "Samsung Galaxy Book4 Ultra (NP960XGL)", ALC298_FIXUP_SAMSUNG_MAX98390_4_AMPS), + SND_PCI_QUIRK(0x144d, 0xc1da, "Samsung Galaxy Book5 Pro 360 (NP960QHA)", ALC298_FIXUP_SAMSUNG_MAX98390_4_AMPS), SND_PCI_QUIRK(0x144d, 0xc868, "Samsung Galaxy Book2 Pro (NP930XED)", ALC298_FIXUP_SAMSUNG_AMP), SND_PCI_QUIRK(0x144d, 0xc870, "Samsung Galaxy Book2 Pro (NP950XED)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), SND_PCI_QUIRK(0x144d, 0xc872, "Samsung Galaxy Book2 Pro (NP950XEE)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), @@ -7478,6 +7493,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC298_FIXUP_SAMSUNG_AMP, .name = "alc298-samsung-amp"}, {.id = ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, .name = "alc298-samsung-amp-v2-2-amps"}, {.id = ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, .name = "alc298-samsung-amp-v2-4-amps"}, + {.id = ALC298_FIXUP_SAMSUNG_MAX98390_4_AMPS, .name = "alc298-samsung-max98390-4-amps"}, {.id = ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, .name = "alc256-samsung-headphone"}, {.id = ALC255_FIXUP_XIAOMI_HEADSET_MIC, .name = "alc255-xiaomi-headset"}, {.id = ALC274_FIXUP_HP_MIC, .name = "alc274-hp-mic-detect"}, diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig index f674e9a9c7d7af..9e9d82e016472b 100644 --- a/sound/hda/codecs/side-codecs/Kconfig +++ b/sound/hda/codecs/side-codecs/Kconfig @@ -141,3 +141,22 @@ config SND_HDA_SCODEC_TAS2781_SPI comment "Set to Y if you want auto-loading the side codec driver" depends on SND_HDA=y && SND_HDA_SCODEC_TAS2781_SPI=m + +config SND_HDA_SCODEC_MAX98390 + tristate + select SND_HDA_GENERIC + select SND_HDA_SCODEC_COMPONENT + +config SND_HDA_SCODEC_MAX98390_I2C + tristate "Build MAX98390 HD-audio side codec support for I2C Bus" + depends on I2C + depends on ACPI + depends on SND_SOC + select SND_SOC_MAX98390 + select SND_HDA_SCODEC_MAX98390 + help + Say Y or M here to include MAX98390 I2C HD-audio side codec support + in snd-hda-intel driver, such as ALC298. + +comment "Set to Y if you want auto-loading the side codec driver" + depends on SND_HDA=y && SND_HDA_SCODEC_MAX98390_I2C=m diff --git a/sound/hda/codecs/side-codecs/Makefile b/sound/hda/codecs/side-codecs/Makefile index 245e84f6a121d6..e80857adcaf38e 100644 --- a/sound/hda/codecs/side-codecs/Makefile +++ b/sound/hda/codecs/side-codecs/Makefile @@ -13,6 +13,8 @@ snd-hda-scodec-component-y := hda_component.o snd-hda-scodec-tas2781-y := tas2781_hda.o snd-hda-scodec-tas2781-i2c-y := tas2781_hda_i2c.o snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o +snd-hda-scodec-max98390-y := max98390_hda.o max98390_hda_filters.o +snd-hda-scodec-max98390-i2c-y := max98390_hda_i2c.o obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC) += snd-hda-cirrus-scodec.o obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC_KUNIT_TEST) += snd-hda-cirrus-scodec-test.o @@ -26,3 +28,5 @@ obj-$(CONFIG_SND_HDA_SCODEC_COMPONENT) += snd-hda-scodec-component.o obj-$(CONFIG_SND_HDA_SCODEC_TAS2781) += snd-hda-scodec-tas2781.o obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_I2C) += snd-hda-scodec-tas2781-i2c.o obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_SPI) += snd-hda-scodec-tas2781-spi.o +obj-$(CONFIG_SND_HDA_SCODEC_MAX98390) += snd-hda-scodec-max98390.o +obj-$(CONFIG_SND_HDA_SCODEC_MAX98390_I2C) += snd-hda-scodec-max98390-i2c.o diff --git a/sound/hda/codecs/side-codecs/max98390_hda.c b/sound/hda/codecs/side-codecs/max98390_hda.c new file mode 100644 index 00000000000000..869169b3915ba3 --- /dev/null +++ b/sound/hda/codecs/side-codecs/max98390_hda.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// MAX98390 HDA driver +// + +#include +#include +#include +#include +#include "hda_local.h" +#include "hda_component.h" +#include "../generic.h" +#include "max98390_hda.h" +#include "max98390_hda_filters.h" +#include "../../../soc/codecs/max98390.h" + +static void max98390_hda_playback_hook(struct device *dev, int action) +{ + struct max98390_hda_priv *priv = dev_get_drvdata(dev); + int ret; + + switch (action) { + case HDA_GEN_PCM_ACT_OPEN: + + /* Enable global and speaker amp */ + ret = regmap_write(priv->regmap, MAX98390_R23FF_GLOBAL_EN, 0x01); + if (ret < 0) + dev_err(dev, "Failed to write GLOBAL_EN: %d\n", ret); + + ret = regmap_write(priv->regmap, MAX98390_R203A_AMP_EN, 0x81); + if (ret < 0) + dev_err(dev, "Failed to write AMP_EN: %d\n", ret); + + break; + + case HDA_GEN_PCM_ACT_CLOSE: + /* Disable speaker amp and global */ + regmap_write(priv->regmap, MAX98390_R203A_AMP_EN, 0x80); + regmap_write(priv->regmap, MAX98390_R23FF_GLOBAL_EN, 0x00); + break; + + default: + break; + } +} + +static int max98390_hda_bind(struct device *dev, struct device *master, void *master_data) +{ + struct max98390_hda_priv *priv = dev_get_drvdata(dev); + struct hda_component_parent *parent = master_data; + struct hda_component *comp; + + comp = hda_component_from_index(parent, priv->index); + if (!comp) + return -EINVAL; + + comp->dev = dev; + strscpy(comp->name, dev_name(dev), sizeof(comp->name)); + comp->playback_hook = max98390_hda_playback_hook; + + dev_info(dev, "MAX98390 HDA component bound (index %d)\n", priv->index); + + return 0; +} + +static void max98390_hda_unbind(struct device *dev, struct device *master, void *master_data) +{ + struct max98390_hda_priv *priv = dev_get_drvdata(dev); + struct hda_component_parent *parent = master_data; + struct hda_component *comp; + + comp = hda_component_from_index(parent, priv->index); + if (comp && comp->dev == dev) { + comp->dev = NULL; + memset(comp->name, 0, sizeof(comp->name)); + comp->playback_hook = NULL; + } + + dev_info(dev, "MAX98390 HDA component unbound\n"); +} + +static const struct component_ops max98390_hda_comp_ops = { + .bind = max98390_hda_bind, + .unbind = max98390_hda_unbind, +}; + +static int max98390_hda_init(struct max98390_hda_priv *priv) +{ + int ret; + unsigned int reg, global_en, amp_en, pcm_rx; + + /* Check device ID */ + ret = regmap_read(priv->regmap, MAX98390_R24FF_REV_ID, ®); + if (ret < 0) { + return ret; + } + + /* Software reset */ + ret = regmap_write(priv->regmap, MAX98390_SOFTWARE_RESET, 0x01); + if (ret < 0) { + return ret; + } + msleep(20); + + /* Basic register initialization (minimal setup for HDA) */ + regmap_write(priv->regmap, MAX98390_CLK_MON, 0x6f); + regmap_write(priv->regmap, MAX98390_DAT_MON, 0x00); + regmap_write(priv->regmap, MAX98390_PWR_GATE_CTL, 0x00); + regmap_write(priv->regmap, MAX98390_PCM_RX_EN_A, 0x03); + regmap_write(priv->regmap, MAX98390_ENV_TRACK_VOUT_HEADROOM, 0x0e); + regmap_write(priv->regmap, MAX98390_BOOST_BYPASS1, 0x46); + regmap_write(priv->regmap, MAX98390_FET_SCALING3, 0x03); + + /* PCM/I2S configuration - CRITICAL for correct audio format */ + /* 0xC0 = I2S mode, 32-bit samples (standard for HDA) */ + regmap_write(priv->regmap, MAX98390_PCM_MODE_CFG, 0xc0); + regmap_write(priv->regmap, MAX98390_PCM_MASTER_MODE, 0x1c); + regmap_write(priv->regmap, MAX98390_PCM_CLK_SETUP, 0x44); + regmap_write(priv->regmap, MAX98390_PCM_SR_SETUP, 0x08); + + /* RESET EN - Write 0x00 to 0x23FF */ + regmap_write(priv->regmap, MAX98390_R23FF_GLOBAL_EN, 0x00); + + /* Wait 50ms */ + msleep(50); + + /* RESET SPK_EN - Write 0x80 to 0x203A */ + regmap_write(priv->regmap, MAX98390_R203A_AMP_EN, 0x80); + + /* RESET DSP_GLOBAL_EN - Write 0x00 to 0x23E1 */ + regmap_write(priv->regmap, MAX98390_R23E1_DSP_GLOBAL_EN, 0x00); + + /* Step 6: Write non-DSM registers (0x2000-0x2084) - done in configure_filters */ + /* Step 7: Write DSM registers (0x2100-0x23E0) - done in configure_filters */ + /* Step 8-10: Enable DSP and amp - done in configure_filters */ + max98390_configure_filters(priv); + + return 0; +} + +int max98390_hda_probe(struct device *dev, const char *device_name, + int id, int irq, struct regmap *regmap, + enum max98390_hda_bus_type bus_type, int i2c_addr) +{ + struct max98390_hda_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->regmap = regmap; + priv->bus_type = bus_type; + priv->irq = irq; + priv->index = id; + priv->i2c_addr = i2c_addr; + dev_set_drvdata(dev, priv); + + ret = max98390_hda_init(priv); + if (ret) + return ret; + + ret = component_add(dev, &max98390_hda_comp_ops); + if (ret) { + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(max98390_hda_probe, "SND_HDA_SCODEC_MAX98390"); + +void max98390_hda_remove(struct device *dev) +{ + struct max98390_hda_priv *priv = dev_get_drvdata(dev); + + component_del(dev, &max98390_hda_comp_ops); + + if (priv && priv->regmap) { + /* Disable amp on removal */ + regmap_write(priv->regmap, MAX98390_R203A_AMP_EN, 0x80); + } +} +EXPORT_SYMBOL_NS_GPL(max98390_hda_remove, "SND_HDA_SCODEC_MAX98390"); + +static int max98390_hda_runtime_suspend(struct device *dev) +{ + struct max98390_hda_priv *priv = dev_get_drvdata(dev); + + regmap_write(priv->regmap, MAX98390_R203A_AMP_EN, 0x80); + regcache_cache_only(priv->regmap, true); + regcache_mark_dirty(priv->regmap); + + return 0; +} + +static int max98390_hda_runtime_resume(struct device *dev) +{ + struct max98390_hda_priv *priv = dev_get_drvdata(dev); + + regcache_cache_only(priv->regmap, false); + regcache_sync(priv->regmap); + + return 0; +} + +const struct dev_pm_ops max98390_hda_pm_ops = { + RUNTIME_PM_OPS(max98390_hda_runtime_suspend, max98390_hda_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS_GPL(max98390_hda_pm_ops, "SND_HDA_SCODEC_MAX98390"); + +MODULE_DESCRIPTION("HDA MAX98390 side codec library"); +MODULE_AUTHOR("Kevin Cuperus "); +MODULE_LICENSE("GPL"); diff --git a/sound/hda/codecs/side-codecs/max98390_hda.h b/sound/hda/codecs/side-codecs/max98390_hda.h new file mode 100644 index 00000000000000..6c9f648e0a5c6d --- /dev/null +++ b/sound/hda/codecs/side-codecs/max98390_hda.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MAX98390 HDA audio driver + */ + +#ifndef __MAX98390_HDA_H__ +#define __MAX98390_HDA_H__ + +#include +#include + +enum max98390_hda_bus_type { + MAX98390_HDA_I2C, +}; + +struct max98390_hda_priv { + struct device *dev; + struct regmap *regmap; + enum max98390_hda_bus_type bus_type; + int irq; + int index; + const char *acpi_subsystem_id; + int i2c_addr; /* I2C address for speaker identification */ +}; + +int max98390_hda_probe(struct device *dev, const char *device_name, + int id, int irq, struct regmap *regmap, + enum max98390_hda_bus_type bus_type, int i2c_addr); +void max98390_hda_remove(struct device *dev); + +extern const struct dev_pm_ops max98390_hda_pm_ops; + +#endif /* __MAX98390_HDA_H__ */ diff --git a/sound/hda/codecs/side-codecs/max98390_hda_filters.c b/sound/hda/codecs/side-codecs/max98390_hda_filters.c new file mode 100644 index 00000000000000..ca4f064b50897a --- /dev/null +++ b/sound/hda/codecs/side-codecs/max98390_hda_filters.c @@ -0,0 +1,334 @@ +// High-pass filter configuration for MAX98390 HDA driver +// This file contains the filter configuration functions + +// Based on typical 4-speaker configurations +// Tweeters are typically at lower I2C addresses +// Woofers are typically at higher I2C addresses +// This pattern may vary by manufacturer + +#include +#include +#include "max98390_hda.h" +#include "max98390_hda_filters.h" +#include "../../../soc/codecs/max98390.h" + +void max98390_configure_filters(struct max98390_hda_priv *priv) +{ + unsigned int i2c_addr = priv->i2c_addr; + bool is_tweeter = false; + int pcm_channel = 0; + + // Identify speaker based on I2C address + // Configure PCM input channel for stereo separation + switch (i2c_addr) { + case 0x38: + is_tweeter = false; + pcm_channel = 0; // Left channel + break; + case 0x39: + is_tweeter = false; + pcm_channel = 1; // Right channel + break; + case 0x3C: + is_tweeter = true; + pcm_channel = 0; // Left channel + break; + case 0x3D: + is_tweeter = true; + pcm_channel = 1; // Right channel + break; + default: + dev_warn(priv->dev, "Unknown speaker at 0x%02x - defaulting to LEFT CH0\n", i2c_addr); + is_tweeter = false; + pcm_channel = 0; + } + + // Configure PCM input channel for stereo separation + // MAX98390_PCM_CH_SRC_1 register bits [3:0] = speaker source + // 0 = PCM Channel 0 (LEFT), 1 = PCM Channel 1 (RIGHT) + regmap_write(priv->regmap, 0x2021, pcm_channel); + + // Load appropriate DSM firmware for woofers AND tweeters + // Tweeters get high-pass filter, woofers get full-range with small boost for bass + max98390_configure_high_pass_filter(priv, 3000, is_tweeter); +} + +// Complete DSM firmware blobs from Google Redrix +// These firmwares configure ALL DSM registers (0x2050-0x23E0) +// Total: 913 bytes each +#define MAX98390_DSM_START_ADDR 0x2050 +#define MAX98390_DSM_PARAM_SIZE 913 + +// Woofer DSM firmware (for 0x38, 0x39) +static const u8 max98390_dsm_firmware_woofer[] = { + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, // 0x2050 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2060 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x80, 0x07, 0x07, 0x01, 0x00, 0x4A, 0x2B, 0x08, 0x00, // 0x2070 + 0x03, 0x03, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2080 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2090 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20A0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20D0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20E0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20F0 + 0x00, 0xDF, 0x58, 0x0F, 0x00, 0x41, 0x4E, 0xE1, 0x00, 0xDF, 0x58, 0x0F, 0x00, 0x13, 0x55, 0xE1, // 0x2100 + 0x00, 0x91, 0xB8, 0x0E, 0x00, 0x69, 0x03, 0x00, 0x00, 0xD2, 0x06, 0x00, 0x00, 0x69, 0x03, 0x00, // 0x2110 + 0x00, 0x13, 0x55, 0xE1, 0x00, 0x91, 0xB8, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2120 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2130 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2140 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2150 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2160 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2170 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2180 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2190 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x21A0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x21B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0xBB, 0x0F, 0x00, 0xB7, 0x23, 0xE1, // 0x21C0 + 0x00, 0xD7, 0x2E, 0x0F, 0x00, 0xB6, 0x21, 0xE1, 0x00, 0x01, 0xE8, 0x0E, 0x00, 0xE1, 0x1B, 0x0F, // 0x21D0 + 0x00, 0x3D, 0xC8, 0xE1, 0x00, 0xE1, 0x1B, 0x0F, 0x00, 0xF3, 0xD4, 0xE1, 0x00, 0x79, 0x44, 0x0E, // 0x21E0 + 0x00, 0x5B, 0x06, 0x00, 0x00, 0xB6, 0x0C, 0x00, 0x00, 0x5B, 0x06, 0x00, 0x00, 0xF3, 0xD4, 0xE1, // 0x21F0 + 0x00, 0x79, 0x44, 0x0E, 0x00, 0x25, 0xB5, 0x0F, 0x00, 0xB6, 0x95, 0xE0, 0x00, 0x25, 0xB5, 0x0F, // 0x2200 + 0x00, 0xB1, 0x9C, 0xE0, 0x00, 0x45, 0x71, 0x0F, 0x00, 0x7D, 0x03, 0x00, 0x00, 0xFB, 0x06, 0x00, // 0x2210 + 0x00, 0x7D, 0x03, 0x00, 0x00, 0xB1, 0x9C, 0xE0, 0x00, 0x45, 0x71, 0x0F, 0x00, 0x5E, 0xF8, 0x0F, // 0x2220 + 0x00, 0x44, 0x0F, 0xE0, 0x00, 0x5E, 0xF8, 0x0F, 0x00, 0x48, 0x0F, 0xE0, 0x00, 0xC0, 0xF0, 0x0F, // 0x2230 + 0x00, 0x6D, 0xEF, 0x0F, 0x00, 0x26, 0x21, 0xE0, 0x00, 0x6D, 0xEF, 0x0F, 0x00, 0x38, 0x21, 0xE0, // 0x2240 + 0x00, 0xEB, 0xDE, 0x0F, 0x00, 0x0E, 0xDC, 0x0F, 0x00, 0xE5, 0x47, 0xE0, 0x00, 0x0E, 0xDC, 0x0F, // 0x2250 + 0x00, 0x36, 0x48, 0xE0, 0x00, 0x6C, 0xB8, 0x0F, 0x00, 0x42, 0xB2, 0x0F, 0x00, 0x7D, 0x9B, 0xE0, // 0x2260 + 0x00, 0x42, 0xB2, 0x0F, 0x00, 0xF6, 0x9C, 0xE0, 0x00, 0xFD, 0x65, 0x0F, 0x00, 0xDF, 0x58, 0x0F, // 0x2270 + 0x00, 0x41, 0x4E, 0xE1, 0x00, 0xDF, 0x58, 0x0F, 0x00, 0x13, 0x55, 0xE1, 0x00, 0x91, 0xB8, 0x0E, // 0x2280 + 0x00, 0x15, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEB, 0x9F, 0xFF, 0x00, 0xF7, 0xD4, 0xE0, // 0x2290 + 0x00, 0xD6, 0x3F, 0x0F, 0x00, 0xB7, 0x1C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0xE3, 0xFC, // 0x22A0 + 0x00, 0x2A, 0x54, 0xF9, 0x00, 0x91, 0xC6, 0x09, 0x00, 0xEB, 0x9F, 0x0F, 0x00, 0xF7, 0xD4, 0xE0, // 0x22B0 + 0x00, 0xEB, 0x9F, 0x0F, 0x00, 0xF7, 0xD4, 0xE0, 0x00, 0xD6, 0x3F, 0x0F, 0x00, 0xE8, 0x8D, 0x00, // 0x22C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x72, 0xFF, 0x00, 0xCE, 0x25, 0xE1, 0x00, 0x2F, 0xE4, 0x0E, // 0x22D0 + 0x00, 0xDF, 0x58, 0x0F, 0x00, 0x41, 0x4E, 0xE1, 0x00, 0xDF, 0x58, 0x0F, 0x00, 0x13, 0x55, 0xE1, // 0x22E0 + 0x00, 0x91, 0xB8, 0x0E, 0x00, 0xDF, 0x58, 0x0F, 0x00, 0x41, 0x4E, 0xE1, 0x00, 0xDF, 0x58, 0x0F, // 0x22F0 + 0x00, 0x13, 0x55, 0xE1, 0x00, 0x91, 0xB8, 0x0E, 0x00, 0x48, 0x00, 0x00, 0x00, 0x83, 0x08, 0x00, // 0x2300 + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2310 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2320 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2330 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2340 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2350 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2360 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2370 + 0x0B, 0x5E, 0x0B, 0x5E, 0x00, 0x01, 0x00, 0x0F, 0x01, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x66, 0x0E, // 0x2380 + 0x94, 0x03, 0x4E, 0x43, 0x08, 0x64, 0xB6, 0x28, 0x0B, 0x03, 0x99, 0x0D, 0x00, 0x00, 0x00, 0x00, // 0x2390 + 0x00, 0x00, 0x00, 0x00, 0x2D, 0x0C, 0x1E, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x23A0 + 0x00, 0x00, 0x01, 0x00, 0x00, 0x1E, 0x08, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x23B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x23C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x23D0 + 0x29, // 0x23E0 +}; + +// Tweeter DSM firmware (for 0x3C, 0x3D) +static const u8 max98390_dsm_firmware_tweeter[] = { + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, // 0x2050 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2060 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x80, 0x07, 0x07, 0x01, 0x00, 0x4A, 0x2B, 0x08, 0x00, // 0x2070 + 0x03, 0x03, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2080 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2090 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20A0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20D0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20E0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20F0 + 0x00, 0x88, 0xBF, 0x0E, 0x00, 0xEF, 0x80, 0xE2, 0x00, 0x88, 0xBF, 0x0E, 0x00, 0x0A, 0x9A, 0xE2, // 0x2100 + 0x00, 0x2C, 0x98, 0x0D, 0x00, 0x8D, 0x0C, 0x00, 0x00, 0x1B, 0x19, 0x00, 0x00, 0x8D, 0x0C, 0x00, // 0x2110 + 0x00, 0x0A, 0x9A, 0xE2, 0x00, 0x2C, 0x98, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2120 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2130 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2140 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2150 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2160 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2170 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2180 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2190 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x21A0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x21B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x7F, 0x0F, 0x00, 0x1A, 0x3D, 0xE2, // 0x21C0 + 0x00, 0x1E, 0x76, 0x0E, 0x00, 0xAF, 0x35, 0xE2, 0x00, 0x4A, 0xEE, 0x0D, 0x00, 0xE1, 0x1B, 0x0F, // 0x21D0 + 0x00, 0x3D, 0xC8, 0xE1, 0x00, 0xE1, 0x1B, 0x0F, 0x00, 0xF3, 0xD4, 0xE1, 0x00, 0x79, 0x44, 0x0E, // 0x21E0 + 0x00, 0x5B, 0x06, 0x00, 0x00, 0xB6, 0x0C, 0x00, 0x00, 0x5B, 0x06, 0x00, 0x00, 0xF3, 0xD4, 0xE1, // 0x21F0 + 0x00, 0x79, 0x44, 0x0E, 0x00, 0xD3, 0x69, 0x0F, 0x00, 0x5A, 0x2C, 0xE1, 0x00, 0xD3, 0x69, 0x0F, // 0x2200 + 0x00, 0x97, 0x46, 0xE1, 0x00, 0xE3, 0xED, 0x0E, 0x00, 0x1E, 0x0D, 0x00, 0x00, 0x3D, 0x1A, 0x00, // 0x2210 + 0x00, 0x1E, 0x0D, 0x00, 0x00, 0x97, 0x46, 0xE1, 0x00, 0xE3, 0xED, 0x0E, 0x00, 0x4A, 0xF8, 0x0F, // 0x2220 + 0x00, 0x6D, 0x0F, 0xE0, 0x00, 0x4A, 0xF8, 0x0F, 0x00, 0x70, 0x0F, 0xE0, 0x00, 0x97, 0xF0, 0x0F, // 0x2230 + 0x00, 0x41, 0xEC, 0x0F, 0x00, 0x7E, 0x27, 0xE0, 0x00, 0x41, 0xEC, 0x0F, 0x00, 0x96, 0x27, 0xE0, // 0x2240 + 0x00, 0x9A, 0xD8, 0x0F, 0x00, 0x8F, 0xCD, 0x0F, 0x00, 0xE1, 0x64, 0xE0, 0x00, 0x8F, 0xCD, 0x0F, // 0x2250 + 0x00, 0x80, 0x65, 0xE0, 0x00, 0xBE, 0x9B, 0x0F, 0x00, 0xE9, 0x7F, 0x0F, 0x00, 0x2E, 0x00, 0xE1, // 0x2260 + 0x00, 0xE9, 0x7F, 0x0F, 0x00, 0x2F, 0x04, 0xE1, 0x00, 0xD3, 0x03, 0x0F, 0x00, 0x88, 0xBF, 0x0E, // 0x2270 + 0x00, 0xEF, 0x80, 0xE2, 0x00, 0x88, 0xBF, 0x0E, 0x00, 0x0A, 0x9A, 0xE2, 0x00, 0x2C, 0x98, 0x0D, // 0x2280 + 0x00, 0x15, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEB, 0x9F, 0xFF, 0x00, 0xF7, 0xD4, 0xE0, // 0x2290 + 0x00, 0xD6, 0x3F, 0x0F, 0x00, 0xB7, 0x1C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0xE3, 0xFC, // 0x22A0 + 0x00, 0x2A, 0x54, 0xF9, 0x00, 0x91, 0xC6, 0x09, 0x00, 0xEB, 0x9F, 0x0F, 0x00, 0xF7, 0xD4, 0xE0, // 0x22B0 + 0x00, 0xEB, 0x9F, 0x0F, 0x00, 0xF7, 0xD4, 0xE0, 0x00, 0xD6, 0x3F, 0x0F, 0x00, 0xE8, 0x8D, 0x00, // 0x22C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x72, 0xFF, 0x00, 0xCE, 0x25, 0xE1, 0x00, 0x2F, 0xE4, 0x0E, // 0x22D0 + 0x00, 0x88, 0xBF, 0x0E, 0x00, 0xEF, 0x80, 0xE2, 0x00, 0x88, 0xBF, 0x0E, 0x00, 0x0A, 0x9A, 0xE2, // 0x22E0 + 0x00, 0x2C, 0x98, 0x0D, 0x00, 0x88, 0xBF, 0x0E, 0x00, 0xEF, 0x80, 0xE2, 0x00, 0x88, 0xBF, 0x0E, // 0x22F0 + 0x00, 0x0A, 0x9A, 0xE2, 0x00, 0x2C, 0x98, 0x0D, 0x00, 0x48, 0x00, 0x00, 0x00, 0x83, 0x08, 0x00, // 0x2300 + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2310 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2320 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2330 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2340 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2350 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2360 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2370 + 0x0B, 0x5E, 0x0B, 0x5E, 0x00, 0x01, 0x00, 0x0F, 0x01, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x66, 0x0E, // 0x2380 + 0x94, 0x03, 0xAA, 0x2A, 0x09, 0x64, 0xB6, 0x28, 0x0B, 0x03, 0x99, 0x0D, 0x00, 0x00, 0x00, 0x00, // 0x2390 + 0x00, 0x00, 0x00, 0x00, 0x2D, 0x0C, 0x1E, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x23A0 + 0x00, 0x00, 0x01, 0x00, 0x00, 0x1E, 0x08, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x23B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x23C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x23D0 + 0x29, // 0x23E0 +}; + +// Load complete DSM firmware blob (913 bytes from 0x2050-0x23E0) +// Selects appropriate firmware based on speaker type (woofer vs tweeter) +static void max98390_load_dsm_firmware(struct max98390_hda_priv *priv, bool is_tweeter) +{ + const u8 *firmware; + int i; + + // Select appropriate firmware blob + if (is_tweeter) { + firmware = max98390_dsm_firmware_tweeter; + } else { + firmware = max98390_dsm_firmware_woofer; + } + + // Write all 913 bytes sequentially starting from 0x2050 + // This matches coolstar's uploadDSMBin() implementation + for (i = 0; i < MAX98390_DSM_PARAM_SIZE; i++) { + regmap_write(priv->regmap, MAX98390_DSM_START_ADDR + i, firmware[i]); + } +} + +// Configure DSM with appropriate firmware based on speaker type +// cutoff_freq: cutoff frequency in Hz (unused - filter built into tweeter firmware) +// is_tweeter: true for tweeters (0x3C, 0x3D), false for woofers (0x38, 0x39) +void max98390_configure_high_pass_filter(struct max98390_hda_priv *priv, int cutoff_freq, bool is_tweeter) +{ + // Load complete DSM firmware (913 bytes, all registers) + // Tweeter firmware includes high-pass filter in STBASS section + // Woofer firmware is full-range + max98390_load_dsm_firmware(priv, is_tweeter); + + // When EQ8_EN is enabled, ALL 8 biquads must be configured! + // Unused biquads must be set to PASSTHROUGH (B0=0x100000, others=0x000000) + // Otherwise H(z) = 0/1 = 0 = NO SOUND! + dev_info(priv->dev, "Configuring all 8 EQ biquads for EQ8_EN...\n"); + + // Set ALL 8 biquads to passthrough first (B0=0x100000 = 1.0 in Q4.20) + // BQ1: 0x2129-0x213B (use custom filter for tweeter later) + // BQ2: 0x213D-0x214F + // BQ3: 0x2151-0x2163 + // BQ4: 0x2165-0x2177 + // BQ5: 0x2179-0x218B + // BQ6: 0x218D-0x219F + // BQ7: 0x21A1-0x21B3 + // BQ8: 0x21B5-0x21C7 + for (int bq = 1; bq <= 8; bq++) { + int base = 0x2129 + (bq - 1) * 0x14; // Each biquad is 20 bytes (0x14) + + // B0 = 0x100000 (1.0 in Q4.20 = passthrough) + regmap_write(priv->regmap, base + 0, 0x00); // B0[7:0] + regmap_write(priv->regmap, base + 1, 0x00); // B0[15:8] + regmap_write(priv->regmap, base + 2, 0x10); // B0[23:16] + // 0x2C reserved + + // B1, B2, A1, A2 = 0x000000 (all zeros for passthrough) + for (int i = 4; i <= 18; i++) { + if ((i - 3) % 4 != 0) { // Skip reserved bytes (every 4th byte) + regmap_write(priv->regmap, base + i, 0x00); + } + } + } + + // For WOOFERS: Apply EasyEffects EQ using BQ1 + if (!is_tweeter) { + // BQ1: 240.8 Hz, +22.91 dB, Q=4.65 (Bell/Peaking filter) + // Converted from EasyEffects preset - Sample rate: 48000 Hz + // Float coefficients: B0=1.011753, B1=-1.997196, B2=0.986436, A1=-1.997196, A2=0.998189 + // Q4.20 format (24-bit signed, LSB-first) + + int bq1_base = 0x2129; // BQ1 starts at 0x2129 + + // BQ1_B0 = 0x103024 (LSB-first: 0x24, 0x30, 0x10) + regmap_write(priv->regmap, bq1_base + 0, 0x24); + regmap_write(priv->regmap, bq1_base + 1, 0x30); + regmap_write(priv->regmap, bq1_base + 2, 0x10); + + // BQ1_B1 = 0xE00B7C (LSB-first: 0x7C, 0x0B, 0xE0) - skip byte 3 (reserved) + regmap_write(priv->regmap, bq1_base + 4, 0x7C); + regmap_write(priv->regmap, bq1_base + 5, 0x0B); + regmap_write(priv->regmap, bq1_base + 6, 0xE0); + + // BQ1_B2 = 0x0FC871 (LSB-first: 0x71, 0xC8, 0x0F) - skip byte 7 (reserved) + regmap_write(priv->regmap, bq1_base + 8, 0x71); + regmap_write(priv->regmap, bq1_base + 9, 0xC8); + regmap_write(priv->regmap, bq1_base + 10, 0x0F); + + // BQ1_A1 = 0xE00B7C (LSB-first: 0x7C, 0x0B, 0xE0) - skip byte 11 (reserved) + regmap_write(priv->regmap, bq1_base + 12, 0x7C); + regmap_write(priv->regmap, bq1_base + 13, 0x0B); + regmap_write(priv->regmap, bq1_base + 14, 0xE0); + + // BQ1_A2 = 0x0FF895 (LSB-first: 0x95, 0xF8, 0x0F) - skip byte 15 (reserved) + regmap_write(priv->regmap, bq1_base + 16, 0x95); + regmap_write(priv->regmap, bq1_base + 17, 0xF8); + regmap_write(priv->regmap, bq1_base + 18, 0x0F); + } + + // For tweeters: BQ1 with custom 3kHz high-pass filter + if (is_tweeter) { + // 2nd-order Butterworth HPF at 3kHz, 48kHz sample rate, Q=0.707 + // Coefficients in Q4.20 fixed-point format (24-bit): + // b0 = 0.7570 → 0x0C1C2C + // b1 = -1.5139 → 0xE7CE52 (two's complement) + // b2 = 0.7570 → 0x0C1C2C + // a1 = -1.4543 → 0xE8BC63 (two's complement) + // a2 = 0.5741 → 0x092E0C + + // BQ1_B0 (0x2129-0x212B): [7:0], [15:8], [23:16] + regmap_write(priv->regmap, 0x2129, 0x2C); + regmap_write(priv->regmap, 0x212A, 0x1C); + regmap_write(priv->regmap, 0x212B, 0x0C); + + // BQ1_B1 (0x212D-0x212F) - 0x212C is RESERVED, skip it! + regmap_write(priv->regmap, 0x212D, 0x52); + regmap_write(priv->regmap, 0x212E, 0xCE); + regmap_write(priv->regmap, 0x212F, 0xE7); + + // BQ1_B2 (0x2131-0x2133) - 0x2130 is RESERVED, skip it! + regmap_write(priv->regmap, 0x2131, 0x2C); + regmap_write(priv->regmap, 0x2132, 0x1C); + regmap_write(priv->regmap, 0x2133, 0x0C); + + // BQ1_A1 (0x2135-0x2137) - 0x2134 is RESERVED, skip it! + regmap_write(priv->regmap, 0x2135, 0x63); + regmap_write(priv->regmap, 0x2136, 0xBC); + regmap_write(priv->regmap, 0x2137, 0xE8); + + // BQ1_A2 (0x2139-0x213B) - 0x2138 is RESERVED, skip it! + regmap_write(priv->regmap, 0x2139, 0x0C); + regmap_write(priv->regmap, 0x213A, 0x2E); + regmap_write(priv->regmap, 0x213B, 0x09); + } + + // Set DSM_VOL_CTRL - restored to firmware default (0xA0) + regmap_write(priv->regmap, 0x23BA, 0xA0); + + // SET DSP_GLOBAL_EN TO 1 (WRITE 0x01 TO 0x23E1) + regmap_write(priv->regmap, 0x23E1, 0x01); + + // SET SPK_EN TO 1 (WRITE 0x81 TO 0x203A) + regmap_write(priv->regmap, 0x203A, 0x81); + + // SET EN TO 1 (WRITE 0x01 TO 0x23FF) + regmap_write(priv->regmap, 0x23FF, 0x01); +} + +MODULE_DESCRIPTION("HDA MAX98390 driver"); +MODULE_AUTHOR("Kevin Cuperus "); +MODULE_LICENSE("GPL"); diff --git a/sound/hda/codecs/side-codecs/max98390_hda_filters.h b/sound/hda/codecs/side-codecs/max98390_hda_filters.h new file mode 100644 index 00000000000000..d41dd4ca3b510c --- /dev/null +++ b/sound/hda/codecs/side-codecs/max98390_hda_filters.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +// +// MAX98390 HDA filter configuration header +// + +#ifndef __MAX98390_HDA_FILTERS_H +#define __MAX98390_HDA_FILTERS_H + +struct max98390_hda_priv; + +// Function prototypes +void max98390_configure_filters(struct max98390_hda_priv *priv); +void max98390_configure_high_pass_filter(struct max98390_hda_priv *priv, int cutoff_freq, bool is_tweeter); + +#endif /* __MAX98390_HDA_FILTERS_H */ \ No newline at end of file diff --git a/sound/hda/codecs/side-codecs/max98390_hda_i2c.c b/sound/hda/codecs/side-codecs/max98390_hda_i2c.c new file mode 100644 index 00000000000000..38d2ca7f8ee661 --- /dev/null +++ b/sound/hda/codecs/side-codecs/max98390_hda_i2c.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// MAX98390 HDA I2C driver +// + +#include +#include +#include + +#include "max98390_hda.h" +#include "../../../soc/codecs/max98390.h" + +static int max98390_hda_i2c_probe(struct i2c_client *clt) +{ + const char *device_name; + const char *name_suffix; + int index = 0; + + /* Match device name from serial-multi-instantiate */ + if (strstr(dev_name(&clt->dev), "MAX98390") || + strstr(dev_name(&clt->dev), "max98390")) + device_name = "MAX98390"; + else + return -ENODEV; + + /* Parse index from device name (e.g., "i2c-MAX98390:00-max98390-hda.0" -> index 0) */ + name_suffix = strrchr(dev_name(&clt->dev), '.'); + if (name_suffix) { + if (kstrtoint(name_suffix + 1, 10, &index) < 0) { + dev_err(&clt->dev, "Failed to parse device index from name: %s\n", + dev_name(&clt->dev)); + return -EINVAL; + } + } + + return max98390_hda_probe(&clt->dev, device_name, index, clt->irq, + devm_regmap_init_i2c(clt, &max98390_regmap), + MAX98390_HDA_I2C, clt->addr); +} + +static void max98390_hda_i2c_remove(struct i2c_client *clt) +{ + max98390_hda_remove(&clt->dev); +} + +static const struct i2c_device_id max98390_hda_i2c_id[] = { + { "max98390-hda", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, max98390_hda_i2c_id); + +static const struct acpi_device_id max98390_acpi_hda_match[] = { + { "MAX98390", 0 }, + { "MX98390", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, max98390_acpi_hda_match); + +static struct i2c_driver max98390_hda_i2c_driver = { + .driver = { + .name = "max98390-hda", + .acpi_match_table = max98390_acpi_hda_match, + .pm = &max98390_hda_pm_ops, + }, + .id_table = max98390_hda_i2c_id, + .probe = max98390_hda_i2c_probe, + .remove = max98390_hda_i2c_remove, +}; +module_i2c_driver(max98390_hda_i2c_driver); + +MODULE_DESCRIPTION("HDA MAX98390 driver"); +MODULE_IMPORT_NS("SND_HDA_SCODEC_MAX98390"); +MODULE_AUTHOR("Kevin Cuperus "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c index 3dd4dd94bc371f..446465cbb4ccd7 100644 --- a/sound/soc/codecs/max98390.c +++ b/sound/soc/codecs/max98390.c @@ -978,7 +978,7 @@ static const struct snd_soc_component_driver soc_codec_dev_max98390 = { .endianness = 1, }; -static const struct regmap_config max98390_regmap = { +const struct regmap_config max98390_regmap = { .reg_bits = 16, .val_bits = 8, .max_register = MAX98390_R24FF_REV_ID, @@ -988,6 +988,7 @@ static const struct regmap_config max98390_regmap = { .volatile_reg = max98390_volatile_reg, .cache_type = REGCACHE_RBTREE, }; +EXPORT_SYMBOL_GPL(max98390_regmap); static void max98390_slot_config(struct i2c_client *i2c, struct max98390_priv *max98390) @@ -1111,6 +1112,7 @@ MODULE_DEVICE_TABLE(of, max98390_of_match); #ifdef CONFIG_ACPI static const struct acpi_device_id max98390_acpi_match[] = { + { "MAX98390", 0 }, { "MX98390", 0 }, {}, }; diff --git a/sound/soc/codecs/max98390.h b/sound/soc/codecs/max98390.h index f4d6758ab4c630..c235df0f2ac822 100644 --- a/sound/soc/codecs/max98390.h +++ b/sound/soc/codecs/max98390.h @@ -664,4 +664,8 @@ struct max98390_priv { unsigned int ambient_temp_value; const char *dsm_param_name; }; + +/* Exported for HDA side-codec driver */ +extern const struct regmap_config max98390_regmap; + #endif