| // SPDX-License-Identifier: GPL-2.0 | |
| // | |
| // cs35l41.c -- CS35l41 ALSA SoC audio driver | |
| // | |
| // Copyright 2017-2021 Cirrus Logic, Inc. | |
| // | |
| // Author: David Rhodes <david.rhodes@cirrus.com> | |
| #include <linux/acpi.h> | |
| #include <acpi/acpi_bus.h> | |
| #include <linux/delay.h> | |
| #include <linux/err.h> | |
| #include <linux/init.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/moduleparam.h> | |
| #include <linux/pm_runtime.h> | |
| #include <linux/property.h> | |
| #include <sound/initval.h> | |
| #include <sound/pcm.h> | |
| #include <sound/pcm_params.h> | |
| #include <sound/soc.h> | |
| #include <sound/soc-dapm.h> | |
| #include <sound/tlv.h> | |
| #include "cs35l41.h" | |
| static const char * const cs35l41_supplies[CS35L41_NUM_SUPPLIES] = { | |
| "VA", | |
| "VP", | |
| }; | |
| struct cs35l41_pll_sysclk_config { | |
| int freq; | |
| int clk_cfg; | |
| }; | |
| static const struct cs35l41_pll_sysclk_config cs35l41_pll_sysclk[] = { | |
| { 32768, 0x00 }, | |
| { 8000, 0x01 }, | |
| { 11025, 0x02 }, | |
| { 12000, 0x03 }, | |
| { 16000, 0x04 }, | |
| { 22050, 0x05 }, | |
| { 24000, 0x06 }, | |
| { 32000, 0x07 }, | |
| { 44100, 0x08 }, | |
| { 48000, 0x09 }, | |
| { 88200, 0x0A }, | |
| { 96000, 0x0B }, | |
| { 128000, 0x0C }, | |
| { 176400, 0x0D }, | |
| { 192000, 0x0E }, | |
| { 256000, 0x0F }, | |
| { 352800, 0x10 }, | |
| { 384000, 0x11 }, | |
| { 512000, 0x12 }, | |
| { 705600, 0x13 }, | |
| { 750000, 0x14 }, | |
| { 768000, 0x15 }, | |
| { 1000000, 0x16 }, | |
| { 1024000, 0x17 }, | |
| { 1200000, 0x18 }, | |
| { 1411200, 0x19 }, | |
| { 1500000, 0x1A }, | |
| { 1536000, 0x1B }, | |
| { 2000000, 0x1C }, | |
| { 2048000, 0x1D }, | |
| { 2400000, 0x1E }, | |
| { 2822400, 0x1F }, | |
| { 3000000, 0x20 }, | |
| { 3072000, 0x21 }, | |
| { 3200000, 0x22 }, | |
| { 4000000, 0x23 }, | |
| { 4096000, 0x24 }, | |
| { 4800000, 0x25 }, | |
| { 5644800, 0x26 }, | |
| { 6000000, 0x27 }, | |
| { 6144000, 0x28 }, | |
| { 6250000, 0x29 }, | |
| { 6400000, 0x2A }, | |
| { 6500000, 0x2B }, | |
| { 6750000, 0x2C }, | |
| { 7526400, 0x2D }, | |
| { 8000000, 0x2E }, | |
| { 8192000, 0x2F }, | |
| { 9600000, 0x30 }, | |
| { 11289600, 0x31 }, | |
| { 12000000, 0x32 }, | |
| { 12288000, 0x33 }, | |
| { 12500000, 0x34 }, | |
| { 12800000, 0x35 }, | |
| { 13000000, 0x36 }, | |
| { 13500000, 0x37 }, | |
| { 19200000, 0x38 }, | |
| { 22579200, 0x39 }, | |
| { 24000000, 0x3A }, | |
| { 24576000, 0x3B }, | |
| { 25000000, 0x3C }, | |
| { 25600000, 0x3D }, | |
| { 26000000, 0x3E }, | |
| { 27000000, 0x3F }, | |
| }; | |
| struct cs35l41_fs_mon_config { | |
| int freq; | |
| unsigned int fs1; | |
| unsigned int fs2; | |
| }; | |
| static const struct cs35l41_fs_mon_config cs35l41_fs_mon[] = { | |
| { 32768, 2254, 3754 }, | |
| { 8000, 9220, 15364 }, | |
| { 11025, 6148, 10244 }, | |
| { 12000, 6148, 10244 }, | |
| { 16000, 4612, 7684 }, | |
| { 22050, 3076, 5124 }, | |
| { 24000, 3076, 5124 }, | |
| { 32000, 2308, 3844 }, | |
| { 44100, 1540, 2564 }, | |
| { 48000, 1540, 2564 }, | |
| { 88200, 772, 1284 }, | |
| { 96000, 772, 1284 }, | |
| { 128000, 580, 964 }, | |
| { 176400, 388, 644 }, | |
| { 192000, 388, 644 }, | |
| { 256000, 292, 484 }, | |
| { 352800, 196, 324 }, | |
| { 384000, 196, 324 }, | |
| { 512000, 148, 244 }, | |
| { 705600, 100, 164 }, | |
| { 750000, 100, 164 }, | |
| { 768000, 100, 164 }, | |
| { 1000000, 76, 124 }, | |
| { 1024000, 76, 124 }, | |
| { 1200000, 64, 104 }, | |
| { 1411200, 52, 84 }, | |
| { 1500000, 52, 84 }, | |
| { 1536000, 52, 84 }, | |
| { 2000000, 40, 64 }, | |
| { 2048000, 40, 64 }, | |
| { 2400000, 34, 54 }, | |
| { 2822400, 28, 44 }, | |
| { 3000000, 28, 44 }, | |
| { 3072000, 28, 44 }, | |
| { 3200000, 27, 42 }, | |
| { 4000000, 22, 34 }, | |
| { 4096000, 22, 34 }, | |
| { 4800000, 19, 29 }, | |
| { 5644800, 16, 24 }, | |
| { 6000000, 16, 24 }, | |
| { 6144000, 16, 24 }, | |
| { 12288000, 0, 0 }, | |
| }; | |
| static int cs35l41_get_fs_mon_config_index(int freq) | |
| { | |
| int i; | |
| for (i = 0; i < ARRAY_SIZE(cs35l41_fs_mon); i++) { | |
| if (cs35l41_fs_mon[i].freq == freq) | |
| return i; | |
| } | |
| return -EINVAL; | |
| } | |
| static const DECLARE_TLV_DB_RANGE(dig_vol_tlv, | |
| 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), | |
| 1, 913, TLV_DB_MINMAX_ITEM(-10200, 1200)); | |
| static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 50, 100, 0); | |
| static const struct snd_kcontrol_new dre_ctrl = | |
| SOC_DAPM_SINGLE("Switch", CS35L41_PWR_CTRL3, 20, 1, 0); | |
| static const char * const cs35l41_pcm_sftramp_text[] = { | |
| "Off", ".5ms", "1ms", "2ms", "4ms", "8ms", "15ms", "30ms" | |
| }; | |
| static SOC_ENUM_SINGLE_DECL(pcm_sft_ramp, | |
| CS35L41_AMP_DIG_VOL_CTRL, 0, | |
| cs35l41_pcm_sftramp_text); | |
| static int cs35l41_dsp_preload_ev(struct snd_soc_dapm_widget *w, | |
| struct snd_kcontrol *kcontrol, int event) | |
| { | |
| struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); | |
| int ret; | |
| switch (event) { | |
| case SND_SOC_DAPM_PRE_PMU: | |
| if (cs35l41->dsp.cs_dsp.booted) | |
| return 0; | |
| return wm_adsp_early_event(w, kcontrol, event); | |
| case SND_SOC_DAPM_PRE_PMD: | |
| if (cs35l41->dsp.preloaded) | |
| return 0; | |
| if (cs35l41->dsp.cs_dsp.running) { | |
| ret = wm_adsp_event(w, kcontrol, event); | |
| if (ret) | |
| return ret; | |
| } | |
| return wm_adsp_early_event(w, kcontrol, event); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int cs35l41_dsp_audio_ev(struct snd_soc_dapm_widget *w, | |
| struct snd_kcontrol *kcontrol, int event) | |
| { | |
| struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); | |
| unsigned int fw_status; | |
| int ret; | |
| switch (event) { | |
| case SND_SOC_DAPM_POST_PMU: | |
| if (!cs35l41->dsp.cs_dsp.running) | |
| return wm_adsp_event(w, kcontrol, event); | |
| ret = regmap_read(cs35l41->regmap, CS35L41_DSP_MBOX_2, &fw_status); | |
| if (ret < 0) { | |
| dev_err(cs35l41->dev, | |
| "Failed to read firmware status: %d\n", ret); | |
| return ret; | |
| } | |
| switch (fw_status) { | |
| case CSPL_MBOX_STS_RUNNING: | |
| case CSPL_MBOX_STS_PAUSED: | |
| break; | |
| default: | |
| dev_err(cs35l41->dev, "Firmware status is invalid: %u\n", | |
| fw_status); | |
| return -EINVAL; | |
| } | |
| return cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, | |
| CSPL_MBOX_CMD_RESUME); | |
| case SND_SOC_DAPM_PRE_PMD: | |
| return cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, | |
| CSPL_MBOX_CMD_PAUSE); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static const char * const cs35l41_pcm_source_texts[] = {"ASP", "DSP"}; | |
| static const unsigned int cs35l41_pcm_source_values[] = {0x08, 0x32}; | |
| static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_pcm_source_enum, | |
| CS35L41_DAC_PCM1_SRC, | |
| 0, CS35L41_ASP_SOURCE_MASK, | |
| cs35l41_pcm_source_texts, | |
| cs35l41_pcm_source_values); | |
| static const struct snd_kcontrol_new pcm_source_mux = | |
| SOC_DAPM_ENUM("PCM Source", cs35l41_pcm_source_enum); | |
| static const char * const cs35l41_tx_input_texts[] = { | |
| "Zero", "ASPRX1", "ASPRX2", "VMON", "IMON", | |
| "VPMON", "VBSTMON", "DSPTX1", "DSPTX2" | |
| }; | |
| static const unsigned int cs35l41_tx_input_values[] = { | |
| 0x00, CS35L41_INPUT_SRC_ASPRX1, CS35L41_INPUT_SRC_ASPRX2, | |
| CS35L41_INPUT_SRC_VMON, CS35L41_INPUT_SRC_IMON, CS35L41_INPUT_SRC_VPMON, | |
| CS35L41_INPUT_SRC_VBSTMON, CS35L41_INPUT_DSP_TX1, CS35L41_INPUT_DSP_TX2 | |
| }; | |
| static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx1_enum, | |
| CS35L41_ASP_TX1_SRC, | |
| 0, CS35L41_ASP_SOURCE_MASK, | |
| cs35l41_tx_input_texts, | |
| cs35l41_tx_input_values); | |
| static const struct snd_kcontrol_new asp_tx1_mux = | |
| SOC_DAPM_ENUM("ASPTX1 SRC", cs35l41_asptx1_enum); | |
| static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx2_enum, | |
| CS35L41_ASP_TX2_SRC, | |
| 0, CS35L41_ASP_SOURCE_MASK, | |
| cs35l41_tx_input_texts, | |
| cs35l41_tx_input_values); | |
| static const struct snd_kcontrol_new asp_tx2_mux = | |
| SOC_DAPM_ENUM("ASPTX2 SRC", cs35l41_asptx2_enum); | |
| static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx3_enum, | |
| CS35L41_ASP_TX3_SRC, | |
| 0, CS35L41_ASP_SOURCE_MASK, | |
| cs35l41_tx_input_texts, | |
| cs35l41_tx_input_values); | |
| static const struct snd_kcontrol_new asp_tx3_mux = | |
| SOC_DAPM_ENUM("ASPTX3 SRC", cs35l41_asptx3_enum); | |
| static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx4_enum, | |
| CS35L41_ASP_TX4_SRC, | |
| 0, CS35L41_ASP_SOURCE_MASK, | |
| cs35l41_tx_input_texts, | |
| cs35l41_tx_input_values); | |
| static const struct snd_kcontrol_new asp_tx4_mux = | |
| SOC_DAPM_ENUM("ASPTX4 SRC", cs35l41_asptx4_enum); | |
| static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_dsprx1_enum, | |
| CS35L41_DSP1_RX1_SRC, | |
| 0, CS35L41_ASP_SOURCE_MASK, | |
| cs35l41_tx_input_texts, | |
| cs35l41_tx_input_values); | |
| static const struct snd_kcontrol_new dsp_rx1_mux = | |
| SOC_DAPM_ENUM("DSPRX1 SRC", cs35l41_dsprx1_enum); | |
| static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_dsprx2_enum, | |
| CS35L41_DSP1_RX2_SRC, | |
| 0, CS35L41_ASP_SOURCE_MASK, | |
| cs35l41_tx_input_texts, | |
| cs35l41_tx_input_values); | |
| static const struct snd_kcontrol_new dsp_rx2_mux = | |
| SOC_DAPM_ENUM("DSPRX2 SRC", cs35l41_dsprx2_enum); | |
| static const struct snd_kcontrol_new cs35l41_aud_controls[] = { | |
| SOC_SINGLE_SX_TLV("Digital PCM Volume", CS35L41_AMP_DIG_VOL_CTRL, | |
| 3, 0x4CF, 0x391, dig_vol_tlv), | |
| SOC_SINGLE_TLV("Analog PCM Volume", CS35L41_AMP_GAIN_CTRL, 5, 0x14, 0, | |
| amp_gain_tlv), | |
| SOC_ENUM("PCM Soft Ramp", pcm_sft_ramp), | |
| SOC_SINGLE("HW Noise Gate Enable", CS35L41_NG_CFG, 8, 63, 0), | |
| SOC_SINGLE("HW Noise Gate Delay", CS35L41_NG_CFG, 4, 7, 0), | |
| SOC_SINGLE("HW Noise Gate Threshold", CS35L41_NG_CFG, 0, 7, 0), | |
| SOC_SINGLE("Aux Noise Gate CH1 Switch", | |
| CS35L41_MIXER_NGATE_CH1_CFG, 16, 1, 0), | |
| SOC_SINGLE("Aux Noise Gate CH1 Entry Delay", | |
| CS35L41_MIXER_NGATE_CH1_CFG, 8, 15, 0), | |
| SOC_SINGLE("Aux Noise Gate CH1 Threshold", | |
| CS35L41_MIXER_NGATE_CH1_CFG, 0, 7, 0), | |
| SOC_SINGLE("Aux Noise Gate CH2 Entry Delay", | |
| CS35L41_MIXER_NGATE_CH2_CFG, 8, 15, 0), | |
| SOC_SINGLE("Aux Noise Gate CH2 Switch", | |
| CS35L41_MIXER_NGATE_CH2_CFG, 16, 1, 0), | |
| SOC_SINGLE("Aux Noise Gate CH2 Threshold", | |
| CS35L41_MIXER_NGATE_CH2_CFG, 0, 7, 0), | |
| SOC_SINGLE("SCLK Force Switch", CS35L41_SP_FORMAT, CS35L41_SCLK_FRC_SHIFT, 1, 0), | |
| SOC_SINGLE("LRCLK Force Switch", CS35L41_SP_FORMAT, CS35L41_LRCLK_FRC_SHIFT, 1, 0), | |
| SOC_SINGLE("Invert Class D Switch", CS35L41_AMP_DIG_VOL_CTRL, | |
| CS35L41_AMP_INV_PCM_SHIFT, 1, 0), | |
| SOC_SINGLE("Amp Gain ZC Switch", CS35L41_AMP_GAIN_CTRL, | |
| CS35L41_AMP_GAIN_ZC_SHIFT, 1, 0), | |
| WM_ADSP2_PRELOAD_SWITCH("DSP1", 1), | |
| WM_ADSP_FW_CONTROL("DSP1", 0), | |
| }; | |
| static void cs35l41_boost_enable(struct cs35l41_private *cs35l41, unsigned int enable) | |
| { | |
| switch (cs35l41->hw_cfg.bst_type) { | |
| case CS35L41_INT_BOOST: | |
| case CS35L41_SHD_BOOST_ACTV: | |
| enable = enable ? CS35L41_BST_EN_DEFAULT : CS35L41_BST_DIS_FET_OFF; | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK, | |
| enable << CS35L41_BST_EN_SHIFT); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| static void cs35l41_error_release(struct cs35l41_private *cs35l41, unsigned int irq_err_bit, | |
| unsigned int rel_err_bit) | |
| { | |
| regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, irq_err_bit); | |
| regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, rel_err_bit, rel_err_bit); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, rel_err_bit, 0); | |
| } | |
| static irqreturn_t cs35l41_irq(int irq, void *data) | |
| { | |
| struct cs35l41_private *cs35l41 = data; | |
| unsigned int status[4] = { 0, 0, 0, 0 }; | |
| unsigned int masks[4] = { 0, 0, 0, 0 }; | |
| unsigned int i; | |
| int ret; | |
| ret = pm_runtime_resume_and_get(cs35l41->dev); | |
| if (ret < 0) { | |
| dev_err(cs35l41->dev, | |
| "pm_runtime_resume_and_get failed in %s: %d\n", | |
| __func__, ret); | |
| return IRQ_NONE; | |
| } | |
| ret = IRQ_NONE; | |
| for (i = 0; i < ARRAY_SIZE(status); i++) { | |
| regmap_read(cs35l41->regmap, | |
| CS35L41_IRQ1_STATUS1 + (i * CS35L41_REGSTRIDE), | |
| &status[i]); | |
| regmap_read(cs35l41->regmap, | |
| CS35L41_IRQ1_MASK1 + (i * CS35L41_REGSTRIDE), | |
| &masks[i]); | |
| } | |
| /* Check to see if unmasked bits are active */ | |
| if (!(status[0] & ~masks[0]) && !(status[1] & ~masks[1]) && | |
| !(status[2] & ~masks[2]) && !(status[3] & ~masks[3])) | |
| goto done; | |
| if (status[3] & CS35L41_OTP_BOOT_DONE) { | |
| regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK4, | |
| CS35L41_OTP_BOOT_DONE, CS35L41_OTP_BOOT_DONE); | |
| } | |
| /* | |
| * The following interrupts require a | |
| * protection release cycle to get the | |
| * speaker out of Safe-Mode. | |
| */ | |
| if (status[0] & CS35L41_AMP_SHORT_ERR) { | |
| dev_crit_ratelimited(cs35l41->dev, "Amp short error\n"); | |
| cs35l41_error_release(cs35l41, CS35L41_AMP_SHORT_ERR, CS35L41_AMP_SHORT_ERR_RLS); | |
| ret = IRQ_HANDLED; | |
| } | |
| if (status[0] & CS35L41_TEMP_WARN) { | |
| dev_crit_ratelimited(cs35l41->dev, "Over temperature warning\n"); | |
| cs35l41_error_release(cs35l41, CS35L41_TEMP_WARN, CS35L41_TEMP_WARN_ERR_RLS); | |
| ret = IRQ_HANDLED; | |
| } | |
| if (status[0] & CS35L41_TEMP_ERR) { | |
| dev_crit_ratelimited(cs35l41->dev, "Over temperature error\n"); | |
| cs35l41_error_release(cs35l41, CS35L41_TEMP_ERR, CS35L41_TEMP_ERR_RLS); | |
| ret = IRQ_HANDLED; | |
| } | |
| if (status[0] & CS35L41_BST_OVP_ERR) { | |
| dev_crit_ratelimited(cs35l41->dev, "VBST Over Voltage error\n"); | |
| cs35l41_boost_enable(cs35l41, 0); | |
| cs35l41_error_release(cs35l41, CS35L41_BST_OVP_ERR, CS35L41_BST_OVP_ERR_RLS); | |
| cs35l41_boost_enable(cs35l41, 1); | |
| ret = IRQ_HANDLED; | |
| } | |
| if (status[0] & CS35L41_BST_DCM_UVP_ERR) { | |
| dev_crit_ratelimited(cs35l41->dev, "DCM VBST Under Voltage Error\n"); | |
| cs35l41_boost_enable(cs35l41, 0); | |
| cs35l41_error_release(cs35l41, CS35L41_BST_DCM_UVP_ERR, CS35L41_BST_UVP_ERR_RLS); | |
| cs35l41_boost_enable(cs35l41, 1); | |
| ret = IRQ_HANDLED; | |
| } | |
| if (status[0] & CS35L41_BST_SHORT_ERR) { | |
| dev_crit_ratelimited(cs35l41->dev, "LBST error: powering off!\n"); | |
| cs35l41_boost_enable(cs35l41, 0); | |
| cs35l41_error_release(cs35l41, CS35L41_BST_SHORT_ERR, CS35L41_BST_SHORT_ERR_RLS); | |
| cs35l41_boost_enable(cs35l41, 1); | |
| ret = IRQ_HANDLED; | |
| } | |
| if (status[2] & CS35L41_PLL_LOCK) { | |
| regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS3, CS35L41_PLL_LOCK); | |
| if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV || | |
| cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS) { | |
| ret = cs35l41_mdsync_up(cs35l41->regmap); | |
| if (ret) | |
| dev_err(cs35l41->dev, "MDSYNC-up failed: %d\n", ret); | |
| else | |
| dev_dbg(cs35l41->dev, "MDSYNC-up done\n"); | |
| dev_dbg(cs35l41->dev, "PUP-done status: %d\n", | |
| !!(status[0] & CS35L41_PUP_DONE_MASK)); | |
| } | |
| ret = IRQ_HANDLED; | |
| } | |
| done: | |
| pm_runtime_put_autosuspend(cs35l41->dev); | |
| return ret; | |
| } | |
| static const struct reg_sequence cs35l41_pup_patch[] = { | |
| { CS35L41_TEST_KEY_CTL, 0x00000055 }, | |
| { CS35L41_TEST_KEY_CTL, 0x000000AA }, | |
| { 0x00002084, 0x002F1AA0 }, | |
| { CS35L41_TEST_KEY_CTL, 0x000000CC }, | |
| { CS35L41_TEST_KEY_CTL, 0x00000033 }, | |
| }; | |
| static const struct reg_sequence cs35l41_pdn_patch[] = { | |
| { CS35L41_TEST_KEY_CTL, 0x00000055 }, | |
| { CS35L41_TEST_KEY_CTL, 0x000000AA }, | |
| { 0x00002084, 0x002F1AA3 }, | |
| { CS35L41_TEST_KEY_CTL, 0x000000CC }, | |
| { CS35L41_TEST_KEY_CTL, 0x00000033 }, | |
| }; | |
| static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w, | |
| struct snd_kcontrol *kcontrol, int event) | |
| { | |
| struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); | |
| int ret = 0; | |
| switch (event) { | |
| case SND_SOC_DAPM_PRE_PMU: | |
| regmap_multi_reg_write_bypassed(cs35l41->regmap, | |
| cs35l41_pup_patch, | |
| ARRAY_SIZE(cs35l41_pup_patch)); | |
| ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, | |
| 1, &cs35l41->dsp.cs_dsp); | |
| break; | |
| case SND_SOC_DAPM_POST_PMD: | |
| ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, | |
| 0, &cs35l41->dsp.cs_dsp); | |
| regmap_multi_reg_write_bypassed(cs35l41->regmap, | |
| cs35l41_pdn_patch, | |
| ARRAY_SIZE(cs35l41_pdn_patch)); | |
| break; | |
| default: | |
| dev_err(cs35l41->dev, "Invalid event = 0x%x\n", event); | |
| ret = -EINVAL; | |
| } | |
| return ret; | |
| } | |
| static const struct snd_soc_dapm_widget cs35l41_dapm_widgets[] = { | |
| SND_SOC_DAPM_SPK("DSP1 Preload", NULL), | |
| SND_SOC_DAPM_SUPPLY_S("DSP1 Preloader", 100, SND_SOC_NOPM, 0, 0, | |
| cs35l41_dsp_preload_ev, | |
| SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), | |
| SND_SOC_DAPM_OUT_DRV_E("DSP1", SND_SOC_NOPM, 0, 0, NULL, 0, | |
| cs35l41_dsp_audio_ev, | |
| SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), | |
| SND_SOC_DAPM_OUTPUT("SPK"), | |
| SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, CS35L41_SP_ENABLES, 16, 0), | |
| SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, CS35L41_SP_ENABLES, 17, 0), | |
| SND_SOC_DAPM_AIF_OUT("ASPTX1", NULL, 0, CS35L41_SP_ENABLES, 0, 0), | |
| SND_SOC_DAPM_AIF_OUT("ASPTX2", NULL, 0, CS35L41_SP_ENABLES, 1, 0), | |
| SND_SOC_DAPM_AIF_OUT("ASPTX3", NULL, 0, CS35L41_SP_ENABLES, 2, 0), | |
| SND_SOC_DAPM_AIF_OUT("ASPTX4", NULL, 0, CS35L41_SP_ENABLES, 3, 0), | |
| SND_SOC_DAPM_SIGGEN("VSENSE"), | |
| SND_SOC_DAPM_SIGGEN("ISENSE"), | |
| SND_SOC_DAPM_SIGGEN("VP"), | |
| SND_SOC_DAPM_SIGGEN("VBST"), | |
| SND_SOC_DAPM_SIGGEN("TEMP"), | |
| SND_SOC_DAPM_SUPPLY("VMON", CS35L41_PWR_CTRL2, 12, 0, NULL, 0), | |
| SND_SOC_DAPM_SUPPLY("IMON", CS35L41_PWR_CTRL2, 13, 0, NULL, 0), | |
| SND_SOC_DAPM_SUPPLY("VPMON", CS35L41_PWR_CTRL2, 8, 0, NULL, 0), | |
| SND_SOC_DAPM_SUPPLY("VBSTMON", CS35L41_PWR_CTRL2, 9, 0, NULL, 0), | |
| SND_SOC_DAPM_SUPPLY("TEMPMON", CS35L41_PWR_CTRL2, 10, 0, NULL, 0), | |
| SND_SOC_DAPM_ADC("VMON ADC", NULL, SND_SOC_NOPM, 0, 0), | |
| SND_SOC_DAPM_ADC("IMON ADC", NULL, SND_SOC_NOPM, 0, 0), | |
| SND_SOC_DAPM_ADC("VPMON ADC", NULL, SND_SOC_NOPM, 0, 0), | |
| SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, SND_SOC_NOPM, 0, 0), | |
| SND_SOC_DAPM_ADC("TEMPMON ADC", NULL, SND_SOC_NOPM, 0, 0), | |
| SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L41_PWR_CTRL3, 4, 0), | |
| SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L41_PWR_CTRL2, 0, 0, NULL, 0, | |
| cs35l41_main_amp_event, | |
| SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), | |
| SND_SOC_DAPM_MUX("ASP TX1 Source", SND_SOC_NOPM, 0, 0, &asp_tx1_mux), | |
| SND_SOC_DAPM_MUX("ASP TX2 Source", SND_SOC_NOPM, 0, 0, &asp_tx2_mux), | |
| SND_SOC_DAPM_MUX("ASP TX3 Source", SND_SOC_NOPM, 0, 0, &asp_tx3_mux), | |
| SND_SOC_DAPM_MUX("ASP TX4 Source", SND_SOC_NOPM, 0, 0, &asp_tx4_mux), | |
| SND_SOC_DAPM_MUX("DSP RX1 Source", SND_SOC_NOPM, 0, 0, &dsp_rx1_mux), | |
| SND_SOC_DAPM_MUX("DSP RX2 Source", SND_SOC_NOPM, 0, 0, &dsp_rx2_mux), | |
| SND_SOC_DAPM_MUX("PCM Source", SND_SOC_NOPM, 0, 0, &pcm_source_mux), | |
| SND_SOC_DAPM_SWITCH("DRE", SND_SOC_NOPM, 0, 0, &dre_ctrl), | |
| }; | |
| static const struct snd_soc_dapm_route cs35l41_audio_map[] = { | |
| {"DSP RX1 Source", "ASPRX1", "ASPRX1"}, | |
| {"DSP RX1 Source", "ASPRX2", "ASPRX2"}, | |
| {"DSP RX2 Source", "ASPRX1", "ASPRX1"}, | |
| {"DSP RX2 Source", "ASPRX2", "ASPRX2"}, | |
| {"DSP1", NULL, "DSP RX1 Source"}, | |
| {"DSP1", NULL, "DSP RX2 Source"}, | |
| {"ASP TX1 Source", "VMON", "VMON ADC"}, | |
| {"ASP TX1 Source", "IMON", "IMON ADC"}, | |
| {"ASP TX1 Source", "VPMON", "VPMON ADC"}, | |
| {"ASP TX1 Source", "VBSTMON", "VBSTMON ADC"}, | |
| {"ASP TX1 Source", "DSPTX1", "DSP1"}, | |
| {"ASP TX1 Source", "DSPTX2", "DSP1"}, | |
| {"ASP TX1 Source", "ASPRX1", "ASPRX1" }, | |
| {"ASP TX1 Source", "ASPRX2", "ASPRX2" }, | |
| {"ASP TX2 Source", "VMON", "VMON ADC"}, | |
| {"ASP TX2 Source", "IMON", "IMON ADC"}, | |
| {"ASP TX2 Source", "VPMON", "VPMON ADC"}, | |
| {"ASP TX2 Source", "VBSTMON", "VBSTMON ADC"}, | |
| {"ASP TX2 Source", "DSPTX1", "DSP1"}, | |
| {"ASP TX2 Source", "DSPTX2", "DSP1"}, | |
| {"ASP TX2 Source", "ASPRX1", "ASPRX1" }, | |
| {"ASP TX2 Source", "ASPRX2", "ASPRX2" }, | |
| {"ASP TX3 Source", "VMON", "VMON ADC"}, | |
| {"ASP TX3 Source", "IMON", "IMON ADC"}, | |
| {"ASP TX3 Source", "VPMON", "VPMON ADC"}, | |
| {"ASP TX3 Source", "VBSTMON", "VBSTMON ADC"}, | |
| {"ASP TX3 Source", "DSPTX1", "DSP1"}, | |
| {"ASP TX3 Source", "DSPTX2", "DSP1"}, | |
| {"ASP TX3 Source", "ASPRX1", "ASPRX1" }, | |
| {"ASP TX3 Source", "ASPRX2", "ASPRX2" }, | |
| {"ASP TX4 Source", "VMON", "VMON ADC"}, | |
| {"ASP TX4 Source", "IMON", "IMON ADC"}, | |
| {"ASP TX4 Source", "VPMON", "VPMON ADC"}, | |
| {"ASP TX4 Source", "VBSTMON", "VBSTMON ADC"}, | |
| {"ASP TX4 Source", "DSPTX1", "DSP1"}, | |
| {"ASP TX4 Source", "DSPTX2", "DSP1"}, | |
| {"ASP TX4 Source", "ASPRX1", "ASPRX1" }, | |
| {"ASP TX4 Source", "ASPRX2", "ASPRX2" }, | |
| {"ASPTX1", NULL, "ASP TX1 Source"}, | |
| {"ASPTX2", NULL, "ASP TX2 Source"}, | |
| {"ASPTX3", NULL, "ASP TX3 Source"}, | |
| {"ASPTX4", NULL, "ASP TX4 Source"}, | |
| {"AMP Capture", NULL, "ASPTX1"}, | |
| {"AMP Capture", NULL, "ASPTX2"}, | |
| {"AMP Capture", NULL, "ASPTX3"}, | |
| {"AMP Capture", NULL, "ASPTX4"}, | |
| {"DSP1", NULL, "VMON"}, | |
| {"DSP1", NULL, "IMON"}, | |
| {"DSP1", NULL, "VPMON"}, | |
| {"DSP1", NULL, "VBSTMON"}, | |
| {"DSP1", NULL, "TEMPMON"}, | |
| {"VMON ADC", NULL, "VMON"}, | |
| {"IMON ADC", NULL, "IMON"}, | |
| {"VPMON ADC", NULL, "VPMON"}, | |
| {"VBSTMON ADC", NULL, "VBSTMON"}, | |
| {"TEMPMON ADC", NULL, "TEMPMON"}, | |
| {"VMON ADC", NULL, "VSENSE"}, | |
| {"IMON ADC", NULL, "ISENSE"}, | |
| {"VPMON ADC", NULL, "VP"}, | |
| {"VBSTMON ADC", NULL, "VBST"}, | |
| {"TEMPMON ADC", NULL, "TEMP"}, | |
| {"DSP1 Preload", NULL, "DSP1 Preloader"}, | |
| {"DSP1", NULL, "DSP1 Preloader"}, | |
| {"ASPRX1", NULL, "AMP Playback"}, | |
| {"ASPRX2", NULL, "AMP Playback"}, | |
| {"DRE", "Switch", "CLASS H"}, | |
| {"Main AMP", NULL, "CLASS H"}, | |
| {"Main AMP", NULL, "DRE"}, | |
| {"SPK", NULL, "Main AMP"}, | |
| {"PCM Source", "ASP", "ASPRX1"}, | |
| {"PCM Source", "DSP", "DSP1"}, | |
| {"CLASS H", NULL, "PCM Source"}, | |
| }; | |
| static int cs35l41_set_channel_map(struct snd_soc_dai *dai, unsigned int tx_n, | |
| const unsigned int *tx_slot, | |
| unsigned int rx_n, const unsigned int *rx_slot) | |
| { | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); | |
| return cs35l41_set_channels(cs35l41->dev, cs35l41->regmap, tx_n, tx_slot, rx_n, rx_slot); | |
| } | |
| static int cs35l41_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
| { | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); | |
| unsigned int daifmt = 0; | |
| switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { | |
| case SND_SOC_DAIFMT_CBP_CFP: | |
| daifmt |= CS35L41_SCLK_MSTR_MASK | CS35L41_LRCLK_MSTR_MASK; | |
| break; | |
| case SND_SOC_DAIFMT_CBC_CFC: | |
| break; | |
| default: | |
| dev_warn(cs35l41->dev, "Mixed provider/consumer mode unsupported\n"); | |
| return -EINVAL; | |
| } | |
| switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
| case SND_SOC_DAIFMT_DSP_A: | |
| break; | |
| case SND_SOC_DAIFMT_I2S: | |
| daifmt |= 2 << CS35L41_ASP_FMT_SHIFT; | |
| break; | |
| default: | |
| dev_warn(cs35l41->dev, "Invalid or unsupported DAI format\n"); | |
| return -EINVAL; | |
| } | |
| switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | |
| case SND_SOC_DAIFMT_NB_IF: | |
| daifmt |= CS35L41_LRCLK_INV_MASK; | |
| break; | |
| case SND_SOC_DAIFMT_IB_NF: | |
| daifmt |= CS35L41_SCLK_INV_MASK; | |
| break; | |
| case SND_SOC_DAIFMT_IB_IF: | |
| daifmt |= CS35L41_LRCLK_INV_MASK | CS35L41_SCLK_INV_MASK; | |
| break; | |
| case SND_SOC_DAIFMT_NB_NF: | |
| break; | |
| default: | |
| dev_warn(cs35l41->dev, "Invalid DAI clock INV\n"); | |
| return -EINVAL; | |
| } | |
| return regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT, | |
| CS35L41_SCLK_MSTR_MASK | CS35L41_LRCLK_MSTR_MASK | | |
| CS35L41_ASP_FMT_MASK | CS35L41_LRCLK_INV_MASK | | |
| CS35L41_SCLK_INV_MASK, daifmt); | |
| } | |
| struct cs35l41_global_fs_config { | |
| int rate; | |
| int fs_cfg; | |
| }; | |
| static const struct cs35l41_global_fs_config cs35l41_fs_rates[] = { | |
| { 12000, 0x01 }, | |
| { 24000, 0x02 }, | |
| { 48000, 0x03 }, | |
| { 96000, 0x04 }, | |
| { 192000, 0x05 }, | |
| { 11025, 0x09 }, | |
| { 22050, 0x0A }, | |
| { 44100, 0x0B }, | |
| { 88200, 0x0C }, | |
| { 176400, 0x0D }, | |
| { 8000, 0x11 }, | |
| { 16000, 0x12 }, | |
| { 32000, 0x13 }, | |
| }; | |
| static int cs35l41_pcm_hw_params(struct snd_pcm_substream *substream, | |
| struct snd_pcm_hw_params *params, | |
| struct snd_soc_dai *dai) | |
| { | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); | |
| unsigned int rate = params_rate(params); | |
| u8 asp_wl; | |
| int i; | |
| for (i = 0; i < ARRAY_SIZE(cs35l41_fs_rates); i++) { | |
| if (rate == cs35l41_fs_rates[i].rate) | |
| break; | |
| } | |
| if (i >= ARRAY_SIZE(cs35l41_fs_rates)) { | |
| dev_err(cs35l41->dev, "Unsupported rate: %u\n", rate); | |
| return -EINVAL; | |
| } | |
| asp_wl = params_width(params); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_GLOBAL_CLK_CTRL, | |
| CS35L41_GLOBAL_FS_MASK, | |
| cs35l41_fs_rates[i].fs_cfg << CS35L41_GLOBAL_FS_SHIFT); | |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
| regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT, | |
| CS35L41_ASP_WIDTH_RX_MASK, | |
| asp_wl << CS35L41_ASP_WIDTH_RX_SHIFT); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_SP_RX_WL, | |
| CS35L41_ASP_RX_WL_MASK, | |
| asp_wl << CS35L41_ASP_RX_WL_SHIFT); | |
| } else { | |
| regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT, | |
| CS35L41_ASP_WIDTH_TX_MASK, | |
| asp_wl << CS35L41_ASP_WIDTH_TX_SHIFT); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_SP_TX_WL, | |
| CS35L41_ASP_TX_WL_MASK, | |
| asp_wl << CS35L41_ASP_TX_WL_SHIFT); | |
| } | |
| return 0; | |
| } | |
| static int cs35l41_get_clk_config(int freq) | |
| { | |
| int i; | |
| for (i = 0; i < ARRAY_SIZE(cs35l41_pll_sysclk); i++) { | |
| if (cs35l41_pll_sysclk[i].freq == freq) | |
| return cs35l41_pll_sysclk[i].clk_cfg; | |
| } | |
| return -EINVAL; | |
| } | |
| static int cs35l41_component_set_sysclk(struct snd_soc_component *component, | |
| int clk_id, int source, | |
| unsigned int freq, int dir) | |
| { | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); | |
| int extclk_cfg, clksrc; | |
| switch (clk_id) { | |
| case CS35L41_CLKID_SCLK: | |
| clksrc = CS35L41_PLLSRC_SCLK; | |
| break; | |
| case CS35L41_CLKID_LRCLK: | |
| clksrc = CS35L41_PLLSRC_LRCLK; | |
| break; | |
| case CS35L41_CLKID_MCLK: | |
| clksrc = CS35L41_PLLSRC_MCLK; | |
| break; | |
| default: | |
| dev_err(cs35l41->dev, "Invalid CLK Config\n"); | |
| return -EINVAL; | |
| } | |
| extclk_cfg = cs35l41_get_clk_config(freq); | |
| if (extclk_cfg < 0) { | |
| dev_err(cs35l41->dev, "Invalid CLK Config: %d, freq: %u\n", | |
| extclk_cfg, freq); | |
| return -EINVAL; | |
| } | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, | |
| CS35L41_PLL_OPENLOOP_MASK, | |
| 1 << CS35L41_PLL_OPENLOOP_SHIFT); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, | |
| CS35L41_REFCLK_FREQ_MASK, | |
| extclk_cfg << CS35L41_REFCLK_FREQ_SHIFT); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, | |
| CS35L41_PLL_CLK_EN_MASK, | |
| 0 << CS35L41_PLL_CLK_EN_SHIFT); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, | |
| CS35L41_PLL_CLK_SEL_MASK, clksrc); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, | |
| CS35L41_PLL_OPENLOOP_MASK, | |
| 0 << CS35L41_PLL_OPENLOOP_SHIFT); | |
| regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, | |
| CS35L41_PLL_CLK_EN_MASK, | |
| 1 << CS35L41_PLL_CLK_EN_SHIFT); | |
| return 0; | |
| } | |
| static int cs35l41_dai_set_sysclk(struct snd_soc_dai *dai, | |
| int clk_id, unsigned int freq, int dir) | |
| { | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); | |
| unsigned int fs1_val; | |
| unsigned int fs2_val; | |
| unsigned int val; | |
| int fsindex; | |
| fsindex = cs35l41_get_fs_mon_config_index(freq); | |
| if (fsindex < 0) { | |
| dev_err(cs35l41->dev, "Invalid CLK Config freq: %u\n", freq); | |
| return -EINVAL; | |
| } | |
| dev_dbg(cs35l41->dev, "Set DAI sysclk %d\n", freq); | |
| if (freq <= 6144000) { | |
| /* Use the lookup table */ | |
| fs1_val = cs35l41_fs_mon[fsindex].fs1; | |
| fs2_val = cs35l41_fs_mon[fsindex].fs2; | |
| } else { | |
| /* Use hard-coded values */ | |
| fs1_val = 0x10; | |
| fs2_val = 0x24; | |
| } | |
| val = fs1_val; | |
| val |= (fs2_val << CS35L41_FS2_WINDOW_SHIFT) & CS35L41_FS2_WINDOW_MASK; | |
| regmap_write(cs35l41->regmap, CS35L41_TST_FS_MON0, val); | |
| return 0; | |
| } | |
| static int cs35l41_set_pdata(struct cs35l41_private *cs35l41) | |
| { | |
| struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; | |
| int ret; | |
| if (!hw_cfg->valid) | |
| return -EINVAL; | |
| if (hw_cfg->bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) | |
| return -EINVAL; | |
| /* Required */ | |
| ret = cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, hw_cfg); | |
| if (ret) | |
| return ret; | |
| /* Optional */ | |
| if (hw_cfg->dout_hiz <= CS35L41_ASP_DOUT_HIZ_MASK && hw_cfg->dout_hiz >= 0) | |
| regmap_update_bits(cs35l41->regmap, CS35L41_SP_HIZ_CTRL, CS35L41_ASP_DOUT_HIZ_MASK, | |
| hw_cfg->dout_hiz); | |
| return 0; | |
| } | |
| static const struct snd_soc_dapm_route cs35l41_ext_bst_routes[] = { | |
| {"Main AMP", NULL, "VSPK"}, | |
| }; | |
| static const struct snd_soc_dapm_widget cs35l41_ext_bst_widget[] = { | |
| SND_SOC_DAPM_SUPPLY("VSPK", CS35L41_GPIO1_CTRL1, CS35L41_GPIO_LVL_SHIFT, 0, NULL, 0), | |
| }; | |
| static int cs35l41_component_probe(struct snd_soc_component *component) | |
| { | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); | |
| struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); | |
| int ret; | |
| if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) { | |
| ret = snd_soc_dapm_new_controls(dapm, cs35l41_ext_bst_widget, | |
| ARRAY_SIZE(cs35l41_ext_bst_widget)); | |
| if (ret) | |
| return ret; | |
| ret = snd_soc_dapm_add_routes(dapm, cs35l41_ext_bst_routes, | |
| ARRAY_SIZE(cs35l41_ext_bst_routes)); | |
| if (ret) | |
| return ret; | |
| } | |
| return wm_adsp2_component_probe(&cs35l41->dsp, component); | |
| } | |
| static void cs35l41_component_remove(struct snd_soc_component *component) | |
| { | |
| struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); | |
| wm_adsp2_component_remove(&cs35l41->dsp, component); | |
| } | |
| static const struct snd_soc_dai_ops cs35l41_ops = { | |
| .set_fmt = cs35l41_set_dai_fmt, | |
| .hw_params = cs35l41_pcm_hw_params, | |
| .set_sysclk = cs35l41_dai_set_sysclk, | |
| .set_channel_map = cs35l41_set_channel_map, | |
| }; | |
| #define CS35L41_RATES ( \ | |
| SNDRV_PCM_RATE_8000_48000 | \ | |
| SNDRV_PCM_RATE_12000 | \ | |
| SNDRV_PCM_RATE_24000 | \ | |
| SNDRV_PCM_RATE_88200 | \ | |
| SNDRV_PCM_RATE_96000 | \ | |
| SNDRV_PCM_RATE_176400 | \ | |
| SNDRV_PCM_RATE_192000) | |
| static struct snd_soc_dai_driver cs35l41_dai[] = { | |
| { | |
| .name = "cs35l41-pcm", | |
| .id = 0, | |
| .playback = { | |
| .stream_name = "AMP Playback", | |
| .channels_min = 1, | |
| .channels_max = 2, | |
| .rates = CS35L41_RATES, | |
| .formats = CS35L41_RX_FORMATS, | |
| }, | |
| .capture = { | |
| .stream_name = "AMP Capture", | |
| .channels_min = 1, | |
| .channels_max = 4, | |
| .rates = CS35L41_RATES, | |
| .formats = CS35L41_TX_FORMATS, | |
| }, | |
| .ops = &cs35l41_ops, | |
| .symmetric_rate = 1, | |
| }, | |
| }; | |
| static const struct snd_soc_component_driver soc_component_dev_cs35l41 = { | |
| .name = "cs35l41-codec", | |
| .probe = cs35l41_component_probe, | |
| .remove = cs35l41_component_remove, | |
| .dapm_widgets = cs35l41_dapm_widgets, | |
| .num_dapm_widgets = ARRAY_SIZE(cs35l41_dapm_widgets), | |
| .dapm_routes = cs35l41_audio_map, | |
| .num_dapm_routes = ARRAY_SIZE(cs35l41_audio_map), | |
| .controls = cs35l41_aud_controls, | |
| .num_controls = ARRAY_SIZE(cs35l41_aud_controls), | |
| .set_sysclk = cs35l41_component_set_sysclk, | |
| .endianness = 1, | |
| }; | |
| static int cs35l41_handle_pdata(struct device *dev, struct cs35l41_hw_cfg *hw_cfg) | |
| { | |
| struct cs35l41_gpio_cfg *gpio1 = &hw_cfg->gpio1; | |
| struct cs35l41_gpio_cfg *gpio2 = &hw_cfg->gpio2; | |
| unsigned int val; | |
| int ret; | |
| /* Some ACPI systems received the Shared Boost feature before the upstream driver, | |
| * leaving those systems with deprecated _DSD properties. | |
| * To correctly configure those systems add shared-boost-active and shared-boost-passive | |
| * properties mapped to the correct value in boost-type. | |
| * These two are not DT properties and should not be used in new systems designs. | |
| */ | |
| if (device_property_read_bool(dev, "cirrus,shared-boost-active")) { | |
| hw_cfg->bst_type = CS35L41_SHD_BOOST_ACTV; | |
| } else if (device_property_read_bool(dev, "cirrus,shared-boost-passive")) { | |
| hw_cfg->bst_type = CS35L41_SHD_BOOST_PASS; | |
| } else { | |
| ret = device_property_read_u32(dev, "cirrus,boost-type", &val); | |
| if (ret >= 0) | |
| hw_cfg->bst_type = val; | |
| } | |
| ret = device_property_read_u32(dev, "cirrus,boost-peak-milliamp", &val); | |
| if (ret >= 0) | |
| hw_cfg->bst_ipk = val; | |
| else | |
| hw_cfg->bst_ipk = -1; | |
| ret = device_property_read_u32(dev, "cirrus,boost-ind-nanohenry", &val); | |
| if (ret >= 0) | |
| hw_cfg->bst_ind = val; | |
| else | |
| hw_cfg->bst_ind = -1; | |
| ret = device_property_read_u32(dev, "cirrus,boost-cap-microfarad", &val); | |
| if (ret >= 0) | |
| hw_cfg->bst_cap = val; | |
| else | |
| hw_cfg->bst_cap = -1; | |
| ret = device_property_read_u32(dev, "cirrus,asp-sdout-hiz", &val); | |
| if (ret >= 0) | |
| hw_cfg->dout_hiz = val; | |
| else | |
| hw_cfg->dout_hiz = -1; | |
| /* GPIO1 Pin Config */ | |
| gpio1->pol_inv = device_property_read_bool(dev, "cirrus,gpio1-polarity-invert"); | |
| gpio1->out_en = device_property_read_bool(dev, "cirrus,gpio1-output-enable"); | |
| ret = device_property_read_u32(dev, "cirrus,gpio1-src-select", &val); | |
| if (ret >= 0) { | |
| gpio1->func = val; | |
| gpio1->valid = true; | |
| } | |
| /* GPIO2 Pin Config */ | |
| gpio2->pol_inv = device_property_read_bool(dev, "cirrus,gpio2-polarity-invert"); | |
| gpio2->out_en = device_property_read_bool(dev, "cirrus,gpio2-output-enable"); | |
| ret = device_property_read_u32(dev, "cirrus,gpio2-src-select", &val); | |
| if (ret >= 0) { | |
| gpio2->func = val; | |
| gpio2->valid = true; | |
| } | |
| hw_cfg->valid = true; | |
| return 0; | |
| } | |
| static int cs35l41_dsp_init(struct cs35l41_private *cs35l41) | |
| { | |
| struct wm_adsp *dsp; | |
| uint32_t dsp1rx5_src; | |
| int ret; | |
| dsp = &cs35l41->dsp; | |
| dsp->part = "cs35l41"; | |
| dsp->fw = 9; /* 9 is WM_ADSP_FW_SPK_PROT in wm_adsp.c */ | |
| dsp->toggle_preload = true; | |
| cs35l41_configure_cs_dsp(cs35l41->dev, cs35l41->regmap, &dsp->cs_dsp); | |
| ret = cs35l41_write_fs_errata(cs35l41->dev, cs35l41->regmap); | |
| if (ret < 0) | |
| return ret; | |
| ret = wm_halo_init(dsp); | |
| if (ret) { | |
| dev_err(cs35l41->dev, "wm_halo_init failed: %d\n", ret); | |
| return ret; | |
| } | |
| switch (cs35l41->hw_cfg.bst_type) { | |
| case CS35L41_INT_BOOST: | |
| case CS35L41_SHD_BOOST_ACTV: | |
| dsp1rx5_src = CS35L41_INPUT_SRC_VPMON; | |
| break; | |
| case CS35L41_EXT_BOOST: | |
| case CS35L41_SHD_BOOST_PASS: | |
| dsp1rx5_src = CS35L41_INPUT_SRC_VBSTMON; | |
| break; | |
| default: | |
| dev_err(cs35l41->dev, "wm_halo_init failed - Invalid Boost Type: %d\n", | |
| cs35l41->hw_cfg.bst_type); | |
| goto err_dsp; | |
| } | |
| ret = regmap_write(cs35l41->regmap, CS35L41_DSP1_RX5_SRC, dsp1rx5_src); | |
| if (ret < 0) { | |
| dev_err(cs35l41->dev, "Write DSP1RX5_SRC: %d failed: %d\n", dsp1rx5_src, ret); | |
| goto err_dsp; | |
| } | |
| ret = regmap_write(cs35l41->regmap, CS35L41_DSP1_RX6_SRC, CS35L41_INPUT_SRC_VBSTMON); | |
| if (ret < 0) { | |
| dev_err(cs35l41->dev, "Write CS35L41_INPUT_SRC_VBSTMON failed: %d\n", ret); | |
| goto err_dsp; | |
| } | |
| ret = regmap_write(cs35l41->regmap, CS35L41_DSP1_RX7_SRC, | |
| CS35L41_INPUT_SRC_TEMPMON); | |
| if (ret < 0) { | |
| dev_err(cs35l41->dev, "Write INPUT_SRC_TEMPMON failed: %d\n", ret); | |
| goto err_dsp; | |
| } | |
| ret = regmap_write(cs35l41->regmap, CS35L41_DSP1_RX8_SRC, | |
| CS35L41_INPUT_SRC_RSVD); | |
| if (ret < 0) { | |
| dev_err(cs35l41->dev, "Write INPUT_SRC_RSVD failed: %d\n", ret); | |
| goto err_dsp; | |
| } | |
| return 0; | |
| err_dsp: | |
| wm_adsp2_remove(dsp); | |
| return ret; | |
| } | |
| static int cs35l41_get_system_name(struct cs35l41_private *cs35l41) | |
| { | |
| struct acpi_device *adev = ACPI_COMPANION(cs35l41->dev); | |
| const char *sub = NULL; | |
| const char *tmp; | |
| int ret = 0; | |
| /* If there is no acpi_device, there is no ACPI for this system, skip checking ACPI */ | |
| if (adev) { | |
| acpi_handle handle = acpi_device_handle(adev); | |
| sub = acpi_get_subsystem_id(handle); | |
| ret = PTR_ERR_OR_ZERO(sub); | |
| if (ret) { | |
| sub = NULL; | |
| /* If no _SUB, fallback to _HID, otherwise fail */ | |
| if (ret == -ENODATA) { | |
| tmp = acpi_device_hid(adev); | |
| /* If dummy hid, return 0 and fallback to legacy firmware path */ | |
| if (!strcmp(tmp, "device")) { | |
| ret = 0; | |
| goto err; | |
| } | |
| sub = kstrdup(tmp, GFP_KERNEL); | |
| if (!sub) { | |
| ret = -ENOMEM; | |
| goto err; | |
| } | |
| } | |
| } | |
| } else { | |
| if (!device_property_read_string(cs35l41->dev, "cirrus,subsystem-id", &tmp)) { | |
| sub = kstrdup(tmp, GFP_KERNEL); | |
| if (!sub) { | |
| ret = -ENOMEM; | |
| goto err; | |
| } | |
| } | |
| } | |
| if (sub) { | |
| cs35l41->dsp.system_name = sub; | |
| dev_info(cs35l41->dev, "Subsystem ID: %s\n", cs35l41->dsp.system_name); | |
| return 0; | |
| } | |
| err: | |
| dev_warn(cs35l41->dev, "Subsystem ID not found\n"); | |
| return ret; | |
| } | |
| int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg) | |
| { | |
| u32 regid, reg_revid, i, mtl_revid, int_status, chipid_match; | |
| int irq_pol = 0; | |
| int ret; | |
| if (hw_cfg) { | |
| cs35l41->hw_cfg = *hw_cfg; | |
| } else { | |
| ret = cs35l41_handle_pdata(cs35l41->dev, &cs35l41->hw_cfg); | |
| if (ret != 0) | |
| return ret; | |
| } | |
| for (i = 0; i < CS35L41_NUM_SUPPLIES; i++) | |
| cs35l41->supplies[i].supply = cs35l41_supplies[i]; | |
| ret = devm_regulator_bulk_get(cs35l41->dev, CS35L41_NUM_SUPPLIES, | |
| cs35l41->supplies); | |
| if (ret != 0) | |
| return dev_err_probe(cs35l41->dev, ret, | |
| "Failed to request core supplies\n"); | |
| ret = regulator_bulk_enable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); | |
| if (ret != 0) | |
| return dev_err_probe(cs35l41->dev, ret, | |
| "Failed to enable core supplies\n"); | |
| /* returning NULL can be an option if in stereo mode */ | |
| cs35l41->reset_gpio = devm_gpiod_get_optional(cs35l41->dev, "reset", | |
| GPIOD_OUT_LOW); | |
| if (IS_ERR(cs35l41->reset_gpio)) { | |
| ret = PTR_ERR(cs35l41->reset_gpio); | |
| cs35l41->reset_gpio = NULL; | |
| if (ret == -EBUSY) { | |
| dev_info(cs35l41->dev, | |
| "Reset line busy, assuming shared reset\n"); | |
| } else { | |
| dev_err_probe(cs35l41->dev, ret, | |
| "Failed to get reset GPIO\n"); | |
| goto err; | |
| } | |
| } | |
| if (cs35l41->reset_gpio) { | |
| /* satisfy minimum reset pulse width spec */ | |
| usleep_range(2000, 2100); | |
| gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); | |
| } | |
| usleep_range(2000, 2100); | |
| ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, | |
| int_status, int_status & CS35L41_OTP_BOOT_DONE, | |
| 1000, 100000); | |
| if (ret) { | |
| dev_err_probe(cs35l41->dev, ret, | |
| "Failed waiting for OTP_BOOT_DONE\n"); | |
| goto err; | |
| } | |
| regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); | |
| if (int_status & CS35L41_OTP_BOOT_ERR) { | |
| dev_err(cs35l41->dev, "OTP Boot error\n"); | |
| ret = -EINVAL; | |
| goto err; | |
| } | |
| ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); | |
| if (ret < 0) { | |
| dev_err_probe(cs35l41->dev, ret, "Get Device ID failed\n"); | |
| goto err; | |
| } | |
| ret = regmap_read(cs35l41->regmap, CS35L41_REVID, ®_revid); | |
| if (ret < 0) { | |
| dev_err_probe(cs35l41->dev, ret, "Get Revision ID failed\n"); | |
| goto err; | |
| } | |
| mtl_revid = reg_revid & CS35L41_MTLREVID_MASK; | |
| /* CS35L41 will have even MTLREVID | |
| * CS35L41R will have odd MTLREVID | |
| */ | |
| chipid_match = (mtl_revid % 2) ? CS35L41R_CHIP_ID : CS35L41_CHIP_ID; | |
| if (regid != chipid_match) { | |
| dev_err(cs35l41->dev, "CS35L41 Device ID (%X). Expected ID %X\n", | |
| regid, chipid_match); | |
| ret = -ENODEV; | |
| goto err; | |
| } | |
| cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); | |
| ret = cs35l41_register_errata_patch(cs35l41->dev, cs35l41->regmap, reg_revid); | |
| if (ret) | |
| goto err; | |
| ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap); | |
| if (ret < 0) { | |
| dev_err_probe(cs35l41->dev, ret, "OTP Unpack failed\n"); | |
| goto err; | |
| } | |
| cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); | |
| irq_pol = cs35l41_gpio_config(cs35l41->regmap, &cs35l41->hw_cfg); | |
| /* Set interrupt masks for critical errors */ | |
| regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, | |
| CS35L41_INT1_MASK_DEFAULT); | |
| if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS || | |
| cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV) | |
| regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK, | |
| 0 << CS35L41_INT3_PLL_LOCK_SHIFT); | |
| ret = devm_request_threaded_irq(cs35l41->dev, cs35l41->irq, NULL, cs35l41_irq, | |
| IRQF_ONESHOT | IRQF_SHARED | irq_pol, | |
| "cs35l41", cs35l41); | |
| if (ret != 0) { | |
| dev_err_probe(cs35l41->dev, ret, "Failed to request IRQ\n"); | |
| goto err; | |
| } | |
| ret = cs35l41_set_pdata(cs35l41); | |
| if (ret < 0) { | |
| dev_err_probe(cs35l41->dev, ret, "Set pdata failed\n"); | |
| goto err; | |
| } | |
| ret = cs35l41_get_system_name(cs35l41); | |
| if (ret < 0) | |
| goto err; | |
| ret = cs35l41_dsp_init(cs35l41); | |
| if (ret < 0) | |
| goto err; | |
| pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000); | |
| pm_runtime_use_autosuspend(cs35l41->dev); | |
| pm_runtime_set_active(cs35l41->dev); | |
| pm_runtime_get_noresume(cs35l41->dev); | |
| pm_runtime_enable(cs35l41->dev); | |
| ret = devm_snd_soc_register_component(cs35l41->dev, | |
| &soc_component_dev_cs35l41, | |
| cs35l41_dai, ARRAY_SIZE(cs35l41_dai)); | |
| if (ret < 0) { | |
| dev_err_probe(cs35l41->dev, ret, "Register codec failed\n"); | |
| goto err_pm; | |
| } | |
| pm_runtime_put_autosuspend(cs35l41->dev); | |
| dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n", | |
| regid, reg_revid); | |
| return 0; | |
| err_pm: | |
| pm_runtime_dont_use_autosuspend(cs35l41->dev); | |
| pm_runtime_disable(cs35l41->dev); | |
| pm_runtime_put_noidle(cs35l41->dev); | |
| wm_adsp2_remove(&cs35l41->dsp); | |
| err: | |
| cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type); | |
| regulator_bulk_disable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); | |
| gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(cs35l41_probe); | |
| void cs35l41_remove(struct cs35l41_private *cs35l41) | |
| { | |
| pm_runtime_get_sync(cs35l41->dev); | |
| pm_runtime_dont_use_autosuspend(cs35l41->dev); | |
| pm_runtime_disable(cs35l41->dev); | |
| regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF); | |
| if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS || | |
| cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV) | |
| regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK, | |
| 1 << CS35L41_INT3_PLL_LOCK_SHIFT); | |
| kfree(cs35l41->dsp.system_name); | |
| wm_adsp2_remove(&cs35l41->dsp); | |
| cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type); | |
| pm_runtime_put_noidle(cs35l41->dev); | |
| regulator_bulk_disable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); | |
| gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); | |
| } | |
| EXPORT_SYMBOL_GPL(cs35l41_remove); | |
| static int cs35l41_runtime_suspend(struct device *dev) | |
| { | |
| struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); | |
| dev_dbg(cs35l41->dev, "Runtime suspend\n"); | |
| if (!cs35l41->dsp.preloaded || !cs35l41->dsp.cs_dsp.running) | |
| return 0; | |
| wm_adsp_hibernate(&cs35l41->dsp, true); | |
| cs35l41_enter_hibernate(dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type); | |
| regcache_cache_only(cs35l41->regmap, true); | |
| regcache_mark_dirty(cs35l41->regmap); | |
| return 0; | |
| } | |
| static int cs35l41_runtime_resume(struct device *dev) | |
| { | |
| struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); | |
| int ret; | |
| dev_dbg(cs35l41->dev, "Runtime resume\n"); | |
| if (!cs35l41->dsp.preloaded || !cs35l41->dsp.cs_dsp.running) | |
| return 0; | |
| regcache_cache_only(cs35l41->regmap, false); | |
| ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); | |
| if (ret) | |
| return ret; | |
| /* Test key needs to be unlocked to allow the OTP settings to re-apply */ | |
| cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); | |
| ret = regcache_sync(cs35l41->regmap); | |
| cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); | |
| wm_adsp_hibernate(&cs35l41->dsp, false); | |
| if (ret) { | |
| dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); | |
| return ret; | |
| } | |
| cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg); | |
| return 0; | |
| } | |
| static int cs35l41_sys_suspend(struct device *dev) | |
| { | |
| struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); | |
| dev_dbg(cs35l41->dev, "System suspend, disabling IRQ\n"); | |
| disable_irq(cs35l41->irq); | |
| return 0; | |
| } | |
| static int cs35l41_sys_suspend_noirq(struct device *dev) | |
| { | |
| struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); | |
| dev_dbg(cs35l41->dev, "Late system suspend, reenabling IRQ\n"); | |
| enable_irq(cs35l41->irq); | |
| return 0; | |
| } | |
| static int cs35l41_sys_resume_noirq(struct device *dev) | |
| { | |
| struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); | |
| dev_dbg(cs35l41->dev, "Early system resume, disabling IRQ\n"); | |
| disable_irq(cs35l41->irq); | |
| return 0; | |
| } | |
| static int cs35l41_sys_resume(struct device *dev) | |
| { | |
| struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); | |
| dev_dbg(cs35l41->dev, "System resume, reenabling IRQ\n"); | |
| enable_irq(cs35l41->irq); | |
| return 0; | |
| } | |
| EXPORT_GPL_DEV_PM_OPS(cs35l41_pm_ops) = { | |
| RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL) | |
| SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend, cs35l41_sys_resume) | |
| NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend_noirq, cs35l41_sys_resume_noirq) | |
| }; | |
| MODULE_DESCRIPTION("ASoC CS35L41 driver"); | |
| MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, <david.rhodes@cirrus.com>"); | |
| MODULE_LICENSE("GPL"); |