diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index ec9b1072d6be..349b896ee737 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -9,6 +9,7 @@ #ifndef __CS35L56_H #define __CS35L56_H +#include #include #include #include @@ -62,6 +63,8 @@ #define CS35L56_IRQ1_MASK_8 0x000E0AC #define CS35L56_IRQ1_MASK_18 0x000E0D4 #define CS35L56_IRQ1_MASK_20 0x000E0DC +#define CS35L56_MIXER_NGATE_CH1_CFG 0x0010004 +#define CS35L56_MIXER_NGATE_CH2_CFG 0x0010008 #define CS35L56_DSP_MBOX_1_RAW 0x0011000 #define CS35L56_DSP_VIRTUAL1_MBOX_1 0x0011020 #define CS35L56_DSP_VIRTUAL1_MBOX_2 0x0011024 @@ -177,6 +180,9 @@ /* IRQ1_EINT_8 */ #define CS35L56_TEMP_ERR_EINT1_MASK 0x80000000 +/* MIXER_NGATE_CHn_CFG */ +#define CS35L56_AUX_NGATE_CHn_EN 0x00000001 + /* Mixer input sources */ #define CS35L56_INPUT_SRC_NONE 0x00 #define CS35L56_INPUT_SRC_ASP1RX1 0x08 @@ -243,6 +249,7 @@ #define CS35L56_MBOX_CMD_AUDIO_PLAY 0x0B000001 #define CS35L56_MBOX_CMD_AUDIO_PAUSE 0x0B000002 #define CS35L56_MBOX_CMD_AUDIO_REINIT 0x0B000003 +#define CS35L56_MBOX_CMD_AUDIO_CALIBRATION 0x0B000006 #define CS35L56_MBOX_CMD_HIBERNATE_NOW 0x02000001 #define CS35L56_MBOX_CMD_WAKEUP 0x02000002 #define CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE 0x02000003 @@ -264,6 +271,9 @@ #define CS35L56_RESET_PULSE_MIN_US 1100 #define CS35L56_WAKE_HOLD_TIME_US 1000 +#define CS35L56_CALIBRATION_POLL_US (100 * USEC_PER_MSEC) +#define CS35L56_CALIBRATION_TIMEOUT_US (5 * USEC_PER_SEC) + #define CS35L56_SDW1_PLAYBACK_PORT 1 #define CS35L56_SDW1_CAPTURE_PORT 3 @@ -291,9 +301,16 @@ struct cs35l56_fw_reg { unsigned int posture_number; }; +struct cs35l56_cal_debugfs_fops { + const struct debugfs_short_fops calibrate; + const struct debugfs_short_fops cal_temperature; + const struct debugfs_short_fops cal_data; +}; + struct cs35l56_base { struct device *dev; struct regmap *regmap; + struct cs_dsp *dsp; int irq; struct mutex irq_lock; u8 type; @@ -309,6 +326,7 @@ struct cs35l56_base { struct cs35l56_spi_payload *spi_payload_buf; const struct cs35l56_fw_reg *fw_reg; const struct cirrus_amp_cal_controls *calibration_controls; + struct dentry *debugfs; u64 silicon_uid; }; @@ -359,6 +377,21 @@ int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp); int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base); +ssize_t cs35l56_calibrate_debugfs_write(struct cs35l56_base *cs35l56_base, + const char __user *from, size_t count, + loff_t *ppos); +ssize_t cs35l56_cal_ambient_debugfs_write(struct cs35l56_base *cs35l56_base, + const char __user *from, size_t count, + loff_t *ppos); +ssize_t cs35l56_cal_data_debugfs_read(struct cs35l56_base *cs35l56_base, + char __user *to, size_t count, + loff_t *ppos); +ssize_t cs35l56_cal_data_debugfs_write(struct cs35l56_base *cs35l56_base, + const char __user *from, size_t count, + loff_t *ppos); +void cs35l56_create_cal_debugfs(struct cs35l56_base *cs35l56_base, + const struct cs35l56_cal_debugfs_fops *fops); +void cs35l56_remove_cal_debugfs(struct cs35l56_base *cs35l56_base); int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base, bool *fw_missing, unsigned int *fw_version); void cs35l56_log_tuning(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 160c07699a8b..6bb24325c2d0 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -896,6 +896,9 @@ config SND_SOC_CS35L56_SDW help Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control +config SND_SOC_CS35L56_CAL_DEBUGFS_COMMON + bool + config SND_SOC_CS40L50 tristate "Cirrus Logic CS40L50 CODEC" depends on MFD_CS40L50_CORE diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index 1ecfc38d8eb4..eeb830e3f743 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -6,11 +6,18 @@ // Cirrus Logic International Semiconductor Ltd. #include +#include +#include #include +#include #include +#include #include #include #include +#include +#include +#include #include #include @@ -206,6 +213,8 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg) case CS35L56_IRQ1_MASK_8: case CS35L56_IRQ1_MASK_18: case CS35L56_IRQ1_MASK_20: + case CS35L56_MIXER_NGATE_CH1_CFG: + case CS35L56_MIXER_NGATE_CH2_CFG: case CS35L56_DSP_VIRTUAL1_MBOX_1: case CS35L56_DSP_VIRTUAL1_MBOX_2: case CS35L56_DSP_VIRTUAL1_MBOX_3: @@ -263,6 +272,8 @@ static bool cs35l56_common_volatile_reg(unsigned int reg) case CS35L56_IRQ1_EINT_1 ... CS35L56_IRQ1_EINT_8: case CS35L56_IRQ1_EINT_18: case CS35L56_IRQ1_EINT_20: + case CS35L56_MIXER_NGATE_CH1_CFG: + case CS35L56_MIXER_NGATE_CH2_CFG: case CS35L56_DSP_VIRTUAL1_MBOX_1: case CS35L56_DSP_VIRTUAL1_MBOX_2: case CS35L56_DSP_VIRTUAL1_MBOX_3: @@ -724,15 +735,11 @@ static void cs35l56_issue_wake_event(struct cs35l56_base *cs35l56_base) cs35l56_wait_control_port_ready(); } -int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base) +static int cs35l56_wait_for_ps3(struct cs35l56_base *cs35l56_base) { unsigned int val; int ret; - if (!cs35l56_base->init_done) - return 0; - - /* Firmware must have entered a power-save state */ ret = regmap_read_poll_timeout(cs35l56_base->regmap, cs35l56_base->fw_reg->transducer_actual_ps, val, (val >= CS35L56_PS3), @@ -741,6 +748,17 @@ int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base) if (ret) dev_warn(cs35l56_base->dev, "PS3 wait failed: %d\n", ret); + return ret; +} + +int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base) +{ + if (!cs35l56_base->init_done) + return 0; + + /* Firmware must have entered a power-save state */ + cs35l56_wait_for_ps3(cs35l56_base); + /* Clear BOOT_DONE so it can be used to detect a reboot */ regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_4, CS35L56_OTP_BOOT_DONE_MASK); @@ -839,6 +857,8 @@ void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_ds cs_dsp->mem = cs35l56_dsp1_regions; cs_dsp->num_mems = ARRAY_SIZE(cs35l56_dsp1_regions); cs_dsp->no_core_startstop = true; + + cs35l56_base->dsp = cs_dsp; } EXPORT_SYMBOL_NS_GPL(cs35l56_init_cs_dsp, "SND_SOC_CS35L56_SHARED"); @@ -942,6 +962,276 @@ int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base) } EXPORT_SYMBOL_NS_GPL(cs35l56_get_calibration, "SND_SOC_CS35L56_SHARED"); +static int cs35l56_stash_calibration(struct cs35l56_base *cs35l56_base, + const struct cirrus_amp_cal_data *data) +{ + + /* Ignore if it is empty */ + if (!data->calTime[0] && !data->calTime[1]) + return -ENODATA; + + if (cs_amp_cal_target_u64(data) != cs35l56_base->silicon_uid) { + dev_err(cs35l56_base->dev, "cal_data not for this silicon ID\n"); + return -EINVAL; + } + + cs35l56_base->cal_data = *data; + cs35l56_base->cal_data_valid = true; + + return 0; +} + +static int cs35l56_perform_calibration(struct cs35l56_base *cs35l56_base) +{ + const struct cirrus_amp_cal_controls *calibration_controls = + cs35l56_base->calibration_controls; + struct cs_dsp *dsp = cs35l56_base->dsp; + struct cirrus_amp_cal_data cal_data; + struct cs_dsp_coeff_ctl *ctl; + bool ngate_ch1_was_enabled = false; + bool ngate_ch2_was_enabled = false; + int cali_norm_en_alg_id, cali_norm_en_mem; + int ret; + __be32 val; + + if (cs35l56_base->silicon_uid == 0) { + dev_err(cs35l56_base->dev, "Cannot calibrate: no silicon UID\n"); + return -ENXIO; + } + + switch (cs35l56_base->type) { + case 0x54: + case 0x56: + case 0x57: + if (cs35l56_base->rev < 0xb2) { + cali_norm_en_alg_id = 0x9f22f; + cali_norm_en_mem = WMFW_ADSP2_YM; + } else { + cali_norm_en_alg_id = 0x9f210; + cali_norm_en_mem = WMFW_ADSP2_XM; + } + break; + default: + cali_norm_en_alg_id = 0xbf210; + cali_norm_en_mem = WMFW_ADSP2_XM; + break; + } + + ret = pm_runtime_resume_and_get(cs35l56_base->dev); + if (ret) + return ret; + + ret = cs35l56_wait_for_ps3(cs35l56_base); + if (ret) + goto err_pm_put; + + regmap_update_bits_check(cs35l56_base->regmap, CS35L56_MIXER_NGATE_CH1_CFG, + CS35L56_AUX_NGATE_CHn_EN, 0, &ngate_ch1_was_enabled); + regmap_update_bits_check(cs35l56_base->regmap, CS35L56_MIXER_NGATE_CH2_CFG, + CS35L56_AUX_NGATE_CHn_EN, 0, &ngate_ch2_was_enabled); + + scoped_guard(mutex, &dsp->pwr_lock) { + ctl = cs_dsp_get_ctl(dsp, + calibration_controls->status, + calibration_controls->mem_region, + calibration_controls->alg_id); + if (!ctl) { + dev_err(cs35l56_base->dev, "Could not get %s control\n", + calibration_controls->status); + ret = -ENXIO; + goto err; + } + + val = cpu_to_be32(0); + ret = cs_dsp_coeff_write_ctrl(cs_dsp_get_ctl(dsp, + "CALI_NORM_EN", + cali_norm_en_mem, + cali_norm_en_alg_id), + 0, &val, sizeof(val)); + if (ret < 0) { + dev_err(cs35l56_base->dev, "Could not write %s: %d\n", "CALI_NORM_EN", ret); + goto err; + } + + ret = cs35l56_mbox_send(cs35l56_base, CS35L56_MBOX_CMD_AUDIO_CALIBRATION); + if (ret) + goto err; + + if (read_poll_timeout(cs_dsp_coeff_read_ctrl, ret, + (val == cpu_to_be32(1)), + CS35L56_CALIBRATION_POLL_US, + CS35L56_CALIBRATION_TIMEOUT_US, + true, + ctl, 0, &val, sizeof(val))) { + dev_err(cs35l56_base->dev, "Calibration timed out (CAL_STATUS: %u)\n", + be32_to_cpu(val)); + ret = -ETIMEDOUT; + goto err; + } + } + + cs35l56_base->cal_data_valid = false; + memset(&cal_data, 0, sizeof(cal_data)); + ret = cs_amp_read_cal_coeffs(dsp, calibration_controls, &cal_data); + if (ret) + goto err; + + dev_info(cs35l56_base->dev, "Cal status:%d calR:%d ambient:%d\n", + cal_data.calStatus, cal_data.calR, cal_data.calAmbient); + + cal_data.calTarget[0] = (u32)cs35l56_base->silicon_uid; + cal_data.calTarget[1] = (u32)(cs35l56_base->silicon_uid >> 32); + cs35l56_base->cal_data = cal_data; + cs35l56_base->cal_data_valid = true; + + ret = 0; + +err: + if (ngate_ch1_was_enabled) { + regmap_set_bits(cs35l56_base->regmap, CS35L56_MIXER_NGATE_CH1_CFG, + CS35L56_AUX_NGATE_CHn_EN); + } + if (ngate_ch2_was_enabled) { + regmap_set_bits(cs35l56_base->regmap, CS35L56_MIXER_NGATE_CH2_CFG, + CS35L56_AUX_NGATE_CHn_EN); + } +err_pm_put: + pm_runtime_put(cs35l56_base->dev); + + return ret; +} + +ssize_t cs35l56_calibrate_debugfs_write(struct cs35l56_base *cs35l56_base, + const char __user *from, size_t count, + loff_t *ppos) +{ + static const char * const options[] = { "factory" }; + char buf[8] = { 0 }; + int ret; + + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_DEBUGFS_COMMON)) + return -ENXIO; + + if (*ppos) + return -EINVAL; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, from, count); + if (ret < 0) + return ret; + + switch (sysfs_match_string(options, buf)) { + case 0: + ret = cs35l56_perform_calibration(cs35l56_base); + if (ret < 0) + return ret; + break; + default: + return -ENXIO; + } + + return count; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_calibrate_debugfs_write, "SND_SOC_CS35L56_SHARED"); + +ssize_t cs35l56_cal_ambient_debugfs_write(struct cs35l56_base *cs35l56_base, + const char __user *from, size_t count, + loff_t *ppos) +{ + unsigned long val; + int ret; + + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_DEBUGFS_COMMON)) + return -ENXIO; + + if (*ppos) + return -EINVAL; + + ret = pm_runtime_resume_and_get(cs35l56_base->dev); + if (ret) + return ret; + + ret = kstrtoul_from_user(from, count, 10, &val); + if (ret < 0) + goto out; + + ret = cs_amp_write_ambient_temp(cs35l56_base->dsp, cs35l56_base->calibration_controls, val); +out: + pm_runtime_put(cs35l56_base->dev); + + if (ret < 0) + return ret; + + return count; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_cal_ambient_debugfs_write, "SND_SOC_CS35L56_SHARED"); + +ssize_t cs35l56_cal_data_debugfs_read(struct cs35l56_base *cs35l56_base, + char __user *to, size_t count, + loff_t *ppos) +{ + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_DEBUGFS_COMMON)) + return -ENXIO; + + if (!cs35l56_base->cal_data_valid) + return 0; + + return simple_read_from_buffer(to, count, ppos, &cs35l56_base->cal_data, + sizeof(cs35l56_base->cal_data)); +} +EXPORT_SYMBOL_NS_GPL(cs35l56_cal_data_debugfs_read, "SND_SOC_CS35L56_SHARED"); + +ssize_t cs35l56_cal_data_debugfs_write(struct cs35l56_base *cs35l56_base, + const char __user *from, size_t count, + loff_t *ppos) +{ + struct cirrus_amp_cal_data cal_data; + int ret; + + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_DEBUGFS_COMMON)) + return -ENXIO; + + /* Only allow a full blob to be written */ + if (*ppos || (count != sizeof(cal_data))) + return -EMSGSIZE; + + ret = simple_write_to_buffer(&cal_data, sizeof(cal_data), ppos, from, count); + if (ret) + return ret; + + ret = cs35l56_stash_calibration(cs35l56_base, &cal_data); + if (ret) + return ret; + + return count; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_cal_data_debugfs_write, "SND_SOC_CS35L56_SHARED"); + +void cs35l56_create_cal_debugfs(struct cs35l56_base *cs35l56_base, + const struct cs35l56_cal_debugfs_fops *fops) +{ + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_DEBUGFS_COMMON)) + return; + + cs35l56_base->debugfs = cs_amp_create_debugfs(cs35l56_base->dev); + + debugfs_create_file("calibrate", + 0200, cs35l56_base->debugfs, cs35l56_base, + &fops->calibrate); + debugfs_create_file("cal_temperature", + 0200, cs35l56_base->debugfs, cs35l56_base, + &fops->cal_temperature); + debugfs_create_file("cal_data", + 0644, cs35l56_base->debugfs, cs35l56_base, + &fops->cal_data); +} +EXPORT_SYMBOL_NS_GPL(cs35l56_create_cal_debugfs, "SND_SOC_CS35L56_SHARED"); + +void cs35l56_remove_cal_debugfs(struct cs35l56_base *cs35l56_base) +{ + debugfs_remove_recursive(cs35l56_base->debugfs); +} +EXPORT_SYMBOL_NS_GPL(cs35l56_remove_cal_debugfs, "SND_SOC_CS35L56_SHARED"); + int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base, bool *fw_missing, unsigned int *fw_version) {