From cc094c82cf06b9caf28bea8ca93413b04cf54fab Mon Sep 17 00:00:00 2001 From: Jeff LaBundy Date: Wed, 2 May 2018 15:53:18 -0500 Subject: [PATCH] misc: cs40l2x: Add nested loops to playback queue Added the ability to group multiple segments together and repeat them multiple times within the queue. Change-Id: I0eb3a4931092961e8ac332ee5f4d5507a0128c20 Signed-off-by: Jeff LaBundy Signed-off-by: Ravi Vembu --- drivers/misc/cs40l2x.c | 229 +++++++++++++++++++++++++++-------------- drivers/misc/cs40l2x.h | 5 + 2 files changed, 158 insertions(+), 76 deletions(-) diff --git a/drivers/misc/cs40l2x.c b/drivers/misc/cs40l2x.c index 71df95989811..036d32bf28fe 100644 --- a/drivers/misc/cs40l2x.c +++ b/drivers/misc/cs40l2x.c @@ -142,7 +142,6 @@ static ssize_t cs40l2x_cp_trigger_queue_show(struct device *dev, { struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev); ssize_t len = 0; - unsigned int tag, mag; int i; if (cs40l2x->pbq_depth == 0) @@ -150,25 +149,29 @@ static ssize_t cs40l2x_cp_trigger_queue_show(struct device *dev, mutex_lock(&cs40l2x->lock); - for (i = 0; i < cs40l2x->pbq_depth - 1; i++) { - tag = cs40l2x->pbq_pairs[i].tag; - mag = cs40l2x->pbq_pairs[i].mag; + for (i = 0; i < cs40l2x->pbq_depth; i++) { + switch (cs40l2x->pbq_pairs[i].tag) { + case CS40L2X_PBQ_TAG_SILENCE: + len += snprintf(buf + len, PAGE_SIZE - len, "%d", + cs40l2x->pbq_pairs[i].mag); + break; + case CS40L2X_PBQ_TAG_START: + len += snprintf(buf + len, PAGE_SIZE - len, "!!"); + break; + case CS40L2X_PBQ_TAG_STOP: + len += snprintf(buf + len, PAGE_SIZE - len, "%d!!", + cs40l2x->pbq_pairs[i].repeat); + break; + default: + len += snprintf(buf + len, PAGE_SIZE - len, "%d.%d", + cs40l2x->pbq_pairs[i].tag, + cs40l2x->pbq_pairs[i].mag); + } - if (tag) - len += snprintf(buf + len, PAGE_SIZE - len, "%d.%d, ", - tag, mag); - else - len += snprintf(buf + len, PAGE_SIZE - len, "%d, ", - mag); + if (i < (cs40l2x->pbq_depth - 1)) + len += snprintf(buf + len, PAGE_SIZE - len, ", "); } - tag = cs40l2x->pbq_pairs[i].tag; - mag = cs40l2x->pbq_pairs[i].mag; - if (tag) - len += snprintf(buf + len, PAGE_SIZE - len, "%d.%d", tag, mag); - else - len += snprintf(buf + len, PAGE_SIZE - len, "%d", mag); - switch (cs40l2x->pbq_repeat) { case -1: len += snprintf(buf + len, PAGE_SIZE - len, ", ~\n"); @@ -195,7 +198,8 @@ static ssize_t cs40l2x_cp_trigger_queue_store(struct device *dev, char *pbq_seg_alloc, *pbq_seg, *pbq_seg_tok; size_t pbq_seg_len; unsigned int pbq_depth = 0; - unsigned int val; + unsigned int val, num_empty; + int pbq_marker = -1; int ret; pbq_str_alloc = kzalloc(count, GFP_KERNEL); @@ -259,22 +263,68 @@ static ssize_t cs40l2x_cp_trigger_queue_store(struct device *dev, /* repetition specifier */ } else if (strnchr(pbq_seg, CS40L2X_PBQ_SEG_LEN_MAX, '!')) { - if (cs40l2x->pbq_repeat) { - ret = -EINVAL; - goto err_mutex; - } + val = 0; + num_empty = 0; + pbq_seg_tok = strsep(&pbq_seg, "!"); - ret = kstrtou32(pbq_seg_tok, 10, &val); - if (ret) { + while (pbq_seg_tok) { + if (strnlen(pbq_seg_tok, + CS40L2X_PBQ_SEG_LEN_MAX)) { + ret = kstrtou32(pbq_seg_tok, 10, &val); + if (ret) { + ret = -EINVAL; + goto err_mutex; + } + if (val > CS40L2X_PBQ_REPEAT_MAX) { + ret = -EINVAL; + goto err_mutex; + } + } else { + num_empty++; + } + + pbq_seg_tok = strsep(&pbq_seg, "!"); + } + + /* number of empty tokens reveals specifier type */ + switch (num_empty) { + case 1: /* outer loop: "n!" or "!n" */ + if (cs40l2x->pbq_repeat) { + ret = -EINVAL; + goto err_mutex; + } + cs40l2x->pbq_repeat = val; + break; + + case 2: /* inner loop stop: "n!!" or "!!n" */ + if (pbq_marker < 0) { + ret = -EINVAL; + goto err_mutex; + } + + cs40l2x->pbq_pairs[pbq_depth].tag = + CS40L2X_PBQ_TAG_STOP; + cs40l2x->pbq_pairs[pbq_depth].mag = pbq_marker; + cs40l2x->pbq_pairs[pbq_depth++].repeat = val; + pbq_marker = -1; + break; + + case 3: /* inner loop start: "!!" */ + if (pbq_marker >= 0) { + ret = -EINVAL; + goto err_mutex; + } + + cs40l2x->pbq_pairs[pbq_depth].tag = + CS40L2X_PBQ_TAG_START; + pbq_marker = pbq_depth++; + break; + + default: ret = -EINVAL; goto err_mutex; } - if (val > CS40L2X_PBQ_REPEAT_MAX) { - ret = -EINVAL; - goto err_mutex; - } - cs40l2x->pbq_repeat = val; /* loop specifier */ } else if (strnchr(pbq_seg, CS40L2X_PBQ_SEG_LEN_MAX, '~')) { @@ -286,7 +336,8 @@ static ssize_t cs40l2x_cp_trigger_queue_store(struct device *dev, /* duration specifier */ } else { - cs40l2x->pbq_pairs[pbq_depth].tag = 0; + cs40l2x->pbq_pairs[pbq_depth].tag = + CS40L2X_PBQ_TAG_SILENCE; ret = kstrtou32(pbq_seg, 10, &val); if (ret) { @@ -956,62 +1007,83 @@ static int cs40l2x_pbq_pair_launch(struct cs40l2x_private *cs40l2x) struct regmap *regmap = cs40l2x->regmap; unsigned int dig_scale = cs40l2x->dig_scale; unsigned int tag, mag; - int ret; + int ret, i; /* this function expects to be called from a locked worker function */ if (!mutex_is_locked(&cs40l2x->lock)) return -EACCES; - if (cs40l2x->pbq_index == cs40l2x->pbq_depth) { - cs40l2x->pbq_index = 0; + do { + /* restart queue as necessary */ + if (cs40l2x->pbq_index == cs40l2x->pbq_depth) { + cs40l2x->pbq_index = 0; + for (i = 0; i < cs40l2x->pbq_depth; i++) + cs40l2x->pbq_pairs[i].remain = + cs40l2x->pbq_pairs[i].repeat; - switch (cs40l2x->pbq_remain) { - case -1: - /* loop until stopped */ + switch (cs40l2x->pbq_remain) { + case -1: + /* loop until stopped */ + break; + case 0: + /* queue is finished */ + cs40l2x->cp_trailer_index = 0; + cs40l2x->pbq_state = CS40L2X_PBQ_STATE_IDLE; + + ret = cs40l2x_dig_scale_set(cs40l2x, dig_scale); + return ret; + default: + /* loop once more */ + cs40l2x->pbq_remain--; + } + } + + tag = cs40l2x->pbq_pairs[cs40l2x->pbq_index].tag; + mag = cs40l2x->pbq_pairs[cs40l2x->pbq_index].mag; + + switch (tag) { + case CS40L2X_PBQ_TAG_SILENCE: + hrtimer_start(&cs40l2x->pbq_timer, + ktime_set(mag / 1000, + (mag % 1000) * 1000000), + HRTIMER_MODE_REL); + cs40l2x->pbq_state = CS40L2X_PBQ_STATE_SILENT; + cs40l2x->pbq_index++; break; - case 0: - /* queue is finished */ - cs40l2x->cp_trailer_index = 0; - cs40l2x->pbq_state = CS40L2X_PBQ_STATE_IDLE; + case CS40L2X_PBQ_TAG_START: + cs40l2x->pbq_index++; + break; + case CS40L2X_PBQ_TAG_STOP: + if (cs40l2x->pbq_pairs[cs40l2x->pbq_index].remain) { + cs40l2x->pbq_pairs[cs40l2x->pbq_index].remain--; + cs40l2x->pbq_index = mag; + } else { + cs40l2x->pbq_index++; + } + break; + default: + dig_scale += cs40l2x_pbq_scale[mag].dig_scale; + if (dig_scale > CS40L2X_DIG_SCALE_MAX) + dig_scale = CS40L2X_DIG_SCALE_MAX; ret = cs40l2x_dig_scale_set(cs40l2x, dig_scale); - return ret; - default: - /* loop once more */ - cs40l2x->pbq_remain--; + if (ret) + return ret; + + ret = regmap_write(regmap, CS40L2X_MBOX_TRIGGERINDEX, + tag); + if (ret) + return ret; + + ret = cs40l2x_pbq_poll(cs40l2x); + if (ret) + return ret; + + cs40l2x->pbq_state = CS40L2X_PBQ_STATE_PLAYING; + cs40l2x->pbq_index++; } - } - tag = cs40l2x->pbq_pairs[cs40l2x->pbq_index].tag; - mag = cs40l2x->pbq_pairs[cs40l2x->pbq_index].mag; - - /* zero tag indicates a period of silence */ - if (tag) { - dig_scale += cs40l2x_pbq_scale[mag].dig_scale; - if (dig_scale > CS40L2X_DIG_SCALE_MAX) - dig_scale = CS40L2X_DIG_SCALE_MAX; - - ret = cs40l2x_dig_scale_set(cs40l2x, dig_scale); - if (ret) - return ret; - - ret = regmap_write(regmap, CS40L2X_MBOX_TRIGGERINDEX, tag); - if (ret) - return ret; - - ret = cs40l2x_pbq_poll(cs40l2x); - if (ret) - return ret; - - cs40l2x->pbq_state = CS40L2X_PBQ_STATE_PLAYING; - } else { - hrtimer_start(&cs40l2x->pbq_timer, - ktime_set(mag / 1000, (mag % 1000) * 1000000), - HRTIMER_MODE_REL); - cs40l2x->pbq_state = CS40L2X_PBQ_STATE_SILENT; - } - - cs40l2x->pbq_index++; + } while (tag == CS40L2X_PBQ_TAG_START || tag == CS40L2X_PBQ_TAG_STOP); return 0; } @@ -1107,7 +1179,7 @@ static void cs40l2x_vibe_start_worker(struct work_struct *work) container_of(work, struct cs40l2x_private, vibe_start_work); struct regmap *regmap = cs40l2x->regmap; struct device *dev = cs40l2x->dev; - int ret; + int ret, i; mutex_lock(&cs40l2x->lock); @@ -1159,6 +1231,11 @@ static void cs40l2x_vibe_start_worker(struct work_struct *work) case CS40L2X_INDEX_PBQ: cs40l2x->pbq_index = 0; cs40l2x->pbq_remain = cs40l2x->pbq_repeat; + + for (i = 0; i < cs40l2x->pbq_depth; i++) + cs40l2x->pbq_pairs[i].remain = + cs40l2x->pbq_pairs[i].repeat; + ret = cs40l2x_pbq_pair_launch(cs40l2x); if (ret) dev_err(dev, "Failed to launch playback queue\n"); diff --git a/drivers/misc/cs40l2x.h b/drivers/misc/cs40l2x.h index 6ac166517266..2adb5b9d1f4c 100644 --- a/drivers/misc/cs40l2x.h +++ b/drivers/misc/cs40l2x.h @@ -700,6 +700,9 @@ #define CS40L2X_PBQ_STATE_IDLE 0x00 #define CS40L2X_PBQ_STATE_PLAYING 0x01 #define CS40L2X_PBQ_STATE_SILENT 0x02 +#define CS40L2X_PBQ_TAG_SILENCE 0x0000 +#define CS40L2X_PBQ_TAG_START 0x8000 +#define CS40L2X_PBQ_TAG_STOP 0x8001 #define CS40L2X_DIAG_STATE_INIT 0x00 #define CS40L2X_DIAG_STATE_RUN 0x01 @@ -760,6 +763,8 @@ struct cs40l2x_coeff_desc { struct cs40l2x_pbq_pair { unsigned int tag; unsigned int mag; + unsigned int repeat; + unsigned int remain; }; struct cs40l2x_dig_scale_map {