summaryrefslogtreecommitdiff
path: root/cs40l26/cs40l26.c
diff options
context:
space:
mode:
authorTai Kuo <taikuo@google.com>2021-11-26 14:11:12 +0800
committerTai Kuo <taikuo@google.com>2022-02-08 11:34:35 +0800
commit9a0148defd9807369323af67c0fba52d5cff2d15 (patch)
treed4781c90a50a63b74f042fbc33a6206d295ef3a9 /cs40l26/cs40l26.c
parentd1ea3b1e371f0d929aeea176dd409cd1e5f644c7 (diff)
downloadamplifiers-9a0148defd9807369323af67c0fba52d5cff2d15.tar.gz
cs40l26: merge CirrusLogic dsp v3.1.6 and cs40l26 v3.4.0
Branch: v5.10-cirrus-dsp-fw Tag: cl-dsp-fw-v3.1.6_5.10 Files: drivers/firmware/cirrus/cl_dsp.c include/linux/firmware/cirrus/cl_dsp.h Features: - cl-dsp-fw-v3.1.4_5.10 Add memchunk flush Bug fixes: - cl-dsp-fw-v3.1.6_5.10 Fix syntax error caught by checkpatch.pl - cl_dsp-v3.1.5_5.10 Fix bug that allowed for a potential NULL pointer dereference - cl-dsp-fw-v3.1.4_5.10 Add empty check for get dsp register. Commits: 9ee092c firmware: cirrus: Fix code syntax error 16af046 firmware: cirrus: Avoid potential NULL dereference f916a30 firmware: cirrus: Add cl_dsp_memchunk_flush() 4cf0400 firmware: cirrus: Add empty check in dsp_get_reg 30034b6 firmware: cirrus: arm64 compiler fix Change-Id: I2efbf1611bad281b67fc035b31b30925e869082b --- Branch: v5.10-cs40l26 Tag: cs40l26-v3.4.0_5.10 Files: drivers/input/misc/cs40l26-i2c.c (No changes) drivers/input/misc/cs40l26-spi.c (No changes) drivers/input/misc/cs40l26-sysfs.c drivers/input/misc/cs40l26-tables.c (No changes) drivers/input/misc/cs40l26.c include/linux/mfd/cs40l26.h sound/soc/codecs/cs40l26.c -> cs40l26-codec.c Features: - cs40l26-v3.4.0_5.10 Improved information from DSP to host mailbox transactions Additional debug logging - cs40l26-v3.3.0_5.10 Expose LRADELAYSAMPS to user Support for DVL tuning Contingency for DSP start failure on boot - cs40l26-v3.1.2_5.10 Add Support for Nested Composite Waveform Improve BUZZGEN GPI trigger support Support GPI triggers of OWT waveforms Fix NUM_OF_WAVES race condition Improve deferred firmware loading DT parameter to set idle -> hibernate DSP timeout Bug fixes: - cs40l26-v3.4.0_5.10 Updated handling of vibe_state Explicitly stop haptic playback before starting I2S stream Prevent asynchrnous I2S start message to mailbox from driver Improved code clarity Resolution of clang, cppcheck, sparse, and checkpatch warnings - cs40l26-v3.3.0_5.10 Prevent asynchrnous erase/upload calls Prevent asynchronous interrupts from affecting code under mutex lock Code improvements to write sequencer Static analysis fixes Memory leak correction - cs40l26-v3.1.2_5.10 Check for the DSP to be initialized before uploading a waveform Do not allow stop command only if the ASP is active. Commits: ba22584 input: cs40l26: Resolve Warnings caused by DEVICE_ATTR b1f116d input: cs40l26: Clarify read size in pseq_init() 3447c80 ASoC: cs40l26: Use new vibe_state handling a3e37ec input: cs40l26: Update handling of vibe_state 8ec6127 ASoC: cs40l26: remove old vibe_state handling bc9f10c input: cs40l26: Add debug message in erase_effect dd88832 input: cs40l26: Support new FW event notifiers 4a963ec input: cs40l26: Add support for FW branch fead3ba input: cs40l26: Stop playback before starting I2S 0827b6c ASoC: cs40l26: Always unlock mutex in cs40l26_pcm_ev 4753f8c input: cs40l26: Resolve clang warnings e7f25c9 input: cs40l26: Resolve cppcheck warnings 5fcb292 input: cs40l26: Ensure vibe_state_set() called under mutex lock 75ad45a input: cs40l26: Additional defines and logging 6da8378 ASoC: cs40l26: Use asp_start() to start I2S stream a14e17b input: cs40l26: Replace asp_workqueue with asp_start function 7191239 ASoC: cs40l26: Remove usage of asp_work 5a32ccf input: cs40l26: Don't remove OWT effect from list if delete fails c7272dc ASoC: cs40l26: Expose delay controls dd4491a input: cs40l26: Load DVL tuning file 4b84df2 input: cs40l26: Use devm_ for allocation 7163487 input: cs40l26: Set up parameters before dsp start 31b55dd input: cs40l26: Retry if DSP is not in run state 626f83c input: cs40l26: Set timeout to max on fw re-load f33edc6 ASoC: cs40l26: Fix potential memory leak aebbb26 input: cs40l26: Fix static analysis errors 7a5489e input: cs40l26: Adding debug prints ff55e98 input: cs40l26: Add erase and upload operations to ordered workqueue 9c9da81 input: cs40l26: Mutex protect interrupt handler 332bb17 input: cs40l26: Write sequencer updates c5be6bb input: cs40l26: No stop command during ASP 7e806e4 input: cs40l26: use type-specific min function f93a999 input: cs40l26: Check for uninitialised DSP in cs40l26_header c659dec input: cs40l26: Changing GPI map API f12a21b input: cs40l26: Add delay before stopping playback 3732052 input: cs40l26: Delay registration of IRQ until firmware is loaded 6de4136 input: cs40l26: Add fw load defer 7d5e01b input: cs40l26: Correct memory space calculation for 4-byte words e8123a3 input: cs40l26: Add Support for Nested Composite Waveforms aa472ba input: cs40l26: Read NUM_OF_WAVES before each trigger 98ff3b5 input: cs40l26: Add dt parameter for DSP transition Bug: 205688153 Test: N/A. Signed-off-by: Tai Kuo <taikuo@google.com> Change-Id: I606c7d75891f85995eadf367d4b833b682f8e23d
Diffstat (limited to 'cs40l26/cs40l26.c')
-rw-r--r--cs40l26/cs40l26.c1600
1 files changed, 1189 insertions, 411 deletions
diff --git a/cs40l26/cs40l26.c b/cs40l26/cs40l26.c
index f8c7afd..be2e22b 100644
--- a/cs40l26/cs40l26.c
+++ b/cs40l26/cs40l26.c
@@ -19,6 +19,29 @@ static inline bool is_owt(unsigned int index)
index <= CS40L26_OWT_INDEX_END;
}
+static inline bool is_buzz(unsigned int index)
+{
+ return (index >= CS40L26_BUZZGEN_INDEX_START &&
+ index <= CS40L26_BUZZGEN_INDEX_END);
+}
+
+static inline bool is_ram(unsigned int index)
+{
+ return (index >= CS40L26_RAM_INDEX_START &&
+ index <= CS40L26_RAM_INDEX_END);
+}
+
+static inline bool is_rom(unsigned int index)
+{
+ return (index >= CS40L26_ROM_INDEX_START &&
+ index <= CS40L26_ROM_INDEX_END);
+}
+
+static inline bool section_complete(struct cs40l26_owt_section *s)
+{
+ return s->delay ? true : false;
+}
+
static int cs40l26_dsp_read(struct cs40l26_private *cs40l26, u32 reg, u32 *val)
{
struct regmap *regmap = cs40l26->regmap;
@@ -153,15 +176,44 @@ int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state)
}
EXPORT_SYMBOL(cs40l26_dsp_state_get);
-int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26,
- u32 timeout_ms)
+static int cs40l26_pm_timer_timeout_ticks4_set(struct cs40l26_private *cs40l26,
+ u32 pm_timer_timeout_ticks4)
{
- u32 timeout_ticks = timeout_ms * CS40L26_PM_TICKS_MS_DIV;
struct regmap *regmap = cs40l26->regmap;
u32 lower_val, reg;
u8 upper_val;
int ret;
+ upper_val = (pm_timer_timeout_ticks4 >>
+ CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT) &
+ CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK;
+
+ lower_val = pm_timer_timeout_ticks4 &
+ CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK;
+
+ ret = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS",
+ CL_DSP_XM_UNPACKED_TYPE, CS40L26_PM_ALGO_ID, &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap,
+ reg + CS40L26_PM_TIMER_TIMEOUT_TICKS4_LOWER_OFFSET,
+ lower_val);
+ if (ret)
+ return ret;
+
+ return regmap_write(regmap,
+ reg + CS40L26_PM_TIMER_TIMEOUT_TICKS4_UPPER_OFFSET,
+ upper_val);
+}
+
+int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26,
+ u32 timeout_ms)
+{
+ u32 timeout_ticks = timeout_ms * CS40L26_PM_TICKS_MS_DIV;
+ u32 lower_val, upper_val, reg;
+ int ret;
+
upper_val = (timeout_ticks >> CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT) &
CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK;
@@ -172,12 +224,12 @@ int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26,
if (ret)
return ret;
- ret = regmap_write(regmap, reg + CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET,
+ ret = cs40l26_dsp_write(cs40l26, reg + CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET,
lower_val);
if (ret)
return ret;
- return regmap_write(regmap, reg + CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET,
+ return cs40l26_dsp_write(cs40l26, reg + CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET,
upper_val);
}
EXPORT_SYMBOL(cs40l26_pm_timeout_ms_set);
@@ -248,37 +300,88 @@ static void cs40l26_pm_runtime_teardown(struct cs40l26_private *cs40l26)
cs40l26->pm_ready = false;
}
+static int cs40l26_check_pm_lock(struct cs40l26_private *cs40l26, bool *locked)
+{
+ int ret;
+ unsigned int dsp_lock;
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_A1_PM_STATE_LOCKS_STATIC_REG
+ + CS40L26_DSP_LOCK3_OFFSET, &dsp_lock);
+ if (ret)
+ return ret;
+
+ if (dsp_lock & CS40L26_DSP_LOCK3_MASK)
+ *locked = true;
+ else
+ *locked = false;
+
+ return 0;
+}
+
int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
enum cs40l26_pm_state state)
{
struct device *dev = cs40l26->dev;
u32 cmd;
- int ret;
+ u8 curr_state;
+ bool dsp_lock;
+ int ret, i;
cmd = (u32) CS40L26_DSP_MBOX_PM_CMD_BASE + state;
switch (state) {
case CS40L26_PM_STATE_WAKEUP:
- /* intentionally fall through */
- case CS40L26_PM_STATE_PREVENT_HIBERNATE:
ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
cmd, CS40L26_DSP_MBOX_RESET);
+ if (ret)
+ return ret;
+
+ break;
+ case CS40L26_PM_STATE_PREVENT_HIBERNATE:
+ for (i = 0; i < CS40L26_DSP_STATE_ATTEMPTS; i++) {
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ cmd, CS40L26_DSP_MBOX_RESET);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_dsp_state_get(cs40l26, &curr_state);
+ if (ret)
+ return ret;
+
+ if (curr_state == CS40L26_DSP_STATE_ACTIVE)
+ break;
+
+ if (curr_state == CS40L26_DSP_STATE_STANDBY) {
+ ret = cs40l26_check_pm_lock(cs40l26, &dsp_lock);
+ if (ret)
+ return ret;
+
+ if (dsp_lock)
+ break;
+ }
+ usleep_range(5000, 5100);
+ }
+
+ if (i == CS40L26_DSP_STATE_ATTEMPTS) {
+ dev_err(cs40l26->dev, "DSP not starting\n");
+ return -ETIMEDOUT;
+ }
+
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);
+ if (ret)
+ return ret;
+
break;
default:
dev_err(dev, "Invalid PM state: %u\n", state);
return -EINVAL;
}
- if (ret)
- return ret;
-
cs40l26->pm_state = state;
return 0;
@@ -287,8 +390,20 @@ int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
static int cs40l26_dsp_start(struct cs40l26_private *cs40l26)
{
u8 dsp_state;
+ unsigned int val;
int ret;
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_A1_DSP_REQ_ACTIVE_REG,
+ &val);
+ if (ret) {
+ dev_err(cs40l26->dev, "Can't read REQ ACTIVE %d\n", ret);
+ return ret;
+ }
+
+ if (val & CS40L26_DSP_PM_ACTIVE)
+ dev_warn(cs40l26->dev, "REQ ACTIVE is 0x%x\n", val);
+
ret = regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL,
CS40L26_DSP_CCM_CORE_RESET);
if (ret) {
@@ -384,7 +499,7 @@ static int cs40l26_dsp_pre_config(struct cs40l26_private *cs40l26)
if (ret)
return ret;
- for (i = 0; i < 10; i++) {
+ for (i = 0; i < CS40L26_DSP_SHUTDOWN_MAX_ATTEMPTS; i++) {
ret = cs40l26_dsp_state_get(cs40l26, &dsp_state);
if (ret)
return ret;
@@ -399,7 +514,7 @@ static int cs40l26_dsp_pre_config(struct cs40l26_private *cs40l26)
CS40L26_MS_TO_US(timeout_ms) + 100);
}
- if (i == 10) {
+ if (i == CS40L26_DSP_SHUTDOWN_MAX_ATTEMPTS) {
dev_err(cs40l26->dev, "DSP Core could not be shut down\n");
return -EINVAL;
}
@@ -499,21 +614,33 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
}
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");
+ case CS40L26_DSP_MBOX_COMPLETE_MBOX:
+ dev_dbg(dev, "Mailbox: COMPLETE_MBOX\n");
+ cs40l26_vibe_state_update(cs40l26,
+ CS40L26_VIBE_STATE_EVENT_MBOX_COMPLETE);
+ break;
+ case CS40L26_DSP_MBOX_COMPLETE_GPIO:
+ dev_dbg(dev, "Mailbox: COMPLETE_GPIO\n");
+ cs40l26_vibe_state_update(cs40l26,
+ CS40L26_VIBE_STATE_EVENT_GPIO_COMPLETE);
+ break;
+ case CS40L26_DSP_MBOX_COMPLETE_I2S:
+ dev_dbg(dev, "Mailbox: COMPLETE_I2S\n");
+ break;
+ case CS40L26_DSP_MBOX_TRIGGER_GPIO:
+ dev_dbg(dev, "Mailbox: TRIGGER_GPIO\n");
+ cs40l26_vibe_state_update(cs40l26,
+ CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER);
break;
case CS40L26_DSP_MBOX_PM_AWAKE:
cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
- dev_dbg(dev, "HALO Core is awake\n");
+ dev_dbg(dev, "Mailbox: AWAKE\n");
break;
case CS40L26_DSP_MBOX_F0_EST_START:
- dev_dbg(dev, "F0_EST_START\n");
+ dev_dbg(dev, "Mailbox: F0_EST_START\n");
break;
case CS40L26_DSP_MBOX_F0_EST_DONE:
- dev_dbg(dev, "F0_EST_DONE\n");
+ dev_dbg(dev, "Mailbox: F0_EST_DONE\n");
if (cs40l26->cal_requested &
CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q) {
cs40l26->cal_requested &=
@@ -528,10 +655,10 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
break;
case CS40L26_DSP_MBOX_REDC_EST_START:
- dev_dbg(dev, "REDC_EST_START\n");
+ dev_dbg(dev, "Mailbox: REDC_EST_START\n");
break;
case CS40L26_DSP_MBOX_REDC_EST_DONE:
- dev_dbg(dev, "REDC_EST_DONE\n");
+ dev_dbg(dev, "Mailbox: REDC_EST_DONE\n");
if (cs40l26->cal_requested &
CS40L26_CALIBRATION_CONTROL_REQUEST_REDC) {
cs40l26->cal_requested &=
@@ -545,14 +672,13 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
}
break;
case CS40L26_DSP_MBOX_LE_EST_START:
- dev_dbg(dev, "LE_EST_START\n");
+ dev_dbg(dev, "Mailbox: LE_EST_START\n");
break;
case CS40L26_DSP_MBOX_LE_EST_DONE:
- dev_dbg(dev, "LE_EST_DONE\n");
+ dev_dbg(dev, "Mailbox: LE_EST_DONE\n");
break;
case CS40L26_DSP_MBOX_SYS_ACK:
- dev_err(dev, "Mbox buffer value (0x%X) not supported\n",
- val);
+ dev_err(dev, "Mailbox: ACK\n");
return -EPERM;
default:
dev_err(dev, "MBOX buffer value (0x%X) is invalid\n",
@@ -564,51 +690,94 @@ static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
return 0;
}
-void cs40l26_asp_worker(struct work_struct *work)
-{
- struct cs40l26_private *cs40l26 =
- container_of(work, struct cs40l26_private, asp_work);
+int cs40l26_asp_start(struct cs40l26_private *cs40l26) {
+ struct device *dev = cs40l26->dev;
+ bool ack = false;
+ unsigned int val;
+ int ret;
+
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_STOP_PLAYBACK, CS40L26_DSP_MBOX_RESET);
+ if (ret) {
+ dev_err(dev, "Failed to stop playback before I2S start\n");
+ return ret;
+ }
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_DSP_MBOX_CMD_START_I2S);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to start I2S\n");
+ return ret;
+ }
+
+ while (!ack) {
+ usleep_range(CS40L26_DSP_TIMEOUT_US_MIN,
+ CS40L26_DSP_TIMEOUT_US_MAX);
- cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
- CS40L26_DSP_MBOX_CMD_START_I2S, CS40L26_DSP_MBOX_RESET);
+ ret = regmap_read(cs40l26->regmap, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ &val);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to read from MBOX_1\n");
+ return ret;
+ }
+
+ if (val == CS40L26_DSP_MBOX_RESET)
+ ack = true;
+ }
+
+ return 0;
}
-EXPORT_SYMBOL(cs40l26_asp_worker);
+EXPORT_SYMBOL(cs40l26_asp_start);
-void cs40l26_vibe_state_set(struct cs40l26_private *cs40l26,
- enum cs40l26_vibe_state new_state)
+void cs40l26_vibe_state_update(struct cs40l26_private *cs40l26,
+ enum cs40l26_vibe_state_event event)
{
- if (cs40l26->vibe_state == new_state)
+ if (!mutex_is_locked(&cs40l26->lock)) {
+ dev_err(cs40l26->dev, "%s must be called under mutex lock\n",
+ __func__);
return;
+ }
+
+ switch (event) {
+ case CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK:
+ case CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER:
+ cs40l26->effects_in_flight++;
+ break;
+ case CS40L26_VIBE_STATE_EVENT_MBOX_COMPLETE:
+ case CS40L26_VIBE_STATE_EVENT_GPIO_COMPLETE:
+ cs40l26->effects_in_flight--;
+ if (cs40l26->effects_in_flight < 0)
+ dev_err(cs40l26->dev, "effects_in_flight < 0, %d\n",
+ cs40l26->effects_in_flight);
+ if (cs40l26->effects_in_flight == 0 && cs40l26->asp_enable)
+ if (cs40l26_asp_start(cs40l26))
+ return;
+ break;
+ case CS40L26_VIBE_STATE_EVENT_ASP_START:
+ cs40l26->asp_enable = true;
+ break;
+ case CS40L26_VIBE_STATE_EVENT_ASP_STOP:
+ cs40l26->asp_enable = false;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Invalid vibe state event: %d\n", event);
+ break;
+ }
- if (new_state == CS40L26_VIBE_STATE_STOPPED && cs40l26->asp_enable)
- queue_work(cs40l26->asp_workqueue, &cs40l26->asp_work);
+ if (cs40l26->effects_in_flight)
+ cs40l26->vibe_state = CS40L26_VIBE_STATE_HAPTIC;
+ else if (cs40l26->asp_enable)
+ cs40l26->vibe_state = CS40L26_VIBE_STATE_ASP;
+ else
+ cs40l26->vibe_state = CS40L26_VIBE_STATE_STOPPED;
- 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,
- &reg);
- if (ret)
- return ret;
-
- ret = regmap_read(cs40l26->regmap, reg, count);
- if (ret)
- dev_err(cs40l26->dev, "Failed to get event count\n");
-
- return ret;
-}
+EXPORT_SYMBOL(cs40l26_vibe_state_update);
static int cs40l26_error_release(struct cs40l26_private *cs40l26,
unsigned int err_rls, bool bst_err)
@@ -764,13 +933,13 @@ static int cs40l26_irq_update_mask(struct cs40l26_private *cs40l26, u32 irq_reg,
}
static int cs40l26_handle_irq1(struct cs40l26_private *cs40l26,
- enum cs40l26_irq1 irq1, bool trigger)
+ enum cs40l26_irq1 irq1)
{
struct device *dev = cs40l26->dev;
u32 err_rls = 0;
unsigned int reg, val;
- bool bst_err;
- int ret;
+ bool bst_err = false;
+ int ret = 0;
switch (irq1) {
case CS40L26_IRQ1_GPIO1_RISE:
@@ -791,10 +960,7 @@ static int cs40l26_handle_irq1(struct cs40l26_private *cs40l26,
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);
+ (irq1 % 2) ? "falling" : "rising");
}
cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
@@ -846,9 +1012,6 @@ static int cs40l26_handle_irq1(struct cs40l26_private *cs40l26,
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");
@@ -1066,8 +1229,6 @@ static irqreturn_t cs40l26_irq(int irq, void *data)
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)) {
@@ -1081,6 +1242,9 @@ static irqreturn_t cs40l26_irq(int irq, void *data)
}
ret = pm_runtime_get_sync(dev);
+
+ mutex_lock(&cs40l26->lock);
+
if (ret < 0) {
pm_runtime_set_active(dev);
@@ -1108,17 +1272,11 @@ static irqreturn_t cs40l26_irq(int irq, void *data)
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);
+ ret = cs40l26_handle_irq1(cs40l26, i);
if (ret)
goto err;
else
@@ -1158,7 +1316,7 @@ static irqreturn_t cs40l26_irq(int irq, void *data)
}
err:
- cs40l26_event_count_get(cs40l26, &cs40l26->event_count);
+ mutex_unlock(&cs40l26->lock);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
@@ -1178,7 +1336,7 @@ static int cs40l26_pseq_add_op(struct cs40l26_private *cs40l26,
struct regmap *regmap = cs40l26->regmap;
struct device *dev = cs40l26->dev;
u32 offset_for_new_op, *op_words;
- int ret;
+ int ret = 0;
struct cs40l26_pseq_op *pseq_op_end, *pseq_op_new, *op;
/* get location of the list terminator */
@@ -1340,12 +1498,58 @@ static int cs40l26_pseq_add_write_reg_h16(struct cs40l26_private *cs40l26,
return ret;
}
+static int cs40l26_pseq_add_write_reg_l16(struct cs40l26_private *cs40l26,
+ u32 addr, u16 data, bool update_if_op_already_in_seq)
+{
+ int ret;
+ struct device *dev = cs40l26->dev;
+ struct cs40l26_pseq_op *op;
+ u32 op_words[CS40L26_PSEQ_OP_WRITE_REG_L16_WORDS];
+
+ memset(op_words, 0, CS40L26_PSEQ_OP_WRITE_REG_L16_WORDS*sizeof(u32));
+
+ if (addr & CS40L26_PSEQ_INVALID_ADDR) {
+ dev_err(dev, "invalid address for pseq write_reg_l16\n");
+ return -EINVAL;
+ }
+
+ op_words[0] = (CS40L26_PSEQ_OP_WRITE_REG_L16 <<
+ CS40L26_PSEQ_OP_SHIFT);
+ op_words[0] |= (addr & CS40L26_PSEQ_WORD1_MASK) >> 8;
+ op_words[1] = (addr & CS40L26_PSEQ_WORD2_MASK) << 16;
+ op_words[1] |= data;
+
+ if (update_if_op_already_in_seq) {
+ list_for_each_entry(op, &cs40l26->pseq_op_head, list) {
+ /* check if op with same op and addr already exists */
+ if ((op->words[0] == op_words[0]) &&
+ ((op->words[1] & CS40L26_PSEQ_EQ_MASK) ==
+ (op_words[1] & CS40L26_PSEQ_EQ_MASK))) {
+ /* 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;
+ }
+ }
+ }
+
+ ret = cs40l26_pseq_add_op(cs40l26,
+ CS40L26_PSEQ_OP_WRITE_REG_L16_WORDS,
+ op_words);
+
+ return ret;
+}
+
static int cs40l26_pseq_init(struct cs40l26_private *cs40l26)
{
struct device *dev = cs40l26->dev;
u32 words[CS40L26_PSEQ_MAX_WORDS], *op_words;
struct cs40l26_pseq_op *pseq_op;
- int ret, i, j, num_words, read_size;
+ int ret, i, j, num_words, read_size_words;
u8 operation;
INIT_LIST_HEAD(&cs40l26->pseq_op_head);
@@ -1360,18 +1564,20 @@ static int cs40l26_pseq_init(struct cs40l26_private *cs40l26)
/* read pseq memory space */
i = 0;
while (i < CS40L26_PSEQ_MAX_WORDS) {
- read_size = min(CS40L26_MAX_I2C_READ_SIZE_BYTES,
- CS40L26_PSEQ_MAX_WORDS - i);
+ if ((CS40L26_PSEQ_MAX_WORDS - i) >
+ CS40L26_MAX_I2C_READ_SIZE_WORDS)
+ read_size_words = CS40L26_MAX_I2C_READ_SIZE_WORDS;
+ else
+ read_size_words = CS40L26_PSEQ_MAX_WORDS - i;
ret = regmap_bulk_read(cs40l26->regmap,
cs40l26->pseq_base + i * CL_DSP_BYTES_PER_WORD,
- words + i,
- read_size);
+ words + i, read_size_words);
if (ret) {
dev_err(dev, "Failed to read from power on seq.\n");
return ret;
}
- i += read_size;
+ i += read_size_words;
}
i = 0;
@@ -1436,6 +1642,20 @@ static int cs40l26_update_reg_defaults_via_pseq(struct cs40l26_private *cs40l26)
struct device *dev = cs40l26->dev;
int ret;
+ ret = cs40l26_pseq_add_write_reg_l16(cs40l26, CS40L26_NGATE1_INPUT,
+ CS40L26_DATA_SRC_DSP1TX4, true);
+ if (ret) {
+ dev_err(dev, "Failed to sequence Noise Gate Input register default updates\n");
+ return ret;
+ }
+
+ ret = cs40l26_pseq_add_write_reg_full(cs40l26, CS40L26_MIXER_NGATE_CH1_CFG,
+ CS40L26_MIXER_NGATE_CH1_CFG_DEFAULT_NEW, true);
+ if (ret) {
+ dev_err(dev, "Failed to sequence Mixer Noise Gate register default updates\n");
+ return ret;
+ }
+
/* set SPK_DEFAULT_HIZ to 1 */
ret = cs40l26_pseq_add_write_reg_h16(cs40l26,
CS40L26_TST_DAC_MSM_CONFIG,
@@ -1448,88 +1668,95 @@ static int cs40l26_update_reg_defaults_via_pseq(struct cs40l26_private *cs40l26)
}
static int cs40l26_buzzgen_set(struct cs40l26_private *cs40l26, u16 freq,
- u16 level, u16 duration, u8 offset)
+ u16 level, u16 duration, u8 buzzgen_num)
{
int ret;
- unsigned int base_reg, offset_reg;
+ unsigned int base_reg, freq_reg, level_reg, duration_reg;
+ /* BUZZ_EFFECTS1_BUZZ_xxx are initially populated by contents of OTP.
+ * The buzz specified by these controls is triggered by writing
+ * 0x01800080 to the DSP mailbox
+ */
ret = cl_dsp_get_reg(cs40l26->dsp, "BUZZ_EFFECTS1_BUZZ_FREQ",
CL_DSP_XM_UNPACKED_TYPE, CS40L26_BUZZGEN_ALGO_ID, &base_reg);
if (ret)
return ret;
- offset_reg = base_reg + (offset * 12);
+ freq_reg = base_reg
+ + ((buzzgen_num) * CS40L26_BUZZGEN_CONFIG_OFFSET);
+ level_reg = base_reg
+ + ((buzzgen_num) * CS40L26_BUZZGEN_CONFIG_OFFSET)
+ + CS40L26_BUZZGEN_LEVEL_OFFSET;
+ duration_reg = base_reg
+ + ((buzzgen_num) * CS40L26_BUZZGEN_CONFIG_OFFSET)
+ + CS40L26_BUZZGEN_DURATION_OFFSET;
- ret = regmap_write(cs40l26->regmap, offset_reg, freq);
+ ret = regmap_write(cs40l26->regmap, freq_reg, freq);
if (ret) {
dev_err(cs40l26->dev, "Failed to write BUZZGEN frequency\n");
return ret;
}
- ret = regmap_write(cs40l26->regmap, offset_reg + 4,
- CS40L26_BUZZGEN_LEVEL_DEFAULT);
+ ret = regmap_write(cs40l26->regmap, level_reg, level);
if (ret) {
dev_err(cs40l26->dev, "Failed to write BUZZGEN level\n");
return ret;
}
- ret = regmap_write(cs40l26->regmap, offset_reg + 8, duration / 4);
+ ret = regmap_write(cs40l26->regmap, duration_reg, duration / 4);
if (ret)
dev_err(cs40l26->dev, "Failed to write BUZZGEN duration\n");
return ret;
}
-static int cs40l26_gpio_index_set(struct cs40l26_private *cs40l26,
- struct ff_effect *effect)
+static int cs40l26_map_gpi_to_haptic(struct cs40l26_private *cs40l26,
+ u32 index, u8 bank, struct ff_effect *effect)
{
+ int ret;
+ bool edge, ev_handler_bank_ram, owt;
+ u32 reg, write_val;
u16 button = effect->trigger.button;
- u8 bank = (button & CS40L26_BTN_BANK_MASK) >> CS40L26_BTN_BANK_SHIFT;
- u8 edge = (button & CS40L26_BTN_EDGE_MASK) >> CS40L26_BTN_EDGE_SHIFT;
u8 gpio = (button & CS40L26_BTN_NUM_MASK) >> CS40L26_BTN_NUM_SHIFT;
- u8 index = button & CS40L26_BTN_INDEX_MASK;
- u8 buzz = button & CS40L26_BTN_BUZZ_MASK;
- u16 write_val, freq;
- u8 offset;
- u32 reg;
- int ret;
- if (gpio != 1) {
- dev_err(cs40l26->dev, "GPIO%u not supported on 0x%02X\n", gpio,
- cs40l26->revid);
+ edge = (button & CS40L26_BTN_EDGE_MASK) >> CS40L26_BTN_EDGE_SHIFT;
+
+ switch (bank) {
+ case CS40L26_RAM_BANK_ID:
+ owt = false;
+ ev_handler_bank_ram = true;
+ break;
+ case CS40L26_ROM_BANK_ID:
+ owt = false;
+ ev_handler_bank_ram = false;
+ break;
+ case CS40L26_OWT_BANK_ID:
+ owt = true;
+ ev_handler_bank_ram = true;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Effect bank %u not supported\n",
+ bank);
return -EINVAL;
}
- if (edge > 1) {
- dev_err(cs40l26->dev, "Invalid GPI edge %u\n", edge);
+ if (gpio != CS40L26_GPIO1) {
+ dev_err(cs40l26->dev, "GPIO%u not supported on 0x%02X\n", gpio,
+ cs40l26->revid);
return -EINVAL;
}
- offset = ((edge ^ 1)) * 4;
- reg = cs40l26->event_map_base + offset;
+ reg = cs40l26->event_map_base + (edge ? 0 : 4);
+ write_val = (index & CS40L26_BTN_INDEX_MASK) |
+ (ev_handler_bank_ram << CS40L26_BTN_BANK_SHIFT) |
+ (owt << CS40L26_BTN_OWT_SHIFT);
pm_runtime_get_sync(cs40l26->dev);
- if (buzz) {
- freq = CS40L26_MS_TO_HZ(effect->u.periodic.period);
-
- ret = cs40l26_buzzgen_set(cs40l26, freq,
- CS40L26_BUZZGEN_LEVEL_DEFAULT,
- effect->replay.length, 0);
- if (ret)
- goto pm_err;
-
- write_val = 1 << CS40L26_BTN_BUZZ_SHIFT;
- } else {
- write_val = index | (bank << CS40L26_BTN_BANK_SHIFT);
- }
-
- ret = regmap_update_bits(cs40l26->regmap, reg,
- CS40L26_EVENT_MAP_INDEX_MASK, write_val);
+ ret = regmap_write(cs40l26->regmap, reg, write_val);
if (ret)
dev_err(cs40l26->dev, "Failed to update event map\n");
-pm_err:
pm_runtime_mark_last_busy(cs40l26->dev);
pm_runtime_put_autosuspend(cs40l26->dev);
@@ -1593,26 +1820,32 @@ static void cs40l26_vibe_start_worker(struct work_struct *work)
struct device *dev = cs40l26->dev;
u32 index = 0;
int ret = 0;
+ struct ff_effect *effect;
struct cs40l26_owt *owt;
- unsigned int reg, freq;
+ unsigned int reg;
bool invert;
u16 duration;
dev_dbg(dev, "%s\n", __func__);
- if (cs40l26->effect->u.periodic.waveform == FF_CUSTOM)
- index = cs40l26->trigger_indices[cs40l26->effect->id];
+ pm_runtime_get_sync(dev);
+ mutex_lock(&cs40l26->lock);
+
+ effect = cs40l26->trigger_effect;
+
+ if (effect->u.periodic.waveform == FF_CUSTOM ||
+ effect->u.periodic.waveform == FF_SINE)
+ index = cs40l26->trigger_indices[effect->id];
if (is_owt(index)) {
- owt = cs40l26_owt_find(cs40l26, cs40l26->effect->id);
- if (owt == NULL)
- return;
+ owt = cs40l26_owt_find(cs40l26, effect->id);
+ if (owt == NULL) {
+ ret = -ENOMEM;
+ goto err_mutex;
+ }
}
- duration = cs40l26->effect->replay.length;
-
- pm_runtime_get_sync(dev);
- mutex_lock(&cs40l26->lock);
+ duration = effect->replay.length;
ret = cl_dsp_get_reg(cs40l26->dsp, "TIMEOUT_MS",
CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, &reg);
@@ -1630,7 +1863,7 @@ static void cs40l26_vibe_start_worker(struct work_struct *work)
if (ret)
goto err_mutex;
- switch (cs40l26->effect->direction) {
+ switch (effect->direction) {
case 0x0000:
invert = false;
break;
@@ -1639,7 +1872,7 @@ static void cs40l26_vibe_start_worker(struct work_struct *work)
break;
default:
dev_err(dev, "Invalid ff_effect direction: 0x%X\n",
- cs40l26->effect->direction);
+ effect->direction);
ret = -EINVAL;
goto err_mutex;
}
@@ -1648,40 +1881,24 @@ static void cs40l26_vibe_start_worker(struct work_struct *work)
if (ret)
goto err_mutex;
- cs40l26_vibe_state_set(cs40l26, CS40L26_VIBE_STATE_HAPTIC);
-
- switch (cs40l26->effect->u.periodic.waveform) {
+ switch (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:
- freq = CS40L26_MS_TO_HZ(cs40l26->effect->u.periodic.period);
-
- ret = cs40l26_buzzgen_set(cs40l26, freq,
- CS40L26_BUZZGEN_LEVEL_DEFAULT, duration, 1);
- if (ret)
- goto err_mutex;
-
ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
- CS40L26_BUZZGEN_INDEX_CP_TRIGGER,
- CS40L26_DSP_MBOX_RESET);
+ index, CS40L26_DSP_MBOX_RESET);
if (ret)
goto err_mutex;
+ cs40l26_vibe_state_update(cs40l26,
+ CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK);
break;
default:
dev_err(dev, "Invalid waveform type: 0x%X\n",
- cs40l26->effect->u.periodic.waveform);
+ 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);
@@ -1701,6 +1918,11 @@ static void cs40l26_vibe_stop_worker(struct work_struct *work)
if (cs40l26->vibe_state != CS40L26_VIBE_STATE_HAPTIC)
goto mutex_exit;
+ /* wait for SVC init phase to complete */
+ if (cs40l26->delay_before_stop_playback_us)
+ usleep_range(cs40l26->delay_before_stop_playback_us,
+ cs40l26->delay_before_stop_playback_us + 100);
+
ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
CS40L26_STOP_PLAYBACK, CS40L26_DSP_MBOX_RESET);
if (ret) {
@@ -1708,8 +1930,6 @@ static void cs40l26_vibe_stop_worker(struct work_struct *work)
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);
@@ -1720,6 +1940,8 @@ static void cs40l26_set_gain(struct input_dev *dev, u16 gain)
{
struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ dev_dbg(cs40l26->dev, "%s: gain = %u\n", __func__, gain);
+
if (gain < 0 || gain >= CS40L26_NUM_PCT_MAP_VALUES) {
dev_err(cs40l26->dev, "Gain value %u out of bounds\n", gain);
return;
@@ -1745,7 +1967,7 @@ static int cs40l26_playback_effect(struct input_dev *dev,
return -EINVAL;
}
- cs40l26->effect = effect;
+ cs40l26->trigger_effect = effect;
if (val > 0)
queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_start_work);
@@ -1785,6 +2007,19 @@ int cs40l26_get_num_waves(struct cs40l26_private *cs40l26, u32 *num_waves)
}
EXPORT_SYMBOL(cs40l26_get_num_waves);
+static struct cl_dsp_owt_header *cs40l26_header(struct cs40l26_private *cs40l26,
+ u8 index)
+{
+ if (!cs40l26->dsp || !cs40l26->dsp->wt_desc)
+ return NULL;
+
+ if (index >= cs40l26->dsp->wt_desc->owt.nwaves)
+ return NULL;
+
+ return &cs40l26->dsp->wt_desc->owt.waves[index];
+
+}
+
static int cs40l26_owt_get_wlength(struct cs40l26_private *cs40l26, u8 index)
{
struct device *dev = cs40l26->dev;
@@ -1794,7 +2029,9 @@ static int cs40l26_owt_get_wlength(struct cs40l26_private *cs40l26, u8 index)
if (index == 0)
return 0;
- entry = &cs40l26->dsp->wt_desc->owt.waves[index];
+ entry = cs40l26_header(cs40l26, index);
+ if (entry == NULL)
+ return -EINVAL;
switch (entry->type) {
case WT_TYPE_V6_PCM_F0_REDC:
@@ -1812,6 +2049,26 @@ static int cs40l26_owt_get_wlength(struct cs40l26_private *cs40l26, u8 index)
return cl_dsp_memchunk_read(&ch, 24);
}
+static void cs40l26_owt_set_section_info(struct cs40l26_private *cs40l26,
+ struct cl_dsp_memchunk *ch,
+ struct cs40l26_owt_section *sections, u8 nsections)
+{
+ int i;
+
+ for (i = 0; i < nsections; i++) {
+ cl_dsp_memchunk_write(ch, 8, sections[i].amplitude);
+ cl_dsp_memchunk_write(ch, 8, sections[i].index);
+ cl_dsp_memchunk_write(ch, 8, sections[i].repeat);
+ cl_dsp_memchunk_write(ch, 8, sections[i].flags);
+ cl_dsp_memchunk_write(ch, 16, sections[i].delay);
+
+ if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) {
+ cl_dsp_memchunk_write(ch, 8, 0x00); /* Pad */
+ cl_dsp_memchunk_write(ch, 16, sections[i].duration);
+ }
+ }
+}
+
static void cs40l26_owt_get_section_info(struct cs40l26_private *cs40l26,
struct cl_dsp_memchunk *ch,
struct cs40l26_owt_section *sections, u8 nsections)
@@ -1819,7 +2076,7 @@ static void cs40l26_owt_get_section_info(struct cs40l26_private *cs40l26,
int i;
for (i = 0; i < nsections; i++) {
- cl_dsp_memchunk_read(ch, 8); /* Skip amplitude */
+ sections[i].amplitude = cl_dsp_memchunk_read(ch, 8);
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);
@@ -1833,23 +2090,16 @@ static void cs40l26_owt_get_section_info(struct cs40l26_private *cs40l26,
}
static int cs40l26_owt_calculate_wlength(struct cs40l26_private *cs40l26,
- s16 *data, u32 data_size, u32 *owt_wlen)
+ u8 nsections, u8 global_rep, u8 *data, u32 data_size_bytes,
+ u32 *owt_wlen)
{
- u32 data_size_bytes = data_size * 2;
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;
- ch = cl_dsp_memchunk_create((void *) data, data_size_bytes);
-
- 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;
@@ -1857,11 +2107,10 @@ static int cs40l26_owt_calculate_wlength(struct cs40l26_private *cs40l26,
sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section),
GFP_KERNEL);
- if (!sections) {
- dev_err(cs40l26->dev, "Failed to allocate OWT sections\n");
+ if (!sections)
return -ENOMEM;
- }
+ ch = cl_dsp_memchunk_create((void *) data, data_size_bytes);
cs40l26_owt_get_section_info(cs40l26, &ch, sections, nsections);
for (i = 0; i < nsections; i++) {
@@ -1924,8 +2173,8 @@ err_free:
return ret;
}
-static int cs40l26_owt_add(struct cs40l26_private *cs40l26, u32 wlen,
- int effect_id, u32 index)
+static int cs40l26_owt_add(struct cs40l26_private *cs40l26, int effect_id,
+ u32 index)
{
struct cs40l26_owt *owt_new;
@@ -1934,7 +2183,6 @@ static int cs40l26_owt_add(struct cs40l26_private *cs40l26, u32 wlen,
return -ENOMEM;
owt_new->effect_id = effect_id;
- owt_new->wlength = wlen;
owt_new->trigger_index = index;
list_add(&owt_new->list, &cs40l26->owt_head);
@@ -1943,51 +2191,13 @@ static int cs40l26_owt_add(struct cs40l26_private *cs40l26, u32 wlen,
return 0;
}
-static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, s16 *data,
- u32 data_size, u32 *wlength)
+static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, u8 *data,
+ u32 data_size_bytes)
{
- bool pwle = (data[0] == 0x0000) ? false : true;
- u32 data_size_bytes = (data_size * 2) + (pwle ? 0 : 4);
struct device *dev = cs40l26->dev;
struct cl_dsp *dsp = cs40l26->dsp;
unsigned int write_reg, reg, wt_offset, wt_size_words, wt_base;
- u32 owt_wlength, full_data_size_bytes = 12 + data_size_bytes;
- struct cl_dsp_memchunk data_ch;
- u8 *full_data;
- int ret = 0;
-
- full_data = kcalloc(full_data_size_bytes, sizeof(u8), GFP_KERNEL);
- if (!full_data)
- return -ENOMEM;
-
- data_ch = cl_dsp_memchunk_create((void *) full_data,
- full_data_size_bytes);
- cl_dsp_memchunk_write(&data_ch, 16, CS40L26_WT_HEADER_DEFAULT_FLAGS);
-
- if (pwle)
- cl_dsp_memchunk_write(&data_ch, 8, WT_TYPE_V6_PWLE);
- else
- cl_dsp_memchunk_write(&data_ch, 8, WT_TYPE_V6_COMPOSITE);
-
- cl_dsp_memchunk_write(&data_ch, 24, CS40L26_WT_HEADER_OFFSET);
- cl_dsp_memchunk_write(&data_ch, 24, data_size_bytes /
- CL_DSP_BYTES_PER_WORD);
-
- if (!pwle) { /* Wlength is included in PWLE raw data */
- ret = cs40l26_owt_calculate_wlength(cs40l26, data, data_size,
- &owt_wlength);
- if (ret)
- goto err_free;
-
- cl_dsp_memchunk_write(&data_ch, 24, owt_wlength);
- }
-
- memcpy(full_data + data_ch.bytes, data, data_size_bytes);
-
- if (pwle)
- owt_wlength = (full_data[data_ch.bytes + 1] << 16) |
- (full_data[data_ch.bytes + 2] << 8) |
- full_data[data_ch.bytes + 3];
+ int ret;
pm_runtime_get_sync(dev);
@@ -2013,7 +2223,7 @@ static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, s16 *data,
goto err_pm;
}
- if ((wt_size_words * 3) < full_data_size_bytes) {
+ if ((wt_size_words * CL_DSP_BYTES_PER_WORD) < data_size_bytes) {
dev_err(dev, "No space for OWT waveform\n");
ret = -ENOSPC;
goto err_pm;
@@ -2026,8 +2236,8 @@ static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, s16 *data,
write_reg = wt_base + (wt_offset * 4);
- ret = cl_dsp_raw_write(cs40l26->dsp, write_reg, full_data,
- full_data_size_bytes, CL_DSP_MAX_WLEN);
+ ret = cl_dsp_raw_write(cs40l26->dsp, write_reg, data, data_size_bytes,
+ CL_DSP_MAX_WLEN);
if (ret) {
dev_err(dev, "Failed to sync OWT\n");
goto err_pm;
@@ -2039,76 +2249,430 @@ static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, s16 *data,
goto err_pm;
dev_dbg(dev, "Successfully wrote waveform (%u bytes) to 0x%08X\n",
- full_data_size_bytes, write_reg);
-
- *wlength = owt_wlength;
+ data_size_bytes, write_reg);
err_pm:
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
-err_free:
- kfree(full_data);
+ return ret;
+}
+
+static u8 *cs40l26_ncw_amp_scaling(struct cs40l26_private *cs40l26, u8 amp,
+ u8 nsections, void *in_data, u32 data_bytes)
+{
+ struct cs40l26_owt_section *sections;
+ struct cl_dsp_memchunk in_ch, out_ch;
+ u16 amp_product;
+ u8 *out_data;
+ int i;
+
+ if (nsections <= 0) {
+ dev_err(cs40l26->dev, "Too few sections for NCW\n");
+ return NULL;
+ }
+
+ sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section),
+ GFP_KERNEL);
+ if (!sections)
+ return NULL;
+
+ in_ch = cl_dsp_memchunk_create(in_data, data_bytes);
+
+ cs40l26_owt_get_section_info(cs40l26, &in_ch, sections, nsections);
+
+ for (i = 0; i < nsections; i++) {
+ if (sections[i].index != 0) {
+ amp_product = sections[i].amplitude * amp;
+ sections[i].amplitude =
+ (u8) DIV_ROUND_UP(amp_product, 100);
+ }
+ }
+
+ out_data = kcalloc(data_bytes, sizeof(u8), GFP_KERNEL);
+ if (!out_data)
+ goto sections_free;
+
+ out_ch = cl_dsp_memchunk_create((void *) out_data, data_bytes);
+ cs40l26_owt_set_section_info(cs40l26, &out_ch, sections, nsections);
+
+
+sections_free:
+ kfree(sections);
+
+ return out_data;
+}
+
+static int cs40l26_owt_comp_data_size(struct cs40l26_private *cs40l26,
+ u8 nsections, struct cs40l26_owt_section *sections)
+{
+ int i, size = 0;
+ struct cl_dsp_owt_header *header;
+
+ for (i = 0; i < nsections; i++) {
+ if (sections[i].index == 0) {
+ size += CS40L26_WT_TYPE10_SECTION_BYTES_MIN;
+ continue;
+ }
+
+ header = cs40l26_header(cs40l26, sections[i].index);
+ if (header == NULL)
+ return -ENOMEM;
+
+ if (header->type == WT_TYPE_V6_COMPOSITE) {
+ size += (header->size - 2) * 4;
+
+ if (section_complete(&sections[i]))
+ size += CS40L26_WT_TYPE10_SECTION_BYTES_MIN;
+ } else {
+ size += sections[i].duration ?
+ CS40L26_WT_TYPE10_SECTION_BYTES_MAX :
+ CS40L26_WT_TYPE10_SECTION_BYTES_MIN;
+ }
+ }
+
+ return size;
+}
+
+static int cs40l26_refactor_owt(struct cs40l26_private *cs40l26, s16 *in_data,
+ u32 in_data_nibbles, bool pwle, u8 **out_data)
+{
+ u8 nsections, global_rep, out_nsections = 0;
+ int ret = 0, pos_byte = 0, in_pos_nib = 2;
+ int in_data_bytes = 2 * in_data_nibbles;
+ int out_data_bytes = 0, data_bytes = 0;
+ struct device *dev = cs40l26->dev;
+ u8 delay_section_data[CS40L26_WT_TYPE10_SECTION_BYTES_MIN];
+ u8 ncw_nsections, ncw_global_rep, *data, *ncw_data;
+ struct cs40l26_owt_section *sections;
+ struct cl_dsp_memchunk ch, out_ch;
+ struct cl_dsp_owt_header *header;
+ u16 section_size_bytes;
+ u32 ncw_bytes, wlen;
+ int i;
+
+ if (pwle) {
+ out_data_bytes = CS40L26_WT_HEADER_PWLE_SIZE + in_data_bytes;
+ *out_data = kcalloc(out_data_bytes, sizeof(u8), GFP_KERNEL);
+ if (!*out_data) {
+ dev_err(dev, "No space for refactored data\n");
+ return -ENOMEM;
+ }
+
+ out_ch = cl_dsp_memchunk_create((void *) *out_data,
+ out_data_bytes);
+ cl_dsp_memchunk_write(&out_ch, 16,
+ CS40L26_WT_HEADER_DEFAULT_FLAGS);
+ cl_dsp_memchunk_write(&out_ch, 8, WT_TYPE_V6_PWLE);
+ cl_dsp_memchunk_write(&out_ch, 24, CS40L26_WT_HEADER_OFFSET);
+ cl_dsp_memchunk_write(&out_ch, 24, in_data_bytes / 4);
+
+ memcpy(*out_data + out_ch.bytes, in_data, in_data_bytes);
+
+ return out_data_bytes;
+ }
+
+ ch = cl_dsp_memchunk_create((void *) in_data, in_data_bytes);
+ cl_dsp_memchunk_read(&ch, 8); /* Skip padding */
+ nsections = cl_dsp_memchunk_read(&ch, 8);
+ global_rep = cl_dsp_memchunk_read(&ch, 8);
+
+ sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section),
+ GFP_KERNEL);
+ if (!sections)
+ return -ENOMEM;
+
+ cs40l26_owt_get_section_info(cs40l26, &ch, sections, nsections);
+
+ data_bytes = cs40l26_owt_comp_data_size(cs40l26, nsections,
+ sections);
+ if (data_bytes <= 0) {
+ dev_err(dev, "Failed to get OWT Composite Data Size\n");
+ ret = data_bytes;
+ goto sections_err_free;
+ }
+
+ data = kcalloc(data_bytes, sizeof(u8), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto sections_err_free;
+ }
+
+ cl_dsp_memchunk_flush(&ch);
+ memset(&delay_section_data, 0, CS40L26_WT_TYPE10_SECTION_BYTES_MIN);
+
+ for (i = 0; i < nsections; i++) {
+ section_size_bytes = sections[i].duration ?
+ CS40L26_WT_TYPE10_SECTION_BYTES_MAX :
+ CS40L26_WT_TYPE10_SECTION_BYTES_MIN;
+
+ if (sections[i].index == 0) {
+ memcpy(data + pos_byte, in_data + in_pos_nib,
+ section_size_bytes);
+ pos_byte += section_size_bytes;
+ in_pos_nib += section_size_bytes / 2;
+ out_nsections++;
+ continue;
+ }
+
+ if (sections[i].repeat != 0) {
+ dev_err(dev, "Inner repeats not allowed for NCWs\n");
+ ret = -EPERM;
+ goto data_err_free;
+ }
+
+ header = cs40l26_header(cs40l26, sections[i].index);
+ if (header == NULL) {
+ ret = -ENOMEM;
+ goto data_err_free;
+ }
+
+ if (header->type == WT_TYPE_V6_COMPOSITE) {
+ ch = cl_dsp_memchunk_create(header->data, 8);
+ cl_dsp_memchunk_read(&ch, 24); /* Skip Wlength */
+ cl_dsp_memchunk_read(&ch, 8); /* Skip Padding */
+ ncw_nsections = cl_dsp_memchunk_read(&ch, 8);
+
+ ncw_global_rep = cl_dsp_memchunk_read(&ch, 8);
+ if (ncw_global_rep != 0) {
+ dev_err(dev,
+ "No NCW support for outer repeat\n");
+ ret = -EPERM;
+ goto data_err_free;
+ }
+
+ cl_dsp_memchunk_flush(&ch);
+
+ ncw_bytes = (header->size - 2) * 4;
+ ncw_data = cs40l26_ncw_amp_scaling(cs40l26,
+ sections[i].amplitude, ncw_nsections,
+ header->data + 8, ncw_bytes);
+ if (ncw_data == NULL) {
+ ret = -ENOMEM;
+ goto data_err_free;
+ }
+
+ memcpy(data + pos_byte, ncw_data, ncw_bytes);
+ pos_byte += ncw_bytes;
+ out_nsections += ncw_nsections;
+ kfree(ncw_data);
+
+ if (section_complete(&sections[i])) {
+ ch = cl_dsp_memchunk_create((void *)
+ delay_section_data,
+ CS40L26_WT_TYPE10_SECTION_BYTES_MIN);
+
+ cl_dsp_memchunk_write(&ch, 24, 0x000000);
+ cl_dsp_memchunk_write(&ch, 8, 0x00);
+ cl_dsp_memchunk_write(&ch, 16,
+ sections[i].delay);
+
+ memcpy(data + pos_byte,
+ delay_section_data,
+ CS40L26_WT_TYPE10_SECTION_BYTES_MIN);
+
+ cl_dsp_memchunk_flush(&ch);
+
+ pos_byte +=
+ CS40L26_WT_TYPE10_SECTION_BYTES_MIN;
+ out_nsections++;
+ }
+ } else {
+ memcpy(data + pos_byte, in_data + in_pos_nib,
+ section_size_bytes);
+ pos_byte += section_size_bytes;
+ out_nsections++;
+ }
+ in_pos_nib += section_size_bytes / 2;
+ }
+
+ out_data_bytes = data_bytes + CS40L26_WT_HEADER_COMP_SIZE;
+ *out_data = kcalloc(out_data_bytes, sizeof(u8), GFP_KERNEL);
+ if (!*out_data) {
+ dev_err(dev, "Failed to allocate space for composite\n");
+ ret = -ENOMEM;
+ goto data_err_free;
+ }
+
+ out_ch = cl_dsp_memchunk_create((void *) *out_data, out_data_bytes);
+ cl_dsp_memchunk_write(&out_ch, 16, CS40L26_WT_HEADER_DEFAULT_FLAGS);
+ cl_dsp_memchunk_write(&out_ch, 8, WT_TYPE_V6_COMPOSITE);
+ cl_dsp_memchunk_write(&out_ch, 24, CS40L26_WT_HEADER_OFFSET);
+ cl_dsp_memchunk_write(&out_ch, 24, data_bytes / CL_DSP_BYTES_PER_WORD);
+
+ ret = cs40l26_owt_calculate_wlength(cs40l26, out_nsections, global_rep,
+ data, data_bytes, &wlen);
+ if (ret)
+ goto data_err_free;
+
+ cl_dsp_memchunk_write(&out_ch, 24, wlen);
+ cl_dsp_memchunk_write(&out_ch, 8, 0x00); /* Pad */
+ cl_dsp_memchunk_write(&out_ch, 8, out_nsections);
+ cl_dsp_memchunk_write(&out_ch, 8, global_rep);
+
+ memcpy(*out_data + out_ch.bytes, data, data_bytes);
+
+data_err_free:
+ kfree(data);
+sections_err_free:
+ kfree(sections);
+
+ return ret ? ret : out_data_bytes;
+}
+
+static u8 cs40l26_get_lowest_free_buzzgen(struct cs40l26_private *cs40l26)
+{
+ int buzzgen, i;
+
+ /* Note that this search starts at RAM_BUZZ1 and does not utilize the
+ * OTP buzz
+ */
+ for (buzzgen = 1; buzzgen <= CS40L26_BUZZGEN_NUM_CONFIGS; buzzgen++) {
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ if (cs40l26->trigger_indices[i] ==
+ CS40L26_BUZZGEN_INDEX_START + buzzgen)
+ break;
+ }
+
+ if (i == FF_MAX_EFFECTS)
+ break;
+ }
+
+ return buzzgen;
+}
+
+static int cs40l26_sine_upload(struct cs40l26_private *cs40l26,
+ struct ff_effect *effect)
+{
+ struct device *dev = cs40l26->dev;
+ int ret;
+ u8 lowest_free_buzzgen, level;
+ u16 freq;
+ u32 trigger_index;
+
+ if (effect->u.periodic.period) {
+ if (effect->u.periodic.period < CS40L26_BUZZGEN_PERIOD_MIN ||
+ effect->u.periodic.period > CS40L26_BUZZGEN_PERIOD_MAX) {
+ dev_err(dev,
+ "%u ms period not within range (4-10 ms)\n",
+ effect->u.periodic.period);
+ return -EINVAL;
+ }
+ } else {
+ dev_err(dev, "Sine wave period not specified\n");
+ return -EINVAL;
+ }
+
+ if (effect->u.periodic.magnitude) {
+ if (effect->u.periodic.magnitude < CS40L26_BUZZGEN_LEVEL_MIN ||
+ effect->u.periodic.magnitude > CS40L26_BUZZGEN_LEVEL_MAX) {
+ dev_err(dev,
+ "%u magnitude not within range (%d-%d)\n",
+ effect->u.periodic.magnitude,
+ CS40L26_BUZZGEN_LEVEL_MIN,
+ CS40L26_BUZZGEN_LEVEL_MAX);
+ return -EINVAL;
+ }
+
+ level = effect->u.periodic.magnitude;
+ } else {
+ level = CS40L26_BUZZGEN_LEVEL_DEFAULT;
+ }
+
+ lowest_free_buzzgen = cs40l26_get_lowest_free_buzzgen(cs40l26);
+ dev_dbg(dev, "lowest_free_buzzgen: %d", lowest_free_buzzgen);
+
+ if (lowest_free_buzzgen > CS40L26_BUZZGEN_NUM_CONFIGS) {
+ dev_err(dev, "Unable to upload buzzgen effect\n");
+ return -ENOSPC;
+ }
+
+ freq = CS40L26_MS_TO_HZ(effect->u.periodic.period);
+
+ ret = cs40l26_buzzgen_set(cs40l26, freq, level,
+ effect->replay.length, lowest_free_buzzgen);
+ if (ret)
+ return ret;
+
+ trigger_index = CS40L26_BUZZGEN_INDEX_START + lowest_free_buzzgen;
+ cs40l26->trigger_indices[effect->id] = trigger_index;
+
+ if (effect->trigger.button)
+ ret = cs40l26_map_gpi_to_haptic(cs40l26, trigger_index,
+ CS40L26_RAM_BANK_ID, effect);
return ret;
}
-static int cs40l26_upload_effect(struct input_dev *dev,
- struct ff_effect *effect, struct ff_effect *old)
+static void cs40l26_upload_worker(struct work_struct *work)
{
- struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ struct cs40l26_private *cs40l26 = container_of(work,
+ struct cs40l26_private, upload_work);
struct device *cdev = cs40l26->dev;
- s16 *raw_custom_data = NULL;
- int ret = 0;
- u32 trigger_index, min_index, max_index, nwaves, owt_wlen;
+ u8 *refactored_data = NULL;
+ int ret = 0, refactored_size, len;
+ u32 trigger_index, min_index, max_index, nwaves;
+ struct ff_effect *effect;
u16 index, bank;
+ bool pwle;
+
+ pm_runtime_get_sync(cdev);
+ mutex_lock(&cs40l26->lock);
+
+ effect = &cs40l26->upload_effect;
if (effect->type != FF_PERIODIC) {
dev_err(cdev, "Effect type 0x%X not supported\n", effect->type);
- 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;
+ ret = -EINVAL;
+ goto out_mutex;
}
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;
+ pwle = (cs40l26->raw_custom_data[0] == 0x0000) ? false : true;
- 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;
- }
+ len = effect->u.periodic.custom_len;
+
+ if (len > CS40L26_CUSTOM_DATA_SIZE) {
+ refactored_size = cs40l26_refactor_owt(cs40l26,
+ cs40l26->raw_custom_data, len, pwle,
+ &refactored_data);
+
+ if (refactored_size <= 0) {
+ dev_err(cdev,
+ "Failed to refactor OWT waveform\n");
+ ret = refactored_size;
+ goto out_mutex;
+ }
- if (effect->u.periodic.custom_len > CS40L26_CUSTOM_DATA_SIZE) {
- ret = cs40l26_owt_upload(cs40l26, raw_custom_data,
- effect->u.periodic.custom_len, &owt_wlen);
+ ret = cs40l26_owt_upload(cs40l26, refactored_data,
+ refactored_size);
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]) &
+ bank = ((u16) cs40l26->raw_custom_data[0]);
+ index = ((u16) cs40l26->raw_custom_data[1]) &
CS40L26_MAX_INDEX_MASK;
}
+ ret = cs40l26_get_num_waves(cs40l26, &nwaves);
+ if (ret)
+ goto out_free;
+
switch (bank) {
case CS40L26_RAM_BANK_ID:
- min_index = CS40L26_RAM_INDEX_START;
- max_index = min_index + cs40l26->num_waves - 1;
+ if (nwaves - cs40l26->num_owt_effects == 0) {
+ dev_err(cdev, "No waveforms in RAM bank\n");
+ ret = -EINVAL;
+ goto out_free;
+ } else {
+ min_index = CS40L26_RAM_INDEX_START;
+ max_index = min_index + nwaves - 1 -
+ cs40l26->num_owt_effects;
+ }
break;
case CS40L26_ROM_BANK_ID:
min_index = CS40L26_ROM_INDEX_START;
@@ -2129,14 +2693,15 @@ static int cs40l26_upload_effect(struct input_dev *dev,
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);
+ dev_err(cdev,
+ "Index (0x%X) out of bounds (0x%X - 0x%X)\n",
+ trigger_index, min_index, max_index);
ret = -EINVAL;
goto out_free;
}
if (is_owt(trigger_index)) {
- ret = cs40l26_owt_add(cs40l26, owt_wlen, effect->id,
+ ret = cs40l26_owt_add(cs40l26, effect->id,
trigger_index);
if (ret)
goto out_free;
@@ -2144,33 +2709,26 @@ static int cs40l26_upload_effect(struct input_dev *dev,
dev_dbg(cdev, "%s: ID = %u, index = 0x%08X\n",
__func__, effect->id, trigger_index);
+
+ if (effect->trigger.button) {
+ ret = cs40l26_map_gpi_to_haptic(cs40l26,
+ trigger_index, bank, effect);
+ if (ret)
+ 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;
- }
+ ret = cs40l26_sine_upload(cs40l26, effect);
+ if (ret)
+ goto out_mutex;
+
break;
default:
dev_err(cdev, "Periodic waveform type 0x%X not supported\n",
effect->u.periodic.waveform);
- return -EINVAL;
- }
-
- if (effect->trigger.button) {
- ret = cs40l26_gpio_index_set(cs40l26, effect);
- if (ret)
- goto out_free;
+ ret = -EINVAL;
+ goto out_mutex;
}
ret = cs40l26_get_num_waves(cs40l26, &nwaves);
@@ -2179,9 +2737,57 @@ static int cs40l26_upload_effect(struct input_dev *dev,
dev_dbg(cdev, "Total number of waveforms = %u\n", nwaves);
+out_free:
+ kfree(refactored_data);
+
+out_mutex:
+ mutex_unlock(&cs40l26->lock);
+ pm_runtime_mark_last_busy(cdev);
+ pm_runtime_put_autosuspend(cdev);
+
+ cs40l26->upload_ret = ret;
+}
+
+static int cs40l26_upload_effect(struct input_dev *dev,
+ struct ff_effect *effect, struct ff_effect *old)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ int len = effect->u.periodic.custom_len;
+ int ret;
+
+ dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect->id);
+
+ memcpy(&cs40l26->upload_effect, effect, sizeof(struct ff_effect));
+
+ if (effect->u.periodic.waveform == FF_CUSTOM) {
+ cs40l26->raw_custom_data_len = len;
+
+ cs40l26->raw_custom_data = kcalloc(len, sizeof(s16),
+ GFP_KERNEL);
+ if (!cs40l26->raw_custom_data) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ if (copy_from_user(cs40l26->raw_custom_data,
+ effect->u.periodic.custom_data,
+ sizeof(s16) * len)) {
+ dev_err(cs40l26->dev, "Failed to get user data\n");
+ ret = -EFAULT;
+ goto out_free;
+ }
+ }
+
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->upload_work);
+
+ /* Wait for upload to finish */
+ flush_work(&cs40l26->upload_work);
+
+ ret = cs40l26->upload_ret;
out_free:
- kfree(raw_custom_data);
+ memset(&cs40l26->upload_effect, 0, sizeof(struct ff_effect));
+ kfree(cs40l26->raw_custom_data);
return ret;
}
@@ -2194,18 +2800,80 @@ const struct attribute_group *cs40l26_dev_attr_groups[] = {
};
#endif
-static int cs40l26_erase_effect(struct input_dev *dev, int effect_id)
+static int cs40l26_clear_gpi_event_reg(struct cs40l26_private *cs40l26, u32 reg)
{
- struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ struct regmap *regmap = cs40l26->regmap;
+ int ret;
+
+ ret = regmap_write(regmap, reg, 0);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to clear gpi reg: %08X", reg);
+
+ return ret;
+}
+
+static int cs40l26_erase_gpi_mapping(struct cs40l26_private *cs40l26,
+ int effect_id)
+{
+ struct device *dev = cs40l26->dev;
u32 index = cs40l26->trigger_indices[effect_id];
+ u8 trigger_index, gpi_index;
+ u32 reg, val;
+ int i, ret;
+
+ trigger_index = index & 0xFF;
+
+ for (i = 0; i < CS40L26_EVENT_MAP_NUM_GPI_REGS; i++) {
+ reg = cs40l26->event_map_base + (i * 4);
+ ret = regmap_read(cs40l26->regmap, reg, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read gpi event reg: %u", reg);
+ return ret;
+ }
+ gpi_index = val & 0xFF;
+
+ if (is_buzz(index) ||
+ (is_owt(index) && val & CS40L26_BTN_OWT_MASK) ||
+ (is_ram(index) && val & CS40L26_BTN_BANK_MASK) ||
+ (is_rom(index) && ~val & CS40L26_BTN_BANK_MASK)) {
+ if (trigger_index == gpi_index) {
+ ret = cs40l26_clear_gpi_event_reg(cs40l26, reg);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int cs40l26_erase_buzz(struct cs40l26_private *cs40l26, int effect_id)
+{
+ cs40l26->trigger_indices[effect_id] = 0;
+
+ return 0;
+}
+
+static int cs40l26_erase_owt(struct cs40l26_private *cs40l26, int effect_id)
+{
u32 cmd = CS40L26_DSP_MBOX_CMD_OWT_DELETE_BASE;
struct cs40l26_owt *owt, *owt_tmp;
+ u32 index;
int ret;
- if (!is_owt(index))
- return 0;
+ owt = cs40l26_owt_find(cs40l26, effect_id);
+ if (owt == NULL)
+ return -ENOMEM;
+
+ cmd |= (owt->trigger_index & 0xFF);
+
+ ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd,
+ CS40L26_DSP_MBOX_RESET);
+ if (ret)
+ return ret;
/* Update indices for OWT waveforms uploaded after erased effect */
+ index = cs40l26->trigger_indices[effect_id];
list_for_each_entry(owt_tmp, &cs40l26->owt_head, list) {
if (owt_tmp->trigger_index > index) {
owt_tmp->trigger_index--;
@@ -2213,29 +2881,79 @@ static int cs40l26_erase_effect(struct input_dev *dev, int effect_id)
}
}
- owt = cs40l26_owt_find(cs40l26, effect_id);
- if (owt == NULL)
- return -ENOMEM;
-
- cmd |= (owt->trigger_index & 0xFF);
-
list_del(&owt->list);
kfree(owt);
+ cs40l26->num_owt_effects--;
+
+ return 0;
+}
+
+
+static void cs40l26_erase_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 = container_of(work,
+ struct cs40l26_private, erase_work);
+ int ret = 0;
+ int effect_id;
+ u32 index;
+ mutex_lock(&cs40l26->lock);
pm_runtime_get_sync(cs40l26->dev);
- ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd,
- CS40L26_DSP_MBOX_RESET);
- if (ret)
- goto pm_err;
+ effect_id = cs40l26->erase_effect->id;
+ index = cs40l26->trigger_indices[effect_id];
- cs40l26->num_owt_effects--;
+ dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id);
-pm_err:
+ if (is_owt(index)) {
+ ret = cs40l26_erase_owt(cs40l26, effect_id);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to erase OWT effect: %d",
+ ret);
+ goto out_mutex;
+ }
+
+ if (is_buzz(index)) {
+ ret = cs40l26_erase_buzz(cs40l26, effect_id);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to erase buzz effect: %d",
+ ret);
+ goto out_mutex;
+ }
+
+ ret = cs40l26_erase_gpi_mapping(cs40l26, effect_id);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to erase gpi mapping: %d", ret);
+
+out_mutex:
+ mutex_unlock(&cs40l26->lock);
pm_runtime_mark_last_busy(cs40l26->dev);
pm_runtime_put_autosuspend(cs40l26->dev);
- return ret;
+ cs40l26->erase_ret = ret;
+}
+
+static int cs40l26_erase_effect(struct input_dev *dev, int effect_id)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ struct ff_effect *effect;
+
+ dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id);
+
+ effect = &dev->ff->effects[effect_id];
+ if (!effect) {
+ dev_err(cs40l26->dev, "No such effect to erase\n");
+ return -EINVAL;
+ }
+
+ cs40l26->erase_effect = effect;
+
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->erase_work);
+
+ /* Wait for erase to finish */
+ flush_work(&cs40l26->erase_work);
+
+ return cs40l26->erase_ret;
}
static int cs40l26_input_init(struct cs40l26_private *cs40l26)
@@ -2396,7 +3114,9 @@ static int cs40l26_cl_dsp_init(struct cs40l26_private *cs40l26, u32 id)
strncpy(cs40l26->fw.coeff_files[2],
CS40L26_SVC_TUNING_FILE_NAME,
CS40L26_SVC_TUNING_FILE_NAME_LEN);
-
+ strncpy(cs40l26->fw.coeff_files[3],
+ CS40L26_DVL_FILE_NAME,
+ CS40L26_DVL_FILE_NAME_LEN);
ret = cl_dsp_wavetable_create(cs40l26->dsp,
CS40L26_VIBEGEN_ALGO_ID, CS40L26_WT_NAME_XM,
CS40L26_WT_NAME_YM, CS40L26_WT_FILE_NAME);
@@ -2725,7 +3445,7 @@ static int cs40l26_verify_fw(struct cs40l26_private *cs40l26)
if (ret)
return ret;
- if (val < fw->min_rev) {
+ if ((val & ~CS40L26_FW_BRANCH_MASK) < 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),
@@ -2804,7 +3524,7 @@ static int cs40l26_bst_dcm_config(struct cs40l26_private *cs40l26)
static int cs40l26_bst_ipk_config(struct cs40l26_private *cs40l26)
{
- u32 val, bst_ipk_ma = cs40l26->pdata.bst_ipk / 1000;
+ u32 val, bst_ipk_ma = cs40l26->pdata.bst_ipk / MILLIAMPS_PER_AMPS;
int ret;
if (bst_ipk_ma < CS40L26_BST_IPK_MILLIAMP_MIN ||
@@ -2812,7 +3532,7 @@ static int cs40l26_bst_ipk_config(struct cs40l26_private *cs40l26)
val = CS40L26_BST_IPK_DEFAULT;
dev_dbg(cs40l26->dev, "Using default BST_IPK\n");
} else {
- val = (bst_ipk_ma / 50) - 16;
+ val = (bst_ipk_ma / CS40L26_BST_IPK_CTL_STEP_SIZE) - CS40L26_BST_IPK_CTL_RESERVED;
}
ret = regmap_write(cs40l26->regmap, CS40L26_BST_IPK_CTL, val);
@@ -2861,8 +3581,8 @@ static int cs40l26_dsp_config(struct cs40l26_private *cs40l26)
struct regmap *regmap = cs40l26->regmap;
struct device *dev = cs40l26->dev;
unsigned int val;
+ u32 reg, nwaves;
int ret;
- u32 reg;
ret = cs40l26_verify_fw(cs40l26);
if (ret)
@@ -2888,15 +3608,15 @@ static int cs40l26_dsp_config(struct cs40l26_private *cs40l26)
cs40l26->fw_loaded = true;
- ret = cs40l26_dsp_start(cs40l26);
+ ret = cs40l26_pseq_init(cs40l26);
if (ret)
return ret;
- ret = cs40l26_pseq_init(cs40l26);
+ ret = cs40l26_update_reg_defaults_via_pseq(cs40l26);
if (ret)
return ret;
- ret = cs40l26_update_reg_defaults_via_pseq(cs40l26);
+ ret = cs40l26_dsp_start(cs40l26);
if (ret)
return ret;
@@ -2935,6 +3655,13 @@ static int cs40l26_dsp_config(struct cs40l26_private *cs40l26)
if (ret)
return ret;
+ /* Send prevent hibernate to ensure we can read HALO STATE */
+ ret = cs40l26_pm_state_transition(cs40l26,
+ CS40L26_PM_STATE_PREVENT_HIBERNATE);
+ if (ret)
+ return ret;
+
+ /* ensure firmware running */
ret = regmap_read(regmap, reg, &val);
if (ret) {
dev_err(dev, "Failed to read HALO_STATE\n");
@@ -2946,6 +3673,11 @@ static int cs40l26_dsp_config(struct cs40l26_private *cs40l26)
return ret;
}
+ ret = cs40l26_pm_state_transition(cs40l26,
+ CS40L26_PM_STATE_ALLOW_HIBERNATE);
+ if (ret)
+ return ret;
+
ret = cs40l26_pm_runtime_setup(cs40l26);
if (ret)
return ret;
@@ -2967,12 +3699,12 @@ static int cs40l26_dsp_config(struct cs40l26_private *cs40l26)
if (ret)
goto pm_err;
- ret = cs40l26_get_num_waves(cs40l26, &cs40l26->num_waves);
+ ret = cs40l26_get_num_waves(cs40l26, &nwaves);
if (ret)
goto pm_err;
dev_info(dev, "%s loaded with %u RAM waveforms\n", CS40L26_DEV_NAME,
- cs40l26->num_waves);
+ nwaves);
ret = cs40l26_owt_setup(cs40l26);
if (ret)
@@ -3036,7 +3768,7 @@ static int cs40l26_tuning_select_from_svc_le(struct cs40l26_private *cs40l26)
break;
}
- if (i == 2) {
+ if (i == CS40L26_SVC_LE_MAX_ATTEMPTS) {
dev_warn(cs40l26->dev, "Using default tunings\n");
} else {
strncat(svc_bin_file, CS40L26_TUNING_FILE_SUFFIX,
@@ -3054,8 +3786,6 @@ pm_err:
pm_runtime_mark_last_busy(cs40l26->dev);
pm_runtime_put_autosuspend(cs40l26->dev);
- kfree(cs40l26->svc_le_vals);
-
return ret;
}
@@ -3063,23 +3793,41 @@ static void cs40l26_coeff_load(struct cs40l26_private *cs40l26)
{
struct device *dev = cs40l26->dev;
const struct firmware *coeff;
- int i;
+ int i, ret;
for (i = 0; i < cs40l26->fw.num_coeff_files; i++) {
- request_firmware(&coeff, cs40l26->fw.coeff_files[i], dev);
- if (!coeff) {
- dev_warn(dev, "Continuing...\n");
+ ret = request_firmware(&coeff, cs40l26->fw.coeff_files[i], dev);
+ if (ret) {
+ dev_warn(dev, "Continuing...");
continue;
}
- if (cl_dsp_coeff_file_parse(cs40l26->dsp, coeff))
- dev_warn(dev, "Continuing...\n");
+ ret = cl_dsp_coeff_file_parse(cs40l26->dsp, coeff);
+ if (ret)
+ dev_warn(dev, "Failed to load, %s, %d. Continuing...",
+ cs40l26->fw.coeff_files[i], ret);
else
dev_info(dev, "%s Loaded Successfully\n",
cs40l26->fw.coeff_files[i]);
+
+ release_firmware(coeff);
}
}
+static int cs40l26_change_firmware_control_defaults(
+ struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int ret;
+
+ ret = cs40l26_pm_timer_timeout_ticks4_set(cs40l26,
+ cs40l26->pdata.pm_timer_timeout_ticks4);
+ if (ret)
+ dev_err(dev, "failed to set pm_timer_timeout_ticks4, %d", ret);
+
+ return ret;
+}
+
static int cs40l26_firmware_load(struct cs40l26_private *cs40l26, u32 id)
{
struct device *dev = cs40l26->dev;
@@ -3097,45 +3845,104 @@ static int cs40l26_firmware_load(struct cs40l26_private *cs40l26, u32 id)
ret = cl_dsp_firmware_parse(cs40l26->dsp, fw, true);
release_firmware(fw);
+ if (ret) {
+ dev_err(dev, "cl_dsp_firmware_parse failed, %d", ret);
+ return ret;
+ }
+
+ ret = cs40l26_change_firmware_control_defaults(cs40l26);
+
+ if (ret)
+ dev_err(dev, "changing firmware control defaults failed %d",
+ ret);
+
return ret;
}
-int cs40l26_fw_swap(struct cs40l26_private *cs40l26, u32 id)
+static int cs40l26_fw_upload(struct cs40l26_private *cs40l26, u32 id)
{
- struct device *dev = cs40l26->dev;
int ret;
- if (id == cs40l26->fw.id) {
- dev_warn(dev, "Cannot swap to same ID as running firmware\n");
- return 0;
- }
-
- cs40l26_pm_runtime_teardown(cs40l26);
-
- cs40l26->fw_loaded = false;
-
- disable_irq(cs40l26->irq);
-
ret = cs40l26_cl_dsp_init(cs40l26, id);
if (ret)
- goto irq_exit;
+ return ret;
ret = cs40l26_dsp_pre_config(cs40l26);
if (ret)
- goto irq_exit;
+ return ret;
ret = cs40l26_firmware_load(cs40l26, id);
if (ret)
- goto irq_exit;
+ return ret;
+
+ if (cs40l26->num_svc_le_vals && id != CS40L26_FW_CALIB_ID) {
+ ret = cs40l26_dsp_config(cs40l26);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_tuning_select_from_svc_le(cs40l26);
+ if (ret)
+ return ret;
+
+ cs40l26_pm_runtime_teardown(cs40l26);
+
+ ret = cs40l26_dsp_pre_config(cs40l26);
+ if (ret)
+ return ret;
+ }
cs40l26_coeff_load(cs40l26);
- ret = cs40l26_dsp_config(cs40l26);
+ return cs40l26_dsp_config(cs40l26);
+}
-irq_exit:
- enable_irq(cs40l26->irq);
+int cs40l26_fw_swap(struct cs40l26_private *cs40l26, u32 id)
+{
+ struct device *dev = cs40l26->dev;
+ bool register_irq = false;
+ int ret;
- return ret;
+ if (id == cs40l26->fw.id) {
+ dev_warn(dev, "Cannot swap to same ID as running firmware\n");
+ return 0;
+ }
+
+ if (cs40l26->fw_mode == CS40L26_FW_MODE_NONE) {
+ cs40l26->fw_mode = CS40L26_FW_MODE_RAM;
+ register_irq = true;
+ } else {
+ disable_irq(cs40l26->irq);
+ cs40l26_pm_runtime_teardown(cs40l26);
+ }
+
+ if (cs40l26->fw_loaded) {
+ ret = cs40l26_pm_timeout_ms_set(cs40l26,
+ CS40L26_PM_TIMEOUT_MS_MAX);
+ if (ret) {
+ dev_err(cs40l26->dev, "Can't set pm_timeout %d\n", ret);
+ return ret;
+ }
+ }
+
+ cs40l26->fw_loaded = false;
+
+ ret = cs40l26_fw_upload(cs40l26, id);
+ if (ret)
+ return ret;
+
+ if (register_irq) {
+ ret = devm_request_threaded_irq(dev, cs40l26->irq, NULL, cs40l26_irq,
+ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW,
+ "cs40l26", cs40l26);
+ if (ret) {
+ dev_err(dev, "Failed to request threaded IRQ: %d\n", ret);
+ return ret;
+ }
+ } else {
+ enable_irq(cs40l26->irq);
+ }
+
+ return 0;
}
EXPORT_SYMBOL(cs40l26_fw_swap);
@@ -3153,7 +3960,7 @@ static int cs40l26_update_reg_defaults(struct cs40l26_private *cs40l26)
static int cs40l26_handle_svc_le_nodes(struct cs40l26_private *cs40l26)
{
struct device *dev = cs40l26->dev;
- int i, init_count, node_count = 0;
+ int i, ret = 0, init_count, node_count = 0;
struct fwnode_handle *child;
unsigned int min, max, index;
const char *node_name;
@@ -3162,9 +3969,12 @@ static int cs40l26_handle_svc_le_nodes(struct cs40l26_private *cs40l26)
if (!init_count)
return 0;
- cs40l26->svc_le_vals = kcalloc(init_count,
+ cs40l26->svc_le_vals = devm_kcalloc(dev, init_count,
sizeof(struct cs40l26_svc_le *), GFP_KERNEL);
+ if (!cs40l26->svc_le_vals)
+ return -ENOMEM;
+
device_for_each_child_node(dev, child) {
node_name = fwnode_get_name(child);
@@ -3202,7 +4012,13 @@ static int cs40l26_handle_svc_le_nodes(struct cs40l26_private *cs40l26)
}
cs40l26->svc_le_vals[node_count] =
- kzalloc(sizeof(struct cs40l26_svc_le), GFP_KERNEL);
+ devm_kzalloc(dev, sizeof(struct cs40l26_svc_le),
+ GFP_KERNEL);
+
+ if (!cs40l26->svc_le_vals[node_count]) {
+ ret = -ENOMEM;
+ goto err;
+ }
cs40l26->svc_le_vals[node_count]->min = min;
cs40l26->svc_le_vals[node_count]->max = max;
@@ -3215,6 +4031,10 @@ static int cs40l26_handle_svc_le_nodes(struct cs40l26_private *cs40l26)
init_count - node_count);
return node_count;
+
+err:
+ devm_kfree(dev, cs40l26->svc_le_vals);
+ return ret;
}
static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
@@ -3240,6 +4060,9 @@ static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
else
cs40l26->fw_mode = CS40L26_FW_MODE_RAM;
+ if (of_property_read_bool(np, "cirrus,fw-defer"))
+ cs40l26->fw_mode = CS40L26_FW_MODE_NONE;
+
cs40l26->pdata.vbbr_en =
of_property_read_bool(np, "cirrus,vbbr-enable");
@@ -3306,6 +4129,12 @@ static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
else
cs40l26->pdata.bst_ipk = 0;
+ if (!of_property_read_u32(np, "cirrus,pm-timer-timeout-ticks4", &val))
+ cs40l26->pdata.pm_timer_timeout_ticks4 = val;
+ else
+ cs40l26->pdata.pm_timer_timeout_ticks4 =
+ CS40L26_PM_TIMER_TIMEOUT_TICKS4_DEFAULT;
+
ret = cs40l26_handle_svc_le_nodes(cs40l26);
if (ret < 0)
cs40l26->num_svc_le_vals = 0;
@@ -3333,15 +4162,8 @@ int cs40l26_probe(struct cs40l26_private *cs40l26,
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);
-
- cs40l26->asp_workqueue = alloc_ordered_workqueue("asp_workqueue",
- WQ_HIGHPRI);
- if (!cs40l26->asp_workqueue) {
- ret = -ENOMEM;
- goto err;
- }
-
- INIT_WORK(&cs40l26->asp_work, cs40l26_asp_worker);
+ INIT_WORK(&cs40l26->upload_work, cs40l26_upload_worker);
+ INIT_WORK(&cs40l26->erase_work, cs40l26_erase_worker);
ret = devm_regulator_bulk_get(dev, CS40L26_NUM_SUPPLIES,
cs40l26_supplies);
@@ -3396,61 +4218,23 @@ int cs40l26_probe(struct cs40l26_private *cs40l26,
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);
-
cs40l26->pm_ready = false;
cs40l26->fw_loaded = false;
- ret = cs40l26_cl_dsp_init(cs40l26, CS40L26_FW_ID);
- if (ret)
- goto irq_err;
-
- ret = cs40l26_dsp_pre_config(cs40l26);
- if (ret)
- goto irq_err;
-
- ret = cs40l26_firmware_load(cs40l26, cs40l26->fw.id);
- if (ret)
- goto irq_err;
-
- if (cs40l26->num_svc_le_vals) {
- ret = cs40l26_dsp_config(cs40l26);
- if (ret)
- goto irq_err;
-
- enable_irq(cs40l26->irq);
-
- ret = cs40l26_tuning_select_from_svc_le(cs40l26);
+ if (cs40l26->fw_mode != CS40L26_FW_MODE_NONE) {
+ ret = cs40l26_fw_upload(cs40l26, CS40L26_FW_ID);
if (ret)
goto err;
- disable_irq(cs40l26->irq);
- cs40l26_pm_runtime_teardown(cs40l26);
-
- ret = cs40l26_dsp_pre_config(cs40l26);
- if (ret)
+ 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;
- }
-
- cs40l26_coeff_load(cs40l26);
-
- ret = cs40l26_dsp_config(cs40l26);
- if (ret)
- goto irq_err;
+ }
- enable_irq(cs40l26->irq);
+ }
ret = cs40l26_input_init(cs40l26);
if (ret)
@@ -3464,9 +4248,6 @@ int cs40l26_probe(struct cs40l26_private *cs40l26,
}
return 0;
-
-irq_err:
- enable_irq(cs40l26->irq);
err:
cs40l26_remove(cs40l26);
@@ -3492,14 +4273,11 @@ int cs40l26_remove(struct cs40l26_private *cs40l26)
cancel_work_sync(&cs40l26->vibe_start_work);
cancel_work_sync(&cs40l26->vibe_stop_work);
cancel_work_sync(&cs40l26->set_gain_work);
+ cancel_work_sync(&cs40l26->upload_work);
+ cancel_work_sync(&cs40l26->erase_work);
destroy_workqueue(cs40l26->vibe_workqueue);
}
- if (cs40l26->asp_workqueue) {
- cancel_work_sync(&cs40l26->asp_work);
- destroy_workqueue(cs40l26->asp_workqueue);
- }
-
if (vp_consumer)
regulator_disable(vp_consumer);