summaryrefslogtreecommitdiff
path: root/cs40l26
diff options
context:
space:
mode:
authorTai Kuo <taikuo@google.com>2021-08-05 14:06:01 -0500
committerTai Kuo <taikuo@google.com>2021-10-05 05:08:49 +0000
commit3ac72aa40e44b51cd4aac3b2432f4a46b39eff90 (patch)
tree243fabcc2ee24a3fb8a0bfd06470c05035b0eef0 /cs40l26
parent51e2f712d064f598b2861fd3abc78747084eda17 (diff)
downloadamplifiers-3ac72aa40e44b51cd4aac3b2432f4a46b39eff90.tar.gz
cs40l26: merge CirrusLogic cs40l26 v3.0.3
Branch: v5.10-cs40l26 Tag: cs40l26-v3.0.3_5.10 Files: - drivers/input/misc/cs40l26-i2c.c (No changes) - drivers/input/misc/cs40l26-spi.c (No changes) - drivers/input/misc/cs40l26-sysfs.c - drivers/input/misc/cs40l26-tables.c (No changes) - drivers/input/misc/cs40l26.c - include/linux/mfd/cs40l26.h - sound/soc/codecs/cs40l26.c -> cs40l26-codec.c Fetures: - Defer Class H Handling to Firmware (rev >= 7.2.10) - Add bost_disable_delay sysfs control - Support for indefinite SVC and Wavetable tunings based on LE value - Add F0_Offset sysfs control - Use firmware timer instead of HR Timer to schedule haptics Bug fixes: - Avoid infinite vibration race-condition - Graceful exiting of interrupt handler if wake fails - Use programmed duration for OWT effects instead of calculated waveform lentgth Commits: 48d307c79dfb input: cs40l26: Use firmware timer to schedule haptic effects 93880c7a2a41 input: cs40l26: Recover from wakeup error in interrupt handler a330f54756ee input: cs40l26: Change timeout control for OWT a7209b6bce3b input: cs40l26: Add F0 offset sysfs (Skip) 80e7aa51feaa Documentation: cs40l26: Support for multiple SVC LE ranges 83e57c54b934 input: cs40l26: Support for multiple SVC LE ranges 0bc5f644a3fd input: cs40l26: Use STOP_PLAYBACK mailbox command a8c2d057d971 input: cs40l26: Add boost_disable_delay control 0f0eb802dd0f input: cs40l26: Remove Class H handling 18877c7a715c ASoC: cs40l26: Remove Class H handling Bug: 193793095 Test: NFC, bugreport, notification vibration can stop. Signed-off-by: Tai Kuo <taikuo@google.com> Change-Id: Iec2a75aa2c94e14c6079b05c2b438afd6c7beca1 (cherry picked from commit aaad7003272facb3e7eb2c768520885390ccb963)
Diffstat (limited to 'cs40l26')
-rw-r--r--cs40l26/cs40l26-codec.c10
-rw-r--r--cs40l26/cs40l26-sysfs.c137
-rw-r--r--cs40l26/cs40l26.c299
-rw-r--r--cs40l26/cs40l26.h56
4 files changed, 342 insertions, 160 deletions
diff --git a/cs40l26/cs40l26-codec.c b/cs40l26/cs40l26-codec.c
index ce8b316..d6def2f 100644
--- a/cs40l26/cs40l26-codec.c
+++ b/cs40l26/cs40l26-codec.c
@@ -220,12 +220,6 @@ static int cs40l26_pcm_ev(struct snd_soc_dapm_widget *w,
goto err_mutex;
}
- if (!codec->bypass_dsp && !codec->svc_for_streaming_data) {
- ret = cs40l26_class_h_set(cs40l26, true);
- if (ret)
- goto err_mutex;
- }
-
asp_enables = 1 | (1 << CS40L26_ASP_TX2_EN_SHIFT)
| (1 << CS40L26_ASP_RX1_EN_SHIFT)
| (1 << CS40L26_ASP_RX2_EN_SHIFT);
@@ -269,10 +263,6 @@ static int cs40l26_pcm_ev(struct snd_soc_dapm_widget *w,
if (ret)
goto err_mutex;
- ret = cs40l26_class_h_set(cs40l26, false);
- if (ret)
- goto err_mutex;
-
ret = regmap_update_bits(regmap, CS40L26_ASP_ENABLES1,
asp_en_mask, 0);
if (ret) {
diff --git a/cs40l26/cs40l26-sysfs.c b/cs40l26/cs40l26-sysfs.c
index b4ab9c5..38dadf6 100644
--- a/cs40l26/cs40l26-sysfs.c
+++ b/cs40l26/cs40l26-sysfs.c
@@ -308,6 +308,141 @@ err_pm:
}
static DEVICE_ATTR(num_waves, 0440, cs40l26_num_waves_show, NULL);
+/* boost_disable_delay is in units of 125us, e.g. 8 -> 1ms */
+static ssize_t cs40l26_boost_disable_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ u32 reg, boost_disable_delay;
+ int ret;
+
+ pm_runtime_get_sync(cs40l26->dev);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "BOOST_DISABLE_DELAY",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, &reg);
+ if (ret)
+ goto err_pm;
+
+ ret = regmap_read(cs40l26->regmap, reg, &boost_disable_delay);
+ if (ret)
+ goto err_pm;
+
+ ret = snprintf(buf, PAGE_SIZE, "%d\n", boost_disable_delay);
+
+err_pm:
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ return ret;
+}
+
+static ssize_t cs40l26_boost_disable_delay_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ u32 reg, boost_disable_delay;
+ int ret;
+
+ dev_dbg(cs40l26->dev, "%s: %s", __func__, buf);
+
+ ret = kstrtou32(buf, 10, &boost_disable_delay);
+
+ if (ret ||
+ boost_disable_delay < CS40L26_BOOST_DISABLE_DELAY_MIN ||
+ boost_disable_delay > CS40L26_BOOST_DISABLE_DELAY_MAX)
+ return -EINVAL;
+
+ pm_runtime_get_sync(cs40l26->dev);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "BOOST_DISABLE_DELAY",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, &reg);
+ if (ret)
+ goto err_pm;
+
+ ret = regmap_write(cs40l26->regmap, reg, boost_disable_delay);
+
+err_pm:
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ if (ret)
+ return ret;
+ else
+ return count;
+}
+static DEVICE_ATTR(boost_disable_delay, 0660, cs40l26_boost_disable_delay_show,
+ cs40l26_boost_disable_delay_store);
+
+static ssize_t cs40l26_f0_offset_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ unsigned int reg, val;
+ int ret;
+
+ pm_runtime_get_sync(cs40l26->dev);
+ mutex_lock(&cs40l26->lock);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "F0_OFFSET",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID,
+ &reg);
+ if (ret)
+ goto err_mutex;
+
+
+ ret = regmap_read(cs40l26->regmap, reg, &val);
+ if (ret)
+ goto err_mutex;
+
+ ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ return ret;
+}
+
+static ssize_t cs40l26_f0_offset_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ unsigned int reg, val;
+ int ret;
+
+ ret = kstrtou32(buf, 10, &val);
+ if (ret)
+ return -EINVAL;
+
+ if (val > CS40L26_F0_OFFSET_MAX && val < CS40L26_F0_OFFSET_MIN)
+ return -EINVAL;
+
+ pm_runtime_get_sync(cs40l26->dev);
+ mutex_lock(&cs40l26->lock);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "F0_OFFSET",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID,
+ &reg);
+ if (ret)
+ goto err_mutex;
+
+ ret = regmap_write(cs40l26->regmap, reg, val);
+ if (ret)
+ goto err_mutex;
+
+ ret = count;
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ return ret;
+}
+static DEVICE_ATTR(f0_offset, 0660, cs40l26_f0_offset_show,
+ cs40l26_f0_offset_store);
+
static struct attribute *cs40l26_dev_attrs[] = {
&dev_attr_num_waves.attr,
&dev_attr_die_temp.attr,
@@ -318,6 +453,8 @@ static struct attribute *cs40l26_dev_attrs[] = {
&dev_attr_fw_mode.attr,
&dev_attr_pm_timeout_ms.attr,
&dev_attr_vibe_state.attr,
+ &dev_attr_boost_disable_delay.attr,
+ &dev_attr_f0_offset.attr,
NULL,
};
diff --git a/cs40l26/cs40l26.c b/cs40l26/cs40l26.c
index a93e3a9..f8c7afd 100644
--- a/cs40l26/cs40l26.c
+++ b/cs40l26/cs40l26.c
@@ -116,26 +116,6 @@ int cs40l26_ack_write(struct cs40l26_private *cs40l26, u32 reg, u32 write_val,
}
EXPORT_SYMBOL(cs40l26_ack_write);
-int cs40l26_class_h_set(struct cs40l26_private *cs40l26, bool class_h)
-{
- int ret;
-
- ret = regmap_update_bits(cs40l26->regmap, CS40L26_VBST_CTL_2,
- CS40L26_BST_CTL_SEL_MASK, class_h);
- if (ret) {
- dev_err(cs40l26->dev, "Failed to select Class H BST CTRL\n");
- return ret;
- }
-
- ret = regmap_update_bits(cs40l26->regmap, CS40L26_BLOCK_ENABLES2,
- CS40L26_CLASS_H_EN_MASK, class_h << CS40L26_CLASS_H_EN_SHIFT);
- if (ret)
- dev_err(cs40l26->dev, "Failed to update CLASS H tracking\n");
-
- return ret;
-}
-EXPORT_SYMBOL(cs40l26_class_h_set);
-
int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state)
{
u32 reg, dsp_state;
@@ -600,16 +580,9 @@ void cs40l26_vibe_state_set(struct cs40l26_private *cs40l26,
if (cs40l26->vibe_state == new_state)
return;
- if (new_state == CS40L26_VIBE_STATE_STOPPED && cs40l26->asp_enable) {
- /* Re-enable audio stream */
- cs40l26_class_h_set(cs40l26, true);
+ if (new_state == CS40L26_VIBE_STATE_STOPPED && cs40l26->asp_enable)
queue_work(cs40l26->asp_workqueue, &cs40l26->asp_work);
- } else if (new_state == CS40L26_VIBE_STATE_HAPTIC &&
- cs40l26->vibe_state == CS40L26_VIBE_STATE_ASP) {
- cs40l26_class_h_set(cs40l26, false);
- }
-
cs40l26->vibe_state = new_state;
#if !IS_ENABLED(CONFIG_INPUT_CS40L26_ATTR_UNDER_BUS)
sysfs_notify(&cs40l26->input->dev.kobj, "default", "vibe_state");
@@ -1107,7 +1080,19 @@ static irqreturn_t cs40l26_irq(int irq, void *data)
return IRQ_NONE;
}
- pm_runtime_get_sync(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_set_active(dev);
+
+ dev_err(dev, "PM Runtime Resume failed, interrupts missed\n");
+
+ cs40l26_dsp_write(cs40l26, CS40L26_IRQ1_EINT_1,
+ CS40L26_IRQ_EINT1_ALL_MASK);
+ cs40l26_dsp_write(cs40l26, CS40L26_IRQ1_EINT_2,
+ CS40L26_IRQ_EINT2_ALL_MASK);
+
+ goto err;
+ }
ret = regmap_read(regmap, CS40L26_IRQ1_EINT_1, &eint);
if (ret) {
@@ -1622,20 +1607,23 @@ static void cs40l26_vibe_start_worker(struct work_struct *work)
owt = cs40l26_owt_find(cs40l26, cs40l26->effect->id);
if (owt == NULL)
return;
-
- duration = CS40L26_SAMPS_TO_MS((owt->wlength &
- CS40L26_WT_TYPE10_WAVELEN_MAX));
- } else {
- duration = cs40l26->effect->replay.length;
}
+ duration = cs40l26->effect->replay.length;
+
pm_runtime_get_sync(dev);
mutex_lock(&cs40l26->lock);
- if (duration > 0) /* Effect duration is known */
- hrtimer_start(&cs40l26->vibe_timer,
- ktime_set(CS40L26_MS_TO_SECS(duration),
- CS40L26_MS_TO_NS(duration % 1000)), HRTIMER_MODE_REL);
+ ret = cl_dsp_get_reg(cs40l26->dsp, "TIMEOUT_MS",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, &reg);
+ if (ret)
+ goto err_mutex;
+
+ ret = regmap_write(cs40l26->regmap, reg, duration);
+ if (ret) {
+ dev_err(dev, "Failed to set TIMEOUT_MS\n");
+ goto err_mutex;
+ }
ret = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_INVERT",
CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, &reg);
@@ -1703,7 +1691,6 @@ static void cs40l26_vibe_stop_worker(struct work_struct *work)
{
struct cs40l26_private *cs40l26 = container_of(work,
struct cs40l26_private, vibe_stop_work);
- unsigned int reg;
int ret;
dev_dbg(cs40l26->dev, "%s\n", __func__);
@@ -1711,30 +1698,14 @@ static void cs40l26_vibe_stop_worker(struct work_struct *work)
pm_runtime_get_sync(cs40l26->dev);
mutex_lock(&cs40l26->lock);
- if (cs40l26->effect->u.periodic.waveform == FF_SINE) {
- ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
- CS40L26_STOP_PLAYBACK, CS40L26_DSP_MBOX_RESET);
- if (ret) {
- dev_err(cs40l26->dev, "Failed to stop playback\n");
- goto mutex_exit;
- }
- } else {
- ret = cl_dsp_get_reg(cs40l26->dsp, "END_PLAYBACK",
- CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, &reg);
- if (ret)
- goto mutex_exit;
+ if (cs40l26->vibe_state != CS40L26_VIBE_STATE_HAPTIC)
+ goto mutex_exit;
- ret = regmap_write(cs40l26->regmap, reg, 1);
- if (ret) {
- dev_err(cs40l26->dev, "Failed to end VIBE playback\n");
- goto mutex_exit;
- }
-
- ret = regmap_write(cs40l26->regmap, reg, 0);
- if (ret) {
- dev_err(cs40l26->dev, "Failed to reset END_PLAYBACK\n");
- goto mutex_exit;
- }
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_STOP_PLAYBACK, CS40L26_DSP_MBOX_RESET);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to stop playback\n");
+ goto mutex_exit;
}
cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_STOPPED);
@@ -1745,16 +1716,6 @@ mutex_exit:
pm_runtime_put_autosuspend(cs40l26->dev);
}
-static enum hrtimer_restart cs40l26_vibe_timer(struct hrtimer *timer)
-{
- struct cs40l26_private *cs40l26 =
- container_of(timer, struct cs40l26_private, vibe_timer);
-
- queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work);
-
- return HRTIMER_NORESTART;
-}
-
static void cs40l26_set_gain(struct input_dev *dev, u16 gain)
{
struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
@@ -1786,12 +1747,10 @@ static int cs40l26_playback_effect(struct input_dev *dev,
cs40l26->effect = effect;
- if (val > 0) {
+ if (val > 0)
queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_start_work);
- } else {
- hrtimer_cancel(&cs40l26->vibe_timer);
+ else
queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work);
- }
return 0;
}
@@ -2321,9 +2280,6 @@ static int cs40l26_input_init(struct cs40l26_private *cs40l26)
return ret;
}
- hrtimer_init(&cs40l26->vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
- cs40l26->vibe_timer.function = cs40l26_vibe_timer;
-
#if !IS_ENABLED(CONFIG_INPUT_CS40L26_ATTR_UNDER_BUS)
ret = sysfs_create_group(&cs40l26->input->dev.kobj,
&cs40l26_dev_attr_group);
@@ -2394,7 +2350,7 @@ static int cs40l26_part_num_resolve(struct cs40l26_private *cs40l26)
static int cs40l26_cl_dsp_init(struct cs40l26_private *cs40l26, u32 id)
{
- int ret = 0;
+ int ret = 0, i;
if (cs40l26->dsp) {
ret = cl_dsp_destroy(cs40l26->dsp);
@@ -2424,10 +2380,22 @@ static int cs40l26_cl_dsp_init(struct cs40l26_private *cs40l26, u32 id)
if (id == CS40L26_FW_CALIB_ID)
cs40l26->fw.min_rev = CS40L26_FW_CALIB_MIN_REV;
- cs40l26->fw.num_coeff_files = 3;
- cs40l26->fw.coeff_files[0] = CS40L26_WT_FILE_NAME;
- cs40l26->fw.coeff_files[1] = CS40L26_A2H_TUNING_FILE_NAME;
- cs40l26->fw.coeff_files[2] = CS40L26_SVC_TUNING_FILE_NAME;
+ cs40l26->fw.num_coeff_files = CS40L26_TUNING_FILES_MAX;
+ cs40l26->fw.coeff_files = devm_kcalloc(cs40l26->dev,
+ CS40L26_TUNING_FILES_MAX, sizeof(char *), GFP_KERNEL);
+
+ for (i = 0; i < CS40L26_TUNING_FILES_MAX; i++)
+ cs40l26->fw.coeff_files[i] = devm_kzalloc(cs40l26->dev,
+ CS40L26_TUNING_FILE_NAME_MAX_LEN, GFP_KERNEL);
+
+ strncpy(cs40l26->fw.coeff_files[0], CS40L26_WT_FILE_NAME,
+ CS40L26_WT_FILE_NAME_LEN);
+ strncpy(cs40l26->fw.coeff_files[1],
+ CS40L26_A2H_TUNING_FILE_NAME,
+ CS40L26_A2H_TUNING_FILE_NAME_LEN);
+ strncpy(cs40l26->fw.coeff_files[2],
+ CS40L26_SVC_TUNING_FILE_NAME,
+ CS40L26_SVC_TUNING_FILE_NAME_LEN);
ret = cl_dsp_wavetable_create(cs40l26->dsp,
CS40L26_VIBEGEN_ALGO_ID, CS40L26_WT_NAME_XM,
@@ -3017,10 +2985,13 @@ pm_err:
return ret;
}
-static int cs40l26_svc_le_select(struct cs40l26_private *cs40l26)
+static int cs40l26_tuning_select_from_svc_le(struct cs40l26_private *cs40l26)
{
- unsigned int reg, le_est = 0;
- int ret, i;
+ unsigned int reg, le = 0;
+ char svc_bin_file[CS40L26_TUNING_FILE_NAME_MAX_LEN];
+ char wt_bin_file[CS40L26_TUNING_FILE_NAME_MAX_LEN];
+ char n_str[2];
+ int ret, i, j;
pm_runtime_get_sync(cs40l26->dev);
@@ -3034,36 +3005,57 @@ static int cs40l26_svc_le_select(struct cs40l26_private *cs40l26)
if (ret)
goto pm_err;
- for (i = 0; i < 2; i++) {
+ for (i = 0; i < CS40L26_SVC_LE_MAX_ATTEMPTS; i++) {
usleep_range(5000, 5100);
- ret = regmap_read(cs40l26->regmap, reg, &le_est);
+ ret = regmap_read(cs40l26->regmap, reg, &le);
if (ret) {
dev_err(cs40l26->dev, "Failed to get LE_EST_STATUS\n");
goto pm_err;
}
- if (le_est >= cs40l26->svc_le->le1_min &&
- le_est <= cs40l26->svc_le->le1_max) {
- cs40l26->fw.coeff_files[2] =
- CS40L26_SVC_TUNING_FILE_NAME1;
- break;
- } else if (le_est >= cs40l26->svc_le->le2_min &&
- le_est <= cs40l26->svc_le->le2_max) {
- cs40l26->fw.coeff_files[2] =
- CS40L26_SVC_TUNING_FILE_NAME2;
- break;
+ for (j = 0; j < cs40l26->num_svc_le_vals; j++) {
+ if (le >= cs40l26->svc_le_vals[j]->min &&
+ le <= cs40l26->svc_le_vals[j]->max) {
+ strncpy(svc_bin_file,
+ CS40L26_SVC_TUNING_FILE_PREFIX,
+ CS40L26_SVC_TUNING_FILE_PREFIX_LEN);
+
+ strncpy(wt_bin_file, CS40L26_WT_FILE_PREFIX,
+ CS40L26_WT_FILE_PREFIX_LEN);
+
+ snprintf(n_str, 2, "%d",
+ cs40l26->svc_le_vals[j]->n);
+
+ strncat(svc_bin_file, n_str, 2);
+ strncat(wt_bin_file, n_str, 2);
+
+ break;
+ }
}
+ if (j < cs40l26->num_svc_le_vals)
+ break;
}
if (i == 2) {
- dev_warn(cs40l26->dev, "Falling back to default SVC tuning\n");
- cs40l26->fw.coeff_files[2] = CS40L26_SVC_TUNING_FILE_NAME;
+ dev_warn(cs40l26->dev, "Using default tunings\n");
+ } else {
+ strncat(svc_bin_file, CS40L26_TUNING_FILE_SUFFIX,
+ CS40L26_TUNING_FILE_SUFFIX_LEN);
+ strncat(wt_bin_file, CS40L26_TUNING_FILE_SUFFIX,
+ CS40L26_TUNING_FILE_SUFFIX_LEN);
+
+ strncpy(cs40l26->fw.coeff_files[0], wt_bin_file,
+ CS40L26_WT_FILE_CONCAT_NAME_LEN);
+ strncpy(cs40l26->fw.coeff_files[2], svc_bin_file,
+ CS40L26_SVC_TUNING_FILE_NAME_LEN);
}
pm_err:
pm_runtime_mark_last_busy(cs40l26->dev);
pm_runtime_put_autosuspend(cs40l26->dev);
+ kfree(cs40l26->svc_le_vals);
+
return ret;
}
@@ -3158,12 +3150,80 @@ static int cs40l26_update_reg_defaults(struct cs40l26_private *cs40l26)
return ret;
}
+static int cs40l26_handle_svc_le_nodes(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int i, init_count, node_count = 0;
+ struct fwnode_handle *child;
+ unsigned int min, max, index;
+ const char *node_name;
+
+ init_count = device_get_child_node_count(dev);
+ if (!init_count)
+ return 0;
+
+ cs40l26->svc_le_vals = kcalloc(init_count,
+ sizeof(struct cs40l26_svc_le *), GFP_KERNEL);
+
+ device_for_each_child_node(dev, child) {
+ node_name = fwnode_get_name(child);
+
+ if (strncmp(node_name, CS40L26_SVC_DT_PREFIX, 6))
+ continue;
+
+ if (fwnode_property_read_u32(child, "cirrus,min", &min)) {
+ dev_err(dev, "No minimum value for SVC LE node\n");
+ continue;
+ }
+
+ if (fwnode_property_read_u32(child, "cirrus,max", &max)) {
+ dev_err(dev, "No maximum value for SVC LE node\n");
+ continue;
+ }
+
+ if (max <= min) {
+ dev_err(dev, "Max <= Min, SVC LE node malformed\n");
+ continue;
+ }
+
+ if (fwnode_property_read_u32(child, "cirrus,index", &index)) {
+ dev_err(dev, "No index specified for SVC LE node\n");
+ continue;
+ }
+
+ for (i = 0; i < node_count; i++) {
+ if (index == cs40l26->svc_le_vals[i]->n)
+ break;
+ }
+
+ if (i < node_count) {
+ dev_err(dev, "SVC LE nodes must have unique index\n");
+ return -EINVAL;
+ }
+
+ cs40l26->svc_le_vals[node_count] =
+ kzalloc(sizeof(struct cs40l26_svc_le), GFP_KERNEL);
+
+ cs40l26->svc_le_vals[node_count]->min = min;
+ cs40l26->svc_le_vals[node_count]->max = max;
+ cs40l26->svc_le_vals[node_count]->n = index;
+ node_count++;
+ }
+
+ if (node_count != init_count)
+ dev_warn(dev, "%d platform nodes unused for SVC LE\n",
+ init_count - node_count);
+
+ return node_count;
+}
+
static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
{
struct device *dev = cs40l26->dev;
struct device_node *np = dev->of_node;
const char *str = NULL;
- u32 val, tmp;
+ int ret;
+ u32 val;
if (!np) {
dev_err(dev, "No platform data found\n");
@@ -3246,29 +3306,11 @@ static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
else
cs40l26->pdata.bst_ipk = 0;
- if (of_property_count_u32_elems(np, "cirrus,svc-le1") == 2 &&
- of_property_count_u32_elems(np, "cirrus,svc-le2") == 2) {
- of_property_read_u32_index(np, "cirrus,svc-le1", 0, &tmp);
- of_property_read_u32_index(np, "cirrus,svc-le1", 1, &val);
-
- cs40l26->svc_le = devm_kzalloc(dev,
- sizeof(struct cs40l26_svc_le), GFP_KERNEL);
- if (!cs40l26->svc_le)
- return -ENOMEM;
-
- cs40l26->svc_le->le1_min = min(tmp, val);
- cs40l26->svc_le->le1_max = max(tmp, val);
-
- of_property_read_u32_index(np, "cirrus,svc-le2", 0, &tmp);
- of_property_read_u32_index(np, "cirrus,svc-le2", 1, &val);
-
- cs40l26->svc_le->le2_min = min(tmp, val);
- cs40l26->svc_le->le2_max = max(tmp, val);
-
- cs40l26->pdata.svc_le_is_valid = true;
- } else {
- cs40l26->pdata.svc_le_is_valid = false;
- }
+ ret = cs40l26_handle_svc_le_nodes(cs40l26);
+ if (ret < 0)
+ cs40l26->num_svc_le_vals = 0;
+ else
+ cs40l26->num_svc_le_vals = ret;
return 0;
}
@@ -3383,14 +3425,14 @@ int cs40l26_probe(struct cs40l26_private *cs40l26,
if (ret)
goto irq_err;
- if (cs40l26->pdata.svc_le_is_valid) {
+ if (cs40l26->num_svc_le_vals) {
ret = cs40l26_dsp_config(cs40l26);
if (ret)
goto irq_err;
enable_irq(cs40l26->irq);
- ret = cs40l26_svc_le_select(cs40l26);
+ ret = cs40l26_tuning_select_from_svc_le(cs40l26);
if (ret)
goto err;
@@ -3466,9 +3508,6 @@ int cs40l26_remove(struct cs40l26_private *cs40l26)
gpiod_set_value_cansleep(cs40l26->reset_gpio, 0);
- if (cs40l26->vibe_timer.function)
- hrtimer_cancel(&cs40l26->vibe_timer);
-
if (cs40l26->vibe_init_success) {
#if !IS_ENABLED(CONFIG_INPUT_CS40L26_ATTR_UNDER_BUS)
sysfs_remove_group(&cs40l26->input->dev.kobj,
diff --git a/cs40l26/cs40l26.h b/cs40l26/cs40l26.h
index 856b531..291d458 100644
--- a/cs40l26/cs40l26.h
+++ b/cs40l26/cs40l26.h
@@ -785,16 +785,30 @@
#define CS40L26_FW_FILE_NAME "cs40l26.wmfw"
#define CS40L26_FW_CALIB_NAME "cs40l26-calib.wmfw"
-#define CS40L26_WT_FILE_NAME "cs40l26.bin"
-#define CS40L26_SVC_TUNING_FILE_NAME "cs40l26-svc.bin"
-#define CS40L26_SVC_TUNING_FILE_NAME1 "cs40l26-svc1.bin"
-#define CS40L26_SVC_TUNING_FILE_NAME2 "cs40l26-svc2.bin"
-#define CS40L26_A2H_TUNING_FILE_NAME "cs40l26-a2h.bin"
+#define CS40L26_TUNING_FILES_MAX 3
+
+#define CS40L26_WT_FILE_NAME "cs40l26.bin"
+#define CS40L26_WT_FILE_NAME_LEN 12
+#define CS40L26_WT_FILE_PREFIX "cs40l26-wt"
+#define CS40L26_WT_FILE_PREFIX_LEN 11
+#define CS40L26_WT_FILE_CONCAT_NAME_LEN 16
+#define CS40L26_SVC_TUNING_FILE_PREFIX "cs40l26-svc"
+#define CS40L26_SVC_TUNING_FILE_PREFIX_LEN 12
+#define CS40L26_SVC_TUNING_FILE_NAME "cs40l26-svc.bin"
+#define CS40L26_SVC_TUNING_FILE_NAME_LEN 16
+#define CS40L26_A2H_TUNING_FILE_NAME "cs40l26-a2h.bin"
+#define CS40L26_A2H_TUNING_FILE_NAME_LEN 16
+#define CS40L26_TUNING_FILE_NAME_MAX_LEN 20
+#define CS40L26_TUNING_FILE_SUFFIX ".bin"
+#define CS40L26_TUNING_FILE_SUFFIX_LEN 4
+
+#define CS40L26_SVC_LE_MAX_ATTEMPTS 2
+#define CS40L26_SVC_DT_PREFIX "svc-le"
#define CS40L26_FW_ID 0x1800D4
#define CS40L26_FW_ROM_MIN_REV 0x040000
#define CS40L26_FW_A0_RAM_MIN_REV 0x050004
-#define CS40L26_FW_A1_RAM_MIN_REV 0x070201
+#define CS40L26_FW_A1_RAM_MIN_REV 0x07020A
#define CS40L26_FW_CALIB_ID 0x1800DA
#define CS40L26_FW_CALIB_MIN_REV 0x010000
@@ -879,6 +893,9 @@
#define CS40L26_IRQ_UNMASK 0
#define CS40L26_IRQ_MASK 1
+#define CS40L26_IRQ_EINT1_ALL_MASK 0xFFDC7FFF
+#define CS40L26_IRQ_EINT2_ALL_MASK 0x07DE0400
+
/* temp monitoring */
#define CS40L26_TEMPMON_EN_MASK BIT(10)
#define CS40L26_TEMPMON_EN_SHIFT 10
@@ -895,6 +912,9 @@
#define CS40L26_BST_IPK_DEFAULT 0x4A
+#define CS40L26_BOOST_DISABLE_DELAY_MIN 0
+#define CS40L26_BOOST_DISABLE_DELAY_MAX 8388608
+
/* brownout prevention */
#define CS40L26_VXBR_DEFAULT 0xFFFFFFFF
@@ -1054,11 +1074,6 @@
#define CS40L26_ASP_RX2_SLOT_MASK GENMASK(13, 8)
#define CS40L26_ASP_RX2_SLOT_SHIFT 8
-#define CS40L26_CLASS_H_EN_MASK BIT(4)
-#define CS40L26_CLASS_H_EN_SHIFT 4
-
-#define CS40L26_BST_CTL_SEL_MASK GENMASK(1, 0)
-
#define CS40L26_A2H_MAX_TUNINGS 5
#define CS40L26_A2H_VOLUME_MAX 0x7FFFFF
@@ -1089,6 +1104,10 @@
#define CS40L26_WT_TYPE10_WAVELEN_CALCULATED 0x800000
#define CS40L26_WT_TYPE10_COMP_DURATION_FLAG 0x8
+/* F0 Offset represented as Q10.14 format */
+#define CS40L26_F0_OFFSET_MAX 0x190000 /* +100 Hz */
+#define CS40L26_F0_OFFSET_MIN 0xE70000 /* -100 Hz */
+
/* Calibration */
#define CS40L26_F0_EST_MIN 0xC8000
#define CS40L26_F0_EST_MAX 0x7FC000
@@ -1235,7 +1254,7 @@ struct cs40l26_fw {
unsigned int id;
unsigned int min_rev;
unsigned int num_coeff_files;
- const char *coeff_files[3];
+ char **coeff_files;
};
struct cs40l26_owt_section {
@@ -1260,10 +1279,9 @@ struct cs40l26_pseq_op {
};
struct cs40l26_svc_le {
- u32 le1_min;
- u32 le1_max;
- u32 le2_min;
- u32 le2_max;
+ u32 min;
+ u32 max;
+ u32 n;
};
struct cs40l26_platform_data {
@@ -1284,7 +1302,6 @@ struct cs40l26_platform_data {
u32 vpbr_rel_rate;
bool bst_dcm_en;
u32 bst_ipk;
- u32 svc_le_is_valid;
};
struct cs40l26_owt {
@@ -1306,7 +1323,6 @@ struct cs40l26_private {
struct cl_dsp *dsp;
unsigned int trigger_indices[FF_MAX_EFFECTS];
struct ff_effect *effect;
- struct hrtimer vibe_timer;
struct work_struct vibe_start_work;
struct work_struct vibe_stop_work;
struct work_struct set_gain_work;
@@ -1334,7 +1350,8 @@ struct cs40l26_private {
int cal_requested;
u16 gain_pct;
u32 event_map_base;
- struct cs40l26_svc_le *svc_le;
+ struct cs40l26_svc_le **svc_le_vals;
+ int num_svc_le_vals;
struct workqueue_struct *asp_workqueue;
struct work_struct asp_work;
};
@@ -1367,7 +1384,6 @@ void cs40l26_asp_worker(struct work_struct *work);
int cs40l26_fw_swap(struct cs40l26_private *cs40l26, u32 id);
void cs40l26_vibe_state_set(struct cs40l26_private *cs40l26,
enum cs40l26_vibe_state);
-int cs40l26_class_h_set(struct cs40l26_private *cs40l26, bool class_h);
int cs40l26_pm_timeout_ms_get(struct cs40l26_private *cs40l26,
u32 *timeout_ms);
int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26,