summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTai Kuo <taikuo@google.com>2023-01-19 15:44:35 +0800
committerTai Kuo <taikuo@google.com>2023-02-17 02:32:58 +0000
commit8c576c450604b70c7b1170803b039ed0b172b1a4 (patch)
tree13f3091cc92a03d29a9545339a99bcf6bfdf40e5
parentc3163996fedeb44a64f865a23a150b5cdaeb8a5a (diff)
downloadamplifiers-8c576c450604b70c7b1170803b039ed0b172b1a4.tar.gz
cs40l26: merge cs40l26 v5.7.1 and dsp v3.2.0 for firmware RAM19
Merge Cirrus kernel driver (tag cs40l26 v5.7.1 and dsp v3.2.0) for firmware RAM19 (v7.2.49 (runtime) & v1.1.35 (calib)) Branches: v6.1-cirrus-dsp-fw and v5.15-cs40l26 Tags: cl-dsp-fw-v3.2.0_6.1, cs40l26-v5.7.1_5.15 Files: Documentation/devicetree/bindings/input/cs40l26.yaml (New) drivers/firmware/cirrus/cl_dsp-debugfs.c (New) drivers/firmware/cirrus/cl_dsp.c include/linux/firmware/cirrus/cl_dsp.h drivers/input/misc/cs40l26-debugfs.c (New) 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 (No changes) ... Tag: cs40l26-v5.7.1_5.15: CS40L26 v5.7.1 Features: - Add device tree parameter for skipping normal delay before stopping playback - Add Debugfs controls for reading FW controls - Add support for debug logger - Add support for DVL coefficient calibration Bug fixes: - Remove unused functions - Fix potential memory leak - Remove terminator write from owt setup Commits: 6ebc38890aba input: cs40l26: Use linked list to track uploaded effects a26249ad215e input: cs40l26: Add dvl_peq_coefficients control 4b3e947f0b4b input: cs40l26: Add support for DVL PEQ cal request 09e8c62468df input: cs40l26: Load cs40l26-dvl.bin for both runtime and cal FW e0176fefb2ba input: cs40l26: Use completions for calibration requests fcec4c470704 input: cs40l26: Add support for cl_dsp debug logger ae8df81dcd84 input: cs40l26: Add Debugfs to read FW controls 52400ada10c5 Documentation: cs40l26: Skip delay for reserved indices e4a7db96e597 input: cs40l26: Skip delay for reserved indices 350990b074eb input: cs40l26: Remove terminator write from owt setup 3dd46703b73c input: cs40l26: Only load tuning if algorithm is present 7e6977e678ae input: cs40l26: Resolve memory leak b98183034193 input: cs40l26: Remove unused functions ... Tag: cl-dsp-fw-v3.2.0_6.1: Cirrus DSP Firmware Driver v3.2.0 New Features: - Support Debugfs Event Logger Improvements: - Use kvmalloc for large data handling - Mask MSB when looking for algorithm ID Commits: 6ebc38890aba input: cs40l26: Use linked list to track uploaded effects 7f373ff54162 firmware: cirrus: Add support for Debugfs and debug logging e7b3c3b3f852 firmware: cirrus: Use kvmalloc() for large memory allocation 00ca52ae9448 firmware: cirrus: Mask bits[31:16] when comparing algorithm ids 021d5954c875 firmware: cirrus: add function to check for algo presence Bug: 261694194 Test: Copy texts and adjust alarm Test: NFC, dumpstate, keyboard vibration Test: idlcli commands Test: Switch firmware continuous Test: Switch firmware and check the first tick effect Test: atest PtsVibratorHalTestSuite \ PtsHapticsTestCases \ VibratorHalCs40l26TestSuite \ VtsHalVibratorManagerTargetTest \ VtsHalVibratorTargetTest \ android.os.cts.VibratorTest \ android.os.cts.VibratorManagerTest \ android.os.cts.VibrationEffectTest \ android.os.cts.VibrationAttributesTest \ android.os.cts.CombinedVibrationTest Change-Id: I10e31d93b0248b32858e0aa07d4b40e20e66b38a Signed-off-by: Tai Kuo <taikuo@google.com>
-rw-r--r--cs40l26/Makefile6
-rw-r--r--cs40l26/cl_dsp-debugfs.c526
-rw-r--r--cs40l26/cl_dsp.c29
-rw-r--r--cs40l26/cl_dsp.h70
-rw-r--r--cs40l26/cs40l26-debugfs.c251
-rw-r--r--cs40l26/cs40l26-sysfs.c143
-rw-r--r--cs40l26/cs40l26.c813
-rw-r--r--cs40l26/cs40l26.h67
-rw-r--r--cs40l26/cs40l26.yaml503
9 files changed, 1991 insertions, 417 deletions
diff --git a/cs40l26/Makefile b/cs40l26/Makefile
index 3c4ceaf..979b83a 100644
--- a/cs40l26/Makefile
+++ b/cs40l26/Makefile
@@ -3,8 +3,10 @@
# Makefile for Cirrus Logic haptic driver.
#
-input-cs40l26-i2c-objs := cs40l26.o cs40l26-tables.o cs40l26-sysfs.o cs40l26-i2c.o
-input-cs40l26-spi-objs := cs40l26.o cs40l26-tables.o cs40l26-sysfs.o cs40l26-spi.o
+input-cs40l26-i2c-objs := cs40l26.o cs40l26-tables.o cs40l26-sysfs.o \
+ cs40l26-i2c.o cs40l26-debugfs.o cl_dsp-debugfs.o
+input-cs40l26-spi-objs := cs40l26.o cs40l26-tables.o cs40l26-sysfs.o \
+ cs40l26-spi.o cs40l26-debugfs.o cl_dsp-debugfs.o
snd-soc-cs40l26-objs := cs40l26-codec.o
obj-$(CONFIG_INPUT_CS40L26_I2C) += input-cs40l26-i2c.o
diff --git a/cs40l26/cl_dsp-debugfs.c b/cs40l26/cl_dsp-debugfs.c
new file mode 100644
index 0000000..94aafe0
--- /dev/null
+++ b/cs40l26/cl_dsp-debugfs.c
@@ -0,0 +1,526 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// cl_dsp.c -- DSP Control for non-ALSA Cirrus Logic Devices
+//
+// Copyright 2021 Cirrus Logic, Inc.
+//
+// Author: Fred Treven <fred.treven@cirrus.com>
+
+#include "cl_dsp.h"
+
+#ifdef CONFIG_DEBUG_FS
+
+static inline u32 host_buffer_field_reg(struct cl_dsp_logger *dl,
+ unsigned long offset)
+{
+ return (u32)(CL_DSP_HALO_XMEM_UNPACKED24_BASE +
+ ((dl->host_buf_ptr + offset) * CL_DSP_BYTES_PER_WORD));
+}
+
+static inline u32 host_buffer_data_reg(struct cl_dsp_logger *dl, int offset)
+{
+ return (u32)(CL_DSP_HALO_XMEM_UNPACKED24_BASE +
+ ((dl->host_buf_base + offset) * CL_DSP_BYTES_PER_WORD));
+}
+
+static int cl_dsp_host_buffer_field_read(struct cl_dsp_debugfs *db,
+ unsigned long field_offset, u32 *data)
+{
+ struct regmap *regmap = db->core->regmap;
+ __be32 raw;
+ u32 reg;
+ int ret;
+
+ reg = host_buffer_field_reg(&db->dl, field_offset);
+
+ ret = regmap_raw_read(regmap, reg, &raw, sizeof(raw));
+ if (ret) {
+ dev_err(db->core->dev, "Failed to get raw host buffer data\n");
+ return ret;
+ }
+
+ *data = CL_DSP_HOST_BUFFER_DATA_MASK & be32_to_cpu(raw);
+ return 0;
+}
+
+static int cl_dsp_host_buffer_field_write(struct cl_dsp_debugfs *db,
+ unsigned long field_offset, u32 data)
+{
+ struct regmap *regmap = db->core->regmap;
+ struct device *dev = db->core->dev;
+ int ret;
+ u32 reg;
+
+ reg = host_buffer_field_reg(&db->dl, field_offset);
+
+ ret = regmap_write(regmap, reg, data);
+ if (ret)
+ dev_err(dev, "Failed to set host buffer data: %d\n", ret);
+
+ return ret;
+}
+
+static ssize_t cl_dsp_debugfs_logger_en_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cl_dsp_debugfs *db = file->private_data;
+ struct regmap *regmap = db->core->regmap;
+ char str[CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE];
+ u32 reg, val;
+ ssize_t ret;
+
+ ret = cl_dsp_get_reg(db->core, "ENABLED", CL_DSP_XM_UNPACKED_TYPE,
+ db->dl.algo_id, &reg);
+ if (ret)
+ return ret;
+
+ ret = pm_runtime_get_sync(db->core->dev);
+ if (ret < 0) {
+ dev_err(db->core->dev, "PM Runtime Resume Failed\n");
+ return ret;
+ }
+
+ ret = regmap_read(regmap, reg, &val);
+ if (ret) {
+ dev_err(db->core->dev, "Failed to get host buffer status\n");
+ goto pm_exit;
+ }
+
+ ret = snprintf(str, CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE, "%d\n", val);
+ if (ret <= 0) {
+ dev_err(db->core->dev, "Failed to parse host buffer status\n");
+ goto pm_exit;
+ }
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, str, strlen(str));
+
+pm_exit:
+ pm_runtime_mark_last_busy(db->core->dev);
+ pm_runtime_put_autosuspend(db->core->dev);
+
+ return ret;
+}
+
+static ssize_t cl_dsp_debugfs_logger_en_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cl_dsp_debugfs *db = file->private_data;
+ struct regmap *regmap = db->core->regmap;
+ struct device *dev = db->core->dev;
+ u32 reg, val;
+ ssize_t ret;
+ char *str;
+
+ str = kzalloc(count, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ ret = simple_write_to_buffer(str, count, ppos, user_buf, count);
+ if (ret <= 0) {
+ dev_err(dev, "Failed to write debugfs data\n");
+ goto exit_free;
+ }
+
+ ret = kstrtou32(str, 10, &val);
+ if (ret)
+ goto exit_free;
+
+ if (val != CL_DSP_DEBUGFS_TRACE_LOG_DISABLE &&
+ val != CL_DSP_DEBUGFS_TRACE_LOG_ENABLE) {
+ dev_err(dev, "Invalid trace log write: %u\n", val);
+ ret = -EINVAL;
+ goto exit_free;
+ }
+
+ ret = cl_dsp_get_reg(db->core, "ENABLED", CL_DSP_XM_UNPACKED_TYPE,
+ db->dl.algo_id, &reg);
+ if (ret)
+ goto exit_free;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ dev_err(db->core->dev, "PM Runtime Resume Failed\n");
+ goto exit_free;
+ }
+
+ ret = regmap_write(regmap, reg, val);
+ if (ret) {
+ dev_err(dev, "Failed to set trace log status\n");
+ goto exit_pm;
+ }
+
+ if (val == CL_DSP_DEBUGFS_TRACE_LOG_DISABLE) {
+ /* Set next_read_index to -1 to reset logger */
+ ret = cl_dsp_host_buffer_field_write(db,
+ HOST_BUFFER_FIELD(next_read_index),
+ CL_DSP_HOST_BUFFER_READ_INDEX_RESET);
+ if (ret) {
+ dev_err(dev, "Failed to reset event logger\n");
+ goto exit_pm;
+ }
+
+ db->dl.buf_data_size = 0;
+ kfree(db->dl.buf_data);
+ }
+
+exit_pm:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+exit_free:
+ kfree(str);
+
+ return ret ? ret : count;
+}
+
+static ssize_t cl_dsp_debugfs_timestamp_shift_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cl_dsp_debugfs *db = file->private_data;
+ struct regmap *regmap = db->core->regmap;
+ char str[CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE];
+ u32 reg, val;
+ ssize_t ret;
+
+ ret = cl_dsp_get_reg(db->core, "TIMESTAMP_SHIFT",
+ CL_DSP_XM_UNPACKED_TYPE, db->dl.algo_id, &reg);
+ if (ret)
+ return ret;
+
+ ret = pm_runtime_get_sync(db->core->dev);
+ if (ret < 0) {
+ dev_err(db->core->dev, "PM Runtime Resume Failed\n");
+ return ret;
+ }
+
+ ret = regmap_read(regmap, reg, &val);
+ if (ret) {
+ dev_err(db->core->dev, "Failed to get timestamp shift\n");
+ goto pm_exit;
+ }
+
+ ret = snprintf(str, CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE, "%d\n", val);
+ if (ret <= 0) {
+ dev_err(db->core->dev, "Failed to parse host buffer status\n");
+ goto pm_exit;
+ }
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, str, strlen(str));
+
+pm_exit:
+ pm_runtime_mark_last_busy(db->core->dev);
+ pm_runtime_put_autosuspend(db->core->dev);
+
+ return ret;
+}
+
+static int cl_dsp_host_buffer_data_read(struct cl_dsp_debugfs *db,
+ u32 read_index, u32 num_words)
+{
+ u32 start_reg, offset = db->dl.buf_data_size;
+ struct regmap *regmap = db->core->regmap;
+ struct device *dev = db->core->dev;
+ int ret;
+
+ start_reg = host_buffer_data_reg(&db->dl, (unsigned long)read_index);
+
+ db->dl.buf_data_size += num_words;
+ db->dl.buf_data = krealloc(db->dl.buf_data, db->dl.buf_data_size * 4,
+ GFP_KERNEL);
+ if (!db->dl.buf_data || IS_ERR(db->dl.buf_data)) {
+ dev_err(dev, "Failed to allocate buffer data space\n");
+ return -ENOMEM;
+ }
+
+ ret = regmap_bulk_read(regmap, start_reg, db->dl.buf_data + offset,
+ num_words);
+ if (ret)
+ dev_err(dev, "Failed to get host buffer data\n");
+
+ return ret;
+}
+
+int cl_dsp_logger_update(struct cl_dsp_debugfs *db)
+{
+ struct cl_dsp_logger *dl = &db->dl;
+ struct device *dev = db->core->dev;
+ u32 n_read_index, n_write_index, num_words;
+ u32 nirq, irq, error_code;
+ int ret;
+
+ /* Check if interrupt was asserted due to an error */
+ ret = cl_dsp_host_buffer_field_read(db, HOST_BUFFER_FIELD(error),
+ &error_code);
+ if (ret)
+ return ret;
+
+ if (error_code) {
+ if (error_code != CL_DSP_HOST_BUFFER_ERROR_OVERFLOW) {
+ dev_err(dev, "Fatal Host Buffer Error with code 0x%X\n",
+ error_code);
+ return -ENOTRECOVERABLE;
+ }
+ dev_warn(dev, "Data lost from Host Buffer Overflow\n");
+ }
+
+ /* Check if next read index is != -1 in order to continue */
+ ret = cl_dsp_host_buffer_field_read(db,
+ HOST_BUFFER_FIELD(next_read_index), &n_read_index);
+ if (ret)
+ return ret;
+
+ if (n_read_index == CL_DSP_HOST_BUFFER_READ_INDEX_RESET) {
+ dev_err(dev, "Host Buffer Not Initialized\n");
+ return -EPERM;
+ }
+
+ ret = cl_dsp_host_buffer_field_read(db, HOST_BUFFER_FIELD(irq_count),
+ &nirq);
+ if (ret)
+ return ret;
+
+ ret = cl_dsp_host_buffer_field_read(db, HOST_BUFFER_FIELD(irq_ack),
+ &irq);
+ if (ret)
+ return ret;
+
+ ret = cl_dsp_host_buffer_field_read(
+ db, HOST_BUFFER_FIELD(next_write_index), &n_write_index);
+ if (ret)
+ return ret;
+
+ if (n_write_index < n_read_index)
+ num_words = (n_write_index + dl->host_buf_size_words) -
+ n_read_index;
+ else
+ num_words = n_write_index - n_read_index;
+
+ /* Get all messages in buffer */
+ ret = cl_dsp_host_buffer_data_read(db, n_read_index, num_words);
+ if (ret)
+ return ret;
+
+ /* Set next_read_index to next_write_index */
+ ret = cl_dsp_host_buffer_field_write(db,
+ HOST_BUFFER_FIELD(next_read_index), n_write_index - 1);
+ if (ret)
+ return ret;
+
+ /* Reset irq_ack by writing irq_count | 0x1 */
+ ret = cl_dsp_host_buffer_field_write(db, HOST_BUFFER_FIELD(irq_ack),
+ nirq | CL_DSP_HOST_BUFFER_IRQ_MASK);
+ if (ret)
+ return ret;
+
+ ret = cl_dsp_host_buffer_field_read(db,
+ HOST_BUFFER_FIELD(irq_ack), &irq);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL(cl_dsp_logger_update);
+
+static int cl_dsp_debugfs_logger_open(struct inode *inode, struct file *file)
+{
+ struct cl_dsp_debugfs *db;
+ int ret;
+
+ ret = simple_open(inode, file);
+ if (ret)
+ return ret;
+
+ db = file->private_data;
+
+ return 0;
+}
+
+static ssize_t cl_dsp_debugfs_logger_read(struct file *file,
+ char __user *user_buf, size_t count,
+ loff_t *ppos)
+{
+ struct cl_dsp_debugfs *db = file->private_data;
+ struct cl_dsp_logger *dl = &db->dl;
+ struct device *dev = db->core->dev;
+ ssize_t ret, buf_str_size;
+ char *str, *buf_str;
+ int i;
+
+ if (dl->buf_data_size == 0)
+ return -ENODATA;
+
+ buf_str_size =
+ CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE * dl->buf_data_size;
+ buf_str = kzalloc(buf_str_size, GFP_KERNEL);
+ if (!buf_str)
+ return -ENOMEM;
+
+ str = kzalloc(CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE, GFP_KERNEL);
+ if (!str) {
+ ret = -ENOMEM;
+ goto err_free2;
+ }
+
+ for (i = 0; i < dl->buf_data_size; i++) {
+ ret = snprintf(str, CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE, "%08X ",
+ dl->buf_data[i]);
+ if (ret <= 0) {
+ dev_err(dev, "Failed to get host buffer data string\n");
+ goto err_free1;
+ }
+
+ strncat(buf_str, str, CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE);
+ }
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf_str,
+ strlen(buf_str));
+
+err_free1:
+ kfree(str);
+err_free2:
+ kfree(buf_str);
+
+ return ret;
+}
+
+static const struct {
+ const char *name;
+ const struct file_operations fops;
+} cl_dsp_debugfs_fops[] = {
+ {
+ .name = "log_data",
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = cl_dsp_debugfs_logger_open,
+ .read = cl_dsp_debugfs_logger_read,
+ },
+ },
+ {
+ .name = "logger_en",
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = cl_dsp_debugfs_logger_en_read,
+ .write = cl_dsp_debugfs_logger_en_write,
+ },
+ },
+ {
+ .name = "timestamp_shift",
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = cl_dsp_debugfs_timestamp_shift_read,
+ }
+ },
+};
+
+static int cl_dsp_logger_init(struct cl_dsp_debugfs *db)
+{
+ struct regmap *regmap = db->core->regmap;
+ struct cl_dsp *dsp = db->core;
+ u32 reg;
+ int ret;
+
+ ret = cl_dsp_get_reg(dsp, "EVENT_LOG_HEADER", CL_DSP_XM_UNPACKED_TYPE,
+ db->dl.algo_id, &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(regmap, reg, &db->dl.host_buf_ptr);
+ if (ret) {
+ dev_err(db->core->dev, "Failed to get host buffer address\n");
+ return ret;
+ }
+
+ ret = cl_dsp_host_buffer_field_read(db, HOST_BUFFER_FIELD(buf1_base),
+ &db->dl.host_buf_base);
+ if (ret)
+ return ret;
+
+ ret = cl_dsp_host_buffer_field_read(db,
+ HOST_BUFFER_FIELD(buf_total_size),
+ &db->dl.host_buf_size_words);
+ if (ret)
+ return ret;
+
+ ret = cl_dsp_host_buffer_field_read(db,
+ HOST_BUFFER_FIELD(high_water_mark),
+ &db->dl.high_watermark);
+
+ return ret;
+}
+struct cl_dsp_debugfs *cl_dsp_debugfs_create(struct cl_dsp *dsp,
+ struct dentry *parent_node,
+ u32 event_log_algo_id)
+{
+ struct cl_dsp_debugfs *db;
+ int ret, i;
+
+ if (IS_ERR(dsp))
+ return ERR_CAST(dsp);
+
+ if (!dsp)
+ return NULL;
+
+ if (IS_ERR(parent_node))
+ return ERR_CAST(parent_node);
+
+ if (!parent_node)
+ return NULL;
+
+ db = kzalloc(sizeof(*db), GFP_KERNEL);
+ if (!db)
+ return ERR_PTR(-ENOMEM);
+
+ db->core = dsp;
+ db->debugfs_root = parent_node ? parent_node : NULL;
+
+ db->debugfs_node = debugfs_create_dir("cl_dsp", db->debugfs_root);
+ if (IS_ERR(db->debugfs_node)) {
+ ret = PTR_ERR(db->debugfs_node);
+ kfree(db);
+ return ERR_PTR(ret);
+ }
+
+ for (i = 0; i < CL_DSP_DEBUGFS_NUM_CONTROLS; i++)
+ debugfs_create_file(cl_dsp_debugfs_fops[i].name,
+ CL_DSP_DEBUGFS_RW_FILE_MODE, db->debugfs_node,
+ db, &cl_dsp_debugfs_fops[i].fops);
+
+ db->dl.algo_id = event_log_algo_id;
+
+ ret = cl_dsp_logger_init(db);
+ if (ret)
+ return ERR_PTR(ret);
+
+ debugfs_create_u32("high_watermark", CL_DSP_DEBUGFS_RO_FILE_MODE,
+ db->debugfs_node, &db->dl.high_watermark);
+
+ return db;
+}
+EXPORT_SYMBOL(cl_dsp_debugfs_create);
+
+void cl_dsp_debugfs_destroy(struct cl_dsp_debugfs *db)
+{
+ int ret;
+
+ if (!db || IS_ERR(db))
+ return;
+
+ /* Set next_read_index to -1 to reset logger */
+ ret = cl_dsp_host_buffer_field_write(db,
+ HOST_BUFFER_FIELD(next_read_index),
+ CL_DSP_HOST_BUFFER_READ_INDEX_RESET);
+ if (ret)
+ dev_err(db->core->dev, "Failed to reset event logger\n");
+
+ debugfs_remove_recursive(db->debugfs_node);
+ kfree(db);
+}
+EXPORT_SYMBOL(cl_dsp_debugfs_destroy);
+
+#endif /* CONFIG_DEBUG_FS */
+
+MODULE_DESCRIPTION("CL DSP Debugfs Driver");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/cs40l26/cl_dsp.c b/cs40l26/cl_dsp.c
index ee6a525..7e32a5b 100644
--- a/cs40l26/cl_dsp.c
+++ b/cs40l26/cl_dsp.c
@@ -213,6 +213,23 @@ int cl_dsp_get_reg(struct cl_dsp *dsp, const char *coeff_name,
}
EXPORT_SYMBOL(cl_dsp_get_reg);
+bool cl_dsp_algo_is_present(struct cl_dsp *dsp, const unsigned int algo_id)
+{
+ int i;
+
+ if (!dsp)
+ return false;
+
+ for (i = 0; i < dsp->num_algos; i++) {
+ if ((GENMASK(15, 0) & dsp->algo_info[i].id) ==
+ (GENMASK(15, 0) & algo_id))
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(cl_dsp_algo_is_present);
+
static int cl_dsp_process_data_be(const u8 *data,
const unsigned int num_bytes, unsigned int *val)
{
@@ -440,7 +457,7 @@ int cl_dsp_coeff_file_parse(struct cl_dsp *dsp, const struct firmware *fw)
pos += CL_DSP_COEFF_DBLK_HEADER_SIZE;
data_len = data_block.header.data_len;
- data_block.payload = kmalloc(data_len, GFP_KERNEL);
+ data_block.payload = kvmalloc(data_len, GFP_KERNEL);
if (!data_block.payload)
return -ENOMEM;
@@ -564,7 +581,7 @@ int cl_dsp_coeff_file_parse(struct cl_dsp *dsp, const struct firmware *fw)
/* Blocks are word-aligned */
pos += (data_len + 3) & ~CL_DSP_ALIGN;
- kfree(data_block.payload);
+ kvfree(data_block.payload);
}
if (wt_found) {
@@ -580,7 +597,7 @@ int cl_dsp_coeff_file_parse(struct cl_dsp *dsp, const struct firmware *fw)
return 0;
err_free:
- kfree(data_block.payload);
+ kvfree(data_block.payload);
return ret;
}
@@ -1016,7 +1033,7 @@ int cl_dsp_firmware_parse(struct cl_dsp *dsp, const struct firmware *fw,
pos += CL_DSP_DBLK_HEADER_SIZE;
data_block.payload =
- kmalloc(data_block.header.data_len, GFP_KERNEL);
+ kvmalloc(data_block.header.data_len, GFP_KERNEL);
memcpy(data_block.payload, &fw->data[pos],
data_block.header.data_len);
@@ -1079,13 +1096,13 @@ int cl_dsp_firmware_parse(struct cl_dsp *dsp, const struct firmware *fw,
/* Blocks are word-aligned */
pos += (data_block.header.data_len + 3) & ~CL_DSP_ALIGN;
- kfree(data_block.payload);
+ kvfree(data_block.payload);
}
return cl_dsp_coeff_init(dsp);
err_free:
- kfree(data_block.payload);
+ kvfree(data_block.payload);
return ret;
}
diff --git a/cs40l26/cl_dsp.h b/cs40l26/cl_dsp.h
index 31169f4..e81f575 100644
--- a/cs40l26/cl_dsp.h
+++ b/cs40l26/cl_dsp.h
@@ -16,6 +16,9 @@
#include <linux/regmap.h>
#include <linux/of_device.h>
#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/debugfs.h>
+#include <linux/pm_runtime.h>
#ifndef __CL_DSP_H__
#define __CL_DSP_H__
@@ -298,6 +301,72 @@ struct cl_dsp {
struct cl_dsp_wt_desc *wt_desc;
};
+#ifdef CONFIG_DEBUG_FS
+/* Debug Logger */
+struct cl_dsp_host_buffer {
+ __be32 buf1_base;
+ __be32 buf1_size;
+ __be32 buf2_base;
+ __be32 buf1_buf2_size;
+ __be32 buf3_base;
+ __be32 buf_total_size;
+ __be32 high_water_mark;
+ __be32 irq_count;
+ __be32 irq_ack;
+ __be32 next_write_index;
+ __be32 next_read_index;
+ __be32 error;
+ __be32 oldest_block_index;
+ __be32 requested_rewind;
+ __be32 reserved_space;
+ __be32 min_free;
+ __be32 blocks_written[2];
+ __be32 words_written[2];
+} __packed;
+
+struct cl_dsp_logger {
+ u32 *buf_data;
+ u32 buf_data_size;
+ u32 algo_id;
+ u32 host_buf_ptr;
+ u32 host_buf_base;
+ int host_buf_size_words;
+ u32 high_watermark;
+};
+
+struct cl_dsp_debugfs {
+ struct cl_dsp *core;
+ struct dentry *debugfs_root;
+ struct dentry *debugfs_node;
+ struct mutex lock;
+ struct cl_dsp_logger dl;
+};
+
+#define CL_DSP_DEBUGFS_NUM_CONTROLS 3
+#define CL_DSP_DEBUGFS_RW_FILE_MODE 0600
+#define CL_DSP_DEBUGFS_RO_FILE_MODE 0400
+#define CL_DSP_DEBUGFS_WO_FILE_MOADE 0200
+#define CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE 3
+#define CL_DSP_DEBUGFS_TRACE_LOG_DISABLE 0
+#define CL_DSP_DEBUGFS_TRACE_LOG_ENABLE 1
+
+#define CL_DSP_HOST_BUFFER_DATA_MASK 0x00FFFFFFu
+#define CL_DSP_HOST_BUFFER_ERROR_OVERFLOW BIT(0)
+#define CL_DSP_HOST_BUFFER_READ_INDEX_RESET 0x00FFFFFF
+#define CL_DSP_HOST_BUFFER_IRQ_MASK BIT(0)
+#define CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE 10
+
+#define HOST_BUFFER_FIELD(field) \
+ (offsetof(struct cl_dsp_host_buffer, field) / sizeof(__be32))
+
+int cl_dsp_logger_update(struct cl_dsp_debugfs *db);
+struct cl_dsp_debugfs *cl_dsp_debugfs_create(struct cl_dsp *dsp,
+ struct dentry *parent_node, u32 event_log_algo_id);
+void cl_dsp_debugfs_destroy(struct cl_dsp_debugfs *db);
+
+#endif /* CONFIG_DEBUG_FS */
+
+/* Exported Functions */
struct cl_dsp *cl_dsp_create(struct device *dev, struct regmap *regmap);
int cl_dsp_destroy(struct cl_dsp *dsp);
int cl_dsp_wavetable_create(struct cl_dsp *dsp, unsigned int id,
@@ -309,6 +378,7 @@ int cl_dsp_coeff_file_parse(struct cl_dsp *dsp, const struct firmware *fw);
int cl_dsp_get_reg(struct cl_dsp *dsp, const char *coeff_name,
const unsigned int block_type, const unsigned int algo_id,
unsigned int *reg);
+bool cl_dsp_algo_is_present(struct cl_dsp *dsp, const unsigned int algo_id);
struct cl_dsp_memchunk cl_dsp_memchunk_create(void *data, int size);
int cl_dsp_memchunk_write(struct cl_dsp_memchunk *ch, int nbits, u32 val);
int cl_dsp_memchunk_read(struct cl_dsp *dsp, struct cl_dsp_memchunk *ch,
diff --git a/cs40l26/cs40l26-debugfs.c b/cs40l26/cs40l26-debugfs.c
new file mode 100644
index 0000000..7a88847
--- /dev/null
+++ b/cs40l26/cs40l26-debugfs.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// cs40l26-debugfs.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and
+// Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+//
+// Copyright 2022 Cirrus Logic, Inc.
+//
+// Author: Fred Treven <fred.treven@cirrus.com>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+
+#include "cs40l26.h"
+
+#ifdef CONFIG_DEBUG_FS
+static ssize_t cs40l26_fw_ctrl_name_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cs40l26_private *cs40l26 = file->private_data;
+ ssize_t ret = 0;
+
+ mutex_lock(&cs40l26->lock);
+
+ if (cs40l26->dbg_fw_ctrl_name)
+ ret = simple_read_from_buffer(user_buf, count, ppos,
+ cs40l26->dbg_fw_ctrl_name,
+ strlen(cs40l26->dbg_fw_ctrl_name));
+
+ mutex_unlock(&cs40l26->lock);
+
+ return ret;
+}
+
+static ssize_t cs40l26_fw_ctrl_name_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cs40l26_private *cs40l26 = file->private_data;
+ ssize_t ret = 0;
+
+ mutex_lock(&cs40l26->lock);
+
+ kfree(cs40l26->dbg_fw_ctrl_name);
+ cs40l26->dbg_fw_ctrl_name = NULL;
+
+ cs40l26->dbg_fw_ctrl_name = kzalloc(count, GFP_KERNEL);
+ if (!cs40l26->dbg_fw_ctrl_name) {
+ ret = -ENOMEM;
+ goto err_mutex;
+ }
+
+ ret = simple_write_to_buffer(cs40l26->dbg_fw_ctrl_name,
+ count, ppos, user_buf, count);
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+
+ return ret ? ret : count;
+}
+
+static ssize_t cs40l26_fw_algo_id_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cs40l26_private *cs40l26 = file->private_data;
+ ssize_t ret;
+ char *str;
+
+ str = kzalloc(CS40L26_ALGO_ID_MAX_STR_LEN, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ mutex_lock(&cs40l26->lock);
+
+ snprintf(str, count, "0x%06X\n", cs40l26->dbg_fw_algo_id);
+
+ mutex_unlock(&cs40l26->lock);
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, str, strlen(str));
+
+ kfree(str);
+
+ return ret;
+}
+
+static ssize_t cs40l26_fw_algo_id_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cs40l26_private *cs40l26 = file->private_data;
+ ssize_t ret;
+ char *str;
+ u32 val;
+
+ str = kzalloc(count, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ simple_write_to_buffer(str, count, ppos, user_buf, count);
+
+ ret = kstrtou32(str, 16, &val);
+ if (ret)
+ goto exit_free;
+
+ mutex_lock(&cs40l26->lock);
+
+ cs40l26->dbg_fw_algo_id = val;
+
+ mutex_unlock(&cs40l26->lock);
+
+exit_free:
+
+ kfree(str);
+
+ return ret ? ret : count;
+}
+
+static ssize_t cs40l26_fw_ctrl_val_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cs40l26_private *cs40l26 = file->private_data;
+ u32 reg, val, mem_type;
+ char *result, *input;
+ ssize_t ret;
+
+ if (!cs40l26->dbg_fw_ctrl_name || !cs40l26->dbg_fw_algo_id)
+ return -ENODEV;
+
+ if (strlen(cs40l26->dbg_fw_ctrl_name) == 0)
+ return -ENODATA;
+
+ ret = pm_runtime_get_sync(cs40l26->dev);
+ if (ret < 0) {
+ cs40l26_resume_error_handle(cs40l26->dev, (int) ret);
+ return ret;
+ }
+
+ mutex_lock(&cs40l26->lock);
+
+ mem_type = cs40l26->dbg_fw_ym ?
+ CL_DSP_YM_UNPACKED_TYPE : CL_DSP_XM_UNPACKED_TYPE;
+
+ input = kzalloc(strlen(cs40l26->dbg_fw_ctrl_name), GFP_KERNEL);
+ if (!input) {
+ ret = -ENOMEM;
+ goto err_mutex;
+ }
+
+ snprintf(input, strlen(cs40l26->dbg_fw_ctrl_name),
+ cs40l26->dbg_fw_ctrl_name);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, input, mem_type,
+ cs40l26->dbg_fw_algo_id, &reg);
+ kfree(input);
+ if (ret)
+ goto err_mutex;
+
+ ret = regmap_read(cs40l26->regmap, reg, &val);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to read fw control\n");
+ goto err_mutex;
+ }
+
+ result = kzalloc(CS40L26_ALGO_ID_MAX_STR_LEN, GFP_KERNEL);
+ if (!result) {
+ ret = -ENOMEM;
+ goto err_mutex;
+ }
+
+ snprintf(result, CS40L26_ALGO_ID_MAX_STR_LEN, "0x%08X\n", val);
+ ret = simple_read_from_buffer(user_buf, count, ppos, result,
+ strlen(result));
+
+ kfree(result);
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ return ret;
+}
+
+static const struct {
+ const char *name;
+ const struct file_operations fops;
+} cs40l26_debugfs_fops[] = {
+ {
+ .name = "fw_ctrl_name",
+ .fops = {
+ .open = simple_open,
+ .read = cs40l26_fw_ctrl_name_read,
+ .write = cs40l26_fw_ctrl_name_write,
+ },
+ },
+ {
+ .name = "fw_algo_id",
+ .fops = {
+ .open = simple_open,
+ .read = cs40l26_fw_algo_id_read,
+ .write = cs40l26_fw_algo_id_write,
+ },
+ },
+ {
+ .name = "fw_ctrl_val",
+ .fops = {
+ .open = simple_open,
+ .read = cs40l26_fw_ctrl_val_read,
+ },
+ },
+};
+
+void cs40l26_debugfs_init(struct cs40l26_private *cs40l26)
+{
+ struct dentry *root = NULL;
+ int i;
+
+ root = debugfs_create_dir("cs40l26", NULL);
+ if (!root)
+ return;
+
+ debugfs_create_bool("fw_ym_space", CL_DSP_DEBUGFS_RW_FILE_MODE,
+ root, &cs40l26->dbg_fw_ym);
+
+ for (i = 0; i < CS40L26_NUM_DEBUGFS; i++)
+ debugfs_create_file(cs40l26_debugfs_fops[i].name,
+ CL_DSP_DEBUGFS_RW_FILE_MODE, root, cs40l26,
+ &cs40l26_debugfs_fops[i].fops);
+
+ cs40l26->dbg_fw_ym = false;
+ cs40l26->dbg_fw_algo_id = CS40L26_VIBEGEN_ALGO_ID;
+ cs40l26->debugfs_root = root;
+
+ cs40l26->cl_dsp_db = cl_dsp_debugfs_create(cs40l26->dsp,
+ cs40l26->debugfs_root,
+ (u32) CS40L26_EVENT_LOGGER_ALGO_ID);
+
+ if (IS_ERR(cs40l26->cl_dsp_db) || !cs40l26->cl_dsp_db)
+ dev_err(cs40l26->dev, "Failed to create CL DSP Debugfs\n");
+}
+EXPORT_SYMBOL(cs40l26_debugfs_init);
+
+void cs40l26_debugfs_cleanup(struct cs40l26_private *cs40l26)
+{
+ cl_dsp_debugfs_destroy(cs40l26->cl_dsp_db);
+ kfree(cs40l26->dbg_fw_ctrl_name);
+ cs40l26->dbg_fw_ctrl_name = NULL;
+ debugfs_remove_recursive(cs40l26->debugfs_root);
+}
+EXPORT_SYMBOL(cs40l26_debugfs_cleanup);
+
+#endif /* CONFIG_DEBUG_FS */
diff --git a/cs40l26/cs40l26-sysfs.c b/cs40l26/cs40l26-sysfs.c
index 6737894..34a2a35 100644
--- a/cs40l26/cs40l26-sysfs.c
+++ b/cs40l26/cs40l26-sysfs.c
@@ -1131,6 +1131,7 @@ static ssize_t trigger_calibration_store(struct device *dev,
struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
u32 mailbox_command, calibration_request_payload;
int ret;
+ struct completion *completion;
dev_dbg(cs40l26->dev, "%s: %s", __func__, buf);
@@ -1140,38 +1141,66 @@ static ssize_t trigger_calibration_store(struct device *dev,
}
ret = kstrtou32(buf, 16, &calibration_request_payload);
- if (ret ||
- calibration_request_payload < 1 ||
- calibration_request_payload > 2)
+ if (ret)
return -EINVAL;
+ switch (calibration_request_payload) {
+ case CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q:
+ completion = &cs40l26->cal_f0_cont;
+ break;
+ case CS40L26_CALIBRATION_CONTROL_REQUEST_REDC:
+ completion = &cs40l26->cal_redc_cont;
+ break;
+ case CS40L26_CALIBRATION_CONTROL_REQUEST_DVL_PEQ:
+ completion = &cs40l26->cal_dvl_peq_cont;
+ break;
+ default:
+ return -EINVAL;
+ }
+
mailbox_command = ((CS40L26_DSP_MBOX_CMD_INDEX_CALIBRATION_CONTROL <<
CS40L26_DSP_MBOX_CMD_INDEX_SHIFT) &
CS40L26_DSP_MBOX_CMD_INDEX_MASK) |
(calibration_request_payload &
CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK);
- /* pm_exit occurs is irq_handler after diagnostic is finished */
ret = cs40l26_pm_enter(cs40l26->dev);
if (ret)
return ret;
mutex_lock(&cs40l26->lock);
+ reinit_completion(completion);
ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
mailbox_command, CS40L26_DSP_MBOX_RESET);
+ mutex_unlock(&cs40l26->lock);
+
if (ret) {
dev_err(cs40l26->dev, "Failed to request calibration\n");
- cs40l26->cal_requested = 0;
- } else {
- cs40l26->cal_requested = calibration_request_payload;
- ret = count;
+ goto err_pm;
}
- mutex_unlock(&cs40l26->lock);
+ if (!wait_for_completion_timeout(
+ completion,
+ msecs_to_jiffies(CS40L26_CALIBRATION_TIMEOUT_MS))) {
+ ret = -ETIME;
+ dev_err(cs40l26->dev, "Failed to complete cal req, %d, err: %d",
+ calibration_request_payload, ret);
+ goto err_pm;
+ }
- return ret;
+ mutex_lock(&cs40l26->lock);
+
+ if (calibration_request_payload ==
+ CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q){
+ ret = cs40l26_copy_f0_est_to_dvl(cs40l26);
+ }
+
+ mutex_unlock(&cs40l26->lock);
+err_pm:
+ cs40l26_pm_exit(cs40l26->dev);
+ return ret ? ret : count;
}
static DEVICE_ATTR_WO(trigger_calibration);
@@ -1700,6 +1729,99 @@ err_mutex:
}
static DEVICE_ATTR_RO(redc_cal_time_ms);
+static ssize_t dvl_peq_coefficients_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 reg, dvl_peq_coefficients[CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS];
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = cs40l26_pm_enter(cs40l26->dev);
+ if (ret)
+ return ret;
+
+ mutex_lock(&cs40l26->lock);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "PEQ_COEF1_X",
+ CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_DVL_ALGO_ID, &reg);
+ if (ret)
+ goto err_mutex;
+
+ ret = regmap_bulk_read(cs40l26->regmap, reg, dvl_peq_coefficients,
+ CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS);
+ if (ret)
+ goto err_mutex;
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+
+ cs40l26_pm_exit(cs40l26->dev);
+
+ if (ret)
+ return ret;
+
+ return snprintf(buf, PAGE_SIZE,
+ "%08X %08X %08X %08X %08X %08X\n",
+ dvl_peq_coefficients[0], dvl_peq_coefficients[1],
+ dvl_peq_coefficients[2], dvl_peq_coefficients[3],
+ dvl_peq_coefficients[4], dvl_peq_coefficients[5]);
+}
+
+static ssize_t dvl_peq_coefficients_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ u32 reg, dvl_peq_coefficients[CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS];
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ char *coeffs_str, *coeffs_str_temp, *coeff_str;
+ int ret, coeffs_found = 0;
+
+ coeffs_str = kstrdup(buf, GFP_KERNEL);
+ if (!coeffs_str)
+ return -ENOMEM;
+
+ coeffs_str_temp = coeffs_str;
+ while ((coeff_str = strsep(&coeffs_str_temp, " ")) != NULL) {
+ ret = kstrtou32(coeff_str, 16,
+ &dvl_peq_coefficients[coeffs_found++]);
+ if (ret)
+ goto err_free;
+ }
+
+ if (coeffs_found != CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS) {
+ dev_err(cs40l26->dev, "Num DVL PEQ coeffs, %d, expecting %d\n",
+ coeffs_found, CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ ret = cs40l26_pm_enter(cs40l26->dev);
+ if (ret)
+ goto err_free;
+
+ mutex_lock(&cs40l26->lock);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "PEQ_COEF1_X",
+ CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_DVL_ALGO_ID, &reg);
+ if (ret)
+ goto err_mutex;
+
+ ret = regmap_bulk_write(cs40l26->regmap, reg, dvl_peq_coefficients,
+ CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to write DVL PEQ coefficients,%d",
+ ret);
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+ cs40l26_pm_exit(cs40l26->dev);
+err_free:
+ kfree(coeffs_str);
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(dvl_peq_coefficients);
+
static ssize_t logging_en_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -2055,6 +2177,7 @@ static struct attribute *cs40l26_dev_attrs_cal[] = {
&dev_attr_f0_measured.attr,
&dev_attr_q_measured.attr,
&dev_attr_redc_measured.attr,
+ &dev_attr_dvl_peq_coefficients.attr,
&dev_attr_redc_est.attr,
&dev_attr_f0_stored.attr,
&dev_attr_q_stored.attr,
diff --git a/cs40l26/cs40l26.c b/cs40l26/cs40l26.c
index 2073191..3a0734e 100644
--- a/cs40l26/cs40l26.c
+++ b/cs40l26/cs40l26.c
@@ -686,6 +686,7 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
{
struct device *dev = cs40l26->dev;
u32 val = 0;
+ int ret;
while (!cs40l26_mbox_buffer_read(cs40l26, &val)) {
if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK)
@@ -695,6 +696,17 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
return -ENOTRECOVERABLE;
}
+ if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK) ==
+ CS40L26_DSP_MBOX_WATERMARK) {
+ dev_dbg(dev, "Mailbox: WATERMARK\n");
+
+ ret = cl_dsp_logger_update(cs40l26->cl_dsp_db);
+ if (ret)
+ return ret;
+
+ continue;
+ }
+
switch (val) {
case CS40L26_DSP_MBOX_COMPLETE_MBOX:
ATRACE_END();
@@ -749,33 +761,14 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
break;
case CS40L26_DSP_MBOX_F0_EST_DONE:
dev_dbg(dev, "Mailbox: F0_EST_DONE\n");
- if (cs40l26->cal_requested &
- CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q) {
- cs40l26->cal_requested &=
- ~CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q;
- /* for pm_runtime_get see trigger_calibration */
- cs40l26_pm_exit(cs40l26->dev);
- } else {
- dev_err(dev, "Unexpected mbox msg: %d", val);
- return -EINVAL;
- }
-
+ complete(&cs40l26->cal_f0_cont);
break;
case CS40L26_DSP_MBOX_REDC_EST_START:
dev_dbg(dev, "Mailbox: REDC_EST_START\n");
break;
case CS40L26_DSP_MBOX_REDC_EST_DONE:
dev_dbg(dev, "Mailbox: REDC_EST_DONE\n");
- if (cs40l26->cal_requested &
- CS40L26_CALIBRATION_CONTROL_REQUEST_REDC) {
- cs40l26->cal_requested &=
- ~CS40L26_CALIBRATION_CONTROL_REQUEST_REDC;
- /* for pm_runtime_get see trigger_calibration */
- cs40l26_pm_exit(cs40l26->dev);
- } else {
- dev_err(dev, "Unexpected mbox msg: %d", val);
- return -EINVAL;
- }
+ complete(&cs40l26->cal_redc_cont);
break;
case CS40L26_DSP_MBOX_LE_EST_START:
dev_dbg(dev, "Mailbox: LE_EST_START\n");
@@ -783,6 +776,13 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
case CS40L26_DSP_MBOX_LE_EST_DONE:
dev_dbg(dev, "Mailbox: LE_EST_DONE\n");
break;
+ case CS40L26_DSP_MBOX_PEQ_CALCULATION_START:
+ dev_dbg(dev, "Mailbox: PEQ_CALCULATION_START\n");
+ break;
+ case CS40L26_DSP_MBOX_PEQ_CALCULATION_DONE:
+ dev_dbg(dev, "Mailbox: PEQ_CALCULATION_DONE\n");
+ complete(&cs40l26->cal_dvl_peq_cont);
+ break;
case CS40L26_DSP_MBOX_SYS_ACK:
dev_err(dev, "Mailbox: ACK\n");
return -EPERM;
@@ -796,6 +796,53 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
return 0;
}
+int cs40l26_copy_f0_est_to_dvl(struct cs40l26_private *cs40l26)
+{
+ u32 reg, f0_measured_q9_14, global_sample_rate, normalized_f0_q1_23;
+ int ret, sample_rate;
+
+ /* Must be awake and under mutex lock */
+ ret = regmap_read(cs40l26->regmap, CS40L26_GLOBAL_SAMPLE_RATE,
+ &global_sample_rate);
+ if (ret)
+ return ret;
+
+ switch (global_sample_rate & CS40L26_GLOBAL_FS_MASK) {
+ case CS40L26_GLOBAL_FS_48K:
+ sample_rate = 48000;
+ break;
+ case CS40L26_GLOBAL_FS_96K:
+ sample_rate = 96000;
+ break;
+ default:
+ dev_warn(cs40l26->dev, "Invalid GLOBAL_FS, %08X",
+ global_sample_rate);
+ return -EINVAL;
+ }
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "F0_EST",
+ CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_F0_EST_ALGO_ID, &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(cs40l26->regmap, reg, &f0_measured_q9_14);
+ if (ret)
+ return ret;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "LRA_NORM_F0",
+ CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_DVL_ALGO_ID, &reg);
+ if (ret)
+ return ret;
+
+ normalized_f0_q1_23 = (f0_measured_q9_14 << 9) / sample_rate;
+ ret = regmap_write(cs40l26->regmap, reg, normalized_f0_q1_23);
+
+ return ret;
+}
+EXPORT_SYMBOL(cs40l26_copy_f0_est_to_dvl);
+
int cs40l26_asp_start(struct cs40l26_private *cs40l26)
{
int ret;
@@ -1668,8 +1715,8 @@ static int cs40l26_irq_update_mask(struct cs40l26_private *cs40l26, u32 reg,
static int cs40l26_buzzgen_set(struct cs40l26_private *cs40l26, u16 freq,
u16 level, u16 duration, u8 buzzgen_num)
{
- int ret;
unsigned int base_reg, freq_reg, level_reg, duration_reg;
+ int ret;
/* BUZZ_EFFECTS1_BUZZ_xxx are initially populated by contents of OTP.
* The buzz specified by these controls is triggered by writing
@@ -1709,7 +1756,8 @@ static int cs40l26_buzzgen_set(struct cs40l26_private *cs40l26, u16 freq,
}
static int cs40l26_map_gpi_to_haptic(struct cs40l26_private *cs40l26,
- u32 index, u8 bank, struct ff_effect *effect)
+ struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
{
u16 button = effect->trigger.button;
u8 gpio = (button & CS40L26_BTN_NUM_MASK) >> CS40L26_BTN_NUM_SHIFT;
@@ -1720,8 +1768,9 @@ static int cs40l26_map_gpi_to_haptic(struct cs40l26_private *cs40l26,
edge = (button & CS40L26_BTN_EDGE_MASK) >> CS40L26_BTN_EDGE_SHIFT;
- switch (bank) {
+ switch (ueffect->wvfrm_bank) {
case CS40L26_RAM_BANK_ID:
+ case CS40L26_BUZ_BANK_ID:
owt = false;
ev_handler_bank_ram = true;
break;
@@ -1735,7 +1784,7 @@ static int cs40l26_map_gpi_to_haptic(struct cs40l26_private *cs40l26,
break;
default:
dev_err(cs40l26->dev, "Effect bank %u not supported\n",
- bank);
+ ueffect->wvfrm_bank);
return -EINVAL;
}
@@ -1746,23 +1795,19 @@ static int cs40l26_map_gpi_to_haptic(struct cs40l26_private *cs40l26,
}
reg = cs40l26->event_map_base + (edge ? 0 : 4);
- write_val = (index & CS40L26_BTN_INDEX_MASK) |
+ write_val = (ueffect->trigger_index & CS40L26_BTN_INDEX_MASK) |
(ev_handler_bank_ram << CS40L26_BTN_BANK_SHIFT) |
(owt << CS40L26_BTN_OWT_SHIFT);
- ret = cs40l26_pm_enter(cs40l26->dev);
- if (ret)
- return ret;
-
ret = regmap_write(cs40l26->regmap, reg, write_val);
if (ret) {
dev_err(cs40l26->dev, "Failed to update event map\n");
- goto pm_exit;
+ return ret;
}
ret = cl_dsp_fw_rev_get(cs40l26->dsp, &fw_rev);
if (ret)
- goto pm_exit;
+ return ret;
use_timeout = (!cs40l26->calib_fw &&
fw_rev >= CS40L26_FW_GPI_TIMEOUT_MIN_REV) ||
@@ -1774,45 +1819,58 @@ static int cs40l26_map_gpi_to_haptic(struct cs40l26_private *cs40l26,
CL_DSP_XM_UNPACKED_TYPE,
CS40L26_VIBEGEN_ALGO_ID, &reg);
if (ret)
- goto pm_exit;
+ return ret;
ret = regmap_write(cs40l26->regmap, reg, effect->replay.length);
if (ret)
- dev_err(cs40l26->dev, "Failed to set GPI timeout\n");
+ dev_warn(cs40l26->dev,
+ "Failed to set GPI timeout, continuing...\n");
}
if (edge)
- cs40l26->gpi_ids[CS40L26_GPIO_MAP_A_PRESS] = effect->id;
+ ueffect->mapping = CS40L26_GPIO_MAP_A_PRESS;
else
- cs40l26->gpi_ids[CS40L26_GPIO_MAP_A_RELEASE] = effect->id;
-
-pm_exit:
- cs40l26_pm_exit(cs40l26->dev);
+ ueffect->mapping = CS40L26_GPIO_MAP_A_RELEASE;
return ret;
}
-static struct cs40l26_owt *cs40l26_owt_find(struct cs40l26_private *cs40l26,
+static struct cs40l26_uploaded_effect
+ *cs40l26_uploaded_effect_find(struct cs40l26_private *cs40l26,
int id)
{
- struct cs40l26_owt *owt;
+ struct list_head *head = &cs40l26->effect_head;
+ struct cs40l26_uploaded_effect *ueffect;
- if (list_empty(&cs40l26->owt_head)) {
- dev_err(cs40l26->dev, "OWT list is empty\n");
- return ERR_PTR(-EINVAL);
+ if (list_empty(head)) {
+ dev_dbg(cs40l26->dev, "Effect list is empty\n");
+ return ERR_PTR(-ENODATA);
}
- list_for_each_entry(owt, &cs40l26->owt_head, list) {
- if (owt->effect_id == id)
+ list_for_each_entry(ueffect, head, list) {
+ if (ueffect->id == id)
break;
}
- if (owt->effect_id != id) {
- dev_err(cs40l26->dev, "OWT effect with ID %d not found\n", id);
- return ERR_PTR(-EINVAL);
+ if (ueffect->id != id) {
+ dev_dbg(cs40l26->dev, "No such effect (ID = %d)\n", id);
+ return ERR_PTR(-ENODEV);
+ }
+
+ return ueffect;
+}
+
+static bool cs40l26_is_no_wait_ram_index(struct cs40l26_private *cs40l26,
+ u32 index)
+{
+ int i;
+
+ for (i = 0; i < cs40l26->num_no_wait_ram_indices; i++) {
+ if (cs40l26->no_wait_ram_indices[i] == index)
+ return true;
}
- return owt;
+ return false;
}
static void cs40l26_set_gain_worker(struct work_struct *work)
@@ -1861,13 +1919,12 @@ static void cs40l26_vibe_start_worker(struct work_struct *work)
struct cs40l26_private *cs40l26 = container_of(work,
struct cs40l26_private, vibe_start_work);
struct device *dev = cs40l26->dev;
- u32 index = 0;
- int ret = 0;
+ struct cs40l26_uploaded_effect *ueffect;
struct ff_effect *effect;
- struct cs40l26_owt *owt;
unsigned int reg;
- bool invert;
u16 duration;
+ bool invert;
+ int ret;
dev_dbg(dev, "%s\n", __func__);
@@ -1879,16 +1936,11 @@ static void cs40l26_vibe_start_worker(struct work_struct *work)
effect = cs40l26->trigger_effect;
- if (effect->u.periodic.waveform == FF_CUSTOM ||
- effect->u.periodic.waveform == FF_SINE)
- index = cs40l26->trigger_indices[effect->id];
-
- if (is_owt(index)) {
- owt = cs40l26_owt_find(cs40l26, effect->id);
- if (IS_ERR(owt)) {
- ret = PTR_ERR(owt);
- goto err_mutex;
- }
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, effect->id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ dev_err(dev, "No such effect to play back\n");
+ ret = PTR_ERR(ueffect);
+ goto err_mutex;
}
duration = effect->replay.length;
@@ -1931,9 +1983,11 @@ static void cs40l26_vibe_start_worker(struct work_struct *work)
case FF_CUSTOM:
case FF_SINE:
ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
- index, CS40L26_DSP_MBOX_RESET);
+ ueffect->trigger_index, CS40L26_DSP_MBOX_RESET);
if (ret)
goto err_mutex;
+
+ cs40l26->cur_index = ueffect->trigger_index;
break;
default:
dev_err(dev, "Invalid waveform type: 0x%X\n",
@@ -1957,6 +2011,8 @@ static void cs40l26_vibe_stop_worker(struct work_struct *work)
{
struct cs40l26_private *cs40l26 = container_of(work,
struct cs40l26_private, vibe_stop_work);
+ bool skip_delay;
+ u32 delay_us;
int ret;
dev_dbg(cs40l26->dev, "%s\n", __func__);
@@ -1965,13 +2021,24 @@ static void cs40l26_vibe_stop_worker(struct work_struct *work)
if (ret)
return;
- /* wait for SVC init phase to complete */
- if (cs40l26->delay_before_stop_playback_us)
- usleep_range(cs40l26->delay_before_stop_playback_us,
- cs40l26->delay_before_stop_playback_us + 100);
-
mutex_lock(&cs40l26->lock);
+ delay_us = cs40l26->delay_before_stop_playback_us;
+ skip_delay = cs40l26_is_no_wait_ram_index(cs40l26, cs40l26->cur_index);
+
+ if (delay_us && !skip_delay) {
+ mutex_unlock(&cs40l26->lock);
+
+ dev_info(cs40l26->dev, "Applying delay\n");
+
+ /* wait for SVC init phase to complete */
+ usleep_range(delay_us, delay_us + 100);
+
+ mutex_lock(&cs40l26->lock);
+ } else {
+ dev_info(cs40l26->dev, "Skipping delay\n");
+ }
+
if (cs40l26->vibe_state != CS40L26_VIBE_STATE_HAPTIC) {
dev_warn(cs40l26->dev, "Attempted stop when vibe_state = %d\n",
cs40l26->vibe_state);
@@ -2260,24 +2327,6 @@ err_free:
return ret;
}
-static int cs40l26_owt_add(struct cs40l26_private *cs40l26, int effect_id,
- u32 index)
-{
- struct cs40l26_owt *owt_new;
-
- owt_new = kzalloc(sizeof(*owt_new), GFP_KERNEL);
- if (!owt_new)
- return -ENOMEM;
-
- owt_new->effect_id = effect_id;
- owt_new->trigger_index = index;
- list_add(&owt_new->list, &cs40l26->owt_head);
-
- cs40l26->num_owt_effects++;
-
- return 0;
-}
-
static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, u8 *data,
u32 data_size_bytes)
{
@@ -2371,7 +2420,7 @@ static u8 *cs40l26_ncw_amp_scaling(struct cs40l26_private *cs40l26, u8 amp,
nsections);
if (ret) {
dev_err(cs40l26->dev, "Failed to get section info\n");
- return ERR_PTR(ret);
+ goto sections_free;
}
for (i = 0; i < nsections; i++) {
@@ -2384,14 +2433,17 @@ static u8 *cs40l26_ncw_amp_scaling(struct cs40l26_private *cs40l26, u8 amp,
out_data = kcalloc(data_bytes, sizeof(u8), GFP_KERNEL);
if (!out_data) {
- kfree(sections);
- return ERR_PTR(-ENOMEM);
+ ret = -ENOMEM;
+ goto sections_free;
}
out_ch = cl_dsp_memchunk_create((void *) out_data, data_bytes);
cs40l26_owt_set_section_info(cs40l26, &out_ch, sections, nsections);
- return out_data;
+sections_free:
+ kfree(sections);
+
+ return ret ? ERR_PTR(ret) : out_data;
}
static int cs40l26_owt_comp_data_size(struct cs40l26_private *cs40l26,
@@ -2649,62 +2701,44 @@ sections_err_free:
static u8 cs40l26_get_lowest_free_buzzgen(struct cs40l26_private *cs40l26)
{
- int buzzgen, i;
+ u8 buzzgen = 1;
+ struct cs40l26_uploaded_effect *ueffect;
- /* Note that this search starts at RAM_BUZZ1 and does not utilize the
- * OTP buzz
- */
- for (buzzgen = 1; buzzgen <= CS40L26_BUZZGEN_NUM_CONFIGS; buzzgen++) {
- for (i = 0; i < FF_MAX_EFFECTS; i++) {
- if (cs40l26->trigger_indices[i] ==
- CS40L26_BUZZGEN_INDEX_START + buzzgen)
- break;
- }
+ if (list_empty(&cs40l26->effect_head))
+ return buzzgen;
- if (i == FF_MAX_EFFECTS)
- break;
+ list_for_each_entry(ueffect, &cs40l26->effect_head, list) {
+ if (ueffect->wvfrm_bank == CS40L26_BUZ_BANK_ID)
+ buzzgen++;
}
return buzzgen;
}
static int cs40l26_sine_upload(struct cs40l26_private *cs40l26,
- struct ff_effect *effect)
+ struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
{
struct device *dev = cs40l26->dev;
- int ret;
u8 lowest_free_buzzgen, level;
- u16 freq;
- u32 trigger_index;
-
- if (effect->u.periodic.period) {
- if (effect->u.periodic.period < CS40L26_BUZZGEN_PERIOD_MIN ||
- effect->u.periodic.period > CS40L26_BUZZGEN_PERIOD_MAX) {
- dev_err(dev,
- "%u ms period not within range (4-10 ms)\n",
- effect->u.periodic.period);
- return -EINVAL;
- }
- } else {
- dev_err(dev, "Sine wave period not specified\n");
- return -EINVAL;
- }
+ u16 freq, period;
+ int ret;
- if (effect->u.periodic.magnitude) {
- if (effect->u.periodic.magnitude < CS40L26_BUZZGEN_LEVEL_MIN ||
- effect->u.periodic.magnitude > CS40L26_BUZZGEN_LEVEL_MAX) {
- dev_err(dev,
- "%u magnitude not within range (%d-%d)\n",
- effect->u.periodic.magnitude,
- CS40L26_BUZZGEN_LEVEL_MIN,
- CS40L26_BUZZGEN_LEVEL_MAX);
- return -EINVAL;
- }
+ if (effect->u.periodic.period < CS40L26_BUZZGEN_PER_MIN)
+ period = CS40L26_BUZZGEN_PER_MIN;
+ else if (effect->u.periodic.period > CS40L26_BUZZGEN_PER_MAX)
+ period = CS40L26_BUZZGEN_PER_MAX;
+ else
+ period = effect->u.periodic.period;
+
+ freq = CS40L26_MS_TO_HZ(period);
+ if (effect->u.periodic.magnitude < CS40L26_BUZZGEN_LEVEL_MIN)
+ level = CS40L26_BUZZGEN_LEVEL_MIN;
+ else if (effect->u.periodic.magnitude > CS40L26_BUZZGEN_LEVEL_MAX)
+ level = CS40L26_BUZZGEN_LEVEL_MAX;
+ else
level = effect->u.periodic.magnitude;
- } else {
- level = CS40L26_BUZZGEN_LEVEL_DEFAULT;
- }
lowest_free_buzzgen = cs40l26_get_lowest_free_buzzgen(cs40l26);
dev_dbg(dev, "lowest_free_buzzgen: %d", lowest_free_buzzgen);
@@ -2714,172 +2748,184 @@ static int cs40l26_sine_upload(struct cs40l26_private *cs40l26,
return -ENOSPC;
}
- freq = CS40L26_MS_TO_HZ(effect->u.periodic.period);
-
ret = cs40l26_buzzgen_set(cs40l26, freq, level,
effect->replay.length, lowest_free_buzzgen);
if (ret)
return ret;
- trigger_index = CS40L26_BUZZGEN_INDEX_START + lowest_free_buzzgen;
- cs40l26->trigger_indices[effect->id] = trigger_index;
-
- if (effect->trigger.button)
- ret = cs40l26_map_gpi_to_haptic(cs40l26, trigger_index,
- CS40L26_RAM_BANK_ID, effect);
+ ueffect->id = effect->id;
+ ueffect->wvfrm_bank = CS40L26_BUZ_BANK_ID;
+ ueffect->trigger_index = CS40L26_BUZZGEN_INDEX_START +
+ lowest_free_buzzgen;
return ret;
}
-static void cs40l26_upload_worker(struct work_struct *work)
+static int cs40l26_custom_upload(struct cs40l26_private *cs40l26,
+ struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
{
- struct cs40l26_private *cs40l26 = container_of(work,
- struct cs40l26_private, upload_work);
- struct device *cdev = cs40l26->dev;
- u8 *refactored_data = NULL;
- int ret = 0, refactored_size, len;
- u32 trigger_index, min_index, max_index, nwaves;
- struct ff_effect *effect;
+ s16 first = cs40l26->raw_custom_data[0];
+ bool is_pwle = (first != CS40L26_WT_TYPE10_COMP_BUFFER);
+ bool is_svc = (first == CS40L26_SVC_ID);
+ struct device *dev = cs40l26->dev;
+ u32 nwaves, min_index, max_index, trigger_index;
+ int ret, data_len, refactored_data_len;
+ u8 *refactored_data;
u16 index, bank;
- bool pwle, svc_waveform;
- ret = cs40l26_pm_enter(cdev);
- if (ret)
- return;
+ data_len = effect->u.periodic.custom_len;
- mutex_lock(&cs40l26->lock);
+ if (data_len > CS40L26_CUSTOM_DATA_SIZE) {
+ refactored_data_len = cs40l26_refactor_owt(cs40l26,
+ cs40l26->raw_custom_data, data_len, is_pwle,
+ is_svc, &refactored_data);
+ if (refactored_data_len <= 0) {
+ dev_err(cs40l26->dev, "Failed to refactor OWT\n");
+ return -ENOMEM;
+ }
- effect = &cs40l26->upload_effect;
+ ret = cs40l26_owt_upload(cs40l26, refactored_data,
+ refactored_data_len);
+ kfree(refactored_data);
+ if (ret)
+ return ret;
- if (effect->type != FF_PERIODIC) {
- dev_err(cdev, "Effect type 0x%X not supported\n", effect->type);
- ret = -EINVAL;
- goto out_mutex;
+ bank = (u16) CS40L26_OWT_BANK_ID;
+ index = (u16) cs40l26->num_owt_effects;
+ } else {
+ bank = (u16) first;
+ index = (u16) (cs40l26->raw_custom_data[1] &
+ CS40L26_MAX_INDEX_MASK);
}
- if (is_owt(cs40l26->trigger_indices[effect->id])) {
- dev_err(cdev, "Open Wavetable effects cannot be edited\n");
- ret = -EPERM;
- goto out_mutex;
+ ret = cs40l26_get_num_waves(cs40l26, &nwaves);
+ if (ret)
+ return ret;
+
+ switch (bank) {
+ case CS40L26_RAM_BANK_ID:
+ if (nwaves - cs40l26->num_owt_effects == 0) {
+ dev_err(dev, "No waveforms in RAM bank\n");
+ return -EINVAL;
+ }
+
+ min_index = CS40L26_RAM_INDEX_START;
+ max_index = min_index + nwaves - cs40l26->num_owt_effects - 1;
+ break;
+ case CS40L26_ROM_BANK_ID:
+ min_index = CS40L26_ROM_INDEX_START;
+ max_index = CS40L26_ROM_INDEX_END;
+ break;
+ case CS40L26_OWT_BANK_ID:
+ min_index = CS40L26_OWT_INDEX_START;
+ max_index = CS40L26_OWT_INDEX_END;
+ break;
+ default:
+ dev_err(dev, "Bank ID (%u) invalid\n", bank);
+ return -EINVAL;
}
- switch (effect->u.periodic.waveform) {
- case FF_CUSTOM:
- pwle = (cs40l26->raw_custom_data[0] ==
- CS40L26_WT_TYPE10_COMP_BUFFER) ? false : true;
- svc_waveform = (cs40l26->raw_custom_data[0] ==
- CS40L26_SVC_ID) ? true : false;
-
- len = effect->u.periodic.custom_len;
-
- if (len > CS40L26_CUSTOM_DATA_SIZE) {
- refactored_size = cs40l26_refactor_owt(cs40l26,
- cs40l26->raw_custom_data, len,
- pwle, svc_waveform,
- &refactored_data);
-
- if (refactored_size <= 0) {
- dev_err(cdev,
- "Failed to refactor OWT waveform\n");
- ret = refactored_size;
- goto out_mutex;
- }
+ trigger_index = index + min_index;
+ if (trigger_index < min_index || trigger_index > max_index) {
+ dev_err(dev, "Index 0x%X out of bounds (0x%X - 0x%X)\n",
+ trigger_index, min_index, max_index);
+ return -EINVAL;
+ }
+ dev_dbg(dev, "ID = %d, trigger index = 0x%08X\n", effect->id,
+ trigger_index);
- ret = cs40l26_owt_upload(cs40l26, refactored_data,
- refactored_size);
- if (ret)
- goto out_free;
+ if (bank == CS40L26_OWT_BANK_ID)
+ cs40l26->num_owt_effects++;
- bank = CS40L26_OWT_BANK_ID;
- index = cs40l26->num_owt_effects;
- } else {
- bank = ((u16) cs40l26->raw_custom_data[0]);
- index = ((u16) cs40l26->raw_custom_data[1]) &
- CS40L26_MAX_INDEX_MASK;
- }
+ ueffect->id = effect->id;
+ ueffect->wvfrm_bank = bank;
+ ueffect->trigger_index = trigger_index;
+
+ return ret;
+}
+
+static int cs40l26_uploaded_effect_add(struct cs40l26_private *cs40l26,
+ struct ff_effect *effect)
+{
+ struct device *dev = cs40l26->dev;
+ bool is_new = false;
+ struct cs40l26_uploaded_effect *ueffect;
+ int ret;
+
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, effect->id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ is_new = true;
+ ueffect = kzalloc(sizeof(*ueffect), GFP_KERNEL);
+ if (!ueffect)
+ return -ENOMEM;
+ }
+
+ if (effect->u.periodic.waveform == FF_CUSTOM) {
+ ret = cs40l26_custom_upload(cs40l26, effect, ueffect);
+ } else if (effect->u.periodic.waveform == FF_SINE) {
+ ret = cs40l26_sine_upload(cs40l26, effect, ueffect);
+ } else {
+ dev_err(dev, "Periodic waveform type 0x%X not supported\n",
+ effect->u.periodic.waveform);
+ ret = -EINVAL;
+ }
+
+ if (ret)
+ goto err_free;
- ret = cs40l26_get_num_waves(cs40l26, &nwaves);
+ if (effect->trigger.button) {
+ ret = cs40l26_map_gpi_to_haptic(cs40l26, effect, ueffect);
if (ret)
- goto out_free;
+ goto err_free;
+ } else {
+ ueffect->mapping = CS40L26_GPIO_MAP_INVALID;
+ }
- switch (bank) {
- case CS40L26_RAM_BANK_ID:
- if (nwaves - cs40l26->num_owt_effects == 0) {
- dev_err(cdev, "No waveforms in RAM bank\n");
- ret = -EINVAL;
- goto out_free;
- } else {
- min_index = CS40L26_RAM_INDEX_START;
- max_index = min_index + nwaves - 1 -
- cs40l26->num_owt_effects;
- }
- break;
- case CS40L26_ROM_BANK_ID:
- min_index = CS40L26_ROM_INDEX_START;
- max_index = CS40L26_ROM_INDEX_END;
- break;
- case CS40L26_OWT_BANK_ID:
- min_index = CS40L26_OWT_INDEX_START;
- max_index = CS40L26_OWT_INDEX_END;
- break;
- default:
- dev_err(cdev, "Bank ID (%u) out of bounds\n", bank);
- ret = -EINVAL;
- goto out_free;
- }
+ if (is_new)
+ list_add(&ueffect->list, &cs40l26->effect_head);
- trigger_index = index + min_index;
+ return 0;
+err_free:
+ if (is_new)
+ kfree(ueffect);
- if (trigger_index >= min_index && trigger_index <= max_index) {
- cs40l26->trigger_indices[effect->id] = trigger_index;
- } else {
- dev_err(cdev,
- "Index (0x%X) out of bounds (0x%X - 0x%X)\n",
- trigger_index, min_index, max_index);
- ret = -EINVAL;
- goto out_free;
- }
+ return ret;
+}
- if (is_owt(trigger_index)) {
- ret = cs40l26_owt_add(cs40l26, effect->id,
- trigger_index);
- if (ret)
- goto out_free;
- }
+static void cs40l26_upload_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 = container_of(work,
+ struct cs40l26_private, upload_work);
+ struct device *cdev = cs40l26->dev;
+ struct ff_effect *effect;
+ u32 nwaves;
+ int ret;
- dev_dbg(cdev, "%s: ID = %u, index = 0x%08X\n",
- __func__, effect->id, trigger_index);
+ ret = cs40l26_pm_enter(cdev);
+ if (ret)
+ return;
- if (effect->trigger.button) {
- ret = cs40l26_map_gpi_to_haptic(cs40l26,
- trigger_index, bank, effect);
- if (ret)
- goto out_free;
- }
+ mutex_lock(&cs40l26->lock);
- break;
- case FF_SINE:
- ret = cs40l26_sine_upload(cs40l26, effect);
- if (ret)
- goto out_mutex;
+ effect = &cs40l26->upload_effect;
- break;
- default:
- dev_err(cdev, "Periodic waveform type 0x%X not supported\n",
- effect->u.periodic.waveform);
+ if (effect->type != FF_PERIODIC) {
+ dev_err(cdev, "Effect type 0x%X not supported\n", effect->type);
ret = -EINVAL;
goto out_mutex;
}
+ ret = cs40l26_uploaded_effect_add(cs40l26, effect);
+ if (ret)
+ goto out_mutex;
+
ret = cs40l26_get_num_waves(cs40l26, &nwaves);
if (ret)
- goto out_free;
+ goto out_mutex;
dev_dbg(cdev, "Total number of waveforms = %u\n", nwaves);
-out_free:
- kfree(refactored_data);
-
out_mutex:
mutex_unlock(&cs40l26->lock);
@@ -2944,17 +2990,6 @@ const struct attribute_group *cs40l26_dev_attr_groups[] = {
};
#endif
-static enum cs40l26_gpio_map cs40l26_map_get(struct cs40l26_private *cs40l26,
- int effect_id)
-{
- if (cs40l26->gpi_ids[CS40L26_GPIO_MAP_A_PRESS] == effect_id)
- return CS40L26_GPIO_MAP_A_PRESS;
- else if (cs40l26->gpi_ids[CS40L26_GPIO_MAP_A_RELEASE] == effect_id)
- return CS40L26_GPIO_MAP_A_RELEASE;
-
- return CS40L26_GPIO_MAP_INVALID;
-}
-
static int cs40l26_erase_gpi_mapping(struct cs40l26_private *cs40l26,
enum cs40l26_gpio_map mapping)
{
@@ -2980,30 +3015,18 @@ static int cs40l26_erase_gpi_mapping(struct cs40l26_private *cs40l26,
return ret;
}
- cs40l26->gpi_ids[mapping] = -1;
-
- return 0;
-}
-
-static int cs40l26_erase_buzz(struct cs40l26_private *cs40l26, int effect_id)
-{
- cs40l26->trigger_indices[effect_id] = 0;
-
return 0;
}
-static int cs40l26_erase_owt(struct cs40l26_private *cs40l26, int effect_id)
+static int cs40l26_erase_owt(struct cs40l26_private *cs40l26,
+ struct cs40l26_uploaded_effect *ueffect)
{
u32 cmd = CS40L26_DSP_MBOX_CMD_OWT_DELETE_BASE;
- struct cs40l26_owt *owt, *owt_tmp;
- u32 index;
+ u32 index = ueffect->trigger_index;
+ struct cs40l26_uploaded_effect *ueffect_tmp;
int ret;
- owt = cs40l26_owt_find(cs40l26, effect_id);
- if (IS_ERR(owt))
- return PTR_ERR(owt);
-
- cmd |= (owt->trigger_index & 0xFF);
+ cmd |= (index & 0xFF);
ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd,
CS40L26_DSP_MBOX_RESET);
@@ -3011,18 +3034,12 @@ static int cs40l26_erase_owt(struct cs40l26_private *cs40l26, int effect_id)
return ret;
/* Update indices for OWT waveforms uploaded after erased effect */
- index = cs40l26->trigger_indices[effect_id];
- list_for_each_entry(owt_tmp, &cs40l26->owt_head, list) {
- if (owt_tmp->trigger_index > index) {
- owt_tmp->trigger_index--;
- cs40l26->trigger_indices[owt_tmp->effect_id]--;
- }
+ list_for_each_entry(ueffect_tmp, &cs40l26->effect_head, list) {
+ if (ueffect_tmp->wvfrm_bank == CS40L26_OWT_BANK_ID &&
+ ueffect_tmp->trigger_index > index)
+ ueffect_tmp->trigger_index--;
}
- cs40l26->trigger_indices[effect_id] = 0;
-
- list_del(&owt->list);
- kfree(owt);
cs40l26->num_owt_effects--;
return 0;
@@ -3032,11 +3049,9 @@ static void cs40l26_erase_worker(struct work_struct *work)
{
struct cs40l26_private *cs40l26 = container_of(work,
struct cs40l26_private, erase_work);
- int ret = 0;
- enum cs40l26_gpio_map mapping;
- int effect_id;
+ struct cs40l26_uploaded_effect *ueffect;
+ int effect_id, ret;
u16 duration;
- u32 index;
ret = cs40l26_pm_enter(cs40l26->dev);
if (ret)
@@ -3045,7 +3060,14 @@ static void cs40l26_erase_worker(struct work_struct *work)
mutex_lock(&cs40l26->lock);
effect_id = cs40l26->erase_effect->id;
- index = cs40l26->trigger_indices[effect_id];
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, effect_id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ dev_err(cs40l26->dev, "No such effect to erase (%d)\n",
+ effect_id);
+ ret = PTR_ERR(ueffect);
+ goto out_mutex;
+ }
+
duration = (cs40l26->erase_effect->replay.length == 0) ?
CS40L26_MAX_WAIT_VIBE_COMPLETE_MS :
cs40l26->erase_effect->replay.length + CS40L26_ERASE_BUFFER_MS;
@@ -3057,8 +3079,8 @@ static void cs40l26_erase_worker(struct work_struct *work)
if (!wait_for_completion_timeout(&cs40l26->erase_cont,
msecs_to_jiffies(duration))) {
ret = -ETIME;
- dev_err(cs40l26->dev, "Failed to erase effect: %d",
- ret);
+ dev_err(cs40l26->dev, "Failed to erase effect (%d)\n",
+ effect_id);
goto pm_err;
}
mutex_lock(&cs40l26->lock);
@@ -3066,20 +3088,23 @@ static void cs40l26_erase_worker(struct work_struct *work)
dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id);
- mapping = cs40l26_map_get(cs40l26, effect_id);
- if (mapping != CS40L26_GPIO_MAP_INVALID) {
- ret = cs40l26_erase_gpi_mapping(cs40l26, mapping);
+ if (ueffect->mapping != CS40L26_GPIO_MAP_INVALID) {
+ ret = cs40l26_erase_gpi_mapping(cs40l26, ueffect->mapping);
if (ret)
goto out_mutex;
+ ueffect->mapping = CS40L26_GPIO_MAP_INVALID;
}
- if (is_owt(index))
- ret = cs40l26_erase_owt(cs40l26, effect_id);
- else if (is_buzz(index))
- ret = cs40l26_erase_buzz(cs40l26, effect_id);
+ if (ueffect->wvfrm_bank == CS40L26_OWT_BANK_ID)
+ ret = cs40l26_erase_owt(cs40l26, ueffect);
- if (ret)
+ if (ret) {
dev_err(cs40l26->dev, "Failed to erase effect: %d", ret);
+ goto out_mutex;
+ }
+
+ list_del(&ueffect->list);
+ kfree(ueffect);
out_mutex:
mutex_unlock(&cs40l26->lock);
@@ -3184,6 +3209,10 @@ static int cs40l26_input_init(struct cs40l26_private *cs40l26)
}
#endif
+ #ifdef CONFIG_DEBUG_FS
+ cs40l26_debugfs_init(cs40l26);
+ #endif
+
cs40l26->vibe_init_success = true;
return ret;
@@ -3755,37 +3784,6 @@ static int cs40l26_bst_ipk_config(struct cs40l26_private *cs40l26)
BIT(CS40L26_IRQ1_BST_IPK_FLAG));
}
-static int cs40l26_owt_setup(struct cs40l26_private *cs40l26)
-{
- u32 reg, offset, base;
- int ret;
-
- INIT_LIST_HEAD(&cs40l26->owt_head);
- cs40l26->num_owt_effects = 0;
-
- ret = cl_dsp_get_reg(cs40l26->dsp, CS40L26_WT_NAME_XM,
- CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, &base);
- if (ret)
- return ret;
-
- ret = cl_dsp_get_reg(cs40l26->dsp, "OWT_NEXT_XM",
- CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, &reg);
- if (ret)
- return ret;
-
- ret = regmap_read(cs40l26->regmap, reg, &offset);
- if (ret) {
- dev_err(cs40l26->dev, "Failed to get wavetable offset\n");
- return ret;
- }
-
- ret = regmap_write(cs40l26->regmap, reg, 0xFFFFFF);
- if (ret)
- dev_err(cs40l26->dev, "Failed to write OWT terminator\n");
-
- return ret;
-}
-
static int cs40l26_lbst_short_test(struct cs40l26_private *cs40l26)
{
struct regmap *regmap = cs40l26->regmap;
@@ -4055,9 +4053,7 @@ static int cs40l26_dsp_config(struct cs40l26_private *cs40l26)
dev_info(dev, "%s loaded with %u RAM waveforms\n", CS40L26_DEV_NAME,
nwaves);
- ret = cs40l26_owt_setup(cs40l26);
- if (ret)
- goto pm_err;
+ cs40l26->num_owt_effects = 0;
value = (cs40l26->comp_enable_redc << CS40L26_COMP_EN_REDC_SHIFT) |
(cs40l26->comp_enable_f0 << CS40L26_COMP_EN_F0_SHIFT);
@@ -4165,64 +4161,70 @@ static int cs40l26_tuning_select_from_svc_le(struct cs40l26_private *cs40l26,
return ret;
}
-static char **cs40l26_get_tuning_names(struct cs40l26_private *cs40l26, int n,
- u32 tuning)
+static char **cs40l26_get_tuning_names(struct cs40l26_private *cs40l26,
+ int *actual_num_files, u32 tuning)
{
- char svc_tuning[CS40L26_TUNING_FILE_NAME_MAX_LEN];
- char wt_tuning[CS40L26_TUNING_FILE_NAME_MAX_LEN];
- char **coeff_files, tuning_str[2];
- int i;
+ char **coeff_files;
+ int i, file_count = 0;
- coeff_files = kcalloc(n, sizeof(char *), GFP_KERNEL);
+ coeff_files = kcalloc(
+ CS40L26_MAX_TUNING_FILES, sizeof(char *), GFP_KERNEL);
if (!coeff_files)
return ERR_PTR(-ENOMEM);
- for (i = 0; i < n; i++) {
+ for (i = 0; i < CS40L26_MAX_TUNING_FILES; i++) {
coeff_files[i] =
kzalloc(CS40L26_TUNING_FILE_NAME_MAX_LEN, GFP_KERNEL);
if (!coeff_files[i])
goto err_free;
}
- strscpy(svc_tuning, CS40L26_SVC_TUNING_FILE_PREFIX,
- CS40L26_TUNING_FILE_NAME_MAX_LEN);
-
- if (tuning) { /* Concatenate tuning number if required */
- strscpy(wt_tuning, CS40L26_WT_FILE_PREFIX,
+ if (tuning) {
+ snprintf(coeff_files[file_count++],
+ CS40L26_TUNING_FILE_NAME_MAX_LEN, "%s%d%s",
+ CS40L26_WT_FILE_PREFIX, tuning,
+ CS40L26_TUNING_FILE_SUFFIX);
+ } else {
+ strscpy(coeff_files[file_count++],
+ CS40L26_WT_FILE_NAME,
CS40L26_TUNING_FILE_NAME_MAX_LEN);
+ }
- snprintf(tuning_str, 2, "%d", tuning);
-
- strncat(wt_tuning, tuning_str, 2);
- strncat(svc_tuning, tuning_str, 2);
-
- strncat(wt_tuning, CS40L26_TUNING_FILE_SUFFIX,
- CS40L26_TUNING_FILE_NAME_MAX_LEN);
+ if (tuning) {
+ snprintf(coeff_files[file_count++],
+ CS40L26_TUNING_FILE_NAME_MAX_LEN, "%s%d%s",
+ CS40L26_SVC_TUNING_FILE_PREFIX, tuning,
+ CS40L26_TUNING_FILE_SUFFIX);
} else {
- strscpy(wt_tuning, CS40L26_WT_FILE_NAME,
- CS40L26_TUNING_FILE_NAME_MAX_LEN);
+ strscpy(coeff_files[file_count++],
+ CS40L26_SVC_TUNING_FILE_NAME,
+ CS40L26_TUNING_FILE_NAME_MAX_LEN);
}
- strncat(svc_tuning, CS40L26_TUNING_FILE_SUFFIX,
+ if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_DVL_ALGO_ID))
+ strscpy(coeff_files[file_count++],
+ CS40L26_DVL_FILE_NAME,
CS40L26_TUNING_FILE_NAME_MAX_LEN);
- strscpy(coeff_files[0], wt_tuning, CS40L26_TUNING_FILE_NAME_MAX_LEN);
- strscpy(coeff_files[1], svc_tuning, CS40L26_TUNING_FILE_NAME_MAX_LEN);
-
if (cs40l26->fw_id == CS40L26_FW_ID) {
- strscpy(coeff_files[2], CS40L26_A2H_TUNING_FILE_NAME,
- CS40L26_TUNING_FILE_NAME_MAX_LEN);
- strscpy(coeff_files[3], CS40L26_DVL_FILE_NAME,
- CS40L26_TUNING_FILE_NAME_MAX_LEN);
+ if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_A2H_ALGO_ID))
+ strscpy(coeff_files[file_count++],
+ CS40L26_A2H_TUNING_FILE_NAME,
+ CS40L26_TUNING_FILE_NAME_MAX_LEN);
} else {
- strscpy(coeff_files[2], CS40L26_CALIB_BIN_FILE_NAME,
+ strscpy(coeff_files[file_count++],
+ CS40L26_CALIB_BIN_FILE_NAME,
CS40L26_TUNING_FILE_NAME_MAX_LEN);
}
+ *actual_num_files = file_count;
return coeff_files;
err_free:
+ for (; i >= 0; i--)
+ kfree(coeff_files[i]);
kfree(coeff_files);
+ *actual_num_files = 0;
return ERR_PTR(-ENOMEM);
}
@@ -4230,17 +4232,15 @@ static int cs40l26_coeff_load(struct cs40l26_private *cs40l26, u32 tuning)
{
struct device *dev = cs40l26->dev;
const struct firmware *coeff;
- int i, ret, num_files;
+ int i, ret, num_files_to_load;
char **coeff_files;
- num_files = (cs40l26->fw_id == CS40L26_FW_ID) ?
- CS40L26_TUNING_FILES_RUNTIME : CS40L26_TUNING_FILES_CAL;
-
- coeff_files = cs40l26_get_tuning_names(cs40l26, num_files, tuning);
+ coeff_files = cs40l26_get_tuning_names(
+ cs40l26, &num_files_to_load, tuning);
if (IS_ERR(coeff_files))
return PTR_ERR(coeff_files);
- for (i = 0; i < num_files; i++) {
+ for (i = 0; i < num_files_to_load; i++) {
ret = request_firmware(&coeff, coeff_files[i], dev);
if (ret) {
dev_warn(dev, "Continuing...\n");
@@ -4606,6 +4606,40 @@ err:
return ret;
}
+static int cs40l26_no_wait_ram_indices_get(struct cs40l26_private *cs40l26,
+ struct device_node *np)
+{
+ int ret, i;
+
+ cs40l26->num_no_wait_ram_indices = of_property_count_u32_elems(np,
+ "cirrus,no-wait-ram-indices");
+
+ if (cs40l26->num_no_wait_ram_indices <= 0)
+ return 0;
+
+ cs40l26->no_wait_ram_indices = devm_kcalloc(cs40l26->dev,
+ cs40l26->num_no_wait_ram_indices, sizeof(u32),
+ GFP_KERNEL);
+ if (!cs40l26->no_wait_ram_indices)
+ return -ENOMEM;
+
+ ret = of_property_read_u32_array(np, "cirrus,no-wait-ram-indices",
+ cs40l26->no_wait_ram_indices,
+ cs40l26->num_no_wait_ram_indices);
+ if (ret)
+ goto err_free;
+
+ for (i = 0; i < cs40l26->num_no_wait_ram_indices; i++)
+ cs40l26->no_wait_ram_indices[i] += CS40L26_RAM_INDEX_START;
+
+ return 0;
+
+err_free:
+ devm_kfree(cs40l26->dev, cs40l26->no_wait_ram_indices);
+ cs40l26->num_no_wait_ram_indices = 0;
+ return ret;
+}
+
static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
{
struct device *dev = cs40l26->dev;
@@ -4784,7 +4818,7 @@ static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
else
cs40l26->pdata.pwle_zero_cross = false;
- return 0;
+ return cs40l26_no_wait_ram_indices_get(cs40l26, np);
}
int cs40l26_probe(struct cs40l26_private *cs40l26,
@@ -4889,6 +4923,9 @@ int cs40l26_probe(struct cs40l26_private *cs40l26,
init_completion(&cs40l26->i2s_cont);
init_completion(&cs40l26->erase_cont);
+ init_completion(&cs40l26->cal_f0_cont);
+ init_completion(&cs40l26->cal_redc_cont);
+ init_completion(&cs40l26->cal_dvl_peq_cont);
if (!cs40l26->fw_defer) {
ret = cs40l26_fw_upload(cs40l26);
@@ -4908,6 +4945,8 @@ int cs40l26_probe(struct cs40l26_private *cs40l26,
if (ret)
goto err;
+ INIT_LIST_HEAD(&cs40l26->effect_head);
+
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cs40l26_devs,
CS40L26_NUM_MFD_DEVS, NULL, 0, NULL);
if (ret) {
@@ -4968,6 +5007,10 @@ int cs40l26_remove(struct cs40l26_private *cs40l26)
#endif
}
+ #ifdef CONFIG_DEBUG_FS
+ cs40l26_debugfs_cleanup(cs40l26);
+ #endif
+
if (cs40l26->input)
input_unregister_device(cs40l26->input);
diff --git a/cs40l26/cs40l26.h b/cs40l26/cs40l26.h
index 69db4a0..4f3e271 100644
--- a/cs40l26/cs40l26.h
+++ b/cs40l26/cs40l26.h
@@ -35,6 +35,7 @@
#include <linux/sysfs.h>
#include <linux/bitops.h>
#include <linux/pm_runtime.h>
+#include <linux/debugfs.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -707,7 +708,13 @@
#define CS40L26_SVC_ALGO_ID 0x0001F207
#define CS40L26_VIBEGEN_ALGO_ID 0x000100BD
#define CS40L26_LOGGER_ALGO_ID 0x0004013D
+#define CS40L26_EVENT_LOGGER_ALGO_ID 0x0004F222
#define CS40L26_EXT_ALGO_ID 0x0004013C
+#define CS40L26_DVL_ALGO_ID 0x00040140
+
+/* DebugFS */
+#define CS40L26_ALGO_ID_MAX_STR_LEN 12
+#define CS40L26_NUM_DEBUGFS 3
/* power management */
#define CS40L26_PSEQ_ROM_END_OF_SCRIPT 0x028003E8
@@ -830,15 +837,17 @@
#define CS40L26_DSP_MBOX_REDC_EST_DONE 0x07000022
#define CS40L26_DSP_MBOX_LE_EST_START 0x07000014
#define CS40L26_DSP_MBOX_LE_EST_DONE 0x07000024
+#define CS40L26_DSP_MBOX_PEQ_CALCULATION_START 0x07000018
+#define CS40L26_DSP_MBOX_PEQ_CALCULATION_DONE 0x07000028
#define CS40L26_DSP_MBOX_SYS_ACK 0x0A000000
#define CS40L26_DSP_MBOX_PANIC 0x0C000000
+#define CS40L26_DSP_MBOX_WATERMARK 0x0D000000
/* Firmware Mode */
#define CS40L26_FW_FILE_NAME "cs40l26.wmfw"
#define CS40L26_FW_CALIB_NAME "cs40l26-calib.wmfw"
-#define CS40L26_TUNING_FILES_RUNTIME 4
-#define CS40L26_TUNING_FILES_CAL 3
+#define CS40L26_MAX_TUNING_FILES 5
#define CS40L26_WT_FILE_NAME "cs40l26.bin"
#define CS40L26_WT_FILE_PREFIX "cs40l26-wt"
@@ -858,10 +867,10 @@
#define CS40L26_SVC_DT_PREFIX "svc-le"
#define CS40L26_FW_ID 0x1800D4
-#define CS40L26_FW_MIN_REV 0x07021C
+#define CS40L26_FW_MIN_REV 0x07022B
#define CS40L26_FW_BRANCH 0x07
#define CS40L26_FW_CALIB_ID 0x1800DA
-#define CS40L26_FW_CALIB_MIN_REV 0x010014
+#define CS40L26_FW_CALIB_MIN_REV 0x010123
#define CS40L26_FW_CALIB_BRANCH 0x01
#define CS40L26_FW_MAINT_MIN_REV 0x270216
#define CS40L26_FW_MAINT_BRANCH 0x27
@@ -910,22 +919,26 @@
#define CS40L26_RAM_BANK_ID 0
#define CS40L26_ROM_BANK_ID 1
#define CS40L26_OWT_BANK_ID 2
+#define CS40L26_BUZ_BANK_ID 3
#define CS40L26_BUZZGEN_CONFIG_OFFSET 12
#define CS40L26_BUZZGEN_NUM_CONFIGS (CS40L26_BUZZGEN_INDEX_END - \
CS40L26_BUZZGEN_INDEX_START)
+
#define CS40L26_BUZZGEN_INDEX_START 0x01800080
#define CS40L26_BUZZGEN_INDEX_CP_TRIGGER 0x01800081
#define CS40L26_BUZZGEN_INDEX_END 0x01800085
+
#define CS40L26_BUZZGEN_FREQ_MAX 250 /* Hz */
#define CS40L26_BUZZGEN_FREQ_MIN 100
-#define CS40L26_BUZZGEN_PERIOD_MAX 10 /* ms */
-#define CS40L26_BUZZGEN_PERIOD_MIN 4
+
+#define CS40L26_BUZZGEN_PER_MAX 10 /* ms */
+#define CS40L26_BUZZGEN_PER_MIN 4
+
#define CS40L26_BUZZGEN_DURATION_OFFSET 8
#define CS40L26_BUZZGEN_DURATION_DIV_STEP 4
-#define CS40L26_BUZZGEN_LEVEL_OFFSET 4
-#define CS40L26_BUZZGEN_LEVEL_DEFAULT 0x50
+#define CS40L26_BUZZGEN_LEVEL_OFFSET 4
#define CS40L26_BUZZGEN_LEVEL_MIN 0x00
#define CS40L26_BUZZGEN_LEVEL_MAX 0xFF
@@ -1217,6 +1230,8 @@
#define CS40L26_Q_EST_MIN 0
#define CS40L26_Q_EST_MAX 0x7FFFFF
+#define CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS 6
+
#define CS40L26_F0_EST_FREQ_SCALE 16384
#define CS40L26_SVC_INITIALIZATION_PERIOD_MS 6
@@ -1226,6 +1241,7 @@
#define CS40L26_F0_CHIRP_DURATION_FACTOR 3750
#define CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q BIT(0)
#define CS40L26_CALIBRATION_CONTROL_REQUEST_REDC BIT(1)
+#define CS40L26_CALIBRATION_CONTROL_REQUEST_DVL_PEQ BIT(3)
#define CS40L26_F0_FREQ_SPAN_MASK GENMASK(23, 0)
#define CS40L26_F0_FREQ_SPAN_SIGN BIT(23)
@@ -1250,6 +1266,9 @@
#define CS40L26_UINT_24_BITS_MAX 16777215
+#define CS40L26_CALIBRATION_TIMEOUT_MS 2000
+
+
/* Compensation */
#define CS40L26_COMP_EN_REDC_SHIFT 1
#define CS40L26_COMP_EN_F0_SHIFT 0
@@ -1466,12 +1485,13 @@ struct cs40l26_platform_data {
bool pwle_zero_cross;
};
-struct cs40l26_owt {
- int effect_id;
+struct cs40l26_uploaded_effect {
+ int id;
u32 trigger_index;
+ u16 wvfrm_bank;
+ enum cs40l26_gpio_map mapping;
struct list_head list;
};
-
struct cs40l26_private {
struct device *dev;
struct regmap *regmap;
@@ -1482,8 +1502,8 @@ struct cs40l26_private {
struct gpio_desc *reset_gpio;
struct input_dev *input;
struct cl_dsp *dsp;
- unsigned int trigger_indices[FF_MAX_EFFECTS];
- int gpi_ids[CS40L26_GPIO_MAP_NUM_AVAILABLE];
+ struct list_head effect_head;
+ unsigned int cur_index;
struct ff_effect *trigger_effect;
struct ff_effect upload_effect;
struct ff_effect *erase_effect;
@@ -1511,7 +1531,6 @@ struct cs40l26_private {
bool asp_enable;
u8 last_wksrc_pol;
u8 wksrc_sts;
- struct list_head owt_head;
int num_owt_effects;
int cal_requested;
u16 gain_pct;
@@ -1529,8 +1548,20 @@ struct cs40l26_private {
bool comp_enable_f0;
struct completion i2s_cont;
struct completion erase_cont;
+ struct completion cal_f0_cont;
+ struct completion cal_redc_cont;
+ struct completion cal_dvl_peq_cont;
u8 vpbr_thld;
unsigned int svc_le_est_stored;
+ u32 *no_wait_ram_indices;
+ ssize_t num_no_wait_ram_indices;
+ #ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs_root;
+ char *dbg_fw_ctrl_name;
+ u32 dbg_fw_algo_id;
+ bool dbg_fw_ym;
+ struct cl_dsp_debugfs *cl_dsp_db;
+ #endif
};
struct cs40l26_codec {
@@ -1597,6 +1628,7 @@ bool cs40l26_readable_reg(struct device *dev, unsigned int reg);
bool cs40l26_volatile_reg(struct device *dev, unsigned int reg);
int cs40l26_pseq_write(struct cs40l26_private *cs40l26, u32 addr,
u32 data, bool update, u8 op_code);
+int cs40l26_copy_f0_est_to_dvl(struct cs40l26_private *cs40l26);
/* external tables */
extern const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1];
@@ -1616,4 +1648,11 @@ extern struct attribute_group cs40l26_dev_attr_group;
extern struct attribute_group cs40l26_dev_attr_cal_group;
extern struct attribute_group cs40l26_dev_attr_dbc_group;
+/* debugfs */
+#ifdef CONFIG_DEBUG_FS
+void cs40l26_debugfs_init(struct cs40l26_private *cs40l26);
+void cs40l26_debugfs_cleanup(struct cs40l26_private *cs40l26);
+
+#endif
+
#endif /* __CS40L26_H__ */
diff --git a/cs40l26/cs40l26.yaml b/cs40l26/cs40l26.yaml
new file mode 100644
index 0000000..f9f796b
--- /dev/null
+++ b/cs40l26/cs40l26.yaml
@@ -0,0 +1,503 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 0.2
+---
+$id: http://devicetree.org/schemas/bindings/input/cs40l26.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic CS40L26 Boosted Haptic Amplifier
+
+maintainers:
+ - fred.treven@cirrus.com
+
+description: |
+ CS40L26 is a Boosted Haptic Driver with Integrated DSP and Waveform Memory
+ with Advanced Closed Loop Algorithms and LRA protection
+
+properties:
+ compatible:
+ enum:
+ - cirrus,cs40l26a
+ - cirrus,cs40l26b
+ - cirrus,cs40l27a
+ - cirrus,cs40l27b
+
+ reg:
+ maxItems: 1
+
+ cirrus,basic-config:
+ description:
+ Boolean which, if present, will launch the device in basic (ROM) mode.
+ If this property is not included, the device will boot into full
+ featured (RAM) mode.
+ (Default) RAM mode
+ type: boolean
+
+ cirrus,vbbr-enable:
+ description:
+ Boolean to determine whether or not VBST brownout prevention is enabled.
+ (Default) Disabled
+ type: boolean
+
+ cirrus,vbbr-thld-mv:
+ description:
+ Boost converter brownout prevention delta voltage threshold represented
+ in milivolts. Brownout prevention mode is entered if the condition
+ "VBST(target) - VBST(actual) <= VBBR_THLD" is met.
+ The possible VBST threshold values range from 109 mV to 3445 mV
+ in increments of 55 mV.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 109
+ maximum: 3445
+ default: 273
+
+ cirrus,vbbr-max-att-db:
+ description:
+ Maximum attenuation that the reactive VBST brownout can apply to the
+ signal. Accepted values range from 0 dB to 15 dB with a 1 dB step size.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0xF
+ default: 0x9
+
+ cirrus,vbbr-atk-step:
+ description:
+ VBST brownout prevention attack step size. Configures the VBST brownout
+ prevention attacking attenuation step size when operating in either
+ digital volume or analog gain modes. There are 8 possible attack step
+ values; they are relative to triggering VBBR_1/VBBR_2/VBBR_3.
+ The potential values are as follows
+ 0x0 = 0.0625 dB / 0.125 dB / 0.250 dB
+ 0x1 = 0.125 dB / 0.250 dB / 0.500 dB (default)
+ 0x2 = 0.250 dB / 0.500 dB / 1.000 dB
+ 0x3 = 0.500 dB / 1.000 dB / 2.000 dB
+ 0x4 = 0.750 dB / 1.500 dB / 3.000 dB
+ 0x5 = 1.000 dB / 2.000 dB / 4.000 dB
+ 0x6 = 1.250 dB / 2.500 dB / 5.000 dB
+ 0x7 = 1.500 dB / 3.000 dB / 6.000 dB
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0x7
+ default: 0x1
+
+ cirrus,vbbr-atk-rate:
+ description:
+ Attenuation attack step rate. Configures the amount of delay between
+ consecutive volume attenuation steps when a brownout condition is
+ present and the VBST brownout condition is in an attacking state. The
+ attack rate can be configured to 8 discrete values.
+ 0x0 = 2.5 us
+ 0x1 = 5 us
+ 0x2 = 10 us (default)
+ 0x3 = 25 us
+ 0x4 = 50 us
+ 0x5 = 100 us
+ 0x6 = 250 us
+ 0x7 = 500 us
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0x7
+ default: 0x2
+
+ cirrus,vbbr-wait:
+ description:
+ Configures the delay between a brownout condition no longer being
+ present and the VBST brownout prevention entering an attenuation release
+ state. The parameter can be set to 4 discrete values.
+ 0x0 = 10 ms
+ 0x1 = 100 ms (default)
+ 0x2 = 250 ms
+ 0x3 = 500 ms
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0x3
+ default: 0x1
+
+ cirrus,vbbr-rel-rate:
+ description:
+ Attenuation release step rate. Configures the delay between consecutive
+ volume attenuation release steps when a brownout condition is no longer
+ present and the VBST brownout prevention is in an attenuation release
+ state. The parameter can be set to 8 discrete values.
+ 0x0 = 5 ms
+ 0x1 = 10 ms
+ 0x2 = 25 ms
+ 0x3 = 50 ms
+ 0x4 = 100 ms
+ 0x5 = 250 ms (default)
+ 0x6 = 500 ms
+ 0x7 = 1000 ms
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0x7
+ default: 0x5
+
+ cirrus,vpbr-enable:
+ description:
+ Boolean to determine whether or not VP brownout prevention is enabled.
+ (Default) Disabled
+ type: boolean
+
+ cirrus,vpbr-thld-mv:
+ description:
+ Battery voltage (VP) brownout prevention threshold represented in
+ milivolts. Brownout prevention mode is entered if VP falls below
+ VPBR_THLD. The possible VP threshold values range from 2497 mV
+ to 3874 mV in increments of 47 mV.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 2497
+ maximum: 3874
+ default: 2639
+
+ cirrus,vpbr-max-att-db:
+ description:
+ Maximum attenuation that the VP brownout prevention can apply to the
+ signal. Accepted values range from 0 dB to 15 dB with a 1 dB step size.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0xF
+ default: 0x9
+
+ cirrus,vpbr-atk-step:
+ description:
+ VP brownout prevention attack step size. Configures the VP brownout
+ prevention attacking attenuation step size when operating in either
+ digital volume or analog gain modes. There are 8 possible attack step
+ values; they are relative to triggering VPBR_1/VPBR_2/VPBR_3.
+ The potential values are as follows
+ 0x0 = 0.0625 dB / 0.125 dB / 0.250 dB
+ 0x1 = 0.125 dB / 0.250 dB / 0.500 dB (default)
+ 0x2 = 0.250 dB / 0.500 dB / 1.000 dB
+ 0x3 = 0.500 dB / 1.000 dB / 2.000 dB
+ 0x4 = 0.750 dB / 1.500 dB / 3.000 dB
+ 0x5 = 1.000 dB / 2.000 dB / 4.000 dB
+ 0x6 = 1.250 dB / 2.500 dB / 5.000 dB
+ 0x7 = 1.500 dB / 3.000 dB / 6.000 dB
+ minimum: 0x0
+ maximum: 0x7
+ default: 0x1
+
+ cirrus,vpbr-atk-rate:
+ description:
+ Attenuation attack step rate. Configures the amount of delay between
+ consecutive volume attenuation steps when a brownout condition is
+ present and the VP brownout condition is in an attacking state. The
+ attack rate can be configured to 8 discrete values.
+ 0x0 = 2.5 us
+ 0x1 = 5 us
+ 0x2 = 10 us (default)
+ 0x3 = 25 us
+ 0x4 = 50 us
+ 0x5 = 100 us
+ 0x6 = 250 us
+ 0x7 = 500 us
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0x7
+ default: 0x2
+
+ cirrus,vpbr-wait:
+ description:
+ Configures the delay between a brownout condition no longer being
+ present and the VP brownout prevention entering an attenuation release
+ state. The parameter can be set to 4 discrete values.
+ 0x0 = 10 ms
+ 0x1 = 100 ms (default)
+ 0x2 = 250 ms
+ 0x3 = 500 ms
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0x3
+ default: 0x1
+
+ cirrus,vpbr-rel-rate:
+ description:
+ Attenuation release step rate. Configures the delay between consecutive
+ volume attenuation release steps when a brownout condition is no longer
+ present and the VP brownout prevention is in an attenuation release
+ state. The parameter can be set to 8 discrete values.
+ 0x0 = 5 ms
+ 0x1 = 10 ms
+ 0x2 = 25 ms
+ 0x3 = 50 ms
+ 0x4 = 100 ms
+ 0x5 = 250 ms (default)
+ 0x6 = 500 ms
+ 0x7 = 1000 ms
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0x0
+ maximum: 0x7
+ default: 0x5
+
+ cirrus,bst-dcm-en:
+ description:
+ Boost converter automatic DCM Mode enable. This enables the digital
+ boost converter to operate in a low power (DCM) mode during low loading
+ conditions.
+ 0 = Boost converter automatic low power mode disabled (DISABLED)
+ 1 = Boost converter automatic low power mode enabled (ENABLED)
+ type: boolean
+
+ cirrus,bst-ipk-microamp:
+ description:
+ Maximum amount of current that can be drawn by the device's boost
+ converter in uA. Accepted values are between 1600000 uA and 4800000 uA in
+ 50000 uA increments.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 1600000
+ maximum: 4800000
+ default: 4500000
+
+ cirrus,boost-ctl-microvolt:
+ description:
+ Maximum target voltage to which the class H algorithm may increase the
+ VBST supply, expressed in uV. Valid values range from 2550000 to 11000000
+ (inclusive) in steps of 50000. If this value is specified as zero or VP
+ rises above this value, VBST is bypassed to VP. If this value is omitted,
+ the maximum target voltage remains at 11 V.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 2550000
+ maximum: 11000000
+ default: 11000000
+
+ cirrus,f0-default:
+ description:
+ Default LRA resonant frequency (f0), expressed as follows
+ cirrus,f0-default = f0 (Hz) * 2^14.
+ This value represents the frequency used during playback of PWLE segments
+ specified with frequency equal to f0; it also serves as the unit-specific
+ f0 input to the click compensation algorithm. It can be overwritten at a
+ later time by writing to the f0_stored sysfs control.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 819200
+ maximum: 8372224
+ default: 2621440
+
+ cirrus,redc-default:
+ description:
+ Default LRA series resistance (ReDC), expressed as follows
+ cirrus,redc-default = ReDC (ohms) * 2^15.
+ This value represents the unit-specific ReDC input to the click compensation
+ algorithm. It can be overwritten at a later time by writing to the redc_stored
+ sysfs control.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 16777215
+ default: 340787
+
+ cirrus,q-default:
+ description:
+ Default LRA Q factor (silicon revision B1 only), expressed as follows
+ cirrus,q-default = Q * 2^16.
+ This value represents the unit-specific Q factor used to select the correct
+ sub-waveform for Q-dependent wavetable indexes. It can be overwritten at a
+ later time by writing to the q_stored sysfs control. If this value is omitted
+ or specified as zero, a default value of 27 is used.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 8388607
+ default: 1769472
+
+ cirrus,asp-gain-scale-pct:
+ description:
+ Scaling applied to gain for ASP streaming if present and less or equal to
+ 100.
+ The value given is in percent between 0 - 100 with 100 being full scale
+ and 0 being mute.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 100
+ default: 100
+
+ svc-le:
+ description:
+ Range of LE values, defined by "cirrus,min" and "cirrus,max" in Henries
+ using Q0.23 number format, that determines which SVC and Wavetable tuning
+ files should be loaded by the driver. The files must be named
+ "cs40l26-svcX.bin" for SVC tunings and "cs40l26-wtX.bin" for wavetables,
+ where 'X' corresponds to the value given to "cirrus,index".
+ The optional property "cirrus,gain-adjust" is used to alter the global
+ digital gain based on which LRA is being used. This value has a range of
+ -100 to +100 and will change the digital gain percentage by this amount.
+ Note that the full gain value must still be valid, the maximum is 100% and
+ the minimum is 0%.
+ If the LE value found for the attached LRA does not match a provided
+ range, or if these properties are omitted/malformed, the default tunings
+ ("cs40l26-svc.bin" and "cs40l26.bin") will be loaded.
+ See examples section for how to use these controls.
+
+ cirrus,pm-active-timeout-ms:
+ description:
+ Time (in milliseconds) it takes for the DSP to transition from active
+ mode to standby mode.
+ $ref: "/schemas/type.yaml#/definitions/uint32"
+ minimum: 100
+ maximum: 10000
+ default: 250
+
+ cirrus,pm-stdby-timeout-ms:
+ description:
+ Time (in milliseconds) it takes for the DSP to transition from standby
+ mode to hibernate mode.
+ $ref: "/schemas/type.yaml#/definitions/uint32"
+ minimum: 100
+ maximum: 10000
+ default: 100
+
+ cirrus,bst-expl-mode-disable:
+ description:
+ Disable boost exploratory mode if this boolean is present in the
+ devicetree. Boost exploratory mode allows the device to overshoot
+ the set peak current limit. This has potential to damage the boost
+ inductor. Disabling this mode will prevent this from happening; it will
+ also prevent the device from detecting boost inductor short errors.
+ (Default) Enabled
+ type: boolean
+
+ cirrus,dbc-enable:
+ description:
+ If present, Dynamic Boost Control will be enabled automatically after
+ firmware is loaded.
+ (Default) Disabled
+ type: boolean
+
+ cirrus,dbc-env-rel-coef:
+ description:
+ Envelope release time represented as a scaling coefficient; Q0.23 format.
+ See Cirrus Logic release notes for more information on how to program
+ this parameter. Maximum is 1 second, default is 999.5 ms.
+ $ref: "/schemas/type.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 8388607
+ default: 8384414
+
+ cirrus,dbc-fall-headroom:
+ description:
+ Headroom for falling envelope in Dynamic Boost Control. Value is a ratio
+ of full-scale (12.3 V) and is in Q0.23 format. To program a headroom of x
+ Volts, the following formula is used. (x/12.3) * 2^(23). Maximum is 12.3
+ V, default is 1.1 V.
+ $ref: "/schemas/type.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 8388607
+ default: 750193
+
+ cirrus,dbc-rise-headroom:
+ description:
+ Headroom for rising envelope in Dynamic Boost Control. Value is a ratio
+ of full-scale (12.3 V) and is in Q0.23 format. To program a headroom of x
+ Volts, the following formula is used. (x/12.3) * 2^(23). Maximum is 12.3
+ V, default is 2.1 V.
+ $ref: "/schemas/type.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 8388607
+ default: 1432204
+
+ cirrus,dbc-tx-lvl-hold-off-ms:
+ description:
+ Amount of time that the input signal must be lower than the threshold
+ (TX_LVL_THRESH, programmed via dbc-tx-lvl-thresh-fs). The algorithm
+ registers this as silence. The signal rising back to the threshold will
+ trigger an immediate rise to the max boost level.
+ The value is provided in milliseconds with a maximum of 1 second.
+ $ref: "/schemas/type.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 1000
+ default: 10
+
+ cirrus,dbc-tx-lvl-thresh-fs:
+ description:
+ Full-scale threshold used by the rise edge detector in the Dynamic Boost
+ Control algorithm. When silence is detected and the signal rises above
+ this threshold an immediate rise to the max boost level is triggered.
+ The value must be provided as a percentage of full scale and is in Q0.23
+ format. To program a threshold of x dBFs use the following formula.
+ 10^(x/20) * 2^(23). Default value is -80 dBFs.
+ $ref: "/schemas/type.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 8388607
+ default: 839
+
+ cirrus,pwle-zero-cross-en:
+ description:
+ Boolean to ensure that PWLE waveforms always end on a zero crossing,
+ extending the length of the waveform if necessary.
+ (Default) Disabled
+ type: boolean
+
+ cirrus,calib-fw:
+ description:
+ Boolean to load calibration firmware ("cs40l26-calib.wmfw")
+ instead of runtime firmware at boot time. Allows user to avoid
+ using the swap_firmware sysfs control to load calibration firmware.
+ (Default) Runtime firmware loaded at boot
+ type: boolean
+
+cirrus,fw-defer:
+ description:
+ Boolean to probe and register the driver without loading RAM firmware.
+ This puts the driver in a partially usable state.
+ To gain full functionality, write the designated values for
+ runtime (0) or calibration (1) firmware to the swap_firmware
+ sysfs control.
+ (Default) Firmware is loaded when driver probes
+ type: boolean
+
+cirrus,no-wait-ram-indices:
+ description:
+ List of RAM indices that will not use the delay_before_stop_playback_us
+ prior to sending a STOP_PLAYBACK command.
+
+required:
+ - compatible
+
+unevaluatedProperties: false
+
+examples:
+ - |
+
+ &i2c0 {
+ cs40l26: cs40l26@58 {
+ compatible = "cirrus,cs40l26";
+ reg = <0x58>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <57 8>;
+ reset-gpios = <&gpio0 54 0>;
+ VA-supply = <&dummy_vreg>;
+ VP-supply = <&dummy_vreg>;
+
+ cirrus,vbbr-enable;
+ cirrus,vbbr-thld-mv = <300>;
+ cirrus,vpbr-enable;
+ cirrus,vpbr-thld-mv = <2592>;
+
+ cirrus,dbc-enable;
+ cirrus,dbc-env-rel-coef = <8377777>; // 16 ms
+ cirrus,dbc-fall-headroom = <750000>; // 1.09 V
+ cirrus,dbc-rise-headroom = <1400000>; // 2.05 V
+ cirrus,dbc-tx-lvl-hold-off-ms = <50>; // 50 ms
+ cirrus,dbc-tx-lvl-thresh-fs = <800>; // -80.4 dBFs
+
+ cirrus,asp-gain-scale-pct = <30>;
+
+ cirrus,no-wait-ram-indices = <5 10 25>;
+
+ svc-le1 {
+ cirrus,min = <500>; //59.6 uH
+ cirrus,max = <750>; //89.4 uH
+ cirrus,index = <1>;
+ cirrus,gain-adjust = "-5";
+ };
+ svc-le2 {
+ cirrus,min = <1000>; //119.2 uH
+ cirrus,max = <1500>; //178.8 uH
+ cirrus,index = <2>;
+ cirrus,gain-adjust = "10";
+ };
+ svc-le3 {
+ cirrus,min = <2000>; //238.4 uH
+ cirrus,max = <2400>; //286.1 uH
+ cirrus,index = <3>;
+ cirrus,gain-adjust = "20";
+ };
+ };
+};