diff options
author | Tai Kuo <taikuo@google.com> | 2023-01-19 15:44:35 +0800 |
---|---|---|
committer | Tai Kuo <taikuo@google.com> | 2023-02-17 02:32:58 +0000 |
commit | 8c576c450604b70c7b1170803b039ed0b172b1a4 (patch) | |
tree | 13f3091cc92a03d29a9545339a99bcf6bfdf40e5 | |
parent | c3163996fedeb44a64f865a23a150b5cdaeb8a5a (diff) | |
download | amplifiers-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/Makefile | 6 | ||||
-rw-r--r-- | cs40l26/cl_dsp-debugfs.c | 526 | ||||
-rw-r--r-- | cs40l26/cl_dsp.c | 29 | ||||
-rw-r--r-- | cs40l26/cl_dsp.h | 70 | ||||
-rw-r--r-- | cs40l26/cs40l26-debugfs.c | 251 | ||||
-rw-r--r-- | cs40l26/cs40l26-sysfs.c | 143 | ||||
-rw-r--r-- | cs40l26/cs40l26.c | 813 | ||||
-rw-r--r-- | cs40l26/cs40l26.h | 67 | ||||
-rw-r--r-- | cs40l26/cs40l26.yaml | 503 |
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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); 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, ®); - 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"; + }; + }; +}; |