| // SPDX-License-Identifier: GPL-2.0 |
| // |
| // cs40l26.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and |
| // Waveform Memory with Advanced Closed Loop Algorithms and LRA protection |
| // |
| // Copyright 2021 Cirrus Logic, Inc. |
| // |
| // Author: Fred Treven <fred.treven@cirrus.com> |
| // |
| // This program is free software; you can redistribute it and/or modify |
| // it under the terms of the GNU General Public License version 2 as |
| // published by the Free Software Foundation. |
| |
| #include "cs40l26.h" |
| |
| static int cs40l26_dsp_read(struct cs40l26_private *cs40l26, u32 reg, u32 *val) |
| { |
| struct regmap *regmap = cs40l26->regmap; |
| struct device *dev = cs40l26->dev; |
| int ret, i; |
| u32 read_val; |
| |
| for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { |
| ret = regmap_read(regmap, reg, &read_val); |
| if (ret) |
| dev_dbg(dev, "Failed to read 0x%X, attempt(s) = %d\n", |
| reg, i + 1); |
| else |
| break; |
| |
| usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, |
| CS40L26_DSP_TIMEOUT_US_MAX); |
| } |
| |
| if (i >= CS40L26_DSP_TIMEOUT_COUNT) { |
| dev_err(dev, "Timed out attempting to read 0x%X\n", reg); |
| return -ETIME; |
| } |
| |
| *val = read_val; |
| |
| return 0; |
| } |
| |
| static int cs40l26_dsp_write(struct cs40l26_private *cs40l26, u32 reg, u32 val) |
| { |
| struct regmap *regmap = cs40l26->regmap; |
| struct device *dev = cs40l26->dev; |
| int ret, i; |
| |
| for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { |
| ret = regmap_write(regmap, reg, val); |
| if (ret) |
| dev_dbg(dev, |
| "Failed to write to 0x%X, attempt(s) = %d\n", |
| reg, i + 1); |
| else |
| break; |
| |
| usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, |
| CS40L26_DSP_TIMEOUT_US_MAX); |
| } |
| |
| if (i >= CS40L26_DSP_TIMEOUT_COUNT) { |
| dev_err(dev, "Timed out attempting to write to 0x%X\n", reg); |
| return -ETIME; |
| } |
| |
| return 0; |
| } |
| |
| static int cs40l26_ack_read(struct cs40l26_private *cs40l26, u32 reg, |
| u32 ack_val) |
| { |
| struct device *dev = cs40l26->dev; |
| int ret, i; |
| u32 val; |
| |
| for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { |
| ret = cs40l26_dsp_read(cs40l26, reg, &val); |
| if (ret) |
| return ret; |
| |
| if (val != ack_val) |
| dev_dbg(dev, "Ack'ed value not equal to expected\n"); |
| else |
| break; |
| |
| usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, |
| CS40L26_DSP_TIMEOUT_US_MAX); |
| } |
| |
| if (i >= CS40L26_DSP_TIMEOUT_COUNT) { |
| dev_err(dev, "Ack timed out (0x%08X != 0x%08X) reg. 0x%08X\n", |
| val, ack_val, reg); |
| return -ETIME; |
| } |
| |
| return 0; |
| } |
| |
| int cs40l26_ack_write(struct cs40l26_private *cs40l26, u32 reg, u32 write_val, |
| u32 reset_val) |
| { |
| int ret; |
| |
| ret = cs40l26_dsp_write(cs40l26, reg, write_val); |
| if (ret) |
| return ret; |
| |
| return cs40l26_ack_read(cs40l26, reg, reset_val); |
| } |
| EXPORT_SYMBOL(cs40l26_ack_write); |
| |
| int cs40l26_class_h_set(struct cs40l26_private *cs40l26, bool class_h) |
| { |
| int ret; |
| |
| ret = regmap_update_bits(cs40l26->regmap, CS40L26_BLOCK_ENABLES2, |
| CS40L26_CLASS_H_EN_MASK, class_h << |
| CS40L26_CLASS_H_EN_SHIFT); |
| if (ret) |
| dev_err(cs40l26->dev, "Failed to update CLASS H tracking\n"); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(cs40l26_class_h_set); |
| |
| int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state) |
| { |
| u32 algo_id, reg, dsp_state; |
| int ret; |
| |
| if (cs40l26->fw_loaded) { |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM) |
| algo_id = CS40L26_PM_ALGO_ID; |
| else |
| algo_id = CS40L26_PM_ROM_ALGO_ID; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "PM_CUR_STATE", |
| CL_DSP_XM_UNPACKED_TYPE, algo_id, ®); |
| if (ret) |
| return ret; |
| } else { |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| reg = CS40L26_A0_PM_CUR_STATE_STATIC_REG; |
| break; |
| case CS40L26_REVID_A1: |
| reg = CS40L26_A1_PM_CUR_STATE_STATIC_REG; |
| break; |
| default: |
| dev_err(cs40l26->dev, |
| "Revid ID not supported: 0x%02X\n", |
| cs40l26->revid); |
| return -EINVAL; |
| } |
| } |
| |
| ret = cs40l26_dsp_read(cs40l26, reg, &dsp_state); |
| if (ret) |
| return ret; |
| |
| switch (dsp_state) { |
| case CS40L26_DSP_STATE_HIBERNATE: |
| /* intentionally fall through */ |
| case CS40L26_DSP_STATE_SHUTDOWN: |
| /* intentionally fall through */ |
| case CS40L26_DSP_STATE_STANDBY: |
| /* intentionally fall through */ |
| case CS40L26_DSP_STATE_ACTIVE: |
| *state = CS40L26_DSP_STATE_MASK & dsp_state; |
| break; |
| default: |
| dev_err(cs40l26->dev, "DSP state %u is invalid\n", dsp_state); |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(cs40l26_dsp_state_get); |
| |
| int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26, |
| u32 timeout_ms) |
| { |
| u32 timeout_ticks = timeout_ms * CS40L26_PM_TICKS_MS_DIV; |
| struct regmap *regmap = cs40l26->regmap; |
| u32 lower_val, reg, algo_id; |
| u8 upper_val; |
| int ret; |
| |
| upper_val = (timeout_ticks >> CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT) & |
| CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK; |
| |
| lower_val = timeout_ticks & CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK; |
| |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM) |
| algo_id = CS40L26_PM_ALGO_ID; |
| else |
| algo_id = CS40L26_PM_ROM_ALGO_ID; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS", |
| CL_DSP_XM_UNPACKED_TYPE, algo_id, ®); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(regmap, reg + CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET, |
| lower_val); |
| if (ret) |
| return ret; |
| |
| return regmap_write(regmap, reg + CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET, |
| upper_val); |
| } |
| EXPORT_SYMBOL(cs40l26_pm_timeout_ms_set); |
| |
| int cs40l26_pm_timeout_ms_get(struct cs40l26_private *cs40l26, |
| u32 *timeout_ms) |
| { |
| u32 lower_val, upper_val, algo_id, reg; |
| int ret; |
| |
| if (cs40l26->fw_loaded) { |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM) |
| algo_id = CS40L26_PM_ALGO_ID; |
| else |
| algo_id = CS40L26_PM_ROM_ALGO_ID; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS", |
| CL_DSP_XM_UNPACKED_TYPE, algo_id, ®); |
| if (ret) |
| return ret; |
| } else { |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| reg = CS40L26_A0_PM_TIMEOUT_TICKS_STATIC_REG; |
| break; |
| case CS40L26_REVID_A1: |
| reg = CS40L26_A1_PM_TIMEOUT_TICKS_STATIC_REG; |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| return -EINVAL; |
| } |
| } |
| |
| ret = regmap_read(cs40l26->regmap, reg + |
| CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET, &lower_val); |
| if (ret) |
| return ret; |
| |
| ret = regmap_read(cs40l26->regmap, reg + |
| CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET, &upper_val); |
| if (ret) |
| return ret; |
| |
| *timeout_ms = |
| ((lower_val & CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK) | |
| ((upper_val & CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK) << |
| CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT)) / |
| CS40L26_PM_TICKS_MS_DIV; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cs40l26_pm_timeout_ms_get); |
| |
| static int cs40l26_pm_runtime_setup(struct cs40l26_private *cs40l26) |
| { |
| struct device *dev = cs40l26->dev; |
| int ret; |
| |
| ret = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_PM_TIMEOUT_MS_MIN); |
| if (ret) |
| return ret; |
| |
| pm_runtime_mark_last_busy(dev); |
| pm_runtime_set_active(dev); |
| pm_runtime_enable(dev); |
| pm_runtime_set_autosuspend_delay(dev, CS40L26_AUTOSUSPEND_DELAY_MS); |
| pm_runtime_use_autosuspend(dev); |
| |
| cs40l26->pm_ready = true; |
| |
| return 0; |
| } |
| |
| static void cs40l26_pm_runtime_teardown(struct cs40l26_private *cs40l26) |
| { |
| struct device *dev = cs40l26->dev; |
| |
| pm_runtime_set_suspended(dev); |
| pm_runtime_disable(dev); |
| pm_runtime_dont_use_autosuspend(dev); |
| |
| cs40l26->pm_ready = false; |
| } |
| |
| int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26, |
| enum cs40l26_pm_state state) |
| { |
| struct device *dev = cs40l26->dev; |
| u32 cmd; |
| int ret; |
| |
| cmd = (u32) CS40L26_DSP_MBOX_PM_CMD_BASE + state; |
| |
| switch (state) { |
| case CS40L26_PM_STATE_WAKEUP: |
| /* intentionally fall through */ |
| case CS40L26_PM_STATE_PREVENT_HIBERNATE: |
| ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, |
| cmd, CS40L26_DSP_MBOX_RESET); |
| break; |
| case CS40L26_PM_STATE_ALLOW_HIBERNATE: |
| /* intentionally fall through */ |
| case CS40L26_PM_STATE_SHUTDOWN: |
| cs40l26->wksrc_sts = 0x00; |
| ret = cs40l26_dsp_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, |
| cmd); |
| break; |
| default: |
| dev_err(dev, "Invalid PM state: %u\n", state); |
| return -EINVAL; |
| } |
| |
| if (ret) |
| return ret; |
| |
| cs40l26->pm_state = state; |
| |
| return 0; |
| } |
| |
| static int cs40l26_dsp_start(struct cs40l26_private *cs40l26) |
| { |
| u8 dsp_state; |
| int ret; |
| |
| ret = regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL, |
| CS40L26_DSP_CCM_CORE_RESET); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to reset DSP core\n"); |
| return ret; |
| } |
| |
| ret = cs40l26_dsp_state_get(cs40l26, &dsp_state); |
| if (ret) |
| return ret; |
| |
| if (dsp_state != CS40L26_DSP_STATE_ACTIVE && |
| dsp_state != CS40L26_DSP_STATE_STANDBY) { |
| dev_err(cs40l26->dev, "Failed to wake DSP core\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int cs40l26_dsp_wake(struct cs40l26_private *cs40l26) |
| { |
| u8 dsp_state; |
| int ret; |
| |
| ret = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_WAKEUP); |
| if (ret) |
| return ret; |
| |
| ret = cs40l26_pm_state_transition(cs40l26, |
| CS40L26_PM_STATE_PREVENT_HIBERNATE); |
| if (ret) |
| return ret; |
| |
| ret = cs40l26_dsp_state_get(cs40l26, &dsp_state); |
| if (ret) |
| return ret; |
| |
| if (dsp_state != CS40L26_DSP_STATE_STANDBY && |
| dsp_state != CS40L26_DSP_STATE_ACTIVE) { |
| dev_err(cs40l26->dev, "Failed to wake DSP\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int cs40l26_dsp_shutdown(struct cs40l26_private *cs40l26) |
| { |
| u32 timeout_ms; |
| int ret; |
| |
| ret = cs40l26_pm_timeout_ms_get(cs40l26, &timeout_ms); |
| if (ret) |
| return ret; |
| |
| ret = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_SHUTDOWN); |
| if (ret) |
| return ret; |
| |
| usleep_range(CS40L26_MS_TO_US(timeout_ms), |
| CS40L26_MS_TO_US(timeout_ms) + 100); |
| |
| return 0; |
| } |
| |
| static int cs40l26_dsp_pre_config(struct cs40l26_private *cs40l26) |
| { |
| u8 dsp_state; |
| u32 halo_state, halo_state_reg; |
| int ret; |
| |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| halo_state_reg = CS40L26_A0_DSP_HALO_STATE_REG; |
| break; |
| case CS40L26_REVID_A1: |
| halo_state_reg = CS40L26_A1_DSP_HALO_STATE_REG; |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| return -EINVAL; |
| } |
| |
| ret = regmap_read(cs40l26->regmap, halo_state_reg, |
| &halo_state); |
| if (ret) |
| return ret; |
| |
| if (halo_state != CS40L26_DSP_HALO_STATE_RUN) { |
| dev_err(cs40l26->dev, "DSP not Ready: HALO_STATE: %08X\n", |
| halo_state); |
| return -EINVAL; |
| } |
| |
| ret = cs40l26_pm_state_transition(cs40l26, |
| CS40L26_PM_STATE_PREVENT_HIBERNATE); |
| if (ret) |
| return ret; |
| |
| ret = cs40l26_dsp_state_get(cs40l26, &dsp_state); |
| if (ret) |
| return ret; |
| |
| if (dsp_state != CS40L26_DSP_STATE_SHUTDOWN && |
| dsp_state != CS40L26_DSP_STATE_STANDBY) { |
| dev_err(cs40l26->dev, "DSP core not safe to kill\n"); |
| return -EINVAL; |
| } |
| |
| /* errata write fixing indeterminent PLL lock time */ |
| ret = regmap_update_bits(cs40l26->regmap, CS40L26_PLL_REFCLK_DETECT_0, |
| CS40L26_PLL_REFCLK_DET_EN_MASK, CS40L26_DISABLE); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to disable PLL refclk detect\n"); |
| return ret; |
| } |
| |
| ret = regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL, |
| CS40L26_DSP_CCM_CORE_KILL); |
| if (ret) |
| dev_err(cs40l26->dev, "Failed to kill DSP core\n"); |
| |
| return ret; |
| } |
| |
| static int cs40l26_mbox_buffer_read(struct cs40l26_private *cs40l26, u32 *val) |
| { |
| struct device *dev = cs40l26->dev; |
| struct regmap *regmap = cs40l26->regmap; |
| u32 base, last, len, write_ptr, read_ptr, mbox_response, reg; |
| u32 buffer[CS40L26_DSP_MBOX_BUFFER_NUM_REGS]; |
| int ret; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "QUEUE_BASE", |
| CL_DSP_XM_UNPACKED_TYPE, CS40L26_MAILBOX_ALGO_ID, |
| ®); |
| if (ret) |
| return ret; |
| |
| ret = regmap_bulk_read(regmap, reg, buffer, |
| CS40L26_DSP_MBOX_BUFFER_NUM_REGS); |
| if (ret) { |
| dev_err(dev, "Failed to read buffer contents\n"); |
| return ret; |
| } |
| |
| base = buffer[0]; |
| len = buffer[1]; |
| write_ptr = buffer[2]; |
| read_ptr = buffer[3]; |
| last = base + ((len - 1) * CL_DSP_BYTES_PER_WORD); |
| |
| if ((read_ptr - CL_DSP_BYTES_PER_WORD) == write_ptr) { |
| dev_err(dev, "Mailbox buffer is full, info missing\n"); |
| return -ENOSPC; |
| } |
| |
| if (read_ptr == write_ptr) { |
| dev_dbg(dev, "Reached end of queue\n"); |
| return 1; |
| } |
| |
| ret = regmap_read(regmap, read_ptr, &mbox_response); |
| if (ret) { |
| dev_err(dev, "Failed to read from mailbox buffer\n"); |
| return ret; |
| } |
| |
| if (read_ptr == last) |
| read_ptr = base; |
| else |
| read_ptr += CL_DSP_BYTES_PER_WORD; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "QUEUE_RD", |
| CL_DSP_XM_UNPACKED_TYPE, CS40L26_MAILBOX_ALGO_ID, |
| ®); |
| if (ret) |
| return ret; |
| |
| ret = regmap_write(regmap, reg, read_ptr); |
| if (ret) { |
| dev_err(dev, "Failed to update read pointer\n"); |
| return ret; |
| } |
| |
| *val = mbox_response; |
| |
| return 0; |
| } |
| |
| static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26) |
| { |
| struct device *dev = cs40l26->dev; |
| u32 val = 0; |
| |
| while (!cs40l26_mbox_buffer_read(cs40l26, &val)) { |
| if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK) |
| == CS40L26_DSP_MBOX_PANIC) { |
| dev_err(dev, "DSP PANIC! Error condition: 0x%06X\n", |
| (u32) (val & CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK)); |
| return -ENOTRECOVERABLE; |
| } |
| |
| switch (val) { |
| case CS40L26_DSP_MBOX_TRIGGER_COMPLETE: |
| if (cs40l26->vibe_state != CS40L26_VIBE_STATE_ASP) |
| cs40l26_vibe_state_set(cs40l26, |
| CS40L26_VIBE_STATE_STOPPED); |
| dev_dbg(dev, "Trigger Complete\n"); |
| break; |
| case CS40L26_DSP_MBOX_PM_AWAKE: |
| cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; |
| dev_dbg(dev, "HALO Core is awake\n"); |
| break; |
| case CS40L26_DSP_MBOX_F0_EST_START: |
| dev_dbg(dev, "F0_EST_START\n"); |
| break; |
| case CS40L26_DSP_MBOX_F0_EST_DONE: |
| dev_dbg(dev, "F0_EST_DONE\n"); |
| if (cs40l26->cal_requested & |
| CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q) { |
| cs40l26->cal_requested &= |
| ~CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q; |
| /* for pm_runtime_get see trigger_calibration */ |
| pm_runtime_mark_last_busy(cs40l26->dev); |
| pm_runtime_put_autosuspend(cs40l26->dev); |
| } else { |
| dev_err(dev, "Unexpected mbox msg: %d", val); |
| return -EINVAL; |
| } |
| |
| break; |
| case CS40L26_DSP_MBOX_REDC_EST_START: |
| dev_dbg(dev, "REDC_EST_START\n"); |
| break; |
| case CS40L26_DSP_MBOX_REDC_EST_DONE: |
| dev_dbg(dev, "REDC_EST_DONE\n"); |
| if (cs40l26->cal_requested & |
| CS40L26_CALIBRATION_CONTROL_REQUEST_REDC) { |
| cs40l26->cal_requested &= |
| ~CS40L26_CALIBRATION_CONTROL_REQUEST_REDC; |
| /* for pm_runtime_get see trigger_calibration */ |
| pm_runtime_mark_last_busy(cs40l26->dev); |
| pm_runtime_put_autosuspend(cs40l26->dev); |
| } else { |
| dev_err(dev, "Unexpected mbox msg: %d", val); |
| return -EINVAL; |
| } |
| break; |
| case CS40L26_DSP_MBOX_SYS_ACK: |
| dev_err(dev, "Mbox buffer value (0x%X) not supported\n", |
| val); |
| return -EPERM; |
| default: |
| dev_err(dev, "MBOX buffer value (0x%X) is invalid\n", |
| val); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void cs40l26_vibe_state_set(struct cs40l26_private *cs40l26, |
| enum cs40l26_vibe_state new_state) |
| { |
| if (cs40l26->vibe_state == new_state) |
| return; |
| |
| if (new_state == CS40L26_VIBE_STATE_STOPPED && cs40l26->asp_enable) { |
| /* Re-enable audio stream */ |
| cs40l26_class_h_set(cs40l26, true); |
| if (!cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, |
| CS40L26_DSP_MBOX_CMD_START_I2S, |
| CS40L26_DSP_MBOX_RESET)) { |
| cs40l26->vibe_state = CS40L26_VIBE_STATE_ASP; |
| return; |
| } else if (new_state == CS40L26_VIBE_STATE_HAPTIC && |
| cs40l26->vibe_state == CS40L26_VIBE_STATE_ASP) { |
| cs40l26_class_h_set(cs40l26, false); |
| } |
| } |
| |
| cs40l26->vibe_state = new_state; |
| #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_set); |
| |
| static int cs40l26_event_count_get(struct cs40l26_private *cs40l26, u32 *count) |
| { |
| unsigned int reg; |
| int ret; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "EVENT_POST_COUNT", |
| CL_DSP_XM_UNPACKED_TYPE, CS40L26_EVENT_HANDLER_ALGO_ID, |
| ®); |
| if (ret) |
| return ret; |
| |
| ret = regmap_read(cs40l26->regmap, reg, count); |
| if (ret) |
| dev_err(cs40l26->dev, "Failed to get event count\n"); |
| |
| return ret; |
| } |
| |
| static int cs40l26_error_release(struct cs40l26_private *cs40l26, |
| unsigned int err_rls, bool bst_err) |
| { |
| struct regmap *regmap = cs40l26->regmap; |
| struct device *dev = cs40l26->dev; |
| u32 err_sts, err_cfg; |
| int ret; |
| |
| /* Boost related errors must be handled with DSP turned off */ |
| if (bst_err) { |
| ret = cs40l26_dsp_shutdown(cs40l26); |
| if (ret) |
| return ret; |
| } |
| |
| ret = regmap_read(regmap, CS40L26_ERROR_RELEASE, &err_sts); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to get error status\n"); |
| return ret; |
| } |
| |
| err_cfg = err_sts & ~BIT(err_rls); |
| |
| ret = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg); |
| if (ret) { |
| dev_err(dev, "Actuator Safe Mode release sequence failed\n"); |
| return ret; |
| } |
| |
| err_cfg |= BIT(err_rls); |
| |
| ret = regmap_write(regmap, CS40L26_ERROR_RELEASE, err_cfg); |
| if (ret) { |
| dev_err(dev, "Actuator Safe Mode release sequence failed\n"); |
| return ret; |
| } |
| |
| err_cfg &= ~BIT(err_rls); |
| |
| ret = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg); |
| if (ret) { |
| dev_err(dev, "Actuator Safe Mode release sequence failed\n"); |
| return ret; |
| } |
| |
| if (bst_err) { |
| ret = cs40l26_dsp_wake(cs40l26); |
| if (ret) |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int cs40l26_iseq_update(struct cs40l26_private *cs40l26, |
| enum cs40l26_iseq update) |
| { |
| int ret; |
| u32 val; |
| |
| ret = regmap_read(cs40l26->regmap, cs40l26->iseq_table[update].addr, |
| &val); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to get IRQ seq. information\n"); |
| return ret; |
| } |
| |
| cs40l26->iseq_table[update].val = val; |
| |
| return 0; |
| } |
| |
| static int cs40l26_iseq_init(struct cs40l26_private *cs40l26) |
| { |
| struct device *dev = cs40l26->dev; |
| struct regmap *regmap = cs40l26->regmap; |
| int ret, i; |
| |
| cs40l26->iseq_table[CS40L26_ISEQ_MASK1].addr = CS40L26_IRQ1_MASK_1; |
| cs40l26->iseq_table[CS40L26_ISEQ_MASK2].addr = CS40L26_IRQ1_MASK_2; |
| cs40l26->iseq_table[CS40L26_ISEQ_EDGE1].addr = CS40L26_IRQ1_EDGE_1; |
| cs40l26->iseq_table[CS40L26_ISEQ_POL1].addr = CS40L26_IRQ1_POL_1; |
| |
| for (i = 0; i < CS40L26_ISEQ_MAX_ENTRIES; i++) { |
| ret = regmap_read(regmap, cs40l26->iseq_table[i].addr, |
| &cs40l26->iseq_table[i].val); |
| if (ret) { |
| dev_err(dev, "Failed to read IRQ settings\n"); |
| return ret; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int cs40l26_iseq_populate(struct cs40l26_private *cs40l26) |
| { |
| int ret, i; |
| |
| for (i = 0; i < CS40L26_ISEQ_MAX_ENTRIES; i++) { |
| ret = regmap_write(cs40l26->regmap, |
| cs40l26->iseq_table[i].addr, |
| cs40l26->iseq_table[i].val); |
| if (ret) { |
| dev_err(cs40l26->dev, |
| "Failed to update IRQ settings\n"); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int cs40l26_irq_update_mask(struct cs40l26_private *cs40l26, u32 irq_reg, |
| u32 bits, bool mask) |
| { |
| struct regmap *regmap = cs40l26->regmap; |
| struct device *dev = cs40l26->dev; |
| enum cs40l26_iseq update; |
| u32 eint_reg; |
| int ret; |
| |
| switch (irq_reg) { |
| case CS40L26_IRQ1_MASK_1: |
| update = CS40L26_ISEQ_MASK1; |
| eint_reg = CS40L26_IRQ1_EINT_1; |
| break; |
| case CS40L26_IRQ1_MASK_2: |
| update = CS40L26_ISEQ_MASK2; |
| eint_reg = CS40L26_IRQ1_EINT_2; |
| break; |
| default: |
| dev_err(dev, "Invalid IRQ mask register 0x%08X\n", irq_reg); |
| return -EINVAL; |
| } |
| |
| ret = regmap_write(regmap, eint_reg, bits); |
| if (ret) { |
| dev_err(dev, |
| "Failed to clear status of bits 0x%08X\n", |
| eint_reg); |
| return ret; |
| } |
| |
| ret = regmap_update_bits(regmap, irq_reg, bits, mask ? bits : ~bits); |
| if (ret) { |
| dev_err(dev, "Failed to update IRQ mask 0x%08X\n", irq_reg); |
| return ret; |
| } |
| |
| return cs40l26_iseq_update(cs40l26, update); |
| } |
| |
| static int cs40l26_handle_irq1(struct cs40l26_private *cs40l26, |
| enum cs40l26_irq1 irq1, bool trigger) |
| { |
| struct device *dev = cs40l26->dev; |
| u32 err_rls = 0; |
| unsigned int reg, val; |
| bool bst_err; |
| int ret; |
| |
| switch (irq1) { |
| case CS40L26_IRQ1_GPIO1_RISE: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_GPIO1_FALL: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_GPIO2_RISE: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_GPIO2_FALL: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_GPIO3_RISE: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_GPIO3_FALL: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_GPIO4_RISE: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_GPIO4_FALL: |
| if (cs40l26->wksrc_sts & CS40L26_WKSRC_STS_EN) { |
| dev_dbg(dev, "GPIO%u %s edge detected\n", |
| (irq1 / 2) + 1, |
| irq1 % 2 ? "falling" : "rising"); |
| if (trigger) |
| cs40l26_vibe_state_set(cs40l26, |
| CS40L26_VIBE_STATE_HAPTIC); |
| } |
| |
| cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; |
| break; |
| case CS40L26_IRQ1_WKSRC_STS_ANY: |
| dev_dbg(dev, "Wakesource detected (ANY)\n"); |
| |
| ret = cs40l26_iseq_populate(cs40l26); |
| if (ret) |
| goto err; |
| |
| ret = regmap_read(cs40l26->regmap, CS40L26_PWRMGT_STS, &val); |
| if (ret) { |
| dev_err(dev, "Failed to get Power Management Status\n"); |
| goto err; |
| } |
| |
| cs40l26->wksrc_sts = (u8) ((val & CS40L26_WKSRC_STS_MASK) >> |
| CS40L26_WKSRC_STS_SHIFT); |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "LAST_WAKESRC_CTL", |
| CL_DSP_XM_UNPACKED_TYPE, |
| CS40L26_FW_ID, ®); |
| if (ret) |
| goto err; |
| |
| ret = regmap_read(cs40l26->regmap, reg, &val); |
| if (ret) { |
| dev_err(dev, "Failed to read LAST_WAKESRC_CTL\n"); |
| goto err; |
| } |
| cs40l26->last_wksrc_pol = |
| (u8) (val & CS40L26_WKSRC_GPIO_POL_MASK); |
| break; |
| case CS40L26_IRQ1_WKSRC_STS_GPIO1: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_WKSRC_STS_GPIO2: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_WKSRC_STS_GPIO3: |
| /* intentionally fall through */ |
| case CS40L26_IRQ1_WKSRC_STS_GPIO4: |
| dev_dbg(dev, "GPIO%u event woke device from hibernate\n", |
| irq1 - CS40L26_IRQ1_WKSRC_STS_GPIO1 + 1); |
| |
| if (cs40l26->wksrc_sts & cs40l26->last_wksrc_pol) { |
| dev_dbg(dev, "GPIO%u falling edge detected\n", |
| irq1 - 8); |
| cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; |
| } else { |
| dev_dbg(dev, "GPIO%u rising edge detected\n", |
| irq1 - 8); |
| } |
| if (trigger) |
| cs40l26_vibe_state_set(cs40l26, |
| CS40L26_VIBE_STATE_HAPTIC); |
| break; |
| case CS40L26_IRQ1_WKSRC_STS_SPI: |
| dev_dbg(dev, "SPI event woke device from hibernate\n"); |
| break; |
| case CS40L26_IRQ1_WKSRC_STS_I2C: |
| dev_dbg(dev, "I2C event woke device from hibernate\n"); |
| break; |
| case CS40L26_IRQ1_GLOBAL_EN_ASSERT: |
| dev_dbg(dev, "Started power up seq. (GLOBAL_EN asserted)\n"); |
| break; |
| case CS40L26_IRQ1_PDN_DONE: |
| dev_dbg(dev, |
| "Completed power down seq. (GLOBAL_EN cleared)\n"); |
| break; |
| case CS40L26_IRQ1_PUP_DONE: |
| dev_dbg(dev, |
| "Completed power up seq. (GLOBAL_EN asserted)\n"); |
| break; |
| case CS40L26_IRQ1_BST_OVP_FLAG_RISE: |
| dev_warn(dev, "BST overvoltage warning\n"); |
| break; |
| case CS40L26_IRQ1_BST_OVP_FLAG_FALL: |
| dev_warn(dev, |
| "BST voltage returned below warning threshold\n"); |
| break; |
| case CS40L26_IRQ1_BST_OVP_ERR: |
| dev_err(dev, "BST overvolt. error, CS40L26 shutting down\n"); |
| err_rls = CS40L26_BST_OVP_ERR_RLS; |
| bst_err = true; |
| break; |
| case CS40L26_IRQ1_BST_DCM_UVP_ERR: |
| dev_err(dev, |
| "BST undervolt. error, CS40L26 shutting down\n"); |
| err_rls = CS40L26_BST_UVP_ERR_RLS; |
| bst_err = true; |
| break; |
| case CS40L26_IRQ1_BST_SHORT_ERR: |
| dev_err(dev, "LBST short detected, CS40L26 shutting down\n"); |
| err_rls = CS40L26_BST_SHORT_ERR_RLS; |
| bst_err = true; |
| break; |
| case CS40L26_IRQ1_BST_IPK_FLAG: |
| dev_warn(dev, "Current is being limited by LBST inductor\n"); |
| break; |
| case CS40L26_IRQ1_TEMP_WARN_RISE: |
| dev_err(dev, "Die overtemperature warning\n"); |
| err_rls = CS40L26_TEMP_WARN_ERR_RLS; |
| break; |
| case CS40L26_IRQ1_TEMP_WARN_FALL: |
| dev_warn(dev, "Die temperature returned below threshold\n"); |
| break; |
| case CS40L26_IRQ1_TEMP_ERR: |
| dev_err(dev, |
| "Die overtemperature error, CS40L26 shutting down\n"); |
| err_rls = CS40L26_TEMP_ERR_RLS; |
| break; |
| case CS40L26_IRQ1_AMP_ERR: |
| dev_err(dev, "AMP short detected, CS40L26 shutting down\n"); |
| err_rls = CS40L26_AMP_SHORT_ERR_RLS; |
| break; |
| case CS40L26_IRQ1_DC_WATCHDOG_RISE: |
| dev_err(dev, "DC level detected\n"); |
| break; |
| case CS40L26_IRQ1_DC_WATCHDOG_FALL: |
| dev_warn(dev, "Previously detected DC level removed\n"); |
| break; |
| case CS40L26_IRQ1_VIRTUAL1_MBOX_WR: |
| dev_dbg(dev, "Virtual 1 MBOX write occurred\n"); |
| break; |
| case CS40L26_IRQ1_VIRTUAL2_MBOX_WR: |
| ret = cs40l26_handle_mbox_buffer(cs40l26); |
| if (ret) |
| goto err; |
| break; |
| default: |
| dev_err(dev, "Unrecognized IRQ1 EINT1 status\n"); |
| return -EINVAL; |
| } |
| |
| if (err_rls) |
| ret = cs40l26_error_release(cs40l26, err_rls, bst_err); |
| |
| err: |
| regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_1, BIT(irq1)); |
| |
| return ret; |
| } |
| |
| static int cs40l26_handle_irq2(struct cs40l26_private *cs40l26, |
| enum cs40l26_irq2 irq2) |
| { |
| struct device *dev = cs40l26->dev; |
| unsigned int val; |
| u32 vbbr_status, vpbr_status; |
| int ret; |
| |
| switch (irq2) { |
| case CS40L26_IRQ2_PLL_LOCK: |
| dev_dbg(dev, "PLL achieved lock\n"); |
| break; |
| case CS40L26_IRQ2_PLL_PHASE_LOCK: |
| dev_dbg(dev, "PLL achieved phase lock\n"); |
| break; |
| case CS40L26_IRQ2_PLL_FREQ_LOCK: |
| dev_dbg(dev, "PLL achieved frequency lock\n"); |
| break; |
| case CS40L26_IRQ2_PLL_UNLOCK_RISE: |
| dev_err(dev, "PLL has lost lock\n"); |
| break; |
| case CS40L26_IRQ2_PLL_UNLOCK_FALL: |
| dev_warn(dev, "PLL has regained lock\n"); |
| break; |
| case CS40L26_IRQ2_PLL_READY: |
| dev_dbg(dev, "PLL ready for use\n"); |
| break; |
| case CS40L26_IRQ2_PLL_REFCLK_PRESENT: |
| dev_warn(dev, "REFCLK present for PLL\n"); |
| break; |
| case CS40L26_IRQ2_REFCLK_MISSING_RISE: |
| dev_err(dev, "REFCLK input for PLL is missing\n"); |
| break; |
| case CS40L26_IRQ2_REFCLK_MISSING_FALL: |
| dev_warn(dev, "REFCLK reported missing is now present\n"); |
| break; |
| case CS40L26_IRQ2_ASP_RXSLOT_CFG_ERR: |
| dev_err(dev, "Misconfig. of ASP_RX 1 2 or 3 SLOT fields\n"); |
| break; |
| case CS40L26_IRQ2_AUX_NG_CH1_ENTRY: |
| dev_warn(dev, |
| "CH1 data of noise gate has fallen below threshold\n"); |
| break; |
| case CS40L26_IRQ2_AUX_NG_CH1_EXIT: |
| dev_err(dev, |
| "CH1 data of noise gate has risen above threshold\n"); |
| break; |
| case CS40L26_IRQ2_AUX_NG_CH2_ENTRY: |
| dev_warn(dev, |
| "CH2 data of noise gate has fallen below threshold\n"); |
| break; |
| case CS40L26_IRQ2_AUX_NG_CH2_EXIT: |
| dev_err(dev, |
| "CH2 data of noise gate has risen above threshold\n"); |
| break; |
| case CS40L26_IRQ2_AMP_NG_ON_RISE: |
| dev_warn(dev, "Amplifier entered noise-gated state\n"); |
| break; |
| case CS40L26_IRQ2_AMP_NG_ON_FALL: |
| dev_warn(dev, "Amplifier exited noise-gated state\n"); |
| break; |
| case CS40L26_IRQ2_VPBR_FLAG: |
| dev_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; |
| u32 event_count; |
| bool trigger; |
| int ret; |
| |
| if (cs40l26_dsp_read(cs40l26, CS40L26_IRQ1_STATUS, &sts)) { |
| dev_err(dev, "Failed to read IRQ1 Status\n"); |
| return IRQ_NONE; |
| } |
| |
| if (sts != CS40L26_IRQ_STATUS_ASSERT) { |
| dev_err(dev, "IRQ1 asserted with no pending interrupts\n"); |
| return IRQ_NONE; |
| } |
| |
| pm_runtime_get_sync(dev); |
| |
| ret = regmap_read(regmap, CS40L26_IRQ1_EINT_1, &eint); |
| if (ret) { |
| dev_err(dev, "Failed to read interrupts status 1\n"); |
| goto err; |
| } |
| |
| ret = regmap_read(regmap, CS40L26_IRQ1_MASK_1, &mask); |
| if (ret) { |
| dev_err(dev, "Failed to get interrupts mask 1\n"); |
| goto err; |
| } |
| |
| val = eint & ~mask; |
| if (val) { |
| ret = cs40l26_event_count_get(cs40l26, &event_count); |
| if (ret) |
| goto err; |
| |
| trigger = (event_count > cs40l26->event_count); |
| |
| num_irq = hweight_long(val); |
| i = 0; |
| while (irq1_count < num_irq && i < CS40L26_IRQ1_NUM_IRQS) { |
| if (val & BIT(i)) { |
| ret = cs40l26_handle_irq1(cs40l26, i, trigger); |
| if (ret) |
| goto err; |
| else |
| irq1_count++; |
| } |
| i++; |
| } |
| } |
| |
| ret = regmap_read(regmap, CS40L26_IRQ1_EINT_2, &eint); |
| if (ret) { |
| dev_err(dev, "Failed to read interrupts status 2\n"); |
| goto err; |
| } |
| |
| ret = regmap_read(regmap, CS40L26_IRQ1_MASK_2, &mask); |
| if (ret) { |
| dev_err(dev, "Failed to get interrupts mask 2\n"); |
| goto err; |
| } |
| |
| val = eint & ~mask; |
| if (val) { |
| num_irq = hweight_long(val); |
| |
| i = 0; |
| while (irq2_count < num_irq && i < CS40L26_IRQ2_NUM_IRQS) { |
| if (val & BIT(i)) { |
| ret = cs40l26_handle_irq2(cs40l26, i); |
| if (ret) |
| goto err; |
| else |
| irq2_count++; |
| } |
| i++; |
| } |
| } |
| |
| err: |
| cs40l26_event_count_get(cs40l26, &cs40l26->event_count); |
| |
| pm_runtime_mark_last_busy(dev); |
| pm_runtime_put_autosuspend(dev); |
| /* if an error has occurred, all IRQs have not been successfully |
| * processed; however, IRQ_HANDLED is still returned if at least one |
| * interrupt request generated by CS40L26 was handled successfully. |
| */ |
| if (ret) |
| dev_err(dev, "Failed to process IRQ (%d): %u\n", irq, ret); |
| |
| return (irq1_count + irq2_count) ? IRQ_HANDLED : IRQ_NONE; |
| } |
| |
| static bool cs40l26_pseq_v1_addr_exists(struct cs40l26_private *cs40l26, |
| u16 addr, int *index) |
| { |
| int i; |
| |
| if (cs40l26->pseq_v1_len == 0) |
| return false; |
| |
| for (i = 0; i < cs40l26->pseq_v1_len; i++) { |
| if (cs40l26->pseq_v1_table[i].addr == addr) { |
| *index = i; |
| return true; |
| } |
| } |
| |
| *index = -1; |
| |
| return false; |
| } |
| |
| static int cs40l26_pseq_v1_write(struct cs40l26_private *cs40l26, |
| unsigned int pseq_v1_offset) |
| { |
| struct regmap *regmap = cs40l26->regmap; |
| unsigned int len = cs40l26->pseq_v1_len; |
| struct device *dev = cs40l26->dev; |
| u32 val; |
| u16 addr; |
| int ret; |
| |
| addr = cs40l26->pseq_v1_table[pseq_v1_offset].addr; |
| val = cs40l26->pseq_v1_table[pseq_v1_offset].val; |
| |
| /* the "upper half" first 24-bit word of the sequence pair is written |
| * to the write sequencer as: [23-16] addr{15-0}, |
| * [15-0] val{31-24} with bits [24-31] acting as a buffer |
| */ |
| ret = regmap_write(regmap, cs40l26->pseq_base + |
| (pseq_v1_offset * CS40L26_PSEQ_V1_STRIDE), |
| (addr << CS40L26_PSEQ_V1_ADDR_SHIFT) | |
| ((val & ~CS40L26_PSEQ_V1_VAL_MASK) |
| >> CS40L26_PSEQ_V1_VAL_SHIFT)); |
| if (ret) { |
| dev_err(dev, "Failed to write power on seq. (upper half)\n"); |
| return ret; |
| } |
| |
| /* the "lower half" of the address-value pair is written to the write |
| * sequencer as: [23-0] data{23-0} with bits [24-31] acting as a buffer |
| */ |
| ret = regmap_write(regmap, cs40l26->pseq_base + CL_DSP_BYTES_PER_WORD |
| + (pseq_v1_offset * CS40L26_PSEQ_V1_STRIDE), |
| val & CS40L26_PSEQ_V1_VAL_MASK); |
| if (ret) { |
| dev_err(dev, "Failed to write power on seq. (lower half)\n"); |
| return ret; |
| } |
| |
| /* end of sequence must be marked by list terminator */ |
| ret = regmap_write(regmap, cs40l26->pseq_base + |
| (len * CS40L26_PSEQ_V1_STRIDE), |
| CS40L26_PSEQ_V1_LIST_TERM); |
| if (ret) |
| dev_err(dev, "Failed to write power on seq. terminator\n"); |
| |
| return ret; |
| } |
| |
| static int cs40l26_pseq_v1_add_pair(struct cs40l26_private *cs40l26, u16 addr, |
| u32 val, bool replace) |
| { |
| unsigned int len = cs40l26->pseq_v1_len; |
| struct device *dev = cs40l26->dev; |
| unsigned int pseq_v1_offset, prev_val; |
| int ret, index; |
| |
| if (len >= CS40L26_PSEQ_V1_MAX_ENTRIES) { |
| dev_err(dev, "Power on seq. exceeded max number of entries\n"); |
| return -E2BIG; |
| } |
| |
| if (cs40l26_pseq_v1_addr_exists(cs40l26, addr, &index) && replace) { |
| prev_val = cs40l26->pseq_v1_table[index].val; |
| cs40l26->pseq_v1_table[index].val = val; |
| pseq_v1_offset = index; |
| } else { |
| cs40l26->pseq_v1_table[len].addr = addr; |
| cs40l26->pseq_v1_table[len].val = val; |
| cs40l26->pseq_v1_len++; |
| pseq_v1_offset = len; |
| } |
| |
| ret = cs40l26_pseq_v1_write(cs40l26, pseq_v1_offset); |
| if (ret) { /* If an error occurs during write, reset the sequence */ |
| if (index < 0) { /* No previous value for this address */ |
| cs40l26->pseq_v1_table[len].addr = 0; |
| cs40l26->pseq_v1_table[len].val = 0; |
| cs40l26->pseq_v1_len--; |
| } else { |
| cs40l26->pseq_v1_table[index].val = prev_val; |
| } |
| } |
| |
| return ret; |
| } |
| |
| int cs40l26_pseq_v1_multi_add_pair(struct cs40l26_private *cs40l26, |
| const struct reg_sequence *reg_seq, int num_regs, bool replace) |
| { |
| int ret, i; |
| |
| for (i = 0; i < num_regs; i++) { |
| ret = cs40l26_pseq_v1_add_pair(cs40l26, (u16) (reg_seq[i].reg & |
| CS40L26_PSEQ_V1_ADDR_MASK), reg_seq[i].def, |
| replace); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cs40l26_pseq_v1_multi_add_pair); |
| |
| static int cs40l26_pseq_v2_add_op(struct cs40l26_private *cs40l26, |
| int num_words, u32 *words) |
| { |
| struct regmap *regmap = cs40l26->regmap; |
| struct device *dev = cs40l26->dev; |
| u32 offset_for_new_op, *op_words; |
| int ret; |
| struct cs40l26_pseq_v2_op *pseq_v2_op_end, *pseq_v2_op_new, *op; |
| |
| /* get location of the list terminator */ |
| list_for_each_entry(pseq_v2_op_end, &cs40l26->pseq_v2_op_head, list) { |
| if (pseq_v2_op_end->operation == CS40L26_PSEQ_V2_OP_END) |
| break; |
| } |
| |
| if (pseq_v2_op_end->operation != CS40L26_PSEQ_V2_OP_END) { |
| dev_err(dev, "Failed to find END_OF_SCRIPT\n"); |
| return -EINVAL; |
| } |
| |
| offset_for_new_op = pseq_v2_op_end->offset; |
| |
| /* add new op to list */ |
| op_words = kzalloc(num_words * CL_DSP_BYTES_PER_WORD, GFP_KERNEL); |
| if (!op_words) |
| return -ENOMEM; |
| memcpy(op_words, words, num_words * CL_DSP_BYTES_PER_WORD); |
| pseq_v2_op_new = devm_kzalloc(dev, sizeof(*pseq_v2_op_new), GFP_KERNEL); |
| if (!pseq_v2_op_new) { |
| ret = -ENOMEM; |
| goto err_free; |
| } |
| |
| pseq_v2_op_new->size = num_words; |
| pseq_v2_op_new->offset = offset_for_new_op; |
| pseq_v2_op_new->words = op_words; |
| list_add(&pseq_v2_op_new->list, &cs40l26->pseq_v2_op_head); |
| |
| cs40l26->pseq_v2_num_ops++; |
| |
| /* bump end of script offset to accomodate new operation */ |
| pseq_v2_op_end->offset += num_words * CL_DSP_BYTES_PER_WORD; |
| |
| list_for_each_entry(op, &cs40l26->pseq_v2_op_head, list) { |
| if (op->offset >= offset_for_new_op) { |
| ret = regmap_bulk_write(regmap, cs40l26->pseq_base + |
| op->offset, |
| op->words, |
| op->size); |
| if (ret) { |
| dev_err(dev, "Failed to write op\n"); |
| return -EIO; |
| } |
| } |
| } |
| |
| return ret; |
| |
| err_free: |
| kfree(op_words); |
| |
| return ret; |
| } |
| |
| static int cs40l26_pseq_v2_add_write_reg_full(struct cs40l26_private *cs40l26, |
| u32 addr, u32 data, bool update_if_op_already_in_seq) |
| { |
| int ret; |
| struct cs40l26_pseq_v2_op *op; |
| u32 op_words[CS40L26_PSEQ_V2_OP_WRITE_REG_FULL_WORDS]; |
| |
| op_words[0] = (CS40L26_PSEQ_V2_OP_WRITE_REG_FULL << |
| CS40L26_PSEQ_V2_OP_SHIFT); |
| op_words[0] |= addr >> 16; |
| op_words[1] = (addr & 0x0000FFFF) << 8; |
| op_words[1] |= (data & 0xFF000000) >> 24; |
| op_words[2] = (data & 0x00FFFFFF); |
| |
| if (update_if_op_already_in_seq) { |
| list_for_each_entry(op, &cs40l26->pseq_v2_op_head, list) { |
| /* check if op with same op and addr already exists */ |
| if ((op->words[0] == op_words[0]) && |
| ((op->words[1] & 0xFFFFFF00) == |
| (op_words[1] & 0xFFFFFF00))) { |
| /* update data in the existing op and return */ |
| ret = regmap_bulk_write(cs40l26->regmap, |
| cs40l26->pseq_base + op->offset, |
| op_words, op->size); |
| if (ret) |
| dev_err(cs40l26->dev, |
| "Failed to update op\n"); |
| return ret; |
| } |
| } |
| } |
| |
| /* if no matching op is found or !update, add the op */ |
| ret = cs40l26_pseq_v2_add_op(cs40l26, |
| CS40L26_PSEQ_V2_OP_WRITE_REG_FULL_WORDS, |
| op_words); |
| |
| return ret; |
| } |
| |
| int cs40l26_pseq_v2_multi_add_write_reg_full(struct cs40l26_private *cs40l26, |
| const struct reg_sequence *reg_seq, int num_regs, |
| bool update_if_op_already_in_seq) |
| { |
| int ret, i; |
| |
| for (i = 0; i < num_regs; i++) { |
| ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26, |
| reg_seq[i].reg, reg_seq[i].def, |
| update_if_op_already_in_seq); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cs40l26_pseq_v2_multi_add_write_reg_full); |
| |
| static int cs40l26_pseq_v1_init(struct cs40l26_private *cs40l26) |
| { |
| struct device *dev = cs40l26->dev; |
| int ret, i, index = 0; |
| u8 upper_val = 0; |
| u16 addr = 0; |
| u32 val, word, algo_id; |
| |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM) |
| algo_id = CS40L26_PM_ALGO_ID; |
| else |
| algo_id = CS40L26_PM_ROM_ALGO_ID; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "POWER_ON_SEQUENCE", |
| CL_DSP_XM_UNPACKED_TYPE, algo_id, &cs40l26->pseq_base); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < CS40L26_PSEQ_V1_MAX_WRITES; i++) { |
| ret = regmap_read(cs40l26->regmap, |
| cs40l26->pseq_base + |
| (i * CL_DSP_BYTES_PER_WORD), &word); |
| if (ret) { |
| dev_err(dev, "Failed to read from power on seq.\n"); |
| return ret; |
| } |
| |
| if ((word & CS40L26_PSEQ_V1_LIST_TERM_MASK) == |
| CS40L26_PSEQ_V1_LIST_TERM) |
| break; |
| |
| if (i % CS40L26_PSEQ_V1_PAIR_NUM_WORDS) { /* lower half */ |
| index = i / CS40L26_PSEQ_V1_PAIR_NUM_WORDS; |
| val = (upper_val << CS40L26_PSEQ_V1_VAL_SHIFT) | |
| (word & CS40L26_PSEQ_V1_VAL_MASK); |
| |
| cs40l26->pseq_v1_table[index].addr = addr; |
| cs40l26->pseq_v1_table[index].val = val; |
| } else { /* upper half */ |
| addr = (word & CS40L26_PSEQ_V1_ADDR_WORD_MASK) >> |
| CS40L26_PSEQ_V1_ADDR_SHIFT; |
| |
| upper_val = word & CS40L26_PSEQ_V1_VAL_WORD_UPPER_MASK; |
| } |
| } |
| |
| if (i >= CS40L26_PSEQ_V1_MAX_WRITES) { |
| dev_err(dev, "Original sequence exceeds max # of entries\n"); |
| return -E2BIG; |
| } |
| |
| ret = regmap_write(cs40l26->regmap, cs40l26->pseq_base + |
| (i * CL_DSP_BYTES_PER_WORD), CS40L26_PSEQ_V1_LIST_TERM); |
| if (ret) |
| return ret; |
| |
| cs40l26->pseq_v1_len = index + 1; |
| return 0; |
| } |
| |
| static int cs40l26_pseq_v2_init(struct cs40l26_private *cs40l26) |
| { |
| struct device *dev = cs40l26->dev; |
| int ret, i, j, num_words, read_size; |
| u8 operation; |
| u32 words[CS40L26_PSEQ_V2_MAX_WORDS], algo_id, *op_words; |
| struct cs40l26_pseq_v2_op *pseq_v2_op; |
| |
| INIT_LIST_HEAD(&cs40l26->pseq_v2_op_head); |
| cs40l26->pseq_v2_num_ops = 0; |
| |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM) |
| algo_id = CS40L26_PM_ALGO_ID; |
| else |
| algo_id = CS40L26_PM_ROM_ALGO_ID; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "POWER_ON_SEQUENCE", |
| CL_DSP_XM_UNPACKED_TYPE, algo_id, &cs40l26->pseq_base); |
| if (ret) |
| return ret; |
| |
| /* read pseq memory space */ |
| i = 0; |
| while (i < CS40L26_PSEQ_V2_MAX_WORDS) { |
| read_size = min(CS40L26_MAX_I2C_READ_SIZE_BYTES, |
| CS40L26_PSEQ_V2_MAX_WORDS - i); |
| ret = regmap_bulk_read(cs40l26->regmap, |
| cs40l26->pseq_base + i * CL_DSP_BYTES_PER_WORD, |
| words + i, |
| read_size); |
| if (ret) { |
| dev_err(dev, "Failed to read from power on seq.\n"); |
| return ret; |
| } |
| i += read_size; |
| } |
| |
| i = 0; |
| while (i < CS40L26_PSEQ_V2_MAX_WORDS) { |
| operation = (words[i] & CS40L26_PSEQ_V2_OP_MASK) >> |
| CS40L26_PSEQ_V2_OP_SHIFT; |
| |
| /* get num words for given operation */ |
| for (j = 0; j < CS40L26_PSEQ_V2_NUM_OPS; j++) { |
| if (cs40l26_pseq_v2_op_sizes[j][0] == operation) { |
| num_words = cs40l26_pseq_v2_op_sizes[j][1]; |
| break; |
| } |
| } |
| |
| if (j == CS40L26_PSEQ_V2_NUM_OPS) { |
| dev_err(dev, "Failed to determine pseq_v2 op size\n"); |
| return -EINVAL; |
| } |
| |
| op_words = kzalloc(num_words * CL_DSP_BYTES_PER_WORD, |
| GFP_KERNEL); |
| if (!op_words) |
| return -ENOMEM; |
| memcpy(op_words, &words[i], num_words * CL_DSP_BYTES_PER_WORD); |
| |
| pseq_v2_op = devm_kzalloc(dev, sizeof(*pseq_v2_op), GFP_KERNEL); |
| if (!pseq_v2_op) { |
| ret = -ENOMEM; |
| goto err_free; |
| } |
| |
| pseq_v2_op->size = num_words; |
| pseq_v2_op->offset = i * CL_DSP_BYTES_PER_WORD; |
| pseq_v2_op->operation = operation; |
| pseq_v2_op->words = op_words; |
| list_add(&pseq_v2_op->list, &cs40l26->pseq_v2_op_head); |
| |
| cs40l26->pseq_v2_num_ops++; |
| i += num_words; |
| |
| if (operation == CS40L26_PSEQ_V2_OP_END) |
| break; |
| |
| } |
| |
| dev_dbg(dev, "PSEQ_V2 num ops: %d\n", cs40l26->pseq_v2_num_ops); |
| dev_dbg(dev, "offset\tsize\twords\n"); |
| list_for_each_entry(pseq_v2_op, &cs40l26->pseq_v2_op_head, list) { |
| dev_dbg(dev, "0x%04X\t%d", pseq_v2_op->offset, |
| pseq_v2_op->size); |
| for (j = 0; j < pseq_v2_op->size; j++) |
| dev_dbg(dev, "0x%08X", *(pseq_v2_op->words + j)); |
| } |
| |
| if (operation != CS40L26_PSEQ_V2_OP_END) { |
| dev_err(dev, "PSEQ_V2 END_OF_SCRIPT not found\n"); |
| return -E2BIG; |
| } |
| |
| return ret; |
| |
| err_free: |
| kfree(op_words); |
| |
| return ret; |
| } |
| |
| static int cs40l26_pseq_init(struct cs40l26_private *cs40l26) |
| { |
| int ret; |
| |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| ret = cs40l26_pseq_v1_init(cs40l26); |
| break; |
| case CS40L26_REVID_A1: |
| ret = cs40l26_pseq_v2_init(cs40l26); |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| return -EINVAL; |
| } |
| |
| if (ret) |
| dev_err(cs40l26->dev, "Failed to init pseq\n"); |
| |
| return ret; |
| } |
| |
| static void cs40l26_set_gain_worker(struct work_struct *work) |
| { |
| struct cs40l26_private *cs40l26 = |
| container_of(work, struct cs40l26_private, set_gain_work); |
| u16 amp_vol_pcm; |
| u32 reg, val; |
| int ret; |
| |
| pm_runtime_get_sync(cs40l26->dev); |
| mutex_lock(&cs40l26->lock); |
| |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| amp_vol_pcm = CS40L26_AMP_VOL_PCM_MAX & cs40l26->gain_pct; |
| |
| ret = regmap_update_bits(cs40l26->regmap, CS40L26_AMP_CTRL, |
| CS40L26_AMP_CTRL_VOL_PCM_MASK, amp_vol_pcm << |
| CS40L26_AMP_CTRL_VOL_PCM_SHIFT); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to update digtal gain\n"); |
| goto err_mutex; |
| } |
| |
| ret = regmap_read(cs40l26->regmap, CS40L26_AMP_CTRL, &val); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to read AMP control\n"); |
| goto err_mutex; |
| } |
| |
| ret = cs40l26_pseq_v1_add_pair(cs40l26, |
| CS40L26_AMP_CTRL, val, true); |
| if (ret) |
| dev_err(cs40l26->dev, "Failed to set gain in pseq\n"); |
| break; |
| case CS40L26_REVID_A1: |
| val = cs40l26_attn_q21_2_vals[cs40l26->gain_pct]; |
| |
| /* 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, val); |
| if (ret) |
| dev_err(cs40l26->dev, "Failed to set attenuation\n"); |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| } |
| |
| err_mutex: |
| mutex_unlock(&cs40l26->lock); |
| pm_runtime_mark_last_busy(cs40l26->dev); |
| pm_runtime_put_autosuspend(cs40l26->dev); |
| } |
| |
| static void cs40l26_vibe_start_worker(struct work_struct *work) |
| { |
| struct cs40l26_private *cs40l26 = container_of(work, |
| struct cs40l26_private, vibe_start_work); |
| struct device *dev = cs40l26->dev; |
| int ret = 0; |
| unsigned int reg, freq; |
| u32 index = 0, buzz_id; |
| u16 duration; |
| |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM) |
| buzz_id = CS40L26_BUZZGEN_ALGO_ID; |
| else |
| buzz_id = CS40L26_BUZZGEN_ROM_ALGO_ID; |
| |
| if (cs40l26->effect->u.periodic.waveform == FF_CUSTOM) |
| index = cs40l26->trigger_indices[cs40l26->effect->id]; |
| |
| if (index >= CS40L26_OWT_INDEX_START && index <= CS40L26_OWT_INDEX_END) |
| duration = CS40L26_SAMPS_TO_MS((cs40l26->owt_wlength & |
| CS40L26_WT_TYPE10_WAVELEN_MAX)); |
| else |
| duration = cs40l26->effect->replay.length; |
| |
| pm_runtime_get_sync(dev); |
| mutex_lock(&cs40l26->lock); |
| |
| if (duration > 0) /* Effect duration is known */ |
| hrtimer_start(&cs40l26->vibe_timer, |
| ktime_set(CS40L26_MS_TO_SECS(duration), |
| CS40L26_MS_TO_NS(duration % 1000)), HRTIMER_MODE_REL); |
| |
| cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_HAPTIC); |
| |
| switch (cs40l26->effect->u.periodic.waveform) { |
| case FF_CUSTOM: |
| ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, |
| index, CS40L26_DSP_MBOX_RESET); |
| if (ret) |
| goto err_mutex; |
| break; |
| case FF_SINE: |
| ret = cl_dsp_get_reg(cs40l26->dsp, "BUZZ_EFFECTS2_BUZZ_FREQ", |
| CL_DSP_XM_UNPACKED_TYPE, buzz_id, ®); |
| if (ret) |
| goto err_mutex; |
| |
| freq = CS40L26_MS_TO_HZ(cs40l26->effect->u.periodic.period); |
| |
| ret = regmap_write(cs40l26->regmap, reg, freq); |
| if (ret) |
| goto err_mutex; |
| |
| ret = regmap_write(cs40l26->regmap, reg + |
| CS40L26_BUZZGEN_LEVEL_OFFSET, |
| CS40L26_BUZZGEN_LEVEL_DEFAULT); |
| if (ret) |
| goto err_mutex; |
| |
| ret = regmap_write(cs40l26->regmap, reg + |
| CS40L26_BUZZGEN_DURATION_OFFSET, duration / |
| CS40L26_BUZZGEN_DURATION_DIV_STEP); |
| if (ret) |
| goto err_mutex; |
| |
| ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, |
| CS40L26_BUZZGEN_INDEX_CP_TRIGGER, |
| CS40L26_DSP_MBOX_RESET); |
| if (ret) |
| goto err_mutex; |
| break; |
| default: |
| dev_err(dev, "Invalid waveform type: 0x%X\n", |
| cs40l26->effect->u.periodic.waveform); |
| ret = -EINVAL; |
| goto err_mutex; |
| } |
| |
| err_mutex: |
| if (ret) |
| cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_STOPPED); |
| |
| mutex_unlock(&cs40l26->lock); |
| pm_runtime_mark_last_busy(dev); |
| pm_runtime_put_autosuspend(dev); |
| } |
| |
| static void cs40l26_vibe_stop_worker(struct work_struct *work) |
| { |
| struct cs40l26_private *cs40l26 = container_of(work, |
| struct cs40l26_private, vibe_stop_work); |
| unsigned int reg; |
| u32 algo_id; |
| int ret; |
| |
| pm_runtime_get_sync(cs40l26->dev); |
| mutex_lock(&cs40l26->lock); |
| |
| if (cs40l26->effect->u.periodic.waveform == FF_SINE) { |
| 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; |
| } |
| } else { |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_ROM) |
| algo_id = CS40L26_VIBEGEN_ROM_ALGO_ID; |
| else |
| algo_id = CS40L26_VIBEGEN_ALGO_ID; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "END_PLAYBACK", |
| CL_DSP_XM_UNPACKED_TYPE, algo_id, ®); |
| if (ret) |
| goto mutex_exit; |
| |
| ret = regmap_write(cs40l26->regmap, reg, 1); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to end VIBE playback\n"); |
| goto mutex_exit; |
| } |
| |
| ret = regmap_write(cs40l26->regmap, reg, 0); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to reset END_PLAYBACK\n"); |
| goto mutex_exit; |
| } |
| } |
| |
| cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_STOPPED); |
| |
| mutex_exit: |
| mutex_unlock(&cs40l26->lock); |
| pm_runtime_mark_last_busy(cs40l26->dev); |
| pm_runtime_put_autosuspend(cs40l26->dev); |
| } |
| |
| static enum hrtimer_restart cs40l26_vibe_timer(struct hrtimer *timer) |
| { |
| struct cs40l26_private *cs40l26 = |
| container_of(timer, struct cs40l26_private, vibe_timer); |
| |
| queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work); |
| |
| return HRTIMER_NORESTART; |
| } |
| |
| static void cs40l26_set_gain(struct input_dev *dev, u16 gain) |
| { |
| struct cs40l26_private *cs40l26 = input_get_drvdata(dev); |
| |
| cs40l26->gain_pct = gain; |
| |
| queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work); |
| } |
| |
| static int cs40l26_playback_effect(struct input_dev *dev, |
| int effect_id, int val) |
| { |
| struct cs40l26_private *cs40l26 = input_get_drvdata(dev); |
| struct ff_effect *effect; |
| |
| effect = &dev->ff->effects[effect_id]; |
| if (!effect) { |
| dev_err(cs40l26->dev, "No such effect to playback\n"); |
| return -EINVAL; |
| } |
| |
| cs40l26->effect = effect; |
| |
| if (val > 0) { |
| queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_start_work); |
| } else { |
| hrtimer_cancel(&cs40l26->vibe_timer); |
| queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work); |
| } |
| |
| return 0; |
| } |
| |
| static int cs40l26_owt_get_wlength(struct cs40l26_private *cs40l26, u8 index) |
| { |
| struct device *dev = cs40l26->dev; |
| struct cl_dsp_owt_header *entry; |
| struct cl_dsp_memchunk ch; |
| |
| if (index == 0) |
| return 0; |
| |
| entry = &cs40l26->dsp->wt_desc->owt.waves[index]; |
| |
| switch (entry->type) { |
| case WT_TYPE_V6_PCM_F0_REDC: |
| case WT_TYPE_V6_PCM_F0_REDC_VAR: |
| case WT_TYPE_V6_PWLE: |
| break; |
| default: |
| dev_err(dev, "Cannot size waveform type %u\n", entry->type); |
| return -EINVAL; |
| } |
| |
| ch = cl_dsp_memchunk_create(entry->data, sizeof(u32)); |
| |
| /* First 24 bits of each waveform is the length in samples @ 8 kHz */ |
| return cl_dsp_memchunk_read(&ch, 24); |
| } |
| |
| static void cs40l26_owt_get_section_info(struct cs40l26_private *cs40l26, |
| struct cl_dsp_memchunk *ch, |
| struct cs40l26_owt_section *sections, u8 nsections) |
| { |
| int i; |
| |
| for (i = 0; i < nsections; i++) { |
| cl_dsp_memchunk_read(ch, 8); /* Skip amplitude */ |
| sections[i].index = cl_dsp_memchunk_read(ch, 8); |
| sections[i].repeat = cl_dsp_memchunk_read(ch, 8); |
| sections[i].flags = cl_dsp_memchunk_read(ch, 8); |
| sections[i].delay = cl_dsp_memchunk_read(ch, 16); |
| |
| if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) { |
| cl_dsp_memchunk_read(ch, 8); /* Skip padding */ |
| sections[i].duration = cl_dsp_memchunk_read(ch, 16); |
| } |
| } |
| } |
| |
| static int cs40l26_owt_calculate_wlength(struct cs40l26_private *cs40l26, |
| struct cl_dsp_memchunk *ch) |
| { |
| u32 total_len = 0, section_len = 0, loop_len = 0; |
| bool in_loop = false; |
| struct cs40l26_owt_section *sections; |
| int ret = 0, i, wlen_whole; |
| u8 nsections, global_rep; |
| u32 dlen, wlen; |
| |
| cl_dsp_memchunk_read(ch, 8); /* Skip padding */ |
| nsections = cl_dsp_memchunk_read(ch, 8); |
| global_rep = cl_dsp_memchunk_read(ch, 8); |
| |
| if (nsections < 1) { |
| dev_err(cs40l26->dev, "Not enough sections for composite\n"); |
| return -EINVAL; |
| } |
| |
| sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section), |
| GFP_KERNEL); |
| if (!sections) { |
| dev_err(cs40l26->dev, "Failed to allocate OWT sections\n"); |
| return -ENOMEM; |
| } |
| |
| cs40l26_owt_get_section_info(cs40l26, ch, sections, nsections); |
| |
| for (i = 0; i < nsections; i++) { |
| wlen_whole = cs40l26_owt_get_wlength(cs40l26, |
| sections[i].index); |
| if (wlen_whole < 0) { |
| dev_err(cs40l26->dev, |
| "Failed to get wlength for index %u\n", |
| sections[i].index); |
| ret = wlen_whole; |
| goto err_free; |
| } |
| |
| if (wlen_whole & CS40L26_WT_TYPE10_WAVELEN_INDEF) { |
| if (!(sections[i].flags & |
| CS40L26_WT_TYPE10_COMP_DURATION_FLAG)) { |
| dev_err(cs40l26->dev, |
| "Indefinite entry needs duration\n"); |
| ret = -EINVAL; |
| goto err_free; |
| } |
| |
| wlen = CS40L26_WT_TYPE10_WAVELEN_MAX; |
| } else { |
| /* Length is 22 LSBs, filter out flags */ |
| wlen = wlen_whole & CS40L26_WT_TYPE10_WAVELEN_MAX; |
| } |
| |
| dlen = 8 * sections[i].delay; |
| |
| if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) { |
| if (wlen > (2 * sections[i].duration)) |
| wlen = 2 * sections[i].duration; |
| } |
| |
| section_len = wlen + dlen; |
| loop_len += section_len; |
| |
| if (sections[i].repeat == 0xFF) { |
| in_loop = true; |
| } else if (sections[i].repeat) { |
| total_len += (loop_len * (sections[i].repeat + 1)); |
| |
| in_loop = false; |
| loop_len = 0; |
| } else if (!in_loop) { |
| total_len += section_len; |
| loop_len = 0; |
| } |
| |
| section_len = 0; |
| } |
| |
| cs40l26->owt_wlength = (total_len * (global_rep + 1)) | |
| CS40L26_WT_TYPE10_WAVELEN_CALCULATED; |
| |
| err_free: |
| kfree(sections); |
| |
| return ret; |
| } |
| |
| static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, s16 *data, |
| u32 data_size) |
| { |
| bool pwle = (data[0] == 0x0000) ? false : true; |
| u32 data_size_bytes = data_size * 2; |
| struct device *dev = cs40l26->dev; |
| struct cl_dsp *dsp = cs40l26->dsp; |
| u32 full_data_size, header_size = CL_DSP_OWT_HEADER_ENTRY_SIZE; |
| unsigned int write_reg, reg, wt_offset, wt_size, wt_base; |
| struct cl_dsp_memchunk header_ch, data_ch; |
| u8 *full_data, *header; |
| int ret = 0; |
| |
| data_ch = cl_dsp_memchunk_create((void *) data, data_size_bytes); |
| |
| if (pwle) { |
| header_size += CS40L26_WT_TERM_SIZE; |
| |
| cs40l26->owt_wlength = cl_dsp_memchunk_read(&data_ch, 24); |
| } else { |
| header_size += CS40L26_WT_WLEN_TERM_SIZE; |
| |
| ret = cs40l26_owt_calculate_wlength(cs40l26, &data_ch); |
| if (ret) |
| return ret; |
| } |
| full_data_size = header_size + data_size_bytes; |
| |
| header = kcalloc(header_size, sizeof(u8), GFP_KERNEL); |
| if (!header) |
| return -ENOMEM; |
| |
| header_ch = cl_dsp_memchunk_create((void *) header, header_size); |
| /* Header */ |
| cl_dsp_memchunk_write(&header_ch, 16, |
| CS40L26_WT_HEADER_DEFAULT_FLAGS); |
| |
| if (pwle) |
| cl_dsp_memchunk_write(&header_ch, 8, WT_TYPE_V6_PWLE); |
| else |
| cl_dsp_memchunk_write(&header_ch, 8, WT_TYPE_V6_COMPOSITE); |
| |
| cl_dsp_memchunk_write(&header_ch, 24, CS40L26_WT_HEADER_OFFSET); |
| cl_dsp_memchunk_write(&header_ch, 24, full_data_size / |
| CL_DSP_BYTES_PER_WORD); |
| |
| cl_dsp_memchunk_write(&header_ch, 24, CS40L26_WT_HEADER_TERM); |
| |
| if (!pwle) /* Wlength is included in PWLE raw data */ |
| cl_dsp_memchunk_write(&header_ch, 24, cs40l26->owt_wlength); |
| |
| full_data = kcalloc(full_data_size, sizeof(u8), GFP_KERNEL); |
| if (!full_data) { |
| ret = -ENOMEM; |
| goto err_free; |
| } |
| |
| memcpy(full_data, header, header_ch.bytes); |
| memcpy(full_data + header_ch.bytes, data, data_size_bytes); |
| |
| pm_runtime_get_sync(dev); |
| |
| ret = cl_dsp_get_reg(dsp, "OWT_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); |
| if (ret) { |
| dev_err(dev, "Failed to get available WT size\n"); |
| goto err_pm; |
| } |
| |
| if (wt_size < full_data_size) { |
| dev_err(dev, "No space for OWT waveform\n"); |
| ret = -ENOSPC; |
| goto err_pm; |
| } |
| |
| ret = cl_dsp_get_reg(dsp, CS40L26_WT_NAME_XM, CL_DSP_XM_UNPACKED_TYPE, |
| CS40L26_VIBEGEN_ALGO_ID, &wt_base); |
| if (ret) |
| goto err_pm; |
| |
| write_reg = wt_base + (wt_offset * 4); |
| |
| ret = cl_dsp_raw_write(cs40l26->dsp, write_reg, full_data, |
| full_data_size, CL_DSP_MAX_WLEN); |
| if (ret) { |
| dev_err(dev, "Failed to sync OWT\n"); |
| goto err_pm; |
| } |
| |
| 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", |
| full_data_size, write_reg); |
| |
| err_pm: |
| pm_runtime_mark_last_busy(dev); |
| pm_runtime_put_autosuspend(dev); |
| |
| err_free: |
| kfree(header); |
| kfree(full_data); |
| |
| return ret; |
| } |
| |
| static int cs40l26_upload_effect(struct input_dev *dev, |
| struct ff_effect *effect, struct ff_effect *old) |
| { |
| struct cs40l26_private *cs40l26 = input_get_drvdata(dev); |
| struct device *cdev = cs40l26->dev; |
| s16 *raw_custom_data = NULL; |
| int ret = 0; |
| u32 trigger_index, min_index, max_index; |
| u16 index, bank; |
| |
| if (effect->type != FF_PERIODIC) { |
| dev_err(cdev, "Effect type 0x%X not supported\n", |
| effect->type); |
| return -EINVAL; |
| } |
| |
| if (effect->replay.length < 0 || |
| effect->replay.length > CS40L26_TIMEOUT_MS_MAX) { |
| dev_err(cdev, "Invalid playback duration: %d ms\n", |
| effect->replay.length); |
| return -EINVAL; |
| } |
| |
| switch (effect->u.periodic.waveform) { |
| case FF_CUSTOM: |
| raw_custom_data = |
| kzalloc(sizeof(s16) * |
| effect->u.periodic.custom_len, GFP_KERNEL); |
| if (!raw_custom_data) |
| return -ENOMEM; |
| |
| if (copy_from_user(raw_custom_data, |
| effect->u.periodic.custom_data, |
| sizeof(s16) * effect->u.periodic.custom_len)) { |
| dev_err(cdev, "Failed to get user data\n"); |
| ret = -EFAULT; |
| goto out_free; |
| } |
| |
| if (effect->u.periodic.custom_len > CS40L26_CUSTOM_DATA_SIZE) { |
| ret = cs40l26_ack_write(cs40l26, |
| CS40L26_DSP_VIRTUAL1_MBOX_1, |
| CS40L26_DSP_MBOX_CMD_OWT_RESET, |
| CS40L26_DSP_MBOX_RESET); |
| if (ret) |
| goto out_free; |
| |
| ret = cs40l26_owt_upload(cs40l26, raw_custom_data, |
| effect->u.periodic.custom_len); |
| if (ret) |
| goto out_free; |
| |
| bank = CS40L26_OWT_BANK_ID; |
| index = cs40l26->num_owt_effects; |
| } else { |
| bank = ((u16) raw_custom_data[0]); |
| index = ((u16) raw_custom_data[1]) & |
| CS40L26_MAX_INDEX_MASK; |
| } |
| |
| switch (bank) { |
| case CS40L26_RAM_BANK_ID: |
| min_index = CS40L26_RAM_INDEX_START; |
| max_index = min_index + cs40l26->num_waves - 1; |
| break; |
| case CS40L26_ROM_BANK_ID: |
| min_index = CS40L26_ROM_INDEX_START; |
| max_index = CS40L26_ROM_INDEX_END; |
| break; |
| case CS40L26_OWT_BANK_ID: |
| min_index = CS40L26_OWT_INDEX_START; |
| max_index = CS40L26_OWT_INDEX_END; |
| break; |
| default: |
| dev_err(cdev, "Bank ID (%u) out of bounds\n", bank); |
| ret = -EINVAL; |
| goto out_free; |
| } |
| |
| trigger_index = index + min_index; |
| |
| if (trigger_index >= min_index && trigger_index <= max_index) { |
| cs40l26->trigger_indices[effect->id] = trigger_index; |
| } else { |
| dev_err(cdev, "Trigger index (0x%X) out of bounds\n", |
| trigger_index); |
| ret = -EINVAL; |
| goto out_free; |
| } |
| |
| break; |
| case FF_SINE: |
| if (effect->u.periodic.period) { |
| if (effect->u.periodic.period |
| < CS40L26_BUZZGEN_PERIOD_MIN |
| || effect->u.periodic.period |
| > CS40L26_BUZZGEN_PERIOD_MAX) { |
| dev_err(cdev, |
| "%u ms period not within range (4-10 ms)\n", |
| effect->u.periodic.period); |
| return -EINVAL; |
| } |
| } else { |
| dev_err(cdev, "Sine wave period not specified\n"); |
| return -EINVAL; |
| } |
| break; |
| default: |
| dev_err(cdev, "Periodic waveform type 0x%X not supported\n", |
| effect->u.periodic.waveform); |
| return -EINVAL; |
| } |
| |
| out_free: |
| kfree(raw_custom_data); |
| |
| return ret; |
| } |
| |
| #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, |
| NULL, |
| }; |
| #endif |
| |
| static int cs40l26_input_init(struct cs40l26_private *cs40l26) |
| { |
| int ret; |
| struct device *dev = cs40l26->dev; |
| |
| cs40l26->input = devm_input_allocate_device(dev); |
| if (!cs40l26->input) |
| return -ENOMEM; |
| |
| cs40l26->input->name = "cs40l26_input"; |
| cs40l26->input->id.product = cs40l26->devid; |
| cs40l26->input->id.version = cs40l26->revid; |
| |
| input_set_drvdata(cs40l26->input, cs40l26); |
| input_set_capability(cs40l26->input, EV_FF, FF_PERIODIC); |
| input_set_capability(cs40l26->input, EV_FF, FF_CUSTOM); |
| input_set_capability(cs40l26->input, EV_FF, FF_SINE); |
| input_set_capability(cs40l26->input, EV_FF, FF_GAIN); |
| |
| ret = input_ff_create(cs40l26->input, FF_MAX_EFFECTS); |
| if (ret) { |
| dev_err(dev, "Failed to create FF device: %d\n", ret); |
| return ret; |
| } |
| |
| /* |
| * input_ff_create() automatically sets FF_RUMBLE capabilities; |
| * we want to restrtict this to only FF_PERIODIC |
| */ |
| __clear_bit(FF_RUMBLE, cs40l26->input->ffbit); |
| |
| cs40l26->input->ff->upload = cs40l26_upload_effect; |
| cs40l26->input->ff->playback = cs40l26_playback_effect; |
| cs40l26->input->ff->set_gain = cs40l26_set_gain; |
| |
| ret = input_register_device(cs40l26->input); |
| if (ret) { |
| dev_err(dev, "Cannot register input device: %d\n", ret); |
| return ret; |
| } |
| |
| hrtimer_init(&cs40l26->vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| cs40l26->vibe_timer.function = cs40l26_vibe_timer; |
| |
| #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; |
| } |
| #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) { |
| dev_err(dev, "Invalid device ID: 0x%06X\n", val); |
| return -EINVAL; |
| } |
| |
| cs40l26->devid = val; |
| |
| ret = regmap_read(regmap, CS40L26_REVID, &val); |
| if (ret) { |
| dev_err(dev, "Failed to read revision ID\n"); |
| return ret; |
| } |
| |
| val &= CS40L26_REVID_MASK; |
| switch (val) { |
| case CS40L26_REVID_A0: |
| case CS40L26_REVID_A1: |
| cs40l26->revid = val; |
| break; |
| default: |
| dev_err(dev, "Invalid device revision: 0x%02X\n", val); |
| return -EINVAL; |
| } |
| |
| dev_info(dev, "Cirrus Logic %s ID: 0x%06X, Revision: 0x%02X\n", |
| CS40L26_DEV_NAME, cs40l26->devid, cs40l26->revid); |
| |
| return 0; |
| } |
| |
| static int cs40l26_cl_dsp_init(struct cs40l26_private *cs40l26) |
| { |
| int ret = 0; |
| |
| cs40l26->dsp = cl_dsp_create(cs40l26->dev, cs40l26->regmap); |
| if (!cs40l26->dsp) |
| return -ENOMEM; |
| |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_ROM) { |
| cs40l26->fw.min_rev = CS40L26_FW_ROM_MIN_REV; |
| cs40l26->fw.num_coeff_files = 0; |
| cs40l26->fw.coeff_files = NULL; |
| } else { |
| if (cs40l26->revid == CS40L26_REVID_A1) |
| cs40l26->fw.min_rev = CS40L26_FW_A1_RAM_MIN_REV; |
| else |
| cs40l26->fw.min_rev = CS40L26_FW_A0_RAM_MIN_REV; |
| |
| cs40l26->fw.num_coeff_files = |
| ARRAY_SIZE(cs40l26_ram_coeff_files); |
| cs40l26->fw.coeff_files = cs40l26_ram_coeff_files; |
| |
| ret = cl_dsp_wavetable_create(cs40l26->dsp, |
| CS40L26_VIBEGEN_ALGO_ID, CS40L26_WT_NAME_XM, |
| CS40L26_WT_NAME_YM, "cs40l26.bin"); |
| } |
| |
| cs40l26->num_owt_effects = 0; |
| |
| return ret; |
| } |
| |
| static int cs40l26_wksrc_config(struct cs40l26_private *cs40l26) |
| { |
| u32 unmask_bits, mask_bits; |
| int ret; |
| |
| unmask_bits = BIT(CS40L26_IRQ1_WKSRC_STS_ANY) | |
| BIT(CS40L26_IRQ1_WKSRC_STS_GPIO1) | |
| BIT(CS40L26_IRQ1_WKSRC_STS_I2C); |
| |
| /* SPI support is not yet available */ |
| mask_bits = BIT(CS40L26_IRQ1_WKSRC_STS_SPI); |
| |
| if (cs40l26->devid == CS40L26_DEVID_A) |
| mask_bits |= (BIT(CS40L26_IRQ1_WKSRC_STS_GPIO2) | |
| BIT(CS40L26_IRQ1_WKSRC_STS_GPIO3) | |
| BIT(CS40L26_IRQ1_WKSRC_STS_GPIO4)); |
| else |
| unmask_bits |= (BIT(CS40L26_IRQ1_WKSRC_STS_GPIO2) | |
| BIT(CS40L26_IRQ1_WKSRC_STS_GPIO3) | |
| BIT(CS40L26_IRQ1_WKSRC_STS_GPIO4)); |
| |
| ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, mask_bits, |
| CS40L26_IRQ_MASK); |
| if (ret) |
| return ret; |
| |
| return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, |
| unmask_bits, CS40L26_IRQ_UNMASK); |
| } |
| |
| static int cs40l26_gpio_config(struct cs40l26_private *cs40l26) |
| { |
| u32 unmask_bits; |
| |
| unmask_bits = BIT(CS40L26_IRQ1_GPIO1_RISE) |
| | BIT(CS40L26_IRQ1_GPIO1_FALL); |
| |
| if (cs40l26->devid == CS40L26_DEVID_B) /* 4 GPIO config */ |
| unmask_bits |= (u32) (GENMASK(CS40L26_IRQ1_GPIO4_FALL, |
| CS40L26_IRQ1_GPIO2_RISE)); |
| |
| return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, |
| unmask_bits, CS40L26_IRQ_UNMASK); |
| } |
| |
| static int cs40l26_brownout_prevention_init(struct cs40l26_private *cs40l26) |
| { |
| struct device *dev = cs40l26->dev; |
| struct regmap *regmap = cs40l26->regmap; |
| u32 vbbr_thld = 0, vpbr_thld = 0; |
| u32 vbbr_max_att = 0, vpbr_max_att = 0; |
| u32 vpbr_atk_step = 0, vbbr_atk_step = 0; |
| u32 vpbr_atk_rate = 0, vbbr_atk_rate = 0; |
| u32 vpbr_wait = 0, vbbr_wait = 0; |
| u32 vpbr_rel_rate = 0, vbbr_rel_rate = 0; |
| u32 val; |
| int ret; |
| |
| ret = regmap_read(regmap, CS40L26_BLOCK_ENABLES2, &val); |
| if (ret) { |
| dev_err(dev, "Failed to read block enables 2\n"); |
| return ret; |
| } |
| |
| val |= ((cs40l26->pdata.vbbr_en << CS40L26_VBBR_EN_SHIFT) |
| | (cs40l26->pdata.vpbr_en << CS40L26_VPBR_EN_SHIFT)); |
| |
| ret = regmap_write(regmap, CS40L26_BLOCK_ENABLES2, val); |
| if (ret) { |
| dev_err(dev, "Failed to enable brownout prevention\n"); |
| return ret; |
| } |
| |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| ret = cs40l26_pseq_v1_add_pair(cs40l26, |
| CS40L26_BLOCK_ENABLES2, val, CS40L26_PSEQ_V1_REPLACE); |
| break; |
| case CS40L26_REVID_A1: |
| ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26, |
| CS40L26_BLOCK_ENABLES2, val, true); |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| return -EINVAL; |
| } |
| |
| if (ret) { |
| dev_err(dev, "Failed to sequence brownout prevention\n"); |
| return ret; |
| } |
| |
| if (cs40l26->pdata.vbbr_en) { |
| ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_2, |
| BIT(CS40L26_IRQ2_VBBR_ATT_CLR) | |
| BIT(CS40L26_IRQ2_VBBR_FLAG), |
| CS40L26_IRQ_UNMASK); |
| if (ret) |
| return ret; |
| |
| ret = regmap_read(regmap, CS40L26_VBBR_CONFIG, &val); |
| if (ret) { |
| dev_err(dev, "Failed to get VBBR config.\n"); |
| return ret; |
| } |
| |
| if (cs40l26->pdata.vbbr_thld) { |
| if (cs40l26->pdata.vbbr_thld |
| >= CS40L26_VBBR_THLD_MV_MAX) |
| vbbr_thld = CS40L26_VBBR_THLD_MAX; |
| else if (cs40l26->pdata.vbbr_thld |
| <= CS40L26_VBBR_THLD_MV_MIN) |
| vbbr_thld = CS40L26_VBBR_THLD_MIN; |
| else |
| vbbr_thld = cs40l26->pdata.vbbr_thld / |
| CS40L26_VBBR_THLD_MV_STEP; |
| |
| val &= ~CS40L26_VBBR_THLD_MASK; |
| val |= (vbbr_thld & CS40L26_VBBR_THLD_MASK); |
| } |
| |
| if (cs40l26->pdata.vbbr_max_att != CS40L26_VXBR_DEFAULT) { |
| if (cs40l26->pdata.vbbr_max_att >= |
| CS40L26_VXBR_MAX_ATT_MAX) |
| vbbr_max_att = CS40L26_VXBR_MAX_ATT_MAX; |
| else |
| vbbr_max_att = cs40l26->pdata.vbbr_max_att; |
| |
| val &= ~CS40L26_VXBR_MAX_ATT_MASK; |
| val |= ((vbbr_max_att << CS40L26_VXBR_MAX_ATT_SHIFT) |
| & CS40L26_VXBR_MAX_ATT_MASK); |
| } |
| |
| if (cs40l26->pdata.vbbr_atk_step) { |
| if (cs40l26->pdata.vbbr_atk_step |
| <= CS40L26_VXBR_ATK_STEP_MIN) |
| vbbr_atk_step = CS40L26_VXBR_ATK_STEP_MIN; |
| else if (cs40l26->pdata.vbbr_atk_step |
| >= CS40L26_VXBR_ATK_STEP_MAX_DB) |
| vbbr_atk_step = CS40L26_VXBR_ATK_STEP_MAX; |
| else |
| vbbr_atk_step = cs40l26->pdata.vbbr_atk_step; |
| |
| val &= ~CS40L26_VXBR_ATK_STEP_MASK; |
| val |= ((vbbr_atk_step << CS40L26_VXBR_ATK_STEP_SHIFT) |
| & CS40L26_VXBR_ATK_STEP_MASK); |
| } |
| |
| if (cs40l26->pdata.vbbr_atk_rate != |
| CS40L26_VXBR_DEFAULT) { |
| if (cs40l26->pdata.vbbr_atk_rate |
| > CS40L26_VXBR_ATK_RATE_MAX) |
| vbbr_atk_rate = CS40L26_VXBR_ATK_RATE_MAX; |
| else |
| vbbr_atk_rate = cs40l26->pdata.vbbr_atk_rate; |
| |
| val &= ~CS40L26_VXBR_ATK_RATE_MASK; |
| val |= ((vbbr_atk_rate << CS40L26_VXBR_ATK_RATE_SHIFT) |
| & CS40L26_VXBR_ATK_RATE_MASK); |
| } |
| |
| if (cs40l26->pdata.vbbr_wait != CS40L26_VXBR_DEFAULT) { |
| if (cs40l26->pdata.vbbr_wait > CS40L26_VXBR_WAIT_MAX) |
| vbbr_wait = CS40L26_VXBR_WAIT_MAX; |
| else |
| vbbr_wait = cs40l26->pdata.vbbr_wait; |
| |
| val &= ~CS40L26_VXBR_WAIT_MASK; |
| val |= ((vbbr_wait << CS40L26_VXBR_WAIT_SHIFT) |
| & CS40L26_VXBR_WAIT_MASK); |
| } |
| |
| if (cs40l26->pdata.vbbr_rel_rate != CS40L26_VXBR_DEFAULT) { |
| if (cs40l26->pdata.vbbr_rel_rate |
| > CS40L26_VXBR_REL_RATE_MAX) |
| vbbr_rel_rate = CS40L26_VXBR_REL_RATE_MAX; |
| else |
| vbbr_rel_rate = cs40l26->pdata.vbbr_rel_rate; |
| |
| val &= ~CS40L26_VXBR_REL_RATE_MASK; |
| val |= ((vbbr_rel_rate << CS40L26_VXBR_REL_RATE_SHIFT) |
| & CS40L26_VXBR_REL_RATE_MASK); |
| } |
| |
| ret = regmap_write(regmap, CS40L26_VBBR_CONFIG, val); |
| if (ret) { |
| dev_err(dev, "Failed to write VBBR config.\n"); |
| return ret; |
| } |
| |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| ret = cs40l26_pseq_v1_add_pair(cs40l26, |
| CS40L26_VBBR_CONFIG, val, |
| CS40L26_PSEQ_V1_REPLACE); |
| break; |
| case CS40L26_REVID_A1: |
| ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26, |
| CS40L26_VBBR_CONFIG, val, |
| true); |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| return -EINVAL; |
| } |
| if (ret) |
| return ret; |
| } |
| |
| ret = regmap_read(regmap, CS40L26_VPBR_CONFIG, &val); |
| if (ret) { |
| dev_err(dev, "Failed to get VBBR config.\n"); |
| return ret; |
| } |
| |
| if (cs40l26->pdata.vpbr_en) { |
| ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_2, |
| BIT(CS40L26_IRQ2_VPBR_ATT_CLR) | |
| BIT(CS40L26_IRQ2_VPBR_FLAG), |
| CS40L26_IRQ_UNMASK); |
| if (ret) |
| return ret; |
| |
| if (cs40l26->pdata.vpbr_thld) { |
| if (cs40l26->pdata.vpbr_thld |
| >= CS40L26_VPBR_THLD_MV_MAX) |
| vpbr_thld = CS40L26_VPBR_THLD_MAX; |
| else if (cs40l26->pdata.vpbr_thld |
| <= CS40L26_VPBR_THLD_MV_MIN) |
| vpbr_thld = CS40L26_VPBR_THLD_MIN; |
| else |
| vpbr_thld = (cs40l26->pdata.vpbr_thld / |
| CS40L26_VPBR_THLD_MV_DIV) |
| - CS40L26_VPBR_THLD_OFFSET; |
| |
| val &= ~CS40L26_VPBR_THLD_MASK; |
| val |= (vpbr_thld & CS40L26_VPBR_THLD_MASK); |
| |
| } |
| |
| if (cs40l26->pdata.vpbr_max_att != CS40L26_VXBR_DEFAULT) { |
| if (cs40l26->pdata.vpbr_max_att >= |
| CS40L26_VXBR_MAX_ATT_MAX) |
| vpbr_max_att = CS40L26_VXBR_MAX_ATT_MAX; |
| else |
| vpbr_max_att = cs40l26->pdata.vpbr_max_att; |
| |
| val &= ~CS40L26_VXBR_MAX_ATT_MASK; |
| val |= ((vpbr_max_att << CS40L26_VXBR_MAX_ATT_SHIFT) |
| & CS40L26_VXBR_MAX_ATT_MASK); |
| } |
| |
| if (cs40l26->pdata.vpbr_atk_step) { |
| if (cs40l26->pdata.vpbr_atk_step |
| <= CS40L26_VXBR_ATK_STEP_MIN) |
| vpbr_atk_step = CS40L26_VXBR_ATK_STEP_MIN; |
| else if (cs40l26->pdata.vpbr_atk_step |
| >= CS40L26_VXBR_ATK_STEP_MAX_DB) |
| vpbr_atk_step = CS40L26_VXBR_ATK_STEP_MAX; |
| else |
| vpbr_atk_step = cs40l26->pdata.vpbr_atk_step; |
| |
| val &= ~CS40L26_VXBR_ATK_STEP_MASK; |
| val |= ((vpbr_atk_step << CS40L26_VXBR_ATK_STEP_SHIFT) |
| & CS40L26_VXBR_ATK_STEP_MASK); |
| } |
| |
| if (cs40l26->pdata.vpbr_atk_rate != |
| CS40L26_VXBR_DEFAULT) { |
| if (cs40l26->pdata.vpbr_atk_rate |
| > CS40L26_VXBR_ATK_RATE_MAX) |
| vpbr_atk_rate = CS40L26_VXBR_ATK_RATE_MAX; |
| else |
| vpbr_atk_rate = cs40l26->pdata.vpbr_atk_rate; |
| |
| val &= ~CS40L26_VXBR_ATK_RATE_MASK; |
| val |= ((vpbr_atk_rate << CS40L26_VXBR_ATK_RATE_SHIFT) |
| & CS40L26_VXBR_ATK_RATE_MASK); |
| |
| } |
| |
| if (cs40l26->pdata.vpbr_wait != CS40L26_VXBR_DEFAULT) { |
| if (cs40l26->pdata.vpbr_wait > CS40L26_VXBR_WAIT_MAX) |
| vpbr_wait = CS40L26_VXBR_WAIT_MAX; |
| else |
| vpbr_wait = cs40l26->pdata.vpbr_wait; |
| |
| val &= ~CS40L26_VXBR_WAIT_MASK; |
| val |= ((vpbr_wait << CS40L26_VXBR_WAIT_SHIFT) |
| & CS40L26_VXBR_WAIT_MASK); |
| } |
| |
| if (cs40l26->pdata.vpbr_rel_rate != CS40L26_VXBR_DEFAULT) { |
| if (cs40l26->pdata.vpbr_rel_rate |
| > CS40L26_VXBR_REL_RATE_MAX) |
| vpbr_rel_rate = CS40L26_VXBR_REL_RATE_MAX; |
| else |
| vpbr_rel_rate = cs40l26->pdata.vpbr_rel_rate; |
| |
| val &= ~CS40L26_VXBR_REL_RATE_MASK; |
| val |= ((vpbr_rel_rate << CS40L26_VXBR_REL_RATE_SHIFT) |
| & CS40L26_VXBR_REL_RATE_MASK); |
| } |
| |
| ret = regmap_write(regmap, CS40L26_VPBR_CONFIG, val); |
| if (ret) { |
| dev_err(dev, "Failed to write VPBR config.\n"); |
| return ret; |
| } |
| |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| ret = cs40l26_pseq_v1_add_pair(cs40l26, |
| CS40L26_VPBR_CONFIG, val, |
| CS40L26_PSEQ_V1_REPLACE); |
| break; |
| case CS40L26_REVID_A1: |
| ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26, |
| CS40L26_VPBR_CONFIG, val, |
| true); |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| return -EINVAL; |
| } |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int cs40l26_get_num_waves(struct cs40l26_private *cs40l26, |
| u32 *num_waves) |
| { |
| int ret; |
| u32 reg; |
| |
| ret = cl_dsp_get_reg(cs40l26->dsp, "NUM_OF_WAVES", |
| CL_DSP_XM_UNPACKED_TYPE, |
| CS40L26_VIBEGEN_ALGO_ID, ®); |
| if (ret) |
| return ret; |
| |
| return regmap_read(cs40l26->regmap, reg, num_waves); |
| } |
| |
| static int cs40l26_verify_fw(struct cs40l26_private *cs40l26) |
| { |
| struct cs40l26_fw *fw = &cs40l26->fw; |
| unsigned int val; |
| int ret; |
| |
| ret = cl_dsp_fw_id_get(cs40l26->dsp, &val); |
| if (ret) |
| return ret; |
| |
| if (val != CS40L26_FW_ID) { |
| dev_err(cs40l26->dev, "Invalid firmware ID: 0x%X\n", val); |
| return -EINVAL; |
| } |
| |
| ret = cl_dsp_fw_rev_get(cs40l26->dsp, &val); |
| if (ret) |
| return ret; |
| |
| if (val < fw->min_rev) { |
| dev_err(cs40l26->dev, "Invalid firmware revision: %d.%d.%d\n", |
| (int) CL_DSP_GET_MAJOR(val), |
| (int) CL_DSP_GET_MINOR(val), |
| (int) CL_DSP_GET_PATCH(val)); |
| return -EINVAL; |
| } |
| |
| dev_info(cs40l26->dev, "Firmware revision %d.%d.%d\n", |
| (int) CL_DSP_GET_MAJOR(val), |
| (int) CL_DSP_GET_MINOR(val), |
| (int) CL_DSP_GET_PATCH(val)); |
| |
| return 0; |
| } |
| |
| static int cs40l26_asp_config(struct cs40l26_private *cs40l26) |
| { |
| struct reg_sequence *dsp1rx_config = |
| kcalloc(2, sizeof(struct reg_sequence), GFP_KERNEL); |
| int ret; |
| |
| if (!dsp1rx_config) { |
| dev_err(cs40l26->dev, "Failed to allocate reg. sequence\n"); |
| return -ENOMEM; |
| } |
| |
| dsp1rx_config[0].reg = CS40L26_DSP1RX1_INPUT; |
| dsp1rx_config[0].def = CS40L26_DATA_SRC_ASPRX1; |
| dsp1rx_config[1].reg = CS40L26_DSP1RX5_INPUT; |
| dsp1rx_config[1].def = CS40L26_DATA_SRC_ASPRX2; |
| |
| ret = regmap_multi_reg_write(cs40l26->regmap, dsp1rx_config, 2); |
| if (ret) { |
| dev_err(cs40l26->dev, "Failed to configure ASP\n"); |
| goto err_free; |
| } |
| |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| ret = cs40l26_pseq_v1_multi_add_pair(cs40l26, dsp1rx_config, 2, |
| CS40L26_PSEQ_V1_REPLACE); |
| break; |
| case CS40L26_REVID_A1: |
| ret = cs40l26_pseq_v2_multi_add_write_reg_full(cs40l26, |
| dsp1rx_config, 2, true); |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| ret = -EINVAL; |
| goto err_free; |
| } |
| |
| if (ret) |
| dev_err(cs40l26->dev, "Failed to add ASP config to pseq\n"); |
| |
| err_free: |
| kfree(dsp1rx_config); |
| |
| return ret; |
| } |
| |
| static int cs40l26_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; |
| } |
| |
| switch (cs40l26->revid) { |
| case CS40L26_REVID_A0: |
| ret = cs40l26_pseq_v1_add_pair(cs40l26, |
| CS40L26_BST_DCM_CTL, val, true); |
| break; |
| case CS40L26_REVID_A1: |
| ret = cs40l26_pseq_v2_add_write_reg_full(cs40l26, |
| CS40L26_BST_DCM_CTL, val, true); |
| break; |
| default: |
| dev_err(cs40l26->dev, "Revid ID not supported: %02X\n", |
| cs40l26->revid); |
| ret = -EINVAL; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int cs40l26_dsp_config(struct cs40l26_private *cs40l26) |
| { |
| struct regmap *regmap = cs40l26->regmap; |
| struct device *dev = cs40l26->dev; |
| unsigned int val; |
| int ret; |
| u32 reg; |
| |
| ret = cs40l26_verify_fw(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| ret = regmap_update_bits(regmap, CS40L26_PWRMGT_CTL, |
| CS40L26_MEM_RDY_MASK, |
| CS40L26_ENABLE << CS40L26_MEM_RDY_SHIFT); |
| if (ret) { |
| dev_err(dev, "Failed to set MEM_RDY to initialize RAM\n"); |
| goto err_out; |
| } |
| |
| if (cs40l26->fw_mode == CS40L26_FW_MODE_RAM) { |
| ret = cl_dsp_get_reg(cs40l26->dsp, "CALL_RAM_INIT", |
| CL_DSP_XM_UNPACKED_TYPE, |
| CS40L26_FW_ID, ®); |
| if (ret) |
| goto err_out; |
| |
| ret = cs40l26_dsp_write(cs40l26, reg, CS40L26_ENABLE); |
| if (ret) |
| goto err_out; |
| } |
| |
| cs40l26->fw_loaded = true; |
| |
| ret = cs40l26_dsp_start(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| ret = cs40l26_pseq_init(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| ret = cs40l26_iseq_init(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, |
| BIT(CS40L26_IRQ1_VIRTUAL2_MBOX_WR), CS40L26_IRQ_UNMASK); |
| if (ret) |
| goto err_out; |
| |
| ret = cs40l26_wksrc_config(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| ret = cs40l26_gpio_config(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| ret = cs40l26_bst_dcm_config(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| ret = cs40l26_brownout_prevention_init(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| /* ensure firmware running */ |
| ret = cl_dsp_get_reg(cs40l26->dsp, "HALO_STATE", |
| CL_DSP_XM_UNPACKED_TYPE, CS40L26_FW_ID, |
| ®); |
| if (ret) |
| goto err_out; |
| |
| ret = regmap_read(regmap, reg, &val); |
| if (ret) { |
| dev_err(dev, "Failed to read HALO_STATE\n"); |
| goto err_out; |
| } |
| |
| if (val != CS40L26_DSP_HALO_STATE_RUN) { |
| dev_err(dev, "Firmware in unexpected state: 0x%X\n", val); |
| goto err_out; |
| } |
| |
| ret = cs40l26_pm_runtime_setup(cs40l26); |
| if (ret) |
| goto err_out; |
| |
| pm_runtime_get_sync(dev); |
| |
| ret = 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, &cs40l26->num_waves); |
| if (!ret) |
| dev_info(dev, "%s loaded with %u RAM waveforms\n", |
| CS40L26_DEV_NAME, cs40l26->num_waves); |
| |
| pm_err: |
| pm_runtime_mark_last_busy(dev); |
| pm_runtime_put_autosuspend(dev); |
| err_out: |
| enable_irq(cs40l26->irq); |
| return ret; |
| } |
| |
| static void cs40l26_firmware_load(const struct firmware *fw, void *context) |
| { |
| struct cs40l26_private *cs40l26 = (struct cs40l26_private *)context; |
| struct device *dev = cs40l26->dev; |
| const struct firmware *coeff_fw; |
| int ret, i; |
| |
| if (!fw) { |
| dev_err(dev, "Failed to request firmware file\n"); |
| return; |
| } |
| |
| cs40l26->pm_ready = false; |
| |
| ret = cl_dsp_firmware_parse(cs40l26->dsp, fw, |
| cs40l26->fw_mode == CS40L26_FW_MODE_RAM); |
| release_firmware(fw); |
| if (ret) |
| return; |
| |
| for (i = 0; i < cs40l26->fw.num_coeff_files; i++) { |
| request_firmware(&coeff_fw, cs40l26->fw.coeff_files[i], |
| dev); |
| if (!coeff_fw) { |
| dev_warn(dev, "Continuing...\n"); |
| continue; |
| } |
| |
| if (cl_dsp_coeff_file_parse(cs40l26->dsp, coeff_fw)) |
| dev_warn(dev, "Continuing...\n"); |
| else |
| dev_dbg(dev, "%s Loaded Successfully\n", |
| cs40l26->fw.coeff_files[i]); |
| } |
| |
| cs40l26_dsp_config(cs40l26); |
| } |
| |
| static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26) |
| { |
| struct device *dev = cs40l26->dev; |
| struct device_node *np = dev->of_node; |
| u32 val; |
| |
| if (!np) { |
| dev_err(dev, "No platform data found\n"); |
| return -ENOENT; |
| } |
| |
| if (of_property_read_bool(np, "cirrus,basic-config")) |
| cs40l26->fw_mode = CS40L26_FW_MODE_ROM; |
| else |
| cs40l26->fw_mode = CS40L26_FW_MODE_RAM; |
| |
| cs40l26->pdata.vbbr_en = |
| of_property_read_bool(np, "cirrus,vbbr-enable"); |
| |
| if (!of_property_read_u32(np, "cirrus,vbbr-thld-mv", &val)) |
| cs40l26->pdata.vbbr_thld = val; |
| |
| if (!of_property_read_u32(np, "cirrus,vbbr-max-att-db", &val)) |
| cs40l26->pdata.vbbr_max_att = val; |
| else |
| cs40l26->pdata.vbbr_max_att = CS40L26_VXBR_DEFAULT; |
| |
| if (!of_property_read_u32(np, "cirrus,vbbr-atk-step", &val)) |
| cs40l26->pdata.vbbr_atk_step = val; |
| |
| if (!of_property_read_u32(np, "cirrus,vbbr-atk-rate", &val)) |
| cs40l26->pdata.vbbr_atk_rate = val; |
| |
| if (!of_property_read_u32(np, "cirrus,vbbr-wait", &val)) |
| cs40l26->pdata.vbbr_wait = val; |
| else |
| cs40l26->pdata.vbbr_wait = CS40L26_VXBR_DEFAULT; |
| |
| if (!of_property_read_u32(np, "cirrus,vbbr-rel-rate", &val)) |
| cs40l26->pdata.vbbr_rel_rate = val; |
| else |
| cs40l26->pdata.vbbr_rel_rate = CS40L26_VXBR_DEFAULT; |
| |
| cs40l26->pdata.vpbr_en = |
| of_property_read_bool(np, "cirrus,vpbr-enable"); |
| |
| if (!of_property_read_u32(np, "cirrus,vpbr-thld-mv", &val)) |
| cs40l26->pdata.vpbr_thld = val; |
| |
| if (!of_property_read_u32(np, "cirrus,vpbr-max-att-db", &val)) |
| cs40l26->pdata.vpbr_max_att = val; |
| else |
| cs40l26->pdata.vpbr_max_att = CS40L26_VXBR_DEFAULT; |
| |
| if (!of_property_read_u32(np, "cirrus,vpbr-atk-step", &val)) |
| cs40l26->pdata.vpbr_atk_step = val; |
| |
| if (!of_property_read_u32(np, "cirrus,vpbr-atk-rate", &val)) |
| cs40l26->pdata.vpbr_atk_rate = val; |
| else |
| cs40l26->pdata.vpbr_atk_rate = CS40L26_VXBR_DEFAULT; |
| |
| if (!of_property_read_u32(np, "cirrus,vpbr-wait", &val)) |
| cs40l26->pdata.vpbr_wait = val; |
| else |
| cs40l26->pdata.vpbr_wait = CS40L26_VXBR_DEFAULT; |
| |
| if (!of_property_read_u32(np, "cirrus,vpbr-rel-rate", &val)) |
| cs40l26->pdata.vpbr_rel_rate = val; |
| else |
| cs40l26->pdata.vpbr_rel_rate = CS40L26_VXBR_DEFAULT; |
| |
| 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; |
| |
| return 0; |
| } |
| |
| int cs40l26_probe(struct cs40l26_private *cs40l26, |
| struct cs40l26_platform_data *pdata) |
| { |
| struct device *dev = cs40l26->dev; |
| struct regulator *vp_consumer, *va_consumer; |
| int ret; |
| |
| mutex_init(&cs40l26->lock); |
| |
| cs40l26->vibe_workqueue = alloc_ordered_workqueue("vibe_workqueue", |
| WQ_HIGHPRI); |
| if (!cs40l26->vibe_workqueue) { |
| ret = -ENOMEM; |
| goto err; |
| } |
| |
| INIT_WORK(&cs40l26->vibe_start_work, cs40l26_vibe_start_worker); |
| INIT_WORK(&cs40l26->vibe_stop_work, cs40l26_vibe_stop_worker); |
| INIT_WORK(&cs40l26->set_gain_work, cs40l26_set_gain_worker); |
| |
| ret = devm_regulator_bulk_get(dev, CS40L26_NUM_SUPPLIES, |
| cs40l26_supplies); |
| if (ret) { |
| dev_err(dev, "Failed to request core supplies: %d\n", ret); |
| goto err; |
| } |
| |
| vp_consumer = cs40l26_supplies[CS40L26_VP_SUPPLY].consumer; |
| va_consumer = cs40l26_supplies[CS40L26_VA_SUPPLY].consumer; |
| |
| if (pdata) { |
| cs40l26->pdata = *pdata; |
| } else if (cs40l26->dev->of_node) { |
| ret = cs40l26_handle_platform_data(cs40l26); |
| if (ret) |
| goto err; |
| } else { |
| dev_err(dev, "No platform data found\n"); |
| ret = -ENOENT; |
| goto err; |
| } |
| |
| ret = regulator_bulk_enable(CS40L26_NUM_SUPPLIES, cs40l26_supplies); |
| if (ret) { |
| dev_err(dev, "Failed to enable core supplies\n"); |
| goto err; |
| } |
| |
| cs40l26->reset_gpio = devm_gpiod_get_optional(dev, "reset", |
| GPIOD_OUT_LOW); |
| if (IS_ERR(cs40l26->reset_gpio)) { |
| dev_err(dev, "Failed to get reset GPIO\n"); |
| |
| ret = PTR_ERR(cs40l26->reset_gpio); |
| cs40l26->reset_gpio = NULL; |
| goto err; |
| } |
| |
| usleep_range(CS40L26_MIN_RESET_PULSE_WIDTH, |
| CS40L26_MIN_RESET_PULSE_WIDTH + 100); |
| |
| gpiod_set_value_cansleep(cs40l26->reset_gpio, CS40L26_ENABLE); |
| |
| usleep_range(CS40L26_CONTROL_PORT_READY_DELAY, |
| CS40L26_CONTROL_PORT_READY_DELAY + 100); |
| |
| ret = cs40l26_part_num_resolve(cs40l26); |
| if (ret) |
| goto err; |
| |
| ret = devm_request_threaded_irq(dev, cs40l26->irq, NULL, cs40l26_irq, |
| IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, |
| "cs40l26", cs40l26); |
| if (ret) { |
| dev_err(dev, "Failed to request threaded IRQ\n"); |
| goto err; |
| } |
| /* the /ALERT pin may be asserted prior to firmware initialization. |
| * Disable the interrupt handler until firmware has downloaded |
| * so erroneous interrupt requests are ignored |
| */ |
| disable_irq(cs40l26->irq); |
| |
| ret = cs40l26_cl_dsp_init(cs40l26); |
| if (ret) |
| goto err; |
| |
| ret = cs40l26_dsp_pre_config(cs40l26); |
| if (ret) |
| goto err; |
| |
| request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, |
| CS40L26_FW_FILE_NAME, dev, GFP_KERNEL, cs40l26, |
| cs40l26_firmware_load); |
| |
| ret = cs40l26_input_init(cs40l26); |
| if (ret) |
| goto err; |
| |
| ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cs40l26_devs, |
| CS40L26_NUM_MFD_DEVS, NULL, 0, NULL); |
| if (ret) { |
| dev_err(dev, "Failed to register codec component\n"); |
| goto err; |
| } |
| |
| return 0; |
| |
| err: |
| cs40l26_remove(cs40l26); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(cs40l26_probe); |
| |
| int cs40l26_remove(struct cs40l26_private *cs40l26) |
| { |
| struct regulator *vp_consumer = |
| cs40l26_supplies[CS40L26_VP_SUPPLY].consumer; |
| struct regulator *va_consumer = |
| cs40l26_supplies[CS40L26_VA_SUPPLY].consumer; |
| |
| disable_irq(cs40l26->irq); |
| mutex_destroy(&cs40l26->lock); |
| |
| |
| if (cs40l26->pm_ready) |
| cs40l26_pm_runtime_teardown(cs40l26); |
| |
| if (cs40l26->vibe_workqueue) { |
| destroy_workqueue(cs40l26->vibe_workqueue); |
| cancel_work_sync(&cs40l26->vibe_start_work); |
| cancel_work_sync(&cs40l26->vibe_stop_work); |
| cancel_work_sync(&cs40l26->set_gain_work); |
| } |
| |
| if (vp_consumer) |
| regulator_disable(vp_consumer); |
| |
| if (va_consumer) |
| regulator_disable(va_consumer); |
| |
| gpiod_set_value_cansleep(cs40l26->reset_gpio, CS40L26_DISABLE); |
| |
| if (cs40l26->vibe_timer.function) |
| hrtimer_cancel(&cs40l26->vibe_timer); |
| |
| if (cs40l26->vibe_init_success) { |
| #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); |
| #else |
| sysfs_remove_groups(&cs40l26->dev->kobj, |
| cs40l26_dev_attr_groups); |
| #endif |
| } |
| |
| if (cs40l26->input) |
| input_unregister_device(cs40l26->input); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cs40l26_remove); |
| |
| int cs40l26_suspend(struct device *dev) |
| { |
| struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); |
| |
| if (!cs40l26->pm_ready) { |
| dev_dbg(dev, "Suspend call ignored\n"); |
| return 0; |
| } |
| |
| dev_dbg(cs40l26->dev, "%s: Enabling hibernation\n", __func__); |
| |
| return cs40l26_pm_state_transition(cs40l26, |
| CS40L26_PM_STATE_ALLOW_HIBERNATE); |
| } |
| EXPORT_SYMBOL(cs40l26_suspend); |
| |
| int cs40l26_sys_suspend(struct device *dev) |
| { |
| struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); |
| struct i2c_client *i2c_client = to_i2c_client(dev); |
| |
| dev_dbg(cs40l26->dev, "System suspend, disabling IRQ\n"); |
| |
| disable_irq(i2c_client->irq); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cs40l26_sys_suspend); |
| |
| int cs40l26_sys_suspend_noirq(struct device *dev) |
| { |
| struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); |
| struct i2c_client *i2c_client = to_i2c_client(dev); |
| |
| dev_dbg(cs40l26->dev, "Late system suspend, re-enabling IRQ\n"); |
| enable_irq(i2c_client->irq); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cs40l26_sys_suspend_noirq); |
| |
| int cs40l26_resume(struct device *dev) |
| { |
| struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); |
| |
| if (!cs40l26->pm_ready) { |
| dev_dbg(dev, "Resume call ignored\n"); |
| return 0; |
| } |
| |
| dev_dbg(cs40l26->dev, "%s: Disabling hibernation\n", __func__); |
| |
| return cs40l26_pm_state_transition(cs40l26, |
| CS40L26_PM_STATE_PREVENT_HIBERNATE); |
| } |
| EXPORT_SYMBOL(cs40l26_resume); |
| |
| int cs40l26_sys_resume(struct device *dev) |
| { |
| struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); |
| struct i2c_client *i2c_client = to_i2c_client(dev); |
| |
| dev_dbg(cs40l26->dev, "System resume, re-enabling IRQ\n"); |
| |
| enable_irq(i2c_client->irq); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cs40l26_sys_resume); |
| |
| int cs40l26_sys_resume_noirq(struct device *dev) |
| { |
| struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); |
| struct i2c_client *i2c_client = to_i2c_client(dev); |
| |
| dev_dbg(cs40l26->dev, "Early system resume, disabling IRQ\n"); |
| |
| disable_irq(i2c_client->irq); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cs40l26_sys_resume_noirq); |
| |
| MODULE_DESCRIPTION("CS40L26 Boosted Mono Class D Amplifier for Haptics"); |
| MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>"); |
| MODULE_LICENSE("GPL"); |