summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTai Kuo <taikuo@google.com>2021-04-06 17:54:30 +0800
committerTreeHugger Robot <treehugger-gerrit@google.com>2021-04-19 13:30:06 +0000
commitb8e1f704583793bcc507616f50e4dc57ccda42df (patch)
tree0fcb74bc080b2d44ebba5ee66b7cd622718ecfe5
parenta34e0b6b624043b7cfa1ba90dbcb793e9102172e (diff)
downloadamplifiers-b8e1f704583793bcc507616f50e4dc57ccda42df.tar.gz
cs40l26: snap driver modules from CirrusLogic
Branch: v5.10-cs40l26 Tag: cs40l26-RC1v5_5.10 Files: drivers/input/misc/cs40l26*.[ch] sound/soc/codecs/cs40l26.c -> cs40l26-codec.c include/linux/mfd/cs40l26.h Commits: 48008bf7e951 input: cs40l26: Allow MFD to number children a466efc1776a input: cs40l26: Configure some ASP controls in core driver 3dd2be5060ce ASoC: cs40l26: Do not access firmware controls in codec_probe 0d8255c507fc input: cs40l26: Remove function for coeff. file loading abb4c12fee99 input: cs40l26: Reserve cs40l26_ack_read for polling a907b2432e82 input: cs40l26: Increment A0 minimum firmware revision to 5.0.4 43ae62e013fd input: cs40l26: Add minimum revision value for A1 firmware 59e7425816a7 input: cs40l26: Verify firmware in core driver 9709db269456 input: cs40l26: Support PWLE waveforms in Open Wavetable a9dfa6d32ac8 input: cs40l26: Fix misspelled variable name 951630cb6608 ASoC: cs40l26: Reverse logic for using tdm_width 40ae66e93fd1 input: cs40l26: Add Open Wavetable Support 0a674a1ccfea input: cs40l26: Add support for pseq v2 d749863a9703 input: cs40l26: Add errata write to fix indeterminate PLL lock time 32ecacc32a36 input: cs40l26: Add support for A1 chip revision e4c3a094ecdf input: cs40l26: Convert mailbox queue to use registers in wmfw 1b193fad6fc1 input: cs40l26: Ensure DSP can accept mailbox commands 1675126b9e1a input: cs40l26: Wait to enable pm runtime controls until firmware ready 9dae893f3448 input: cs40l26: Improvements to pm_state_transition 96e1be15046b ASoC: cs40l26: Wake device when probing codec driver. a9444c9b2c77 input: cs40l26: Change some instances of dsp_read to regmap_read c4b71016240d ASoC: cs40l26: Switch A2H tunings at runtime 1629fb45fec6 input: cs40l26: Re-enable I2S stream after haptic effect ffc4e2ccfe95 ASoC: cs40l26: Add ASP vibe state enum 8733e2547135 input: cs40l26: Improve vibe_state tracking a5b0a135f849 ASoC: cs40l26: Enable Class H tracking during ASP playback 56a8557861dd input: cs40l26: Function to set Class H 8817c80d021f ASoC: cs40l26: Add Option to Disable ASP Playback 98bf94b1df98 input: cs40l26: Changes to PM hibernate/shutdown timeout 1a09000c60d8 input: cs40l26: Remove Firmware ID check after state transition 1b5a5b26e9fe input: cs40l26: Use LAST_WAKESRC_CTL to determine GPIO status 7407cb8ffc91 input: cs40l26: Add set_gain callback 67095dcbc5ca input: cs40l26: Share of_device_id table between SPI/I2C drivers 268c9da5bcfd ASoC: cs40l26: Add set_tdm_slot callback 919ab920876b ASoC: cs40l26: Add TDM Support 77baad927c8d ASoC: cs40l26: Remove redundant stream direction check 830c4e0b5618 ASoC: cs40l26: Don't write DAI format directly from cs40l26_set_dai_fmt f938eac74f10 ASoC: cs40l26: Remove incorrect usage of params_physical_width f0b8a8823f06 ASoC: cs40l26: Remove unused ASP Switch 468770e2856f ASoC: cs40l26: Use PLL Open Loop when changing REFCLK 37a598f9bac0 input: cs40l26: Enable IRQ after error a81070994853 input: cs40l26: Enable CS40L26 Codec in core driver fc48e705a381 ASoC: cs40l26: Add CS40L26 Stub Codec Driver e731f6adaac6 input: cs40l26: Share regmap config between I2C and SPI drivers. 1aacdf180f91 input: cs40l26: Don't build core driver module 6d75aa2d5dc0 input: cs40l26: Remove extra space in function prototype 8d287c195371 Documentation: cs40l26: Add CS40L26A/B devicetree options 74867e2b5f25 input: cs40l26: Add CS40L26A/B devicetree options 0e3bcf4b2e52 input: cs40l26: Read entire mailbox queue upon DSP write ca583f106052 input: cs40l26: Add Runtime Power Management Support 25b5096dc06d input: cs40l26: Change include location of CL DSP 3884ddc083c2 input: cs40l26: Configure wakesource IRQs based on device settings 5b7430bf8db5 input: cs40l26: Reject indeces exceeding number of loaded waveforms b664bc4faa40 input: cs40l26: Changes to sysfs controls f7322edc1820 input: cs40l26: Function to read DSP state 2e1dc1d42ab4 input: cs40l26: Update Copyright Year ea17111bb00b input: cs40l26: Add debug message for completed playback 17457b94d942 input: cs40l26: Enable DSP Shutdown and Wakeup Commands 705aa77f234c input: cs40l26: Update Start Up Procedure for A0 Silicon a42bac99c20b input: cs40l26: Remove cs40l26_dsp_shutdown() 89fba3c7e7e1 input: cs40l26: Updates to power on sequence 4123501cf9d0 input: cs40l26: Clear Interrupt after error f2b73020a677 input: cs40l26: Change IRQ notificatios to dev_dbg() 2be81af5037e input: cs40l26: Change msleep() to usleep_range() fd8c8f5ab3b0 input: cs40l26: Disable VA and VP Regulators Separately 2175609afa4e input: cs40l26: Add support for CS40L26A/B c519e1b08467 input: cs40l26: Add Support for SVC tuning 2b87832f2b0f input: cs40l26: Decrease ROM waveform bank granularity 3df8b4237b2e input: cs40l26: Graceful shutdown when handling BST errors 92a3eff7ff40 input: cs40l26: Add Support for Coefficient File Loading f67aaddce758 input: cs40l26: Correct BUZZGEN duration setting f25e0fd73654 input: cs40l26: Improve efficiency of Interrupt Handling c31b95210834 input: cs40l26: Fix error release sequence 2f8d22e816a6 input: cs40l26: Add IRQ unmask function 875e70684e57 Documentation: cs40l26: Add Support for RAM firmware 96b59a7edb94 input: cs40l26: Add Support for RAM firmware d86abc93be70 Documentation: cs40l26: Add dt-bindings file for CS40L26 71f39122dae9 input: cs40l26: Initial Commit for CS40L26 Branch: v5.10-cirrus-dsp-fw Tag: cl-dsp-fw-v3.1.0_5.10 Files: drivers/firmware/cirrus/cl_dsp.c include/linux/firmware/cirrus/cl_dsp.h Commits: 47254bf6e2c2 firmware: cirrus: Ignore Silicon Revision when looking for wavetable f31f14932f72 firmware: cirrus: Warn user about mismatched revisions 97963c7898f1 firmware: cirrus: Let core driver handle revision checking 5ebd6b1cdf42 firmware: cirrus: Add Open Wavetable Support 6149c55ae03e firmware: cirrus: Ignore chip revision when verifying algo id d5273a4a9ffd firmware: cirrus: Remove check before debug statement. 672fc70f5c74 firmware: cirrus: Move CL DSP to drivers/firmware f922454891ef input: cl_dsp: Updates to coefficient descriptor list 072f7252b854 input: cl_dsp: Macros for algorithm and firmware revision cd2c76181a67 input: cl_dsp: Improve Coefficient File Parsing 7f9340b2186d input: cl_dsp: Improve Algorithm Parsing 60323ea6b804 input: cl_dsp: Standardize HALO registers 33ae8822e6ca input: cl_dsp: Parse coeff flags and length 9485dc94122c input: cl_dsp: Process Data in WMFW Header 0af257e7d6c7 input: cl_dsp: Require regmap pointer when creating struct d5db51b265af input: cl_dsp: Increase maximum number of algorithms b01a1e0e0219 input: cl_dsp: Option to skip writing to RAM c7811f4beccb input: cl_dsp: Improve firmware data parsing 276542f62ced input: cl_dsp: Move includes to .h file ebd97015fa68 input: cl_dsp: Improved memory management d6bcce379fb0 input: cl_dsp: Move .h file to mfd 6e8352d35ba2 input: cl_dsp: Ensure packed data is aligned to 4 words ff235ec69691 input: cl_dsp: Catch additional errors in get_reg function eb65b8e79dbb input: cl_dsp: Support packed types in coefficient files 9f7125dca680 input: cl_dsp: Add support for wavetable date info a4515a386b9c input: cl_dsp: Add support for expliclty freeing coefficient memory fbed7662b3be input: cl_dsp: Add Support for Generic Coefficient Files 2a234316bcae input: cl_dsp: Initialize return variable d5678c49af94 input: cl_dsp: Add support for unpacked types in WMFW 7d7857d11bc9 input: cl_dsp: Add text to Kconfig for cl_dsp 2791c97e5554 input: cl_dsp: Add support for wavetable loading a728fda6c333 input: cl_dsp: Fix typo in error message f85e18f4ae46 input: cl_dsp: Add helper function for firmware parsing fd5b30bc28a7 input: cl_dsp: DSP firmware loading for input devices Bug: 184610991 Test: n/a Signed-off-by: Tai Kuo <taikuo@google.com> Change-Id: I2d29c23aafebe57c46fc2edee354dbc69826faf4
-rw-r--r--cs40l26/cl_dsp.c1114
-rw-r--r--cs40l26/cl_dsp.h326
-rw-r--r--cs40l26/cs40l26-codec.c574
-rw-r--r--cs40l26/cs40l26-i2c.c72
-rw-r--r--cs40l26/cs40l26-spi.c72
-rw-r--r--cs40l26/cs40l26-sysfs.c194
-rw-r--r--cs40l26/cs40l26-tables.c614
-rw-r--r--cs40l26/cs40l26.c3195
-rw-r--r--cs40l26/cs40l26.h1338
9 files changed, 7499 insertions, 0 deletions
diff --git a/cs40l26/cl_dsp.c b/cs40l26/cl_dsp.c
new file mode 100644
index 0000000..3efac31
--- /dev/null
+++ b/cs40l26/cl_dsp.c
@@ -0,0 +1,1114 @@
+// 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 <linux/firmware/cirrus/cl_dsp.h>
+
+struct cl_dsp_memchunk cl_dsp_memchunk_create(void *data, int size)
+{
+ struct cl_dsp_memchunk ch = {
+ .data = data,
+ .max = data + size,
+ };
+
+ return ch;
+}
+EXPORT_SYMBOL(cl_dsp_memchunk_create);
+
+static inline bool cl_dsp_memchunk_end(struct cl_dsp_memchunk *ch)
+{
+ return ch->data == ch->max;
+}
+
+static inline bool cl_dsp_memchunk_valid_addr(struct cl_dsp_memchunk *ch,
+ void *addr)
+{
+ return (u8 *)addr <= ch->max;
+}
+
+int cl_dsp_memchunk_read(struct cl_dsp_memchunk *ch, int nbits)
+{
+ int nread, i;
+ u32 result;
+
+ if (!ch->cachebits) {
+ if (cl_dsp_memchunk_end(ch))
+ return -ENOSPC;
+
+ ch->cache = 0;
+ ch->cachebits = 24;
+
+ for (i = 0; i < sizeof(ch->cache); i++, ch->cache <<= 8)
+ ch->cache |= *ch->data++;
+
+ ch->bytes += sizeof(ch->cache);
+ }
+
+ nread = min(ch->cachebits, nbits);
+ nbits -= nread;
+
+ result = ch->cache >> (32 - nread);
+ ch->cache <<= nread;
+ ch->cachebits -= nread;
+
+ if (nbits)
+ result = (result << nbits) | cl_dsp_memchunk_read(ch, nbits);
+
+ return result;
+}
+EXPORT_SYMBOL(cl_dsp_memchunk_read);
+
+int cl_dsp_memchunk_write(struct cl_dsp_memchunk *ch, int nbits, u32 val)
+{
+ int nwrite, i;
+
+ nwrite = min(24 - ch->cachebits, nbits);
+
+ ch->cache <<= nwrite;
+ ch->cache |= val >> (nbits - nwrite);
+ ch->cachebits += nwrite;
+ nbits -= nwrite;
+
+ if (ch->cachebits == 24) {
+ if (cl_dsp_memchunk_end(ch))
+ return -ENOSPC;
+
+ ch->cache &= 0xFFFFFF;
+ for (i = 0; i < sizeof(ch->cache); i++, ch->cache <<= 8)
+ *ch->data++ = (ch->cache & 0xFF000000) >> 24;
+
+ ch->bytes += sizeof(ch->cache);
+ ch->cachebits = 0;
+ }
+
+ if (nbits)
+ return cl_dsp_memchunk_write(ch, nbits, val);
+
+ return 0;
+}
+EXPORT_SYMBOL(cl_dsp_memchunk_write);
+
+int cl_dsp_raw_write(struct cl_dsp *dsp, unsigned int reg,
+ const void *val, size_t val_len, size_t limit)
+{
+ int i, ret = 0;
+
+ if (!dsp)
+ return -EPERM;
+
+ /* Restrict write length to limit value */
+ for (i = 0; i < val_len; i += limit) {
+ ret = regmap_raw_write(dsp->regmap, (reg + i), (val + i),
+ (val_len - i) > limit ? limit : (val_len - i));
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(cl_dsp_raw_write);
+
+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)
+{
+ int ret = 0;
+ struct cl_dsp_coeff_desc *coeff_desc;
+ unsigned int mem_region_prefix;
+
+ if (!dsp)
+ return -EPERM;
+
+ list_for_each_entry(coeff_desc, &dsp->coeff_desc_head, list) {
+ if (strncmp(coeff_desc->name, coeff_name,
+ CL_DSP_COEFF_NAME_LEN_MAX))
+ continue;
+ if (coeff_desc->block_type != block_type)
+ continue;
+ if ((coeff_desc->parent_id & 0xFFFF) != (algo_id & 0xFFFF))
+ continue;
+
+ *reg = coeff_desc->reg;
+ if (*reg == 0) {
+ dev_err(dsp->dev,
+ "No DSP control called %s for block 0x%X\n",
+ coeff_name, block_type);
+ return -ENXIO;
+ }
+
+ break;
+ }
+
+ /* verify register found in expected region */
+ switch (block_type) {
+ case CL_DSP_XM_PACKED_TYPE:
+ mem_region_prefix = (CL_DSP_HALO_XMEM_PACKED_BASE
+ & CL_DSP_MEM_REG_TYPE_MASK)
+ >> CL_DSP_MEM_REG_TYPE_SHIFT;
+ break;
+ case CL_DSP_XM_UNPACKED_TYPE:
+ mem_region_prefix = (CL_DSP_HALO_XMEM_UNPACKED24_BASE
+ & CL_DSP_MEM_REG_TYPE_MASK)
+ >> CL_DSP_MEM_REG_TYPE_SHIFT;
+ break;
+ case CL_DSP_YM_PACKED_TYPE:
+ mem_region_prefix = (CL_DSP_HALO_YMEM_PACKED_BASE
+ & CL_DSP_MEM_REG_TYPE_MASK)
+ >> CL_DSP_MEM_REG_TYPE_SHIFT;
+ break;
+ case CL_DSP_YM_UNPACKED_TYPE:
+ mem_region_prefix = (CL_DSP_HALO_YMEM_UNPACKED24_BASE
+ & CL_DSP_MEM_REG_TYPE_MASK)
+ >> CL_DSP_MEM_REG_TYPE_SHIFT;
+ break;
+ case CL_DSP_PM_PACKED_TYPE:
+ mem_region_prefix = (CL_DSP_HALO_PMEM_BASE
+ & CL_DSP_MEM_REG_TYPE_MASK)
+ >> CL_DSP_MEM_REG_TYPE_SHIFT;
+ break;
+ default:
+ dev_err(dsp->dev, "Unrecognized block type: 0x%X\n",
+ block_type);
+ return -EINVAL;
+ }
+
+ if (((*reg & CL_DSP_MEM_REG_TYPE_MASK) >> CL_DSP_MEM_REG_TYPE_SHIFT)
+ != mem_region_prefix) {
+ dev_err(dsp->dev,
+ "DSP control %s at 0x%X found in unexpected region\n",
+ coeff_name, *reg);
+
+ ret = -EFAULT;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(cl_dsp_get_reg);
+
+static int cl_dsp_process_data_be(const u8 *data,
+ const unsigned int num_bytes, unsigned int *val)
+{
+ int i;
+
+ if (num_bytes > sizeof(unsigned int))
+ return -EINVAL;
+
+ *val = 0;
+ for (i = 0; i < num_bytes; i++)
+ *val += *(data + i) << (i * CL_DSP_BITS_PER_BYTE);
+
+ return 0;
+}
+
+static int cl_dsp_owt_init(struct cl_dsp *dsp, const struct firmware *bin)
+{
+ if (!dsp->wt_desc) {
+ dev_err(dsp->dev, "Wavetable not supported by core driver\n");
+ return -EPERM;
+ }
+
+ memcpy(&dsp->wt_desc->owt.raw_data, &bin->data[0], bin->size);
+
+ return 0;
+}
+
+static int cl_dsp_read_wt(struct cl_dsp *dsp, int pos, int size)
+{
+ struct cl_dsp_owt_header *entry = dsp->wt_desc->owt.waves;
+ void *buf = (void *)&dsp->wt_desc->owt.raw_data[pos];
+ struct cl_dsp_memchunk ch = cl_dsp_memchunk_create(buf, size);
+ u32 *wbuf = buf, *max = buf;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dsp->wt_desc->owt.waves); i++, entry++) {
+ entry->flags = cl_dsp_memchunk_read(&ch, 16);
+ entry->type = cl_dsp_memchunk_read(&ch, 8);
+
+ if (entry->type == WT_TYPE_TERMINATOR) {
+ dsp->wt_desc->owt.nwaves = i;
+ dsp->wt_desc->owt.bytes = max(ch.bytes,
+ (void *)max - buf);
+
+ return dsp->wt_desc->owt.bytes;
+ }
+
+ entry->offset = cl_dsp_memchunk_read(&ch, 24);
+ entry->size = cl_dsp_memchunk_read(&ch, 24);
+ entry->data = wbuf + entry->offset;
+
+ if (wbuf + entry->offset + entry->size > max) {
+ max = wbuf + entry->offset + entry->size;
+
+ if (!cl_dsp_memchunk_valid_addr(&ch, max))
+ return -EINVAL;
+ }
+ }
+
+ return -E2BIG;
+}
+
+static int cl_dsp_coeff_header_parse(struct cl_dsp *dsp,
+ union cl_dsp_wmdr_header header)
+{
+ struct device *dev = dsp->dev;
+
+ if (memcmp(header.magic, CL_DSP_WMDR_MAGIC_ID, CL_DSP_MAGIC_ID_SIZE)) {
+ dev_err(dev, "Failed to recognize coefficient file\n");
+ return -EINVAL;
+ }
+
+ if (header.header_len != CL_DSP_COEFF_FILE_HEADER_SIZE) {
+ dev_err(dev, "Malformed coeff. header\n");
+ return -EINVAL;
+ }
+
+ if (CL_DSP_GET_MAJOR(header.fw_revision)
+ != CL_DSP_GET_MAJOR(dsp->algo_info[0].rev)) {
+ dev_err(dev,
+ "Coeff. revision 0x%06X incompatible with 0x%06X\n",
+ header.fw_revision, dsp->algo_info[0].rev);
+ return -EINVAL;
+ } else if (header.fw_revision != dsp->algo_info[0].rev) {
+ dev_warn(dev,
+ "Coeff. rev. 0x%06X mistmatches 0x%06X, continuing..\n",
+ header.fw_revision, dsp->algo_info[0].rev);
+ }
+
+ if (header.file_format_version < CL_DSP_COEFF_MIN_FORMAT_VERSION) {
+ dev_err(dev, "File format version 0x%02X is outdated\n",
+ header.file_format_version);
+ return -EINVAL;
+ }
+
+ if (header.api_revision != CL_DSP_COEFF_API_REV_HALO &&
+ header.api_revision != CL_DSP_COEFF_API_REV_ADSP2) {
+ dev_err(dev, "API Revision 0x%06X is not compatible\n",
+ header.api_revision);
+ return -EINVAL;
+ }
+
+ if (header.target_core != CL_DSP_TARGET_CORE_ADSP2 &&
+ header.target_core != CL_DSP_TARGET_CORE_HALO) {
+ dev_err(dev, "Target core 0x%02X is not compatible\n",
+ header.target_core);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void cl_dsp_coeff_handle_info_text(struct cl_dsp *dsp, const u8 *data,
+ u32 len)
+{
+ char *info_str;
+
+ info_str = kzalloc(len, GFP_KERNEL);
+ if (!info_str)
+ return;
+
+ memcpy(info_str, data, len);
+
+ dev_dbg(dsp->dev, "WMDR Info: %s\n", info_str);
+
+ kfree(info_str);
+}
+
+int cl_dsp_coeff_file_parse(struct cl_dsp *dsp, const struct firmware *fw)
+{
+ unsigned int pos = CL_DSP_COEFF_FILE_HEADER_SIZE;
+ struct device *dev = dsp->dev;
+ bool wt_found = false;
+ int ret = -EINVAL;
+ struct cl_dsp_coeff_data_block data_block;
+ union cl_dsp_wmdr_header wmdr_header;
+ char wt_date[CL_DSP_WMDR_DATE_LEN];
+ unsigned int reg, wt_reg, algo_rev;
+ int i;
+
+ if (!dsp)
+ return -EPERM;
+
+ *wt_date = '\0';
+
+ memcpy(wmdr_header.data, fw->data, CL_DSP_COEFF_FILE_HEADER_SIZE);
+
+ if (fw->size % CL_DSP_BYTES_PER_WORD) {
+ dev_err(dev, "Coefficient file is not word-aligned\n");
+ return ret;
+ }
+
+ ret = cl_dsp_coeff_header_parse(dsp, wmdr_header);
+ if (ret)
+ return ret;
+
+ while (pos < fw->size) {
+ memcpy(data_block.header.data, &fw->data[pos],
+ CL_DSP_COEFF_DBLK_HEADER_SIZE);
+ pos += CL_DSP_COEFF_DBLK_HEADER_SIZE;
+
+ data_block.payload = kmalloc(data_block.header.data_len,
+ GFP_KERNEL);
+ if (!data_block.payload)
+ return -ENOMEM;
+
+ memcpy(data_block.payload, &fw->data[pos],
+ data_block.header.data_len);
+
+ if (data_block.header.block_type != CL_DSP_WMDR_NAME_TYPE &&
+ data_block.header.block_type != CL_DSP_WMDR_INFO_TYPE) {
+ for (i = 0; i < dsp->num_algos; i++) {
+ if (data_block.header.algo_id
+ == dsp->algo_info[i].id)
+ break;
+ }
+
+ if (i == dsp->num_algos) {
+ dev_err(dev, "Invalid algo. ID: 0x%06X\n",
+ data_block.header.algo_id);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ algo_rev =
+ CL_DSP_SHIFT_REV(data_block.header.algo_rev);
+ if (CL_DSP_GET_MAJOR(algo_rev) !=
+ CL_DSP_GET_MAJOR(dsp->algo_info[i].rev)) {
+ dev_err(dev,
+ "Invalid algo. rev.: %d.%d.%d (0x%06X)\n",
+ (int) CL_DSP_GET_MAJOR(algo_rev),
+ (int) CL_DSP_GET_MINOR(algo_rev),
+ (int) CL_DSP_GET_PATCH(algo_rev),
+ data_block.header.algo_id);
+
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ wt_found = ((data_block.header.algo_id & 0xFFFF) ==
+ (dsp->wt_desc->id & 0xFFFF));
+ }
+
+ switch (data_block.header.block_type) {
+ case CL_DSP_WMDR_NAME_TYPE:
+ case CL_DSP_WMDR_INFO_TYPE:
+ reg = 0;
+
+ cl_dsp_coeff_handle_info_text(dsp, data_block.payload,
+ data_block.header.data_len);
+
+ if (data_block.header.data_len < CL_DSP_WMDR_DATE_LEN)
+ break;
+
+ if (memcmp(&fw->data[pos], CL_DSP_WMDR_DATE_PREFIX,
+ CL_DSP_WMDR_DATE_PREFIX_LEN))
+ break; /* date already recorded */
+
+ memcpy(wt_date,
+ &fw->data[pos + CL_DSP_WMDR_DATE_PREFIX_LEN],
+ CL_DSP_WMDR_DATE_LEN -
+ CL_DSP_WMDR_DATE_PREFIX_LEN);
+
+ wt_date[CL_DSP_WMDR_DATE_LEN -
+ CL_DSP_WMDR_DATE_PREFIX_LEN] = '\0';
+ break;
+ case CL_DSP_XM_UNPACKED_TYPE:
+ reg = CL_DSP_HALO_XMEM_UNPACKED24_BASE +
+ data_block.header.start_offset +
+ dsp->algo_info[i].xm_base *
+ CL_DSP_BYTES_PER_WORD;
+
+ if (wt_found) {
+ ret = cl_dsp_get_reg(dsp,
+ dsp->wt_desc->wt_name_xm,
+ CL_DSP_XM_UNPACKED_TYPE,
+ dsp->wt_desc->id, &wt_reg);
+ if (ret)
+ goto err_free;
+
+ if (reg == wt_reg) {
+ if (data_block.header.data_len >
+ dsp->wt_desc->wt_limit_xm) {
+ dev_err(dev,
+ "XM too large: %d bytes\n",
+ data_block.header.data_len
+ / 4 * 3);
+
+ ret = -EINVAL;
+ goto err_free;
+ } else {
+ ret = cl_dsp_owt_init(dsp, fw);
+ if (ret)
+ goto err_free;
+
+ ret = cl_dsp_read_wt(dsp, pos,
+ data_block.header.data_len);
+ if (ret < 0)
+ goto err_free;
+ dsp->wt_desc->is_xm = true;
+ }
+
+ dev_info(dev,
+ "Wavetable found: %d bytes (XM)\n",
+ data_block.header.data_len / 4 * 3);
+ }
+ }
+ break;
+ case CL_DSP_XM_PACKED_TYPE:
+ reg = (CL_DSP_HALO_XMEM_PACKED_BASE +
+ data_block.header.start_offset +
+ dsp->algo_info[i].xm_base *
+ CL_DSP_PACKED_NUM_BYTES) &
+ ~CL_DSP_ALIGN;
+ break;
+ case CL_DSP_YM_UNPACKED_TYPE:
+ reg = CL_DSP_HALO_YMEM_UNPACKED24_BASE +
+ data_block.header.start_offset +
+ dsp->algo_info[i].ym_base *
+ CL_DSP_UNPACKED_NUM_BYTES;
+ if (wt_found) {
+ ret = cl_dsp_get_reg(dsp,
+ dsp->wt_desc->wt_name_ym,
+ CL_DSP_YM_UNPACKED_TYPE,
+ dsp->wt_desc->id, &wt_reg);
+ if (ret)
+ goto err_free;
+
+ if (reg == wt_reg) {
+ if (data_block.header.data_len >
+ dsp->wt_desc->wt_limit_ym) {
+ dev_err(dev,
+ "YM too large: %d bytes\n",
+ data_block.header.data_len
+ / 4 * 3);
+
+ ret = -EINVAL;
+ goto err_free;
+ } else {
+ ret = cl_dsp_read_wt(dsp, pos,
+ data_block.header.data_len);
+ if (ret < 0)
+ goto err_free;
+ dsp->wt_desc->is_xm = false;
+ }
+
+ dev_dbg(dev,
+ "Wavetable found: %d bytes (YM)\n",
+ data_block.header.data_len / 4 * 3);
+ }
+ }
+ break;
+ case CL_DSP_YM_PACKED_TYPE:
+ reg = (CL_DSP_HALO_YMEM_PACKED_BASE +
+ data_block.header.start_offset +
+ dsp->algo_info[i].ym_base *
+ CL_DSP_PACKED_NUM_BYTES) &
+ ~CL_DSP_ALIGN;
+ break;
+ default:
+ dev_err(dev, "Unexpected block type: 0x%04X\n",
+ data_block.header.block_type);
+ ret = -EINVAL;
+ goto err_free;
+ }
+ if (reg) {
+ ret = cl_dsp_raw_write(dsp, reg, &fw->data[pos],
+ data_block.header.data_len,
+ CL_DSP_MAX_WLEN);
+ if (ret) {
+ dev_err(dev, "Failed to write coefficients\n");
+ goto err_free;
+ }
+ }
+
+ /* Blocks are word-aligned */
+ pos += (data_block.header.data_len + 3) & ~CL_DSP_ALIGN;
+
+ kfree(data_block.payload);
+ }
+
+ if (wt_found) {
+ if (*wt_date != '\0')
+ strlcpy(dsp->wt_desc->wt_date, wt_date,
+ CL_DSP_WMDR_DATE_LEN);
+ else
+ strlcpy(dsp->wt_desc->wt_date,
+ CL_DSP_WMDR_FILE_DATE_MISSING,
+ CL_DSP_WMDR_DATE_LEN);
+ }
+
+ return 0;
+
+err_free:
+ kfree(data_block.payload);
+
+ return ret;
+}
+EXPORT_SYMBOL(cl_dsp_coeff_file_parse);
+
+static int cl_dsp_algo_parse(struct cl_dsp *dsp, const unsigned char *data)
+{
+ struct cl_dsp_coeff_desc *coeff_desc;
+ u8 algo_name_len;
+ u16 algo_desc_len, block_offset, block_type;
+ u32 algo_id, block_len;
+ char *algo_name;
+ unsigned int coeff_count, val, pos = 0, header_pos = 0;
+ unsigned int coeff_name_len, coeff_fullname_len, coeff_desc_len;
+ unsigned int coeff_flags, coeff_len;
+ int i, ret;
+
+ ret = cl_dsp_process_data_be(&data[pos], CL_DSP_ALGO_ID_SIZE,
+ &algo_id);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ return ret;
+ }
+ pos += CL_DSP_ALGO_ID_SIZE;
+
+ ret = cl_dsp_process_data_be(&data[pos], CL_DSP_ALGO_NAME_LEN_SIZE,
+ &val);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ return ret;
+ }
+ algo_name_len = (u8) (val & CL_DSP_BYTE_MASK);
+
+ algo_name = kzalloc(algo_name_len, GFP_KERNEL);
+ if (!algo_name)
+ return -ENOMEM;
+
+ memcpy(algo_name, &data[pos + CL_DSP_ALGO_NAME_LEN_SIZE],
+ algo_name_len);
+ pos += CL_DSP_WORD_ALIGN(algo_name_len);
+
+ ret = cl_dsp_process_data_be(&data[pos], CL_DSP_ALGO_DESC_LEN_SIZE,
+ &val);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ goto err_free;
+ }
+
+ /* Nothing required for algo. description, so it is skipped */
+ algo_desc_len = (u16) (val & CL_DSP_NIBBLE_MASK);
+ pos += CL_DSP_WORD_ALIGN(algo_desc_len);
+
+ ret = cl_dsp_process_data_be(&data[pos], CL_DSP_COEFF_COUNT_SIZE,
+ &coeff_count);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ goto err_free;
+ }
+ pos += CL_DSP_COEFF_COUNT_SIZE;
+
+ for (i = 0; i < coeff_count; i++) {
+ ret = cl_dsp_process_data_be(&data[pos],
+ CL_DSP_COEFF_OFFSET_SIZE, &val);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ goto err_free;
+ }
+
+ block_offset = (u16) (val & CL_DSP_NIBBLE_MASK);
+ pos += CL_DSP_COEFF_OFFSET_SIZE;
+
+ ret = cl_dsp_process_data_be(&data[pos],
+ CL_DSP_COEFF_TYPE_SIZE, &val);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ goto err_free;
+ }
+
+ block_type = (u16) (val & CL_DSP_NIBBLE_MASK);
+ pos += CL_DSP_COEFF_TYPE_SIZE;
+
+ ret = cl_dsp_process_data_be(&data[pos], CL_DSP_COEFF_LEN_SIZE,
+ &block_len);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ goto err_free;
+ }
+ pos += CL_DSP_COEFF_LEN_SIZE;
+ header_pos = pos;
+
+ coeff_desc = devm_kzalloc(dsp->dev, sizeof(*coeff_desc),
+ GFP_KERNEL);
+ if (!coeff_desc) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ coeff_desc->parent_id = algo_id;
+ coeff_desc->parent_name = algo_name;
+ coeff_desc->block_offset = block_offset;
+ coeff_desc->block_type = block_type;
+
+ memcpy(coeff_desc->name, data + pos + 1, *(data + pos));
+ coeff_desc->name[*(data + pos)] = '\0';
+
+ ret = cl_dsp_process_data_be(&data[pos],
+ CL_DSP_COEFF_NAME_LEN_SIZE, &coeff_name_len);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ return ret;
+ }
+ pos += CL_DSP_WORD_ALIGN(coeff_name_len);
+
+ ret = cl_dsp_process_data_be(&data[pos],
+ CL_DSP_COEFF_FULLNAME_LEN_SIZE,
+ &coeff_fullname_len);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ return ret;
+ }
+ pos += CL_DSP_WORD_ALIGN(coeff_fullname_len);
+
+ ret = cl_dsp_process_data_be(&data[pos],
+ CL_DSP_COEFF_DESC_LEN_SIZE, &coeff_desc_len);
+
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ return ret;
+ }
+ pos += CL_DSP_WORD_ALIGN(coeff_desc_len);
+
+ ret = cl_dsp_process_data_be(&data[pos],
+ CL_DSP_COEFF_FLAGS_SIZE, &coeff_flags);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ return ret;
+ }
+ pos += CL_DSP_COEFF_FLAGS_SIZE;
+
+ ret = cl_dsp_process_data_be(&data[pos], CL_DSP_COEFF_LEN_SIZE,
+ &coeff_len);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read data\n");
+ return ret;
+ }
+ pos += CL_DSP_COEFF_LEN_SIZE;
+
+ coeff_desc->flags = coeff_flags >> CL_DSP_COEFF_FLAGS_SHIFT;
+ coeff_desc->length = coeff_len;
+
+ list_add(&coeff_desc->list, &dsp->coeff_desc_head);
+
+ pos = header_pos + block_len;
+ }
+
+err_free:
+ kfree(algo_name);
+
+ return ret;
+}
+
+int cl_dsp_fw_id_get(struct cl_dsp *dsp, unsigned int *id)
+{
+ int ret = 0;
+
+ ret = regmap_read(dsp->regmap, CL_DSP_HALO_XM_FW_ID_REG, id);
+ if (ret)
+ dev_err(dsp->dev, "Failed to read firmware ID\n");
+
+ return ret;
+}
+EXPORT_SYMBOL(cl_dsp_fw_id_get);
+
+int cl_dsp_fw_rev_get(struct cl_dsp *dsp, unsigned int *rev)
+{
+ int ret = 0;
+
+ ret = regmap_read(dsp->regmap, CL_DSP_HALO_XM_FW_ID_REG +
+ CL_DSP_HALO_ALGO_REV_OFFSET, rev);
+ if (ret)
+ dev_err(dsp->dev, "Failed to read firmware revision\n");
+
+ return ret;
+}
+EXPORT_SYMBOL(cl_dsp_fw_rev_get);
+
+static int cl_dsp_coeff_init(struct cl_dsp *dsp)
+{
+ struct regmap *regmap = dsp->regmap;
+ struct device *dev = dsp->dev;
+ struct cl_dsp_coeff_desc *coeff_desc;
+ unsigned int reg = CL_DSP_HALO_XM_FW_ID_REG;
+ unsigned int val;
+ int ret, i;
+
+ if (!dsp)
+ return -EPERM;
+
+ ret = regmap_read(regmap, CL_DSP_HALO_NUM_ALGOS_REG, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read number of algorithms\n");
+ return ret;
+ }
+
+ if (val > CL_DSP_NUM_ALGOS_MAX) {
+ dev_err(dev, "Invalid number of algorithms: %d\n", val);
+ return -EINVAL;
+ }
+ dsp->num_algos = val + 1;
+
+ for (i = 0; i < dsp->num_algos; i++) {
+ ret = regmap_read(regmap, reg, &dsp->algo_info[i].id);
+ if (ret) {
+ dev_err(dev, "Failed to read algo. %d ID\n", i);
+ return ret;
+ }
+
+ ret = regmap_read(regmap, reg + CL_DSP_HALO_ALGO_REV_OFFSET,
+ &dsp->algo_info[i].rev);
+ if (ret) {
+ dev_err(dev, "Failed to read algo. %d revision\n", i);
+ return ret;
+ }
+
+ ret = regmap_read(regmap, reg +
+ CL_DSP_HALO_ALGO_XM_BASE_OFFSET,
+ &dsp->algo_info[i].xm_base);
+ if (ret) {
+ dev_err(dev, "Failed to read algo. %d XM_BASE\n", i);
+ return ret;
+ }
+
+ ret = regmap_read(regmap, reg +
+ CL_DSP_HALO_ALGO_XM_SIZE_OFFSET,
+ &dsp->algo_info[i].xm_size);
+ if (ret) {
+ dev_err(dev, "Failed to read algo. %d XM_SIZE\n", i);
+ return ret;
+ }
+
+ ret = regmap_read(regmap, reg +
+ CL_DSP_HALO_ALGO_YM_BASE_OFFSET,
+ &dsp->algo_info[i].ym_base);
+ if (ret) {
+ dev_err(dev, "Failed to read algo. %d YM_BASE\n", i);
+ return ret;
+ }
+
+ ret = regmap_read(regmap, reg +
+ CL_DSP_HALO_ALGO_YM_SIZE_OFFSET,
+ &dsp->algo_info[i].ym_size);
+ if (ret) {
+ dev_err(dev, "Failed to read algo. %d YM_SIZE\n", i);
+ return ret;
+ }
+
+ list_for_each_entry(coeff_desc, &dsp->coeff_desc_head, list) {
+ if (coeff_desc->parent_id != dsp->algo_info[i].id)
+ continue;
+
+ switch (coeff_desc->block_type) {
+ case CL_DSP_XM_UNPACKED_TYPE:
+ coeff_desc->reg =
+ CL_DSP_HALO_XMEM_UNPACKED24_BASE
+ + dsp->algo_info[i].xm_base
+ * CL_DSP_UNPACKED_NUM_BYTES
+ + coeff_desc->block_offset
+ * CL_DSP_UNPACKED_NUM_BYTES;
+
+ if (dsp->wt_desc && !strncmp(coeff_desc->name,
+ dsp->wt_desc->wt_name_xm,
+ CL_DSP_COEFF_NAME_LEN_MAX))
+ dsp->wt_desc->wt_limit_xm =
+ (dsp->algo_info[i].xm_size -
+ coeff_desc->block_offset) *
+ CL_DSP_UNPACKED_NUM_BYTES;
+ break;
+ case CL_DSP_YM_UNPACKED_TYPE:
+ coeff_desc->reg =
+ CL_DSP_HALO_YMEM_UNPACKED24_BASE +
+ dsp->algo_info[i].ym_base *
+ CL_DSP_BYTES_PER_WORD +
+ coeff_desc->block_offset *
+ CL_DSP_BYTES_PER_WORD;
+
+ if (dsp->wt_desc && !strncmp(coeff_desc->name,
+ dsp->wt_desc->wt_name_ym,
+ CL_DSP_COEFF_NAME_LEN_MAX))
+ dsp->wt_desc->wt_limit_ym =
+ (dsp->algo_info[i].ym_size -
+ coeff_desc->block_offset) *
+ CL_DSP_UNPACKED_NUM_BYTES;
+ break;
+ }
+
+ dev_dbg(dev,
+ "Control %s at 0x%08X with parent ID = 0x%X\n",
+ coeff_desc->name, coeff_desc->reg,
+ coeff_desc->parent_id);
+ }
+
+ /* System algo. contains one extra register (num. algos.) */
+ if (i)
+ reg += CL_DSP_ALGO_ENTRY_SIZE;
+ else
+ reg += (CL_DSP_ALGO_ENTRY_SIZE +
+ CL_DSP_BYTES_PER_WORD);
+ }
+
+ ret = regmap_read(regmap, reg, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read list terminator\n");
+ return ret;
+ }
+
+ if (val != CL_DSP_ALGO_LIST_TERM) {
+ dev_err(dev, "Invalid list terminator: 0x%X\n", val);
+ return -EINVAL;
+ }
+
+ if (dsp->wt_desc)
+ dev_info(dev,
+ "Max. wavetable size: %d bytes (XM), %d bytes (YM)\n",
+ dsp->wt_desc->wt_limit_xm / 4 * 3,
+ dsp->wt_desc->wt_limit_ym / 4 * 3);
+
+ return 0;
+}
+
+static void cl_dsp_coeff_free(struct cl_dsp *dsp)
+{
+ struct cl_dsp_coeff_desc *coeff_desc;
+
+ if (!dsp)
+ return;
+
+ while (!list_empty(&dsp->coeff_desc_head)) {
+ coeff_desc = list_first_entry(&dsp->coeff_desc_head,
+ struct cl_dsp_coeff_desc, list);
+ list_del(&coeff_desc->list);
+ kfree(coeff_desc->parent_name);
+ devm_kfree(dsp->dev, coeff_desc);
+ }
+}
+
+static int cl_dsp_header_parse(struct cl_dsp *dsp,
+ union cl_dsp_wmfw_header header)
+{
+ struct device *dev = dsp->dev;
+
+ if (memcmp(header.magic, CL_DSP_WMFW_MAGIC_ID, CL_DSP_MAGIC_ID_SIZE)) {
+ dev_err(dev, "Failed to recognize firmware file\n");
+ return -EINVAL;
+ }
+
+ if (header.header_len != CL_DSP_FW_FILE_HEADER_SIZE) {
+ dev_err(dev, "Malformed fimrware header\n");
+ return -EINVAL;
+ }
+
+ if (header.api_revision != CL_DSP_API_REVISION) {
+ dev_err(dev, "Firmware API Revision Incompatible with Core\n");
+ return -EINVAL;
+ }
+
+ if (header.target_core != CL_DSP_TARGET_CORE_HALO) {
+ dev_err(dev, "Unexpected target core type: 0x%02X\n",
+ header.target_core);
+ return -EINVAL;
+ }
+
+ if (header.file_format_version < CL_DSP_MIN_FORMAT_VERSION) {
+ dev_err(dev, "File Format Version 0x%02X is outdated",
+ header.file_format_version);
+ return -EINVAL;
+ }
+
+ dev_info(dev,
+ "Loading memory (bytes): XM: %u, YM: %u, PM: %u, ZM: %u\n",
+ header.xm_size, header.ym_size, header.pm_size, header.zm_size);
+
+ return 0;
+}
+
+static void cl_dsp_handle_info_text(struct cl_dsp *dsp,
+ const u8 *data, u32 len)
+{
+ char *info_str;
+
+ info_str = kzalloc(len, GFP_KERNEL);
+ if (!info_str)
+ /* info block is empty or memory not allocated */
+ return;
+
+ memcpy(info_str, data, len);
+
+ dev_info(dsp->dev, "WMFW Info: %s\n", info_str);
+
+ kfree(info_str);
+}
+
+int cl_dsp_firmware_parse(struct cl_dsp *dsp, const struct firmware *fw,
+ bool write_fw)
+{
+ struct device *dev = dsp->dev;
+ unsigned int pos = CL_DSP_FW_FILE_HEADER_SIZE, reg = 0;
+ struct cl_dsp_data_block data_block;
+ union cl_dsp_wmfw_header wmfw_header;
+ int ret;
+
+ if (!dsp)
+ return -EPERM;
+
+ memcpy(wmfw_header.data, fw->data, CL_DSP_FW_FILE_HEADER_SIZE);
+
+ ret = cl_dsp_header_parse(dsp, wmfw_header);
+ if (ret)
+ return ret;
+
+ if (fw->size % CL_DSP_BYTES_PER_WORD) {
+ dev_err(dev, "Firmware file is not word-aligned\n");
+ return -EINVAL;
+ }
+
+ while (pos < fw->size) {
+ memcpy(data_block.header.data, &fw->data[pos],
+ CL_DSP_DBLK_HEADER_SIZE);
+
+ pos += CL_DSP_DBLK_HEADER_SIZE;
+
+ data_block.payload =
+ kmalloc(data_block.header.data_len, GFP_KERNEL);
+ memcpy(data_block.payload, &fw->data[pos],
+ data_block.header.data_len);
+
+ switch (data_block.header.block_type) {
+ case CL_DSP_WMFW_INFO_TYPE:
+ reg = 0;
+ cl_dsp_handle_info_text(dsp, data_block.payload,
+ data_block.header.data_len);
+ break;
+ case CL_DSP_PM_PACKED_TYPE:
+ reg = CL_DSP_HALO_PMEM_BASE +
+ data_block.header.start_offset *
+ CL_DSP_PM_NUM_BYTES;
+ break;
+ case CL_DSP_XM_PACKED_TYPE:
+ reg = CL_DSP_HALO_XMEM_PACKED_BASE +
+ data_block.header.start_offset *
+ CL_DSP_PACKED_NUM_BYTES;
+ break;
+ case CL_DSP_XM_UNPACKED_TYPE:
+ reg = CL_DSP_HALO_XMEM_UNPACKED24_BASE +
+ data_block.header.start_offset *
+ CL_DSP_UNPACKED_NUM_BYTES;
+ break;
+ case CL_DSP_YM_PACKED_TYPE:
+ reg = CL_DSP_HALO_YMEM_PACKED_BASE +
+ data_block.header.start_offset *
+ CL_DSP_PACKED_NUM_BYTES;
+ break;
+ case CL_DSP_YM_UNPACKED_TYPE:
+ reg = CL_DSP_HALO_YMEM_UNPACKED24_BASE +
+ data_block.header.start_offset *
+ CL_DSP_UNPACKED_NUM_BYTES;
+ break;
+ case CL_DSP_ALGO_INFO_TYPE:
+ reg = 0;
+
+ ret = cl_dsp_algo_parse(dsp, data_block.payload);
+ if (ret)
+ goto err_free;
+ break;
+ default:
+ dev_err(dev, "Unexpected block type : 0x%02X\n",
+ data_block.header.block_type);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ if (write_fw && reg) {
+ ret = cl_dsp_raw_write(dsp, reg, data_block.payload,
+ data_block.header.data_len,
+ CL_DSP_MAX_WLEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to write to base 0x%X\n", reg);
+ goto err_free;
+ }
+ }
+
+ /* Blocks are word-aligned */
+ pos += (data_block.header.data_len + 3) & ~CL_DSP_ALIGN;
+
+ kfree(data_block.payload);
+ }
+
+ return cl_dsp_coeff_init(dsp);
+
+err_free:
+ kfree(data_block.payload);
+
+ return ret;
+}
+EXPORT_SYMBOL(cl_dsp_firmware_parse);
+
+int cl_dsp_wavetable_create(struct cl_dsp *dsp, unsigned int id,
+ const char *wt_name_xm, const char *wt_name_ym,
+ const char *wt_file)
+{
+ struct cl_dsp_wt_desc *wt_desc;
+
+ if (!dsp)
+ return -EPERM;
+
+ wt_desc = devm_kzalloc(dsp->dev, sizeof(struct cl_dsp_wt_desc),
+ GFP_KERNEL);
+ if (!wt_desc)
+ return -ENOMEM;
+
+ wt_desc->id = id;
+ memcpy(wt_desc->wt_name_xm, wt_name_xm, CL_DSP_WMDR_NAME_LEN);
+ memcpy(wt_desc->wt_name_ym, wt_name_ym, CL_DSP_WMDR_NAME_LEN);
+ memcpy(wt_desc->wt_file, wt_file, CL_DSP_WMDR_NAME_LEN);
+
+ dsp->wt_desc = wt_desc;
+
+ return 0;
+}
+EXPORT_SYMBOL(cl_dsp_wavetable_create);
+
+struct cl_dsp *cl_dsp_create(struct device *dev, struct regmap *regmap)
+{
+ struct cl_dsp *dsp;
+
+ dsp = devm_kzalloc(dev, sizeof(struct cl_dsp), GFP_KERNEL);
+ if (!dsp)
+ return NULL;
+
+ dsp->dev = dev;
+ dsp->regmap = regmap;
+
+ INIT_LIST_HEAD(&dsp->coeff_desc_head);
+
+ return dsp;
+}
+EXPORT_SYMBOL(cl_dsp_create);
+
+int cl_dsp_destroy(struct cl_dsp *dsp)
+{
+ if (!dsp)
+ return -EPERM;
+
+ if (!list_empty(&dsp->coeff_desc_head))
+ cl_dsp_coeff_free(dsp);
+
+ if (dsp->wt_desc)
+ devm_kfree(dsp->dev, dsp->wt_desc);
+
+ devm_kfree(dsp->dev, dsp);
+
+ return 0;
+}
+EXPORT_SYMBOL(cl_dsp_destroy);
+
+MODULE_DESCRIPTION("Cirrus Logic DSP Firmware Driver");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc, <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/cs40l26/cl_dsp.h b/cs40l26/cl_dsp.h
new file mode 100644
index 0000000..2cc1578
--- /dev/null
+++ b/cs40l26/cl_dsp.h
@@ -0,0 +1,326 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * cl_dsp.h -- DSP control for non-ALSA Cirrus Logic devices
+ *
+ * Copyright 2021 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/regmap.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+
+#ifndef __CL_DSP_H__
+#define __CL_DSP_H__
+
+#define CL_DSP_BYTES_PER_WORD 4
+#define CL_DSP_BITS_PER_BYTE 8
+
+#define CL_DSP_BYTE_MASK GENMASK(7, 0)
+#define CL_DSP_NIBBLE_MASK GENMASK(15, 0)
+
+#define CL_DSP_FW_FILE_HEADER_SIZE 40
+#define CL_DSP_COEFF_FILE_HEADER_SIZE 16
+
+#define CL_DSP_MAGIC_ID_SIZE 4
+
+#define CL_DSP_WMFW_MAGIC_ID "WMFW"
+#define CL_DSP_WMDR_MAGIC_ID "WMDR"
+
+#define CL_DSP_DBLK_HEADER_SIZE 8
+#define CL_DSP_COEFF_DBLK_HEADER_SIZE 20
+
+#define CL_DSP_ALIGN 0x00000003
+
+#define CL_DSP_TARGET_CORE_ADSP1 0x01
+#define CL_DSP_TARGET_CORE_ADSP2 0x02
+#define CL_DSP_TARGET_CORE_HALO 0x04
+#define CL_DSP_TARGET_CORE_WARP2 0x12
+#define CL_DSP_TARGET_CORE_MCU 0x45
+
+#define CL_DSP_MIN_FORMAT_VERSION 0x03
+#define CL_DSP_API_REVISION 0x0300
+
+#define CL_DSP_ALGO_NAME_LEN_SIZE 1
+#define CL_DSP_ALGO_DESC_LEN_SIZE 2
+#define CL_DSP_ALGO_ID_SIZE 4
+#define CL_DSP_COEFF_COUNT_SIZE 4
+#define CL_DSP_COEFF_OFFSET_SIZE 2
+#define CL_DSP_COEFF_TYPE_SIZE 2
+#define CL_DSP_COEFF_NAME_LEN_SIZE 1
+#define CL_DSP_COEFF_FULLNAME_LEN_SIZE 1
+#define CL_DSP_COEFF_DESC_LEN_SIZE 2
+#define CL_DSP_COEFF_LEN_SIZE 4
+#define CL_DSP_COEFF_FLAGS_SIZE 4
+#define CL_DSP_COEFF_FLAGS_SHIFT 16
+#define CL_DSP_COEFF_NAME_LEN_MAX 32
+#define CL_DSP_COEFF_MIN_FORMAT_VERSION 0x01
+#define CL_DSP_COEFF_API_REV_HALO 0x030000
+#define CL_DSP_COEFF_API_REV_ADSP2 0x000500
+
+#define CL_DSP_ALGO_LIST_TERM 0xBEDEAD
+
+#define CL_DSP_REV_OFFSET_MASK GENMASK(23, 16)
+#define CL_DSP_REV_OFFSET_SHIFT 8
+
+#define CL_DSP_REV_MAJOR_MASK GENMASK(23, 16)
+#define CL_DSP_REV_MAJOR_SHIFT 16
+#define CL_DSP_REV_MINOR_MASK GENMASK(15, 8)
+#define CL_DSP_REV_MINOR_SHIFT 8
+#define CL_DSP_REV_PATCH_MASK GENMASK(7, 0)
+
+#define CL_DSP_NUM_ALGOS_MAX 32
+
+#define CL_DSP_MAX_WLEN 4096
+
+#define CL_DSP_XM_UNPACKED_TYPE 0x0005
+#define CL_DSP_YM_UNPACKED_TYPE 0x0006
+#define CL_DSP_PM_PACKED_TYPE 0x0010
+#define CL_DSP_XM_PACKED_TYPE 0x0011
+#define CL_DSP_YM_PACKED_TYPE 0x0012
+#define CL_DSP_ALGO_INFO_TYPE 0x00F2
+#define CL_DSP_WMFW_INFO_TYPE 0x00FF
+
+#define CL_DSP_MEM_REG_TYPE_MASK GENMASK(27, 20)
+#define CL_DSP_MEM_REG_TYPE_SHIFT 20
+
+#define CL_DSP_PM_NUM_BYTES 5
+#define CL_DSP_PACKED_NUM_BYTES 3
+#define CL_DSP_UNPACKED_NUM_BYTES 4
+
+#define CL_DSP_WMDR_DBLK_OFFSET_SIZE 2
+#define CL_DSP_WMDR_DBLK_TYPE_SIZE 2
+#define CL_DSP_WMDR_ALGO_ID_SIZE 4
+#define CL_DSP_WMDR_ALGO_REV_SIZE 4
+#define CL_DSP_WMDR_SAMPLE_RATE_SIZE 4
+#define CL_DSP_WMDR_DBLK_LEN_SIZE 4
+#define CL_DSP_WMDR_NAME_LEN 32
+#define CL_DSP_WMDR_DATE_LEN 16
+#define CL_DSP_WMDR_HEADER_LEN_SIZE 4
+
+#define CL_DSP_WMDR_DATE_PREFIX "Date: "
+#define CL_DSP_WMDR_DATE_PREFIX_LEN 6
+
+#define CL_DSP_WMDR_FILE_NAME_MISSING "N/A"
+#define CL_DSP_WMDR_FILE_DATE_MISSING "N/A"
+
+#define CL_DSP_WMDR_NAME_TYPE 0xFE00
+#define CL_DSP_WMDR_INFO_TYPE 0xFF00
+
+//HALO core specific registers
+#define CL_DSP_HALO_XMEM_PACKED_BASE 0x02000000
+#define CL_DSP_HALO_XROM_PACKED_BASE 0x02006000
+#define CL_DSP_HALO_XMEM_UNPACKED32_BASE 0x02400000
+#define CL_DSP_HALO_XMEM_UNPACKED24_BASE 0x02800000
+#define CL_DSP_HALO_XROM_UNPACKED24_BASE 0x02808000
+#define CL_DSP_HALO_YMEM_PACKED_BASE 0x02C00000
+#define CL_DSP_HALO_YMEM_UNPACKED32_BASE 0x03000000
+#define CL_DSP_HALO_YMEM_UNPACKED24_BASE 0x03400000
+#define CL_DSP_HALO_PMEM_BASE 0x03800000
+#define CL_DSP_HALO_PROM_BASE 0x03C60000
+
+#define CL_DSP_HALO_XM_FW_ID_REG 0x0280000C
+#define CL_DSP_HALO_NUM_ALGOS_REG 0x02800024
+
+#define CL_DSP_HALO_ALGO_REV_OFFSET 4
+#define CL_DSP_HALO_ALGO_XM_BASE_OFFSET 8
+#define CL_DSP_HALO_ALGO_XM_SIZE_OFFSET 12
+#define CL_DSP_HALO_ALGO_YM_BASE_OFFSET 16
+#define CL_DSP_HALO_ALGO_YM_SIZE_OFFSET 20
+#define CL_DSP_ALGO_ENTRY_SIZE 24
+
+/* open wavetable */
+#define CL_DSP_OWT_HEADER_MAX_LEN 128
+#define CL_DSP_OWT_HEADER_ENTRY_SIZE 12
+
+#define CL_DSP_MAX_BIN_SIZE 9584
+
+/* macros */
+#define CL_DSP_WORD_ALIGN(n) (CL_DSP_BYTES_PER_WORD +\
+ (((n) / CL_DSP_BYTES_PER_WORD) *\
+ CL_DSP_BYTES_PER_WORD))
+
+#define CL_DSP_SHIFT_REV(n) (((n) >> CL_DSP_REV_OFFSET_SHIFT) &\
+ CL_DSP_REV_OFFSET_MASK)
+
+#define CL_DSP_GET_MAJOR(n) (((n) & CL_DSP_REV_MAJOR_MASK) >>\
+ CL_DSP_REV_MAJOR_SHIFT)
+
+#define CL_DSP_GET_MINOR(n) (((n) & CL_DSP_REV_MINOR_MASK) >>\
+ CL_DSP_REV_MINOR_SHIFT)
+
+#define CL_DSP_GET_PATCH(n) ((n) & CL_DSP_REV_PATCH_MASK)
+
+enum cl_dsp_wt_type {
+ WT_TYPE_V4_PCM = 0,
+ WT_TYPE_V4_PWLE = 1,
+ WT_TYPE_V4_PCM_F0_REDC = 2,
+ WT_TYPE_V4_PCM_F0_REDC_VAR = 3,
+ WT_TYPE_V4_COMPOSITE = 4,
+ WT_TYPE_V5_PCM_PCM_F0_REDC_Q = 5,
+ WT_TYPE_V5_PWLE_LONG = 6,
+ WT_TYPE_V5_PWLE_LINEAR = 7,
+ WT_TYPE_V6_PCM_F0_REDC = 8,
+ WT_TYPE_V6_PCM_F0_REDC_VAR = 9,
+ WT_TYPE_V6_COMPOSITE = 10,
+ WT_TYPE_V6_PCM_F0_REDC_Q = 11,
+ WT_TYPE_V6_PWLE = 12,
+
+ WT_TYPE_TERMINATOR = 0xFF,
+};
+
+union cl_dsp_wmdr_header {
+ struct {
+ char magic[CL_DSP_BYTES_PER_WORD];
+ u32 header_len;
+ u32 fw_revision : 24;
+ u8 file_format_version;
+ u32 api_revision : 24;
+ u8 target_core;
+ } __attribute__((__packed__));
+ u8 data[CL_DSP_COEFF_FILE_HEADER_SIZE];
+};
+
+union cl_dsp_wmfw_header {
+ struct {
+ char magic[CL_DSP_BYTES_PER_WORD];
+ u32 header_len;
+ u16 api_revision;
+ u8 target_core;
+ u8 file_format_version;
+ u32 xm_size;
+ u32 ym_size;
+ u32 pm_size;
+ u32 zm_size;
+ u32 timestamp[2];
+ u32 checksum;
+ } __attribute__((__packed__));
+ u8 data[CL_DSP_FW_FILE_HEADER_SIZE];
+};
+
+union cl_dsp_data_block_header {
+ struct {
+ u32 start_offset : 24;
+ u8 block_type;
+ u32 data_len;
+ } __attribute__((__packed__));
+ u8 data[CL_DSP_DBLK_HEADER_SIZE];
+};
+
+union cl_dsp_coeff_data_block_header {
+ struct {
+ u16 start_offset;
+ u16 block_type;
+ u32 algo_id;
+ u32 algo_rev;
+ u32 sample_rate;
+ u32 data_len;
+ } __attribute__((__packed__));
+ u8 data[CL_DSP_COEFF_DBLK_HEADER_SIZE];
+};
+
+struct cl_dsp_data_block {
+ union cl_dsp_data_block_header header;
+ u8 *payload;
+};
+
+struct cl_dsp_coeff_data_block {
+ union cl_dsp_coeff_data_block_header header;
+ u8 *payload;
+};
+
+struct cl_dsp_coeff_desc {
+ u32 parent_id;
+ char *parent_name;
+ u16 block_offset;
+ u16 block_type;
+ unsigned char name[CL_DSP_COEFF_NAME_LEN_MAX];
+ unsigned int reg;
+ unsigned int flags;
+ unsigned int length;
+ struct list_head list;
+};
+
+struct cl_dsp_memchunk {
+ u8 *data;
+ u8 *max;
+ int bytes;
+ u32 cache;
+ int cachebits;
+};
+
+struct cl_dsp_owt_header {
+ enum cl_dsp_wt_type type;
+ u16 flags;
+ u32 offset;
+ u32 size;
+ void *data;
+};
+
+struct cl_dsp_owt_desc {
+ struct cl_dsp_owt_header waves[CL_DSP_OWT_HEADER_MAX_LEN];
+ int nwaves;
+ int bytes;
+ u8 raw_data[CL_DSP_MAX_BIN_SIZE];
+};
+
+struct cl_dsp_wt_desc {
+ unsigned int id;
+ char wt_name_xm[CL_DSP_WMDR_NAME_LEN];
+ char wt_name_ym[CL_DSP_WMDR_NAME_LEN];
+ unsigned int wt_limit_xm;
+ unsigned int wt_limit_ym;
+ char wt_file[CL_DSP_WMDR_NAME_LEN];
+ char wt_date[CL_DSP_WMDR_DATE_LEN];
+ struct cl_dsp_owt_desc owt;
+ bool is_xm;
+};
+
+struct cl_dsp_algo_info {
+ unsigned int id;
+ unsigned int rev;
+ unsigned int xm_base;
+ unsigned int xm_size;
+ unsigned int ym_base;
+ unsigned int ym_size;
+};
+
+struct cl_dsp {
+ struct device *dev;
+ struct regmap *regmap;
+ struct list_head coeff_desc_head;
+ unsigned int num_algos;
+ struct cl_dsp_algo_info algo_info[CL_DSP_NUM_ALGOS_MAX + 1];
+ const struct cl_dsp_fw_desc *fw_desc;
+ const struct cl_dsp_mem_reg_desc *mem_reg_desc;
+ const struct cl_dsp_algo_params *algo_params;
+ struct cl_dsp_wt_desc *wt_desc;
+};
+
+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,
+ const char *wt_name_xm, const char *wt_name_ym,
+ const char *wt_file);
+int cl_dsp_firmware_parse(struct cl_dsp *dsp, const struct firmware *fw,
+ bool write_fw);
+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);
+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_memchunk *ch, int nbits);
+int cl_dsp_raw_write(struct cl_dsp *dsp, unsigned int reg,
+ const void *val, size_t val_len, size_t limit);
+int cl_dsp_fw_id_get(struct cl_dsp *dsp, unsigned int *id);
+int cl_dsp_fw_rev_get(struct cl_dsp *dsp, unsigned int *rev);
+
+#endif /* __CL_DSP_H */
diff --git a/cs40l26/cs40l26-codec.c b/cs40l26/cs40l26-codec.c
new file mode 100644
index 0000000..7115176
--- /dev/null
+++ b/cs40l26/cs40l26-codec.c
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// cs40l26.c -- ALSA SoC Audio driver for Cirrus Logic Haptic Device: CS40L26
+//
+// Copyright 2021 Cirrus Logic. Inc.
+
+#include <cs40l26.h>
+
+static const struct cs40l26_pll_sysclk_config cs40l26_pll_sysclk[] = {
+ {CS40L26_PLL_CLK_FRQ0, CS40L26_PLL_CLK_CFG0},
+ {CS40L26_PLL_CLK_FRQ1, CS40L26_PLL_CLK_CFG1},
+ {CS40L26_PLL_CLK_FRQ2, CS40L26_PLL_CLK_CFG2},
+ {CS40L26_PLL_CLK_FRQ3, CS40L26_PLL_CLK_CFG3},
+ {CS40L26_PLL_CLK_FRQ4, CS40L26_PLL_CLK_CFG4},
+ {CS40L26_PLL_CLK_FRQ5, CS40L26_PLL_CLK_CFG5},
+};
+
+static int cs40l26_get_clk_config(u32 freq, u8 *clk_cfg)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cs40l26_pll_sysclk); i++) {
+ if (cs40l26_pll_sysclk[i].freq == freq) {
+ *clk_cfg = cs40l26_pll_sysclk[i].clk_cfg;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int cs40l26_swap_ext_clk(struct cs40l26_codec *codec, u8 clk_src)
+{
+ struct regmap *regmap = codec->regmap;
+ struct device *dev = codec->dev;
+ u8 clk_cfg, clk_sel;
+ int ret;
+
+ switch (clk_src) {
+ case CS40L26_PLL_REFCLK_BCLK:
+ clk_sel = CS40L26_PLL_CLK_SEL_BCLK;
+
+ ret = cs40l26_get_clk_config(codec->sysclk_rate, &clk_cfg);
+ break;
+ case CS40L26_PLL_REFCLK_MCLK:
+ clk_sel = CS40L26_PLL_CLK_SEL_MCLK;
+
+ ret = cs40l26_get_clk_config(CS40L26_PLL_CLK_FRQ0, &clk_cfg);
+ break;
+ case CS40L26_PLL_REFCLK_FSYNC:
+ ret = -EPERM;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(dev, "Failed to get clock configuration\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_REFCLK_INPUT,
+ CS40L26_PLL_REFCLK_OPEN_LOOP_MASK, CS40L26_ENABLE <<
+ CS40L26_PLL_REFCLK_OPEN_LOOP_SHIFT);
+ if (ret) {
+ dev_err(dev, "Failed to set Open-Loop PLL\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_REFCLK_INPUT,
+ CS40L26_PLL_REFCLK_FREQ_MASK |
+ CS40L26_PLL_REFCLK_SEL_MASK, (clk_cfg <<
+ CS40L26_PLL_REFCLK_FREQ_SHIFT) | clk_sel);
+ if (ret) {
+ dev_err(dev, "Failed to update REFCLK input\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_REFCLK_INPUT,
+ CS40L26_PLL_REFCLK_OPEN_LOOP_MASK, CS40L26_DISABLE <<
+ CS40L26_PLL_REFCLK_OPEN_LOOP_SHIFT);
+ if (ret)
+ dev_err(dev, "Failed to close PLL loop\n");
+
+ return ret;
+}
+
+static int cs40l26_clk_en(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct cs40l26_codec *codec =
+ snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm));
+ struct cs40l26_private *cs40l26 = codec->core;
+ struct device *dev = cs40l26->dev;
+ int ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ mutex_lock(&cs40l26->lock);
+ cs40l26->asp_enable = true;
+ cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_ASP);
+ mutex_unlock(&cs40l26->lock);
+
+ ret = cs40l26_swap_ext_clk(codec, CS40L26_PLL_REFCLK_BCLK);
+ if (ret)
+ return ret;
+
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ ret = cs40l26_swap_ext_clk(codec, CS40L26_PLL_REFCLK_MCLK);
+ if (ret)
+ return ret;
+
+ mutex_lock(&cs40l26->lock);
+ cs40l26->asp_enable = false;
+ cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_STOPPED);
+ mutex_unlock(&cs40l26->lock);
+
+ break;
+ default:
+ dev_err(dev, "Invalid event: %d\n", event);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs40l26_a2h_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{ struct cs40l26_codec *codec =
+ snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm));
+ struct cs40l26_private *cs40l26 = codec->core;
+ struct device *dev = cs40l26->dev;
+ const struct firmware *fw;
+ int ret;
+ u32 reg;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "A2HEN", CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_A2H_ALGO_ID, &reg);
+ if (ret)
+ return ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ if (codec->tuning != codec->tuning_prev) {
+ ret = request_firmware(&fw, codec->bin_file, dev);
+ if (ret) {
+ dev_err(codec->dev, "Failed to request %s\n",
+ codec->bin_file);
+ return ret;
+ }
+
+ ret = cl_dsp_coeff_file_parse(cs40l26->dsp, fw);
+ if (ret)
+ return ret;
+ codec->tuning_prev = codec->tuning;
+ release_firmware(fw);
+
+ ret = cs40l26_ack_write(cs40l26,
+ CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_DSP_MBOX_CMD_A2H_REINIT,
+ CS40L26_DSP_MBOX_RESET);
+ if (ret)
+ return ret;
+ }
+ return regmap_write(cs40l26->regmap, reg, CS40L26_ENABLE);
+ case SND_SOC_DAPM_PRE_PMD:
+ return regmap_write(cs40l26->regmap, reg, CS40L26_DISABLE);
+ default:
+ dev_err(dev, "Invalid A2H event: %d\n", event);
+ return -EINVAL;
+ }
+}
+
+static int cs40l26_pcm_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{ struct cs40l26_codec *codec =
+ snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm));
+ struct cs40l26_private *cs40l26 = codec->core;
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ u32 asp_en_mask = CS40L26_ASP_TX1_EN_MASK | CS40L26_ASP_TX2_EN_MASK |
+ CS40L26_ASP_RX1_EN_MASK | CS40L26_ASP_RX2_EN_MASK;
+ u32 asp_enables;
+ int ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ ret = regmap_update_bits(regmap, CS40L26_DACPCM1_INPUT,
+ CS40L26_DATA_SRC_MASK, CS40L26_DATA_SRC_DSP1TX1);
+ if (ret) {
+ dev_err(dev, "Failed to set DAC PCM input\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_ASPTX1_INPUT,
+ CS40L26_DATA_SRC_MASK, CS40L26_DATA_SRC_DSP1TX1);
+ if (ret) {
+ dev_err(dev, "Failed to set ASPTX1 input\n");
+ return ret;
+ }
+
+ asp_enables = CS40L26_ENABLE | (CS40L26_ENABLE <<
+ CS40L26_ASP_TX2_EN_SHIFT) | (CS40L26_ENABLE <<
+ CS40L26_ASP_RX1_EN_SHIFT) | (CS40L26_ENABLE <<
+ CS40L26_ASP_RX2_EN_SHIFT);
+
+ ret = regmap_update_bits(regmap, CS40L26_ASP_ENABLES1,
+ asp_en_mask, asp_enables);
+ if (ret) {
+ dev_err(dev, "Failed to enable ASP channels\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_2,
+ CS40L26_BST_CTL_SEL_MASK,
+ CS40L26_BST_CTL_SEL_CLASSH);
+ if (ret) {
+ dev_err(dev, "Failed to select Class H BST CTRL\n");
+ return ret;
+ }
+
+ ret = cs40l26_class_h_set(cs40l26, true);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_DSP_MBOX_CMD_START_I2S,
+ CS40L26_DSP_MBOX_RESET);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_DSP_MBOX_CMD_STOP_I2S,
+ CS40L26_DSP_MBOX_RESET);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_class_h_set(cs40l26, false);
+ if (ret)
+ return ret;
+
+ asp_enables = CS40L26_DISABLE | (CS40L26_DISABLE <<
+ CS40L26_ASP_TX2_EN_SHIFT) | (CS40L26_DISABLE <<
+ CS40L26_ASP_RX1_EN_SHIFT) | (CS40L26_DISABLE <<
+ CS40L26_ASP_RX2_EN_SHIFT);
+
+ ret = regmap_update_bits(regmap, CS40L26_ASP_ENABLES1,
+ asp_en_mask, asp_enables);
+ if (ret) {
+ dev_err(dev, "Failed to clear ASPTX1 input\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_ASPTX1_INPUT,
+ CS40L26_DATA_SRC_MASK, CS40L26_DATA_SRC_VMON);
+ if (ret)
+ dev_err(dev, "Failed to set ASPTX1 input\n");
+ break;
+ default:
+ dev_err(dev, "Invalid PCM event: %d\n", event);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int cs40l26_tuning_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct cs40l26_codec *codec =
+ snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
+
+ ucontrol->value.enumerated.item[0] = codec->tuning;
+
+ return 0;
+}
+
+static int cs40l26_tuning_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct cs40l26_codec *codec =
+ snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
+ struct cs40l26_private *cs40l26 = codec->core;
+
+ if (ucontrol->value.enumerated.item[0] == codec->tuning)
+ return 0;
+
+ if (cs40l26->asp_enable)
+ return -EBUSY;
+
+ codec->tuning = ucontrol->value.enumerated.item[0];
+
+ memset(codec->bin_file, 0, PAGE_SIZE);
+ codec->bin_file[PAGE_SIZE - 1] = '\0';
+
+ if (codec->tuning > 0)
+ snprintf(codec->bin_file, PAGE_SIZE, "cs40l26-a2h%d.bin",
+ codec->tuning);
+ else
+ snprintf(codec->bin_file, PAGE_SIZE, "cs40l26-a2h.bin");
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new cs40l26_controls[] = {
+ SOC_SINGLE_EXT("A2H Tuning", 0, 0, CS40L26_A2H_MAX_TUNINGS, 0,
+ cs40l26_tuning_get, cs40l26_tuning_put),
+};
+
+static const char * const cs40l26_out_mux_texts[] = { "Off", "PCM", "A2H" };
+static SOC_ENUM_SINGLE_VIRT_DECL(cs40l26_out_mux_enum, cs40l26_out_mux_texts);
+static const struct snd_kcontrol_new cs40l26_out_mux =
+ SOC_DAPM_ENUM("Haptics Source", cs40l26_out_mux_enum);
+
+static const struct snd_soc_dapm_widget
+ cs40l26_dapm_widgets[] = {
+ SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l26_clk_en,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_PGA_E("PCM", SND_SOC_NOPM, 0, 0, NULL, 0, cs40l26_pcm_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MIXER_E("A2H", SND_SOC_NOPM, 0, 0, NULL, 0, cs40l26_a2h_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_MUX("Haptics Source", SND_SOC_NOPM, 0, 0,
+ &cs40l26_out_mux),
+ SND_SOC_DAPM_OUTPUT("OUT"),
+};
+
+static const struct snd_soc_dapm_route
+ cs40l26_dapm_routes[] = {
+ { "ASP Playback", NULL, "ASP PLL" },
+ { "ASPRX1", NULL, "ASP Playback" },
+ { "ASPRX2", NULL, "ASP Playback" },
+
+ { "PCM", NULL, "ASPRX1" },
+ { "PCM", NULL, "ASPRX2" },
+ { "A2H", NULL, "PCM" },
+
+ { "Haptics Source", "PCM", "PCM" },
+ { "Haptics Source", "A2H", "A2H" },
+ { "OUT", NULL, "Haptics Source" },
+};
+
+static int cs40l26_component_set_sysclk(struct snd_soc_component *component,
+ int clk_id, int source, unsigned int freq, int dir)
+{
+ struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component);
+ struct device *dev = codec->dev;
+ u8 clk_cfg;
+ int ret;
+
+ ret = cs40l26_get_clk_config((u32) (CS40L26_PLL_CLK_FREQ_MASK & freq),
+ &clk_cfg);
+ if (ret) {
+ dev_err(dev, "Invalid Clock Frequency: %u Hz\n", freq);
+ return ret;
+ }
+
+ if (clk_id != 0) {
+ dev_err(dev, "Invalid Input Clock (ID: %d)\n", clk_id);
+ return -EINVAL;
+ }
+
+ codec->sysclk_rate = (u32) (CS40L26_PLL_CLK_FREQ_MASK & freq);
+
+ return 0;
+}
+
+static int cs40l26_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct cs40l26_codec *codec =
+ snd_soc_component_get_drvdata(codec_dai->component);
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) {
+ dev_err(codec->dev, "Device can not be master\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ codec->daifmt = 0;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ codec->daifmt = CS40L26_ASP_FSYNC_INV_MASK;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ codec->daifmt = CS40L26_ASP_BCLK_INV_MASK;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ codec->daifmt = CS40L26_ASP_FSYNC_INV_MASK |
+ CS40L26_ASP_BCLK_INV_MASK;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid DAI clock INV\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ codec->daifmt |= ((CS40L26_ASP_FMT_TDM1_DSPA <<
+ CS40L26_ASP_FMT_SHIFT) & CS40L26_ASP_FMT_MASK);
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ codec->daifmt |= ((CS40L26_ASP_FMT_I2S <<
+ CS40L26_ASP_FMT_SHIFT) & CS40L26_ASP_FMT_MASK);
+ break;
+ default:
+ dev_err(codec->dev, "Invalid DAI format: 0x%X\n",
+ fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs40l26_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct cs40l26_codec *codec =
+ snd_soc_component_get_drvdata(dai->component);
+ u8 asp_rx_wl, asp_rx_width;
+ int ret;
+
+ pm_runtime_get_sync(codec->dev);
+
+ asp_rx_wl = (u8) (params_width(params) & 0xFF);
+ ret = regmap_update_bits(codec->regmap, CS40L26_ASP_DATA_CONTROL5,
+ CS40L26_ASP_RX_WL_MASK, asp_rx_wl);
+ if (ret) {
+ dev_err(codec->dev, "Failed to update ASP RX WL\n");
+ goto err_pm;
+ }
+
+ if (!codec->tdm_width)
+ asp_rx_width = asp_rx_wl;
+ else
+ asp_rx_width = (u8) (codec->tdm_width & 0xFF);
+
+ codec->daifmt |= ((asp_rx_width << CS40L26_ASP_RX_WIDTH_SHIFT) &
+ CS40L26_ASP_RX_WIDTH_MASK);
+
+ ret = regmap_update_bits(codec->regmap, CS40L26_ASP_CONTROL2,
+ CS40L26_ASP_FSYNC_INV_MASK | CS40L26_ASP_BCLK_INV_MASK |
+ CS40L26_ASP_FMT_MASK | CS40L26_ASP_RX_WIDTH_MASK,
+ codec->daifmt);
+ if (ret)
+ dev_err(codec->dev, "Failed to update ASP RX width\n");
+
+err_pm:
+ pm_runtime_mark_last_busy(codec->dev);
+ pm_runtime_put_autosuspend(codec->dev);
+
+ return ret;
+}
+
+static int cs40l26_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct cs40l26_codec *codec =
+ snd_soc_component_get_drvdata(dai->component);
+
+ if (dai->id != 0) {
+ dev_err(codec->dev, "Invalid DAI ID: %d\n", dai->id);
+ return -EINVAL;
+ }
+
+ codec->tdm_width = slot_width;
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cs40l26_dai_ops = {
+ .set_fmt = cs40l26_set_dai_fmt,
+ .set_tdm_slot = cs40l26_set_tdm_slot,
+ .hw_params = cs40l26_pcm_hw_params,
+};
+
+static struct snd_soc_dai_driver cs40l26_dai[] = {
+ {
+ .name = "cs40l26-pcm",
+ .id = 0,
+ .playback = {
+ .stream_name = "ASP Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = CS40L26_FORMATS,
+ },
+ .ops = &cs40l26_dai_ops,
+ .symmetric_rates = 1,
+ },
+};
+
+static int cs40l26_codec_probe(struct snd_soc_component *component)
+{
+ struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component);
+
+ codec->bin_file = devm_kzalloc(codec->dev, PAGE_SIZE, GFP_KERNEL);
+ if (!codec->bin_file)
+ return -ENOMEM;
+
+ codec->bin_file[PAGE_SIZE - 1] = '\0';
+ snprintf(codec->bin_file, PAGE_SIZE, CS40L26_A2H_TUNING_FILE_NAME);
+
+ /* Default audio SCLK frequency */
+ codec->sysclk_rate = CS40L26_PLL_CLK_FRQ1;
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_codec_dev_cs40l26 = {
+ .probe = cs40l26_codec_probe,
+ .set_sysclk = cs40l26_component_set_sysclk,
+
+ .dapm_widgets = cs40l26_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs40l26_dapm_widgets),
+ .dapm_routes = cs40l26_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(cs40l26_dapm_routes),
+ .controls = cs40l26_controls,
+ .num_controls = ARRAY_SIZE(cs40l26_controls),
+};
+
+static int cs40l26_codec_driver_probe(struct platform_device *pdev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(pdev->dev.parent);
+ struct cs40l26_codec *codec;
+ int ret;
+
+ codec = devm_kzalloc(&pdev->dev, sizeof(struct cs40l26_codec),
+ GFP_KERNEL);
+ if (!codec)
+ return -ENOMEM;
+
+ codec->core = cs40l26;
+ codec->regmap = cs40l26->regmap;
+ codec->dev = &pdev->dev;
+
+ platform_set_drvdata(pdev, codec);
+
+ pm_runtime_enable(&pdev->dev);
+
+ ret = snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l26,
+ cs40l26_dai, ARRAY_SIZE(cs40l26_dai));
+ if (ret < 0)
+ dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
+
+ return ret;
+}
+
+static int cs40l26_codec_driver_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ snd_soc_unregister_component(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver cs40l26_codec_driver = {
+ .driver = {
+ .name = "cs40l26-codec",
+ },
+ .probe = cs40l26_codec_driver_probe,
+ .remove = cs40l26_codec_driver_remove,
+};
+module_platform_driver(cs40l26_codec_driver);
+
+MODULE_DESCRIPTION("ASoC CS40L26 driver");
+MODULE_AUTHOR("Fred Treven <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:cs40l26-codec");
diff --git a/cs40l26/cs40l26-i2c.c b/cs40l26/cs40l26-i2c.c
new file mode 100644
index 0000000..c3fe8a4
--- /dev/null
+++ b/cs40l26/cs40l26-i2c.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// cs40l26-i2c.c -- CS40L26 I2C Driver
+//
+// Copyright 2021 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 <linux/mfd/cs40l26.h>
+
+static const struct i2c_device_id cs40l26_id_i2c[] = {
+ {"cs40l26a", 0},
+ {"cs40l26b", 1},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, cs40l26_id_i2c);
+
+static int cs40l26_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct cs40l26_private *cs40l26;
+ struct device *dev = &client->dev;
+ struct cs40l26_platform_data *pdata = dev_get_platdata(dev);
+
+ cs40l26 = devm_kzalloc(dev, sizeof(struct cs40l26_private), GFP_KERNEL);
+ if (!cs40l26)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, cs40l26);
+
+ cs40l26->regmap = devm_regmap_init_i2c(client, &cs40l26_regmap);
+ if (IS_ERR(cs40l26->regmap)) {
+ ret = PTR_ERR(cs40l26->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", ret);
+ return ret;
+ }
+
+ cs40l26->dev = dev;
+ cs40l26->irq = client->irq;
+
+ return cs40l26_probe(cs40l26, pdata);
+}
+
+static int cs40l26_i2c_remove(struct i2c_client *client)
+{
+ struct cs40l26_private *cs40l26 = i2c_get_clientdata(client);
+
+ return cs40l26_remove(cs40l26);
+}
+
+static struct i2c_driver cs40l26_i2c_driver = {
+ .driver = {
+ .name = "cs40l26",
+ .of_match_table = cs40l26_of_match,
+ .pm = &cs40l26_pm_ops,
+ },
+ .id_table = cs40l26_id_i2c,
+ .probe = cs40l26_i2c_probe,
+ .remove = cs40l26_i2c_remove,
+};
+
+module_i2c_driver(cs40l26_i2c_driver);
+
+MODULE_DESCRIPTION("CS40L26 I2C Driver");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/cs40l26/cs40l26-spi.c b/cs40l26/cs40l26-spi.c
new file mode 100644
index 0000000..ceb1188
--- /dev/null
+++ b/cs40l26/cs40l26-spi.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// cs40l26-spi.c -- CS40L26 SPI Driver
+//
+// Copyright 2021 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 <linux/mfd/cs40l26.h>
+
+static const struct spi_device_id cs40l26_id_spi[] = {
+ {"cs40l26a", 0},
+ {"cs40l26b", 1},
+ {}
+};
+
+MODULE_DEVICE_TABLE(spi, cs40l26_id_spi);
+
+static int cs40l26_spi_probe(struct spi_device *spi)
+{
+ int ret;
+ struct cs40l26_private *cs40l26;
+ struct device *dev = &spi->dev;
+ struct cs40l26_platform_data *pdata = dev_get_platdata(&spi->dev);
+
+ cs40l26 = devm_kzalloc(dev, sizeof(struct cs40l26_private), GFP_KERNEL);
+ if (!cs40l26)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, cs40l26);
+
+ cs40l26->regmap = devm_regmap_init_spi(spi, &cs40l26_regmap);
+ if (IS_ERR(cs40l26->regmap)) {
+ ret = PTR_ERR(cs40l26->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", ret);
+ return ret;
+ }
+
+ cs40l26->dev = dev;
+ cs40l26->irq = spi->irq;
+
+ return cs40l26_probe(cs40l26, pdata);
+}
+
+static int cs40l26_spi_remove(struct spi_device *spi)
+{
+ struct cs40l26_private *cs40l26 = spi_get_drvdata(spi);
+
+ return cs40l26_remove(cs40l26);
+}
+
+static struct spi_driver cs40l26_spi_driver = {
+ .driver = {
+ .name = "cs40l26",
+ .of_match_table = cs40l26_of_match,
+ .pm = &cs40l26_pm_ops,
+ },
+
+ .id_table = cs40l26_id_spi,
+ .probe = cs40l26_spi_probe,
+ .remove = cs40l26_spi_remove,
+};
+
+module_spi_driver(cs40l26_spi_driver);
+
+MODULE_DESCRIPTION("CS40L26 SPI Driver");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/cs40l26/cs40l26-sysfs.c b/cs40l26/cs40l26-sysfs.c
new file mode 100644
index 0000000..f0b877b
--- /dev/null
+++ b/cs40l26/cs40l26-sysfs.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// cs40l26-syfs.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and
+// Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+//
+// Copyright 2021 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 <linux/mfd/cs40l26.h>
+
+static ssize_t cs40l26_dsp_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ char str[CS40L26_DSP_STATE_STR_LEN];
+ u8 dsp_state;
+ int ret;
+
+ pm_runtime_get_sync(cs40l26->dev);
+
+ ret = cs40l26_dsp_state_get(cs40l26, &dsp_state);
+ if (ret)
+ return ret;
+
+ switch (dsp_state) {
+ case CS40L26_DSP_STATE_HIBERNATE:
+ strncpy(str, "Hibernate", CS40L26_DSP_STATE_STR_LEN);
+ break;
+ case CS40L26_DSP_STATE_SHUTDOWN:
+ strncpy(str, "Shutdown", CS40L26_DSP_STATE_STR_LEN);
+ break;
+ case CS40L26_DSP_STATE_STANDBY:
+ strncpy(str, "Standby", CS40L26_DSP_STATE_STR_LEN);
+ break;
+ case CS40L26_DSP_STATE_ACTIVE:
+ strncpy(str, "Active", CS40L26_DSP_STATE_STR_LEN);
+ break;
+ default:
+ dev_err(cs40l26->dev, "DSP state %u is invalid\n", dsp_state);
+ return -EINVAL;
+ }
+
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ return snprintf(buf, PAGE_SIZE, "DSP state: %s\n", str);
+}
+static DEVICE_ATTR(dsp_state, 0660, cs40l26_dsp_state_show, NULL);
+
+static ssize_t cs40l26_halo_heartbeat_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ u32 reg, halo_heartbeat;
+ int ret;
+
+ pm_runtime_get_sync(cs40l26->dev);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "HALO_HEARTBEAT",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_FW_ID, &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(cs40l26->regmap, reg, &halo_heartbeat);
+ if (ret)
+ return ret;
+
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", halo_heartbeat);
+}
+static DEVICE_ATTR(halo_heartbeat, 0660, cs40l26_halo_heartbeat_show, NULL);
+
+static ssize_t cs40l26_fw_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+
+ pm_runtime_get_sync(cs40l26->dev);
+
+ if (cs40l26->fw_mode != CS40L26_FW_MODE_ROM
+ && cs40l26->fw_mode != CS40L26_FW_MODE_RAM) {
+ dev_err(cs40l26->dev, "Invalid firmware mode: %u\n",
+ cs40l26->fw_mode);
+ return -EINVAL;
+ }
+
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ return snprintf(buf, PAGE_SIZE, "Firmware is in %s mode\n",
+ cs40l26->fw_mode == CS40L26_FW_MODE_ROM ? "ROM" : "RAM");
+}
+static DEVICE_ATTR(fw_mode, 0660, cs40l26_fw_mode_show, NULL);
+
+static ssize_t cs40l26_pm_timeout_ms_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ u32 timeout_ms;
+ int ret;
+
+ pm_runtime_get_sync(cs40l26->dev);
+
+ ret = cs40l26_pm_timeout_ms_get(cs40l26, &timeout_ms);
+
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ if (ret)
+ return ret;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", timeout_ms);
+}
+
+static ssize_t cs40l26_pm_timeout_ms_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ u32 timeout_ms;
+ int ret;
+
+ ret = kstrtou32(buf, 10, &timeout_ms);
+ if (ret || timeout_ms < CS40L26_PM_TIMEOUT_MS_MIN)
+ return -EINVAL;
+
+ pm_runtime_get_sync(cs40l26->dev);
+
+ ret = cs40l26_pm_timeout_ms_set(cs40l26, timeout_ms);
+
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR(pm_timeout_ms, 0660, cs40l26_pm_timeout_ms_show,
+ cs40l26_pm_timeout_ms_store);
+
+static ssize_t cs40l26_vibe_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ int ret = 0;
+ char str[10];
+
+ mutex_lock(&cs40l26->lock);
+
+ switch (cs40l26->vibe_state) {
+ case CS40L26_VIBE_STATE_STOPPED:
+ strncpy(str, "Stopped", 10);
+ break;
+ case CS40L26_VIBE_STATE_HAPTIC:
+ strncpy(str, "Haptic", 10);
+ break;
+ case CS40L26_VIBE_STATE_ASP:
+ strncpy(str, "ASP", 10);
+ break;
+ default:
+ dev_err(cs40l26->dev, "Invalid vibe state: %u\n",
+ cs40l26->vibe_state);
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&cs40l26->lock);
+
+ if (ret)
+ return ret;
+ else
+ return snprintf(buf, PAGE_SIZE, "Vibe state: %s\n", str);
+}
+static DEVICE_ATTR(vibe_state, 0660, cs40l26_vibe_state_show, NULL);
+
+static struct attribute *cs40l26_dev_attrs[] = {
+ &dev_attr_dsp_state.attr,
+ &dev_attr_halo_heartbeat.attr,
+ &dev_attr_fw_mode.attr,
+ &dev_attr_pm_timeout_ms.attr,
+ &dev_attr_vibe_state.attr,
+ NULL,
+};
+
+struct attribute_group cs40l26_dev_attr_group = {
+ .name = "default",
+ .attrs = cs40l26_dev_attrs,
+};
diff --git a/cs40l26/cs40l26-tables.c b/cs40l26/cs40l26-tables.c
new file mode 100644
index 0000000..e35ce3c
--- /dev/null
+++ b/cs40l26/cs40l26-tables.c
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// cs40l26-tables.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and
+// Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+//
+// Copyright 2021 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 <linux/mfd/cs40l26.h>
+
+const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1] = {
+ { .compatible = "cirrus,cs40l26a" },
+ { .compatible = "cirrus,cs40l26b" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, cs40l26_of_match);
+
+const struct regmap_config cs40l26_regmap = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .max_register = CS40L26_LASTREG,
+ .num_reg_defaults = 0,
+ .precious_reg = cs40l26_precious_reg,
+ .readable_reg = cs40l26_readable_reg,
+ .volatile_reg = cs40l26_volatile_reg,
+ .cache_type = REGCACHE_NONE,
+};
+
+const struct dev_pm_ops cs40l26_pm_ops = {
+ SET_RUNTIME_PM_OPS(cs40l26_suspend, cs40l26_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend, cs40l26_sys_resume)
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend_noirq,
+ cs40l26_sys_resume_noirq)
+};
+
+const char * const cs40l26_ram_coeff_files[3] = {
+ CS40L26_WT_FILE_NAME,
+ CS40L26_SVC_TUNING_FILE_NAME,
+ CS40L26_A2H_TUNING_FILE_NAME,
+};
+
+const u8 cs40l26_pseq_v2_op_sizes[CS40L26_PSEQ_V2_NUM_OPS][2] = {
+ { CS40L26_PSEQ_V2_OP_WRITE_REG_FULL,
+ CS40L26_PSEQ_V2_OP_WRITE_REG_FULL_WORDS},
+ { CS40L26_PSEQ_V2_OP_WRITE_FIELD,
+ CS40L26_PSEQ_V2_OP_WRITE_FIELD_WORDS},
+ { CS40L26_PSEQ_V2_OP_WRITE_REG_ADDR8,
+ CS40L26_PSEQ_V2_OP_WRITE_REG_ADDR8_WORDS},
+ { CS40L26_PSEQ_V2_OP_WRITE_REG_INCR,
+ CS40L26_PSEQ_V2_OP_WRITE_REG_INCR_WORDS},
+ { CS40L26_PSEQ_V2_OP_WRITE_REG_L16,
+ CS40L26_PSEQ_V2_OP_WRITE_REG_L16_WORDS},
+ { CS40L26_PSEQ_V2_OP_WRITE_REG_H16,
+ CS40L26_PSEQ_V2_OP_WRITE_REG_H16_WORDS},
+ { CS40L26_PSEQ_V2_OP_DELAY,
+ CS40L26_PSEQ_V2_OP_DELAY_WORDS},
+ { CS40L26_PSEQ_V2_OP_END,
+ CS40L26_PSEQ_V2_OP_END_WORDS},
+};
+
+struct regulator_bulk_data cs40l26_supplies[CS40L26_NUM_SUPPLIES] = {
+ { .supply = CS40L26_VP_SUPPLY_NAME },
+ { .supply = CS40L26_VA_SUPPLY_NAME },
+};
+
+const struct mfd_cell cs40l26_devs[CS40L26_NUM_MFD_DEVS] = {
+ { .name = "cs40l26-codec" },
+};
+
+bool cs40l26_precious_reg(struct device *dev, unsigned int reg)
+{
+ return false;
+}
+EXPORT_SYMBOL(cs40l26_precious_reg);
+
+bool cs40l26_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return false;
+}
+EXPORT_SYMBOL(cs40l26_volatile_reg);
+
+bool cs40l26_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS40L26_DEVID:
+ case CS40L26_REVID:
+ case CS40L26_FABID:
+ case CS40L26_RELID:
+ case CS40L26_OTPID:
+ case CS40L26_SFT_RESET:
+ case CS40L26_TEST_KEY_CTRL:
+ case CS40L26_USER_KEY_CTRL:
+ case CS40L26_CTRL_ASYNC0:
+ case CS40L26_CTRL_ASYNC1:
+ case CS40L26_CTRL_ASYNC2:
+ case CS40L26_CTRL_ASYNC3:
+ case CS40L26_CIF_MON1:
+ case CS40L26_CIF_MON2:
+ case CS40L26_CIF_MON_PADDR:
+ case CS40L26_CTRL_IF_SPARE1:
+ case CS40L26_CTRL_IF_I2C:
+ case CS40L26_CTRL_IF_I2C_1_CONTROL:
+ case CS40L26_CTRL_IF_I2C_1_BROADCAST:
+ case CS40L26_APB_MSTR_DSP_BRIDGE_ERR:
+ case CS40L26_CIF1_BRIDGE_ERR:
+ case CS40L26_CIF2_BRIDGE_ERR:
+ case CS40L26_OTP_CTRL0:
+ case CS40L26_OTP_CTRL1:
+ case CS40L26_OTP_CTRL3:
+ case CS40L26_OTP_CTRL4:
+ case CS40L26_OTP_CTRL5:
+ case CS40L26_OTP_CTRL6:
+ case CS40L26_OTP_CTRL7:
+ case CS40L26_OTP_CTRL8:
+ case CS40L26_GLOBAL_ENABLES:
+ case CS40L26_BLOCK_ENABLES:
+ case CS40L26_BLOCK_ENABLES2:
+ case CS40L26_GLOBAL_OVERRIDES:
+ case CS40L26_GLOBAL_SYNC:
+ case CS40L26_GLOBAL_STATUS:
+ case CS40L26_DISCH_FILT:
+ case CS40L26_OSC_TRIM:
+ case CS40L26_ERROR_RELEASE:
+ case CS40L26_PLL_OVERRIDE:
+ case CS40L26_BIAS_PTE_MODE_CONTROL:
+ case CS40L26_SCL_PAD_CONTROL:
+ case CS40L26_SDA_PAD_CONTROL:
+ case CS40L26_LRCK_PAD_CONTROL:
+ case CS40L26_SCLK_PAD_CONTROL:
+ case CS40L26_SDIN_PAD_CONTROL:
+ case CS40L26_SDOUT_PAD_CONTROL:
+ case CS40L26_GPIO_PAD_CONTROL:
+ case CS40L26_MDSYNC_PAD_CONTROL:
+ case CS40L26_JTAG_CONTROL:
+ case CS40L26_GPIO1_PAD_CONTROL:
+ case CS40L26_GPIO_GLOBAL_ENABLE_CONTROL:
+ case CS40L26_GPIO_LEVELSHIFT_BYPASS:
+ case CS40L26_I2C_ADDR_DETECT_CNTL0:
+ case CS40L26_I2C_ADDR_DET_STATUS0:
+ case CS40L26_DEVID_METAL:
+ case CS40L26_PWRMGT_CTL:
+ case CS40L26_WAKESRC_CTL:
+ case CS40L26_WAKEI2C_CTL:
+ case CS40L26_PWRMGT_STS:
+ case CS40L26_PWRMGT_RST:
+ case CS40L26_REFCLK_INPUT:
+ case CS40L26_DSP_CLOCK_GEARING:
+ case CS40L26_GLOBAL_SAMPLE_RATE:
+ case CS40L26_DATA_FS_SEL:
+ case CS40L26_FREE_RUN_FORCE:
+ case CS40L26_ASP_RATE_DOUBLE_CONTROL0:
+ case CS40L26_NZ_AUDIO_DETECT0:
+ case CS40L26_NZ_AUDIO_DETECT1:
+ case CS40L26_NZ_AUDIO_DETECT2:
+ case CS40L26_PLL_REFCLK_DETECT_0:
+ case CS40L26_SP_SCLK_CLOCKING:
+ case CS40L26_CONFIG0:
+ case CS40L26_CONFIG1:
+ case CS40L26_CONFIG2:
+ case CS40L26_FS_MON_0:
+ case CS40L26_FS_MON_1:
+ case CS40L26_FS_MON_2:
+ case CS40L26_FS_MON_OVERRIDE:
+ case CS40L26_DFT:
+ case CS40L26_ANALOG_ADC_CONTROLS:
+ case CS40L26_SPK_CHOP_CLK_CONTROLS:
+ case CS40L26_DSP1_SAMPLE_RATE_RX1:
+ case CS40L26_DSP1_SAMPLE_RATE_RX2:
+ case CS40L26_DSP1_SAMPLE_RATE_TX1:
+ case CS40L26_DSP1_SAMPLE_RATE_TX2:
+ case CS40L26_CLOCK_PHASE:
+ case CS40L26_USER_CONTROL:
+ case CS40L26_CONFIG_RATES:
+ case CS40L26_LOOP_PARAMETERS:
+ case CS40L26_LDOA_CONTROL:
+ case CS40L26_DCO_CONTROL:
+ case CS40L26_MISC_CONTROL:
+ case CS40L26_LOOP_OVERRIDES:
+ case CS40L26_DCO_CTRL_OVERRIDES:
+ case CS40L26_CONTROL_READ:
+ case CS40L26_CONTROL_READ_2:
+ case CS40L26_DCO_CAL_CONTROL_1:
+ case CS40L26_DCO_CAL_CONTROL_2:
+ case CS40L26_DCO_CAL_STATUS:
+ case CS40L26_SYNC_TX_RX_ENABLES:
+ case CS40L26_SYNC_POWER_CTL:
+ case CS40L26_SYNC_SW_TX_ID:
+ case CS40L26_SYNC_SW_RX:
+ case CS40L26_SYNC_SW_TX:
+ case CS40L26_SYNC_LSW_RX:
+ case CS40L26_SYNC_LSW_TX:
+ case CS40L26_SYNC_SW_DATA_TX_STATUS:
+ case CS40L26_SYNC_SW_DATA_RX_STATUS:
+ case CS40L26_SYNC_ERROR_STATUS:
+ case CS40L26_MDSYNC_SYNC_RX_DECODE_CTL_1:
+ case CS40L26_MDSYNC_SYNC_RX_DECODE_CTL_2:
+ case CS40L26_MDSYNC_SYNC_TX_ENCODE_CTL:
+ case CS40L26_MDSYNC_SYNC_IDLE_STATE_CTL:
+ case CS40L26_MDSYNC_SYNC_SLEEP_STATE_CTL:
+ case CS40L26_MDSYNC_SYNC_TYPE:
+ case CS40L26_MDSYNC_SYNC_TRIGGER:
+ case CS40L26_MDSYNC_SYNC_PTE0:
+ case CS40L26_MDSYNC_SYNC_PTE1:
+ case CS40L26_SYNC_PTE2:
+ case CS40L26_SYNC_PTE3:
+ case CS40L26_VBST_CTL_1:
+ case CS40L26_VBST_CTL_2:
+ case CS40L26_BST_IPK_CTL:
+ case CS40L26_SOFT_RAMP:
+ case CS40L26_BST_LOOP_COEFF:
+ case CS40L26_LBST_SLOPE:
+ case CS40L26_BST_SW_FREQ:
+ case CS40L26_BST_DCM_CTL:
+ case CS40L26_DCM_FORCE:
+ case CS40L26_VBST_OVP:
+ case CS40L26_BST_DCR:
+ case CS40L26_VPI_LIMIT_MODE:
+ case CS40L26_VPI_LIMITING:
+ case CS40L26_VPI_VP_THLDS:
+ case CS40L26_VPI_TRACKING:
+ case CS40L26_VPI_TRIG_TIME:
+ case CS40L26_VPI_TRIG_STEPS:
+ case CS40L26_VPI_STATES:
+ case CS40L26_VPI_OUTPUT_RATE:
+ case CS40L26_VMON_IMON_VOL_POL:
+ case CS40L26_SPKMON_RATE_SEL:
+ case CS40L26_MONITOR_FILT:
+ case CS40L26_IMON_COMP:
+ case CS40L26_WARN_LIMIT_THRESHOLD:
+ case CS40L26_CONFIGURATION:
+ case CS40L26_STATUS:
+ case CS40L26_ENABLES_AND_CODES_ANA:
+ case CS40L26_ENABLES_AND_CODES_DIG:
+ case CS40L26_CALIBR_STATUS:
+ case CS40L26_TEMP_RESYNC:
+ case CS40L26_ERROR_LIMIT_THLD_OVERRIDE:
+ case CS40L26_WARN_LIMIT_THLD_OVERRIDE:
+ case CS40L26_CALIBR_ROUTINE_CONFIGURATIONS:
+ case CS40L26_STATUS_FS:
+ case CS40L26_ASP_ENABLES1:
+ case CS40L26_ASP_CONTROL1:
+ case CS40L26_ASP_CONTROL2:
+ case CS40L26_ASP_CONTROL3:
+ case CS40L26_ASP_FRAME_CONTROL1:
+ case CS40L26_ASP_FRAME_CONTROL5:
+ case CS40L26_ASP_DATA_CONTROL1:
+ case CS40L26_ASP_DATA_CONTROL5:
+ case CS40L26_ASP_LATENCY1:
+ case CS40L26_ASP_CONTROL4:
+ case CS40L26_ASP_FSYNC_CONTROL1:
+ case CS40L26_ASP_FSYNC_CONTROL2:
+ case CS40L26_ASP_FSYNC_STATUS1:
+ case CS40L26_DACPCM1_INPUT:
+ case CS40L26_DACMETA1_INPUT:
+ case CS40L26_DACPCM2_INPUT:
+ case CS40L26_ASPTX1_INPUT:
+ case CS40L26_ASPTX2_INPUT:
+ case CS40L26_ASPTX3_INPUT:
+ case CS40L26_ASPTX4_INPUT:
+ case CS40L26_DSP1RX1_INPUT:
+ case CS40L26_DSP1RX2_INPUT:
+ case CS40L26_DSP1RX3_INPUT:
+ case CS40L26_DSP1RX4_INPUT:
+ case CS40L26_DSP1RX5_INPUT:
+ case CS40L26_DSP1RX6_INPUT:
+ case CS40L26_NGATE1_INPUT:
+ case CS40L26_NGATE2_INPUT:
+ case CS40L26_SPARE_CP_BITS_0:
+ case CS40L26_VIS_ADDR_CNTL1_4:
+ case CS40L26_VIS_ADDR_CNTL5_8:
+ case CS40L26_VIS_ADDR_CNTL9_12:
+ case CS40L26_VIS_ADDR_CNTL13_16:
+ case CS40L26_VIS_ADDR_CNTL_17_20:
+ case CS40L26_BLOCK_SEL_CNTL0_3:
+ case CS40L26_BIT_SEL_CNTL:
+ case CS40L26_ANALOG_VIS_CNTL:
+ case CS40L26_AMP_CTRL:
+ case CS40L26_VPBR_CONFIG:
+ case CS40L26_VBBR_CONFIG:
+ case CS40L26_VPBR_STATUS:
+ case CS40L26_VBBR_STATUS:
+ case CS40L26_OTW_CONFIG:
+ case CS40L26_AMP_ERROR_VOL_SEL:
+ case CS40L26_VPBR_FILTER_CONFIG:
+ case CS40L26_VBBR_FILTER_CONFIG:
+ case CS40L26_VOL_STATUS_TO_DSP:
+ case CS40L26_AMP_GAIN:
+ case CS40L26_SVC_CTRL:
+ case CS40L26_SVC_SER_R:
+ case CS40L26_SVC_R_LPF:
+ case CS40L26_SVC_FILT_CFG:
+ case CS40L26_SVC_SER_L_CTRL:
+ case CS40L26_SVC_SER_C_CTRL:
+ case CS40L26_SVC_PAR_RLC_SF:
+ case CS40L26_SVC_PAR_RLC_C1:
+ case CS40L26_SVC_PAR_RLC_C2:
+ case CS40L26_SVC_PAR_RLC_B1:
+ case CS40L26_SVC_GAIN:
+ case CS40L26_SVC_STATUS:
+ case CS40L26_SVC_IMON_SF:
+ case CS40L26_DAC_MSM_CONFIG:
+ case CS40L26_TST_DAC_MSM_CONFIG:
+ case CS40L26_ALIVE_DCIN_WD:
+ case CS40L26_IRQ1_CFG:
+ case CS40L26_IRQ1_STATUS:
+ case CS40L26_IRQ1_EINT_1:
+ case CS40L26_IRQ1_EINT_2:
+ case CS40L26_IRQ1_EINT_3:
+ case CS40L26_IRQ1_EINT_4:
+ case CS40L26_IRQ1_EINT_5:
+ case CS40L26_IRQ1_STS_1:
+ case CS40L26_IRQ1_STS_2:
+ case CS40L26_IRQ1_STS_3:
+ case CS40L26_IRQ1_STS_4:
+ case CS40L26_IRQ1_STS_5:
+ case CS40L26_IRQ1_MASK_1:
+ case CS40L26_IRQ1_MASK_2:
+ case CS40L26_IRQ1_MASK_3:
+ case CS40L26_IRQ1_MASK_4:
+ case CS40L26_IRQ1_MASK_5:
+ case CS40L26_IRQ1_FRC_1:
+ case CS40L26_IRQ1_FRC_2:
+ case CS40L26_IRQ1_FRC_3:
+ case CS40L26_IRQ1_FRC_4:
+ case CS40L26_IRQ1_FRC_5:
+ case CS40L26_IRQ1_EDGE_1:
+ case CS40L26_IRQ1_POL_1:
+ case CS40L26_IRQ1_POL_2:
+ case CS40L26_IRQ1_POL_3:
+ case CS40L26_IRQ1_POL_5:
+ case CS40L26_IRQ1_DB_2:
+ case CS40L26_GPIO_STATUS1:
+ case CS40L26_GPIO_FORCE:
+ case CS40L26_GPIO1_CTRL1:
+ case CS40L26_GPIO2_CTRL1:
+ case CS40L26_GPIO3_CTRL1:
+ case CS40L26_GPIO4_CTRL1:
+ case CS40L26_MIXER_NGATE_CFG:
+ case CS40L26_MIXER_NGATE_CH1_CFG:
+ case CS40L26_MIXER_NGATE_CH2_CFG:
+ case CS40L26_DSP_MBOX_1:
+ case CS40L26_DSP_MBOX_2:
+ case CS40L26_DSP_MBOX_3:
+ case CS40L26_DSP_MBOX_4:
+ case CS40L26_DSP_MBOX_5:
+ case CS40L26_DSP_MBOX_6:
+ case CS40L26_DSP_MBOX_7:
+ case CS40L26_DSP_MBOX_8:
+ case CS40L26_DSP_VIRTUAL1_MBOX_1:
+ case CS40L26_DSP_VIRTUAL1_MBOX_2:
+ case CS40L26_DSP_VIRTUAL1_MBOX_3:
+ case CS40L26_DSP_VIRTUAL1_MBOX_4:
+ case CS40L26_DSP_VIRTUAL1_MBOX_5:
+ case CS40L26_DSP_VIRTUAL1_MBOX_6:
+ case CS40L26_DSP_VIRTUAL1_MBOX_7:
+ case CS40L26_DSP_VIRTUAL1_MBOX_8:
+ case CS40L26_DSP_VIRTUAL2_MBOX_1:
+ case CS40L26_DSP_VIRTUAL2_MBOX_2:
+ case CS40L26_DSP_VIRTUAL2_MBOX_3:
+ case CS40L26_DSP_VIRTUAL2_MBOX_4:
+ case CS40L26_DSP_VIRTUAL2_MBOX_5:
+ case CS40L26_DSP_VIRTUAL2_MBOX_6:
+ case CS40L26_DSP_VIRTUAL2_MBOX_7:
+ case CS40L26_DSP_VIRTUAL2_MBOX_8:
+ case CS40L26_TIMER1_CONTROL:
+ case CS40L26_TIMER1_COUNT_PRESET:
+ case CS40L26_TIMER1_START_AND_STOP:
+ case CS40L26_TIMER1_STATUS:
+ case CS40L26_TIMER1_COUNT_READBACK:
+ case CS40L26_TIMER1_DSP_CLOCK_CONFIG:
+ case CS40L26_TIMER1_DSP_CLOCK_STATUS:
+ case CS40L26_TIMER2_CONTROL:
+ case CS40L26_TIMER2_COUNT_PRESET:
+ case CS40L26_TIMER2_START_AND_STOP:
+ case CS40L26_TIMER2_STATUS:
+ case CS40L26_TIMER2_COUNT_READBACK:
+ case CS40L26_TIMER2_DSP_CLOCK_CONFIG:
+ case CS40L26_TIMER2_DSP_CLOCK_STATUS:
+ case CS40L26_DFT_JTAG_CTRL:
+ case CS40L26_TEMP_CAL2:
+ case CS40L26_OTP_MEM0 ...
+CS40L26_OTP_MEM31:
+ case CS40L26_DSP1_XMEM_PACKED_0 ...
+CS40L26_DSP1_XMEM_PACKED_6143:
+ case CS40L26_DSP1_XROM_PACKED_0 ...
+CS40L26_DSP1_XROM_PACKED_4604:
+ case CS40L26_DSP1_XMEM_UNPACKED32_0 ...
+CS40L26_DSP1_XROM_UNPACKED32_3070:
+ case CS40L26_DSP1_TIMESTAMP_COUNT:
+ case CS40L26_DSP1_SYS_INFO_ID:
+ case CS40L26_DSP1_SYS_INFO_VERSION:
+ case CS40L26_DSP1_SYS_INFO_CORE_ID:
+ case CS40L26_DSP1_SYS_INFO_AHB_ADDR:
+ case CS40L26_DSP1_SYS_INFO_XM_SRAM_SIZE:
+ case CS40L26_DSP1_SYS_INFO_XM_ROM_SIZE:
+ case CS40L26_DSP1_SYS_INFO_YM_SRAM_SIZE:
+ case CS40L26_DSP1_SYS_INFO_YM_ROM_SIZE:
+ case CS40L26_DSP1_SYS_INFO_PM_SRAM_SIZE:
+ case CS40L26_DSP1_SYS_INFO_PM_BOOT_SIZE:
+ case CS40L26_DSP1_SYS_INFO_FEATURES:
+ case CS40L26_DSP1_SYS_INFO_FIR_FILTERS:
+ case CS40L26_DSP1_SYS_INFO_LMS_FILTERS:
+ case CS40L26_DSP1_SYS_INFO_XM_BANK_SIZE:
+ case CS40L26_DSP1_SYS_INFO_YM_BANK_SIZE:
+ case CS40L26_DSP1_SYS_INFO_PM_BANK_SIZE:
+ case CS40L26_DSP1_SYS_INFO_STREAM_ARB:
+ case CS40L26_DSP1_SYS_INFO_XM_EMEM_SIZE:
+ case CS40L26_DSP1_SYS_INFO_YM_EMEM_SIZE:
+ case CS40L26_DSP1_AHBM_WINDOW0_CONTROL_0:
+ case CS40L26_DSP1_AHBM_WINDOW0_CONTROL_1:
+ case CS40L26_DSP1_AHBM_WINDOW1_CONTROL_0:
+ case CS40L26_DSP1_AHBM_WINDOW1_CONTROL_1:
+ case CS40L26_DSP1_AHBM_WINDOW2_CONTROL_0:
+ case CS40L26_DSP1_AHBM_WINDOW2_CONTROL_1:
+ case CS40L26_DSP1_AHBM_WINDOW3_CONTROL_0:
+ case CS40L26_DSP1_AHBM_WINDOW3_CONTROL_1:
+ case CS40L26_DSP1_AHBM_WINDOW4_CONTROL_0:
+ case CS40L26_DSP1_AHBM_WINDOW4_CONTROL_1:
+ case CS40L26_DSP1_AHBM_WINDOW5_CONTROL_0:
+ case CS40L26_DSP1_AHBM_WINDOW5_CONTROL_1:
+ case CS40L26_DSP1_AHBM_WINDOW6_CONTROL_0:
+ case CS40L26_DSP1_AHBM_WINDOW6_CONTROL_1:
+ case CS40L26_DSP1_AHBM_WINDOW7_CONTROL_0:
+ case CS40L26_DSP1_AHBM_WINDOW7_CONTROL_1:
+ case CS40L26_DSP1_XMEM_UNPACKED24_0 ...
+CS40L26_DSP1_XMEM_UNPACKED24_8191:
+ case CS40L26_DSP1_XROM_UNPACKED24_0 ...
+CS40L26_DSP1_XROM_UNPACKED24_6141:
+ case CS40L26_DSP1_CLOCK_FREQ:
+ case CS40L26_DSP1_CLOCK_STATUS:
+ case CS40L26_DSP1_CORE_SOFT_RESET:
+ case CS40L26_DSP1_CORE_WRAP_STATUS:
+ case CS40L26_DSP1_TIMER_CONTROL:
+ case CS40L26_DSP1_STREAM_ARB_CONTROL:
+ case CS40L26_DSP1_NMI_CONTROL1:
+ case CS40L26_DSP1_NMI_CONTROL2:
+ case CS40L26_DSP1_NMI_CONTROL3:
+ case CS40L26_DSP1_NMI_CONTROL4:
+ case CS40L26_DSP1_NMI_CONTROL5:
+ case CS40L26_DSP1_NMI_CONTROL6:
+ case CS40L26_DSP1_NMI_CONTROL7:
+ case CS40L26_DSP1_NMI_CONTROL8:
+ case CS40L26_DSP1_RESUME_CONTROL:
+ case CS40L26_DSP1_IRQ1_CONTROL:
+ case CS40L26_DSP1_IRQ2_CONTROL:
+ case CS40L26_DSP1_IRQ3_CONTROL:
+ case CS40L26_DSP1_IRQ4_CONTROL:
+ case CS40L26_DSP1_IRQ5_CONTROL:
+ case CS40L26_DSP1_IRQ6_CONTROL:
+ case CS40L26_DSP1_IRQ7_CONTROL:
+ case CS40L26_DSP1_IRQ8_CONTROL:
+ case CS40L26_DSP1_IRQ9_CONTROL:
+ case CS40L26_DSP1_IRQ10_CONTROL:
+ case CS40L26_DSP1_IRQ11_CONTROL:
+ case CS40L26_DSP1_IRQ12_CONTROL:
+ case CS40L26_DSP1_IRQ13_CONTROL:
+ case CS40L26_DSP1_IRQ14_CONTROL:
+ case CS40L26_DSP1_IRQ15_CONTROL:
+ case CS40L26_DSP1_IRQ16_CONTROL:
+ case CS40L26_DSP1_IRQ17_CONTROL:
+ case CS40L26_DSP1_IRQ18_CONTROL:
+ case CS40L26_DSP1_IRQ19_CONTROL:
+ case CS40L26_DSP1_IRQ20_CONTROL:
+ case CS40L26_DSP1_IRQ21_CONTROL:
+ case CS40L26_DSP1_IRQ22_CONTROL:
+ case CS40L26_DSP1_IRQ23_CONTROL:
+ case CS40L26_DSP1_SCRATCH1:
+ case CS40L26_DSP1_SCRATCH2:
+ case CS40L26_DSP1_SCRATCH3:
+ case CS40L26_DSP1_SCRATCH4:
+ case CS40L26_DSP1_CCM_CORE_CONTROL:
+ case CS40L26_DSP1_CCM_CLK_OVERRIDE:
+ case CS40L26_DSP1_MEM_CTRL_XM_MSTR_EN:
+ case CS40L26_DSP1_MEM_CTRL_XM_CORE_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_XM_PL0_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_XM_PL1_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_XM_PL2_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_XM_NPL0_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_YM_MSTR_EN:
+ case CS40L26_DSP1_MEM_CTRL_YM_CORE_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_YM_PL0_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_YM_PL1_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_YM_PL2_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_YM_NPL0_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_PM_MSTR_EN:
+ case CS40L26_DSP1_MEM_CTRL_FIXED_PRIO:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH0_ADDR:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH0_EN:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH0_DATA_LO:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH0_DATA_HI:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH1_ADDR:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH1_EN:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH1_DATA_LO:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH1_DATA_HI:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH2_ADDR:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH2_EN:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH2_DATA_LO:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH2_DATA_HI:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH3_ADDR:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH3_EN:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH3_DATA_LO:
+ case CS40L26_DSP1_MEM_CTRL_PM_PATCH3_DATA_HI:
+ case CS40L26_DSP1_MPU_XMEM_ACCESS_0:
+ case CS40L26_DSP1_MPU_YMEM_ACCESS_0:
+ case CS40L26_DSP1_MPU_WINDOW_ACCESS_0:
+ case CS40L26_DSP1_MPU_XREG_ACCESS_0:
+ case CS40L26_DSP1_MPU_YREG_ACCESS_0:
+ case CS40L26_DSP1_MPU_XMEM_ACCESS_1:
+ case CS40L26_DSP1_MPU_YMEM_ACCESS_1:
+ case CS40L26_DSP1_MPU_WINDOW_ACCESS_1:
+ case CS40L26_DSP1_MPU_XREG_ACCESS_1:
+ case CS40L26_DSP1_MPU_YREG_ACCESS_1:
+ case CS40L26_DSP1_MPU_XMEM_ACCESS_2:
+ case CS40L26_DSP1_MPU_YMEM_ACCESS_2:
+ case CS40L26_DSP1_MPU_WINDOW_ACCESS_2:
+ case CS40L26_DSP1_MPU_XREG_ACCESS_2:
+ case CS40L26_DSP1_MPU_YREG_ACCESS_2:
+ case CS40L26_DSP1_MPU_XMEM_ACCESS_3:
+ case CS40L26_DSP1_MPU_YMEM_ACCESS_3:
+ case CS40L26_DSP1_MPU_WINDOW_ACCESS_3:
+ case CS40L26_DSP1_MPU_XREG_ACCESS_3:
+ case CS40L26_DSP1_MPU_YREG_ACCESS_3:
+ case CS40L26_DSP1_MPU_X_EXT_MEM_ACCESS_0:
+ case CS40L26_DSP1_MPU_Y_EXT_MEM_ACCESS_0:
+ case CS40L26_DSP1_MPU_XM_VIO_ADDR:
+ case CS40L26_DSP1_MPU_XM_VIO_STATUS:
+ case CS40L26_DSP1_MPU_YM_VIO_ADDR:
+ case CS40L26_DSP1_MPU_YM_VIO_STATUS:
+ case CS40L26_DSP1_MPU_PM_VIO_ADDR:
+ case CS40L26_DSP1_MPU_PM_VIO_STATUS:
+ case CS40L26_DSP1_MPU_LOCK_CONFIG:
+ case CS40L26_DSP1_MPU_WDT_RESET_CONTROL:
+ case CS40L26_DSP1_STREAM_ARB_MSTR1_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_MSTR1_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_MSTR1_CONFIG_2:
+ case CS40L26_DSP1_STREAM_ARB_MSTR2_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_MSTR2_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_MSTR2_CONFIG_2:
+ case CS40L26_DSP1_STREAM_ARB_MSTR3_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_MSTR3_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_MSTR3_CONFIG_2:
+ case CS40L26_DSP1_STREAM_ARB_MSTR4_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_MSTR4_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_MSTR4_CONFIG_2:
+ case CS40L26_DSP1_STREAM_ARB_TX1_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_TX1_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_TX2_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_TX2_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_TX3_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_TX3_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_TX4_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_TX4_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_TX5_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_TX5_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_TX6_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_TX6_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_RX1_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_RX1_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_RX2_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_RX2_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_RX3_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_RX3_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_RX4_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_RX4_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_RX5_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_RX5_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_RX6_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_RX6_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_IRQ1_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_IRQ1_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_IRQ1_CONFIG_2:
+ case CS40L26_DSP1_STREAM_ARB_IRQ2_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_IRQ2_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_IRQ2_CONFIG_2:
+ case CS40L26_DSP1_STREAM_ARB_IRQ3_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_IRQ3_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_IRQ3_CONFIG_2:
+ case CS40L26_DSP1_STREAM_ARB_IRQ4_CONFIG_0:
+ case CS40L26_DSP1_STREAM_ARB_IRQ4_CONFIG_1:
+ case CS40L26_DSP1_STREAM_ARB_IRQ4_CONFIG_2:
+ case CS40L26_DSP1_STREAM_ARB_RESYNC_MSK1:
+ case CS40L26_DSP1_STREAM_ARB_ERR_STATUS:
+ case CS40L26_DSP1_WDT_CONTROL:
+ case CS40L26_DSP1_WDT_STATUS:
+ case CS40L26_DSP1_ACCEL_DB_IN:
+ case CS40L26_DSP1_ACCEL_LINEAR_OUT:
+ case CS40L26_DSP1_ACCEL_LINEAR_IN:
+ case CS40L26_DSP1_ACCEL_DB_OUT:
+ case CS40L26_DSP1_ACCEL_RAND_NUM:
+ case CS40L26_DSP1_YMEM_PACKED_0:
+ case CS40L26_DSP1_YMEM_PACKED_1 ...
+CS40L26_DSP1_YMEM_PACKED_1532:
+ case CS40L26_DSP1_YMEM_UNPACKED32_0 ...
+CS40L26_DSP1_YMEM_UNPACKED32_1022:
+ case CS40L26_DSP1_YMEM_UNPACKED24_0 ...
+CS40L26_DSP1_YMEM_UNPACKED24_2045:
+ case CS40L26_DSP1_PMEM_0 ...
+CS40L26_DSP1_PMEM_5114:
+ case CS40L26_DSP1_PROM_0 ...
+CS40L26_DSP1_PROM_30714:
+ return true;
+ default:
+ return false;
+ }
+}
+EXPORT_SYMBOL(cs40l26_readable_reg);
diff --git a/cs40l26/cs40l26.c b/cs40l26/cs40l26.c
new file mode 100644
index 0000000..62b03f1
--- /dev/null
+++ b/cs40l26/cs40l26.c
@@ -0,0 +1,3195 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// cs40l26.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and
+// Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+//
+// Copyright 2021 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 <linux/mfd/cs40l26.h>
+
+static int cs40l26_dsp_read(struct cs40l26_private *cs40l26, u32 reg, u32 *val)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ int ret, i;
+ u32 read_val;
+
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ ret = regmap_read(regmap, reg, &read_val);
+ if (ret)
+ dev_dbg(dev, "Failed to read 0x%X, attempt(s) = %d\n",
+ reg, i + 1);
+ else
+ break;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US_MIN,
+ CS40L26_DSP_TIMEOUT_US_MAX);
+ }
+
+ if (i >= CS40L26_DSP_TIMEOUT_COUNT) {
+ dev_err(dev, "Timed out attempting to read 0x%X\n", reg);
+ return -ETIME;
+ }
+
+ *val = read_val;
+
+ return 0;
+}
+
+static int cs40l26_dsp_write(struct cs40l26_private *cs40l26, u32 reg, u32 val)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ int ret, i;
+
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ ret = regmap_write(regmap, reg, val);
+ if (ret)
+ dev_dbg(dev,
+ "Failed to write to 0x%X, attempt(s) = %d\n",
+ reg, i + 1);
+ else
+ break;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US_MIN,
+ CS40L26_DSP_TIMEOUT_US_MAX);
+ }
+
+ if (i >= CS40L26_DSP_TIMEOUT_COUNT) {
+ dev_err(dev, "Timed out attempting to write to 0x%X\n", reg);
+ return -ETIME;
+ }
+
+ return 0;
+}
+
+static int cs40l26_ack_read(struct cs40l26_private *cs40l26, u32 reg,
+ u32 ack_val)
+{
+ struct device *dev = cs40l26->dev;
+ int ret, i;
+ u32 val;
+
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ ret = cs40l26_dsp_read(cs40l26, reg, &val);
+ if (ret)
+ return ret;
+
+ if (val != ack_val)
+ dev_dbg(dev, "Ack'ed value not equal to expected\n");
+ else
+ break;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US_MIN,
+ CS40L26_DSP_TIMEOUT_US_MAX);
+ }
+
+ if (i >= CS40L26_DSP_TIMEOUT_COUNT) {
+ dev_err(dev, "Ack timed out (0x%08X != 0x%08X) reg. 0x%08X\n",
+ val, ack_val, reg);
+ return -ETIME;
+ }
+
+ return 0;
+}
+
+int cs40l26_ack_write(struct cs40l26_private *cs40l26, u32 reg, u32 write_val,
+ u32 reset_val)
+{
+ int ret;
+
+ ret = cs40l26_dsp_write(cs40l26, reg, write_val);
+ if (ret)
+ return ret;
+
+ return cs40l26_ack_read(cs40l26, reg, reset_val);
+}
+EXPORT_SYMBOL(cs40l26_ack_write);
+
+int cs40l26_class_h_set(struct cs40l26_private *cs40l26, bool class_h)
+{
+ int ret;
+
+ ret = regmap_update_bits(cs40l26->regmap, CS40L26_BLOCK_ENABLES2,
+ CS40L26_CLASS_H_EN_MASK, class_h <<
+ CS40L26_CLASS_H_EN_SHIFT);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to update CLASS H tracking\n");
+
+ return ret;
+}
+EXPORT_SYMBOL(cs40l26_class_h_set);
+
+int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state)
+{
+ u32 algo_id, reg, dsp_state;
+ int ret;
+
+ if (cs40l26->fw_loaded) {
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM)
+ algo_id = CS40L26_PM_ALGO_ID;
+ else
+ algo_id = CS40L26_PM_ROM_ALGO_ID;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "PM_CUR_STATE",
+ CL_DSP_XM_UNPACKED_TYPE, algo_id, &reg);
+ if (ret)
+ return ret;
+ } else {
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ reg = CS40L26_A0_PM_CUR_STATE_STATIC_REG;
+ break;
+ case CS40L26_REVID_A1:
+ reg = CS40L26_A1_PM_CUR_STATE_STATIC_REG;
+ break;
+ default:
+ dev_err(cs40l26->dev,
+ "Revid ID not supported: 0x%02X\n",
+ cs40l26->revid);
+ return -EINVAL;
+ }
+ }
+
+ ret = cs40l26_dsp_read(cs40l26, reg, &dsp_state);
+ if (ret)
+ return ret;
+
+ switch (dsp_state) {
+ case CS40L26_DSP_STATE_HIBERNATE:
+ /* intentionally fall through */
+ case CS40L26_DSP_STATE_SHUTDOWN:
+ /* intentionally fall through */
+ case CS40L26_DSP_STATE_STANDBY:
+ /* intentionally fall through */
+ case CS40L26_DSP_STATE_ACTIVE:
+ *state = CS40L26_DSP_STATE_MASK & dsp_state;
+ break;
+ default:
+ dev_err(cs40l26->dev, "DSP state %u is invalid\n", dsp_state);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(cs40l26_dsp_state_get);
+
+int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26,
+ u32 timeout_ms)
+{
+ u32 timeout_ticks = timeout_ms * CS40L26_PM_TICKS_MS_DIV;
+ struct regmap *regmap = cs40l26->regmap;
+ u32 lower_val, reg, algo_id;
+ u8 upper_val;
+ int ret;
+
+ upper_val = (timeout_ticks >> CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT) &
+ CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK;
+
+ lower_val = timeout_ticks & CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK;
+
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM)
+ algo_id = CS40L26_PM_ALGO_ID;
+ else
+ algo_id = CS40L26_PM_ROM_ALGO_ID;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS",
+ CL_DSP_XM_UNPACKED_TYPE, algo_id, &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, reg + CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET,
+ lower_val);
+ if (ret)
+ return ret;
+
+ return regmap_write(regmap, reg + CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET,
+ upper_val);
+}
+EXPORT_SYMBOL(cs40l26_pm_timeout_ms_set);
+
+int cs40l26_pm_timeout_ms_get(struct cs40l26_private *cs40l26,
+ u32 *timeout_ms)
+{
+ u32 lower_val, upper_val, algo_id, reg;
+ int ret;
+
+ if (cs40l26->fw_loaded) {
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM)
+ algo_id = CS40L26_PM_ALGO_ID;
+ else
+ algo_id = CS40L26_PM_ROM_ALGO_ID;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS",
+ CL_DSP_XM_UNPACKED_TYPE, algo_id, &reg);
+ if (ret)
+ return ret;
+ } else {
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ reg = CS40L26_A0_PM_TIMEOUT_TICKS_STATIC_REG;
+ break;
+ case CS40L26_REVID_A1:
+ reg = CS40L26_A1_PM_TIMEOUT_TICKS_STATIC_REG;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Revid ID not supported: %02X\n",
+ cs40l26->revid);
+ return -EINVAL;
+ }
+ }
+
+ ret = regmap_read(cs40l26->regmap, reg +
+ CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET, &lower_val);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(cs40l26->regmap, reg +
+ CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET, &upper_val);
+ if (ret)
+ return ret;
+
+ *timeout_ms =
+ ((lower_val & CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK) |
+ ((upper_val & CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK) <<
+ CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT)) /
+ CS40L26_PM_TICKS_MS_DIV;
+
+ return 0;
+}
+EXPORT_SYMBOL(cs40l26_pm_timeout_ms_get);
+
+static int cs40l26_pm_runtime_setup(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int ret;
+
+ ret = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_PM_TIMEOUT_MS_MIN);
+ if (ret)
+ return ret;
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, CS40L26_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+
+ cs40l26->pm_ready = true;
+
+ return 0;
+}
+
+static void cs40l26_pm_runtime_teardown(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+
+ pm_runtime_set_suspended(dev);
+ pm_runtime_disable(dev);
+ pm_runtime_dont_use_autosuspend(dev);
+
+ cs40l26->pm_ready = false;
+}
+
+static int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
+ enum cs40l26_pm_state state)
+{
+ struct device *dev = cs40l26->dev;
+ u32 cmd;
+ int ret;
+
+ cmd = (u32) CS40L26_DSP_MBOX_PM_CMD_BASE + state;
+
+ switch (state) {
+ case CS40L26_PM_STATE_WAKEUP:
+ /* intentionally fall through */
+ case CS40L26_PM_STATE_PREVENT_HIBERNATE:
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ cmd, CS40L26_DSP_MBOX_RESET);
+ break;
+ case CS40L26_PM_STATE_ALLOW_HIBERNATE:
+ /* intentionally fall through */
+ case CS40L26_PM_STATE_SHUTDOWN:
+ cs40l26->wksrc_sts = 0x00;
+ ret = cs40l26_dsp_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ cmd);
+ break;
+ default:
+ dev_err(dev, "Invalid PM state: %u\n", state);
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ cs40l26->pm_state = state;
+
+ return 0;
+}
+
+static int cs40l26_dsp_start(struct cs40l26_private *cs40l26)
+{
+ u8 dsp_state;
+ int ret;
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL,
+ CS40L26_DSP_CCM_CORE_RESET);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to reset DSP core\n");
+ return ret;
+ }
+
+ ret = cs40l26_dsp_state_get(cs40l26, &dsp_state);
+ if (ret)
+ return ret;
+
+ if (dsp_state != CS40L26_DSP_STATE_ACTIVE &&
+ dsp_state != CS40L26_DSP_STATE_STANDBY) {
+ dev_err(cs40l26->dev, "Failed to wake DSP core\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs40l26_dsp_wake(struct cs40l26_private *cs40l26)
+{
+ u8 dsp_state;
+ int ret;
+
+ ret = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_WAKEUP);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_pm_state_transition(cs40l26,
+ CS40L26_PM_STATE_PREVENT_HIBERNATE);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_dsp_state_get(cs40l26, &dsp_state);
+ if (ret)
+ return ret;
+
+ if (dsp_state != CS40L26_DSP_STATE_STANDBY &&
+ dsp_state != CS40L26_DSP_STATE_ACTIVE) {
+ dev_err(cs40l26->dev, "Failed to wake DSP\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs40l26_dsp_shutdown(struct cs40l26_private *cs40l26)
+{
+ u32 timeout_ms;
+ int ret;
+
+ ret = cs40l26_pm_timeout_ms_get(cs40l26, &timeout_ms);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_SHUTDOWN);
+ if (ret)
+ return ret;
+
+ usleep_range(CS40L26_MS_TO_US(timeout_ms),
+ CS40L26_MS_TO_US(timeout_ms) + 100);
+
+ return 0;
+}
+
+static int cs40l26_dsp_pre_config(struct cs40l26_private *cs40l26)
+{
+ u8 dsp_state;
+ u32 halo_state, halo_state_reg;
+ int ret;
+
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ halo_state_reg = CS40L26_A0_DSP_HALO_STATE_REG;
+ break;
+ case CS40L26_REVID_A1:
+ halo_state_reg = CS40L26_A1_DSP_HALO_STATE_REG;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Revid ID not supported: %02X\n",
+ cs40l26->revid);
+ return -EINVAL;
+ }
+
+ ret = regmap_read(cs40l26->regmap, halo_state_reg,
+ &halo_state);
+ if (ret)
+ return ret;
+
+ if (halo_state != CS40L26_DSP_HALO_STATE_RUN) {
+ dev_err(cs40l26->dev, "DSP not Ready: HALO_STATE: %08X\n",
+ halo_state);
+ return -EINVAL;
+ }
+
+ ret = cs40l26_pm_state_transition(cs40l26,
+ CS40L26_PM_STATE_PREVENT_HIBERNATE);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_dsp_state_get(cs40l26, &dsp_state);
+ if (ret)
+ return ret;
+
+ if (dsp_state != CS40L26_DSP_STATE_SHUTDOWN &&
+ dsp_state != CS40L26_DSP_STATE_STANDBY) {
+ dev_err(cs40l26->dev, "DSP core not safe to kill\n");
+ return -EINVAL;
+ }
+
+ /* errata write fixing indeterminent PLL lock time */
+ ret = regmap_update_bits(cs40l26->regmap, CS40L26_PLL_REFCLK_DETECT_0,
+ CS40L26_PLL_REFCLK_DET_EN_MASK, CS40L26_DISABLE);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to disable PLL refclk detect\n");
+ return ret;
+ }
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL,
+ CS40L26_DSP_CCM_CORE_KILL);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to kill DSP core\n");
+
+ return ret;
+}
+
+static int cs40l26_mbox_buffer_read(struct cs40l26_private *cs40l26, u32 *val)
+{
+ struct device *dev = cs40l26->dev;
+ struct regmap *regmap = cs40l26->regmap;
+ u32 base, last, len, write_ptr, read_ptr, mbox_response, reg;
+ u32 buffer[CS40L26_DSP_MBOX_BUFFER_NUM_REGS];
+ int ret;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "QUEUE_BASE",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_MAILBOX_ALGO_ID,
+ &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(regmap, reg, buffer,
+ CS40L26_DSP_MBOX_BUFFER_NUM_REGS);
+ if (ret) {
+ dev_err(dev, "Failed to read buffer contents\n");
+ return ret;
+ }
+
+ base = buffer[0];
+ len = buffer[1];
+ write_ptr = buffer[2];
+ read_ptr = buffer[3];
+ last = base + ((len - 1) * CL_DSP_BYTES_PER_WORD);
+
+ if ((read_ptr - CL_DSP_BYTES_PER_WORD) == write_ptr) {
+ dev_err(dev, "Mailbox buffer is full, info missing\n");
+ return -ENOSPC;
+ }
+
+ if (read_ptr == write_ptr) {
+ dev_dbg(dev, "Reached end of queue\n");
+ return 1;
+ }
+
+ ret = regmap_read(regmap, read_ptr, &mbox_response);
+ if (ret) {
+ dev_err(dev, "Failed to read from mailbox buffer\n");
+ return ret;
+ }
+
+ if (read_ptr == last)
+ read_ptr = base;
+ else
+ read_ptr += CL_DSP_BYTES_PER_WORD;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "QUEUE_RD",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_MAILBOX_ALGO_ID,
+ &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, reg, read_ptr);
+ if (ret) {
+ dev_err(dev, "Failed to update read pointer\n");
+ return ret;
+ }
+
+ *val = mbox_response;
+
+ return 0;
+}
+
+static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ u32 val = 0;
+
+ while (!cs40l26_mbox_buffer_read(cs40l26, &val)) {
+ if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK)
+ == CS40L26_DSP_MBOX_PANIC) {
+ dev_alert(dev, "DSP PANIC! Error condition: 0x%06X\n",
+ (u32) (val & CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK));
+ return -ENOTRECOVERABLE;
+ }
+
+ switch (val) {
+ case CS40L26_DSP_MBOX_TRIGGER_COMPLETE:
+ if (cs40l26->vibe_state != CS40L26_VIBE_STATE_ASP)
+ cs40l26_vibe_state_set(cs40l26,
+ CS40L26_VIBE_STATE_STOPPED);
+ dev_dbg(dev, "Trigger Complete\n");
+ break;
+ case CS40L26_DSP_MBOX_PM_AWAKE:
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ dev_dbg(dev, "HALO Core is awake\n");
+ break;
+ case CS40L26_DSP_MBOX_F0_EST_START:
+ /* intentionally fall through */
+ case CS40L26_DSP_MBOX_F0_EST_DONE:
+ /* intentionally fall through */
+ case CS40L26_DSP_MBOX_REDC_EST_START:
+ /* intentionally fall through */
+ case CS40L26_DSP_MBOX_REDC_EST_DONE:
+ /* intentionally fall through */
+ case CS40L26_DSP_MBOX_SYS_ACK:
+ dev_err(dev, "Mbox buffer value (0x%X) not supported\n",
+ val);
+ return -EPERM;
+ default:
+ dev_err(dev, "MBOX buffer value (0x%X) is invalid\n",
+ val);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+void cs40l26_vibe_state_set(struct cs40l26_private *cs40l26,
+ enum cs40l26_vibe_state new_state)
+{
+ if (cs40l26->vibe_state == new_state)
+ return;
+
+ if (new_state == CS40L26_VIBE_STATE_STOPPED && cs40l26->asp_enable) {
+ /* Re-enable audio stream */
+ cs40l26_class_h_set(cs40l26, true);
+ if (!cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_DSP_MBOX_CMD_START_I2S,
+ CS40L26_DSP_MBOX_RESET)) {
+ cs40l26->vibe_state = CS40L26_VIBE_STATE_ASP;
+ return;
+ } else if (new_state == CS40L26_VIBE_STATE_HAPTIC &&
+ cs40l26->vibe_state == CS40L26_VIBE_STATE_ASP) {
+ cs40l26_class_h_set(cs40l26, false);
+ }
+ }
+
+ cs40l26->vibe_state = new_state;
+ sysfs_notify(&cs40l26->dev->kobj, NULL, "vibe_state");
+}
+EXPORT_SYMBOL(cs40l26_vibe_state_set);
+
+static int cs40l26_event_count_get(struct cs40l26_private *cs40l26, u32 *count)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "EVENT_POST_COUNT",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_EVENT_HANDLER_ALGO_ID,
+ &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(cs40l26->regmap, reg, count);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to get event count\n");
+
+ return ret;
+}
+
+static int cs40l26_error_release(struct cs40l26_private *cs40l26,
+ unsigned int err_rls, bool bst_err)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ u32 err_sts, err_cfg;
+ int ret;
+
+ /* Boost related errors must be handled with DSP turned off */
+ if (bst_err) {
+ ret = cs40l26_dsp_shutdown(cs40l26);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read(regmap, CS40L26_ERROR_RELEASE, &err_sts);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to get error status\n");
+ return ret;
+ }
+
+ err_cfg = err_sts & ~BIT(err_rls);
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg);
+ if (ret) {
+ dev_err(dev, "Actuator Safe Mode release sequence failed\n");
+ return ret;
+ }
+
+ err_cfg |= BIT(err_rls);
+
+ ret = regmap_write(regmap, CS40L26_ERROR_RELEASE, err_cfg);
+ if (ret) {
+ dev_err(dev, "Actuator Safe Mode release sequence failed\n");
+ return ret;
+ }
+
+ err_cfg &= ~BIT(err_rls);
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg);
+ if (ret) {
+ dev_err(dev, "Actuator Safe Mode release sequence failed\n");
+ return ret;
+ }
+
+ if (bst_err) {
+ ret = cs40l26_dsp_wake(cs40l26);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int cs40l26_iseq_update(struct cs40l26_private *cs40l26,
+ enum cs40l26_iseq update)
+{
+ int ret;
+ u32 val;
+
+ ret = regmap_read(cs40l26->regmap, cs40l26->iseq_table[update].addr,
+ &val);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to get IRQ seq. information\n");
+ return ret;
+ }
+
+ cs40l26->iseq_table[update].val = val;
+
+ return 0;
+}
+
+static int cs40l26_iseq_init(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ struct regmap *regmap = cs40l26->regmap;
+ int ret, i;
+
+ cs40l26->iseq_table[CS40L26_ISEQ_MASK1].addr = CS40L26_IRQ1_MASK_1;
+ cs40l26->iseq_table[CS40L26_ISEQ_MASK2].addr = CS40L26_IRQ1_MASK_2;
+ cs40l26->iseq_table[CS40L26_ISEQ_EDGE1].addr = CS40L26_IRQ1_EDGE_1;
+ cs40l26->iseq_table[CS40L26_ISEQ_POL1].addr = CS40L26_IRQ1_POL_1;
+
+ for (i = 0; i < CS40L26_ISEQ_MAX_ENTRIES; i++) {
+ ret = regmap_read(regmap, cs40l26->iseq_table[i].addr,
+ &cs40l26->iseq_table[i].val);
+ if (ret) {
+ dev_err(dev, "Failed to read IRQ settings\n");
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static int cs40l26_iseq_populate(struct cs40l26_private *cs40l26)
+{
+ int ret, i;
+
+ for (i = 0; i < CS40L26_ISEQ_MAX_ENTRIES; i++) {
+ ret = regmap_write(cs40l26->regmap,
+ cs40l26->iseq_table[i].addr,
+ cs40l26->iseq_table[i].val);
+ if (ret) {
+ dev_err(cs40l26->dev,
+ "Failed to update IRQ settings\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int cs40l26_irq_update_mask(struct cs40l26_private *cs40l26, u32 irq_reg,
+ u32 bits, bool mask)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ enum cs40l26_iseq update;
+ u32 eint_reg;
+ int ret;
+
+ switch (irq_reg) {
+ case CS40L26_IRQ1_MASK_1:
+ update = CS40L26_ISEQ_MASK1;
+ eint_reg = CS40L26_IRQ1_EINT_1;
+ break;
+ case CS40L26_IRQ1_MASK_2:
+ update = CS40L26_ISEQ_MASK2;
+ eint_reg = CS40L26_IRQ1_EINT_2;
+ break;
+ default:
+ dev_err(dev, "Invalid IRQ mask register 0x%08X\n", irq_reg);
+ return -EINVAL;
+ }
+
+ ret = regmap_write(regmap, eint_reg, bits);
+ if (ret) {
+ dev_err(dev,
+ "Failed to clear status of bits 0x%08X\n",
+ eint_reg);
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, irq_reg, bits, mask ? bits : ~bits);
+ if (ret) {
+ dev_err(dev, "Failed to update IRQ mask 0x%08X\n", irq_reg);
+ return ret;
+ }
+
+ return cs40l26_iseq_update(cs40l26, update);
+}
+
+static int cs40l26_handle_irq1(struct cs40l26_private *cs40l26,
+ enum cs40l26_irq1 irq1, bool trigger)
+{
+ struct device *dev = cs40l26->dev;
+ u32 err_rls = 0;
+ unsigned int reg, val;
+ bool bst_err;
+ int ret;
+
+ switch (irq1) {
+ case CS40L26_IRQ1_GPIO1_RISE:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_GPIO1_FALL:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_GPIO2_RISE:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_GPIO2_FALL:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_GPIO3_RISE:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_GPIO3_FALL:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_GPIO4_RISE:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_GPIO4_FALL:
+ if (cs40l26->wksrc_sts & CS40L26_WKSRC_STS_EN) {
+ dev_dbg(dev, "GPIO%u %s edge detected\n",
+ (irq1 / 2) + 1,
+ irq1 % 2 ? "falling" : "rising");
+ if (trigger)
+ cs40l26_vibe_state_set(cs40l26,
+ CS40L26_VIBE_STATE_HAPTIC);
+ }
+
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_ANY:
+ dev_dbg(dev, "Wakesource detected (ANY)\n");
+
+ ret = cs40l26_iseq_populate(cs40l26);
+ if (ret)
+ goto err;
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_PWRMGT_STS, &val);
+ if (ret) {
+ dev_err(dev, "Failed to get Power Management Status\n");
+ goto err;
+ }
+
+ cs40l26->wksrc_sts = (u8) ((val & CS40L26_WKSRC_STS_MASK) >>
+ CS40L26_WKSRC_STS_SHIFT);
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "LAST_WAKESRC_CTL",
+ CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_FW_ID, &reg);
+ if (ret)
+ goto err;
+
+ ret = regmap_read(cs40l26->regmap, reg, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read LAST_WAKESRC_CTL\n");
+ goto err;
+ }
+ cs40l26->last_wksrc_pol =
+ (u8) (val & CS40L26_WKSRC_GPIO_POL_MASK);
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_GPIO1:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_WKSRC_STS_GPIO2:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_WKSRC_STS_GPIO3:
+ /* intentionally fall through */
+ case CS40L26_IRQ1_WKSRC_STS_GPIO4:
+ dev_dbg(dev, "GPIO%u event woke device from hibernate\n",
+ irq1 - CS40L26_IRQ1_WKSRC_STS_GPIO1 + 1);
+
+ if (cs40l26->wksrc_sts & cs40l26->last_wksrc_pol) {
+ dev_dbg(dev, "GPIO%u falling edge detected\n",
+ irq1 - 8);
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ } else {
+ dev_dbg(dev, "GPIO%u rising edge detected\n",
+ irq1 - 8);
+ }
+ if (trigger)
+ cs40l26_vibe_state_set(cs40l26,
+ CS40L26_VIBE_STATE_HAPTIC);
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_SPI:
+ dev_dbg(dev, "SPI event woke device from hibernate\n");
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_I2C:
+ dev_dbg(dev, "I2C event woke device from hibernate\n");
+ break;
+ case CS40L26_IRQ1_GLOBAL_EN_ASSERT:
+ dev_dbg(dev, "Started power up seq. (GLOBAL_EN asserted)\n");
+ break;
+ case CS40L26_IRQ1_PDN_DONE:
+ dev_dbg(dev,
+ "Completed power down seq. (GLOBAL_EN cleared)\n");
+ break;
+ case CS40L26_IRQ1_PUP_DONE:
+ dev_dbg(dev,
+ "Completed power up seq. (GLOBAL_EN asserted)\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_FLAG_RISE:
+ dev_warn(dev, "BST overvoltage warning\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_FLAG_FALL:
+ dev_warn(dev,
+ "BST voltage returned below warning threshold\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_ERR:
+ dev_alert(dev, "BST overvolt. error, CS40L26 shutting down\n");
+ err_rls = CS40L26_BST_OVP_ERR_RLS;
+ bst_err = true;
+ break;
+ case CS40L26_IRQ1_BST_DCM_UVP_ERR:
+ dev_alert(dev,
+ "BST undervolt. error, CS40L26 shutting down\n");
+ err_rls = CS40L26_BST_UVP_ERR_RLS;
+ bst_err = true;
+ break;
+ case CS40L26_IRQ1_BST_SHORT_ERR:
+ dev_alert(dev, "LBST short detected, CS40L26 shutting down\n");
+ err_rls = CS40L26_BST_SHORT_ERR_RLS;
+ bst_err = true;
+ break;
+ case CS40L26_IRQ1_BST_IPK_FLAG:
+ dev_warn(dev, "Current is being limited by LBST inductor\n");
+ break;
+ case CS40L26_IRQ1_TEMP_WARN_RISE:
+ dev_err(dev, "Die overtemperature warning\n");
+ err_rls = CS40L26_TEMP_WARN_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_TEMP_WARN_FALL:
+ dev_warn(dev, "Die temperature returned below threshold\n");
+ break;
+ case CS40L26_IRQ1_TEMP_ERR:
+ dev_alert(dev,
+ "Die overtemperature error, CS40L26 shutting down\n");
+ err_rls = CS40L26_TEMP_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_AMP_ERR:
+ dev_alert(dev, "AMP short detected, CS40L26 shutting down\n");
+ err_rls = CS40L26_AMP_SHORT_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_DC_WATCHDOG_RISE:
+ dev_err(dev, "DC level detected\n");
+ break;
+ case CS40L26_IRQ1_DC_WATCHDOG_FALL:
+ dev_warn(dev, "Previously detected DC level removed\n");
+ break;
+ case CS40L26_IRQ1_VIRTUAL1_MBOX_WR:
+ dev_dbg(dev, "Virtual 1 MBOX write occurred\n");
+ break;
+ case CS40L26_IRQ1_VIRTUAL2_MBOX_WR:
+ ret = cs40l26_handle_mbox_buffer(cs40l26);
+ if (ret)
+ goto err;
+ break;
+ default:
+ dev_err(dev, "Unrecognized IRQ1 EINT1 status\n");
+ return -EINVAL;
+ }
+
+ if (err_rls)
+ ret = cs40l26_error_release(cs40l26, err_rls, bst_err);
+
+err:
+ regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_1, BIT(irq1));
+
+ return ret;
+}
+
+static int cs40l26_handle_irq2(struct cs40l26_private *cs40l26,
+ enum cs40l26_irq2 irq2)
+{
+ struct device *dev = cs40l26->dev;
+ unsigned int val;
+ u32 vbbr_status, vpbr_status;
+ int ret;
+
+ switch (irq2) {
+ case CS40L26_IRQ2_PLL_LOCK:
+ dev_dbg(dev, "PLL achieved lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_PHASE_LOCK:
+ dev_dbg(dev, "PLL achieved phase lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_FREQ_LOCK:
+ dev_dbg(dev, "PLL achieved frequency lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_UNLOCK_RISE:
+ dev_err(dev, "PLL has lost lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_UNLOCK_FALL:
+ dev_warn(dev, "PLL has regained lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_READY:
+ dev_dbg(dev, "PLL ready for use\n");
+ break;
+ case CS40L26_IRQ2_PLL_REFCLK_PRESENT:
+ dev_warn(dev, "REFCLK present for PLL\n");
+ break;
+ case CS40L26_IRQ2_REFCLK_MISSING_RISE:
+ dev_err(dev, "REFCLK input for PLL is missing\n");
+ break;
+ case CS40L26_IRQ2_REFCLK_MISSING_FALL:
+ dev_warn(dev, "REFCLK reported missing is now present\n");
+ break;
+ case CS40L26_IRQ2_ASP_RXSLOT_CFG_ERR:
+ dev_err(dev, "Misconfig. of ASP_RX 1 2 or 3 SLOT fields\n");
+ break;
+ case CS40L26_IRQ2_AUX_NG_CH1_ENTRY:
+ dev_warn(dev,
+ "CH1 data of noise gate has fallen below threshold\n");
+ break;
+ case CS40L26_IRQ2_AUX_NG_CH1_EXIT:
+ dev_err(dev,
+ "CH1 data of noise gate has risen above threshold\n");
+ break;
+ case CS40L26_IRQ2_AUX_NG_CH2_ENTRY:
+ dev_warn(dev,
+ "CH2 data of noise gate has fallen below threshold\n");
+ break;
+ case CS40L26_IRQ2_AUX_NG_CH2_EXIT:
+ dev_err(dev,
+ "CH2 data of noise gate has risen above threshold\n");
+ break;
+ case CS40L26_IRQ2_AMP_NG_ON_RISE:
+ dev_warn(dev, "Amplifier entered noise-gated state\n");
+ break;
+ case CS40L26_IRQ2_AMP_NG_ON_FALL:
+ dev_warn(dev, "Amplifier exited noise-gated state\n");
+ break;
+ case CS40L26_IRQ2_VPBR_FLAG:
+ dev_alert(dev,
+ "VP voltage has dropped below brownout threshold\n");
+ ret = regmap_read(cs40l26->regmap, CS40L26_VPBR_STATUS, &val);
+ if (ret) {
+ dev_err(dev, "Failed to get VPBR_STATUS\n");
+ return ret;
+ }
+
+ vpbr_status = (val & CS40L26_VXBR_STATUS_MASK);
+ dev_alert(dev, "VPBR Attenuation applied = %u x 10^-4 dB\n",
+ vpbr_status * CS40L26_VXBR_STATUS_DIV_STEP);
+ break;
+ case CS40L26_IRQ2_VPBR_ATT_CLR:
+ dev_warn(dev,
+ "Cleared attenuation applied by VP brownout event\n");
+ break;
+ case CS40L26_IRQ2_VBBR_FLAG:
+ dev_alert(dev,
+ "VBST voltage has dropped below brownout threshold\n");
+ ret = regmap_read(cs40l26->regmap, CS40L26_VBBR_STATUS, &val);
+ if (ret) {
+ dev_err(dev, "Failed to get VPBR_STATUS\n");
+ return ret;
+ }
+
+ vbbr_status = (val & CS40L26_VXBR_STATUS_MASK);
+ dev_alert(dev, "VBBR Attenuation applied = %u x 10^-4 dB\n",
+ vbbr_status * CS40L26_VXBR_STATUS_DIV_STEP);
+ break;
+ case CS40L26_IRQ2_VBBR_ATT_CLR:
+ dev_warn(dev, "Cleared attenuation caused by VBST brownout\n");
+ break;
+ case CS40L26_IRQ2_I2C_NACK_ERR:
+ dev_err(dev, "I2C interface NACK during Broadcast Mode\n");
+ break;
+ case CS40L26_IRQ2_VPMON_CLIPPED:
+ dev_err(dev, "Input larger than full-scale value (VPMON)\n");
+ break;
+ case CS40L26_IRQ2_VBSTMON_CLIPPED:
+ dev_err(dev, "Input larger than full-scale value (VBSTMON)\n");
+ break;
+ case CS40L26_IRQ2_VMON_CLIPPED:
+ dev_err(dev, "Input larger than full-scale value (VMON)\n");
+ break;
+ case CS40L26_IRQ2_IMON_CLIPPED:
+ dev_err(dev, "Input larger than full-scale value (IMON)\n");
+ break;
+ default:
+ dev_err(dev, "Unrecognized IRQ1 EINT2 status\n");
+ return -EINVAL;
+ }
+
+ /* write 1 to clear the interrupt flag */
+ ret = regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_2, BIT(irq2));
+ if (ret)
+ dev_err(dev, "Failed to clear IRQ1 EINT2 %u\n", irq2);
+
+ return ret;
+}
+
+static irqreturn_t cs40l26_irq(int irq, void *data)
+{
+ struct cs40l26_private *cs40l26 = (struct cs40l26_private *)data;
+ unsigned int sts, val, eint, mask, i, irq1_count = 0, irq2_count = 0;
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ unsigned long num_irq;
+ u32 event_count;
+ bool trigger;
+ int ret;
+
+ if (cs40l26_dsp_read(cs40l26, CS40L26_IRQ1_STATUS, &sts)) {
+ dev_err(dev, "Failed to read IRQ1 Status\n");
+ return IRQ_NONE;
+ }
+
+ if (sts != CS40L26_IRQ_STATUS_ASSERT) {
+ dev_err(dev, "IRQ1 asserted with no pending interrupts\n");
+ return IRQ_NONE;
+ }
+
+ pm_runtime_get_sync(dev);
+
+ ret = regmap_read(regmap, CS40L26_IRQ1_EINT_1, &eint);
+ if (ret) {
+ dev_err(dev, "Failed to read interrupts status 1\n");
+ goto err;
+ }
+
+ ret = regmap_read(regmap, CS40L26_IRQ1_MASK_1, &mask);
+ if (ret) {
+ dev_err(dev, "Failed to get interrupts mask 1\n");
+ goto err;
+ }
+
+ val = eint & ~mask;
+ if (val) {
+ ret = cs40l26_event_count_get(cs40l26, &event_count);
+ if (ret)
+ goto err;
+
+ trigger = (event_count > cs40l26->event_count);
+
+ num_irq = hweight_long(val);
+ i = 0;
+ while (irq1_count < num_irq && i < CS40L26_IRQ1_NUM_IRQS) {
+ if (val & BIT(i)) {
+ ret = cs40l26_handle_irq1(cs40l26, i, trigger);
+ if (ret)
+ goto err;
+ else
+ irq1_count++;
+ }
+ i++;
+ }
+ }
+
+ ret = regmap_read(regmap, CS40L26_IRQ1_EINT_2, &eint);
+ if (ret) {
+ dev_err(dev, "Failed to read interrupts status 2\n");
+ goto err;
+ }
+
+ ret = regmap_read(regmap, CS40L26_IRQ1_MASK_2, &mask);
+ if (ret) {
+ dev_err(dev, "Failed to get interrupts mask 2\n");
+ goto err;
+ }
+
+ val = eint & ~mask;
+ if (val) {
+ num_irq = hweight_long(val);
+
+ i = 0;
+ while (irq2_count < num_irq && i < CS40L26_IRQ2_NUM_IRQS) {
+ if (val & BIT(i)) {
+ ret = cs40l26_handle_irq2(cs40l26, i);
+ if (ret)
+ goto err;
+ else
+ irq2_count++;
+ }
+ i++;
+ }
+ }
+
+err:
+ cs40l26_event_count_get(cs40l26, &cs40l26->event_count);
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ /* if an error has occurred, all IRQs have not been successfully
+ * processed; however, IRQ_HANDLED is still returned if at least one
+ * interrupt request generated by CS40L26 was handled successfully.
+ */
+ if (ret)
+ dev_err(dev, "Failed to process IRQ (%d): %u\n", irq, ret);
+
+ return (irq1_count + irq2_count) ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static bool cs40l26_pseq_v1_addr_exists(struct cs40l26_private *cs40l26,
+ u16 addr, int *index)
+{
+ int i;
+
+ if (cs40l26->pseq_v1_len == 0)
+ return false;
+
+ for (i = 0; i < cs40l26->pseq_v1_len; i++) {
+ if (cs40l26->pseq_v1_table[i].addr == addr) {
+ *index = i;
+ return true;
+ }
+ }
+
+ *index = -1;
+
+ return false;
+}
+
+static int cs40l26_pseq_v1_write(struct cs40l26_private *cs40l26,
+ unsigned int pseq_v1_offset)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ unsigned int len = cs40l26->pseq_v1_len;
+ struct device *dev = cs40l26->dev;
+ u32 val;
+ u16 addr;
+ int ret;
+
+ addr = cs40l26->pseq_v1_table[pseq_v1_offset].addr;
+ val = cs40l26->pseq_v1_table[pseq_v1_offset].val;
+
+ /* the "upper half" first 24-bit word of the sequence pair is written
+ * to the write sequencer as: [23-16] addr{15-0},
+ * [15-0] val{31-24} with bits [24-31] acting as a buffer
+ */
+ ret = regmap_write(regmap, cs40l26->pseq_base +
+ (pseq_v1_offset * CS40L26_PSEQ_V1_STRIDE),
+ (addr << CS40L26_PSEQ_V1_ADDR_SHIFT) |
+ ((val & ~CS40L26_PSEQ_V1_VAL_MASK)
+ >> CS40L26_PSEQ_V1_VAL_SHIFT));
+ if (ret) {
+ dev_err(dev, "Failed to write power on seq. (upper half)\n");
+ return ret;
+ }
+
+ /* the "lower half" of the address-value pair is written to the write
+ * sequencer as: [23-0] data{23-0} with bits [24-31] acting as a buffer
+ */
+ ret = regmap_write(regmap, cs40l26->pseq_base + CL_DSP_BYTES_PER_WORD
+ + (pseq_v1_offset * CS40L26_PSEQ_V1_STRIDE),
+ val & CS40L26_PSEQ_V1_VAL_MASK);
+ if (ret) {
+ dev_err(dev, "Failed to write power on seq. (lower half)\n");
+ return ret;
+ }
+
+ /* end of sequence must be marked by list terminator */
+ ret = regmap_write(regmap, cs40l26->pseq_base +
+ (len * CS40L26_PSEQ_V1_STRIDE),
+ CS40L26_PSEQ_V1_LIST_TERM);
+ if (ret)
+ dev_err(dev, "Failed to write power on seq. terminator\n");
+
+ return ret;
+}
+
+static int cs40l26_pseq_v1_add_pair(struct cs40l26_private *cs40l26, u16 addr,
+ u32 val, bool replace)
+{
+ unsigned int len = cs40l26->pseq_v1_len;
+ struct device *dev = cs40l26->dev;
+ unsigned int pseq_v1_offset, prev_val;
+ int ret, index;
+
+ if (len >= CS40L26_PSEQ_V1_MAX_ENTRIES) {
+ dev_err(dev, "Power on seq. exceeded max number of entries\n");
+ return -E2BIG;
+ }
+
+ if (cs40l26_pseq_v1_addr_exists(cs40l26, addr, &index) && replace) {
+ prev_val = cs40l26->pseq_v1_table[index].val;
+ cs40l26->pseq_v1_table[index].val = val;
+ pseq_v1_offset = index;
+ } else {
+ cs40l26->pseq_v1_table[len].addr = addr;
+ cs40l26->pseq_v1_table[len].val = val;
+ cs40l26->pseq_v1_len++;
+ pseq_v1_offset = len;
+ }
+
+ ret = cs40l26_pseq_v1_write(cs40l26, pseq_v1_offset);
+ if (ret) { /* If an error occurs during write, reset the sequence */
+ if (index < 0) { /* No previous value for this address */
+ cs40l26->pseq_v1_table[len].addr = 0;
+ cs40l26->pseq_v1_table[len].val = 0;
+ cs40l26->pseq_v1_len--;
+ } else {
+ cs40l26->pseq_v1_table[index].val = prev_val;
+ }
+ }
+
+ return ret;
+}
+
+int cs40l26_pseq_v1_multi_add_pair(struct cs40l26_private *cs40l26,
+ const struct reg_sequence *reg_seq, int num_regs, bool replace)
+{
+ int ret, i;
+
+ for (i = 0; i < num_regs; i++) {
+ ret = cs40l26_pseq_v1_add_pair(cs40l26, (u16) (reg_seq[i].reg &
+ CS40L26_PSEQ_V1_ADDR_MASK), reg_seq[i].def,
+ replace);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(cs40l26_pseq_v1_multi_add_pair);
+
+static int cs40l26_pseq_v2_add_op(struct cs40l26_private *cs40l26,
+ int num_words, u32 *words)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ u32 offset_for_new_op, *op_words;
+ int ret;
+ struct cs40l26_pseq_v2_op *pseq_v2_op_end, *pseq_v2_op_new, *op;
+
+ /* get location of the list terminator */
+ list_for_each_entry(pseq_v2_op_end, &cs40l26->pseq_v2_op_head, list) {
+ if (pseq_v2_op_end->operation == CS40L26_PSEQ_V2_OP_END)
+ break;
+ }
+
+ if (pseq_v2_op_end->operation != CS40L26_PSEQ_V2_OP_END) {
+ dev_err(dev, "Failed to find END_OF_SCRIPT\n");
+ return -EINVAL;
+ }
+
+ offset_for_new_op = pseq_v2_op_end->offset;
+
+ /* add new op to list */
+ op_words = kzalloc(num_words * CL_DSP_BYTES_PER_WORD, GFP_KERNEL);
+ if (!op_words)
+ return -ENOMEM;
+ memcpy(op_words, words, num_words * CL_DSP_BYTES_PER_WORD);
+ pseq_v2_op_new = devm_kzalloc(dev, sizeof(*pseq_v2_op_new), GFP_KERNEL);
+ if (!pseq_v2_op_new) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ pseq_v2_op_new->size = num_words;
+ pseq_v2_op_new->offset = offset_for_new_op;
+ pseq_v2_op_new->words = op_words;
+ list_add(&pseq_v2_op_new->list, &cs40l26->pseq_v2_op_head);
+
+ cs40l26->pseq_v2_num_ops++;
+
+ /* bump end of script offset to accomodate new operation */
+ pseq_v2_op_end->offset += num_words * CL_DSP_BYTES_PER_WORD;
+
+ list_for_each_entry(op, &cs40l26->pseq_v2_op_head, list) {
+ if (op->offset >= offset_for_new_op) {
+ ret = regmap_bulk_write(regmap, cs40l26->pseq_base +
+ op->offset,
+ op->words,
+ op->size);
+ if (ret) {
+ dev_err(dev, "Failed to write op\n");
+ return -EIO;
+ }
+ }
+ }
+
+ return ret;
+
+err_free:
+ kfree(op_words);
+
+ return ret;
+}
+
+static int cs40l26_pseq_v2_add_write_reg_full(struct cs40l26_private *cs40l26,
+ u32 addr, u32 data, bool update_if_op_already_in_seq)
+{
+ int ret;
+ struct cs40l26_pseq_v2_op *op;
+ u32 op_words[CS40L26_PSEQ_V2_OP_WRITE_REG_FULL_WORDS];
+
+ op_words[0] = (CS40L26_PSEQ_V2_OP_WRITE_REG_FULL <<
+ CS40L26_PSEQ_V2_OP_SHIFT);
+ op_words[0] |= addr >> 16;
+ op_words[1] = (addr & 0x0000FFFF) << 8;
+ op_words[1] |= (data & 0xFF000000) >> 24;
+ op_words[2] = (data & 0x00FFFFFF);
+
+ if (update_if_op_already_in_seq) {
+ list_for_each_entry(op, &cs40l26->pseq_v2_op_head, list) {
+ /* check if op with same op and addr already exists */
+ if ((op->words[0] == op_words[0]) &&
+ ((op->words[1] & 0xFFFFFF00) ==
+ (op_words[1] & 0xFFFFFF00))) {
+ /* update data in the existing op and return */
+ ret = regmap_bulk_write(cs40l26->regmap,
+ cs40l26->pseq_base + op->offset,
+ op_words, op->size);
+ if (ret)
+ dev_err(cs40l26->dev,
+ "Failed to update op\n");
+ return ret;
+ }
+ }
+ }
+
+ /* if no matching op is found or !update, add the op */
+ ret = cs40l26_pseq_v2_add_op(cs40l26,
+ CS40L26_PSEQ_V2_OP_WRITE_REG_FULL_WORDS,
+ op_words);
+
+ return ret;
+}
+
+int cs40l26_pseq_v2_multi_add_write_reg_full(struct cs40l26_private *cs40l26,
+ const struct reg_sequence *reg_seq, int num_regs,
+ bool update_if_op_already_in_seq)
+{
+ int ret, i;
+
+ for (i = 0; i < num_regs; i++) {
+ ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26,
+ reg_seq[i].reg, reg_seq[i].def,
+ update_if_op_already_in_seq);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(cs40l26_pseq_v2_multi_add_write_reg_full);
+
+static int cs40l26_pseq_v1_init(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int ret, i, index = 0;
+ u8 upper_val = 0;
+ u16 addr = 0;
+ u32 val, word, algo_id;
+
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM)
+ algo_id = CS40L26_PM_ALGO_ID;
+ else
+ algo_id = CS40L26_PM_ROM_ALGO_ID;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "POWER_ON_SEQUENCE",
+ CL_DSP_XM_UNPACKED_TYPE, algo_id, &cs40l26->pseq_base);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < CS40L26_PSEQ_V1_MAX_WRITES; i++) {
+ ret = regmap_read(cs40l26->regmap,
+ cs40l26->pseq_base +
+ (i * CL_DSP_BYTES_PER_WORD), &word);
+ if (ret) {
+ dev_err(dev, "Failed to read from power on seq.\n");
+ return ret;
+ }
+
+ if ((word & CS40L26_PSEQ_V1_LIST_TERM_MASK) ==
+ CS40L26_PSEQ_V1_LIST_TERM)
+ break;
+
+ if (i % CS40L26_PSEQ_V1_PAIR_NUM_WORDS) { /* lower half */
+ index = i / CS40L26_PSEQ_V1_PAIR_NUM_WORDS;
+ val = (upper_val << CS40L26_PSEQ_V1_VAL_SHIFT) |
+ (word & CS40L26_PSEQ_V1_VAL_MASK);
+
+ cs40l26->pseq_v1_table[index].addr = addr;
+ cs40l26->pseq_v1_table[index].val = val;
+ } else { /* upper half */
+ addr = (word & CS40L26_PSEQ_V1_ADDR_WORD_MASK) >>
+ CS40L26_PSEQ_V1_ADDR_SHIFT;
+
+ upper_val = word & CS40L26_PSEQ_V1_VAL_WORD_UPPER_MASK;
+ }
+ }
+
+ if (i >= CS40L26_PSEQ_V1_MAX_WRITES) {
+ dev_err(dev, "Original sequence exceeds max # of entries\n");
+ return -E2BIG;
+ }
+
+ ret = regmap_write(cs40l26->regmap, cs40l26->pseq_base +
+ (i * CL_DSP_BYTES_PER_WORD), CS40L26_PSEQ_V1_LIST_TERM);
+ if (ret)
+ return ret;
+
+ cs40l26->pseq_v1_len = index + 1;
+ return 0;
+}
+
+static int cs40l26_pseq_v2_init(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int ret, i, j, num_words, read_size;
+ u8 operation;
+ u32 words[CS40L26_PSEQ_V2_MAX_WORDS], algo_id, *op_words;
+ struct cs40l26_pseq_v2_op *pseq_v2_op;
+
+ INIT_LIST_HEAD(&cs40l26->pseq_v2_op_head);
+ cs40l26->pseq_v2_num_ops = 0;
+
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM)
+ algo_id = CS40L26_PM_ALGO_ID;
+ else
+ algo_id = CS40L26_PM_ROM_ALGO_ID;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "POWER_ON_SEQUENCE",
+ CL_DSP_XM_UNPACKED_TYPE, algo_id, &cs40l26->pseq_base);
+ if (ret)
+ return ret;
+
+ /* read pseq memory space */
+ i = 0;
+ while (i < CS40L26_PSEQ_V2_MAX_WORDS) {
+ read_size = min(CS40L26_MAX_I2C_READ_SIZE_BYTES,
+ CS40L26_PSEQ_V2_MAX_WORDS - i);
+ ret = regmap_bulk_read(cs40l26->regmap,
+ cs40l26->pseq_base + i * CL_DSP_BYTES_PER_WORD,
+ words + i,
+ read_size);
+ if (ret) {
+ dev_err(dev, "Failed to read from power on seq.\n");
+ return ret;
+ }
+ i += read_size;
+ }
+
+ i = 0;
+ while (i < CS40L26_PSEQ_V2_MAX_WORDS) {
+ operation = (words[i] & CS40L26_PSEQ_V2_OP_MASK) >>
+ CS40L26_PSEQ_V2_OP_SHIFT;
+
+ /* get num words for given operation */
+ for (j = 0; j < CS40L26_PSEQ_V2_NUM_OPS; j++) {
+ if (cs40l26_pseq_v2_op_sizes[j][0] == operation) {
+ num_words = cs40l26_pseq_v2_op_sizes[j][1];
+ break;
+ }
+ }
+
+ if (j == CS40L26_PSEQ_V2_NUM_OPS) {
+ dev_err(dev, "Failed to determine pseq_v2 op size\n");
+ return -EINVAL;
+ }
+
+ op_words = kzalloc(num_words * CL_DSP_BYTES_PER_WORD,
+ GFP_KERNEL);
+ if (!op_words)
+ return -ENOMEM;
+ memcpy(op_words, &words[i], num_words * CL_DSP_BYTES_PER_WORD);
+
+ pseq_v2_op = devm_kzalloc(dev, sizeof(*pseq_v2_op), GFP_KERNEL);
+ if (!pseq_v2_op) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ pseq_v2_op->size = num_words;
+ pseq_v2_op->offset = i * CL_DSP_BYTES_PER_WORD;
+ pseq_v2_op->operation = operation;
+ pseq_v2_op->words = op_words;
+ list_add(&pseq_v2_op->list, &cs40l26->pseq_v2_op_head);
+
+ cs40l26->pseq_v2_num_ops++;
+ i += num_words;
+
+ if (operation == CS40L26_PSEQ_V2_OP_END)
+ break;
+
+ }
+
+ dev_dbg(dev, "PSEQ_V2 num ops: %d\n", cs40l26->pseq_v2_num_ops);
+ dev_dbg(dev, "offset\tsize\twords\n");
+ list_for_each_entry(pseq_v2_op, &cs40l26->pseq_v2_op_head, list) {
+ dev_dbg(dev, "0x%04X\t%d", pseq_v2_op->offset,
+ pseq_v2_op->size);
+ for (j = 0; j < pseq_v2_op->size; j++)
+ dev_dbg(dev, "0x%08X", *(pseq_v2_op->words + j));
+ }
+
+ if (operation != CS40L26_PSEQ_V2_OP_END) {
+ dev_err(dev, "PSEQ_V2 END_OF_SCRIPT not found\n");
+ return -E2BIG;
+ }
+
+ return ret;
+
+err_free:
+ kfree(op_words);
+
+ return ret;
+}
+
+static int cs40l26_pseq_init(struct cs40l26_private *cs40l26)
+{
+ int ret;
+
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ ret = cs40l26_pseq_v1_init(cs40l26);
+ break;
+ case CS40L26_REVID_A1:
+ ret = cs40l26_pseq_v2_init(cs40l26);
+ break;
+ default:
+ dev_err(cs40l26->dev, "Revid ID not supported: %02X\n",
+ cs40l26->revid);
+ return -EINVAL;
+ }
+
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to init pseq\n");
+
+ return ret;
+}
+
+static void cs40l26_set_gain_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 =
+ container_of(work, struct cs40l26_private, set_gain_work);
+ u32 val;
+ int ret;
+
+ pm_runtime_get_sync(cs40l26->dev);
+ mutex_lock(&cs40l26->lock);
+
+ ret = regmap_update_bits(cs40l26->regmap, CS40L26_AMP_CTRL,
+ CS40L26_AMP_CTRL_VOL_PCM_MASK,
+ cs40l26->amp_vol_pcm << CS40L26_AMP_CTRL_VOL_PCM_SHIFT);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to update digtal gain\n");
+ goto err_mutex;
+ }
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_AMP_CTRL, &val);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to read AMP control\n");
+ goto err_mutex;
+ }
+
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ ret = cs40l26_pseq_v1_add_pair(cs40l26,
+ CS40L26_AMP_CTRL, val, true);
+ break;
+ case CS40L26_REVID_A1:
+ ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26,
+ CS40L26_AMP_CTRL, val, true);
+ break;
+ default:
+ dev_err(cs40l26->dev, "Revid ID not supported: %02X\n",
+ cs40l26->revid);
+ }
+
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to set gain in pseq\n");
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+}
+
+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;
+ int ret = 0;
+ unsigned int reg, freq;
+ u32 index, algo_id;
+ u16 duration;
+
+ pm_runtime_get_sync(dev);
+ mutex_lock(&cs40l26->lock);
+
+ if (cs40l26->effect->u.periodic.waveform == FF_CUSTOM)
+ index = cs40l26->trigger_indices[cs40l26->effect->id];
+
+ if (index >= CS40L26_OWT_INDEX_START && index <= CS40L26_OWT_INDEX_END)
+ duration = CS40L26_SAMPS_TO_MS((cs40l26->owt_wlength &
+ CS40L26_WT_TYPE10_WAVELEN_MAX));
+ else
+ duration = cs40l26->effect->replay.length;
+
+ hrtimer_start(&cs40l26->vibe_timer,
+ ktime_set(CS40L26_MS_TO_SECS(duration),
+ CS40L26_MS_TO_NS(duration % 1000)), HRTIMER_MODE_REL);
+
+ cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_HAPTIC);
+
+ switch (cs40l26->effect->u.periodic.waveform) {
+ case FF_CUSTOM:
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ index, CS40L26_DSP_MBOX_RESET);
+ if (ret)
+ goto err_mutex;
+ break;
+ case FF_SINE:
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM)
+ algo_id = CS40L26_BUZZGEN_ALGO_ID;
+ else
+ algo_id = CS40L26_BUZZGEN_ROM_ALGO_ID;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "BUZZ_EFFECTS2_BUZZ_FREQ",
+ CL_DSP_XM_UNPACKED_TYPE, algo_id, &reg);
+ if (ret) {
+ dev_err(dev, "Failed to find BUZZGEN control\n");
+ goto err_mutex;
+ }
+
+ freq = CS40L26_MS_TO_HZ(cs40l26->effect->u.periodic.period);
+
+ ret = regmap_write(cs40l26->regmap, reg, freq);
+ if (ret)
+ goto err_mutex;
+
+ ret = regmap_write(cs40l26->regmap, reg +
+ CS40L26_BUZZGEN_LEVEL_OFFSET,
+ CS40L26_BUZZGEN_LEVEL_DEFAULT);
+ if (ret)
+ goto err_mutex;
+
+ ret = regmap_write(cs40l26->regmap, reg +
+ CS40L26_BUZZGEN_DURATION_OFFSET, duration /
+ CS40L26_BUZZGEN_DURATION_DIV_STEP);
+ if (ret)
+ goto err_mutex;
+
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_BUZZGEN_INDEX_CP_TRIGGER,
+ CS40L26_DSP_MBOX_RESET);
+ if (ret)
+ goto err_mutex;
+ break;
+ default:
+ dev_err(dev, "Invalid waveform type: 0x%X\n",
+ cs40l26->effect->u.periodic.waveform);
+ ret = -EINVAL;
+ goto err_mutex;
+ }
+
+err_mutex:
+ if (ret)
+ cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_STOPPED);
+
+ mutex_unlock(&cs40l26->lock);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+}
+
+static void cs40l26_vibe_stop_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 = container_of(work,
+ struct cs40l26_private, vibe_stop_work);
+ int ret;
+
+ pm_runtime_get_sync(cs40l26->dev);
+ mutex_lock(&cs40l26->lock);
+
+ if (cs40l26->vibe_state == CS40L26_VIBE_STATE_STOPPED)
+ goto mutex_exit;
+
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_STOP_PLAYBACK, CS40L26_DSP_MBOX_RESET);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to stop playback\n");
+ goto mutex_exit;
+ }
+
+ cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_STOPPED);
+
+mutex_exit:
+ mutex_unlock(&cs40l26->lock);
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_put_autosuspend(cs40l26->dev);
+}
+
+static enum hrtimer_restart cs40l26_vibe_timer(struct hrtimer *timer)
+{
+ struct cs40l26_private *cs40l26 =
+ container_of(timer, struct cs40l26_private, vibe_timer);
+
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work);
+
+ return HRTIMER_NORESTART;
+}
+
+static void cs40l26_set_gain(struct input_dev *dev, u16 gain)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+
+ cs40l26->amp_vol_pcm = CS40L26_AMP_VOL_PCM_MAX & gain;
+
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work);
+}
+
+static int cs40l26_playback_effect(struct input_dev *dev,
+ int effect_id, int val)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ struct ff_effect *effect;
+
+ effect = &dev->ff->effects[effect_id];
+ if (!effect) {
+ dev_err(cs40l26->dev, "No such effect to playback\n");
+ return -EINVAL;
+ }
+
+ cs40l26->effect = effect;
+
+ if (val > 0) {
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_start_work);
+ } else {
+ hrtimer_cancel(&cs40l26->vibe_timer);
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work);
+ }
+
+ return 0;
+}
+
+static int cs40l26_owt_get_wlength(struct cs40l26_private *cs40l26, u8 index)
+{
+ struct device *dev = cs40l26->dev;
+ struct cl_dsp_owt_header *entry;
+ struct cl_dsp_memchunk ch;
+
+ if (index == 0)
+ return 0;
+
+ entry = &cs40l26->dsp->wt_desc->owt.waves[index];
+
+ switch (entry->type) {
+ case WT_TYPE_V6_PCM_F0_REDC:
+ case WT_TYPE_V6_PCM_F0_REDC_VAR:
+ case WT_TYPE_V6_PWLE:
+ break;
+ default:
+ dev_err(dev, "Cannot size waveform type %u\n", entry->type);
+ return -EINVAL;
+ }
+
+ ch = cl_dsp_memchunk_create(entry->data, sizeof(u32));
+
+ /* First 24 bits of each waveform is the length in samples @ 8 kHz */
+ return cl_dsp_memchunk_read(&ch, 24);
+}
+
+static void cs40l26_owt_get_section_info(struct cs40l26_private *cs40l26,
+ struct cl_dsp_memchunk *ch,
+ struct cs40l26_owt_section *sections, u8 nsections)
+{
+ int i;
+
+ for (i = 0; i < nsections; i++) {
+ cl_dsp_memchunk_read(ch, 8); /* Skip amplitude */
+ sections[i].index = cl_dsp_memchunk_read(ch, 8);
+ sections[i].repeat = cl_dsp_memchunk_read(ch, 8);
+ sections[i].flags = cl_dsp_memchunk_read(ch, 8);
+ sections[i].delay = cl_dsp_memchunk_read(ch, 16);
+
+ if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) {
+ cl_dsp_memchunk_read(ch, 8); /* Skip padding */
+ sections[i].duration = cl_dsp_memchunk_read(ch, 16);
+ }
+ }
+}
+
+static int cs40l26_owt_calculate_wlength(struct cs40l26_private *cs40l26,
+ struct cl_dsp_memchunk *ch)
+{
+ u32 total_len = 0, section_len = 0, loop_len = 0;
+ bool in_loop = false;
+ struct cs40l26_owt_section *sections;
+ int ret = 0, i, wlen_whole;
+ u8 nsections, global_rep;
+ u32 dlen, wlen;
+
+ cl_dsp_memchunk_read(ch, 8); /* Skip padding */
+ nsections = cl_dsp_memchunk_read(ch, 8);
+ global_rep = cl_dsp_memchunk_read(ch, 8);
+
+ if (nsections < 1) {
+ dev_err(cs40l26->dev, "Not enough sections for composite\n");
+ return -EINVAL;
+ }
+
+ sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section),
+ GFP_KERNEL);
+ if (!sections) {
+ dev_err(cs40l26->dev, "Failed to allocate OWT sections\n");
+ return -ENOMEM;
+ }
+
+ cs40l26_owt_get_section_info(cs40l26, ch, sections, nsections);
+
+ for (i = 0; i < nsections; i++) {
+ wlen_whole = cs40l26_owt_get_wlength(cs40l26,
+ sections[i].index);
+ if (wlen_whole < 0) {
+ dev_err(cs40l26->dev,
+ "Failed to get wlength for index %u\n",
+ sections[i].index);
+ ret = wlen_whole;
+ goto err_free;
+ }
+
+ if (wlen_whole & CS40L26_WT_TYPE10_WAVELEN_INDEF) {
+ if (!(sections[i].flags &
+ CS40L26_WT_TYPE10_COMP_DURATION_FLAG)) {
+ dev_err(cs40l26->dev,
+ "Indefinite entry needs duration\n");
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ wlen = CS40L26_WT_TYPE10_WAVELEN_MAX;
+ } else {
+ /* Length is 22 LSBs, filter out flags */
+ wlen = wlen_whole & CS40L26_WT_TYPE10_WAVELEN_MAX;
+ }
+
+ dlen = 8 * sections[i].delay;
+
+ if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) {
+ if (wlen > (2 * sections[i].duration))
+ wlen = 2 * sections[i].duration;
+ }
+
+ section_len = wlen + dlen;
+ loop_len += section_len;
+
+ if (sections[i].repeat == 0xFF) {
+ in_loop = true;
+ } else if (sections[i].repeat) {
+ total_len += (loop_len * (sections[i].repeat + 1));
+
+ in_loop = false;
+ loop_len = 0;
+ } else if (!in_loop) {
+ total_len += section_len;
+ loop_len = 0;
+ }
+
+ section_len = 0;
+ }
+
+ cs40l26->owt_wlength = (total_len * (global_rep + 1)) |
+ CS40L26_WT_TYPE10_WAVELEN_CALCULATED;
+
+err_free:
+ kfree(sections);
+
+ return ret;
+}
+
+static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, s16 *data,
+ u32 data_size)
+{
+ bool pwle = (data[0] == 0x0000) ? false : true;
+ u32 data_size_bytes = data_size * 2;
+ struct device *dev = cs40l26->dev;
+ struct cl_dsp *dsp = cs40l26->dsp;
+ u32 full_data_size, header_size = CL_DSP_OWT_HEADER_ENTRY_SIZE;
+ unsigned int write_reg, reg, wt_offset, wt_size, wt_base;
+ struct cl_dsp_memchunk header_ch, data_ch;
+ u8 *full_data, *header;
+ int ret = 0, i;
+
+ data_ch = cl_dsp_memchunk_create((void *) data, data_size_bytes);
+
+ if (pwle) {
+ header_size += CS40L26_WT_TERM_SIZE;
+
+ cs40l26->owt_wlength = cl_dsp_memchunk_read(&data_ch, 24);
+ } else {
+ header_size += CS40L26_WT_WLEN_TERM_SIZE;
+
+ ret = cs40l26_owt_calculate_wlength(cs40l26, &data_ch);
+ if (ret)
+ return ret;
+ }
+ full_data_size = header_size + data_size_bytes;
+
+ header = kcalloc(header_size, sizeof(u8), GFP_KERNEL);
+ if (!header)
+ return -ENOMEM;
+
+ header_ch = cl_dsp_memchunk_create((void *) header, header_size);
+ /* Header */
+ cl_dsp_memchunk_write(&header_ch, 16,
+ CS40L26_WT_HEADER_DEFAULT_FLAGS);
+
+ if (pwle)
+ cl_dsp_memchunk_write(&header_ch, 8, WT_TYPE_V6_PWLE);
+ else
+ cl_dsp_memchunk_write(&header_ch, 8, WT_TYPE_V6_COMPOSITE);
+
+ cl_dsp_memchunk_write(&header_ch, 24, CS40L26_WT_HEADER_OFFSET);
+ cl_dsp_memchunk_write(&header_ch, 24, full_data_size /
+ CL_DSP_BYTES_PER_WORD);
+
+ cl_dsp_memchunk_write(&header_ch, 24, CS40L26_WT_HEADER_TERM);
+
+ if (!pwle) /* Wlength is included in PWLE raw data */
+ cl_dsp_memchunk_write(&header_ch, 24, cs40l26->owt_wlength);
+
+ full_data = kcalloc(full_data_size, sizeof(u8), GFP_KERNEL);
+ if (!full_data) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ memcpy(full_data, header, header_ch.bytes);
+ memcpy(full_data + header_ch.bytes, data, data_size_bytes);
+
+ pm_runtime_get_sync(dev);
+
+ ret = cl_dsp_get_reg(dsp, "OWT_BASE_XM", CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_VIBEGEN_ALGO_ID, &reg);
+ if (ret)
+ goto err_pm;
+
+ ret = regmap_read(cs40l26->regmap, reg, &wt_offset);
+ if (ret) {
+ dev_err(dev, "Failed to get wavetable offset\n");
+ goto err_pm;
+ }
+
+ ret = cl_dsp_get_reg(dsp, "OWT_SIZE_XM", CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_VIBEGEN_ALGO_ID, &reg);
+ if (ret)
+ goto err_pm;
+
+ ret = regmap_read(cs40l26->regmap, reg, &wt_size);
+ if (ret) {
+ dev_err(dev, "Failed to get available WT size\n");
+ goto err_pm;
+ }
+
+ if (wt_size < full_data_size) {
+ dev_err(dev, "No space for OWT waveform\n");
+ ret = -ENOSPC;
+ goto err_pm;
+ }
+
+ ret = cl_dsp_get_reg(dsp, CS40L26_WT_NAME_XM, CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_VIBEGEN_ALGO_ID, &wt_base);
+ if (ret)
+ goto err_pm;
+
+ write_reg = wt_base + (wt_offset * CL_DSP_BYTES_PER_WORD);
+
+ ret = cl_dsp_raw_write(cs40l26->dsp, write_reg, full_data,
+ full_data_size, CL_DSP_MAX_WLEN);
+ if (ret) {
+ dev_err(dev, "Failed to sync OWT\n");
+ goto err_pm;
+ }
+
+ dev_dbg(dev, "Successfully wrote waveform (%u bytes) to 0x%08X\n",
+ full_data_size, write_reg);
+
+err_pm:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+err_free:
+ kfree(header);
+ kfree(full_data);
+
+ return ret;
+}
+
+static int cs40l26_upload_effect(struct input_dev *dev,
+ struct ff_effect *effect, struct ff_effect *old)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ struct device *cdev = cs40l26->dev;
+ s16 *raw_custom_data = NULL;
+ int ret = 0;
+ u32 trigger_index, min_index, max_index;
+ u16 index, bank;
+
+ if (effect->type != FF_PERIODIC) {
+ dev_err(cdev, "Effect type 0x%X not supported\n",
+ effect->type);
+ return -EINVAL;
+ }
+
+ if (effect->replay.length < 0 ||
+ effect->replay.length > CS40L26_TIMEOUT_MS_MAX) {
+ dev_err(cdev, "Invalid playback duration: %d ms\n",
+ effect->replay.length);
+ return -EINVAL;
+ }
+
+ switch (effect->u.periodic.waveform) {
+ case FF_CUSTOM:
+ raw_custom_data =
+ kzalloc(sizeof(s16) *
+ effect->u.periodic.custom_len, GFP_KERNEL);
+ if (!raw_custom_data)
+ return -ENOMEM;
+
+ if (copy_from_user(raw_custom_data,
+ effect->u.periodic.custom_data,
+ sizeof(s16) * effect->u.periodic.custom_len)) {
+ dev_err(cdev, "Failed to get user data\n");
+ ret = -EFAULT;
+ goto out_free;
+ }
+
+ if (effect->u.periodic.custom_len > CS40L26_CUSTOM_DATA_SIZE) {
+ ret = cs40l26_owt_upload(cs40l26, raw_custom_data,
+ effect->u.periodic.custom_len);
+ if (ret)
+ goto out_free;
+
+ bank = CS40L26_OWT_BANK_ID;
+ index = cs40l26->num_owt_effects;
+ } else {
+ bank = ((u16) raw_custom_data[0]);
+ index = ((u16) raw_custom_data[1]) &
+ CS40L26_MAX_INDEX_MASK;
+ }
+
+ switch (bank) {
+ case CS40L26_RAM_BANK_ID:
+ min_index = CS40L26_RAM_INDEX_START;
+ max_index = min_index + cs40l26->num_waves - 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(cdev, "Bank ID (%u) out of bounds\n", bank);
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ trigger_index = index + min_index;
+
+ if (trigger_index >= min_index && trigger_index <= max_index) {
+ cs40l26->trigger_indices[effect->id] = trigger_index;
+ } else {
+ dev_err(cdev, "Trigger index (0x%X) out of bounds\n",
+ trigger_index);
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ break;
+ case FF_SINE:
+ if (effect->u.periodic.period) {
+ if (effect->u.periodic.period
+ < CS40L26_BUZZGEN_PERIOD_MIN
+ || effect->u.periodic.period
+ > CS40L26_BUZZGEN_PERIOD_MAX) {
+ dev_err(cdev,
+ "%u ms period not within range (4-10 ms)\n",
+ effect->u.periodic.period);
+ return -EINVAL;
+ }
+ } else {
+ dev_err(cdev, "Sine wave period not specified\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_err(cdev, "Periodic waveform type 0x%X not supported\n",
+ effect->u.periodic.waveform);
+ return -EINVAL;
+ }
+
+out_free:
+ kfree(raw_custom_data);
+
+ return ret;
+}
+
+static int cs40l26_input_init(struct cs40l26_private *cs40l26)
+{
+ int ret;
+ struct device *dev = cs40l26->dev;
+
+ cs40l26->input = devm_input_allocate_device(dev);
+ if (!cs40l26->input)
+ return -ENOMEM;
+
+ cs40l26->input->name = "cs40l26_input";
+ cs40l26->input->id.product = cs40l26->devid;
+ cs40l26->input->id.version = cs40l26->revid;
+
+ input_set_drvdata(cs40l26->input, cs40l26);
+ input_set_capability(cs40l26->input, EV_FF, FF_PERIODIC);
+ input_set_capability(cs40l26->input, EV_FF, FF_CUSTOM);
+ input_set_capability(cs40l26->input, EV_FF, FF_SINE);
+ input_set_capability(cs40l26->input, EV_FF, FF_GAIN);
+
+ ret = input_ff_create(cs40l26->input, FF_MAX_EFFECTS);
+ if (ret) {
+ dev_err(dev, "Failed to create FF device: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * input_ff_create() automatically sets FF_RUMBLE capabilities;
+ * we want to restrtict this to only FF_PERIODIC
+ */
+ __clear_bit(FF_RUMBLE, cs40l26->input->ffbit);
+
+ cs40l26->input->ff->upload = cs40l26_upload_effect;
+ cs40l26->input->ff->playback = cs40l26_playback_effect;
+ cs40l26->input->ff->set_gain = cs40l26_set_gain;
+
+ ret = input_register_device(cs40l26->input);
+ if (ret) {
+ dev_err(dev, "Cannot register input device: %d\n", ret);
+ return ret;
+ }
+
+ hrtimer_init(&cs40l26->vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ cs40l26->vibe_timer.function = cs40l26_vibe_timer;
+
+ ret = sysfs_create_group(&cs40l26->input->dev.kobj,
+ &cs40l26_dev_attr_group);
+ if (ret)
+ dev_err(dev, "Failed to create sysfs group: %d\n", ret);
+ else
+ cs40l26->vibe_init_success = true;
+
+ return ret;
+}
+
+static int cs40l26_part_num_resolve(struct cs40l26_private *cs40l26)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ int ret;
+ u32 val;
+
+ ret = regmap_read(regmap, CS40L26_DEVID, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read device ID\n");
+ return ret;
+ }
+
+ val &= CS40L26_DEVID_MASK;
+ if (val != CS40L26_DEVID_A && val != CS40L26_DEVID_B) {
+ dev_err(dev, "Invalid device ID: 0x%06X\n", val);
+ return -EINVAL;
+ }
+
+ cs40l26->devid = val;
+
+ ret = regmap_read(regmap, CS40L26_REVID, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read revision ID\n");
+ return ret;
+ }
+
+ val &= CS40L26_REVID_MASK;
+ switch (val) {
+ case CS40L26_REVID_A0:
+ case CS40L26_REVID_A1:
+ cs40l26->revid = val;
+ break;
+ default:
+ dev_err(dev, "Invalid device revision: 0x%02X\n", val);
+ return -EINVAL;
+ }
+
+ dev_info(dev, "Cirrus Logic %s ID: 0x%06X, Revision: 0x%02X\n",
+ CS40L26_DEV_NAME, cs40l26->devid, cs40l26->revid);
+
+ return 0;
+}
+
+static int cs40l26_cl_dsp_init(struct cs40l26_private *cs40l26)
+{
+ int ret = 0;
+
+ cs40l26->dsp = cl_dsp_create(cs40l26->dev, cs40l26->regmap);
+ if (!cs40l26->dsp)
+ return -ENOMEM;
+
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_ROM) {
+ cs40l26->fw.min_rev = CS40L26_FW_ROM_MIN_REV;
+ cs40l26->fw.num_coeff_files = 0;
+ cs40l26->fw.coeff_files = NULL;
+ } else {
+ if (cs40l26->revid == CS40L26_REVID_A1)
+ cs40l26->fw.min_rev = CS40L26_FW_A1_RAM_MIN_REV;
+ else
+ cs40l26->fw.min_rev = CS40L26_FW_A0_RAM_MIN_REV;
+
+ cs40l26->fw.num_coeff_files =
+ ARRAY_SIZE(cs40l26_ram_coeff_files);
+ cs40l26->fw.coeff_files = cs40l26_ram_coeff_files;
+
+ ret = cl_dsp_wavetable_create(cs40l26->dsp,
+ CS40L26_VIBEGEN_ALGO_ID, CS40L26_WT_NAME_XM,
+ CS40L26_WT_NAME_YM, "cs40l26.bin");
+ }
+
+ cs40l26->num_owt_effects = 0;
+
+ return ret;
+}
+
+static int cs40l26_wksrc_config(struct cs40l26_private *cs40l26)
+{
+ u32 unmask_bits, mask_bits;
+ int ret;
+
+ unmask_bits = BIT(CS40L26_IRQ1_WKSRC_STS_ANY) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO1) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_I2C);
+
+ /* SPI support is not yet available */
+ mask_bits = BIT(CS40L26_IRQ1_WKSRC_STS_SPI);
+
+ if (cs40l26->devid == CS40L26_DEVID_A)
+ mask_bits |= (BIT(CS40L26_IRQ1_WKSRC_STS_GPIO2) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO3) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO4));
+ else
+ unmask_bits |= (BIT(CS40L26_IRQ1_WKSRC_STS_GPIO2) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO3) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO4));
+
+ ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, mask_bits,
+ CS40L26_IRQ_MASK);
+ if (ret)
+ return ret;
+
+ return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1,
+ unmask_bits, CS40L26_IRQ_UNMASK);
+}
+
+static int cs40l26_gpio_config(struct cs40l26_private *cs40l26)
+{
+ u32 unmask_bits;
+
+ unmask_bits = BIT(CS40L26_IRQ1_GPIO1_RISE)
+ | BIT(CS40L26_IRQ1_GPIO1_FALL);
+
+ if (cs40l26->devid == CS40L26_DEVID_B) /* 4 GPIO config */
+ unmask_bits |= (u32) (GENMASK(CS40L26_IRQ1_GPIO4_FALL,
+ CS40L26_IRQ1_GPIO2_RISE));
+
+ return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1,
+ unmask_bits, CS40L26_IRQ_UNMASK);
+}
+
+static int cs40l26_brownout_prevention_init(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ struct regmap *regmap = cs40l26->regmap;
+ u32 vbbr_thld = 0, vpbr_thld = 0;
+ u32 vbbr_max_att = 0, vpbr_max_att = 0;
+ u32 vpbr_atk_step = 0, vbbr_atk_step = 0;
+ u32 vpbr_atk_rate = 0, vbbr_atk_rate = 0;
+ u32 vpbr_wait = 0, vbbr_wait = 0;
+ u32 vpbr_rel_rate = 0, vbbr_rel_rate = 0;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(regmap, CS40L26_BLOCK_ENABLES2, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read block enables 2\n");
+ return ret;
+ }
+
+ val |= ((cs40l26->pdata.vbbr_en << CS40L26_VBBR_EN_SHIFT)
+ | (cs40l26->pdata.vpbr_en << CS40L26_VPBR_EN_SHIFT));
+
+ ret = regmap_write(regmap, CS40L26_BLOCK_ENABLES2, val);
+ if (ret) {
+ dev_err(dev, "Failed to enable brownout prevention\n");
+ return ret;
+ }
+
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ ret = cs40l26_pseq_v1_add_pair(cs40l26,
+ CS40L26_BLOCK_ENABLES2, val, CS40L26_PSEQ_V1_REPLACE);
+ break;
+ case CS40L26_REVID_A1:
+ ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26,
+ CS40L26_BLOCK_ENABLES2, val, true);
+ break;
+ default:
+ dev_err(cs40l26->dev, "Revid ID not supported: %02X\n",
+ cs40l26->revid);
+ return -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(dev, "Failed to sequence brownout prevention\n");
+ return ret;
+ }
+
+ if (cs40l26->pdata.vbbr_en) {
+ ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_2,
+ BIT(CS40L26_IRQ2_VBBR_ATT_CLR) |
+ BIT(CS40L26_IRQ2_VBBR_FLAG),
+ CS40L26_IRQ_UNMASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(regmap, CS40L26_VBBR_CONFIG, &val);
+ if (ret) {
+ dev_err(dev, "Failed to get VBBR config.\n");
+ return ret;
+ }
+
+ if (cs40l26->pdata.vbbr_thld) {
+ if (cs40l26->pdata.vbbr_thld
+ >= CS40L26_VBBR_THLD_MV_MAX)
+ vbbr_thld = CS40L26_VBBR_THLD_MAX;
+ else if (cs40l26->pdata.vbbr_thld
+ <= CS40L26_VBBR_THLD_MV_MIN)
+ vbbr_thld = CS40L26_VBBR_THLD_MIN;
+ else
+ vbbr_thld = cs40l26->pdata.vbbr_thld /
+ CS40L26_VBBR_THLD_MV_STEP;
+
+ val &= ~CS40L26_VBBR_THLD_MASK;
+ val |= (vbbr_thld & CS40L26_VBBR_THLD_MASK);
+ }
+
+ if (cs40l26->pdata.vbbr_max_att != CS40L26_VXBR_DEFAULT) {
+ if (cs40l26->pdata.vbbr_max_att >=
+ CS40L26_VXBR_MAX_ATT_MAX)
+ vbbr_max_att = CS40L26_VXBR_MAX_ATT_MAX;
+ else
+ vbbr_max_att = cs40l26->pdata.vbbr_max_att;
+
+ val &= ~CS40L26_VXBR_MAX_ATT_MASK;
+ val |= ((vbbr_max_att << CS40L26_VXBR_MAX_ATT_SHIFT)
+ & CS40L26_VXBR_MAX_ATT_MASK);
+ }
+
+ if (cs40l26->pdata.vbbr_atk_step) {
+ if (cs40l26->pdata.vbbr_atk_step
+ <= CS40L26_VXBR_ATK_STEP_MIN)
+ vbbr_atk_step = CS40L26_VXBR_ATK_STEP_MIN;
+ else if (cs40l26->pdata.vbbr_atk_step
+ >= CS40L26_VXBR_ATK_STEP_MAX_DB)
+ vbbr_atk_step = CS40L26_VXBR_ATK_STEP_MAX;
+ else
+ vbbr_atk_step = cs40l26->pdata.vbbr_atk_step;
+
+ val &= ~CS40L26_VXBR_ATK_STEP_MASK;
+ val |= ((vbbr_atk_step << CS40L26_VXBR_ATK_STEP_SHIFT)
+ & CS40L26_VXBR_ATK_STEP_MASK);
+ }
+
+ if (cs40l26->pdata.vbbr_atk_rate !=
+ CS40L26_VXBR_DEFAULT) {
+ if (cs40l26->pdata.vbbr_atk_rate
+ > CS40L26_VXBR_ATK_RATE_MAX)
+ vbbr_atk_rate = CS40L26_VXBR_ATK_RATE_MAX;
+ else
+ vbbr_atk_rate = cs40l26->pdata.vbbr_atk_rate;
+
+ val &= ~CS40L26_VXBR_ATK_RATE_MASK;
+ val |= ((vbbr_atk_rate << CS40L26_VXBR_ATK_RATE_SHIFT)
+ & CS40L26_VXBR_ATK_RATE_MASK);
+ }
+
+ if (cs40l26->pdata.vbbr_wait != CS40L26_VXBR_DEFAULT) {
+ if (cs40l26->pdata.vbbr_wait > CS40L26_VXBR_WAIT_MAX)
+ vbbr_wait = CS40L26_VXBR_WAIT_MAX;
+ else
+ vbbr_wait = cs40l26->pdata.vbbr_wait;
+
+ val &= ~CS40L26_VXBR_WAIT_MASK;
+ val |= ((vbbr_wait << CS40L26_VXBR_WAIT_SHIFT)
+ & CS40L26_VXBR_WAIT_MASK);
+ }
+
+ if (cs40l26->pdata.vbbr_rel_rate != CS40L26_VXBR_DEFAULT) {
+ if (cs40l26->pdata.vbbr_rel_rate
+ > CS40L26_VXBR_REL_RATE_MAX)
+ vbbr_rel_rate = CS40L26_VXBR_REL_RATE_MAX;
+ else
+ vbbr_rel_rate = cs40l26->pdata.vbbr_rel_rate;
+
+ val &= ~CS40L26_VXBR_REL_RATE_MASK;
+ val |= ((vbbr_rel_rate << CS40L26_VXBR_REL_RATE_SHIFT)
+ & CS40L26_VXBR_REL_RATE_MASK);
+ }
+
+ ret = regmap_write(regmap, CS40L26_VBBR_CONFIG, val);
+ if (ret) {
+ dev_err(dev, "Failed to write VBBR config.\n");
+ return ret;
+ }
+
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ ret = cs40l26_pseq_v1_add_pair(cs40l26,
+ CS40L26_VBBR_CONFIG, val,
+ CS40L26_PSEQ_V1_REPLACE);
+ break;
+ case CS40L26_REVID_A1:
+ ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26,
+ CS40L26_VBBR_CONFIG, val,
+ true);
+ break;
+ default:
+ dev_err(cs40l26->dev, "Revid ID not supported: %02X\n",
+ cs40l26->revid);
+ return -EINVAL;
+ }
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read(regmap, CS40L26_VPBR_CONFIG, &val);
+ if (ret) {
+ dev_err(dev, "Failed to get VBBR config.\n");
+ return ret;
+ }
+
+ if (cs40l26->pdata.vpbr_en) {
+ ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_2,
+ BIT(CS40L26_IRQ2_VPBR_ATT_CLR) |
+ BIT(CS40L26_IRQ2_VPBR_FLAG),
+ CS40L26_IRQ_UNMASK);
+ if (ret)
+ return ret;
+
+ if (cs40l26->pdata.vpbr_thld) {
+ if (cs40l26->pdata.vpbr_thld
+ >= CS40L26_VPBR_THLD_MV_MAX)
+ vpbr_thld = CS40L26_VPBR_THLD_MAX;
+ else if (cs40l26->pdata.vpbr_thld
+ <= CS40L26_VPBR_THLD_MV_MIN)
+ vpbr_thld = CS40L26_VPBR_THLD_MIN;
+ else
+ vpbr_thld = (cs40l26->pdata.vpbr_thld /
+ CS40L26_VPBR_THLD_MV_DIV)
+ - CS40L26_VPBR_THLD_OFFSET;
+
+ val &= ~CS40L26_VPBR_THLD_MASK;
+ val |= (vpbr_thld & CS40L26_VPBR_THLD_MASK);
+
+ }
+
+ if (cs40l26->pdata.vpbr_max_att != CS40L26_VXBR_DEFAULT) {
+ if (cs40l26->pdata.vpbr_max_att >=
+ CS40L26_VXBR_MAX_ATT_MAX)
+ vpbr_max_att = CS40L26_VXBR_MAX_ATT_MAX;
+ else
+ vpbr_max_att = cs40l26->pdata.vpbr_max_att;
+
+ val &= ~CS40L26_VXBR_MAX_ATT_MASK;
+ val |= ((vpbr_max_att << CS40L26_VXBR_MAX_ATT_SHIFT)
+ & CS40L26_VXBR_MAX_ATT_MASK);
+ }
+
+ if (cs40l26->pdata.vpbr_atk_step) {
+ if (cs40l26->pdata.vpbr_atk_step
+ <= CS40L26_VXBR_ATK_STEP_MIN)
+ vpbr_atk_step = CS40L26_VXBR_ATK_STEP_MIN;
+ else if (cs40l26->pdata.vpbr_atk_step
+ >= CS40L26_VXBR_ATK_STEP_MAX_DB)
+ vpbr_atk_step = CS40L26_VXBR_ATK_STEP_MAX;
+ else
+ vpbr_atk_step = cs40l26->pdata.vpbr_atk_step;
+
+ val &= ~CS40L26_VXBR_ATK_STEP_MASK;
+ val |= ((vpbr_atk_step << CS40L26_VXBR_ATK_STEP_SHIFT)
+ & CS40L26_VXBR_ATK_STEP_MASK);
+ }
+
+ if (cs40l26->pdata.vpbr_atk_rate !=
+ CS40L26_VXBR_DEFAULT) {
+ if (cs40l26->pdata.vpbr_atk_rate
+ > CS40L26_VXBR_ATK_RATE_MAX)
+ vpbr_atk_rate = CS40L26_VXBR_ATK_RATE_MAX;
+ else
+ vpbr_atk_rate = cs40l26->pdata.vpbr_atk_rate;
+
+ val &= ~CS40L26_VXBR_ATK_RATE_MASK;
+ val |= ((vpbr_atk_rate << CS40L26_VXBR_ATK_RATE_SHIFT)
+ & CS40L26_VXBR_ATK_RATE_MASK);
+
+ }
+
+ if (cs40l26->pdata.vpbr_wait != CS40L26_VXBR_DEFAULT) {
+ if (cs40l26->pdata.vpbr_wait > CS40L26_VXBR_WAIT_MAX)
+ vpbr_wait = CS40L26_VXBR_WAIT_MAX;
+ else
+ vpbr_wait = cs40l26->pdata.vpbr_wait;
+
+ val &= ~CS40L26_VXBR_WAIT_MASK;
+ val |= ((vpbr_wait << CS40L26_VXBR_WAIT_SHIFT)
+ & CS40L26_VXBR_WAIT_MASK);
+ }
+
+ if (cs40l26->pdata.vpbr_rel_rate != CS40L26_VXBR_DEFAULT) {
+ if (cs40l26->pdata.vpbr_rel_rate
+ > CS40L26_VXBR_REL_RATE_MAX)
+ vpbr_rel_rate = CS40L26_VXBR_REL_RATE_MAX;
+ else
+ vpbr_rel_rate = cs40l26->pdata.vpbr_rel_rate;
+
+ val &= ~CS40L26_VXBR_REL_RATE_MASK;
+ val |= ((vpbr_rel_rate << CS40L26_VXBR_REL_RATE_SHIFT)
+ & CS40L26_VXBR_REL_RATE_MASK);
+ }
+
+ ret = regmap_write(regmap, CS40L26_VPBR_CONFIG, val);
+ if (ret) {
+ dev_err(dev, "Failed to write VPBR config.\n");
+ return ret;
+ }
+
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ ret = cs40l26_pseq_v1_add_pair(cs40l26,
+ CS40L26_VPBR_CONFIG, val,
+ CS40L26_PSEQ_V1_REPLACE);
+ break;
+ case CS40L26_REVID_A1:
+ ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26,
+ CS40L26_VPBR_CONFIG, val,
+ true);
+ break;
+ default:
+ dev_err(cs40l26->dev, "Revid ID not supported: %02X\n",
+ cs40l26->revid);
+ return -EINVAL;
+ }
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cs40l26_get_num_waves(struct cs40l26_private *cs40l26,
+ u32 *num_waves)
+{
+ int ret;
+ u32 reg;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "NUM_OF_WAVES",
+ CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_VIBEGEN_ALGO_ID, &reg);
+ if (ret)
+ return ret;
+
+ return regmap_read(cs40l26->regmap, reg, num_waves);
+}
+
+static int cs40l26_verify_fw(struct cs40l26_private *cs40l26)
+{
+ struct cs40l26_fw *fw = &cs40l26->fw;
+ unsigned int val;
+ int ret;
+
+ ret = cl_dsp_fw_id_get(cs40l26->dsp, &val);
+ if (ret)
+ return ret;
+
+ if (val != CS40L26_FW_ID) {
+ dev_err(cs40l26->dev, "Invalid firmware ID: 0x%X\n", val);
+ return -EINVAL;
+ }
+
+ ret = cl_dsp_fw_rev_get(cs40l26->dsp, &val);
+ if (ret)
+ return ret;
+
+ if (val < fw->min_rev) {
+ dev_err(cs40l26->dev, "Invalid firmware revision: %d.%d.%d\n",
+ (int) CL_DSP_GET_MAJOR(val),
+ (int) CL_DSP_GET_MINOR(val),
+ (int) CL_DSP_GET_PATCH(val));
+ return -EINVAL;
+ }
+
+ dev_info(cs40l26->dev, "Firmware revision %d.%d.%d\n",
+ (int) CL_DSP_GET_MAJOR(val),
+ (int) CL_DSP_GET_MINOR(val),
+ (int) CL_DSP_GET_PATCH(val));
+
+ return 0;
+}
+
+static int cs40l26_asp_config(struct cs40l26_private *cs40l26)
+{
+ struct reg_sequence *dsp1rx_config =
+ kcalloc(2, sizeof(struct reg_sequence), GFP_KERNEL);
+ int ret;
+
+ if (!dsp1rx_config) {
+ dev_err(cs40l26->dev, "Failed to allocate reg. sequence\n");
+ return -ENOMEM;
+ }
+
+ dsp1rx_config[0].reg = CS40L26_DSP1RX1_INPUT;
+ dsp1rx_config[0].def = CS40L26_DATA_SRC_ASPRX1;
+ dsp1rx_config[1].reg = CS40L26_DSP1RX5_INPUT;
+ dsp1rx_config[1].def = CS40L26_DATA_SRC_ASPRX2;
+
+ ret = regmap_multi_reg_write(cs40l26->regmap, dsp1rx_config, 2);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to configure ASP\n");
+ goto err_free;
+ }
+
+ switch (cs40l26->revid) {
+ case CS40L26_REVID_A0:
+ ret = cs40l26_pseq_v1_multi_add_pair(cs40l26, dsp1rx_config, 2,
+ CS40L26_PSEQ_V1_REPLACE);
+ break;
+ case CS40L26_REVID_A1:
+ ret = cs40l26_pseq_v2_multi_add_write_reg_full(cs40l26,
+ dsp1rx_config, 2, true);
+ break;
+ default:
+ dev_err(cs40l26->dev, "Revid ID not supported: %02X\n",
+ cs40l26->revid);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to add ASP config to pseq\n");
+
+err_free:
+ kfree(dsp1rx_config);
+
+ return ret;
+}
+
+static int cs40l26_dsp_config(struct cs40l26_private *cs40l26)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ unsigned int val;
+ int ret;
+ u32 reg;
+
+ ret = cs40l26_verify_fw(cs40l26);
+ if (ret)
+ goto err_out;
+
+ ret = regmap_update_bits(regmap, CS40L26_PWRMGT_CTL,
+ CS40L26_MEM_RDY_MASK,
+ CS40L26_ENABLE << CS40L26_MEM_RDY_SHIFT);
+ if (ret) {
+ dev_err(dev, "Failed to set MEM_RDY to initialize RAM\n");
+ goto err_out;
+ }
+
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM) {
+ ret = cl_dsp_get_reg(cs40l26->dsp, "CALL_RAM_INIT",
+ CL_DSP_XM_UNPACKED_TYPE,
+ CS40L26_FW_ID, &reg);
+ if (ret)
+ goto err_out;
+
+ ret = cs40l26_dsp_write(cs40l26, reg, CS40L26_ENABLE);
+ if (ret)
+ goto err_out;
+ }
+
+ cs40l26->fw_loaded = true;
+
+ ret = cs40l26_dsp_start(cs40l26);
+ if (ret)
+ goto err_out;
+
+ ret = cs40l26_pseq_init(cs40l26);
+ if (ret)
+ goto err_out;
+
+ ret = cs40l26_iseq_init(cs40l26);
+ if (ret)
+ goto err_out;
+
+ ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1,
+ BIT(CS40L26_IRQ1_VIRTUAL2_MBOX_WR), CS40L26_IRQ_UNMASK);
+ if (ret)
+ goto err_out;
+
+ ret = cs40l26_wksrc_config(cs40l26);
+ if (ret)
+ goto err_out;
+
+ ret = cs40l26_gpio_config(cs40l26);
+ if (ret)
+ goto err_out;
+
+ ret = cs40l26_brownout_prevention_init(cs40l26);
+ if (ret)
+ goto err_out;
+
+ /* ensure firmware running */
+ ret = cl_dsp_get_reg(cs40l26->dsp, "HALO_STATE",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_FW_ID,
+ &reg);
+ if (ret)
+ goto err_out;
+
+ ret = regmap_read(regmap, reg, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read HALO_STATE\n");
+ goto err_out;
+ }
+
+ if (val != CS40L26_DSP_HALO_STATE_RUN) {
+ dev_err(dev, "Firmware in unexpected state: 0x%X\n", val);
+ goto err_out;
+ }
+
+ ret = cs40l26_pm_runtime_setup(cs40l26);
+ if (ret)
+ goto err_out;
+
+ pm_runtime_get_sync(dev);
+
+ ret = cs40l26_asp_config(cs40l26);
+ if (ret)
+ goto pm_err;
+
+ ret = cs40l26_get_num_waves(cs40l26, &cs40l26->num_waves);
+ if (!ret)
+ dev_info(dev, "%s loaded with %u RAM waveforms\n",
+ CS40L26_DEV_NAME, cs40l26->num_waves);
+
+pm_err:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+err_out:
+ enable_irq(cs40l26->irq);
+ return ret;
+}
+
+static void cs40l26_firmware_load(const struct firmware *fw, void *context)
+{
+ struct cs40l26_private *cs40l26 = (struct cs40l26_private *)context;
+ struct device *dev = cs40l26->dev;
+ const struct firmware *coeff_fw;
+ int ret, i;
+
+ if (!fw) {
+ dev_err(dev, "Failed to request firmware file\n");
+ return;
+ }
+
+ cs40l26->pm_ready = false;
+
+ ret = cl_dsp_firmware_parse(cs40l26->dsp, fw,
+ cs40l26->fw_mode == CS40L26_FW_MODE_RAM);
+ release_firmware(fw);
+ if (ret)
+ return;
+
+ for (i = 0; i < cs40l26->fw.num_coeff_files; i++) {
+ request_firmware(&coeff_fw, cs40l26->fw.coeff_files[i],
+ dev);
+ if (!coeff_fw) {
+ dev_warn(dev, "Continuing...\n");
+ continue;
+ }
+
+ if (cl_dsp_coeff_file_parse(cs40l26->dsp, coeff_fw))
+ dev_warn(dev, "Continuing...\n");
+ else
+ dev_dbg(dev, "%s Loaded Successfully\n",
+ cs40l26->fw.coeff_files[i]);
+ }
+
+ cs40l26_dsp_config(cs40l26);
+}
+
+static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ struct device_node *np = dev->of_node;
+ u32 val;
+
+ if (!np) {
+ dev_err(dev, "No platform data found\n");
+ return -ENOENT;
+ }
+
+ if (of_property_read_bool(np, "cirrus,basic-config"))
+ cs40l26->fw_mode = CS40L26_FW_MODE_ROM;
+ else
+ cs40l26->fw_mode = CS40L26_FW_MODE_RAM;
+
+ cs40l26->pdata.vbbr_en =
+ of_property_read_bool(np, "cirrus,vbbr-enable");
+
+ if (!of_property_read_u32(np, "cirrus,vbbr-thld-mv", &val))
+ cs40l26->pdata.vbbr_thld = val;
+
+ if (!of_property_read_u32(np, "cirrus,vbbr-max-att-db", &val))
+ cs40l26->pdata.vbbr_max_att = val;
+ else
+ cs40l26->pdata.vbbr_max_att = CS40L26_VXBR_DEFAULT;
+
+ if (!of_property_read_u32(np, "cirrus,vbbr-atk-step", &val))
+ cs40l26->pdata.vbbr_atk_step = val;
+
+ if (!of_property_read_u32(np, "cirrus,vbbr-atk-rate", &val))
+ cs40l26->pdata.vbbr_atk_rate = val;
+
+ if (!of_property_read_u32(np, "cirrus,vbbr-wait", &val))
+ cs40l26->pdata.vbbr_wait = val;
+ else
+ cs40l26->pdata.vbbr_wait = CS40L26_VXBR_DEFAULT;
+
+ if (!of_property_read_u32(np, "cirrus,vbbr-rel-rate", &val))
+ cs40l26->pdata.vbbr_rel_rate = val;
+ else
+ cs40l26->pdata.vbbr_rel_rate = CS40L26_VXBR_DEFAULT;
+
+ cs40l26->pdata.vpbr_en =
+ of_property_read_bool(np, "cirrus,vpbr-enable");
+
+ if (!of_property_read_u32(np, "cirrus,vpbr-thld-mv", &val))
+ cs40l26->pdata.vpbr_thld = val;
+
+ if (!of_property_read_u32(np, "cirrus,vpbr-max-att-db", &val))
+ cs40l26->pdata.vpbr_max_att = val;
+ else
+ cs40l26->pdata.vpbr_max_att = CS40L26_VXBR_DEFAULT;
+
+ if (!of_property_read_u32(np, "cirrus,vpbr-atk-step", &val))
+ cs40l26->pdata.vpbr_atk_step = val;
+
+ if (!of_property_read_u32(np, "cirrus,vpbr-atk-rate", &val))
+ cs40l26->pdata.vpbr_atk_rate = val;
+ else
+ cs40l26->pdata.vpbr_atk_rate = CS40L26_VXBR_DEFAULT;
+
+ if (!of_property_read_u32(np, "cirrus,vpbr-wait", &val))
+ cs40l26->pdata.vpbr_wait = val;
+ else
+ cs40l26->pdata.vpbr_wait = CS40L26_VXBR_DEFAULT;
+
+ if (!of_property_read_u32(np, "cirrus,vpbr-rel-rate", &val))
+ cs40l26->pdata.vpbr_rel_rate = val;
+ else
+ cs40l26->pdata.vpbr_rel_rate = CS40L26_VXBR_DEFAULT;
+
+ return 0;
+}
+
+int cs40l26_probe(struct cs40l26_private *cs40l26,
+ struct cs40l26_platform_data *pdata)
+{
+ struct device *dev = cs40l26->dev;
+ struct regulator *vp_consumer, *va_consumer;
+ int ret;
+
+ mutex_init(&cs40l26->lock);
+
+ cs40l26->vibe_workqueue = alloc_ordered_workqueue("vibe_workqueue",
+ WQ_HIGHPRI);
+ if (!cs40l26->vibe_workqueue) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ INIT_WORK(&cs40l26->vibe_start_work, cs40l26_vibe_start_worker);
+ INIT_WORK(&cs40l26->vibe_stop_work, cs40l26_vibe_stop_worker);
+ INIT_WORK(&cs40l26->set_gain_work, cs40l26_set_gain_worker);
+
+ ret = devm_regulator_bulk_get(dev, CS40L26_NUM_SUPPLIES,
+ cs40l26_supplies);
+ if (ret) {
+ dev_err(dev, "Failed to request core supplies: %d\n", ret);
+ goto err;
+ }
+
+ vp_consumer = cs40l26_supplies[CS40L26_VP_SUPPLY].consumer;
+ va_consumer = cs40l26_supplies[CS40L26_VA_SUPPLY].consumer;
+
+ if (pdata) {
+ cs40l26->pdata = *pdata;
+ } else if (cs40l26->dev->of_node) {
+ ret = cs40l26_handle_platform_data(cs40l26);
+ if (ret)
+ goto err;
+ } else {
+ dev_err(dev, "No platform data found\n");
+ ret = -ENOENT;
+ goto err;
+ }
+
+ ret = regulator_bulk_enable(CS40L26_NUM_SUPPLIES, cs40l26_supplies);
+ if (ret) {
+ dev_err(dev, "Failed to enable core supplies\n");
+ goto err;
+ }
+
+ cs40l26->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(cs40l26->reset_gpio)) {
+ dev_err(dev, "Failed to get reset GPIO\n");
+
+ ret = PTR_ERR(cs40l26->reset_gpio);
+ cs40l26->reset_gpio = NULL;
+ goto err;
+ }
+
+ usleep_range(CS40L26_MIN_RESET_PULSE_WIDTH,
+ CS40L26_MIN_RESET_PULSE_WIDTH + 100);
+
+ gpiod_set_value_cansleep(cs40l26->reset_gpio, CS40L26_ENABLE);
+
+ usleep_range(CS40L26_CONTROL_PORT_READY_DELAY,
+ CS40L26_CONTROL_PORT_READY_DELAY + 100);
+
+ ret = cs40l26_part_num_resolve(cs40l26);
+ if (ret)
+ goto err;
+
+ ret = devm_request_threaded_irq(dev, cs40l26->irq, NULL, cs40l26_irq,
+ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW,
+ "cs40l26", cs40l26);
+ if (ret) {
+ dev_err(dev, "Failed to request threaded IRQ\n");
+ goto err;
+ }
+ /* the /ALERT pin may be asserted prior to firmware initialization.
+ * Disable the interrupt handler until firmware has downloaded
+ * so erroneous interrupt requests are ignored
+ */
+ disable_irq(cs40l26->irq);
+
+ ret = cs40l26_cl_dsp_init(cs40l26);
+ if (ret)
+ goto err;
+
+ ret = cs40l26_dsp_pre_config(cs40l26);
+ if (ret)
+ goto err;
+
+ request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ CS40L26_FW_FILE_NAME, dev, GFP_KERNEL, cs40l26,
+ cs40l26_firmware_load);
+
+ ret = cs40l26_input_init(cs40l26);
+ if (ret)
+ goto err;
+
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cs40l26_devs,
+ CS40L26_NUM_MFD_DEVS, NULL, 0, NULL);
+ if (ret) {
+ dev_err(dev, "Failed to register codec component\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ cs40l26_remove(cs40l26);
+
+ return ret;
+}
+EXPORT_SYMBOL(cs40l26_probe);
+
+int cs40l26_remove(struct cs40l26_private *cs40l26)
+{
+ struct regulator *vp_consumer =
+ cs40l26_supplies[CS40L26_VP_SUPPLY].consumer;
+ struct regulator *va_consumer =
+ cs40l26_supplies[CS40L26_VA_SUPPLY].consumer;
+
+ disable_irq(cs40l26->irq);
+ mutex_destroy(&cs40l26->lock);
+
+
+ if (cs40l26->pm_ready)
+ cs40l26_pm_runtime_teardown(cs40l26);
+
+ if (cs40l26->vibe_workqueue) {
+ destroy_workqueue(cs40l26->vibe_workqueue);
+ cancel_work_sync(&cs40l26->vibe_start_work);
+ cancel_work_sync(&cs40l26->vibe_stop_work);
+ cancel_work_sync(&cs40l26->set_gain_work);
+ }
+
+ if (vp_consumer)
+ regulator_disable(vp_consumer);
+
+ if (va_consumer)
+ regulator_disable(va_consumer);
+
+ gpiod_set_value_cansleep(cs40l26->reset_gpio, CS40L26_DISABLE);
+
+ if (cs40l26->vibe_timer.function)
+ hrtimer_cancel(&cs40l26->vibe_timer);
+
+ if (cs40l26->vibe_init_success)
+ sysfs_remove_group(&cs40l26->input->dev.kobj,
+ &cs40l26_dev_attr_group);
+
+ if (cs40l26->input)
+ input_unregister_device(cs40l26->input);
+
+ return 0;
+}
+EXPORT_SYMBOL(cs40l26_remove);
+
+int cs40l26_suspend(struct device *dev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+
+ if (!cs40l26->pm_ready) {
+ dev_dbg(dev, "Suspend call ignored\n");
+ return 0;
+ }
+
+ dev_dbg(cs40l26->dev, "%s: Enabling hibernation\n", __func__);
+
+ return cs40l26_pm_state_transition(cs40l26,
+ CS40L26_PM_STATE_ALLOW_HIBERNATE);
+}
+EXPORT_SYMBOL(cs40l26_suspend);
+
+int cs40l26_sys_suspend(struct device *dev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(cs40l26->dev, "System suspend, disabling IRQ\n");
+
+ disable_irq(i2c_client->irq);
+
+ return 0;
+}
+EXPORT_SYMBOL(cs40l26_sys_suspend);
+
+int cs40l26_sys_suspend_noirq(struct device *dev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(cs40l26->dev, "Late system suspend, re-enabling IRQ\n");
+ enable_irq(i2c_client->irq);
+
+ return 0;
+}
+EXPORT_SYMBOL(cs40l26_sys_suspend_noirq);
+
+int cs40l26_resume(struct device *dev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+
+ if (!cs40l26->pm_ready) {
+ dev_dbg(dev, "Resume call ignored\n");
+ return 0;
+ }
+
+ dev_dbg(cs40l26->dev, "%s: Disabling hibernation\n", __func__);
+
+ return cs40l26_pm_state_transition(cs40l26,
+ CS40L26_PM_STATE_PREVENT_HIBERNATE);
+}
+EXPORT_SYMBOL(cs40l26_resume);
+
+int cs40l26_sys_resume(struct device *dev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(cs40l26->dev, "System resume, re-enabling IRQ\n");
+
+ enable_irq(i2c_client->irq);
+
+ return 0;
+}
+EXPORT_SYMBOL(cs40l26_sys_resume);
+
+int cs40l26_sys_resume_noirq(struct device *dev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(cs40l26->dev, "Early system resume, disabling IRQ\n");
+
+ disable_irq(i2c_client->irq);
+
+ return 0;
+}
+EXPORT_SYMBOL(cs40l26_sys_resume_noirq);
+
+MODULE_DESCRIPTION("CS40L26 Boosted Mono Class D Amplifier for Haptics");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/cs40l26/cs40l26.h b/cs40l26/cs40l26.h
new file mode 100644
index 0000000..1bfc002
--- /dev/null
+++ b/cs40l26/cs40l26.h
@@ -0,0 +1,1338 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * cs40l26.h -- CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2021 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#ifndef __CS40L26_H__
+#define __CS40L26_H__
+
+#include <linux/input.h>
+#include <linux/regmap.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/string.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/sysfs.h>
+#include <linux/bitops.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <linux/firmware/cirrus/cl_dsp.h>
+
+#define CS40L26_FIRSTREG 0x0
+#define CS40L26_LASTREG 0x3C7DFE8
+
+#define CS40L26_DEVID 0x0
+#define CS40L26_REVID 0x4
+#define CS40L26_FABID 0x8
+#define CS40L26_RELID 0xC
+#define CS40L26_OTPID 0x10
+#define CS40L26_SFT_RESET 0x20
+#define CS40L26_TEST_KEY_CTRL 0x40
+#define CS40L26_USER_KEY_CTRL 0x44
+#define CS40L26_CTRL_ASYNC0 0x50
+#define CS40L26_CTRL_ASYNC1 0x54
+#define CS40L26_CTRL_ASYNC2 0x58
+#define CS40L26_CTRL_ASYNC3 0x5C
+#define CS40L26_CIF_MON1 0x140
+#define CS40L26_CIF_MON2 0x144
+#define CS40L26_CIF_MON_PADDR 0x148
+#define CS40L26_CTRL_IF_SPARE1 0x154
+#define CS40L26_CTRL_IF_I2C 0x158
+#define CS40L26_CTRL_IF_I2C_1_CONTROL 0x160
+#define CS40L26_CTRL_IF_I2C_1_BROADCAST 0x164
+#define CS40L26_APB_MSTR_DSP_BRIDGE_ERR 0x174
+#define CS40L26_CIF1_BRIDGE_ERR 0x178
+#define CS40L26_CIF2_BRIDGE_ERR 0x17C
+#define CS40L26_OTP_CTRL0 0x400
+#define CS40L26_OTP_CTRL1 0x404
+#define CS40L26_OTP_CTRL3 0x408
+#define CS40L26_OTP_CTRL4 0x40C
+#define CS40L26_OTP_CTRL5 0x410
+#define CS40L26_OTP_CTRL6 0x414
+#define CS40L26_OTP_CTRL7 0x418
+#define CS40L26_OTP_CTRL8 0x41C
+#define CS40L26_GLOBAL_ENABLES 0x2014
+#define CS40L26_BLOCK_ENABLES 0x2018
+#define CS40L26_BLOCK_ENABLES2 0x201C
+#define CS40L26_GLOBAL_OVERRIDES 0x2020
+#define CS40L26_GLOBAL_SYNC 0x2024
+#define CS40L26_GLOBAL_STATUS 0x2028
+#define CS40L26_DISCH_FILT 0x202C
+#define CS40L26_OSC_TRIM 0x2030
+#define CS40L26_ERROR_RELEASE 0x2034
+#define CS40L26_PLL_OVERRIDE 0x2038
+#define CS40L26_BIAS_PTE_MODE_CONTROL 0x2404
+#define CS40L26_SCL_PAD_CONTROL 0x2408
+#define CS40L26_SDA_PAD_CONTROL 0x240C
+#define CS40L26_LRCK_PAD_CONTROL 0x2418
+#define CS40L26_SCLK_PAD_CONTROL 0x241C
+#define CS40L26_SDIN_PAD_CONTROL 0x2420
+#define CS40L26_SDOUT_PAD_CONTROL 0x2424
+#define CS40L26_GPIO_PAD_CONTROL 0x242C
+#define CS40L26_MDSYNC_PAD_CONTROL 0x2430
+#define CS40L26_JTAG_CONTROL 0x2438
+#define CS40L26_GPIO1_PAD_CONTROL 0x243C
+#define CS40L26_GPIO_GLOBAL_ENABLE_CONTROL 0x2440
+#define CS40L26_GPIO_LEVELSHIFT_BYPASS 0x2444
+#define CS40L26_I2C_ADDR_DETECT_CNTL0 0x2800
+#define CS40L26_I2C_ADDR_DET_STATUS0 0x2820
+#define CS40L26_DEVID_METAL 0x2854
+#define CS40L26_PWRMGT_CTL 0x2900
+#define CS40L26_WAKESRC_CTL 0x2904
+#define CS40L26_WAKEI2C_CTL 0x2908
+#define CS40L26_PWRMGT_STS 0x290C
+#define CS40L26_PWRMGT_RST 0x2910
+#define CS40L26_REFCLK_INPUT 0x2C04
+#define CS40L26_DSP_CLOCK_GEARING 0x2C08
+#define CS40L26_GLOBAL_SAMPLE_RATE 0x2C0C
+#define CS40L26_DATA_FS_SEL 0x2C10
+#define CS40L26_FREE_RUN_FORCE 0x2C14
+#define CS40L26_ASP_RATE_DOUBLE_CONTROL0 0x2C18
+#define CS40L26_NZ_AUDIO_DETECT0 0x2C1C
+#define CS40L26_NZ_AUDIO_DETECT1 0x2C20
+#define CS40L26_NZ_AUDIO_DETECT2 0x2C24
+#define CS40L26_PLL_REFCLK_DETECT_0 0x2C28
+#define CS40L26_SP_SCLK_CLOCKING 0x2D00
+#define CS40L26_CONFIG0 0x2D04
+#define CS40L26_CONFIG1 0x2D08
+#define CS40L26_CONFIG2 0x2D0C
+#define CS40L26_FS_MON_0 0x2D10
+#define CS40L26_FS_MON_1 0x2D14
+#define CS40L26_FS_MON_2 0x2D18
+#define CS40L26_FS_MON_OVERRIDE 0x2D1C
+#define CS40L26_DFT 0x2D20
+#define CS40L26_ANALOG_ADC_CONTROLS 0x2D24
+#define CS40L26_SPK_CHOP_CLK_CONTROLS 0x2D28
+#define CS40L26_DSP1_SAMPLE_RATE_RX1 0x2D3C
+#define CS40L26_DSP1_SAMPLE_RATE_RX2 0x2D40
+#define CS40L26_DSP1_SAMPLE_RATE_TX1 0x2D60
+#define CS40L26_DSP1_SAMPLE_RATE_TX2 0x2D64
+#define CS40L26_CLOCK_PHASE 0x2D80
+#define CS40L26_USER_CONTROL 0x3000
+#define CS40L26_CONFIG_RATES 0x3004
+#define CS40L26_LOOP_PARAMETERS 0x3008
+#define CS40L26_LDOA_CONTROL 0x300C
+#define CS40L26_DCO_CONTROL 0x3010
+#define CS40L26_MISC_CONTROL 0x3014
+#define CS40L26_LOOP_OVERRIDES 0x3018
+#define CS40L26_DCO_CTRL_OVERRIDES 0x301C
+#define CS40L26_CONTROL_READ 0x3020
+#define CS40L26_CONTROL_READ_2 0x3024
+#define CS40L26_DCO_CAL_CONTROL_1 0x3028
+#define CS40L26_DCO_CAL_CONTROL_2 0x302C
+#define CS40L26_DCO_CAL_STATUS 0x3030
+#define CS40L26_SYNC_TX_RX_ENABLES 0x3400
+#define CS40L26_SYNC_POWER_CTL 0x3404
+#define CS40L26_SYNC_SW_TX_ID 0x3408
+#define CS40L26_SYNC_SW_RX 0x3414
+#define CS40L26_SYNC_SW_TX 0x3418
+#define CS40L26_SYNC_LSW_RX 0x3424
+#define CS40L26_SYNC_LSW_TX 0x3428
+#define CS40L26_SYNC_SW_DATA_TX_STATUS 0x3430
+#define CS40L26_SYNC_SW_DATA_RX_STATUS 0x3434
+#define CS40L26_SYNC_ERROR_STATUS 0x3438
+#define CS40L26_MDSYNC_SYNC_RX_DECODE_CTL_1 0x3500
+#define CS40L26_MDSYNC_SYNC_RX_DECODE_CTL_2 0x3504
+#define CS40L26_MDSYNC_SYNC_TX_ENCODE_CTL 0x3508
+#define CS40L26_MDSYNC_SYNC_IDLE_STATE_CTL 0x350C
+#define CS40L26_MDSYNC_SYNC_SLEEP_STATE_CTL 0x3510
+#define CS40L26_MDSYNC_SYNC_TYPE 0x3514
+#define CS40L26_MDSYNC_SYNC_TRIGGER 0x3518
+#define CS40L26_MDSYNC_SYNC_PTE0 0x3520
+#define CS40L26_MDSYNC_SYNC_PTE1 0x3524
+#define CS40L26_SYNC_PTE2 0x3528
+#define CS40L26_SYNC_PTE3 0x352C
+#define CS40L26_VBST_CTL_1 0x3800
+#define CS40L26_VBST_CTL_2 0x3804
+#define CS40L26_BST_IPK_CTL 0x3808
+#define CS40L26_SOFT_RAMP 0x380C
+#define CS40L26_BST_LOOP_COEFF 0x3810
+#define CS40L26_LBST_SLOPE 0x3814
+#define CS40L26_BST_SW_FREQ 0x3818
+#define CS40L26_BST_DCM_CTL 0x381C
+#define CS40L26_DCM_FORCE 0x3820
+#define CS40L26_VBST_OVP 0x3830
+#define CS40L26_BST_DCR 0x3840
+#define CS40L26_VPI_LIMIT_MODE 0x3C04
+#define CS40L26_VPI_LIMITING 0x3C08
+#define CS40L26_VPI_VP_THLDS 0x3C0C
+#define CS40L26_VPI_TRACKING 0x3C10
+#define CS40L26_VPI_TRIG_TIME 0x3C14
+#define CS40L26_VPI_TRIG_STEPS 0x3C18
+#define CS40L26_VPI_STATES 0x3E04
+#define CS40L26_VPI_OUTPUT_RATE 0x3E08
+#define CS40L26_VMON_IMON_VOL_POL 0x4000
+#define CS40L26_SPKMON_RATE_SEL 0x4004
+#define CS40L26_MONITOR_FILT 0x4008
+#define CS40L26_IMON_COMP 0x4010
+#define CS40L26_WARN_LIMIT_THRESHOLD 0x4220
+#define CS40L26_CONFIGURATION 0x4224
+#define CS40L26_STATUS 0x4300
+#define CS40L26_ENABLES_AND_CODES_ANA 0x4304
+#define CS40L26_ENABLES_AND_CODES_DIG 0x4308
+#define CS40L26_CALIBR_STATUS 0x430C
+#define CS40L26_TEMP_RESYNC 0x4310
+#define CS40L26_ERROR_LIMIT_THLD_OVERRIDE 0x4320
+#define CS40L26_WARN_LIMIT_THLD_OVERRIDE 0x4324
+#define CS40L26_CALIBR_ROUTINE_CONFIGURATIONS 0x4368
+#define CS40L26_STATUS_FS 0x4380
+#define CS40L26_ASP_ENABLES1 0x4800
+#define CS40L26_ASP_CONTROL1 0x4804
+#define CS40L26_ASP_CONTROL2 0x4808
+#define CS40L26_ASP_CONTROL3 0x480C
+#define CS40L26_ASP_FRAME_CONTROL1 0x4810
+#define CS40L26_ASP_FRAME_CONTROL5 0x4820
+#define CS40L26_ASP_DATA_CONTROL1 0x4830
+#define CS40L26_ASP_DATA_CONTROL5 0x4840
+#define CS40L26_ASP_LATENCY1 0x4850
+#define CS40L26_ASP_CONTROL4 0x4854
+#define CS40L26_ASP_FSYNC_CONTROL1 0x4860
+#define CS40L26_ASP_FSYNC_CONTROL2 0x4864
+#define CS40L26_ASP_FSYNC_STATUS1 0x4868
+#define CS40L26_DACPCM1_INPUT 0x4C00
+#define CS40L26_DACMETA1_INPUT 0x4C04
+#define CS40L26_DACPCM2_INPUT 0x4C08
+#define CS40L26_ASPTX1_INPUT 0x4C20
+#define CS40L26_ASPTX2_INPUT 0x4C24
+#define CS40L26_ASPTX3_INPUT 0x4C28
+#define CS40L26_ASPTX4_INPUT 0x4C2C
+#define CS40L26_DSP1RX1_INPUT 0x4C40
+#define CS40L26_DSP1RX2_INPUT 0x4C44
+#define CS40L26_DSP1RX3_INPUT 0x4C48
+#define CS40L26_DSP1RX4_INPUT 0x4C4C
+#define CS40L26_DSP1RX5_INPUT 0x4C50
+#define CS40L26_DSP1RX6_INPUT 0x4C54
+#define CS40L26_NGATE1_INPUT 0x4C60
+#define CS40L26_NGATE2_INPUT 0x4C64
+#define CS40L26_SPARE_CP_BITS_0 0x5C00
+#define CS40L26_VIS_ADDR_CNTL1_4 0x5C40
+#define CS40L26_VIS_ADDR_CNTL5_8 0x5C44
+#define CS40L26_VIS_ADDR_CNTL9_12 0x5C48
+#define CS40L26_VIS_ADDR_CNTL13_16 0x5C4C
+#define CS40L26_VIS_ADDR_CNTL_17_20 0x5C50
+#define CS40L26_BLOCK_SEL_CNTL0_3 0x5C54
+#define CS40L26_BIT_SEL_CNTL 0x5C5C
+#define CS40L26_ANALOG_VIS_CNTL 0x5C60
+#define CS40L26_AMP_CTRL 0x6000
+#define CS40L26_VPBR_CONFIG 0x6404
+#define CS40L26_VBBR_CONFIG 0x6408
+#define CS40L26_VPBR_STATUS 0x640C
+#define CS40L26_VBBR_STATUS 0x6410
+#define CS40L26_OTW_CONFIG 0x6414
+#define CS40L26_AMP_ERROR_VOL_SEL 0x6418
+#define CS40L26_VPBR_FILTER_CONFIG 0x6448
+#define CS40L26_VBBR_FILTER_CONFIG 0x644C
+#define CS40L26_VOL_STATUS_TO_DSP 0x6450
+#define CS40L26_AMP_GAIN 0x6C04
+#define CS40L26_SVC_CTRL 0x7200
+#define CS40L26_SVC_SER_R 0x7204
+#define CS40L26_SVC_R_LPF 0x7208
+#define CS40L26_SVC_FILT_CFG 0x720C
+#define CS40L26_SVC_SER_L_CTRL 0x7218
+#define CS40L26_SVC_SER_C_CTRL 0x721C
+#define CS40L26_SVC_PAR_RLC_SF 0x7220
+#define CS40L26_SVC_PAR_RLC_C1 0x7224
+#define CS40L26_SVC_PAR_RLC_C2 0x7228
+#define CS40L26_SVC_PAR_RLC_B1 0x722C
+#define CS40L26_SVC_GAIN 0x7230
+#define CS40L26_SVC_STATUS 0x7238
+#define CS40L26_SVC_IMON_SF 0x723C
+#define CS40L26_DAC_MSM_CONFIG 0x7400
+#define CS40L26_TST_DAC_MSM_CONFIG 0x7404
+#define CS40L26_ALIVE_DCIN_WD 0x7424
+#define CS40L26_IRQ1_CFG 0x10000
+#define CS40L26_IRQ1_STATUS 0x10004
+#define CS40L26_IRQ1_EINT_1 0x10010
+#define CS40L26_IRQ1_EINT_2 0x10014
+#define CS40L26_IRQ1_EINT_3 0x10018
+#define CS40L26_IRQ1_EINT_4 0x1001C
+#define CS40L26_IRQ1_EINT_5 0x10020
+#define CS40L26_IRQ1_STS_1 0x10090
+#define CS40L26_IRQ1_STS_2 0x10094
+#define CS40L26_IRQ1_STS_3 0x10098
+#define CS40L26_IRQ1_STS_4 0x1009C
+#define CS40L26_IRQ1_STS_5 0x100A0
+#define CS40L26_IRQ1_MASK_1 0x10110
+#define CS40L26_IRQ1_MASK_2 0x10114
+#define CS40L26_IRQ1_MASK_3 0x10118
+#define CS40L26_IRQ1_MASK_4 0x1011C
+#define CS40L26_IRQ1_MASK_5 0x10120
+#define CS40L26_IRQ1_FRC_1 0x10190
+#define CS40L26_IRQ1_FRC_2 0x10194
+#define CS40L26_IRQ1_FRC_3 0x10198
+#define CS40L26_IRQ1_FRC_4 0x1019C
+#define CS40L26_IRQ1_FRC_5 0x101A0
+#define CS40L26_IRQ1_EDGE_1 0x10210
+#define CS40L26_IRQ1_POL_1 0x10290
+#define CS40L26_IRQ1_POL_2 0x10294
+#define CS40L26_IRQ1_POL_3 0x10298
+#define CS40L26_IRQ1_POL_5 0x102A0
+#define CS40L26_IRQ1_DB_2 0x10314
+#define CS40L26_GPIO_STATUS1 0x11000
+#define CS40L26_GPIO_FORCE 0x11004
+#define CS40L26_GPIO1_CTRL1 0x11008
+#define CS40L26_GPIO2_CTRL1 0x1100C
+#define CS40L26_GPIO3_CTRL1 0x11010
+#define CS40L26_GPIO4_CTRL1 0x11014
+#define CS40L26_MIXER_NGATE_CFG 0x12000
+#define CS40L26_MIXER_NGATE_CH1_CFG 0x12004
+#define CS40L26_MIXER_NGATE_CH2_CFG 0x12008
+#define CS40L26_DSP_MBOX_1 0x13000
+#define CS40L26_DSP_MBOX_2 0x13004
+#define CS40L26_DSP_MBOX_3 0x13008
+#define CS40L26_DSP_MBOX_4 0x1300C
+#define CS40L26_DSP_MBOX_5 0x13010
+#define CS40L26_DSP_MBOX_6 0x13014
+#define CS40L26_DSP_MBOX_7 0x13018
+#define CS40L26_DSP_MBOX_8 0x1301C
+#define CS40L26_DSP_VIRTUAL1_MBOX_1 0x13020
+#define CS40L26_DSP_VIRTUAL1_MBOX_2 0x13024
+#define CS40L26_DSP_VIRTUAL1_MBOX_3 0x13028
+#define CS40L26_DSP_VIRTUAL1_MBOX_4 0x1302C
+#define CS40L26_DSP_VIRTUAL1_MBOX_5 0x13030
+#define CS40L26_DSP_VIRTUAL1_MBOX_6 0x13034
+#define CS40L26_DSP_VIRTUAL1_MBOX_7 0x13038
+#define CS40L26_DSP_VIRTUAL1_MBOX_8 0x1303C
+#define CS40L26_DSP_VIRTUAL2_MBOX_1 0x13040
+#define CS40L26_DSP_VIRTUAL2_MBOX_2 0x13044
+#define CS40L26_DSP_VIRTUAL2_MBOX_3 0x13048
+#define CS40L26_DSP_VIRTUAL2_MBOX_4 0x1304C
+#define CS40L26_DSP_VIRTUAL2_MBOX_5 0x13050
+#define CS40L26_DSP_VIRTUAL2_MBOX_6 0x13054
+#define CS40L26_DSP_VIRTUAL2_MBOX_7 0x13058
+#define CS40L26_DSP_VIRTUAL2_MBOX_8 0x1305C
+#define CS40L26_TIMER1_CONTROL 0x15000
+#define CS40L26_TIMER1_COUNT_PRESET 0x15004
+#define CS40L26_TIMER1_START_AND_STOP 0x1500C
+#define CS40L26_TIMER1_STATUS 0x15010
+#define CS40L26_TIMER1_COUNT_READBACK 0x15014
+#define CS40L26_TIMER1_DSP_CLOCK_CONFIG 0x15018
+#define CS40L26_TIMER1_DSP_CLOCK_STATUS 0x1501C
+#define CS40L26_TIMER2_CONTROL 0x15100
+#define CS40L26_TIMER2_COUNT_PRESET 0x15104
+#define CS40L26_TIMER2_START_AND_STOP 0x1510C
+#define CS40L26_TIMER2_STATUS 0x15110
+#define CS40L26_TIMER2_COUNT_READBACK 0x15114
+#define CS40L26_TIMER2_DSP_CLOCK_CONFIG 0x15118
+#define CS40L26_TIMER2_DSP_CLOCK_STATUS 0x1511C
+#define CS40L26_DFT_JTAG_CTRL 0x16000
+#define CS40L26_TEMP_CAL2 0x1704C
+#define CS40L26_OTP_MEM0 0x30000
+#define CS40L26_OTP_MEM31 0x3007C
+#define CS40L26_DSP1_XMEM_PACKED_0 0x2000000
+#define CS40L26_DSP1_XMEM_PACKED_1 0x2000004
+#define CS40L26_DSP1_XMEM_PACKED_2 0x2000008
+#define CS40L26_DSP1_XMEM_PACKED_6141 0x2005FF4
+#define CS40L26_DSP1_XMEM_PACKED_6142 0x2005FF8
+#define CS40L26_DSP1_XMEM_PACKED_6143 0x2005FFC
+#define CS40L26_DSP1_XROM_PACKED_0 0x2006000
+#define CS40L26_DSP1_XROM_PACKED_1 0x2006004
+#define CS40L26_DSP1_XROM_PACKED_2 0x2006008
+#define CS40L26_DSP1_XROM_PACKED_4602 0x200A7E8
+#define CS40L26_DSP1_XROM_PACKED_4603 0x200A7EC
+#define CS40L26_DSP1_XROM_PACKED_4604 0x200A7F0
+#define CS40L26_DSP1_XMEM_UNPACKED32_0 0x2400000
+#define CS40L26_DSP1_XMEM_UNPACKED32_1 0x2400004
+#define CS40L26_DSP1_XMEM_UNPACKED32_4094 0x2403FF8
+#define CS40L26_DSP1_XMEM_UNPACKED32_4095 0x2403FFC
+#define CS40L26_DSP1_XROM_UNPACKED32_0 0x2404000
+#define CS40L26_DSP1_XROM_UNPACKED32_1 0x2404004
+#define CS40L26_DSP1_XROM_UNPACKED32_3069 0x2406FF4
+#define CS40L26_DSP1_XROM_UNPACKED32_3070 0x2406FF8
+#define CS40L26_DSP1_TIMESTAMP_COUNT 0x25C0800
+#define CS40L26_DSP1_SYS_INFO_ID 0x25E0000
+#define CS40L26_DSP1_SYS_INFO_VERSION 0x25E0004
+#define CS40L26_DSP1_SYS_INFO_CORE_ID 0x25E0008
+#define CS40L26_DSP1_SYS_INFO_AHB_ADDR 0x25E000C
+#define CS40L26_DSP1_SYS_INFO_XM_SRAM_SIZE 0x25E0010
+#define CS40L26_DSP1_SYS_INFO_XM_ROM_SIZE 0x25E0014
+#define CS40L26_DSP1_SYS_INFO_YM_SRAM_SIZE 0x25E0018
+#define CS40L26_DSP1_SYS_INFO_YM_ROM_SIZE 0x25E001C
+#define CS40L26_DSP1_SYS_INFO_PM_SRAM_SIZE 0x25E0020
+#define CS40L26_DSP1_SYS_INFO_PM_BOOT_SIZE 0x25E0028
+#define CS40L26_DSP1_SYS_INFO_FEATURES 0x25E002C
+#define CS40L26_DSP1_SYS_INFO_FIR_FILTERS 0x25E0030
+#define CS40L26_DSP1_SYS_INFO_LMS_FILTERS 0x25E0034
+#define CS40L26_DSP1_SYS_INFO_XM_BANK_SIZE 0x25E0038
+#define CS40L26_DSP1_SYS_INFO_YM_BANK_SIZE 0x25E003C
+#define CS40L26_DSP1_SYS_INFO_PM_BANK_SIZE 0x25E0040
+#define CS40L26_DSP1_SYS_INFO_STREAM_ARB 0x25E0044
+#define CS40L26_DSP1_SYS_INFO_XM_EMEM_SIZE 0x25E0048
+#define CS40L26_DSP1_SYS_INFO_YM_EMEM_SIZE 0x25E004C
+#define CS40L26_DSP1_AHBM_WINDOW0_CONTROL_0 0x25E2000
+#define CS40L26_DSP1_AHBM_WINDOW0_CONTROL_1 0x25E2004
+#define CS40L26_DSP1_AHBM_WINDOW1_CONTROL_0 0x25E2008
+#define CS40L26_DSP1_AHBM_WINDOW1_CONTROL_1 0x25E200C
+#define CS40L26_DSP1_AHBM_WINDOW2_CONTROL_0 0x25E2010
+#define CS40L26_DSP1_AHBM_WINDOW2_CONTROL_1 0x25E2014
+#define CS40L26_DSP1_AHBM_WINDOW3_CONTROL_0 0x25E2018
+#define CS40L26_DSP1_AHBM_WINDOW3_CONTROL_1 0x25E201C
+#define CS40L26_DSP1_AHBM_WINDOW4_CONTROL_0 0x25E2020
+#define CS40L26_DSP1_AHBM_WINDOW4_CONTROL_1 0x25E2024
+#define CS40L26_DSP1_AHBM_WINDOW5_CONTROL_0 0x25E2028
+#define CS40L26_DSP1_AHBM_WINDOW5_CONTROL_1 0x25E202C
+#define CS40L26_DSP1_AHBM_WINDOW6_CONTROL_0 0x25E2030
+#define CS40L26_DSP1_AHBM_WINDOW6_CONTROL_1 0x25E2034
+#define CS40L26_DSP1_AHBM_WINDOW7_CONTROL_0 0x25E2038
+#define CS40L26_DSP1_AHBM_WINDOW7_CONTROL_1 0x25E203C
+#define CS40L26_DSP1_XMEM_UNPACKED24_0 0x2800000
+#define CS40L26_DSP1_XMEM_UNPACKED24_1 0x2800004
+#define CS40L26_DSP1_XMEM_UNPACKED24_2 0x2800008
+#define CS40L26_DSP1_XMEM_UNPACKED24_3 0x280000C
+#define CS40L26_DSP1_XMEM_UNPACKED24_8188 0x2807FF0
+#define CS40L26_DSP1_XMEM_UNPACKED24_8189 0x2807FF4
+#define CS40L26_DSP1_XMEM_UNPACKED24_8190 0x2807FF8
+#define CS40L26_DSP1_XMEM_UNPACKED24_8191 0x2807FFC
+#define CS40L26_DSP1_XROM_UNPACKED24_0 0x2808000
+#define CS40L26_DSP1_XROM_UNPACKED24_1 0x2808004
+#define CS40L26_DSP1_XROM_UNPACKED24_2 0x2808008
+#define CS40L26_DSP1_XROM_UNPACKED24_3 0x280800C
+#define CS40L26_DSP1_XROM_UNPACKED24_6138 0x280DFE8
+#define CS40L26_DSP1_XROM_UNPACKED24_6139 0x280DFEC
+#define CS40L26_DSP1_XROM_UNPACKED24_6140 0x280DFF0
+#define CS40L26_DSP1_XROM_UNPACKED24_6141 0x280DFF4
+#define CS40L26_DSP1_CLOCK_FREQ 0x2B80000
+#define CS40L26_DSP1_CLOCK_STATUS 0x2B80008
+#define CS40L26_DSP1_CORE_SOFT_RESET 0x2B80010
+#define CS40L26_DSP1_CORE_WRAP_STATUS 0x2B80020
+#define CS40L26_DSP1_TIMER_CONTROL 0x2B80048
+#define CS40L26_DSP1_STREAM_ARB_CONTROL 0x2B80050
+#define CS40L26_DSP1_NMI_CONTROL1 0x2B80480
+#define CS40L26_DSP1_NMI_CONTROL2 0x2B80488
+#define CS40L26_DSP1_NMI_CONTROL3 0x2B80490
+#define CS40L26_DSP1_NMI_CONTROL4 0x2B80498
+#define CS40L26_DSP1_NMI_CONTROL5 0x2B804A0
+#define CS40L26_DSP1_NMI_CONTROL6 0x2B804A8
+#define CS40L26_DSP1_NMI_CONTROL7 0x2B804B0
+#define CS40L26_DSP1_NMI_CONTROL8 0x2B804B8
+#define CS40L26_DSP1_RESUME_CONTROL 0x2B80500
+#define CS40L26_DSP1_IRQ1_CONTROL 0x2B80508
+#define CS40L26_DSP1_IRQ2_CONTROL 0x2B80510
+#define CS40L26_DSP1_IRQ3_CONTROL 0x2B80518
+#define CS40L26_DSP1_IRQ4_CONTROL 0x2B80520
+#define CS40L26_DSP1_IRQ5_CONTROL 0x2B80528
+#define CS40L26_DSP1_IRQ6_CONTROL 0x2B80530
+#define CS40L26_DSP1_IRQ7_CONTROL 0x2B80538
+#define CS40L26_DSP1_IRQ8_CONTROL 0x2B80540
+#define CS40L26_DSP1_IRQ9_CONTROL 0x2B80548
+#define CS40L26_DSP1_IRQ10_CONTROL 0x2B80550
+#define CS40L26_DSP1_IRQ11_CONTROL 0x2B80558
+#define CS40L26_DSP1_IRQ12_CONTROL 0x2B80560
+#define CS40L26_DSP1_IRQ13_CONTROL 0x2B80568
+#define CS40L26_DSP1_IRQ14_CONTROL 0x2B80570
+#define CS40L26_DSP1_IRQ15_CONTROL 0x2B80578
+#define CS40L26_DSP1_IRQ16_CONTROL 0x2B80580
+#define CS40L26_DSP1_IRQ17_CONTROL 0x2B80588
+#define CS40L26_DSP1_IRQ18_CONTROL 0x2B80590
+#define CS40L26_DSP1_IRQ19_CONTROL 0x2B80598
+#define CS40L26_DSP1_IRQ20_CONTROL 0x2B805A0
+#define CS40L26_DSP1_IRQ21_CONTROL 0x2B805A8
+#define CS40L26_DSP1_IRQ22_CONTROL 0x2B805B0
+#define CS40L26_DSP1_IRQ23_CONTROL 0x2B805B8
+#define CS40L26_DSP1_SCRATCH1 0x2B805C0
+#define CS40L26_DSP1_SCRATCH2 0x2B805C8
+#define CS40L26_DSP1_SCRATCH3 0x2B805D0
+#define CS40L26_DSP1_SCRATCH4 0x2B805D8
+#define CS40L26_DSP1_CCM_CORE_CONTROL 0x2BC1000
+#define CS40L26_DSP1_CCM_CLK_OVERRIDE 0x2BC1008
+#define CS40L26_DSP1_MEM_CTRL_XM_MSTR_EN 0x2BC2000
+#define CS40L26_DSP1_MEM_CTRL_XM_CORE_PRIO 0x2BC2008
+#define CS40L26_DSP1_MEM_CTRL_XM_PL0_PRIO 0x2BC2010
+#define CS40L26_DSP1_MEM_CTRL_XM_PL1_PRIO 0x2BC2018
+#define CS40L26_DSP1_MEM_CTRL_XM_PL2_PRIO 0x2BC2020
+#define CS40L26_DSP1_MEM_CTRL_XM_NPL0_PRIO 0x2BC2078
+#define CS40L26_DSP1_MEM_CTRL_YM_MSTR_EN 0x2BC20C0
+#define CS40L26_DSP1_MEM_CTRL_YM_CORE_PRIO 0x2BC20C8
+#define CS40L26_DSP1_MEM_CTRL_YM_PL0_PRIO 0x2BC20D0
+#define CS40L26_DSP1_MEM_CTRL_YM_PL1_PRIO 0x2BC20D8
+#define CS40L26_DSP1_MEM_CTRL_YM_PL2_PRIO 0x2BC20E0
+#define CS40L26_DSP1_MEM_CTRL_YM_NPL0_PRIO 0x2BC2138
+#define CS40L26_DSP1_MEM_CTRL_PM_MSTR_EN 0x2BC2180
+#define CS40L26_DSP1_MEM_CTRL_FIXED_PRIO 0x2BC2184
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH0_ADDR 0x2BC2188
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH0_EN 0x2BC218C
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH0_DATA_LO 0x2BC2190
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH0_DATA_HI 0x2BC2194
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH1_ADDR 0x2BC2198
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH1_EN 0x2BC219C
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH1_DATA_LO 0x2BC21A0
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH1_DATA_HI 0x2BC21A4
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH2_ADDR 0x2BC21A8
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH2_EN 0x2BC21AC
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH2_DATA_LO 0x2BC21B0
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH2_DATA_HI 0x2BC21B4
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH3_ADDR 0x2BC21B8
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH3_EN 0x2BC21BC
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH3_DATA_LO 0x2BC21C0
+#define CS40L26_DSP1_MEM_CTRL_PM_PATCH3_DATA_HI 0x2BC21C4
+#define CS40L26_DSP1_MPU_XMEM_ACCESS_0 0x2BC3000
+#define CS40L26_DSP1_MPU_YMEM_ACCESS_0 0x2BC3004
+#define CS40L26_DSP1_MPU_WINDOW_ACCESS_0 0x2BC3008
+#define CS40L26_DSP1_MPU_XREG_ACCESS_0 0x2BC300C
+#define CS40L26_DSP1_MPU_YREG_ACCESS_0 0x2BC3014
+#define CS40L26_DSP1_MPU_XMEM_ACCESS_1 0x2BC3018
+#define CS40L26_DSP1_MPU_YMEM_ACCESS_1 0x2BC301C
+#define CS40L26_DSP1_MPU_WINDOW_ACCESS_1 0x2BC3020
+#define CS40L26_DSP1_MPU_XREG_ACCESS_1 0x2BC3024
+#define CS40L26_DSP1_MPU_YREG_ACCESS_1 0x2BC302C
+#define CS40L26_DSP1_MPU_XMEM_ACCESS_2 0x2BC3030
+#define CS40L26_DSP1_MPU_YMEM_ACCESS_2 0x2BC3034
+#define CS40L26_DSP1_MPU_WINDOW_ACCESS_2 0x2BC3038
+#define CS40L26_DSP1_MPU_XREG_ACCESS_2 0x2BC303C
+#define CS40L26_DSP1_MPU_YREG_ACCESS_2 0x2BC3044
+#define CS40L26_DSP1_MPU_XMEM_ACCESS_3 0x2BC3048
+#define CS40L26_DSP1_MPU_YMEM_ACCESS_3 0x2BC304C
+#define CS40L26_DSP1_MPU_WINDOW_ACCESS_3 0x2BC3050
+#define CS40L26_DSP1_MPU_XREG_ACCESS_3 0x2BC3054
+#define CS40L26_DSP1_MPU_YREG_ACCESS_3 0x2BC305C
+#define CS40L26_DSP1_MPU_X_EXT_MEM_ACCESS_0 0x2BC3060
+#define CS40L26_DSP1_MPU_Y_EXT_MEM_ACCESS_0 0x2BC3064
+#define CS40L26_DSP1_MPU_XM_VIO_ADDR 0x2BC3100
+#define CS40L26_DSP1_MPU_XM_VIO_STATUS 0x2BC3104
+#define CS40L26_DSP1_MPU_YM_VIO_ADDR 0x2BC3108
+#define CS40L26_DSP1_MPU_YM_VIO_STATUS 0x2BC310C
+#define CS40L26_DSP1_MPU_PM_VIO_ADDR 0x2BC3110
+#define CS40L26_DSP1_MPU_PM_VIO_STATUS 0x2BC3114
+#define CS40L26_DSP1_MPU_LOCK_CONFIG 0x2BC3140
+#define CS40L26_DSP1_MPU_WDT_RESET_CONTROL 0x2BC3180
+#define CS40L26_DSP1_STREAM_ARB_MSTR1_CONFIG_0 0x2BC5000
+#define CS40L26_DSP1_STREAM_ARB_MSTR1_CONFIG_1 0x2BC5004
+#define CS40L26_DSP1_STREAM_ARB_MSTR1_CONFIG_2 0x2BC5008
+#define CS40L26_DSP1_STREAM_ARB_MSTR2_CONFIG_0 0x2BC5010
+#define CS40L26_DSP1_STREAM_ARB_MSTR2_CONFIG_1 0x2BC5014
+#define CS40L26_DSP1_STREAM_ARB_MSTR2_CONFIG_2 0x2BC5018
+#define CS40L26_DSP1_STREAM_ARB_MSTR3_CONFIG_0 0x2BC5020
+#define CS40L26_DSP1_STREAM_ARB_MSTR3_CONFIG_1 0x2BC5024
+#define CS40L26_DSP1_STREAM_ARB_MSTR3_CONFIG_2 0x2BC5028
+#define CS40L26_DSP1_STREAM_ARB_MSTR4_CONFIG_0 0x2BC5030
+#define CS40L26_DSP1_STREAM_ARB_MSTR4_CONFIG_1 0x2BC5034
+#define CS40L26_DSP1_STREAM_ARB_MSTR4_CONFIG_2 0x2BC5038
+#define CS40L26_DSP1_STREAM_ARB_TX1_CONFIG_0 0x2BC5200
+#define CS40L26_DSP1_STREAM_ARB_TX1_CONFIG_1 0x2BC5204
+#define CS40L26_DSP1_STREAM_ARB_TX2_CONFIG_0 0x2BC5208
+#define CS40L26_DSP1_STREAM_ARB_TX2_CONFIG_1 0x2BC520C
+#define CS40L26_DSP1_STREAM_ARB_TX3_CONFIG_0 0x2BC5210
+#define CS40L26_DSP1_STREAM_ARB_TX3_CONFIG_1 0x2BC5214
+#define CS40L26_DSP1_STREAM_ARB_TX4_CONFIG_0 0x2BC5218
+#define CS40L26_DSP1_STREAM_ARB_TX4_CONFIG_1 0x2BC521C
+#define CS40L26_DSP1_STREAM_ARB_TX5_CONFIG_0 0x2BC5220
+#define CS40L26_DSP1_STREAM_ARB_TX5_CONFIG_1 0x2BC5224
+#define CS40L26_DSP1_STREAM_ARB_TX6_CONFIG_0 0x2BC5228
+#define CS40L26_DSP1_STREAM_ARB_TX6_CONFIG_1 0x2BC522C
+#define CS40L26_DSP1_STREAM_ARB_RX1_CONFIG_0 0x2BC5400
+#define CS40L26_DSP1_STREAM_ARB_RX1_CONFIG_1 0x2BC5404
+#define CS40L26_DSP1_STREAM_ARB_RX2_CONFIG_0 0x2BC5408
+#define CS40L26_DSP1_STREAM_ARB_RX2_CONFIG_1 0x2BC540C
+#define CS40L26_DSP1_STREAM_ARB_RX3_CONFIG_0 0x2BC5410
+#define CS40L26_DSP1_STREAM_ARB_RX3_CONFIG_1 0x2BC5414
+#define CS40L26_DSP1_STREAM_ARB_RX4_CONFIG_0 0x2BC5418
+#define CS40L26_DSP1_STREAM_ARB_RX4_CONFIG_1 0x2BC541C
+#define CS40L26_DSP1_STREAM_ARB_RX5_CONFIG_0 0x2BC5420
+#define CS40L26_DSP1_STREAM_ARB_RX5_CONFIG_1 0x2BC5424
+#define CS40L26_DSP1_STREAM_ARB_RX6_CONFIG_0 0x2BC5428
+#define CS40L26_DSP1_STREAM_ARB_RX6_CONFIG_1 0x2BC542C
+#define CS40L26_DSP1_STREAM_ARB_IRQ1_CONFIG_0 0x2BC5600
+#define CS40L26_DSP1_STREAM_ARB_IRQ1_CONFIG_1 0x2BC5604
+#define CS40L26_DSP1_STREAM_ARB_IRQ1_CONFIG_2 0x2BC5608
+#define CS40L26_DSP1_STREAM_ARB_IRQ2_CONFIG_0 0x2BC5610
+#define CS40L26_DSP1_STREAM_ARB_IRQ2_CONFIG_1 0x2BC5614
+#define CS40L26_DSP1_STREAM_ARB_IRQ2_CONFIG_2 0x2BC5618
+#define CS40L26_DSP1_STREAM_ARB_IRQ3_CONFIG_0 0x2BC5620
+#define CS40L26_DSP1_STREAM_ARB_IRQ3_CONFIG_1 0x2BC5624
+#define CS40L26_DSP1_STREAM_ARB_IRQ3_CONFIG_2 0x2BC5628
+#define CS40L26_DSP1_STREAM_ARB_IRQ4_CONFIG_0 0x2BC5630
+#define CS40L26_DSP1_STREAM_ARB_IRQ4_CONFIG_1 0x2BC5634
+#define CS40L26_DSP1_STREAM_ARB_IRQ4_CONFIG_2 0x2BC5638
+#define CS40L26_DSP1_STREAM_ARB_RESYNC_MSK1 0x2BC5A00
+#define CS40L26_DSP1_STREAM_ARB_ERR_STATUS 0x2BC5A08
+#define CS40L26_DSP1_WDT_CONTROL 0x2BC7000
+#define CS40L26_DSP1_WDT_STATUS 0x2BC7008
+#define CS40L26_DSP1_ACCEL_DB_IN 0x2BCD000
+#define CS40L26_DSP1_ACCEL_LINEAR_OUT 0x2BCD008
+#define CS40L26_DSP1_ACCEL_LINEAR_IN 0x2BCD010
+#define CS40L26_DSP1_ACCEL_DB_OUT 0x2BCD018
+#define CS40L26_DSP1_ACCEL_RAND_NUM 0x2BCD020
+#define CS40L26_DSP1_YMEM_PACKED_0 0x2C00000
+#define CS40L26_DSP1_YMEM_PACKED_1 0x2C00004
+#define CS40L26_DSP1_YMEM_PACKED_2 0x2C00008
+#define CS40L26_DSP1_YMEM_PACKED_1530 0x2C017E8
+#define CS40L26_DSP1_YMEM_PACKED_1531 0x2C017EC
+#define CS40L26_DSP1_YMEM_PACKED_1532 0x2C017F0
+#define CS40L26_DSP1_YMEM_UNPACKED32_0 0x3000000
+#define CS40L26_DSP1_YMEM_UNPACKED32_1 0x3000004
+#define CS40L26_DSP1_YMEM_UNPACKED32_1021 0x3000FF4
+#define CS40L26_DSP1_YMEM_UNPACKED32_1022 0x3000FF8
+#define CS40L26_DSP1_YMEM_UNPACKED24_0 0x3400000
+#define CS40L26_DSP1_YMEM_UNPACKED24_1 0x3400004
+#define CS40L26_DSP1_YMEM_UNPACKED24_2 0x3400008
+#define CS40L26_DSP1_YMEM_UNPACKED24_3 0x340000C
+#define CS40L26_DSP1_YMEM_UNPACKED24_2042 0x3401FE8
+#define CS40L26_DSP1_YMEM_UNPACKED24_2043 0x3401FEC
+#define CS40L26_DSP1_YMEM_UNPACKED24_2044 0x3401FF0
+#define CS40L26_DSP1_YMEM_UNPACKED24_2045 0x3401FF4
+#define CS40L26_DSP1_PMEM_0 0x3800000
+#define CS40L26_DSP1_PMEM_1 0x3800004
+#define CS40L26_DSP1_PMEM_2 0x3800008
+#define CS40L26_DSP1_PMEM_3 0x380000C
+#define CS40L26_DSP1_PMEM_4 0x3800010
+#define CS40L26_DSP1_PMEM_5110 0x3804FD8
+#define CS40L26_DSP1_PMEM_5111 0x3804FDC
+#define CS40L26_DSP1_PMEM_5112 0x3804FE0
+#define CS40L26_DSP1_PMEM_5113 0x3804FE4
+#define CS40L26_DSP1_PMEM_5114 0x3804FE8
+#define CS40L26_DSP1_PROM_0 0x3C60000
+#define CS40L26_DSP1_PROM_1 0x3C60004
+#define CS40L26_DSP1_PROM_2 0x3C60008
+#define CS40L26_DSP1_PROM_3 0x3C6000C
+#define CS40L26_DSP1_PROM_4 0x3C60010
+#define CS40L26_DSP1_PROM_30710 0x3C7DFD8
+#define CS40L26_DSP1_PROM_30711 0x3C7DFDC
+#define CS40L26_DSP1_PROM_30712 0x3C7DFE0
+#define CS40L26_DSP1_PROM_30713 0x3C7DFE4
+#define CS40L26_DSP1_PROM_30714 0x3C7DFE8
+
+/* this is not a CS40L26 restriction and may be able to be removed */
+#define CS40L26_MAX_I2C_READ_SIZE_BYTES 32
+
+#define CS40L26_DEV_NAME "CS40L26"
+#define CS40L26_DEVID_A 0x40A260
+#define CS40L26_DEVID_B 0x40A26B
+#define CS40L26_DEVID_MASK GENMASK(23, 0)
+#define CS40L26_NUM_DEVS 2
+
+#define CS40L26_REVID_A0 0xA0
+#define CS40L26_REVID_A1 0xA1
+#define CS40L26_REVID_MASK GENMASK(7, 0)
+
+#define CS40L26_GLOBAL_EN_MASK BIT(0)
+
+#define CS40L26_DISABLE 0
+#define CS40L26_ENABLE 1
+
+#define CS40L26_DSP_CCM_CORE_KILL 0x00000080
+#define CS40L26_DSP_CCM_CORE_RESET 0x00000281
+
+#define CS40L26_MEM_RDY_MASK BIT(1)
+#define CS40L26_MEM_RDY_SHIFT 1
+
+#define CS40L26_PLL_REFCLK_DET_EN_MASK BIT(0)
+
+#define CS40L26_DSP_HALO_STATE_RUN 2
+
+/* DSP State */
+#define CS40L26_DSP_STATE_HIBERNATE 0
+#define CS40L26_DSP_STATE_SHUTDOWN 1
+#define CS40L26_DSP_STATE_STANDBY 2
+#define CS40L26_DSP_STATE_ACTIVE 3
+
+#define CS40L26_DSP_STATE_MASK GENMASK(7, 0)
+
+#define CS40L26_DSP_STATE_STR_LEN 10
+
+/* ROM Controls A0 */
+#define CS40L26_A0_PM_CUR_STATE_STATIC_REG 0x02800358
+#define CS40L26_A0_PM_TIMEOUT_TICKS_STATIC_REG 0x02800338
+#define CS40L26_A0_DSP_HALO_STATE_REG 0x02806f40
+
+
+/* ROM Controls A1 */
+#define CS40L26_A1_PM_CUR_STATE_STATIC_REG 0x02800370
+#define CS40L26_A1_PM_TIMEOUT_TICKS_STATIC_REG 0x02800350
+#define CS40L26_A1_DSP_HALO_STATE_REG 0x02800fa8
+
+
+/* algorithms */
+#define CS40L26_A2H_ALGO_ID 0x00010110
+#define CS40L26_BUZZGEN_ALGO_ID 0x0001F202
+#define CS40L26_DYNAMIC_F0_ALGO_ID 0x0001F21B
+#define CS40L26_EVENT_HANDLER_ALGO_ID 0x0001F200
+#define CS40L26_F0_EST_ALGO_ID 0x0001F20C
+#define CS40L26_GPIO_ALGO_ID 0x0001F201
+#define CS40L26_MAILBOX_ALGO_ID 0x0001F203
+#define CS40L26_MDSYNC_ALGO_ID 0x0001F20F
+#define CS40L26_PM_ALGO_ID 0x0001F206
+#define CS40l26_SVC_ALGO_ID 0x0001F207
+#define CS40L26_VIBEGEN_ALGO_ID 0x000100BD
+
+#define CS40L26_BUZZGEN_ROM_ALGO_ID 0x0000F202
+#define CS40L26_PM_ROM_ALGO_ID 0x0000F206
+
+/* power management */
+#define CS40L26_PSEQ_V1_MAX_ENTRIES 32
+#define CS40L26_PSEQ_V1_MAX_WRITES 64
+#define CS40L26_PSEQ_V1_VAL_SHIFT 24
+#define CS40L26_PSEQ_V1_VAL_MASK GENMASK(23, 0)
+#define CS40L26_PSEQ_V1_ADDR_SHIFT 8
+#define CS40L26_PSEQ_V1_ADDR_MASK GENMASK(15, 0)
+#define CS40L26_PSEQ_V1_LIST_TERM 0xFFFFFF
+#define CS40L26_PSEQ_V1_LIST_TERM_MASK GENMASK(31, 0)
+#define CS40L26_PSEQ_V1_STRIDE 8
+#define CS40L26_PSEQ_V1_PAIR_NUM_WORDS 2
+#define CS40L26_PSEQ_V1_ADDR_WORD_MASK GENMASK(23, 8)
+#define CS40L26_PSEQ_V1_VAL_WORD_UPPER_MASK GENMASK(8, 0)
+#define CS40L26_PSEQ_V1_DO_NOT_REPLACE 0
+#define CS40L26_PSEQ_V1_REPLACE 1
+
+#define CS40L26_PSEQ_V2_MAX_WORDS_PER_OP CS40L26_PSEQ_V2_OP_WRITE_FIELD_WORDS
+#define CS40L26_PSEQ_V2_MAX_WORDS 129
+#define CS40L26_PSEQ_V2_NUM_OPS 8
+#define CS40L26_PSEQ_V2_OP_MASK GENMASK(23, 16)
+#define CS40L26_PSEQ_V2_OP_SHIFT 16
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_FULL 0x00
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_FULL_WORDS 3
+#define CS40L26_PSEQ_V2_OP_WRITE_FIELD 0x01
+#define CS40L26_PSEQ_V2_OP_WRITE_FIELD_WORDS 4
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_ADDR8 0x02
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_ADDR8_WORDS 2
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_INCR 0x03
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_INCR_WORDS 2
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_L16 0x04
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_L16_WORDS 2
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_H16 0x05
+#define CS40L26_PSEQ_V2_OP_WRITE_REG_H16_WORDS 2
+#define CS40L26_PSEQ_V2_OP_DELAY 0xFE
+#define CS40L26_PSEQ_V2_OP_DELAY_WORDS 1
+#define CS40L26_PSEQ_V2_OP_END 0xFF
+#define CS40L26_PSEQ_V2_OP_END_WORDS 1
+
+#define CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET 16
+#define CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET 20
+#define CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK GENMASK(23, 0)
+#define CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK GENMASK(7, 0)
+#define CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT 24
+#define CS40L26_PM_TICKS_MS_DIV 32
+
+#define CS40L26_PM_TIMEOUT_MS_MIN 100
+
+#define CS40L26_AUTOSUSPEND_DELAY_MS 2000
+
+#define CS40L26_WKSRC_STS_MASK GENMASK(9, 4)
+#define CS40L26_WKSRC_STS_SHIFT 4
+
+#define CS40L26_WKSRC_GPIO_POL_MASK GENMASK(3, 0)
+
+#define CS40L26_IRQ1_WKSRC_MASK GENMASK(14, 9)
+#define CS40L26_IRQ1_WKSRC_SHIFT 9
+#define CS40L26_IRQ1_WKSRC_GPIO_MASK GENMASK(3, 0)
+
+#define CS40L26_WKSRC_STS_EN BIT(7)
+
+/* DSP mailbox controls */
+#define CS40L26_DSP_TIMEOUT_US_MIN 1000
+#define CS40L26_DSP_TIMEOUT_US_MAX 1100
+#define CS40L26_DSP_TIMEOUT_COUNT 50
+
+#define CS40L26_DSP_MBOX_RESET 0x0
+
+#define CS40L26_DSP_MBOX_CMD_HIBER 0x02000001
+#define CS40L26_DSP_MBOX_CMD_WAKEUP 0x02000002
+#define CS40L26_DSP_MBOX_CMD_PREVENT_HIBER 0x02000003
+#define CS40L26_DSP_MBOX_CMD_ALLOW_HIBER 0x02000004
+#define CS40L26_DSP_MBOX_CMD_SHUTDOWN 0x02000005
+#define CS40L26_DSP_MBOX_PM_CMD_BASE CS40L26_DSP_MBOX_CMD_HIBER
+
+#define CS40L26_DSP_MBOX_CMD_DURATION_REPORT 0x03000001
+#define CS40L26_DSP_MBOX_CMD_START_I2S 0x03000002
+#define CS40L26_DSP_MBOX_CMD_STOP_I2S 0x03000003
+#define CS40L26_DSP_MBOX_CMD_A2H_REINIT 0x03000007
+
+#define CS40L26_DSP_MBOX_CMD_INDEX_MASK GENMASK(28, 24)
+#define CS40L26_DSP_MBOX_CMD_INDEX_SHIFT 24
+
+#define CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK GENMASK(23, 0)
+
+#define CS40L26_DSP_MBOX_BUFFER_NUM_REGS 4
+
+#define CS40L26_DSP_MBOX_TRIGGER_COMPLETE 0x01000000
+#define CS40L26_DSP_MBOX_PM_AWAKE 0x02000002
+#define CS40L26_DSP_MBOX_F0_EST_START 0x07000011
+#define CS40L26_DSP_MBOX_F0_EST_DONE 0x07000021
+#define CS40L26_DSP_MBOX_REDC_EST_START 0x07000012
+#define CS40L26_DSP_MBOX_REDC_EST_DONE 0x07000022
+#define CS40L26_DSP_MBOX_SYS_ACK 0x0A000000
+#define CS40L26_DSP_MBOX_PANIC 0x0C000000
+
+/* Firmware Mode */
+#define CS40L26_FW_FILE_NAME "cs40l26.wmfw"
+
+#define CS40L26_WT_FILE_NAME "cs40l26.bin"
+#define CS40L26_SVC_TUNING_FILE_NAME "cs40l26-svc.bin"
+#define CS40L26_A2H_TUNING_FILE_NAME "cs40l26-a2h.bin"
+
+#define CS40L26_FW_ID 0x1800D4
+#define CS40L26_FW_ROM_MIN_REV 0x040000
+#define CS40L26_FW_A0_RAM_MIN_REV 0x050004
+#define CS40L26_FW_A1_RAM_MIN_REV 0x070001
+
+#define CS40L26_CCM_CORE_RESET 0x00000200
+#define CS40L26_CCM_CORE_ENABLE 0x00000281
+
+/* wavetable */
+#define CS40L26_WT_NAME_XM "WAVE_XM_TABLE"
+#define CS40L26_WT_NAME_YM "WAVE_YM_TABLE"
+
+/* power supplies */
+#define CS40L26_VP_SUPPLY 0
+#define CS40L26_VA_SUPPLY 1
+#define CS40L26_NUM_SUPPLIES 2
+#define CS40L26_VP_SUPPLY_NAME "VP"
+#define CS40L26_VA_SUPPLY_NAME "VA"
+
+#define CS40L26_MIN_RESET_PULSE_WIDTH 1500
+#define CS40L26_CONTROL_PORT_READY_DELAY 3000
+
+/* haptic triggering */
+#define CS40L26_TIMEOUT_MS_MAX 5000 /* ms */
+
+#define CS40L26_TRIGGER_EFFECT 1
+
+#define CS40L26_STOP_PLAYBACK 0x05000000
+
+#define CS40L26_MAX_INDEX_MASK 0x0000FFFF
+
+#define CS40L26_CUSTOM_DATA_SIZE 2
+
+#define CS40L26_RAM_INDEX_START 0x01000000
+#define CS40L26_RAM_INDEX_END 0x0100007F
+
+#define CS40L26_ROM_INDEX_START 0x01800000
+#define CS40L26_ROM_INDEX_END 0x01800026
+
+#define CS40L26_OWT_INDEX_START 0x01400000
+#define CS40L26_OWT_INDEX_END 0x01400005
+
+
+#define CS40L26_RAM_BANK_ID 0
+#define CS40L26_ROM_BANK_ID 1
+#define CS40L26_OWT_BANK_ID 2
+
+#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_DURATION_OFFSET 8
+#define CS40L26_BUZZGEN_DURATION_DIV_STEP 4
+#define CS40L26_BUZZGEN_LEVEL_OFFSET 4
+#define CS40L26_BUZZGEN_LEVEL_DEFAULT 0x50
+
+#define CS40L26_AMP_CTRL_VOL_PCM_MASK GENMASK(13, 3)
+#define CS40L26_AMP_CTRL_VOL_PCM_SHIFT 3
+
+#define CS40L26_AMP_VOL_PCM_MAX 0x07FF
+
+/* Interrupts */
+#define CS40L26_IRQ_STATUS_DEASSERT 0x0
+#define CS40L26_IRQ_STATUS_ASSERT 0x1
+
+#define CS40L26_ISEQ_MAX_ENTRIES 4
+
+#define CS40L26_IRQ_UNMASK 0
+#define CS40L26_IRQ_MASK 1
+
+/* output */
+#define CS40L26_GLOBAL_ENABLES2_DEFAULT 0x01000000
+#define CS40L26_BST_CTRL_DEFAULT 0x000000AA
+#define CS40L26_DACPCM1_INPUT_DEFAULT 0x00000032
+#define CS40L26_ASP_ENABLES1_DEFAULT 0x00070003
+#define CS40L26_ASP_CTRL2_DEFAULT 0x20200011
+#define CS40L26_DSP1RX5_INPUT_DEFAULT 0x00000009
+
+#define CS40L26_NUM_OUTPUT_SETUP_WRITES 3
+
+/* brownout prevention */
+#define CS40L26_VXBR_DEFAULT 0xFFFFFFFF
+
+#define CS40L26_VXBR_STATUS_DIV_STEP 625
+#define CS40L26_VXBR_STATUS_MASK GENMASK(7, 0)
+#define CS40L26_VXBR_STATUS_MUTE_MASK BIT(8)
+#define CS40L26_VXBR_STATUS_MUTE_SHIFT 8
+
+#define CS40L26_VBBR_EN_MASK BIT(13)
+#define CS40L26_VBBR_EN_SHIFT 13
+
+#define CS40L26_VBBR_FLAG_MASK BIT(19)
+#define CS40L26_VBBR_FLAG_SHIFT 19
+#define CS40L26_VBBR_ATT_CLR_MASK BIT(20)
+#define CS40L26_VBBR_ATT_CLR_SHIFT 20
+
+#define CS40L26_VPBR_EN_MASK BIT(12)
+#define CS40L26_VPBR_EN_SHIFT 12
+#define CS40L26_VPBR_THLD_MASK GENMASK(4, 0)
+
+#define CS40L26_VPBR_THLD_MIN 0x02
+#define CS40L26_VPBR_THLD_MAX 0x1F
+#define CS40L26_VPBR_THLD_MV_DIV 47
+#define CS40L26_VPBR_THLD_OFFSET 51
+#define CS40L26_VPBR_THLD_MV_MIN 2497
+#define CS40L26_VPBR_THLD_MV_MAX 3874
+
+#define CS40L26_VBBR_THLD_MASK GENMASK(5, 0)
+#define CS40L26_VBBR_THLD_MIN 0x02
+#define CS40L26_VBBR_THLD_MAX 0x3F
+#define CS40L26_VBBR_THLD_MV_STEP 55
+#define CS40L26_VBBR_THLD_MV_MIN 109
+#define CS40L26_VBBR_THLD_MV_MAX 3445
+
+#define CS40L26_VXBR_MAX_ATT_MASK GENMASK(11, 8)
+#define CS40L26_VXBR_MAX_ATT_SHIFT 8
+#define CS40L26_VXBR_MAX_ATT_MAX 0xF
+
+#define CS40L26_VXBR_ATK_STEP_DIV_DB 1000
+#define CS40L26_VXBR_ATK_STEP_MIN_DB 250
+#define CS40L26_VXBR_ATK_STEP_MAX_DB 6000
+#define CS40L26_VXBR_ATK_STEP_MIN 0x0
+#define CS40L26_VXBR_ATK_STEP_MAX 0x7
+#define CS40L26_VXBR_ATK_STEP_MASK GENMASK(15, 12)
+#define CS40L26_VXBR_ATK_STEP_SHIFT 12
+#define CS40L26_VXBR_ATK_STEP_OFFSET 1
+
+#define CS40L26_VXBR_ATK_RATE_MIN 0x0
+#define CS40L26_VXBR_ATK_RATE_MAX 0x7
+#define CS40L26_VXBR_ATK_RATE_MASK GENMASK(18, 16)
+#define CS40L26_VXBR_ATK_RATE_SHIFT 16
+#define CS40L26_VXBR_ATK_RATE_OFFSET 1
+
+#define CS40L26_VXBR_WAIT_MAX 0x3
+#define CS40L26_VXBR_WAIT_MASK GENMASK(20, 19)
+#define CS40L26_VXBR_WAIT_SHIFT 19
+
+#define CS40L26_VXBR_REL_RATE_MAX 0x7
+#define CS40L26_VXBR_REL_RATE_MASK GENMASK(23, 21)
+#define CS40L26_VXBR_REL_RATE_SHIFT 21
+
+/* audio */
+#define CS40L26_PLL_CLK_CFG0 0x00
+#define CS40L26_PLL_CLK_CFG1 0x1B
+#define CS40L26_PLL_CLK_CFG2 0x21
+#define CS40L26_PLL_CLK_CFG3 0x28
+#define CS40L26_PLL_CLK_CFG4 0x30
+#define CS40L26_PLL_CLK_CFG5 0x33
+
+#define CS40L26_PLL_CLK_FRQ0 32768
+#define CS40L26_PLL_CLK_FRQ1 1536000
+#define CS40L26_PLL_CLK_FRQ2 3072000
+#define CS40L26_PLL_CLK_FRQ3 6144000
+#define CS40L26_PLL_CLK_FRQ4 9600000
+#define CS40L26_PLL_CLK_FRQ5 12288000
+
+#define CS40L26_PLL_CLK_SEL_BCLK 0x0
+#define CS40L26_PLL_CLK_SEL_FSYNC 0x1
+#define CS40L26_PLL_CLK_SEL_MCLK 0x5
+
+#define CS40L26_PLL_CLK_FREQ_MASK GENMASK(31, 0)
+#define CS40L26_PLL_CLK_CFG_MASK GENMASK(5, 0)
+
+#define CS40L26_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+#define CS40L26_ASP_RX_WIDTH_MASK GENMASK(31, 24)
+#define CS40L26_ASP_RX_WIDTH_SHIFT 24
+#define CS40L26_ASP_FMT_MASK GENMASK(10, 8)
+#define CS40L26_ASP_FMT_SHIFT 8
+#define CS40L26_ASP_BCLK_INV_MASK BIT(6)
+#define CS40L26_ASP_BCLK_INV_SHIFT 6
+#define CS40L26_ASP_FSYNC_INV_MASK BIT(2)
+#define CS40L26_ASP_FSYNC_INV_SHIFT 2
+
+#define CS40L26_ASP_FMT_TDM1_DSPA 0x0
+#define CS40L26_ASP_FMT_I2S 0x2
+#define CS40L26_ASP_FMT_TDM1P5 0x4
+
+#define CS40L26_PLL_REFCLK_BCLK 0x0
+#define CS40L26_PLL_REFCLK_FSYNC 0x1
+#define CS40L26_PLL_REFCLK_MCLK 0x5
+
+#define CS40L26_PLL_REFCLK_SEL_MASK GENMASK(2, 0)
+#define CS40L26_PLL_REFCLK_EN_MASK BIT(4)
+#define CS40L26_PLL_REFCLK_EN_SHIFT 4
+#define CS40L26_PLL_REFCLK_FREQ_MASK GENMASK(10, 5)
+#define CS40L26_PLL_REFCLK_FREQ_SHIFT 5
+#define CS40L26_PLL_REFCLK_OPEN_LOOP_MASK BIT(11)
+#define CS40L26_PLL_REFCLK_OPEN_LOOP_SHIFT 11
+#define CS40L26_PLL_REFCLK_FORCE_EN_MASK BIT(16)
+#define CS40L26_PLL_REFCLK_FORCE_EN_SHIFT 16
+
+#define CS40L26_ASP_RX_WL_MASK GENMASK(5, 0)
+
+#define CS40L26_DATA_SRC_ZERO_FILL 0x00
+#define CS40L26_DATA_SRC_DIAG_GEN 0x04
+#define CS40L26_DATA_SRC_ASPRX1 0x08
+#define CS40L26_DATA_SRC_ASPRX2 0x09
+#define CS40L26_DATA_SRC_ASPRX3 0x0A
+#define CS40L26_DATA_SRC_VMON 0x18
+#define CS40L26_DATA_SRC_IMON 0x19
+#define CS40L26_DATA_SRC_VMON_FSX2 0x1A
+#define CS40L26_DATA_SRC_IMON_FSX2 0x1B
+#define CS40L26_DATA_SRC_ERR_VOL 0x20
+#define CS40L26_DATA_SRC_CLASSH_TGT 0x21
+#define CS40L26_DATA_SRC_VPMON 0x28
+#define CS40L26_DATA_SRC_VBSTMON 0x29
+#define CS40L26_DATA_SRC_BOOST_MON_MUX0 0x30
+#define CS40L26_DATA_SRC_BOOST_MON_MUX1 0x31
+#define CS40L26_DATA_SRC_DSP1TX1 0x32
+#define CS40L26_DATA_SRC_DSP1TX2 0x33
+#define CS40L26_DATA_SRC_DSP1TX3 0x34
+#define CS40L26_DATA_SRC_DSP1TX4 0x35
+#define CS40L26_DATA_SRC_DSP1TX5 0x36
+#define CS40L26_DATA_SRC_DSP1TX6 0x37
+#define CS40L26_DATA_SRC_TEMP_MON 0x3A
+#define CS40L26_DATA_SRC_AMP_INTP 0x40
+
+#define CS40L26_DATA_SRC_MASK GENMASK(6, 0)
+
+#define CS40L26_ASP_TX1_EN_MASK BIT(0)
+#define CS40L26_ASP_TX2_EN_MASK BIT(1)
+#define CS40L26_ASP_TX2_EN_SHIFT 1
+#define CS40L26_ASP_TX3_EN_MASK BIT(2)
+#define CS40L26_ASP_TX3_EN_SHIFT 2
+#define CS40L26_ASP_TX4_EN_MASK BIT(3)
+#define CS40L26_ASP_TX4_EN_SHIFT 3
+#define CS40L26_ASP_RX1_EN_MASK BIT(16)
+#define CS40L26_ASP_RX1_EN_SHIFT 16
+#define CS40L26_ASP_RX2_EN_MASK BIT(17)
+#define CS40L26_ASP_RX2_EN_SHIFT 17
+#define CS40L26_ASP_RX3_EN_MASK BIT(18)
+#define CS40L26_ASP_RX3_EN_SHIFT 18
+
+#define CS40L26_CLASS_H_EN_MASK BIT(4)
+#define CS40L26_CLASS_H_EN_SHIFT 4
+
+#define CS40L26_BST_CTL_SEL_MASK GENMASK(1, 0)
+#define CS40L26_BST_CTL_SEL_FIXED 0x0
+#define CS40L26_BST_CTL_SEL_CLASSH 0x1
+
+#define CS40L26_A2H_MAX_TUNINGS 5
+
+/* OWT */
+#define CS40L26_WT_STR_MAX_LEN 512
+#define CS40L26_WT_MAX_SEGS 512
+#define CS40L26_WT_MAX_SECTS 256
+#define CS40L26_WT_MAX_DELAY 10000
+#define CS40L26_WT_MAX_FINITE_REPEAT 32
+
+#define CS40L26_WT_REPEAT_LOOP_MARKER 0xFF
+#define CS40L26_WT_INDEF_TIME_VAL 0xFFFF
+#define CS40L26_WT_MAX_TIME_VAL 16383 /* ms */
+
+#define CS40L26_WT_TERM_SIZE 4
+#define CS40L26_WT_WLEN_TERM_SIZE 8
+#define CS40L26_WT_HEADER_TERM 0xFFFFFF
+#define CS40L26_WT_HEADER_OFFSET 4
+#define CS40L26_WT_HEADER_DEFAULT_FLAGS 0x0000
+
+#define CS40L26_WT_TYPE10_COMP_SEG_LEN_MAX 20
+
+#define CS40L26_WT_TYPE10_WAVELEN_MAX 0x3FFFFF
+#define CS40L26_WT_TYPE10_WAVELEN_INDEF 0x400000
+#define CS40L26_WT_TYPE10_WAVELEN_CALCULATED 0x800000
+#define CS40L26_WT_TYPE10_COMP_DURATION_FLAG 0x8
+
+
+/* MFD */
+#define CS40L26_NUM_MFD_DEVS 1
+
+/* macros */
+#define CS40L26_OTP_MEM(n) (CS40L26_OTP_MEM0 + \
+ ((n) * CL_DSP_BYTES_PER_WORD))
+
+#define CS40L26_MS_TO_SECS(n) ((n) / 1000)
+
+#define CS40L26_MS_TO_US(n) ((n) * 1000)
+
+#define CS40L26_MS_TO_NS(n) ((n) * 1000000)
+
+#define CS40L26_MS_TO_HZ(n) (1000 / (n))
+
+#define CS40L26_SAMPS_TO_MS(n) ((n) / 8)
+
+extern const struct cl_dsp_fw_desc cs40l26_fw;
+extern const struct cl_dsp_fw_desc cs40l26_ram_fw;
+
+/* enums */
+enum cs40l26_vibe_state {
+ CS40L26_VIBE_STATE_STOPPED,
+ CS40L26_VIBE_STATE_HAPTIC,
+ CS40L26_VIBE_STATE_ASP,
+};
+
+enum cs40l26_fw_mode {
+ CS40L26_FW_MODE_ROM,
+ CS40L26_FW_MODE_RAM,
+};
+
+enum cs40l26_iseq {
+ CS40L26_ISEQ_MASK1,
+ CS40L26_ISEQ_MASK2,
+ CS40L26_ISEQ_EDGE1,
+ CS40L26_ISEQ_POL1,
+};
+
+enum cs40l26_err_rls {
+ CS40L26_RSRVD_ERR_RLS,/* 0 */
+ CS40L26_AMP_SHORT_ERR_RLS,/* 1 */
+ CS40L26_BST_SHORT_ERR_RLS,/* 2 */
+ CS40L26_BST_OVP_ERR_RLS,/* 3 */
+ CS40L26_BST_UVP_ERR_RLS,/* 4 */
+ CS40L26_TEMP_WARN_ERR_RLS,/* 5 */
+ CS40L26_TEMP_ERR_RLS,/* 6 */
+};
+
+enum cs40l26_irq1 {
+ CS40L26_IRQ1_GPIO1_RISE,/* 0 */
+ CS40L26_IRQ1_GPIO1_FALL,/* 1 */
+ CS40L26_IRQ1_GPIO2_RISE,/* 2 */
+ CS40L26_IRQ1_GPIO2_FALL,/* 3 */
+ CS40L26_IRQ1_GPIO3_RISE,/* 4 */
+ CS40L26_IRQ1_GPIO3_FALL,/* 5 */
+ CS40L26_IRQ1_GPIO4_RISE,/* 6 */
+ CS40L26_IRQ1_GPIO4_FALL,/* 7 */
+ CS40L26_IRQ1_WKSRC_STS_ANY,/* 8 */
+ CS40L26_IRQ1_WKSRC_STS_GPIO1,/* 9 */
+ CS40L26_IRQ1_WKSRC_STS_GPIO2,/* 10 */
+ CS40L26_IRQ1_WKSRC_STS_GPIO3,/* 11 */
+ CS40L26_IRQ1_WKSRC_STS_GPIO4,/* 12 */
+ CS40L26_IRQ1_WKSRC_STS_SPI,/* 13 */
+ CS40L26_IRQ1_WKSRC_STS_I2C,/* 14 */
+ CS40L26_IRQ1_GLOBAL_EN_ASSERT,/* 15 */
+ CS40L26_IRQ1_PDN_DONE,/* 16 */
+ CS40L26_IRQ1_PUP_DONE,/* 17 */
+ CS40L26_IRQ1_BST_OVP_FLAG_RISE,/* 18 */
+ CS40L26_IRQ1_BST_OVP_FLAG_FALL,/* 19 */
+ CS40L26_IRQ1_BST_OVP_ERR,/* 20 */
+ CS40L26_IRQ1_BST_DCM_UVP_ERR,/* 21 */
+ CS40L26_IRQ1_BST_SHORT_ERR,/* 22 */
+ CS40L26_IRQ1_BST_IPK_FLAG,/* 23 */
+ CS40L26_IRQ1_TEMP_WARN_RISE,/* 24 */
+ CS40L26_IRQ1_TEMP_WARN_FALL,/* 25 */
+ CS40L26_IRQ1_TEMP_ERR,/* 26 */
+ CS40L26_IRQ1_AMP_ERR,/* 27 */
+ CS40L26_IRQ1_DC_WATCHDOG_RISE,/* 28 */
+ CS40L26_IRQ1_DC_WATCHDOG_FALL,/* 29 */
+ CS40L26_IRQ1_VIRTUAL1_MBOX_WR,/* 30 */
+ CS40L26_IRQ1_VIRTUAL2_MBOX_WR,/* 31 */
+ CS40L26_IRQ1_NUM_IRQS,
+};
+
+enum cs40l26_irq2 {
+ CS40L26_IRQ2_PLL_LOCK,/* 0 */
+ CS40L26_IRQ2_PLL_PHASE_LOCK,/* 1 */
+ CS40L26_IRQ2_PLL_FREQ_LOCK,/* 2 */
+ CS40L26_IRQ2_PLL_UNLOCK_RISE,/* 3 */
+ CS40L26_IRQ2_PLL_UNLOCK_FALL,/* 4 */
+ CS40L26_IRQ2_PLL_READY,/* 5 */
+ CS40L26_IRQ2_PLL_REFCLK_PRESENT,/* 6 */
+ CS40L26_IRQ2_REFCLK_MISSING_RISE,/* 7 */
+ CS40L26_IRQ2_REFCLK_MISSING_FALL,/* 8 */
+ CS40L26_IRQ2_RESERVED,/* 9 */
+ CS40L26_IRQ2_ASP_RXSLOT_CFG_ERR,/* 10 */
+ CS40L26_IRQ2_AUX_NG_CH1_ENTRY,/* 11 */
+ CS40L26_IRQ2_AUX_NG_CH1_EXIT,/* 12 */
+ CS40L26_IRQ2_AUX_NG_CH2_ENTRY,/* 13 */
+ CS40L26_IRQ2_AUX_NG_CH2_EXIT,/* 14 */
+ CS40L26_IRQ2_AMP_NG_ON_RISE,/* 15 */
+ CS40L26_IRQ2_AMP_NG_ON_FALL,/* 16 */
+ CS40L26_IRQ2_VPBR_FLAG,/* 17 */
+ CS40L26_IRQ2_VPBR_ATT_CLR,/* 18 */
+ CS40L26_IRQ2_VBBR_FLAG,/* 19 */
+ CS40L26_IRQ2_VBBR_ATT_CLR,/* 20 */
+ CS40L26_IRQ2_RESERVED2,/* 21 */
+ CS40L26_IRQ2_I2C_NACK_ERR,/* 22 */
+ CS40L26_IRQ2_VPMON_CLIPPED,/* 23 */
+ CS40L26_IRQ2_VBSTMON_CLIPPED,/* 24 */
+ CS40L26_IRQ2_VMON_CLIPPED,/* 25 */
+ CS40L26_IRQ2_IMON_CLIPPED,/* 26 */
+ CS40L26_IRQ2_NUM_IRQS,
+};
+
+enum cs40l26_pm_state {
+ CS40L26_PM_STATE_HIBERNATE,
+ CS40L26_PM_STATE_WAKEUP,
+ CS40L26_PM_STATE_PREVENT_HIBERNATE,
+ CS40L26_PM_STATE_ALLOW_HIBERNATE,
+ CS40L26_PM_STATE_SHUTDOWN,
+};
+
+/* structs */
+struct cs40l26_fw {
+ unsigned int id;
+ unsigned int min_rev;
+ unsigned int halo_state_run;
+ unsigned int num_coeff_files;
+ const char * const *coeff_files;
+ const char *fw_file;
+ bool write_fw;
+};
+
+struct cs40l26_owt_section {
+ u8 flags;
+ u8 repeat;
+ u8 index;
+ u16 delay;
+ u16 duration;
+};
+
+struct cs40l26_iseq_pair {
+ u32 addr;
+ u32 val;
+};
+
+struct cs40l26_pseq_v1_pair {
+ u16 addr;
+ u32 val;
+};
+
+struct cs40l26_pseq_v2_op {
+ u8 size;
+ u16 offset; /* offset in bytes from pseq_base */
+ u8 operation;
+ u32 *words;
+ struct list_head list;
+};
+
+struct cs40l26_platform_data {
+ bool vbbr_en;
+ u32 vbbr_thld;
+ u32 vbbr_max_att;
+ u32 vbbr_atk_step;
+ u32 vbbr_atk_rate;
+ u32 vbbr_wait;
+ u32 vbbr_rel_rate;
+ bool vpbr_en;
+ u32 vpbr_thld;
+ u32 vpbr_max_att;
+ u32 vpbr_atk_step;
+ u32 vpbr_atk_rate;
+ u32 vpbr_wait;
+ u32 vpbr_rel_rate;
+};
+
+struct cs40l26_private {
+ struct device *dev;
+ struct regmap *regmap;
+ u32 devid : 24;
+ u8 revid;
+ struct mutex lock;
+ struct cs40l26_platform_data pdata;
+ struct gpio_desc *reset_gpio;
+ struct input_dev *input;
+ struct cl_dsp *dsp;
+ unsigned int trigger_indices[FF_MAX_EFFECTS];
+ struct ff_effect *effect;
+ struct hrtimer vibe_timer;
+ struct work_struct vibe_start_work;
+ struct work_struct vibe_stop_work;
+ struct work_struct set_gain_work;
+ struct workqueue_struct *vibe_workqueue;
+ int irq;
+ bool vibe_init_success;
+ unsigned int pseq_v1_len;
+ unsigned int pseq_v2_num_ops;
+ u32 pseq_base;
+ struct cs40l26_pseq_v1_pair pseq_v1_table[CS40L26_PSEQ_V1_MAX_ENTRIES];
+ struct list_head pseq_v2_op_head;
+ enum cs40l26_pm_state pm_state;
+ struct cs40l26_iseq_pair iseq_table[CS40L26_ISEQ_MAX_ENTRIES];
+ enum cs40l26_fw_mode fw_mode;
+ enum cs40l26_vibe_state vibe_state;
+ int num_loaded_coeff_files;
+ u32 num_waves;
+ struct cs40l26_fw fw;
+ bool fw_loaded;
+ bool pm_ready;
+ bool asp_enable;
+ u16 amp_vol_pcm;
+ u8 last_wksrc_pol;
+ u8 wksrc_sts;
+ u32 event_count;
+ u32 owt_wlength;
+ int num_owt_effects;
+};
+
+struct cs40l26_codec {
+ struct cs40l26_private *core;
+ struct device *dev;
+ struct regmap *regmap;
+ int sysclk_rate;
+ int tuning;
+ int tuning_prev;
+ char *bin_file;
+ u32 daifmt;
+ int tdm_width;
+};
+
+struct cs40l26_pll_sysclk_config {
+ u32 freq;
+ u8 clk_cfg;
+};
+
+/* exported function prototypes */
+void cs40l26_vibe_state_set(struct cs40l26_private *cs40l26,
+ enum cs40l26_vibe_state);
+int cs40l26_class_h_set(struct cs40l26_private *cs40l26, bool class_h);
+int cs40l26_pm_timeout_ms_get(struct cs40l26_private *cs40l26,
+ u32 *timeout_ms);
+int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26,
+ u32 timeout_ms);
+int cs40l26_ack_write(struct cs40l26_private *cs40l26, u32 reg, u32 write_val,
+ u32 reset_val);
+int cs40l26_pseq_v1_multi_add_pair(struct cs40l26_private *cs40l26,
+ const struct reg_sequence *reg_seq, int num_regs, bool replace);
+int cs40l26_pseq_v2_multi_add_write_reg_full(struct cs40l26_private *cs40l26,
+ const struct reg_sequence *reg_seq, int num_regs,
+ bool update_if_op_already_in_seq);
+int cs40l26_resume(struct device *dev);
+int cs40l26_sys_resume(struct device *dev);
+int cs40l26_sys_resume_noirq(struct device *dev);
+int cs40l26_suspend(struct device *dev);
+int cs40l26_sys_suspend(struct device *dev);
+int cs40l26_sys_suspend_noirq(struct device *dev);
+int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state);
+int cs40l26_probe(struct cs40l26_private *cs40l26,
+ struct cs40l26_platform_data *pdata);
+int cs40l26_remove(struct cs40l26_private *cs40l26);
+bool cs40l26_precious_reg(struct device *dev, unsigned int ret);
+bool cs40l26_readable_reg(struct device *dev, unsigned int reg);
+bool cs40l26_volatile_reg(struct device *dev, unsigned int reg);
+
+/* external tables */
+extern const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1];
+extern struct regulator_bulk_data
+ cs40l26_supplies[CS40L26_NUM_SUPPLIES];
+extern const struct dev_pm_ops cs40l26_pm_ops;
+extern const struct regmap_config cs40l26_regmap;
+extern const struct mfd_cell cs40l26_devs[CS40L26_NUM_MFD_DEVS];
+extern const u8 cs40l26_pseq_v2_op_sizes[CS40L26_PSEQ_V2_NUM_OPS][2];
+extern const char * const cs40l26_ram_coeff_files[3];
+
+
+/* sysfs */
+extern struct attribute_group cs40l26_dev_attr_group;
+
+#endif /* __CS40L26_H__ */