diff options
author | Tai Kuo <taikuo@google.com> | 2021-04-06 17:54:30 +0800 |
---|---|---|
committer | TreeHugger Robot <treehugger-gerrit@google.com> | 2021-04-19 13:30:06 +0000 |
commit | b8e1f704583793bcc507616f50e4dc57ccda42df (patch) | |
tree | 0fcb74bc080b2d44ebba5ee66b7cd622718ecfe5 | |
parent | a34e0b6b624043b7cfa1ba90dbcb793e9102172e (diff) | |
download | amplifiers-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.c | 1114 | ||||
-rw-r--r-- | cs40l26/cl_dsp.h | 326 | ||||
-rw-r--r-- | cs40l26/cs40l26-codec.c | 574 | ||||
-rw-r--r-- | cs40l26/cs40l26-i2c.c | 72 | ||||
-rw-r--r-- | cs40l26/cs40l26-spi.c | 72 | ||||
-rw-r--r-- | cs40l26/cs40l26-sysfs.c | 194 | ||||
-rw-r--r-- | cs40l26/cs40l26-tables.c | 614 | ||||
-rw-r--r-- | cs40l26/cs40l26.c | 3195 | ||||
-rw-r--r-- | cs40l26/cs40l26.h | 1338 |
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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, + ®); + 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, + ®); + 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, + ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, + ®); + 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__ */ |