// 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 2022 Cirrus Logic, Inc. // // Author: Fred Treven // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. #include "cs40l26.h" static inline bool section_complete(struct cs40l26_owt_section *s) { return s->delay ? true : false; } static u32 gpio_map_get(struct device_node *np, enum cs40l26_gpio_map gpio) { const char *bank, *name = gpio == CS40L26_GPIO_MAP_A_PRESS ? "cirrus,press-index" : "cirrus,release-index"; u32 val; if (!of_property_read_string_index(np, name, 0, &bank) && !of_property_read_u32_index(np, name, 1, &val)) { if (!strncmp(bank, "RAM", 3)) return (val & CS40L26_BTN_INDEX_MASK) | (1 << CS40L26_BTN_BANK_SHIFT); else if (!strncmp(bank, "ROM", 3)) return val & CS40L26_BTN_INDEX_MASK; } return CS40L26_EVENT_MAP_GPI_DISABLE; } 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) 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_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state) { u32 reg, dsp_state; int ret = 0; if (cs40l26->fw_loaded) ret = cl_dsp_get_reg(cs40l26->dsp, "PM_CUR_STATE", CL_DSP_XM_UNPACKED_TYPE, CS40L26_PM_ALGO_ID, ®); else reg = CS40L26_A1_PM_CUR_STATE_STATIC_REG; if (ret) return ret; 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_set_pll_loop(struct cs40l26_private *cs40l26, unsigned int pll_loop) { int ret, i; if (pll_loop != CS40L26_PLL_REFCLK_SET_OPEN_LOOP && pll_loop != CS40L26_PLL_REFCLK_SET_CLOSED_LOOP) { dev_err(cs40l26->dev, "Invalid PLL Loop setting: %u\n", pll_loop); return -EINVAL; } /* Retry in case DSP is hibernating */ for (i = 0; i < CS40L26_PLL_REFCLK_SET_ATTEMPTS; i++) { ret = regmap_update_bits(cs40l26->regmap, CS40L26_REFCLK_INPUT, CS40L26_PLL_REFCLK_LOOP_MASK, pll_loop << CS40L26_PLL_REFCLK_LOOP_SHIFT); if (!ret) break; } if (i == CS40L26_PLL_REFCLK_SET_ATTEMPTS) { dev_err(cs40l26->dev, "Failed to configure PLL\n"); return -ETIMEDOUT; } return 0; } EXPORT_SYMBOL(cs40l26_set_pll_loop); int cs40l26_dbc_get(struct cs40l26_private *cs40l26, enum cs40l26_dbc dbc, unsigned int *val) { struct device *dev = cs40l26->dev; unsigned int reg; int ret; ret = cs40l26_pm_enter(dev); if (ret) return ret; mutex_lock(&cs40l26->lock); ret = cl_dsp_get_reg(cs40l26->dsp, cs40l26_dbc_names[dbc], CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); if (ret) goto err_pm; ret = regmap_read(cs40l26->regmap, reg, val); if (ret) dev_err(dev, "Failed to read Dynamic Boost Control value\n"); err_pm: mutex_unlock(&cs40l26->lock); cs40l26_pm_exit(dev); return ret; } EXPORT_SYMBOL(cs40l26_dbc_get); int cs40l26_dbc_set(struct cs40l26_private *cs40l26, enum cs40l26_dbc dbc, u32 val) { struct device *dev = cs40l26->dev; unsigned int reg, max; int ret; if (dbc == CS40L26_DBC_TX_LVL_HOLD_OFF_MS) max = CS40L26_DBC_TX_LVL_HOLD_OFF_MS_MAX; else max = CS40L26_DBC_CONTROLS_MAX; if (val > max) { dev_err(dev, "DBC input %u out of bounds\n", val); return -EINVAL; } ret = cl_dsp_get_reg(cs40l26->dsp, cs40l26_dbc_names[dbc], CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); if (ret) return ret; ret = regmap_write(cs40l26->regmap, reg, val); if (ret) dev_err(dev, "Failed to write Dynamic Boost Control value\n"); return ret; } EXPORT_SYMBOL(cs40l26_dbc_set); static int cs40l26_pm_timeout_ticks_write(struct cs40l26_private *cs40l26, u32 ms, unsigned int lower_offset, unsigned int upper_offset) { struct regmap *regmap = cs40l26->regmap; struct device *dev = cs40l26->dev; u32 lower_val, reg, ticks; u8 upper_val; int ret; if (ms > CS40L26_PM_TIMEOUT_MS_MAX) { dev_warn(dev, "Timeout (%u ms) invalid, using maximum\n", ms); ticks = CS40L26_PM_TIMEOUT_MS_MAX * CS40L26_PM_TICKS_MS_DIV; } else { ticks = ms * CS40L26_PM_TICKS_MS_DIV; } upper_val = (ticks >> CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT) & CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK; lower_val = ticks & CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK; ret = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_PM_ALGO_ID, ®); if (ret) return ret; ret = regmap_write(regmap, reg + lower_offset, lower_val); if (ret) { dev_err(dev, "Failed to write timeout ticks to 0x%08X\n", reg + lower_offset); return ret; } ret = regmap_write(regmap, reg + upper_offset, upper_val); if (ret) dev_err(dev, "Failed to write timeout ticks to 0x%08X\n", reg + upper_offset); return ret; } static int cs40l26_pm_timeout_ticks_read(struct cs40l26_private *cs40l26, unsigned int lower_offset, unsigned int upper_offset, u32 *ticks) { u32 lower_val, upper_val, reg; int ret = 0; if (cs40l26->fw_loaded) ret = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_PM_ALGO_ID, ®); else reg = CS40L26_A1_PM_TIMEOUT_TICKS_STATIC_REG; if (ret) return ret; ret = regmap_read(cs40l26->regmap, reg + lower_offset, &lower_val); if (ret) return ret; ret = regmap_read(cs40l26->regmap, reg + upper_offset, &upper_val); if (ret) return ret; *ticks = ((lower_val & CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK) | ((upper_val & CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK) << CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT)); return 0; } int cs40l26_pm_active_timeout_ms_set(struct cs40l26_private *cs40l26, u32 timeout_ms) { return cs40l26_pm_timeout_ticks_write(cs40l26, timeout_ms, CS40L26_PM_ACTIVE_TIMEOUT_LOWER_OFFSET, CS40L26_PM_ACTIVE_TIMEOUT_UPPER_OFFSET); } EXPORT_SYMBOL(cs40l26_pm_active_timeout_ms_set); int cs40l26_pm_active_timeout_ms_get(struct cs40l26_private *cs40l26, u32 *timeout_ms) { u32 ticks; int ret; ret = cs40l26_pm_timeout_ticks_read(cs40l26, CS40L26_PM_ACTIVE_TIMEOUT_LOWER_OFFSET, CS40L26_PM_ACTIVE_TIMEOUT_UPPER_OFFSET, &ticks); if (ret) return ret; *timeout_ms = ticks / CS40L26_PM_TICKS_MS_DIV; return 0; } EXPORT_SYMBOL(cs40l26_pm_active_timeout_ms_get); int cs40l26_pm_stdby_timeout_ms_set(struct cs40l26_private *cs40l26, u32 timeout_ms) { return cs40l26_pm_timeout_ticks_write(cs40l26, timeout_ms, CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET, CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET); } EXPORT_SYMBOL(cs40l26_pm_stdby_timeout_ms_set); int cs40l26_pm_stdby_timeout_ms_get(struct cs40l26_private *cs40l26, u32 *timeout_ms) { u32 ticks; int ret; ret = cs40l26_pm_timeout_ticks_read(cs40l26, CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET, CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET, &ticks); if (ret) return ret; *timeout_ms = ticks / CS40L26_PM_TICKS_MS_DIV; return 0; } EXPORT_SYMBOL(cs40l26_pm_stdby_timeout_ms_get); static void cs40l26_pm_runtime_setup(struct cs40l26_private *cs40l26) { struct device *dev = cs40l26->dev; 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; } 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_check_pm_lock(struct cs40l26_private *cs40l26, bool *locked) { int ret; unsigned int dsp_lock; ret = regmap_read(cs40l26->regmap, CS40L26_A1_PM_STATE_LOCKS_STATIC_REG + CS40L26_DSP_LOCK3_OFFSET, &dsp_lock); if (ret) return ret; if (dsp_lock & CS40L26_DSP_LOCK3_MASK) *locked = true; else *locked = false; return 0; } static void cs40l26_remove_asp_scaling(struct cs40l26_private *cs40l26) { struct device *dev = cs40l26->dev; u16 gain; if (cs40l26->pdata.asp_scale_pct >= CS40L26_GAIN_FULL_SCALE || !cs40l26->scaling_applied) return; gain = cs40l26->gain_tmp; if (gain >= CS40L26_NUM_PCT_MAP_VALUES) { dev_err(dev, "Gain %u%% out of bounds\n", gain); return; } cs40l26->gain_pct = gain; cs40l26->scaling_applied = false; queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work); } int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26, enum cs40l26_pm_state state) { struct device *dev = cs40l26->dev; u32 cmd; u8 curr_state; bool dsp_lock; int ret, i; cmd = (u32) CS40L26_DSP_MBOX_PM_CMD_BASE + state; switch (state) { case CS40L26_PM_STATE_WAKEUP: ATRACE_BEGIN("CS40L26_PM_STATE_WAKEUP"); ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd, CS40L26_DSP_MBOX_RESET); if (ret) return ret; ATRACE_END(); break; case CS40L26_PM_STATE_PREVENT_HIBERNATE: ATRACE_BEGIN("CS40L26_PM_STATE_PREVENT_HIBERNATE"); for (i = 0; i < CS40L26_DSP_STATE_ATTEMPTS; i++) { ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd, CS40L26_DSP_MBOX_RESET); if (ret) return ret; ret = cs40l26_dsp_state_get(cs40l26, &curr_state); if (ret) return ret; if (curr_state == CS40L26_DSP_STATE_ACTIVE) break; if (curr_state == CS40L26_DSP_STATE_STANDBY) { ret = cs40l26_check_pm_lock(cs40l26, &dsp_lock); if (ret) return ret; if (dsp_lock) break; } usleep_range(5000, 5100); } if (i == CS40L26_DSP_STATE_ATTEMPTS) { dev_err(cs40l26->dev, "DSP not starting\n"); return -ETIMEDOUT; } ATRACE_END(); break; case CS40L26_PM_STATE_ALLOW_HIBERNATE: cs40l26->wksrc_sts = 0x00; ret = cs40l26_dsp_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd); if (ret) return ret; break; case CS40L26_PM_STATE_SHUTDOWN: cs40l26->wksrc_sts = 0x00; ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd, CS40L26_DSP_MBOX_RESET); break; default: dev_err(dev, "Invalid PM state: %u\n", state); return -EINVAL; } cs40l26->pm_state = state; return 0; } static int cs40l26_dsp_start(struct cs40l26_private *cs40l26) { u8 dsp_state; unsigned int val; int ret; ret = regmap_read(cs40l26->regmap, CS40L26_A1_DSP_REQ_ACTIVE_REG, &val); if (ret) { dev_err(cs40l26->dev, "Can't read REQ ACTIVE %d\n", ret); return ret; } if (val & CS40L26_DSP_PM_ACTIVE) dev_warn(cs40l26->dev, "REQ ACTIVE is 0x%x\n", val); 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_pre_config(struct cs40l26_private *cs40l26) { u32 halo_state, timeout_ms; u8 dsp_state; int ret, i; ret = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_PREVENT_HIBERNATE); if (ret) return ret; ret = regmap_read(cs40l26->regmap, CS40L26_A1_DSP_HALO_STATE_REG, &halo_state); if (ret) { dev_err(cs40l26->dev, "Failed to get HALO state\n"); 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_active_timeout_ms_get(cs40l26, &timeout_ms); if (ret) return ret; for (i = 0; i < CS40L26_DSP_SHUTDOWN_MAX_ATTEMPTS; i++) { 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_warn(cs40l26->dev, "DSP core not safe to kill\n"); else break; usleep_range(CS40L26_MS_TO_US(timeout_ms), CS40L26_MS_TO_US(timeout_ms) + 100); } if (i == CS40L26_DSP_SHUTDOWN_MAX_ATTEMPTS) { dev_err(cs40l26->dev, "DSP Core could not be shut down\n"); return -EINVAL; } 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; int ret; while (!cs40l26_mbox_buffer_read(cs40l26, &val)) { if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK) == CS40L26_DSP_MBOX_PANIC) { dev_err(dev, "DSP PANIC! Error condition: 0x%06X\n", (u32) (val & CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK)); return -ENOTRECOVERABLE; } if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK) == CS40L26_DSP_MBOX_WATERMARK) { dev_dbg(dev, "Mailbox: WATERMARK\n"); ret = cl_dsp_logger_update(cs40l26->cl_dsp_db); if (ret) return ret; continue; } switch (val) { case CS40L26_DSP_MBOX_COMPLETE_MBOX: ATRACE_END(); dev_dbg(dev, "Mailbox: COMPLETE_MBOX\n"); complete_all(&cs40l26->erase_cont); cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_MBOX_COMPLETE); break; case CS40L26_DSP_MBOX_COMPLETE_GPIO: ATRACE_END(); dev_dbg(dev, "Mailbox: COMPLETE_GPIO\n"); cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_GPIO_COMPLETE); break; case CS40L26_DSP_MBOX_COMPLETE_I2S: ATRACE_END(); dev_dbg(dev, "Mailbox: COMPLETE_I2S\n"); /* ASP is interrupted */ if (cs40l26->asp_enable) complete(&cs40l26->i2s_cont); break; case CS40L26_DSP_MBOX_TRIGGER_I2S: ATRACE_BEGIN("TRIGGER_I2S"); dev_dbg(dev, "Mailbox: TRIGGER_I2S\n"); complete(&cs40l26->i2s_cont); break; case CS40L26_DSP_MBOX_TRIGGER_CP: if (!cs40l26->vibe_state_reporting) { dev_err(dev, "vibe_state not supported\n"); return -EPERM; } ATRACE_BEGIN("TRIGGER_CP"); dev_dbg(dev, "Mailbox: TRIGGER_CP\n"); cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK); break; case CS40L26_DSP_MBOX_TRIGGER_GPIO: ATRACE_BEGIN("TRIGGER_GPIO"); dev_dbg(dev, "Mailbox: TRIGGER_GPIO\n"); cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER); break; case CS40L26_DSP_MBOX_PM_AWAKE: ATRACE_BEGIN("AWAKE"); cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; ATRACE_END(); dev_dbg(dev, "Mailbox: AWAKE\n"); break; case CS40L26_DSP_MBOX_F0_EST_START: dev_dbg(dev, "Mailbox: F0_EST_START\n"); break; case CS40L26_DSP_MBOX_F0_EST_DONE: dev_dbg(dev, "Mailbox: F0_EST_DONE\n"); complete(&cs40l26->cal_f0_cont); break; case CS40L26_DSP_MBOX_REDC_EST_START: dev_dbg(dev, "Mailbox: REDC_EST_START\n"); break; case CS40L26_DSP_MBOX_REDC_EST_DONE: dev_dbg(dev, "Mailbox: REDC_EST_DONE\n"); complete(&cs40l26->cal_redc_cont); break; case CS40L26_DSP_MBOX_LE_EST_START: dev_dbg(dev, "Mailbox: LE_EST_START\n"); break; case CS40L26_DSP_MBOX_LE_EST_DONE: dev_dbg(dev, "Mailbox: LE_EST_DONE\n"); break; case CS40L26_DSP_MBOX_PEQ_CALCULATION_START: dev_dbg(dev, "Mailbox: PEQ_CALCULATION_START\n"); break; case CS40L26_DSP_MBOX_PEQ_CALCULATION_DONE: dev_dbg(dev, "Mailbox: PEQ_CALCULATION_DONE\n"); complete(&cs40l26->cal_dvl_peq_cont); break; case CS40L26_DSP_MBOX_SYS_ACK: dev_err(dev, "Mailbox: ACK\n"); return -EPERM; default: dev_err(dev, "MBOX buffer value (0x%X) is invalid\n", val); return -EINVAL; } } return 0; } int cs40l26_copy_f0_est_to_dvl(struct cs40l26_private *cs40l26) { u32 reg, f0_measured_q9_14, global_sample_rate, normalized_f0_q1_23; int ret, sample_rate; /* Must be awake and under mutex lock */ ret = regmap_read(cs40l26->regmap, CS40L26_GLOBAL_SAMPLE_RATE, &global_sample_rate); if (ret) return ret; switch (global_sample_rate & CS40L26_GLOBAL_FS_MASK) { case CS40L26_GLOBAL_FS_48K: sample_rate = 48000; break; case CS40L26_GLOBAL_FS_96K: sample_rate = 96000; break; default: dev_warn(cs40l26->dev, "Invalid GLOBAL_FS, %08X", global_sample_rate); return -EINVAL; } ret = cl_dsp_get_reg(cs40l26->dsp, "F0_EST", CL_DSP_XM_UNPACKED_TYPE, CS40L26_F0_EST_ALGO_ID, ®); if (ret) return ret; ret = regmap_read(cs40l26->regmap, reg, &f0_measured_q9_14); if (ret) return ret; ret = cl_dsp_get_reg(cs40l26->dsp, "LRA_NORM_F0", CL_DSP_XM_UNPACKED_TYPE, CS40L26_DVL_ALGO_ID, ®); if (ret) return ret; normalized_f0_q1_23 = (f0_measured_q9_14 << 9) / sample_rate; ret = regmap_write(cs40l26->regmap, reg, normalized_f0_q1_23); return ret; } EXPORT_SYMBOL(cs40l26_copy_f0_est_to_dvl); int cs40l26_asp_start(struct cs40l26_private *cs40l26) { int ret; if (cs40l26->pdata.asp_scale_pct < CS40L26_GAIN_FULL_SCALE) queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work); 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 before I2S start\n"); return ret; } reinit_completion(&cs40l26->i2s_cont); return cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, CS40L26_DSP_MBOX_CMD_START_I2S, CS40L26_DSP_MBOX_RESET); } EXPORT_SYMBOL(cs40l26_asp_start); void cs40l26_vibe_state_update(struct cs40l26_private *cs40l26, enum cs40l26_vibe_state_event event) { if (!mutex_is_locked(&cs40l26->lock)) { dev_err(cs40l26->dev, "%s must be called under mutex lock\n", __func__); return; } dev_dbg(cs40l26->dev, "effects_in_flight = %d\n", cs40l26->effects_in_flight); switch (event) { case CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK: case CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER: cs40l26_remove_asp_scaling(cs40l26); cs40l26->effects_in_flight = cs40l26->effects_in_flight <= 0 ? 1 : cs40l26->effects_in_flight + 1; break; case CS40L26_VIBE_STATE_EVENT_MBOX_COMPLETE: case CS40L26_VIBE_STATE_EVENT_GPIO_COMPLETE: cs40l26->effects_in_flight = cs40l26->effects_in_flight <= 0 ? 0 : cs40l26->effects_in_flight - 1; if (cs40l26->effects_in_flight == 0 && cs40l26->asp_enable) if (cs40l26_asp_start(cs40l26)) return; break; case CS40L26_VIBE_STATE_EVENT_ASP_START: cs40l26->asp_enable = true; break; case CS40L26_VIBE_STATE_EVENT_ASP_STOP: cs40l26_remove_asp_scaling(cs40l26); cs40l26->asp_enable = false; break; default: dev_err(cs40l26->dev, "Invalid vibe state event: %d\n", event); break; } if (cs40l26->effects_in_flight) cs40l26->vibe_state = CS40L26_VIBE_STATE_HAPTIC; else if (cs40l26->asp_enable) cs40l26->vibe_state = CS40L26_VIBE_STATE_ASP; else cs40l26->vibe_state = CS40L26_VIBE_STATE_STOPPED; #if !IS_ENABLED(CONFIG_INPUT_CS40L26_ATTR_UNDER_BUS) sysfs_notify(&cs40l26->input->dev.kobj, "default", "vibe_state"); #else sysfs_notify(&cs40l26->dev->kobj, "default", "vibe_state"); #endif } EXPORT_SYMBOL(cs40l26_vibe_state_update); static int cs40l26_error_release(struct cs40l26_private *cs40l26, unsigned int err_rls) { struct regmap *regmap = cs40l26->regmap; struct device *dev = cs40l26->dev; u32 err_sts, err_cfg; int 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; } static int cs40l26_handle_irq1(struct cs40l26_private *cs40l26, enum cs40l26_irq1 irq1) { struct device *dev = cs40l26->dev; u32 err_rls = 0; int ret = 0; unsigned int reg, val; 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"); } cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; break; case CS40L26_IRQ1_WKSRC_STS_ANY: dev_dbg(dev, "Wakesource detected (ANY)\n"); 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); } 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_err(dev, "BST overvolt. error\n"); err_rls = CS40L26_BST_OVP_ERR_RLS; break; case CS40L26_IRQ1_BST_DCM_UVP_ERR: dev_err(dev, "BST undervolt. error\n"); err_rls = CS40L26_BST_UVP_ERR_RLS; break; case CS40L26_IRQ1_BST_SHORT_ERR: dev_err(dev, "LBST short detected\n"); err_rls = CS40L26_BST_SHORT_ERR_RLS; break; case CS40L26_IRQ1_BST_IPK_FLAG: dev_dbg(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_err(dev, "Die overtemperature error\n"); err_rls = CS40L26_TEMP_ERR_RLS; break; case CS40L26_IRQ1_AMP_ERR: dev_err(dev, "AMP short detected\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 = regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_1, BIT(irq1)); if (ret) { dev_err(dev, "Failed to clear Mailbox IRQ\n"); goto err; } return cs40l26_handle_mbox_buffer(cs40l26); default: dev_err(dev, "Unrecognized IRQ1 EINT1 status\n"); return -EINVAL; } if (err_rls) ret = cs40l26_error_release(cs40l26, err_rls); 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_err(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_err(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_err(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_err(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; int ret; ret = pm_runtime_get_sync(dev); if (ret < 0) { cs40l26_resume_error_handle(dev, ret); dev_err(dev, "Interrupts missed\n"); cs40l26_dsp_write(cs40l26, CS40L26_IRQ1_EINT_1, CS40L26_IRQ_EINT1_ALL_MASK); cs40l26_dsp_write(cs40l26, CS40L26_IRQ1_EINT_2, CS40L26_IRQ_EINT2_ALL_MASK); return IRQ_NONE; } mutex_lock(&cs40l26->lock); if (regmap_read(regmap, CS40L26_IRQ1_STATUS, &sts)) { dev_err(dev, "Failed to read IRQ1 Status\n"); ret = IRQ_NONE; goto err; } if (sts != CS40L26_IRQ_STATUS_ASSERT) { dev_err(dev, "IRQ1 asserted with no pending interrupts\n"); ret = IRQ_NONE; goto err; } 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) { 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); 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: mutex_unlock(&cs40l26->lock); cs40l26_pm_exit(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 int cs40l26_pseq_find_end(struct cs40l26_private *cs40l26, struct cs40l26_pseq_op **op_end) { struct cs40l26_pseq_op *op; list_for_each_entry(op, &cs40l26->pseq_op_head, list) { if (op->operation == CS40L26_PSEQ_OP_END) break; } if (op->operation != CS40L26_PSEQ_OP_END) { dev_err(cs40l26->dev, "Failed to find PSEQ list terminator\n"); return -ENOENT; } *op_end = op; return 0; } int cs40l26_pseq_write(struct cs40l26_private *cs40l26, u32 addr, u32 data, bool update, u8 op_code) { struct device *dev = cs40l26->dev; bool is_new = true; unsigned int l_addr_mask, u_addr_mask, u_data_mask, l_data_mask; unsigned int l_addr_shift, u_addr_shift, u_data_shift; struct cs40l26_pseq_op *op, *op_new, *op_end; unsigned int op_mask; int num_op_words; u32 *op_words; int ret; /* * Due to a bug in the DSP ROM, if data[23] = 1 for a WRITE_FULL * operation, then, when the DSP power-on write sequencer * actually applies these writes when coming out of hibernate, * the DSP sign-extends bit 23 to bits[31:24]. So, warn if it * appears the PSEQ will not function as expected. */ if ((op_code == CS40L26_PSEQ_OP_WRITE_FULL) && (data & BIT(23)) && (((data & GENMASK(31, 24)) >> 24) != 0xFF)) { dev_warn(dev, "PSEQ to set data[31:24] to 0xFF reg: %08X, data: %08X", addr, data); } if (op_code == CS40L26_PSEQ_OP_WRITE_FULL) { num_op_words = CS40L26_PSEQ_OP_WRITE_FULL_WORDS; l_addr_shift = CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_SHIFT; l_addr_mask = CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_MASK; u_addr_shift = CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_SHIFT; u_addr_mask = CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_MASK; u_data_shift = CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_SHIFT; u_data_mask = CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_MASK; l_data_mask = CS40L26_PSEQ_WRITE_FULL_LOWER_DATA_MASK; op_mask = CS40L26_PSEQ_WRITE_FULL_OP_MASK; } else if (op_code == CS40L26_PSEQ_OP_WRITE_H16 || op_code == CS40L26_PSEQ_OP_WRITE_L16) { if (addr & CS40L26_PSEQ_INVALID_ADDR) { dev_err(dev, "Invalid PSEQ address: 0x%08X\n", addr); return -EINVAL; } num_op_words = CS40L26_PSEQ_OP_WRITE_X16_WORDS; l_addr_shift = CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_SHIFT; l_addr_mask = CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_MASK; u_addr_shift = CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_SHIFT; u_addr_mask = CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_MASK; u_data_shift = CS40L26_PSEQ_WRITE_X16_UPPER_DATA_SHIFT; u_data_mask = CS40L26_PSEQ_WRITE_X16_UPPER_DATA_MASK; op_mask = CS40L26_PSEQ_WRITE_X16_OP_MASK; } else { dev_err(dev, "Invalid PSEQ OP code: 0x%02X\n", op_code); return -EINVAL; } op_words = devm_kcalloc(dev, num_op_words, sizeof(u32), GFP_KERNEL); if (!op_words) return -ENOMEM; op_words[0] = (op_code << CS40L26_PSEQ_OP_SHIFT); op_words[0] |= (addr & u_addr_mask) >> u_addr_shift; op_words[1] = (addr & l_addr_mask) << l_addr_shift; op_words[1] |= (data & u_data_mask) >> u_data_shift; if (op_code == CS40L26_PSEQ_OP_WRITE_FULL) op_words[2] = data & l_data_mask; list_for_each_entry(op, &cs40l26->pseq_op_head, list) { if (op->words[0] == op_words[0] && (op->words[1] & op_mask) == (op_words[1] & op_mask) && update) { if (op->size != num_op_words) { dev_err(dev, "Failed to replace PSEQ op.\n"); ret = -EINVAL; goto op_words_free; } is_new = false; break; } } op_new = devm_kzalloc(dev, sizeof(*op_new), GFP_KERNEL); if (!op_new) { ret = -ENOMEM; goto op_words_free; } op_new->size = num_op_words; op_new->words = op_words; op_new->operation = op_code; ret = cs40l26_pseq_find_end(cs40l26, &op_end); if (ret) goto op_new_free; if (((CS40L26_PSEQ_MAX_WORDS * CL_DSP_BYTES_PER_WORD) - op_end->offset) < (op_new->size * CL_DSP_BYTES_PER_WORD)) { dev_err(dev, "Not enough space in pseq to add op\n"); ret = -ENOMEM; goto op_new_free; } if (is_new) { op_new->offset = op_end->offset; op_end->offset += (num_op_words * CL_DSP_BYTES_PER_WORD); } else { op_new->offset = op->offset; } ret = regmap_bulk_write(cs40l26->regmap, cs40l26->pseq_base + op_new->offset, op_new->words, op_new->size); if (ret) { dev_err(dev, "Failed to write PSEQ op.\n"); goto op_new_free; } if (is_new) { ret = regmap_bulk_write(cs40l26->regmap, cs40l26->pseq_base + op_end->offset, op_end->words, op_end->size); if (ret) { dev_err(dev, "Failed to write PSEQ terminator\n"); goto op_new_free; } list_add(&op_new->list, &cs40l26->pseq_op_head); cs40l26->pseq_num_ops++; } else { list_replace(&op->list, &op_new->list); } return 0; op_new_free: devm_kfree(dev, op_new); op_words_free: devm_kfree(dev, op_words); return ret; } EXPORT_SYMBOL(cs40l26_pseq_write); static int cs40l26_pseq_multi_write(struct cs40l26_private *cs40l26, const struct reg_sequence *reg_seq, int num_regs, bool update, u8 op_code) { int ret, i; for (i = 0; i < num_regs; i++) { ret = cs40l26_pseq_write(cs40l26, reg_seq[i].reg, reg_seq[i].def, update, op_code); if (ret) return ret; } return 0; } static int cs40l26_pseq_init(struct cs40l26_private *cs40l26) { struct device *dev = cs40l26->dev; u32 words[CS40L26_PSEQ_MAX_WORDS], *op_words; struct cs40l26_pseq_op *pseq_op; int ret, i, j, num_words, read_size_words; u8 operation; INIT_LIST_HEAD(&cs40l26->pseq_op_head); cs40l26->pseq_num_ops = 0; ret = cl_dsp_get_reg(cs40l26->dsp, "POWER_ON_SEQUENCE", CL_DSP_XM_UNPACKED_TYPE, CS40L26_PM_ALGO_ID, &cs40l26->pseq_base); if (ret) return ret; /* read pseq memory space */ i = 0; while (i < CS40L26_PSEQ_MAX_WORDS) { if ((CS40L26_PSEQ_MAX_WORDS - i) > CS40L26_MAX_I2C_READ_SIZE_WORDS) read_size_words = CS40L26_MAX_I2C_READ_SIZE_WORDS; else read_size_words = CS40L26_PSEQ_MAX_WORDS - i; ret = regmap_bulk_read(cs40l26->regmap, cs40l26->pseq_base + i * CL_DSP_BYTES_PER_WORD, words + i, read_size_words); if (ret) { dev_err(dev, "Failed to read from power on seq.\n"); return ret; } i += read_size_words; } i = 0; while (i < CS40L26_PSEQ_MAX_WORDS) { operation = (words[i] & CS40L26_PSEQ_OP_MASK) >> CS40L26_PSEQ_OP_SHIFT; /* get num words for given operation */ for (j = 0; j < CS40L26_PSEQ_NUM_OPS; j++) { if (cs40l26_pseq_op_sizes[j][0] == operation) { num_words = cs40l26_pseq_op_sizes[j][1]; break; } } if (j == CS40L26_PSEQ_NUM_OPS) { dev_err(dev, "Failed to determine pseq 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_op = devm_kzalloc(dev, sizeof(*pseq_op), GFP_KERNEL); if (!pseq_op) { ret = -ENOMEM; goto err_free; } pseq_op->size = num_words; pseq_op->offset = i * CL_DSP_BYTES_PER_WORD; pseq_op->operation = operation; pseq_op->words = op_words; list_add(&pseq_op->list, &cs40l26->pseq_op_head); cs40l26->pseq_num_ops++; i += num_words; if (operation == CS40L26_PSEQ_OP_END) break; } if (operation != CS40L26_PSEQ_OP_END) { dev_err(dev, "PSEQ END_OF_SCRIPT not found\n"); return -E2BIG; } return ret; err_free: kfree(op_words); return ret; } static int cs40l26_update_reg_defaults_via_pseq(struct cs40l26_private *cs40l26) { struct device *dev = cs40l26->dev; int ret; ret = cs40l26_pseq_write(cs40l26, CS40L26_NGATE1_INPUT, CS40L26_DATA_SRC_DSP1TX4, true, CS40L26_PSEQ_OP_WRITE_L16); if (ret) return ret; ret = cs40l26_pseq_write(cs40l26, CS40L26_MIXER_NGATE_CH1_CFG, CS40L26_MIXER_NGATE_CH1_CFG_DEFAULT_NEW, true, CS40L26_PSEQ_OP_WRITE_FULL); if (ret) { dev_err(dev, "Failed to sequence Mixer Noise Gate\n"); return ret; } /* set SPK_DEFAULT_HIZ to 1 */ ret = cs40l26_pseq_write(cs40l26, CS40L26_TST_DAC_MSM_CONFIG, CS40L26_TST_DAC_MSM_CONFIG_DEFAULT_CHANGE_VALUE_H16, true, CS40L26_PSEQ_OP_WRITE_H16); if (ret) dev_err(dev, "Failed to sequence register default updates\n"); return ret; } static int cs40l26_irq_update_mask(struct cs40l26_private *cs40l26, u32 reg, u32 val, u32 bit_mask) { u32 eint_reg, cur_mask, new_mask; int ret; if (reg == CS40L26_IRQ1_MASK_1) { eint_reg = CS40L26_IRQ1_EINT_1; } else if (reg == CS40L26_IRQ1_MASK_2) { eint_reg = CS40L26_IRQ1_EINT_2; } else { dev_err(cs40l26->dev, "Invalid IRQ mask reg: 0x%08X\n", reg); return -EINVAL; } ret = regmap_read(cs40l26->regmap, reg, &cur_mask); if (ret) { dev_err(cs40l26->dev, "Failed to get IRQ mask\n"); return ret; } new_mask = (cur_mask & ~bit_mask) | val; /* Clear interrupt prior to masking/unmasking */ ret = regmap_write(cs40l26->regmap, eint_reg, bit_mask); if (ret) { dev_err(cs40l26->dev, "Failed to clear IRQ\n"); return ret; } ret = regmap_write(cs40l26->regmap, reg, new_mask); if (ret) { dev_err(cs40l26->dev, "Failed to update IRQ mask\n"); return ret; } if (bit_mask & GENMASK(31, 16)) { ret = cs40l26_pseq_write(cs40l26, reg, (new_mask & GENMASK(31, 16)) >> 16, true, CS40L26_PSEQ_OP_WRITE_H16); if (ret) { dev_err(cs40l26->dev, "Failed to update IRQ mask H16"); return ret; } } if (bit_mask & GENMASK(15, 0)) { ret = cs40l26_pseq_write(cs40l26, reg, (new_mask & GENMASK(15, 0)), true, CS40L26_PSEQ_OP_WRITE_L16); if (ret) { dev_err(cs40l26->dev, "Failed to update IRQ mask L16"); return ret; } } return ret; } static int cs40l26_buzzgen_set(struct cs40l26_private *cs40l26, u16 freq, u16 level, u16 duration, u8 buzzgen_num) { unsigned int base_reg, freq_reg, level_reg, duration_reg; int ret; /* BUZZ_EFFECTS1_BUZZ_xxx are initially populated by contents of OTP. * The buzz specified by these controls is triggered by writing * 0x01800080 to the DSP mailbox */ ret = cl_dsp_get_reg(cs40l26->dsp, "BUZZ_EFFECTS1_BUZZ_FREQ", CL_DSP_XM_UNPACKED_TYPE, CS40L26_BUZZGEN_ALGO_ID, &base_reg); if (ret) return ret; freq_reg = base_reg + ((buzzgen_num) * CS40L26_BUZZGEN_CONFIG_OFFSET); level_reg = base_reg + ((buzzgen_num) * CS40L26_BUZZGEN_CONFIG_OFFSET) + CS40L26_BUZZGEN_LEVEL_OFFSET; duration_reg = base_reg + ((buzzgen_num) * CS40L26_BUZZGEN_CONFIG_OFFSET) + CS40L26_BUZZGEN_DURATION_OFFSET; ret = regmap_write(cs40l26->regmap, freq_reg, freq); if (ret) { dev_err(cs40l26->dev, "Failed to write BUZZGEN frequency\n"); return ret; } ret = regmap_write(cs40l26->regmap, level_reg, level); if (ret) { dev_err(cs40l26->dev, "Failed to write BUZZGEN level\n"); return ret; } ret = regmap_write(cs40l26->regmap, duration_reg, duration / 4); if (ret) dev_err(cs40l26->dev, "Failed to write BUZZGEN duration\n"); return ret; } static int cs40l26_map_gpi_to_haptic(struct cs40l26_private *cs40l26, struct ff_effect *effect, struct cs40l26_uploaded_effect *ueffect) { u16 button = effect->trigger.button; u8 gpio = (button & CS40L26_BTN_NUM_MASK) >> CS40L26_BTN_NUM_SHIFT; bool edge, ev_handler_bank_ram, owt, use_timeout; unsigned int fw_rev; u32 reg, write_val; int ret; edge = (button & CS40L26_BTN_EDGE_MASK) >> CS40L26_BTN_EDGE_SHIFT; switch (ueffect->wvfrm_bank) { case CS40L26_RAM_BANK_ID: case CS40L26_BUZ_BANK_ID: owt = false; ev_handler_bank_ram = true; break; case CS40L26_ROM_BANK_ID: owt = false; ev_handler_bank_ram = false; break; case CS40L26_OWT_BANK_ID: owt = true; ev_handler_bank_ram = true; break; default: dev_err(cs40l26->dev, "Effect bank %u not supported\n", ueffect->wvfrm_bank); return -EINVAL; } if (gpio != CS40L26_GPIO1) { dev_err(cs40l26->dev, "GPIO%u not supported on 0x%02X\n", gpio, cs40l26->revid); return -EINVAL; } reg = cs40l26->event_map_base + (edge ? 0 : 4); write_val = (ueffect->trigger_index & CS40L26_BTN_INDEX_MASK) | (ev_handler_bank_ram << CS40L26_BTN_BANK_SHIFT) | (owt << CS40L26_BTN_OWT_SHIFT); ret = regmap_write(cs40l26->regmap, reg, write_val); if (ret) { dev_err(cs40l26->dev, "Failed to update event map\n"); return ret; } ret = cl_dsp_fw_rev_get(cs40l26->dsp, &fw_rev); if (ret) return ret; use_timeout = (!cs40l26->calib_fw && fw_rev >= CS40L26_FW_GPI_TIMEOUT_MIN_REV) || (cs40l26->calib_fw && fw_rev >= CS40L26_FW_GPI_TIMEOUT_CALIB_MIN_REV); if (use_timeout) { ret = cl_dsp_get_reg(cs40l26->dsp, "TIMEOUT_GPI_MS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) return ret; ret = regmap_write(cs40l26->regmap, reg, effect->replay.length); if (ret) dev_warn(cs40l26->dev, "Failed to set GPI timeout, continuing...\n"); } if (edge) ueffect->mapping = CS40L26_GPIO_MAP_A_PRESS; else ueffect->mapping = CS40L26_GPIO_MAP_A_RELEASE; return ret; } static struct cs40l26_uploaded_effect *cs40l26_uploaded_effect_find(struct cs40l26_private *cs40l26, int id) { struct list_head *head = &cs40l26->effect_head; struct cs40l26_uploaded_effect *ueffect; if (list_empty(head)) { dev_dbg(cs40l26->dev, "Effect list is empty\n"); return ERR_PTR(-ENODATA); } list_for_each_entry(ueffect, head, list) { if (ueffect->id == id) break; } if (ueffect->id != id) { dev_dbg(cs40l26->dev, "No such effect (ID = %d)\n", id); return ERR_PTR(-ENODEV); } return ueffect; } static bool cs40l26_is_no_wait_ram_index(struct cs40l26_private *cs40l26, u32 index) { int i; for (i = 0; i < cs40l26->num_no_wait_ram_indices; i++) { if (cs40l26->no_wait_ram_indices[i] == index) return true; } return false; } static void cs40l26_set_gain_worker(struct work_struct *work) { struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, set_gain_work); u16 gain; u32 reg; int ret; ret = cs40l26_pm_enter(cs40l26->dev); if (ret) return; mutex_lock(&cs40l26->lock); if (cs40l26->vibe_state == CS40L26_VIBE_STATE_ASP) { gain = (cs40l26->pdata.asp_scale_pct * cs40l26->gain_pct) / CS40L26_GAIN_FULL_SCALE; cs40l26->gain_tmp = cs40l26->gain_pct; cs40l26->gain_pct = gain; cs40l26->scaling_applied = true; } else { gain = cs40l26->gain_pct; } dev_dbg(cs40l26->dev, "%s: gain = %u%%\n", __func__, gain); /* Write Q21.2 value to SOURCE_ATTENUATION */ ret = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_ATTENUATION", CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); if (ret) goto err_mutex; ret = regmap_write(cs40l26->regmap, reg, cs40l26_attn_q21_2_vals[gain]); if (ret) dev_err(cs40l26->dev, "Failed to set attenuation\n"); err_mutex: mutex_unlock(&cs40l26->lock); cs40l26_pm_exit(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; struct cs40l26_uploaded_effect *ueffect; struct ff_effect *effect; unsigned int reg; u16 duration; bool invert; int ret; dev_dbg(dev, "%s\n", __func__); ret = cs40l26_pm_enter(dev); if (ret) return; mutex_lock(&cs40l26->lock); effect = cs40l26->trigger_effect; ueffect = cs40l26_uploaded_effect_find(cs40l26, effect->id); if (IS_ERR_OR_NULL(ueffect)) { dev_err(dev, "No such effect to play back\n"); ret = PTR_ERR(ueffect); goto err_mutex; } duration = effect->replay.length; ret = cl_dsp_get_reg(cs40l26->dsp, "TIMEOUT_MS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) goto err_mutex; ret = regmap_write(cs40l26->regmap, reg, duration); if (ret) { dev_err(dev, "Failed to set TIMEOUT_MS\n"); goto err_mutex; } ret = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_INVERT", CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); if (ret) goto err_mutex; switch (effect->direction) { case 0x0000: invert = false; break; case 0x8000: invert = true; break; default: dev_err(dev, "Invalid ff_effect direction: 0x%X\n", effect->direction); ret = -EINVAL; goto err_mutex; } ret = regmap_write(cs40l26->regmap, reg, invert); if (ret) goto err_mutex; switch (effect->u.periodic.waveform) { case FF_CUSTOM: case FF_SINE: ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, ueffect->trigger_index, CS40L26_DSP_MBOX_RESET); if (ret) goto err_mutex; cs40l26->cur_index = ueffect->trigger_index; break; default: dev_err(dev, "Invalid waveform type: 0x%X\n", effect->u.periodic.waveform); ret = -EINVAL; goto err_mutex; } if (!cs40l26->vibe_state_reporting) cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK); reinit_completion(&cs40l26->erase_cont); err_mutex: mutex_unlock(&cs40l26->lock); cs40l26_pm_exit(dev); } static void cs40l26_vibe_stop_worker(struct work_struct *work) { struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, vibe_stop_work); bool skip_delay; u32 delay_us; int ret; dev_dbg(cs40l26->dev, "%s\n", __func__); ret = cs40l26_pm_enter(cs40l26->dev); if (ret) return; mutex_lock(&cs40l26->lock); delay_us = cs40l26->delay_before_stop_playback_us; skip_delay = cs40l26_is_no_wait_ram_index(cs40l26, cs40l26->cur_index); if (delay_us && !skip_delay) { mutex_unlock(&cs40l26->lock); dev_info(cs40l26->dev, "Applying delay\n"); /* wait for SVC init phase to complete */ usleep_range(delay_us, delay_us + 100); mutex_lock(&cs40l26->lock); } else { dev_info(cs40l26->dev, "Skipping delay\n"); } if (cs40l26->vibe_state != CS40L26_VIBE_STATE_HAPTIC) { dev_warn(cs40l26->dev, "Attempted stop when vibe_state = %d\n", cs40l26->vibe_state); 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; } mutex_exit: mutex_unlock(&cs40l26->lock); cs40l26_pm_exit(cs40l26->dev); } static void cs40l26_set_gain(struct input_dev *dev, u16 gain) { struct cs40l26_private *cs40l26 = input_get_drvdata(dev); ATRACE_BEGIN(__func__); if (gain >= CS40L26_NUM_PCT_MAP_VALUES) { dev_err(cs40l26->dev, "Gain value %u %% out of bounds\n", gain); return; } cs40l26->gain_pct = gain; queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work); ATRACE_END(); } 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; ATRACE_BEGIN(__func__); dev_dbg(cs40l26->dev, "%s: effect ID = %d, val = %d\n", __func__, effect_id, val); effect = &dev->ff->effects[effect_id]; if (!effect) { dev_err(cs40l26->dev, "No such effect to playback\n"); return -EINVAL; } cs40l26->trigger_effect = effect; if (val > 0) queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_start_work); else queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work); ATRACE_END(); return 0; } int cs40l26_get_num_waves(struct cs40l26_private *cs40l26, u32 *num_waves) { int ret; u32 reg, nwaves, nowt; ret = cl_dsp_get_reg(cs40l26->dsp, "NUM_OF_WAVES", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) return ret; ret = cs40l26_dsp_read(cs40l26, reg, &nwaves); if (ret) return ret; ret = cl_dsp_get_reg(cs40l26->dsp, "OWT_NUM_OF_WAVES_XM", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) return ret; ret = cs40l26_dsp_read(cs40l26, reg, &nowt); if (ret) return ret; *num_waves = nwaves + nowt; return 0; } EXPORT_SYMBOL(cs40l26_get_num_waves); static struct cl_dsp_owt_header *cs40l26_header(struct cs40l26_private *cs40l26, u8 index) { if (!cs40l26->dsp || !cs40l26->dsp->wt_desc || index >= cs40l26->dsp->wt_desc->owt.nwaves) return ERR_PTR(-EINVAL); return &cs40l26->dsp->wt_desc->owt.waves[index]; } static int cs40l26_owt_get_wlength(struct cs40l26_private *cs40l26, u8 index, u32 *wlen_whole) { struct device *dev = cs40l26->dev; struct cl_dsp_owt_header *entry; struct cl_dsp_memchunk ch; if (index == 0) { *wlen_whole = 0; return 0; } entry = cs40l26_header(cs40l26, index); if (IS_ERR(entry)) return PTR_ERR(entry); 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(cs40l26->dsp, &ch, 24, wlen_whole); } static void cs40l26_owt_set_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_write(ch, 8, sections[i].amplitude); cl_dsp_memchunk_write(ch, 8, sections[i].index); cl_dsp_memchunk_write(ch, 8, sections[i].repeat); cl_dsp_memchunk_write(ch, 8, sections[i].flags); cl_dsp_memchunk_write(ch, 16, sections[i].delay); if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) { cl_dsp_memchunk_write(ch, 8, 0x00); /* Pad */ cl_dsp_memchunk_write(ch, 16, sections[i].duration); } } } static int cs40l26_owt_get_section_info(struct cs40l26_private *cs40l26, struct cl_dsp_memchunk *ch, struct cs40l26_owt_section *sections, u8 nsections) { int ret = 0, i; for (i = 0; i < nsections; i++) { ret = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, §ions[i].amplitude); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, §ions[i].index); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, §ions[i].repeat); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, §ions[i].flags); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, ch, 16, §ions[i].delay); if (ret) return ret; if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) { /* Skip padding */ ret = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, NULL); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, ch, 16, §ions[i].duration); if (ret) return ret; } } return ret; } static int cs40l26_owt_calculate_wlength(struct cs40l26_private *cs40l26, u8 nsections, u8 global_rep, u8 *data, u32 data_size_bytes, u32 *owt_wlen) { u32 total_len = 0, section_len = 0, loop_len = 0, wlen_whole = 0; bool in_loop = false; int ret = 0, i; struct cs40l26_owt_section *sections; struct cl_dsp_memchunk ch; u32 dlen, wlen; 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) return -ENOMEM; ch = cl_dsp_memchunk_create((void *) data, data_size_bytes); ret = cs40l26_owt_get_section_info(cs40l26, &ch, sections, nsections); if (ret) { dev_err(cs40l26->dev, "Failed to get section info\n"); goto err_free; } for (i = 0; i < nsections; i++) { ret = cs40l26_owt_get_wlength(cs40l26, sections[i].index, &wlen_whole); if (ret < 0) { dev_err(cs40l26->dev, "Failed to get wlength for index %u\n", sections[i].index); 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; } *owt_wlen = (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, u8 *data, u32 data_size_bytes) { struct device *dev = cs40l26->dev; struct cl_dsp *dsp = cs40l26->dsp; unsigned int write_reg, reg, wt_offset, wt_size_words, wt_base; int ret; ret = cs40l26_pm_enter(dev); if (ret) return ret; ret = cl_dsp_get_reg(dsp, "OWT_NEXT_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_words); if (ret) { dev_err(dev, "Failed to get available WT size\n"); goto err_pm; } if ((wt_size_words * CL_DSP_BYTES_PER_WORD) < data_size_bytes) { 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 * 4); ret = cl_dsp_raw_write(cs40l26->dsp, write_reg, data, data_size_bytes, CL_DSP_MAX_WLEN); if (ret) { dev_err(dev, "Failed to sync OWT\n"); goto err_pm; } ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, CS40L26_DSP_MBOX_CMD_OWT_PUSH, CS40L26_DSP_MBOX_RESET); if (ret) goto err_pm; dev_dbg(dev, "Successfully wrote waveform (%u bytes) to 0x%08X\n", data_size_bytes, write_reg); err_pm: cs40l26_pm_exit(dev); return ret; } static u8 *cs40l26_ncw_amp_scaling(struct cs40l26_private *cs40l26, u8 amp, u8 nsections, void *in_data, u32 data_bytes) { struct cs40l26_owt_section *sections; struct cl_dsp_memchunk in_ch, out_ch; u16 amp_product; u8 *out_data; int i, ret; if (nsections <= 0) { dev_err(cs40l26->dev, "Too few sections for NCW\n"); return ERR_PTR(-EINVAL); } sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section), GFP_KERNEL); if (!sections) return ERR_PTR(-ENOMEM); in_ch = cl_dsp_memchunk_create(in_data, data_bytes); ret = cs40l26_owt_get_section_info(cs40l26, &in_ch, sections, nsections); if (ret) { dev_err(cs40l26->dev, "Failed to get section info\n"); goto sections_free; } for (i = 0; i < nsections; i++) { if (sections[i].index != 0) { amp_product = sections[i].amplitude * amp; sections[i].amplitude = (u8) DIV_ROUND_UP(amp_product, 100); } } out_data = kcalloc(data_bytes, sizeof(u8), GFP_KERNEL); if (!out_data) { ret = -ENOMEM; goto sections_free; } out_ch = cl_dsp_memchunk_create((void *) out_data, data_bytes); cs40l26_owt_set_section_info(cs40l26, &out_ch, sections, nsections); sections_free: kfree(sections); return ret ? ERR_PTR(ret) : out_data; } static int cs40l26_owt_comp_data_size(struct cs40l26_private *cs40l26, u8 nsections, struct cs40l26_owt_section *sections) { int i, size = 0; struct cl_dsp_owt_header *header; for (i = 0; i < nsections; i++) { if (sections[i].index == 0) { size += CS40L26_WT_TYPE10_SECTION_BYTES_MIN; continue; } header = cs40l26_header(cs40l26, sections[i].index); if (IS_ERR(header)) return PTR_ERR(header); if (header->type == WT_TYPE_V6_COMPOSITE) { size += (header->size - 2) * 4; if (section_complete(§ions[i])) size += CS40L26_WT_TYPE10_SECTION_BYTES_MIN; } else { size += sections[i].duration ? CS40L26_WT_TYPE10_SECTION_BYTES_MAX : CS40L26_WT_TYPE10_SECTION_BYTES_MIN; } } return size; } static int cs40l26_refactor_owt(struct cs40l26_private *cs40l26, s16 *in_data, u32 in_data_nibbles, bool pwle, bool svc_waveform, u8 **out_data) { u8 nsections, global_rep, out_nsections = 0; int ret = 0, pos_byte = 0, in_pos_nib = 2; int in_data_bytes = 2 * in_data_nibbles; int out_data_bytes = 0, data_bytes = 0; struct device *dev = cs40l26->dev; u8 delay_section_data[CS40L26_WT_TYPE10_SECTION_BYTES_MIN]; u8 ncw_nsections, ncw_global_rep, *data, *ncw_data; struct cs40l26_owt_section *sections; struct cl_dsp_memchunk ch, out_ch; struct cl_dsp_owt_header *header; u16 section_size_bytes; u32 ncw_bytes, wlen; int i; if (pwle) { out_data_bytes = CS40L26_WT_HEADER_PWLE_SIZE + in_data_bytes; *out_data = kcalloc(out_data_bytes, sizeof(u8), GFP_KERNEL); if (!*out_data) { dev_err(dev, "No space for refactored data\n"); return -ENOMEM; } out_ch = cl_dsp_memchunk_create((void *) *out_data, out_data_bytes); cl_dsp_memchunk_write(&out_ch, 16, CS40L26_WT_HEADER_DEFAULT_FLAGS | (svc_waveform ? CS40L26_OWT_SVC_METADATA : 0)); cl_dsp_memchunk_write(&out_ch, 8, WT_TYPE_V6_PWLE); cl_dsp_memchunk_write(&out_ch, 24, CS40L26_WT_HEADER_OFFSET + (svc_waveform ? CS40L26_WT_METADATA_OFFSET : 0)); cl_dsp_memchunk_write(&out_ch, 24, (in_data_bytes / 4) - (svc_waveform ? CS40L26_WT_METADATA_OFFSET : 0)); memcpy(*out_data + out_ch.bytes, in_data, in_data_bytes); return out_data_bytes; } ch = cl_dsp_memchunk_create((void *) in_data, in_data_bytes); /* Skip padding */ ret = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, NULL); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, &nsections); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, &global_rep); if (ret) return ret; sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section), GFP_KERNEL); if (!sections) return -ENOMEM; ret = cs40l26_owt_get_section_info(cs40l26, &ch, sections, nsections); if (ret) { dev_err(cs40l26->dev, "Failed to get section info\n"); return ret; } data_bytes = cs40l26_owt_comp_data_size(cs40l26, nsections, sections); if (data_bytes <= 0) { dev_err(dev, "Failed to get OWT Composite Data Size\n"); ret = data_bytes; goto sections_err_free; } data = kcalloc(data_bytes, sizeof(u8), GFP_KERNEL); if (!data) { ret = -ENOMEM; goto sections_err_free; } cl_dsp_memchunk_flush(&ch); memset(&delay_section_data, 0, CS40L26_WT_TYPE10_SECTION_BYTES_MIN); for (i = 0; i < nsections; i++) { section_size_bytes = sections[i].duration ? CS40L26_WT_TYPE10_SECTION_BYTES_MAX : CS40L26_WT_TYPE10_SECTION_BYTES_MIN; if (sections[i].index == 0) { memcpy(data + pos_byte, in_data + in_pos_nib, section_size_bytes); pos_byte += section_size_bytes; in_pos_nib += section_size_bytes / 2; out_nsections++; continue; } if (sections[i].repeat != 0) { dev_err(dev, "Inner repeats not allowed for NCWs\n"); ret = -EPERM; goto data_err_free; } header = cs40l26_header(cs40l26, sections[i].index); if (IS_ERR(header)) { ret = PTR_ERR(header); goto data_err_free; } if (header->type == WT_TYPE_V6_COMPOSITE) { ch = cl_dsp_memchunk_create(header->data, 8); /* Skip Wlength */ ret = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 24, NULL); if (ret) return ret; /* Skip Padding */ ret = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, NULL); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, &ncw_nsections); if (ret) return ret; ret = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, &ncw_global_rep); if (ret) return ret; if (ncw_global_rep != 0) { dev_err(dev, "No NCW support for outer repeat\n"); ret = -EPERM; goto data_err_free; } cl_dsp_memchunk_flush(&ch); ncw_bytes = (header->size - 2) * 4; ncw_data = cs40l26_ncw_amp_scaling(cs40l26, sections[i].amplitude, ncw_nsections, header->data + 8, ncw_bytes); if (IS_ERR(ncw_data)) { ret = PTR_ERR(ncw_data); goto data_err_free; } memcpy(data + pos_byte, ncw_data, ncw_bytes); pos_byte += ncw_bytes; out_nsections += ncw_nsections; kfree(ncw_data); if (section_complete(§ions[i])) { ch = cl_dsp_memchunk_create((void *) delay_section_data, CS40L26_WT_TYPE10_SECTION_BYTES_MIN); cl_dsp_memchunk_write(&ch, 24, 0x000000); cl_dsp_memchunk_write(&ch, 8, 0x00); cl_dsp_memchunk_write(&ch, 16, sections[i].delay); memcpy(data + pos_byte, delay_section_data, CS40L26_WT_TYPE10_SECTION_BYTES_MIN); cl_dsp_memchunk_flush(&ch); pos_byte += CS40L26_WT_TYPE10_SECTION_BYTES_MIN; out_nsections++; } } else { memcpy(data + pos_byte, in_data + in_pos_nib, section_size_bytes); pos_byte += section_size_bytes; out_nsections++; } in_pos_nib += section_size_bytes / 2; } out_data_bytes = data_bytes + CS40L26_WT_HEADER_COMP_SIZE; *out_data = kcalloc(out_data_bytes, sizeof(u8), GFP_KERNEL); if (!*out_data) { dev_err(dev, "Failed to allocate space for composite\n"); ret = -ENOMEM; goto data_err_free; } out_ch = cl_dsp_memchunk_create((void *) *out_data, out_data_bytes); cl_dsp_memchunk_write(&out_ch, 16, CS40L26_WT_HEADER_DEFAULT_FLAGS); cl_dsp_memchunk_write(&out_ch, 8, WT_TYPE_V6_COMPOSITE); cl_dsp_memchunk_write(&out_ch, 24, CS40L26_WT_HEADER_OFFSET); cl_dsp_memchunk_write(&out_ch, 24, data_bytes / CL_DSP_BYTES_PER_WORD); ret = cs40l26_owt_calculate_wlength(cs40l26, out_nsections, global_rep, data, data_bytes, &wlen); if (ret) goto data_err_free; cl_dsp_memchunk_write(&out_ch, 24, wlen); cl_dsp_memchunk_write(&out_ch, 8, 0x00); /* Pad */ cl_dsp_memchunk_write(&out_ch, 8, out_nsections); cl_dsp_memchunk_write(&out_ch, 8, global_rep); memcpy(*out_data + out_ch.bytes, data, data_bytes); data_err_free: kfree(data); sections_err_free: kfree(sections); return ret ? ret : out_data_bytes; } static u8 cs40l26_get_lowest_free_buzzgen(struct cs40l26_private *cs40l26) { u8 buzzgen = 1; struct cs40l26_uploaded_effect *ueffect; if (list_empty(&cs40l26->effect_head)) return buzzgen; list_for_each_entry(ueffect, &cs40l26->effect_head, list) { if (ueffect->wvfrm_bank == CS40L26_BUZ_BANK_ID) buzzgen++; } return buzzgen; } static int cs40l26_sine_upload(struct cs40l26_private *cs40l26, struct ff_effect *effect, struct cs40l26_uploaded_effect *ueffect) { struct device *dev = cs40l26->dev; u8 lowest_free_buzzgen, level; u16 freq, period; int ret; if (effect->u.periodic.period < CS40L26_BUZZGEN_PER_MIN) period = CS40L26_BUZZGEN_PER_MIN; else if (effect->u.periodic.period > CS40L26_BUZZGEN_PER_MAX) period = CS40L26_BUZZGEN_PER_MAX; else period = effect->u.periodic.period; freq = CS40L26_MS_TO_HZ(period); if (effect->u.periodic.magnitude < CS40L26_BUZZGEN_LEVEL_MIN) level = CS40L26_BUZZGEN_LEVEL_MIN; else if (effect->u.periodic.magnitude > CS40L26_BUZZGEN_LEVEL_MAX) level = CS40L26_BUZZGEN_LEVEL_MAX; else level = effect->u.periodic.magnitude; lowest_free_buzzgen = cs40l26_get_lowest_free_buzzgen(cs40l26); dev_dbg(dev, "lowest_free_buzzgen: %d", lowest_free_buzzgen); if (lowest_free_buzzgen > CS40L26_BUZZGEN_NUM_CONFIGS) { dev_err(dev, "Unable to upload buzzgen effect\n"); return -ENOSPC; } ret = cs40l26_buzzgen_set(cs40l26, freq, level, effect->replay.length, lowest_free_buzzgen); if (ret) return ret; ueffect->id = effect->id; ueffect->wvfrm_bank = CS40L26_BUZ_BANK_ID; ueffect->trigger_index = CS40L26_BUZZGEN_INDEX_START + lowest_free_buzzgen; return ret; } static int cs40l26_custom_upload(struct cs40l26_private *cs40l26, struct ff_effect *effect, struct cs40l26_uploaded_effect *ueffect) { s16 first = cs40l26->raw_custom_data[0]; bool is_pwle = (first != CS40L26_WT_TYPE10_COMP_BUFFER); bool is_svc = (first == CS40L26_SVC_ID); struct device *dev = cs40l26->dev; u32 nwaves, min_index, max_index, trigger_index; int ret, data_len, refactored_data_len; u8 *refactored_data; u16 index, bank; data_len = effect->u.periodic.custom_len; if (data_len > CS40L26_CUSTOM_DATA_SIZE) { refactored_data_len = cs40l26_refactor_owt(cs40l26, cs40l26->raw_custom_data, data_len, is_pwle, is_svc, &refactored_data); if (refactored_data_len <= 0) { dev_err(cs40l26->dev, "Failed to refactor OWT\n"); return -ENOMEM; } ret = cs40l26_owt_upload(cs40l26, refactored_data, refactored_data_len); kfree(refactored_data); if (ret) return ret; bank = (u16) CS40L26_OWT_BANK_ID; index = (u16) cs40l26->num_owt_effects; } else { bank = (u16) first; index = (u16) (cs40l26->raw_custom_data[1] & CS40L26_MAX_INDEX_MASK); } ret = cs40l26_get_num_waves(cs40l26, &nwaves); if (ret) return ret; switch (bank) { case CS40L26_RAM_BANK_ID: if (nwaves - cs40l26->num_owt_effects == 0) { dev_err(dev, "No waveforms in RAM bank\n"); return -EINVAL; } min_index = CS40L26_RAM_INDEX_START; max_index = min_index + nwaves - cs40l26->num_owt_effects - 1; break; case CS40L26_ROM_BANK_ID: min_index = CS40L26_ROM_INDEX_START; max_index = CS40L26_ROM_INDEX_END; break; case CS40L26_OWT_BANK_ID: min_index = CS40L26_OWT_INDEX_START; max_index = CS40L26_OWT_INDEX_END; break; default: dev_err(dev, "Bank ID (%u) invalid\n", bank); return -EINVAL; } trigger_index = index + min_index; if (trigger_index < min_index || trigger_index > max_index) { dev_err(dev, "Index 0x%X out of bounds (0x%X - 0x%X)\n", trigger_index, min_index, max_index); return -EINVAL; } dev_dbg(dev, "ID = %d, trigger index = 0x%08X\n", effect->id, trigger_index); if (bank == CS40L26_OWT_BANK_ID) cs40l26->num_owt_effects++; ueffect->id = effect->id; ueffect->wvfrm_bank = bank; ueffect->trigger_index = trigger_index; return ret; } static int cs40l26_uploaded_effect_add(struct cs40l26_private *cs40l26, struct ff_effect *effect) { struct device *dev = cs40l26->dev; bool is_new = false; struct cs40l26_uploaded_effect *ueffect; int ret; ueffect = cs40l26_uploaded_effect_find(cs40l26, effect->id); if (IS_ERR_OR_NULL(ueffect)) { is_new = true; ueffect = kzalloc(sizeof(*ueffect), GFP_KERNEL); if (!ueffect) return -ENOMEM; } if (effect->u.periodic.waveform == FF_CUSTOM) { ret = cs40l26_custom_upload(cs40l26, effect, ueffect); } else if (effect->u.periodic.waveform == FF_SINE) { ret = cs40l26_sine_upload(cs40l26, effect, ueffect); } else { dev_err(dev, "Periodic waveform type 0x%X not supported\n", effect->u.periodic.waveform); ret = -EINVAL; } if (ret) goto err_free; if (effect->trigger.button) { ret = cs40l26_map_gpi_to_haptic(cs40l26, effect, ueffect); if (ret) goto err_free; } else { ueffect->mapping = CS40L26_GPIO_MAP_INVALID; } if (is_new) list_add(&ueffect->list, &cs40l26->effect_head); return 0; err_free: if (is_new) kfree(ueffect); return ret; } static void cs40l26_upload_worker(struct work_struct *work) { struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, upload_work); struct device *cdev = cs40l26->dev; struct ff_effect *effect; u32 nwaves; int ret; ret = cs40l26_pm_enter(cdev); if (ret) return; mutex_lock(&cs40l26->lock); effect = &cs40l26->upload_effect; if (effect->type != FF_PERIODIC) { dev_err(cdev, "Effect type 0x%X not supported\n", effect->type); ret = -EINVAL; goto out_mutex; } ret = cs40l26_uploaded_effect_add(cs40l26, effect); if (ret) goto out_mutex; ret = cs40l26_get_num_waves(cs40l26, &nwaves); if (ret) goto out_mutex; dev_dbg(cdev, "Total number of waveforms = %u\n", nwaves); out_mutex: mutex_unlock(&cs40l26->lock); cs40l26_pm_exit(cdev); cs40l26->upload_ret = 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); int len = effect->u.periodic.custom_len; int ret; ATRACE_BEGIN(__func__); dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect->id); memcpy(&cs40l26->upload_effect, effect, sizeof(struct ff_effect)); if (effect->u.periodic.waveform == FF_CUSTOM) { cs40l26->raw_custom_data_len = len; cs40l26->raw_custom_data = kcalloc(len, sizeof(s16), GFP_KERNEL); if (!cs40l26->raw_custom_data) { ret = -ENOMEM; goto out_free; } if (copy_from_user(cs40l26->raw_custom_data, effect->u.periodic.custom_data, sizeof(s16) * len)) { dev_err(cs40l26->dev, "Failed to get user data\n"); ret = -EFAULT; goto out_free; } } queue_work(cs40l26->vibe_workqueue, &cs40l26->upload_work); /* Wait for upload to finish */ flush_work(&cs40l26->upload_work); ret = cs40l26->upload_ret; out_free: memset(&cs40l26->upload_effect, 0, sizeof(struct ff_effect)); kfree(cs40l26->raw_custom_data); cs40l26->raw_custom_data = NULL; ATRACE_END(); return ret; } #if IS_ENABLED(CONFIG_INPUT_CS40L26_ATTR_UNDER_BUS) const struct attribute_group *cs40l26_dev_attr_groups[] = { &cs40l26_dev_attr_group, &cs40l26_dev_attr_cal_group, &cs40l26_dev_attr_dbc_group, NULL, }; #endif static int cs40l26_erase_gpi_mapping(struct cs40l26_private *cs40l26, enum cs40l26_gpio_map mapping) { int ret = 0; u32 reg; if (mapping == CS40L26_GPIO_MAP_A_PRESS) reg = CS40L26_A1_EVENT_MAP_1; else if (mapping == CS40L26_GPIO_MAP_A_RELEASE) reg = CS40L26_A1_EVENT_MAP_2; else ret = -EINVAL; if (ret) { dev_err(cs40l26->dev, "Invalid GPI mapping %u\n", mapping); return ret; } ret = regmap_write(cs40l26->regmap, reg, CS40L26_EVENT_MAP_GPI_DISABLE); if (ret) { dev_err(cs40l26->dev, "Failed to clear GPI mapping %u\n", mapping); return ret; } return 0; } static int cs40l26_erase_owt(struct cs40l26_private *cs40l26, struct cs40l26_uploaded_effect *ueffect) { u32 cmd = CS40L26_DSP_MBOX_CMD_OWT_DELETE_BASE; u32 index = ueffect->trigger_index; struct cs40l26_uploaded_effect *ueffect_tmp; int ret; cmd |= (index & 0xFF); ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd, CS40L26_DSP_MBOX_RESET); if (ret) return ret; /* Update indices for OWT waveforms uploaded after erased effect */ list_for_each_entry(ueffect_tmp, &cs40l26->effect_head, list) { if (ueffect_tmp->wvfrm_bank == CS40L26_OWT_BANK_ID && ueffect_tmp->trigger_index > index) ueffect_tmp->trigger_index--; } cs40l26->num_owt_effects--; return 0; } static void cs40l26_erase_worker(struct work_struct *work) { struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, erase_work); struct cs40l26_uploaded_effect *ueffect; int effect_id, ret; u16 duration; ret = cs40l26_pm_enter(cs40l26->dev); if (ret) return; mutex_lock(&cs40l26->lock); effect_id = cs40l26->erase_effect->id; ueffect = cs40l26_uploaded_effect_find(cs40l26, effect_id); if (IS_ERR_OR_NULL(ueffect)) { dev_err(cs40l26->dev, "No such effect to erase (%d)\n", effect_id); ret = PTR_ERR(ueffect); goto out_mutex; } duration = (cs40l26->erase_effect->replay.length == 0) ? CS40L26_MAX_WAIT_VIBE_COMPLETE_MS : cs40l26->erase_effect->replay.length + CS40L26_ERASE_BUFFER_MS; /* Check for ongoing effect playback. */ if (cs40l26->vibe_state == CS40L26_VIBE_STATE_HAPTIC) { /* Wait for effect to complete. */ mutex_unlock(&cs40l26->lock); if (!wait_for_completion_timeout(&cs40l26->erase_cont, msecs_to_jiffies(duration))) { ret = -ETIME; dev_err(cs40l26->dev, "Failed to erase effect (%d)\n", effect_id); goto pm_err; } mutex_lock(&cs40l26->lock); } dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id); if (ueffect->mapping != CS40L26_GPIO_MAP_INVALID) { ret = cs40l26_erase_gpi_mapping(cs40l26, ueffect->mapping); if (ret) goto out_mutex; ueffect->mapping = CS40L26_GPIO_MAP_INVALID; } if (ueffect->wvfrm_bank == CS40L26_OWT_BANK_ID) ret = cs40l26_erase_owt(cs40l26, ueffect); if (ret) { dev_err(cs40l26->dev, "Failed to erase effect: %d", ret); goto out_mutex; } list_del(&ueffect->list); kfree(ueffect); out_mutex: mutex_unlock(&cs40l26->lock); pm_err: cs40l26_pm_exit(cs40l26->dev); cs40l26->erase_ret = ret; } static int cs40l26_erase_effect(struct input_dev *dev, int effect_id) { struct cs40l26_private *cs40l26 = input_get_drvdata(dev); struct ff_effect *effect; ATRACE_BEGIN(__func__); dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id); effect = &dev->ff->effects[effect_id]; if (!effect) { dev_err(cs40l26->dev, "No such effect to erase\n"); return -EINVAL; } cs40l26->erase_effect = effect; queue_work(cs40l26->vibe_workqueue, &cs40l26->erase_work); /* Wait for erase to finish */ flush_work(&cs40l26->erase_work); ATRACE_END(); return cs40l26->erase_ret; } static int cs40l26_input_init(struct cs40l26_private *cs40l26) { struct device *dev = cs40l26->dev; int ret; cs40l26->input = devm_input_allocate_device(dev); if (!cs40l26->input) return -ENOMEM; cs40l26->input->name = cs40l26->pdata.device_name; 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; cs40l26->input->ff->erase = cs40l26_erase_effect; ret = input_register_device(cs40l26->input); if (ret) { dev_err(dev, "Cannot register input device: %d\n", ret); return ret; } #if !IS_ENABLED(CONFIG_INPUT_CS40L26_ATTR_UNDER_BUS) 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); return ret; } ret = sysfs_create_group(&cs40l26->input->dev.kobj, &cs40l26_dev_attr_cal_group); if (ret) { dev_err(dev, "Failed to create cal sysfs group: %d\n", ret); return ret; } ret = sysfs_create_group(&cs40l26->input->dev.kobj, &cs40l26_dev_attr_dbc_group); if (ret) { dev_err(dev, "Failed to create DBC sysfs group\n"); return ret; } #else ret = sysfs_create_groups(&cs40l26->dev->kobj, cs40l26_dev_attr_groups); if (ret) { dev_err(dev, "Failed to create sysfs groups: %d\n", ret); return ret; } #endif 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 && val != CS40L26_DEVID_L27_A && val != CS40L26_DEVID_L27_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; if (val == CS40L26_REVID_A1 || val == CS40L26_REVID_B0) { cs40l26->revid = val; } else { 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_wksrc_config(struct cs40l26_private *cs40l26) { u8 mask_wksrc; u32 val, mask; if (cs40l26->devid == CS40L26_DEVID_A || cs40l26->devid == CS40L26_DEVID_L27_A) mask_wksrc = 1; else mask_wksrc = 0; val = BIT(CS40L26_IRQ1_WKSRC_STS_SPI) | (mask_wksrc << CS40L26_IRQ1_WKSRC_STS_GPIO2) | (mask_wksrc << CS40L26_IRQ1_WKSRC_STS_GPIO3) | (mask_wksrc << CS40L26_IRQ1_WKSRC_STS_GPIO4); mask = BIT(CS40L26_IRQ1_WKSRC_STS_ANY) | BIT(CS40L26_IRQ1_WKSRC_STS_GPIO1) | BIT(CS40L26_IRQ1_WKSRC_STS_I2C) | BIT(CS40L26_IRQ1_WKSRC_STS_SPI) | BIT(CS40L26_IRQ1_WKSRC_STS_GPIO2) | BIT(CS40L26_IRQ1_WKSRC_STS_GPIO3) | BIT(CS40L26_IRQ1_WKSRC_STS_GPIO4); return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, val, mask); } static int cs40l26_gpio_config(struct cs40l26_private *cs40l26) { u32 val, mask; u8 mask_gpio; int ret; if (cs40l26->devid == CS40L26_DEVID_A || cs40l26->devid == CS40L26_DEVID_L27_A) mask_gpio = 1; else mask_gpio = 0; ret = cl_dsp_get_reg(cs40l26->dsp, "ENT_MAP_TABLE_EVENT_DATA_PACKED", CL_DSP_XM_UNPACKED_TYPE, CS40L26_EVENT_HANDLER_ALGO_ID, &cs40l26->event_map_base); if (ret) return ret; mask = (u32) (GENMASK(CS40L26_IRQ1_GPIO4_FALL, CS40L26_IRQ1_GPIO1_RISE)); if (mask_gpio) #if IS_ENABLED(CONFIG_GOOG_CUST) /* Extend the GPIO trigger mask to ignore GPIO1 falling edge */ val = (u32) GENMASK(CS40L26_IRQ1_GPIO4_FALL, CS40L26_IRQ1_GPIO1_FALL); #else val = (u32) GENMASK(CS40L26_IRQ1_GPIO4_FALL, CS40L26_IRQ1_GPIO2_RISE); #endif else val = 0; ret = regmap_write(cs40l26->regmap, CS40L26_A1_EVENT_MAP_1, cs40l26->pdata.press_idx); if (ret) { dev_err(cs40l26->dev, "Failed to map press GPI event\n"); return ret; } ret = regmap_write(cs40l26->regmap, CS40L26_A1_EVENT_MAP_2, cs40l26->pdata.release_idx); if (ret) { dev_err(cs40l26->dev, "Failed to map release GPI event\n"); return ret; } return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, val, mask); } static int cs40l26_brownout_prevention_init(struct cs40l26_private *cs40l26) { struct device *dev = cs40l26->dev; struct regmap *regmap = cs40l26->regmap; u32 vpbr_atk_step = 0, vbbr_atk_step = 0; u32 vpbr_atk_rate = 0, vbbr_atk_rate = 0; u32 vpbr_rel_rate = 0, vbbr_rel_rate = 0; u32 vbbr_max_att = 0, vpbr_max_att = 0; u32 vbbr_thld = 0, vpbr_thld = 0; u32 vpbr_wait = 0, vbbr_wait = 0; u32 pseq_val = 0, pseq_mask = 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; } ret = cs40l26_pseq_write(cs40l26, CS40L26_BLOCK_ENABLES2, val, true, CS40L26_PSEQ_OP_WRITE_FULL); if (ret) { dev_err(dev, "Failed to sequence brownout prevention\n"); return ret; } if (cs40l26->pdata.vbbr_en) { pseq_mask |= BIT(CS40L26_IRQ2_VBBR_ATT_CLR) | BIT(CS40L26_IRQ2_VBBR_FLAG); 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_mv) { if (cs40l26->pdata.vbbr_thld_mv >= CS40L26_VBBR_THLD_MV_MAX) vbbr_thld = CS40L26_VBBR_THLD_MAX; else if (cs40l26->pdata.vbbr_thld_mv <= CS40L26_VBBR_THLD_MV_MIN) vbbr_thld = CS40L26_VBBR_THLD_MIN; else vbbr_thld = cs40l26->pdata.vbbr_thld_mv / 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; } ret = cs40l26_pseq_write(cs40l26, CS40L26_VBBR_CONFIG, (val & GENMASK(31, 16)) >> 16, true, CS40L26_PSEQ_OP_WRITE_H16); if (ret) return ret; ret = cs40l26_pseq_write(cs40l26, CS40L26_VBBR_CONFIG, (val & GENMASK(15, 0)), true, CS40L26_PSEQ_OP_WRITE_L16); 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) { pseq_mask |= BIT(CS40L26_IRQ2_VPBR_ATT_CLR) | BIT(CS40L26_IRQ2_VPBR_FLAG); if (cs40l26->pdata.vpbr_thld_mv) { if (cs40l26->pdata.vpbr_thld_mv >= CS40L26_VPBR_THLD_MV_MAX) { vpbr_thld = CS40L26_VPBR_THLD_MAX; } else if (cs40l26->pdata.vpbr_thld_mv <= CS40L26_VPBR_THLD_MV_MIN) { vpbr_thld = CS40L26_VPBR_THLD_MIN; } else { vpbr_thld = (cs40l26->pdata.vpbr_thld_mv / CS40L26_VPBR_THLD_MV_DIV) - CS40L26_VPBR_THLD_OFFSET; } cs40l26->vpbr_thld = vpbr_thld & CS40L26_VPBR_THLD_MASK; 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; } ret = cs40l26_pseq_write(cs40l26, CS40L26_VPBR_CONFIG, (val & GENMASK(31, 16)) >> 16, true, CS40L26_PSEQ_OP_WRITE_H16); if (ret) return ret; ret = cs40l26_pseq_write(cs40l26, CS40L26_VPBR_CONFIG, (val & GENMASK(15, 0)), true, CS40L26_PSEQ_OP_WRITE_L16); if (ret) return ret; } return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_2, pseq_val, pseq_mask); } 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; } ret = cs40l26_pseq_multi_write(cs40l26, dsp1rx_config, 2, true, CS40L26_PSEQ_OP_WRITE_L16); err_free: kfree(dsp1rx_config); return ret; } static int cs40l26_bst_dcm_config(struct cs40l26_private *cs40l26) { int ret = 0; u32 val; if (cs40l26->pdata.bst_dcm_en != CS40L26_BST_DCM_EN_DEFAULT) { ret = regmap_read(cs40l26->regmap, CS40L26_BST_DCM_CTL, &val); if (ret) { dev_err(cs40l26->dev, "Failed to read BST_DCM_CTL\n"); return ret; } val &= ~CS40L26_BST_DCM_EN_MASK; val |= cs40l26->pdata.bst_dcm_en << CS40L26_BST_DCM_EN_SHIFT; ret = regmap_write(cs40l26->regmap, CS40L26_BST_DCM_CTL, val); if (ret) { dev_err(cs40l26->dev, "Failed to write BST_DCM_CTL\n"); return ret; } ret = cs40l26_pseq_write(cs40l26, CS40L26_BST_DCM_CTL, val, true, CS40L26_PSEQ_OP_WRITE_FULL); } return ret; } static int cs40l26_zero_cross_config(struct cs40l26_private *cs40l26) { int ret = 0; u32 reg; if (cs40l26->pdata.pwle_zero_cross) { ret = cl_dsp_get_reg(cs40l26->dsp, "PWLE_EXTEND_ZERO_CROSS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) return ret; ret = regmap_write(cs40l26->regmap, reg, 1); if (ret) dev_err(cs40l26->dev, "Failed to set PWLE_EXTEND_ZERO_CROSS\n"); } return ret; } static int calib_device_tree_config(struct cs40l26_private *cs40l26) { int ret = 0; u32 reg, bst_ctl, bst_ctl_cfg; if (cs40l26->pdata.f0_default <= CS40L26_F0_EST_MAX && cs40l26->pdata.f0_default >= CS40L26_F0_EST_MIN) { ret = cl_dsp_get_reg(cs40l26->dsp, "F0_OTP_STORED", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) return ret; ret = regmap_write(cs40l26->regmap, reg, cs40l26->pdata.f0_default); if (ret) { dev_err(cs40l26->dev, "Failed to write default f0\n"); return ret; } } if (cs40l26->pdata.redc_default && cs40l26->pdata.redc_default <= CS40L26_UINT_24_BITS_MAX) { ret = cl_dsp_get_reg(cs40l26->dsp, "REDC_OTP_STORED", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) return ret; ret = regmap_write(cs40l26->regmap, reg, cs40l26->pdata.redc_default); if (ret) { dev_err(cs40l26->dev, "Failed to write default ReDC\n"); return ret; } } if (cs40l26->pdata.q_default <= CS40L26_Q_EST_MAX && cs40l26->pdata.q_default >= CS40L26_Q_EST_MIN) { ret = cl_dsp_get_reg(cs40l26->dsp, "Q_STORED", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) return ret; ret = regmap_write(cs40l26->regmap, reg, cs40l26->pdata.q_default); if (ret) { dev_err(cs40l26->dev, "Failed to write default Q\n"); return ret; } } if (cs40l26->pdata.boost_ctl <= CS40L26_BST_VOLT_MAX && cs40l26->pdata.boost_ctl >= CS40L26_BST_VOLT_MIN) { bst_ctl = ((cs40l26->pdata.boost_ctl - CS40L26_BST_VOLT_MIN) / CS40L26_BST_VOLT_STEP) + 1; ret = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_1, bst_ctl); if (ret) { dev_err(cs40l26->dev, "Failed to write VBST limit\n"); return ret; } ret = cs40l26_pseq_write(cs40l26, CS40L26_VBST_CTL_1, bst_ctl, true, CS40L26_PSEQ_OP_WRITE_L16); if (ret) return ret; ret = regmap_read(cs40l26->regmap, CS40L26_VBST_CTL_2, &bst_ctl_cfg); if (ret) { dev_err(cs40l26->dev, "Failed to get VBST config\n"); return ret; } bst_ctl_cfg |= (1 << CS40L26_BST_CTL_LIM_EN_SHIFT); ret = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_2, bst_ctl_cfg); if (ret) { dev_err(cs40l26->dev, "Failed to write VBST config\n"); return ret; } ret = cs40l26_pseq_write(cs40l26, CS40L26_VBST_CTL_2, bst_ctl_cfg, true, CS40L26_PSEQ_OP_WRITE_FULL); } return ret; } static int cs40l26_bst_ipk_config(struct cs40l26_private *cs40l26) { u32 val, bst_ipk_ma = cs40l26->pdata.bst_ipk / MILLIAMPS_PER_AMPS; int ret; if (bst_ipk_ma < CS40L26_BST_IPK_MILLIAMP_MIN || bst_ipk_ma > CS40L26_BST_IPK_MILLIAMP_MAX) { val = CS40L26_BST_IPK_DEFAULT; dev_dbg(cs40l26->dev, "Using default BST_IPK\n"); } else { val = (bst_ipk_ma / CS40L26_BST_IPK_CTL_STEP_SIZE) - CS40L26_BST_IPK_CTL_RESERVED; } ret = regmap_write(cs40l26->regmap, CS40L26_BST_IPK_CTL, val); if (ret) { dev_err(cs40l26->dev, "Failed to update BST peak current\n"); return ret; } ret = cs40l26_pseq_write(cs40l26, CS40L26_BST_IPK_CTL, val, true, CS40L26_PSEQ_OP_WRITE_L16); if (ret) return ret; return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, 0, BIT(CS40L26_IRQ1_BST_IPK_FLAG)); } static int cs40l26_lbst_short_test(struct cs40l26_private *cs40l26) { struct regmap *regmap = cs40l26->regmap; struct device *dev = cs40l26->dev; unsigned int err; int ret; ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_2, CS40L26_BST_CTL_SEL_MASK, CS40L26_BST_CTL_SEL_FIXED); if (ret) { dev_err(dev, "Failed to set VBST_CTL_2\n"); return ret; } ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_1, CS40L26_BST_CTL_MASK, CS40L26_BST_CTL_VP); if (ret) { dev_err(dev, "Failed to set VBST_CTL_1\n"); return ret; } /* Set GLOBAL_EN; safe because DSP is guaranteed to be off here */ ret = regmap_update_bits(regmap, CS40L26_GLOBAL_ENABLES, CS40L26_GLOBAL_EN_MASK, 1); if (ret) { dev_err(dev, "Failed to set GLOBAL_EN\n"); return ret; } /* Wait until boost converter is guaranteed to be powered up */ usleep_range(CS40L26_BST_TIME_MIN_US, CS40L26_BST_TIME_MAX_US); ret = regmap_read(regmap, CS40L26_ERROR_RELEASE, &err); if (ret) { dev_err(dev, "Failed to get ERROR_RELEASE contents\n"); return ret; } if (err & BIT(CS40L26_BST_SHORT_ERR_RLS)) { dev_err(dev, "FATAL: Boost shorted at startup\n"); return ret; } /* Clear GLOBAL_EN; safe because DSP is guaranteed to be off here */ ret = regmap_update_bits(regmap, CS40L26_GLOBAL_ENABLES, CS40L26_GLOBAL_EN_MASK, 0); if (ret) { dev_err(dev, "Failed to clear GLOBAL_EN\n"); return ret; } ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_2, CS40L26_BST_CTL_SEL_MASK, CS40L26_BST_CTL_SEL_CLASS_H); if (ret) { dev_err(dev, "Failed to set VBST_CTL_2\n"); return ret; } ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_1, CS40L26_BST_CTL_MASK, CS40L26_BST_CTL_VP); if (ret) dev_err(dev, "Failed to set VBST_CTL_1\n"); return ret; } static int cs40l26_handle_errata(struct cs40l26_private *cs40l26) { int ret, num_writes; if (!cs40l26->pdata.expl_mode_enabled) { ret = cs40l26_lbst_short_test(cs40l26); if (ret) return ret; num_writes = CS40L26_ERRATA_A1_NUM_WRITES; } else { num_writes = CS40L26_ERRATA_A1_EXPL_EN_NUM_WRITES; } return cs40l26_pseq_multi_write(cs40l26, cs40l26_a1_errata, num_writes, false, CS40L26_PSEQ_OP_WRITE_FULL); } int cs40l26_dbc_enable(struct cs40l26_private *cs40l26, u32 enable) { unsigned int reg; int ret; ret = cl_dsp_get_reg(cs40l26->dsp, "FLAGS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); if (ret) return ret; ret = regmap_update_bits(cs40l26->regmap, reg, CS40L26_DBC_ENABLE_MASK, enable << CS40L26_DBC_ENABLE_SHIFT); if (ret) dev_err(cs40l26->dev, "Failed to %s DBC\n", (enable == 1) ? "enable" : "disable"); return ret; } EXPORT_SYMBOL(cs40l26_dbc_enable); static int cs40l26_handle_dbc_defaults(struct cs40l26_private *cs40l26) { unsigned int i; u32 val; int ret; for (i = 0; i < CS40L26_DBC_NUM_CONTROLS; i++) { val = cs40l26->pdata.dbc_defaults[i]; if (val != CS40L26_DBC_USE_DEFAULT) { ret = cs40l26_dbc_set(cs40l26, i, val); if (ret) return ret; } } if (cs40l26->pdata.dbc_enable_default) { ret = cs40l26_dbc_enable(cs40l26, 1); if (ret) return ret; } return 0; } static int cs40l26_dsp_config(struct cs40l26_private *cs40l26) { struct regmap *regmap = cs40l26->regmap; struct device *dev = cs40l26->dev; unsigned int val; u32 reg, nwaves, value; int ret; ret = regmap_update_bits(regmap, CS40L26_PWRMGT_CTL, CS40L26_MEM_RDY_MASK, 1 << CS40L26_MEM_RDY_SHIFT); if (ret) { dev_err(dev, "Failed to set MEM_RDY to initialize RAM\n"); return ret; } ret = cl_dsp_get_reg(cs40l26->dsp, "CALL_RAM_INIT", CL_DSP_XM_UNPACKED_TYPE, cs40l26->fw_id, ®); if (ret) return ret; ret = cs40l26_dsp_write(cs40l26, reg, 1); if (ret) return ret; cs40l26->fw_loaded = true; #ifdef CONFIG_DEBUG_FS cs40l26_debugfs_init(cs40l26); #endif ret = cs40l26_pseq_init(cs40l26); if (ret) return ret; ret = cs40l26_update_reg_defaults_via_pseq(cs40l26); if (ret) return ret; ret = cs40l26_handle_errata(cs40l26); if (ret) return ret; ret = cs40l26_dsp_start(cs40l26); if (ret) return ret; ret = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_PREVENT_HIBERNATE); if (ret) return ret; /* ensure firmware running */ ret = cl_dsp_get_reg(cs40l26->dsp, "HALO_STATE", CL_DSP_XM_UNPACKED_TYPE, cs40l26->fw_id, ®); if (ret) return ret; ret = regmap_read(regmap, reg, &val); if (ret) { dev_err(dev, "Failed to read HALO_STATE\n"); return ret; } if (val != CS40L26_DSP_HALO_STATE_RUN) { dev_err(dev, "Firmware in unexpected state: 0x%X\n", val); return ret; } ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, 0, BIT(CS40L26_IRQ1_AMP_ERR) | BIT(CS40L26_IRQ1_TEMP_ERR) | BIT(CS40L26_IRQ1_BST_SHORT_ERR) | BIT(CS40L26_IRQ1_BST_DCM_UVP_ERR) | BIT(CS40L26_IRQ1_BST_OVP_ERR) | BIT(CS40L26_IRQ1_VIRTUAL2_MBOX_WR)); if (ret) return ret; ret = cs40l26_wksrc_config(cs40l26); if (ret) return ret; ret = cs40l26_gpio_config(cs40l26); if (ret) return ret; ret = cs40l26_bst_dcm_config(cs40l26); if (ret) return ret; ret = cs40l26_bst_ipk_config(cs40l26); if (ret) return ret; ret = cs40l26_handle_dbc_defaults(cs40l26); if (ret) return ret; ret = cs40l26_zero_cross_config(cs40l26); if (ret) return ret; if (!cs40l26->vibe_init_success) { ret = calib_device_tree_config(cs40l26); if (ret) return ret; } ret = cs40l26_brownout_prevention_init(cs40l26); if (ret) return ret; cs40l26_pm_runtime_setup(cs40l26); ret = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_ALLOW_HIBERNATE); if (ret) return ret; ret = cs40l26_pm_enter(dev); if (ret) return ret; ret = cl_dsp_get_reg(cs40l26->dsp, "TIMEOUT_MS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) goto pm_err; ret = regmap_write(regmap, reg, 0); if (ret) { dev_err(dev, "Failed to set TIMEOUT_MS\n"); goto pm_err; } ret = cs40l26_asp_config(cs40l26); if (ret) goto pm_err; ret = cs40l26_get_num_waves(cs40l26, &nwaves); if (ret) goto pm_err; dev_info(dev, "%s loaded with %u RAM waveforms\n", CS40L26_DEV_NAME, nwaves); cs40l26->num_owt_effects = 0; value = (cs40l26->comp_enable_redc << CS40L26_COMP_EN_REDC_SHIFT) | (cs40l26->comp_enable_f0 << CS40L26_COMP_EN_F0_SHIFT); if (cs40l26->fw_id != CS40L26_FW_CALIB_ID) { ret = cl_dsp_get_reg(cs40l26->dsp, "COMPENSATION_ENABLE", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); if (ret) goto pm_err; ret = regmap_write(cs40l26->regmap, reg, value); if (ret) dev_err(dev, "Failed to configure compensation\n"); } pm_err: cs40l26_pm_exit(dev); return ret; } static void cs40l26_gain_adjust(struct cs40l26_private *cs40l26, s32 adjust) { u16 total, asp, change; asp = cs40l26->pdata.asp_scale_pct; if (adjust < 0) { change = (u16) ((adjust * -1) & 0xFFFF); if (asp < change) total = 0; else total = asp - change; } else { change = (u16) (adjust & 0xFFFF); total = asp + change; if (total > CS40L26_GAIN_FULL_SCALE) total = CS40L26_GAIN_FULL_SCALE; } cs40l26->pdata.asp_scale_pct = total; } int cs40l26_svc_le_estimate(struct cs40l26_private *cs40l26, unsigned int *le) { struct device *dev = cs40l26->dev; unsigned int reg, le_est = 0; int ret, i; ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, CS40L26_DSP_MBOX_CMD_LE_EST, CS40L26_DSP_MBOX_RESET); if (ret) return ret; ret = cl_dsp_get_reg(cs40l26->dsp, "LE_EST_STATUS", CL_DSP_YM_UNPACKED_TYPE, CS40L26_SVC_ALGO_ID, ®); if (ret) return ret; for (i = 0; i < CS40L26_SVC_LE_MAX_ATTEMPTS; i++) { usleep_range(CS40L26_SVC_LE_EST_TIME_US, CS40L26_SVC_LE_EST_TIME_US + 100); ret = regmap_read(cs40l26->regmap, reg, &le_est); if (ret) { dev_err(dev, "Failed to get LE_EST_STATUS\n"); return ret; } dev_info(dev, "Measured Le Estimation = %u\n", le_est); if (le_est) break; } *le = le_est; return 0; } EXPORT_SYMBOL(cs40l26_svc_le_estimate); static int cs40l26_tuning_select_from_svc_le(struct cs40l26_private *cs40l26, unsigned int le, u32 *tuning_num) { int ret = 0; int i; if (le) { for (i = 0; i < cs40l26->num_svc_le_vals; i++) { if (le >= cs40l26->svc_le_vals[i]->min && le <= cs40l26->svc_le_vals[i]->max) { *tuning_num = cs40l26->svc_le_vals[i]->n; cs40l26_gain_adjust(cs40l26, cs40l26->svc_le_vals[i]->gain_adjust); break; } } } if (!le || i == cs40l26->num_svc_le_vals) dev_warn(cs40l26->dev, "Using default tunings\n"); return ret; } static char **cs40l26_get_tuning_names(struct cs40l26_private *cs40l26, int *actual_num_files, u32 tuning) { char **coeff_files; int i, file_count = 0; coeff_files = kcalloc( CS40L26_MAX_TUNING_FILES, sizeof(char *), GFP_KERNEL); if (!coeff_files) return ERR_PTR(-ENOMEM); for (i = 0; i < CS40L26_MAX_TUNING_FILES; i++) { coeff_files[i] = kzalloc(CS40L26_TUNING_FILE_NAME_MAX_LEN, GFP_KERNEL); if (!coeff_files[i]) goto err_free; } if (tuning) { snprintf(coeff_files[file_count++], CS40L26_TUNING_FILE_NAME_MAX_LEN, "%s%d%s", CS40L26_WT_FILE_PREFIX, tuning, CS40L26_TUNING_FILE_SUFFIX); } else { strscpy(coeff_files[file_count++], CS40L26_WT_FILE_NAME, CS40L26_TUNING_FILE_NAME_MAX_LEN); } if (tuning) { snprintf(coeff_files[file_count++], CS40L26_TUNING_FILE_NAME_MAX_LEN, "%s%d%s", CS40L26_SVC_TUNING_FILE_PREFIX, tuning, CS40L26_TUNING_FILE_SUFFIX); } else { strscpy(coeff_files[file_count++], CS40L26_SVC_TUNING_FILE_NAME, CS40L26_TUNING_FILE_NAME_MAX_LEN); } if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_DVL_ALGO_ID)) strscpy(coeff_files[file_count++], CS40L26_DVL_FILE_NAME, CS40L26_TUNING_FILE_NAME_MAX_LEN); if (cs40l26->fw_id == CS40L26_FW_ID) { if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_A2H_ALGO_ID)) strscpy(coeff_files[file_count++], CS40L26_A2H_TUNING_FILE_NAME, CS40L26_TUNING_FILE_NAME_MAX_LEN); } else { strscpy(coeff_files[file_count++], CS40L26_CALIB_BIN_FILE_NAME, CS40L26_TUNING_FILE_NAME_MAX_LEN); } *actual_num_files = file_count; return coeff_files; err_free: for (; i >= 0; i--) kfree(coeff_files[i]); kfree(coeff_files); *actual_num_files = 0; return ERR_PTR(-ENOMEM); } static int cs40l26_coeff_load(struct cs40l26_private *cs40l26, u32 tuning) { struct device *dev = cs40l26->dev; const struct firmware *coeff; int i, ret, num_files_to_load; char **coeff_files; coeff_files = cs40l26_get_tuning_names( cs40l26, &num_files_to_load, tuning); if (IS_ERR(coeff_files)) return PTR_ERR(coeff_files); for (i = 0; i < num_files_to_load; i++) { ret = request_firmware(&coeff, coeff_files[i], dev); if (ret) { dev_warn(dev, "Continuing...\n"); continue; } ret = cl_dsp_coeff_file_parse(cs40l26->dsp, coeff); if (ret) dev_warn(dev, "Failed to load %s, %d. Continuing...\n", coeff_files[i], ret); else dev_info(dev, "%s Loaded Successfully\n", coeff_files[i]); release_firmware(coeff); } kfree(coeff_files); return 0; } static int cs40l26_change_fw_control_defaults(struct cs40l26_private *cs40l26) { int ret; ret = cs40l26_pm_stdby_timeout_ms_set(cs40l26, cs40l26->pdata.pm_stdby_timeout_ms); if (ret) return ret; return cs40l26_pm_active_timeout_ms_set(cs40l26, cs40l26->pdata.pm_active_timeout_ms); } static int cs40l26_get_fw_params(struct cs40l26_private *cs40l26) { u32 id, min_rev, rev, branch; int ret, maj, min, patch; ret = cl_dsp_fw_rev_get(cs40l26->dsp, &rev); if (ret) return ret; branch = CL_DSP_GET_MAJOR(rev); maj = (int) branch; min = (int) CL_DSP_GET_MINOR(rev); patch = (int) CL_DSP_GET_PATCH(rev); ret = cl_dsp_fw_id_get(cs40l26->dsp, &id); if (ret) return ret; switch (id) { case CS40L26_FW_ID: if (branch == CS40L26_FW_BRANCH) { min_rev = CS40L26_FW_MIN_REV; cs40l26->vibe_state_reporting = true; } else if (branch == CS40L26_FW_MAINT_BRANCH) { min_rev = CS40L26_FW_MAINT_MIN_REV; cs40l26->vibe_state_reporting = false; } else { ret = -EINVAL; } break; case CS40L26_FW_CALIB_ID: if (branch == CS40L26_FW_CALIB_BRANCH) { min_rev = CS40L26_FW_CALIB_MIN_REV; cs40l26->vibe_state_reporting = true; } else if (branch == CS40L26_FW_MAINT_CALIB_BRANCH) { min_rev = CS40L26_FW_MAINT_CALIB_MIN_REV; cs40l26->vibe_state_reporting = false; } else { ret = -EINVAL; } break; default: dev_err(cs40l26->dev, "Invalid FW ID: 0x%06X\n", id); return -EINVAL; } if (ret) { dev_err(cs40l26->dev, "Rev. Branch 0x%02X invalid\n", maj); return ret; } if (rev < min_rev) { dev_err(cs40l26->dev, "Invalid firmware revision: %d.%d.%d\n", maj, min, patch); return -EINVAL; } cs40l26->fw_id = id; dev_info(cs40l26->dev, "Firmware revision %d.%d.%d\n", maj, min, patch); return 0; } static int cs40l26_cl_dsp_reinit(struct cs40l26_private *cs40l26) { int ret; if (cs40l26->dsp) { ret = cl_dsp_destroy(cs40l26->dsp); if (ret) { dev_err(cs40l26->dev, "Failed to destroy DSP struct\n"); return ret; } cs40l26->dsp = NULL; } cs40l26->dsp = cl_dsp_create(cs40l26->dev, cs40l26->regmap); if (IS_ERR(cs40l26->dsp)) return PTR_ERR(cs40l26->dsp); return cl_dsp_wavetable_create(cs40l26->dsp, CS40L26_VIBEGEN_ALGO_ID, CS40L26_WT_NAME_XM, CS40L26_WT_NAME_YM, CS40L26_WT_FILE_NAME); } static int cs40l26_fw_upload(struct cs40l26_private *cs40l26) { bool svc_le_required = cs40l26->num_svc_le_vals && !cs40l26->calib_fw; struct device *dev = cs40l26->dev; u32 rev, branch, tuning_num = 0; const struct firmware *fw; int ret; unsigned int le = 0; cs40l26->fw_loaded = false; ret = cs40l26_cl_dsp_reinit(cs40l26); if (ret) return ret; if (cs40l26->calib_fw) ret = request_firmware(&fw, CS40L26_FW_CALIB_NAME, dev); else ret = request_firmware(&fw, CS40L26_FW_FILE_NAME, dev); if (ret) { release_firmware(fw); return ret; } ret = cs40l26_dsp_pre_config(cs40l26); if (ret) return ret; ret = cl_dsp_firmware_parse(cs40l26->dsp, fw, true); release_firmware(fw); if (ret) return ret; ret = cs40l26_change_fw_control_defaults(cs40l26); if (ret) return ret; ret = cs40l26_get_fw_params(cs40l26); if (ret) return ret; if (svc_le_required) { ret = cl_dsp_fw_rev_get(cs40l26->dsp, &rev); if (ret) return ret; branch = CL_DSP_GET_MAJOR(rev); switch (branch) { case CS40L26_FW_MAINT_BRANCH: ret = cs40l26_dsp_config(cs40l26); if (ret) return ret; ret = cs40l26_pm_enter(dev); if (ret) return ret; ret = cs40l26_svc_le_estimate(cs40l26, &le); if (ret) dev_warn(dev, "svc_le est failed, %d", ret); cs40l26_pm_exit(dev); cs40l26_pm_runtime_teardown(cs40l26); ret = cs40l26_dsp_pre_config(cs40l26); if (ret) return ret; break; case CS40L26_FW_BRANCH: le = cs40l26->svc_le_est_stored; break; default: dev_err(dev, "Invalid firmware branch, %d", branch); return -EINVAL; } ret = cs40l26_tuning_select_from_svc_le(cs40l26, le, &tuning_num); if (ret) return ret; } ret = cs40l26_coeff_load(cs40l26, tuning_num); if (ret) return ret; return cs40l26_dsp_config(cs40l26); } int cs40l26_fw_swap(struct cs40l26_private *cs40l26, const u32 id) { struct device *dev = cs40l26->dev; bool re_enable = false; int ret = 0; if (cs40l26->revid != CS40L26_REVID_A1 && cs40l26->revid != CS40L26_REVID_B0) { dev_err(dev, "pseq unrecognized revid: %d\n", cs40l26->revid); return -EINVAL; } if (cs40l26->fw_loaded) { disable_irq(cs40l26->irq); cs40l26_pm_runtime_teardown(cs40l26); re_enable = true; } /* reset pseq END_OF_SCRIPT to location from ROM */ ret = cs40l26_dsp_write(cs40l26, CS40L26_PSEQ_ROM_END_OF_SCRIPT, CS40L26_PSEQ_OP_END << CS40L26_PSEQ_OP_SHIFT); if (ret) { dev_err(dev, "Failed to reset pseq END_OF_SCRIPT %d\n", ret); return ret; } if (id == CS40L26_FW_CALIB_ID) cs40l26->calib_fw = true; else cs40l26->calib_fw = false; ret = cs40l26_fw_upload(cs40l26); if (ret) return ret; if (cs40l26->fw_defer && cs40l26->fw_loaded) { 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: %d\n", ret); return ret; } cs40l26->fw_defer = false; } if (re_enable) enable_irq(cs40l26->irq); return ret; } EXPORT_SYMBOL(cs40l26_fw_swap); static int cs40l26_handle_svc_le_nodes(struct cs40l26_private *cs40l26) { struct device *dev = cs40l26->dev; int i, ret = 0, init_count, node_count = 0; struct fwnode_handle *child; unsigned int min, max, index; const char *gain_adjust_str; const char *node_name; s32 gain_adjust; init_count = device_get_child_node_count(dev); if (!init_count) return 0; cs40l26->svc_le_vals = devm_kcalloc(dev, init_count, sizeof(struct cs40l26_svc_le *), GFP_KERNEL); if (!cs40l26->svc_le_vals) return -ENOMEM; device_for_each_child_node(dev, child) { node_name = fwnode_get_name(child); if (strncmp(node_name, CS40L26_SVC_DT_PREFIX, 6)) continue; if (fwnode_property_read_u32(child, "cirrus,min", &min)) { dev_err(dev, "No minimum value for SVC LE node\n"); continue; } if (fwnode_property_read_u32(child, "cirrus,max", &max)) { dev_err(dev, "No maximum value for SVC LE node\n"); continue; } if (max <= min) { dev_err(dev, "Max <= Min, SVC LE node malformed\n"); continue; } if (fwnode_property_read_string(child, "cirrus,gain-adjust", &gain_adjust_str)) { gain_adjust = 0; } else { ret = kstrtos32(gain_adjust_str, 10, &gain_adjust); if (ret) { dev_warn(dev, "Failed to get gain adjust\n"); gain_adjust = 0; } } if (fwnode_property_read_u32(child, "cirrus,index", &index)) { dev_err(dev, "No index specified for SVC LE node\n"); continue; } for (i = 0; i < node_count; i++) { if (index == cs40l26->svc_le_vals[i]->n) break; } if (i < node_count) { dev_err(dev, "SVC LE nodes must have unique index\n"); return -EINVAL; } cs40l26->svc_le_vals[node_count] = devm_kzalloc(dev, sizeof(struct cs40l26_svc_le), GFP_KERNEL); if (!cs40l26->svc_le_vals[node_count]) { ret = -ENOMEM; goto err; } cs40l26->svc_le_vals[node_count]->min = min; cs40l26->svc_le_vals[node_count]->max = max; cs40l26->svc_le_vals[node_count]->gain_adjust = gain_adjust; cs40l26->svc_le_vals[node_count]->n = index; node_count++; } if (node_count != init_count) dev_warn(dev, "%d platform nodes unused for SVC LE\n", init_count - node_count); return node_count; err: devm_kfree(dev, cs40l26->svc_le_vals); return ret; } static int cs40l26_no_wait_ram_indices_get(struct cs40l26_private *cs40l26, struct device_node *np) { int ret, i; cs40l26->num_no_wait_ram_indices = of_property_count_u32_elems(np, "cirrus,no-wait-ram-indices"); if (cs40l26->num_no_wait_ram_indices <= 0) return 0; cs40l26->no_wait_ram_indices = devm_kcalloc(cs40l26->dev, cs40l26->num_no_wait_ram_indices, sizeof(u32), GFP_KERNEL); if (!cs40l26->no_wait_ram_indices) return -ENOMEM; ret = of_property_read_u32_array(np, "cirrus,no-wait-ram-indices", cs40l26->no_wait_ram_indices, cs40l26->num_no_wait_ram_indices); if (ret) goto err_free; for (i = 0; i < cs40l26->num_no_wait_ram_indices; i++) cs40l26->no_wait_ram_indices[i] += CS40L26_RAM_INDEX_START; return 0; err_free: devm_kfree(cs40l26->dev, cs40l26->no_wait_ram_indices); cs40l26->num_no_wait_ram_indices = 0; return ret; } static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26) { struct device *dev = cs40l26->dev; struct device_node *np = dev->of_node; const char *str = NULL; int ret; u32 val; if (!np) { dev_err(dev, "No platform data found\n"); return -ENOENT; } if (!of_property_read_string(np, "input-device-name", &str)) cs40l26->pdata.device_name = str; else cs40l26->pdata.device_name = CS40L26_INPUT_DEV_NAME; if (of_property_read_bool(np, "cirrus,fw-defer")) cs40l26->fw_defer = true; if (of_property_read_bool(np, "cirrus,calib-fw")) cs40l26->calib_fw = true; if (of_property_read_bool(np, "cirrus,bst-expl-mode-disable")) cs40l26->pdata.expl_mode_enabled = false; else cs40l26->pdata.expl_mode_enabled = true; 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_mv = 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_mv = 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; if (!of_property_read_u32(np, "cirrus,bst-dcm-en", &val)) cs40l26->pdata.bst_dcm_en = val; else cs40l26->pdata.bst_dcm_en = CS40L26_BST_DCM_EN_DEFAULT; if (!of_property_read_u32(np, "cirrus,bst-ipk-microamp", &val)) cs40l26->pdata.bst_ipk = val; else cs40l26->pdata.bst_ipk = 0; if (!of_property_read_u32(np, "cirrus,pm-stdby-timeout-ms", &val)) cs40l26->pdata.pm_stdby_timeout_ms = val; else cs40l26->pdata.pm_stdby_timeout_ms = CS40L26_PM_STDBY_TIMEOUT_MS_DEFAULT; if (!of_property_read_u32(np, "cirrus,pm-active-timeout-ms", &val)) cs40l26->pdata.pm_active_timeout_ms = val; else cs40l26->pdata.pm_active_timeout_ms = CS40L26_PM_ACTIVE_TIMEOUT_MS_DEFAULT; ret = cs40l26_handle_svc_le_nodes(cs40l26); if (ret < 0) cs40l26->num_svc_le_vals = 0; else cs40l26->num_svc_le_vals = ret; cs40l26->pdata.asp_scale_pct = CS40L26_GAIN_FULL_SCALE; cs40l26->gain_pct = CS40L26_GAIN_FULL_SCALE; cs40l26->gain_tmp = CS40L26_GAIN_FULL_SCALE; if (!of_property_read_u32(np, "cirrus,asp-gain-scale-pct", &val)) { if (val <= CS40L26_GAIN_FULL_SCALE) cs40l26->pdata.asp_scale_pct = val; else dev_warn(dev, "ASP scaling > 100 %%, using maximum\n"); } if (!of_property_read_u32(np, "cirrus,boost-ctl-microvolt", &val)) cs40l26->pdata.boost_ctl = val; else cs40l26->pdata.boost_ctl = CS40L26_BST_CTL_DEFAULT; if (!of_property_read_u32(np, "cirrus,f0-default", &val)) cs40l26->pdata.f0_default = val; if (!of_property_read_u32(np, "cirrus,redc-default", &val)) cs40l26->pdata.redc_default = val; if (!of_property_read_u32(np, "cirrus,q-default", &val)) cs40l26->pdata.q_default = val; if (of_property_read_bool(np, "cirrus,dbc-enable")) cs40l26->pdata.dbc_enable_default = true; else cs40l26->pdata.dbc_enable_default = false; if (!of_property_read_u32(np, "cirrus,dbc-env-rel-coef", &val)) cs40l26->pdata.dbc_defaults[CS40L26_DBC_ENV_REL_COEF] = val; else cs40l26->pdata.dbc_defaults[CS40L26_DBC_ENV_REL_COEF] = CS40L26_DBC_USE_DEFAULT; if (!of_property_read_u32(np, "cirrus,dbc-fall-headroom", &val)) cs40l26->pdata.dbc_defaults[CS40L26_DBC_FALL_HEADROOM] = val; else cs40l26->pdata.dbc_defaults[CS40L26_DBC_FALL_HEADROOM] = CS40L26_DBC_USE_DEFAULT; if (!of_property_read_u32(np, "cirrus,dbc-rise-headroom", &val)) cs40l26->pdata.dbc_defaults[CS40L26_DBC_RISE_HEADROOM] = val; else cs40l26->pdata.dbc_defaults[CS40L26_DBC_RISE_HEADROOM] = CS40L26_DBC_USE_DEFAULT; if (!of_property_read_u32(np, "cirrus,dbc-tx-lvl-hold-off-ms", &val)) cs40l26->pdata.dbc_defaults[CS40L26_DBC_TX_LVL_HOLD_OFF_MS] = val; else cs40l26->pdata.dbc_defaults[CS40L26_DBC_TX_LVL_HOLD_OFF_MS] = CS40L26_DBC_USE_DEFAULT; if (!of_property_read_u32(np, "cirrus,dbc-tx-lvl-thresh-fs", &val)) cs40l26->pdata.dbc_defaults[CS40L26_DBC_TX_LVL_THRESH_FS] = val; else cs40l26->pdata.dbc_defaults[CS40L26_DBC_TX_LVL_THRESH_FS] = CS40L26_DBC_USE_DEFAULT; if (of_property_read_bool(np, "cirrus,pwle-zero-cross-en")) cs40l26->pdata.pwle_zero_cross = true; else cs40l26->pdata.pwle_zero_cross = false; cs40l26->pdata.press_idx = gpio_map_get(np, CS40L26_GPIO_MAP_A_PRESS); cs40l26->pdata.release_idx = gpio_map_get(np, CS40L26_GPIO_MAP_A_RELEASE); return cs40l26_no_wait_ram_indices_get(cs40l26, np); } int cs40l26_probe(struct cs40l26_private *cs40l26, struct cs40l26_platform_data *pdata) { struct device *dev = cs40l26->dev; 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); INIT_WORK(&cs40l26->upload_work, cs40l26_upload_worker); INIT_WORK(&cs40l26->erase_work, cs40l26_erase_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; } 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, 1); usleep_range(CS40L26_CONTROL_PORT_READY_DELAY, CS40L26_CONTROL_PORT_READY_DELAY + 100); /* * The DSP may lock up if a haptic effect is triggered via * GPI event or control port and the PLL is set to closed-loop. * * Set PLL to open-loop and remove any default GPI mappings * to prevent this while the driver is loading and configuring RAM * firmware. */ ret = cs40l26_set_pll_loop(cs40l26, CS40L26_PLL_REFCLK_SET_OPEN_LOOP); if (ret) goto err; ret = cs40l26_erase_gpi_mapping(cs40l26, CS40L26_GPIO_MAP_A_PRESS); if (ret) goto err; ret = cs40l26_erase_gpi_mapping(cs40l26, CS40L26_GPIO_MAP_A_RELEASE); if (ret) goto err; ret = cs40l26_part_num_resolve(cs40l26); if (ret) goto err; /* Set LRA to high-z to avoid fault conditions */ ret = regmap_update_bits(cs40l26->regmap, CS40L26_TST_DAC_MSM_CONFIG, CS40L26_SPK_DEFAULT_HIZ_MASK, 1 << CS40L26_SPK_DEFAULT_HIZ_SHIFT); if (ret) { dev_err(dev, "Failed to set LRA to HI-Z\n"); goto err; } cs40l26->pm_ready = false; init_completion(&cs40l26->i2s_cont); init_completion(&cs40l26->erase_cont); init_completion(&cs40l26->cal_f0_cont); init_completion(&cs40l26->cal_redc_cont); init_completion(&cs40l26->cal_dvl_peq_cont); if (!cs40l26->fw_defer) { ret = cs40l26_fw_upload(cs40l26); 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; } } ret = cs40l26_input_init(cs40l26); if (ret) goto err; INIT_LIST_HEAD(&cs40l26->effect_head); ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cs40l26_devs, CS40L26_NUM_MFD_DEVS, NULL, 0, NULL); if (ret) { 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) { cancel_work_sync(&cs40l26->vibe_start_work); cancel_work_sync(&cs40l26->vibe_stop_work); cancel_work_sync(&cs40l26->set_gain_work); cancel_work_sync(&cs40l26->upload_work); cancel_work_sync(&cs40l26->erase_work); destroy_workqueue(cs40l26->vibe_workqueue); } if (vp_consumer) regulator_disable(vp_consumer); if (va_consumer) regulator_disable(va_consumer); gpiod_set_value_cansleep(cs40l26->reset_gpio, 0); if (cs40l26->vibe_init_success) { #if !IS_ENABLED(CONFIG_INPUT_CS40L26_ATTR_UNDER_BUS) sysfs_remove_group(&cs40l26->input->dev.kobj, &cs40l26_dev_attr_group); sysfs_remove_group(&cs40l26->input->dev.kobj, &cs40l26_dev_attr_cal_group); sysfs_remove_group(&cs40l26->input->dev.kobj, &cs40l26_dev_attr_dbc_group); #else sysfs_remove_groups(&cs40l26->dev->kobj, cs40l26_dev_attr_groups); #endif } #ifdef CONFIG_DEBUG_FS cs40l26_debugfs_cleanup(cs40l26); #endif if (cs40l26->input) input_unregister_device(cs40l26->input); return 0; } EXPORT_SYMBOL(cs40l26_remove); int cs40l26_pm_enter(struct device *dev) { int ret; ret = pm_runtime_get_sync(dev); if (ret < 0) { cs40l26_resume_error_handle(dev, ret); return ret; } return 0; } EXPORT_SYMBOL(cs40l26_pm_enter); void cs40l26_pm_exit(struct device *dev) { pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); } EXPORT_SYMBOL(cs40l26_pm_exit); 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); void cs40l26_resume_error_handle(struct device *dev, int ret) { dev_err(dev, "PM Runtime Resume failed: %d\n", ret); pm_runtime_set_active(dev); cs40l26_pm_exit(dev); } EXPORT_SYMBOL(cs40l26_resume_error_handle); 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. "); MODULE_LICENSE("GPL"); MODULE_VERSION("6.0.1");