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

ALSA: hda: cs35l41: Add initial DSP support and firmware loading

This patch adds support for the CS35L41 DSP.
The DSP allows for extra features, such as running
speaker protection algorithms and hibernations.

To utilize these features, the driver must load
firmware into the DSP, as well as various tuning
files which allow for customization for specific
models.

[ Slightly simplified Kconfig changes by tiwai ]

Signed-off-by: Vitaly Rodionov <vitaly.rodionov@cirrus.com>
Signed-off-by: Vitaly Rodionov <vitalyr@opensource.cirrus.com>
Link: https://lore.kernel.org/r/20220630002335.366545-5-vitalyr@opensource.cirrus.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>

authored by

Vitaly Rodionov and committed by
Takashi Iwai
2e81e1ff 22d5cbd2

+269 -1
+4
include/sound/cs35l41.h
··· 665 665 #define CS35L41_BST_EN_DEFAULT 0x2 666 666 #define CS35L41_AMP_EN_SHIFT 0 667 667 #define CS35L41_AMP_EN_MASK 1 668 + #define CS35L41_VMON_EN_MASK 0x1000 669 + #define CS35L41_VMON_EN_SHIFT 12 670 + #define CS35L41_IMON_EN_MASK 0x2000 671 + #define CS35L41_IMON_EN_SHIFT 13 668 672 669 673 #define CS35L41_PDN_DONE_MASK 0x00800000 670 674 #define CS35L41_PDN_DONE_SHIFT 23
+2
sound/pci/hda/Kconfig
··· 107 107 depends on SND_SOC 108 108 select SND_SOC_CS35L41_LIB 109 109 select SND_HDA_SCODEC_CS35L41 110 + select SND_HDA_CS_DSP_CONTROLS 110 111 help 111 112 Say Y or M here to include CS35L41 I2C HD-audio side codec support 112 113 in snd-hda-intel driver, such as ALC287. ··· 122 121 depends on SND_SOC 123 122 select SND_SOC_CS35L41_LIB 124 123 select SND_HDA_SCODEC_CS35L41 124 + select SND_HDA_CS_DSP_CONTROLS 125 125 help 126 126 Say Y or M here to include CS35L41 SPI HD-audio side codec support 127 127 in snd-hda-intel driver, such as ALC287.
+250 -1
sound/pci/hda/cs35l41_hda.c
··· 9 9 #include <linux/acpi.h> 10 10 #include <linux/module.h> 11 11 #include <sound/hda_codec.h> 12 + #include <sound/soc.h> 12 13 #include "hda_local.h" 13 14 #include "hda_auto_parser.h" 14 15 #include "hda_jack.h" 15 16 #include "hda_generic.h" 16 17 #include "hda_component.h" 17 18 #include "cs35l41_hda.h" 19 + #include "hda_cs_dsp_ctl.h" 20 + 21 + #define CS35L41_FIRMWARE_ROOT "cirrus/" 22 + #define CS35L41_PART "cs35l41" 23 + #define FW_NAME "CSPL" 24 + 25 + #define HALO_STATE_DSP_CTL_NAME "HALO_STATE" 26 + #define HALO_STATE_DSP_CTL_TYPE 5 27 + #define HALO_STATE_DSP_CTL_ALG 262308 18 28 19 29 static const struct reg_sequence cs35l41_hda_config[] = { 20 30 { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 ··· 37 27 { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB 38 28 }; 39 29 30 + static const struct reg_sequence cs35l41_hda_config_dsp[] = { 31 + { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 32 + { CS35L41_DSP_CLK_CTRL, 0x00000003 }, // DSP CLK EN 33 + { CS35L41_GLOBAL_CLK_CTRL, 0x00000003 }, // GLOBAL_FS = 48 kHz 34 + { CS35L41_SP_ENABLES, 0x00010001 }, // ASP_RX1_EN = 1, ASP_TX1_EN = 1 35 + { CS35L41_SP_RATE_CTRL, 0x00000021 }, // ASP_BCLK_FREQ = 3.072 MHz 36 + { CS35L41_SP_FORMAT, 0x20200200 }, // 32 bits RX/TX slots, I2S, clk consumer 37 + { CS35L41_SP_HIZ_CTRL, 0x00000003 }, // Hi-Z unused/disabled 38 + { CS35L41_SP_TX_WL, 0x00000018 }, // 24 cycles/slot 39 + { CS35L41_SP_RX_WL, 0x00000018 }, // 24 cycles/slot 40 + { CS35L41_DAC_PCM1_SRC, 0x00000032 }, // DACPCM1_SRC = ERR_VOL 41 + { CS35L41_ASP_TX1_SRC, 0x00000018 }, // ASPTX1 SRC = VMON 42 + { CS35L41_ASP_TX2_SRC, 0x00000019 }, // ASPTX2 SRC = IMON 43 + { CS35L41_ASP_TX3_SRC, 0x00000028 }, // ASPTX3 SRC = VPMON 44 + { CS35L41_ASP_TX4_SRC, 0x00000029 }, // ASPTX4 SRC = VBSTMON 45 + { CS35L41_DSP1_RX1_SRC, 0x00000008 }, // DSP1RX1 SRC = ASPRX1 46 + { CS35L41_DSP1_RX2_SRC, 0x00000008 }, // DSP1RX2 SRC = ASPRX1 47 + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON 48 + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON 49 + { CS35L41_DSP1_RX5_SRC, 0x00000029 }, // DSP1RX5 SRC = VBSTMON 50 + { CS35L41_AMP_DIG_VOL_CTRL, 0x00000000 }, // AMP_VOL_PCM 0.0 dB 51 + { CS35L41_AMP_GAIN_CTRL, 0x00000233 }, // AMP_GAIN_PCM = 17.5dB AMP_GAIN_PDM = 19.5dB 52 + }; 53 + 40 54 static const struct reg_sequence cs35l41_hda_mute[] = { 41 55 { CS35L41_AMP_GAIN_CTRL, 0x00000000 }, // AMP_GAIN_PCM 0.5 dB 42 56 { CS35L41_AMP_DIG_VOL_CTRL, 0x0000A678 }, // AMP_VOL_PCM Mute 43 57 }; 58 + 59 + static int cs35l41_control_add(struct cs_dsp_coeff_ctl *cs_ctl) 60 + { 61 + struct cs35l41_hda *cs35l41 = container_of(cs_ctl->dsp, struct cs35l41_hda, cs_dsp); 62 + struct hda_cs_dsp_ctl_info info; 63 + 64 + info.device_name = cs35l41->amp_name; 65 + info.fw_type = HDA_CS_DSP_FW_SPK_PROT; 66 + info.card = cs35l41->codec->card; 67 + 68 + return hda_cs_dsp_control_add(cs_ctl, &info); 69 + } 70 + 71 + static const struct cs_dsp_client_ops client_ops = { 72 + .control_add = cs35l41_control_add, 73 + .control_remove = hda_cs_dsp_control_remove, 74 + }; 75 + 76 + static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, 77 + const struct firmware **firmware, char **filename, 78 + const char *dir, const char *filetype) 79 + { 80 + const char * const dsp_name = cs35l41->cs_dsp.name; 81 + char *s, c; 82 + int ret = 0; 83 + 84 + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, CS35L41_PART, dsp_name, "spk-prot", 85 + filetype); 86 + 87 + if (*filename == NULL) 88 + return -ENOMEM; 89 + 90 + /* 91 + * Make sure that filename is lower-case and any non alpha-numeric 92 + * characters except full stop and '/' are replaced with hyphens. 93 + */ 94 + s = *filename; 95 + while (*s) { 96 + c = *s; 97 + if (isalnum(c)) 98 + *s = tolower(c); 99 + else if (c != '.' && c != '/') 100 + *s = '-'; 101 + s++; 102 + } 103 + 104 + ret = firmware_request_nowarn(firmware, *filename, cs35l41->dev); 105 + if (ret != 0) { 106 + dev_dbg(cs35l41->dev, "Failed to request '%s'\n", *filename); 107 + kfree(*filename); 108 + *filename = NULL; 109 + } 110 + 111 + return ret; 112 + } 113 + 114 + static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41, 115 + const struct firmware **wmfw_firmware, 116 + char **wmfw_filename, 117 + const struct firmware **coeff_firmware, 118 + char **coeff_filename) 119 + { 120 + int ret; 121 + 122 + /* cirrus/part-dspN-fwtype.wmfw */ 123 + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, 124 + CS35L41_FIRMWARE_ROOT, "wmfw"); 125 + if (!ret) { 126 + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, 127 + CS35L41_FIRMWARE_ROOT, "bin"); 128 + return 0; 129 + } 130 + 131 + dev_warn(cs35l41->dev, "Failed to request firmware\n"); 132 + 133 + return ret; 134 + } 135 + 136 + static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) 137 + { 138 + const struct firmware *coeff_firmware = NULL; 139 + const struct firmware *wmfw_firmware = NULL; 140 + struct cs_dsp *dsp = &cs35l41->cs_dsp; 141 + char *coeff_filename = NULL; 142 + char *wmfw_filename = NULL; 143 + int ret; 144 + 145 + if (!cs35l41->halo_initialized) { 146 + cs35l41_configure_cs_dsp(cs35l41->dev, cs35l41->regmap, dsp); 147 + dsp->client_ops = &client_ops; 148 + 149 + ret = cs_dsp_halo_init(&cs35l41->cs_dsp); 150 + if (ret) 151 + return ret; 152 + cs35l41->halo_initialized = true; 153 + } 154 + 155 + ret = cs35l41_request_firmware_files(cs35l41, &wmfw_firmware, &wmfw_filename, 156 + &coeff_firmware, &coeff_filename); 157 + if (ret < 0) 158 + return ret; 159 + 160 + dev_dbg(cs35l41->dev, "Loading WMFW Firmware: %s\n", wmfw_filename); 161 + if (coeff_filename) 162 + dev_dbg(cs35l41->dev, "Loading Coefficient File: %s\n", coeff_filename); 163 + else 164 + dev_warn(cs35l41->dev, "No Coefficient File available.\n"); 165 + 166 + ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, 167 + FW_NAME); 168 + 169 + release_firmware(wmfw_firmware); 170 + release_firmware(coeff_firmware); 171 + kfree(wmfw_filename); 172 + kfree(coeff_filename); 173 + 174 + return ret; 175 + } 176 + 177 + static void cs35l41_shutdown_dsp(struct cs35l41_hda *cs35l41) 178 + { 179 + struct cs_dsp *dsp = &cs35l41->cs_dsp; 180 + 181 + cs_dsp_stop(dsp); 182 + cs_dsp_power_down(dsp); 183 + cs35l41->firmware_running = false; 184 + dev_dbg(cs35l41->dev, "Unloaded Firmware\n"); 185 + } 186 + 187 + static void cs35l41_remove_dsp(struct cs35l41_hda *cs35l41) 188 + { 189 + struct cs_dsp *dsp = &cs35l41->cs_dsp; 190 + 191 + cs35l41_shutdown_dsp(cs35l41); 192 + cs_dsp_remove(dsp); 193 + cs35l41->halo_initialized = false; 194 + } 44 195 45 196 /* Protection release cycle to get the speaker out of Safe-Mode */ 46 197 static void cs35l41_error_release(struct device *dev, struct regmap *regmap, unsigned int mask) ··· 224 53 struct regmap *reg = cs35l41->regmap; 225 54 int ret = 0; 226 55 56 + mutex_lock(&cs35l41->fw_mutex); 57 + 227 58 switch (action) { 228 59 case HDA_GEN_PCM_ACT_OPEN: 229 - regmap_multi_reg_write(reg, cs35l41_hda_config, ARRAY_SIZE(cs35l41_hda_config)); 60 + if (cs35l41->firmware_running) { 61 + regmap_multi_reg_write(reg, cs35l41_hda_config_dsp, 62 + ARRAY_SIZE(cs35l41_hda_config_dsp)); 63 + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, 64 + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, 65 + 1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT); 66 + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, 67 + CSPL_MBOX_CMD_RESUME); 68 + } else { 69 + regmap_multi_reg_write(reg, cs35l41_hda_config, 70 + ARRAY_SIZE(cs35l41_hda_config)); 71 + } 230 72 ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, 231 73 CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT); 232 74 if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) ··· 257 73 CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); 258 74 if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) 259 75 regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001); 76 + if (cs35l41->firmware_running) { 77 + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, 78 + CSPL_MBOX_CMD_PAUSE); 79 + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, 80 + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, 81 + 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); 82 + } 260 83 cs35l41_irq_release(cs35l41); 261 84 break; 262 85 default: 263 86 dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action); 264 87 break; 265 88 } 89 + 90 + mutex_unlock(&cs35l41->fw_mutex); 266 91 267 92 if (ret) 268 93 dev_err(cs35l41->dev, "Regmap access fail: %d\n", ret); ··· 297 104 rx_slot); 298 105 } 299 106 107 + static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) 108 + { 109 + int halo_sts; 110 + int ret; 111 + 112 + ret = cs35l41_init_dsp(cs35l41); 113 + if (ret) { 114 + dev_warn(cs35l41->dev, "Cannot Initialize Firmware. Error: %d\n", ret); 115 + goto clean_dsp; 116 + } 117 + 118 + ret = cs35l41_write_fs_errata(cs35l41->dev, cs35l41->regmap); 119 + if (ret) { 120 + dev_err(cs35l41->dev, "Cannot Write FS Errata: %d\n", ret); 121 + goto clean_dsp; 122 + } 123 + 124 + ret = cs_dsp_run(&cs35l41->cs_dsp); 125 + if (ret) { 126 + dev_err(cs35l41->dev, "Fail to start dsp: %d\n", ret); 127 + goto clean_dsp; 128 + } 129 + 130 + ret = read_poll_timeout(hda_cs_dsp_read_ctl, ret, 131 + be32_to_cpu(halo_sts) == HALO_STATE_CODE_RUN, 132 + 1000, 15000, false, &cs35l41->cs_dsp, HALO_STATE_DSP_CTL_NAME, 133 + HALO_STATE_DSP_CTL_TYPE, HALO_STATE_DSP_CTL_ALG, 134 + &halo_sts, sizeof(halo_sts)); 135 + 136 + if (ret) { 137 + dev_err(cs35l41->dev, "Timeout waiting for HALO Core to start. State: %d\n", 138 + halo_sts); 139 + goto clean_dsp; 140 + } 141 + 142 + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); 143 + cs35l41->firmware_running = true; 144 + 145 + return 0; 146 + 147 + clean_dsp: 148 + cs35l41_shutdown_dsp(cs35l41); 149 + return ret; 150 + } 151 + 300 152 static int cs35l41_hda_bind(struct device *dev, struct device *master, void *master_data) 301 153 { 302 154 struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ··· 358 120 cs35l41->codec = comps->codec; 359 121 strscpy(comps->name, dev_name(dev), sizeof(comps->name)); 360 122 comps->playback_hook = cs35l41_hda_playback_hook; 123 + 124 + mutex_lock(&cs35l41->fw_mutex); 125 + if (cs35l41_smart_amp(cs35l41) < 0) 126 + dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n"); 127 + mutex_unlock(&cs35l41->fw_mutex); 361 128 362 129 return 0; 363 130 } ··· 778 535 if (ret) 779 536 goto err; 780 537 538 + mutex_init(&cs35l41->fw_mutex); 539 + 781 540 ret = cs35l41_hda_apply_properties(cs35l41); 782 541 if (ret) 783 542 goto err; ··· 807 562 { 808 563 struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); 809 564 565 + if (cs35l41->halo_initialized) 566 + cs35l41_remove_dsp(cs35l41); 567 + 810 568 component_del(cs35l41->dev, &cs35l41_hda_comp_ops); 811 569 812 570 if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) ··· 819 571 EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41); 820 572 821 573 MODULE_DESCRIPTION("CS35L41 HDA Driver"); 574 + MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS); 822 575 MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, <tanureal@opensource.cirrus.com>"); 823 576 MODULE_LICENSE("GPL");
+13
sound/pci/hda/cs35l41_hda.h
··· 15 15 #include <linux/device.h> 16 16 #include <sound/cs35l41.h> 17 17 18 + #include <linux/firmware/cirrus/cs_dsp.h> 19 + #include <linux/firmware/cirrus/wmfw.h> 20 + 18 21 enum cs35l41_hda_spk_pos { 19 22 CS35l41_LEFT, 20 23 CS35l41_RIGHT, ··· 42 39 int channel_index; 43 40 unsigned volatile long irq_errors; 44 41 const char *amp_name; 42 + struct mutex fw_mutex; 45 43 struct regmap_irq_chip_data *irq_data; 44 + bool firmware_running; 45 + bool halo_initialized; 46 + struct cs_dsp cs_dsp; 47 + }; 48 + 49 + enum halo_state { 50 + HALO_STATE_CODE_INIT_DOWNLOAD = 0, 51 + HALO_STATE_CODE_START, 52 + HALO_STATE_CODE_RUN 46 53 }; 47 54 48 55 int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq,