mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-10 06:09:23 -04:00
misc: cs40l2x: Add playback queue support
* Added the ability to trigger multiple waveform indexes and delays in a sequence based on a string passed to a sysfs control. * Moved the call to start vibe_timer to the vibe_start_worker function and restricted the timer's use to continuous playback. * Canceled vibe_timer prior to destroying vibe_workqueue. * Replaced hard-coded waveform indexes with macros. * Replaced all digital volume register writes with a single function. Change-Id: I1b28bcc8d9d2a90bda432f0d3def83563baf9496 Signed-off-by: Jeff LaBundy <jeff.labundy@cirrus.com> Signed-off-by: Ravi Vembu <raviv@motorola.com>
This commit is contained in:
parent
e03c0063ac
commit
23248efe7d
3 changed files with 557 additions and 21 deletions
|
|
@ -740,3 +740,108 @@ const struct cs40l2x_otp_desc cs40l2x_otp_map[CS40L2X_NUM_OTP_MAPS] = {
|
|||
.trim_table = cs40l2x_trim_table_d,
|
||||
},
|
||||
};
|
||||
|
||||
const struct cs40l2x_dig_scale_map
|
||||
cs40l2x_pbq_scale[CS40L2X_PBQ_SCALE_MAX + 1] = {
|
||||
{0, 0},
|
||||
{1, 320},
|
||||
{2, 272},
|
||||
{3, 244},
|
||||
{4, 224},
|
||||
{5, 208},
|
||||
{6, 195},
|
||||
{7, 185},
|
||||
{8, 176},
|
||||
{9, 167},
|
||||
{10, 160},
|
||||
{11, 153},
|
||||
{12, 147},
|
||||
{13, 142},
|
||||
{14, 137},
|
||||
{15, 132},
|
||||
{16, 127},
|
||||
{17, 123},
|
||||
{18, 119},
|
||||
{19, 115},
|
||||
{20, 112},
|
||||
{21, 108},
|
||||
{22, 105},
|
||||
{23, 102},
|
||||
{24, 99},
|
||||
{25, 96},
|
||||
{26, 94},
|
||||
{27, 91},
|
||||
{28, 88},
|
||||
{29, 86},
|
||||
{30, 84},
|
||||
{31, 81},
|
||||
{32, 79},
|
||||
{33, 77},
|
||||
{34, 75},
|
||||
{35, 73},
|
||||
{36, 71},
|
||||
{37, 69},
|
||||
{38, 67},
|
||||
{39, 65},
|
||||
{40, 64},
|
||||
{41, 62},
|
||||
{42, 60},
|
||||
{43, 59},
|
||||
{44, 57},
|
||||
{45, 55},
|
||||
{46, 54},
|
||||
{47, 52},
|
||||
{48, 51},
|
||||
{49, 50},
|
||||
{50, 48},
|
||||
{51, 47},
|
||||
{52, 45},
|
||||
{53, 44},
|
||||
{54, 43},
|
||||
{55, 42},
|
||||
{56, 40},
|
||||
{57, 39},
|
||||
{58, 38},
|
||||
{59, 37},
|
||||
{60, 35},
|
||||
{61, 34},
|
||||
{62, 33},
|
||||
{63, 32},
|
||||
{64, 31},
|
||||
{65, 30},
|
||||
{66, 29},
|
||||
{67, 28},
|
||||
{68, 27},
|
||||
{69, 26},
|
||||
{70, 25},
|
||||
{71, 24},
|
||||
{72, 23},
|
||||
{73, 22},
|
||||
{74, 21},
|
||||
{75, 20},
|
||||
{76, 19},
|
||||
{77, 18},
|
||||
{78, 17},
|
||||
{79, 16},
|
||||
{80, 16},
|
||||
{81, 15},
|
||||
{82, 14},
|
||||
{83, 13},
|
||||
{84, 12},
|
||||
{85, 11},
|
||||
{86, 10},
|
||||
{87, 10},
|
||||
{88, 9},
|
||||
{89, 8},
|
||||
{90, 7},
|
||||
{91, 7},
|
||||
{92, 6},
|
||||
{93, 5},
|
||||
{94, 4},
|
||||
{95, 4},
|
||||
{96, 3},
|
||||
{97, 2},
|
||||
{98, 1},
|
||||
{99, 1},
|
||||
{100, 0},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/cs40l2x.h>
|
||||
|
|
@ -36,7 +37,6 @@
|
|||
|
||||
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
|
||||
#include "../staging/android/timed_output.h"
|
||||
#include <linux/hrtimer.h>
|
||||
#else
|
||||
#include <linux/leds.h>
|
||||
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
|
||||
|
|
@ -49,6 +49,7 @@ struct cs40l2x_private {
|
|||
unsigned int devid;
|
||||
unsigned int revid;
|
||||
struct work_struct vibe_start_work;
|
||||
struct work_struct vibe_pbq_work;
|
||||
struct work_struct vibe_stop_work;
|
||||
struct workqueue_struct *vibe_workqueue;
|
||||
struct mutex lock;
|
||||
|
|
@ -67,9 +68,17 @@ struct cs40l2x_private {
|
|||
unsigned int f0_measured;
|
||||
unsigned int redc_measured;
|
||||
unsigned int dig_scale;
|
||||
struct cs40l2x_pbq_pair pbq_pairs[CS40L2X_PBQ_DEPTH_MAX];
|
||||
struct hrtimer pbq_timer;
|
||||
unsigned int pbq_depth;
|
||||
unsigned int pbq_index;
|
||||
unsigned int pbq_state;
|
||||
int pbq_repeat;
|
||||
int pbq_remain;
|
||||
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
|
||||
struct timed_output_dev timed_dev;
|
||||
struct hrtimer vibe_timer;
|
||||
int vibe_timeout;
|
||||
#else
|
||||
struct led_classdev led_dev;
|
||||
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
|
||||
|
|
@ -119,6 +128,7 @@ static ssize_t cs40l2x_cp_trigger_index_store(struct device *dev,
|
|||
return -EINVAL;
|
||||
|
||||
if ((index & CS40L2X_INDEX_MASK) >= cs40l2x->num_waves
|
||||
&& index != CS40L2X_INDEX_PBQ
|
||||
&& index != CS40L2X_INDEX_DIAG)
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -127,6 +137,188 @@ static ssize_t cs40l2x_cp_trigger_index_store(struct device *dev,
|
|||
return count;
|
||||
}
|
||||
|
||||
static ssize_t cs40l2x_cp_trigger_queue_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
|
||||
ssize_t len = 0;
|
||||
unsigned int tag, mag;
|
||||
int i;
|
||||
|
||||
if (cs40l2x->pbq_depth == 0)
|
||||
return -ENODATA;
|
||||
|
||||
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;
|
||||
|
||||
if (tag)
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, "%d.%d, ",
|
||||
tag, mag);
|
||||
else
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, "%d, ",
|
||||
mag);
|
||||
}
|
||||
|
||||
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");
|
||||
break;
|
||||
case 0:
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
|
||||
break;
|
||||
default:
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, ", %d!\n",
|
||||
cs40l2x->pbq_repeat);
|
||||
}
|
||||
|
||||
mutex_unlock(&cs40l2x->lock);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t cs40l2x_cp_trigger_queue_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
|
||||
char *pbq_str_alloc, *pbq_str, *pbq_str_tok;
|
||||
char *pbq_seg_alloc, *pbq_seg, *pbq_seg_tok;
|
||||
unsigned int pbq_depth = 0;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
pbq_str_alloc = kzalloc(count, GFP_KERNEL);
|
||||
if (!pbq_str_alloc)
|
||||
return -ENOMEM;
|
||||
|
||||
pbq_seg_alloc = kzalloc(CS40L2X_PBQ_SEG_LEN_MAX + 1, GFP_KERNEL);
|
||||
if (!pbq_seg_alloc) {
|
||||
kfree(pbq_str_alloc);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
mutex_lock(&cs40l2x->lock);
|
||||
|
||||
cs40l2x->pbq_depth = 0;
|
||||
cs40l2x->pbq_repeat = 0;
|
||||
|
||||
pbq_str = pbq_str_alloc;
|
||||
strcpy(pbq_str, buf);
|
||||
|
||||
pbq_str_tok = strsep(&pbq_str, ",");
|
||||
|
||||
while (pbq_str_tok) {
|
||||
if (strlen(strim(pbq_str_tok)) > CS40L2X_PBQ_SEG_LEN_MAX) {
|
||||
ret = -E2BIG;
|
||||
goto err_mutex;
|
||||
}
|
||||
|
||||
pbq_seg = pbq_seg_alloc;
|
||||
strcpy(pbq_seg, strim(pbq_str_tok));
|
||||
|
||||
/* waveform specifier */
|
||||
if (strchr(pbq_seg, '.')) {
|
||||
/* index */
|
||||
pbq_seg_tok = strsep(&pbq_seg, ".");
|
||||
|
||||
ret = kstrtou32(pbq_seg_tok, 10, &val);
|
||||
if (ret) {
|
||||
ret = -EINVAL;
|
||||
goto err_mutex;
|
||||
}
|
||||
if (val == 0 || val >= cs40l2x->num_waves) {
|
||||
ret = -EINVAL;
|
||||
goto err_mutex;
|
||||
}
|
||||
cs40l2x->pbq_pairs[pbq_depth].tag = val;
|
||||
|
||||
/* scale */
|
||||
pbq_seg_tok = strsep(&pbq_seg, ".");
|
||||
|
||||
ret = kstrtou32(pbq_seg_tok, 10, &val);
|
||||
if (ret) {
|
||||
ret = -EINVAL;
|
||||
goto err_mutex;
|
||||
}
|
||||
if (val == 0 || val > CS40L2X_PBQ_SCALE_MAX) {
|
||||
ret = -EINVAL;
|
||||
goto err_mutex;
|
||||
}
|
||||
cs40l2x->pbq_pairs[pbq_depth++].mag = val;
|
||||
|
||||
/* repetition specifier */
|
||||
} else if (strchr(pbq_seg, '!')) {
|
||||
if (cs40l2x->pbq_repeat) {
|
||||
ret = -EINVAL;
|
||||
goto err_mutex;
|
||||
}
|
||||
pbq_seg_tok = strsep(&pbq_seg, "!");
|
||||
|
||||
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;
|
||||
}
|
||||
cs40l2x->pbq_repeat = val;
|
||||
|
||||
/* loop specifier */
|
||||
} else if (strchr(pbq_seg, '~')) {
|
||||
if (cs40l2x->pbq_repeat) {
|
||||
ret = -EINVAL;
|
||||
goto err_mutex;
|
||||
}
|
||||
cs40l2x->pbq_repeat = -1;
|
||||
|
||||
/* duration specifier */
|
||||
} else {
|
||||
cs40l2x->pbq_pairs[pbq_depth].tag = 0;
|
||||
|
||||
ret = kstrtou32(pbq_seg, 10, &val);
|
||||
if (ret) {
|
||||
ret = -EINVAL;
|
||||
goto err_mutex;
|
||||
}
|
||||
if (val > CS40L2X_PBQ_DELAY_MAX) {
|
||||
ret = -EINVAL;
|
||||
goto err_mutex;
|
||||
}
|
||||
cs40l2x->pbq_pairs[pbq_depth++].mag = val;
|
||||
}
|
||||
|
||||
if (pbq_depth == CS40L2X_PBQ_DEPTH_MAX) {
|
||||
ret = -E2BIG;
|
||||
goto err_mutex;
|
||||
}
|
||||
|
||||
pbq_str_tok = strsep(&pbq_str, ",");
|
||||
}
|
||||
|
||||
cs40l2x->pbq_depth = pbq_depth;
|
||||
ret = count;
|
||||
|
||||
err_mutex:
|
||||
mutex_unlock(&cs40l2x->lock);
|
||||
|
||||
kfree(pbq_str_alloc);
|
||||
kfree(pbq_seg_alloc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned int cs40l2x_dsp_reg(struct cs40l2x_private *cs40l2x,
|
||||
const char *coeff_name, const unsigned char block_type)
|
||||
{
|
||||
|
|
@ -520,6 +712,21 @@ static ssize_t cs40l2x_comp_enable_store(struct device *dev,
|
|||
return count;
|
||||
}
|
||||
|
||||
static int cs40l2x_dig_scale_set(struct cs40l2x_private *cs40l2x,
|
||||
unsigned int dig_scale)
|
||||
{
|
||||
/* this function expects to be called while mutex is locked */
|
||||
if (!mutex_is_locked(&cs40l2x->lock))
|
||||
return -EACCES;
|
||||
|
||||
return regmap_update_bits(cs40l2x->regmap,
|
||||
CS40L2X_AMP_DIG_VOL_CTRL,
|
||||
CS40L2X_AMP_VOL_PCM_MASK,
|
||||
((CS40L2X_DIG_SCALE_ZERO - dig_scale)
|
||||
& CS40L2X_DIG_SCALE_MASK)
|
||||
<< CS40L2X_AMP_VOL_PCM_SHIFT);
|
||||
}
|
||||
|
||||
static ssize_t cs40l2x_dig_scale_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
|
|
@ -544,10 +751,7 @@ static ssize_t cs40l2x_dig_scale_store(struct device *dev,
|
|||
return -EINVAL;
|
||||
|
||||
mutex_lock(&cs40l2x->lock);
|
||||
ret = regmap_update_bits(cs40l2x->regmap, CS40L2X_AMP_DIG_VOL_CTRL,
|
||||
CS40L2X_AMP_VOL_PCM_MASK,
|
||||
((0x800 - dig_scale) & 0x7FF)
|
||||
<< CS40L2X_AMP_VOL_PCM_SHIFT);
|
||||
cs40l2x_dig_scale_set(cs40l2x, dig_scale);
|
||||
mutex_unlock(&cs40l2x->lock);
|
||||
|
||||
if (ret) {
|
||||
|
|
@ -591,6 +795,8 @@ static ssize_t cs40l2x_num_waves_show(struct device *dev,
|
|||
|
||||
static DEVICE_ATTR(cp_trigger_index, 0660, cs40l2x_cp_trigger_index_show,
|
||||
cs40l2x_cp_trigger_index_store);
|
||||
static DEVICE_ATTR(cp_trigger_queue, 0660, cs40l2x_cp_trigger_queue_show,
|
||||
cs40l2x_cp_trigger_queue_store);
|
||||
static DEVICE_ATTR(gpio1_rise_index, 0660, cs40l2x_gpio1_rise_index_show,
|
||||
cs40l2x_gpio1_rise_index_store);
|
||||
static DEVICE_ATTR(gpio1_fall_index, 0660, cs40l2x_gpio1_fall_index_show,
|
||||
|
|
@ -614,6 +820,7 @@ static DEVICE_ATTR(num_waves, 0660, cs40l2x_num_waves_show, NULL);
|
|||
|
||||
static struct attribute *cs40l2x_dev_attrs[] = {
|
||||
&dev_attr_cp_trigger_index.attr,
|
||||
&dev_attr_cp_trigger_queue.attr,
|
||||
&dev_attr_gpio1_rise_index.attr,
|
||||
&dev_attr_gpio1_fall_index.attr,
|
||||
&dev_attr_gpio1_fall_timeout.attr,
|
||||
|
|
@ -633,6 +840,160 @@ static struct attribute_group cs40l2x_dev_attr_group = {
|
|||
.attrs = cs40l2x_dev_attrs,
|
||||
};
|
||||
|
||||
static int cs40l2x_pbq_cancel(struct cs40l2x_private *cs40l2x)
|
||||
{
|
||||
struct regmap *regmap = cs40l2x->regmap;
|
||||
int ret;
|
||||
|
||||
/* this function expects to be called from a locked worker function */
|
||||
if (!mutex_is_locked(&cs40l2x->lock))
|
||||
return -EACCES;
|
||||
|
||||
hrtimer_cancel(&cs40l2x->pbq_timer);
|
||||
|
||||
ret = regmap_write(regmap, cs40l2x_dsp_reg(cs40l2x, "ENDPLAYBACK",
|
||||
CS40L2X_XM_UNPACKED_TYPE), 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = cs40l2x_dig_scale_set(cs40l2x, cs40l2x->dig_scale);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cs40l2x->pbq_state = CS40L2X_PBQ_STATE_IDLE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
|
||||
hrtimer_start(&cs40l2x->pbq_timer,
|
||||
ktime_set(0, CS40L2X_PBQ_POLL_NS),
|
||||
HRTIMER_MODE_REL);
|
||||
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++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cs40l2x_vibe_pbq_worker(struct work_struct *work)
|
||||
{
|
||||
struct cs40l2x_private *cs40l2x =
|
||||
container_of(work, struct cs40l2x_private, vibe_pbq_work);
|
||||
struct regmap *regmap = cs40l2x->regmap;
|
||||
struct device *dev = cs40l2x->dev;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cs40l2x->lock);
|
||||
|
||||
switch (cs40l2x->pbq_state) {
|
||||
case CS40L2X_PBQ_STATE_IDLE:
|
||||
/* queue may have been canceled */
|
||||
goto err_mutex;
|
||||
|
||||
case CS40L2X_PBQ_STATE_PLAYING:
|
||||
ret = regmap_read(regmap, cs40l2x_dsp_reg(cs40l2x, "STATUS",
|
||||
CS40L2X_XM_UNPACKED_TYPE), &val);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to capture playback status\n");
|
||||
goto err_mutex;
|
||||
}
|
||||
|
||||
if (val == CS40L2X_STATUS_IDLE) {
|
||||
ret = cs40l2x_pbq_pair_launch(cs40l2x);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to continue playback queue\n");
|
||||
goto err_mutex;
|
||||
}
|
||||
} else {
|
||||
hrtimer_start(&cs40l2x->pbq_timer,
|
||||
ktime_set(0, CS40L2X_PBQ_POLL_NS),
|
||||
HRTIMER_MODE_REL);
|
||||
}
|
||||
break;
|
||||
|
||||
case CS40L2X_PBQ_STATE_SILENT:
|
||||
ret = cs40l2x_pbq_pair_launch(cs40l2x);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to continue playback queue\n");
|
||||
goto err_mutex;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(dev, "Unexpected playback queue state: %d\n",
|
||||
cs40l2x->pbq_state);
|
||||
}
|
||||
|
||||
err_mutex:
|
||||
mutex_unlock(&cs40l2x->lock);
|
||||
}
|
||||
|
||||
static enum hrtimer_restart cs40l2x_pbq_timer(struct hrtimer *timer)
|
||||
{
|
||||
struct cs40l2x_private *cs40l2x =
|
||||
container_of(timer, struct cs40l2x_private, pbq_timer);
|
||||
|
||||
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_pbq_work);
|
||||
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static int cs40l2x_diag_capture(struct cs40l2x_private *cs40l2x)
|
||||
{
|
||||
struct regmap *regmap = cs40l2x->regmap;
|
||||
|
|
@ -670,36 +1031,63 @@ static void cs40l2x_vibe_start_worker(struct work_struct *work)
|
|||
|
||||
mutex_lock(&cs40l2x->lock);
|
||||
|
||||
/* gracefully exit if diagnostics stimulus is interrupted */
|
||||
if (cs40l2x->cp_trailer_index == CS40L2X_INDEX_DIAG) {
|
||||
/* handle interruption of special cases */
|
||||
switch (cs40l2x->cp_trailer_index) {
|
||||
case CS40L2X_INDEX_PBQ:
|
||||
ret = cs40l2x_pbq_cancel(cs40l2x);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to cancel playback queue\n");
|
||||
break;
|
||||
|
||||
case CS40L2X_INDEX_DIAG:
|
||||
cs40l2x->diag_state = CS40L2X_DIAG_STATE_INIT;
|
||||
goto err_mutex;
|
||||
}
|
||||
|
||||
cs40l2x->cp_trailer_index = cs40l2x->cp_trigger_index;
|
||||
|
||||
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
|
||||
/* launch timer in the event of continuous playback */
|
||||
switch (cs40l2x->cp_trailer_index) {
|
||||
case 0x0000:
|
||||
case 0x8000 ... 0xFFFE:
|
||||
case CS40L2X_INDEX_VIBE:
|
||||
case CS40L2X_INDEX_CONT_MIN ... CS40L2X_INDEX_CONT_MAX:
|
||||
case CS40L2X_INDEX_DIAG:
|
||||
hrtimer_start(&cs40l2x->vibe_timer,
|
||||
ktime_set(cs40l2x->vibe_timeout / 1000,
|
||||
(cs40l2x->vibe_timeout % 1000)
|
||||
* 1000000),
|
||||
HRTIMER_MODE_REL);
|
||||
}
|
||||
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
|
||||
|
||||
switch (cs40l2x->cp_trailer_index) {
|
||||
case CS40L2X_INDEX_VIBE:
|
||||
case CS40L2X_INDEX_CONT_MIN ... CS40L2X_INDEX_CONT_MAX:
|
||||
ret = regmap_write(regmap, CS40L2X_MBOX_TRIGGER_MS,
|
||||
cs40l2x->cp_trailer_index & CS40L2X_INDEX_MASK);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to start playback\n");
|
||||
break;
|
||||
|
||||
case 0x0001 ... 0x7FFF:
|
||||
case CS40L2X_INDEX_CLICK_MIN ... CS40L2X_INDEX_CLICK_MAX:
|
||||
ret = regmap_write(regmap, CS40L2X_MBOX_TRIGGERINDEX,
|
||||
cs40l2x->cp_trailer_index);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to start playback\n");
|
||||
break;
|
||||
|
||||
case CS40L2X_INDEX_PBQ:
|
||||
cs40l2x->pbq_index = 0;
|
||||
cs40l2x->pbq_remain = cs40l2x->pbq_repeat;
|
||||
ret = cs40l2x_pbq_pair_launch(cs40l2x);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to launch playback queue\n");
|
||||
break;
|
||||
|
||||
case CS40L2X_INDEX_DIAG:
|
||||
cs40l2x->diag_state = CS40L2X_DIAG_STATE_INIT;
|
||||
|
||||
ret = regmap_update_bits(cs40l2x->regmap,
|
||||
CS40L2X_AMP_DIG_VOL_CTRL,
|
||||
CS40L2X_AMP_VOL_PCM_MASK, 0);
|
||||
ret = cs40l2x_dig_scale_set(cs40l2x, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to reset digital scale\n");
|
||||
goto err_mutex;
|
||||
|
|
@ -760,15 +1148,17 @@ static void cs40l2x_vibe_stop_worker(struct work_struct *work)
|
|||
if (ret)
|
||||
dev_err(dev, "Failed to disable stimulus mode\n");
|
||||
|
||||
ret = regmap_update_bits(cs40l2x->regmap,
|
||||
CS40L2X_AMP_DIG_VOL_CTRL,
|
||||
CS40L2X_AMP_VOL_PCM_MASK,
|
||||
((0x800 - cs40l2x->dig_scale) & 0x7FF)
|
||||
<< CS40L2X_AMP_VOL_PCM_SHIFT);
|
||||
ret = cs40l2x_dig_scale_set(cs40l2x, cs40l2x->dig_scale);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to restore digital scale\n");
|
||||
break;
|
||||
|
||||
case CS40L2X_INDEX_PBQ:
|
||||
ret = cs40l2x_pbq_cancel(cs40l2x);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to cancel playback queue\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = regmap_write(regmap,
|
||||
cs40l2x_dsp_reg(cs40l2x, "ENDPLAYBACK",
|
||||
|
|
@ -790,9 +1180,7 @@ static void cs40l2x_vibe_enable(struct timed_output_dev *sdev, int timeout)
|
|||
container_of(sdev, struct cs40l2x_private, timed_dev);
|
||||
|
||||
if (timeout > 0) {
|
||||
hrtimer_start(&cs40l2x->vibe_timer,
|
||||
ktime_set(timeout / 1000, (timeout % 1000) * 1000000),
|
||||
HRTIMER_MODE_REL);
|
||||
cs40l2x->vibe_timeout = timeout;
|
||||
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_start_work);
|
||||
} else {
|
||||
hrtimer_cancel(&cs40l2x->vibe_timer);
|
||||
|
|
@ -894,6 +1282,8 @@ static void cs40l2x_create_led(struct cs40l2x_private *cs40l2x)
|
|||
|
||||
static void cs40l2x_vibe_init(struct cs40l2x_private *cs40l2x)
|
||||
{
|
||||
struct hrtimer *pbq_timer = &cs40l2x->pbq_timer;
|
||||
|
||||
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
|
||||
cs40l2x_create_timed_output(cs40l2x);
|
||||
#else
|
||||
|
|
@ -908,8 +1298,12 @@ static void cs40l2x_vibe_init(struct cs40l2x_private *cs40l2x)
|
|||
}
|
||||
|
||||
INIT_WORK(&cs40l2x->vibe_start_work, cs40l2x_vibe_start_worker);
|
||||
INIT_WORK(&cs40l2x->vibe_pbq_work, cs40l2x_vibe_pbq_worker);
|
||||
INIT_WORK(&cs40l2x->vibe_stop_work, cs40l2x_vibe_stop_worker);
|
||||
|
||||
hrtimer_init(pbq_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
pbq_timer->function = cs40l2x_pbq_timer;
|
||||
|
||||
cs40l2x->vibe_init_success = true;
|
||||
}
|
||||
|
||||
|
|
@ -2034,6 +2428,8 @@ static int cs40l2x_i2c_remove(struct i2c_client *i2c_client)
|
|||
|
||||
if (cs40l2x->vibe_init_success) {
|
||||
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
|
||||
hrtimer_cancel(&cs40l2x->vibe_timer);
|
||||
|
||||
timed_output_dev_unregister(&cs40l2x->timed_dev);
|
||||
|
||||
sysfs_remove_group(&cs40l2x->timed_dev.dev->kobj,
|
||||
|
|
@ -2045,7 +2441,10 @@ static int cs40l2x_i2c_remove(struct i2c_client *i2c_client)
|
|||
&cs40l2x_dev_attr_group);
|
||||
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
|
||||
|
||||
hrtimer_cancel(&cs40l2x->pbq_timer);
|
||||
|
||||
cancel_work_sync(&cs40l2x->vibe_start_work);
|
||||
cancel_work_sync(&cs40l2x->vibe_pbq_work);
|
||||
cancel_work_sync(&cs40l2x->vibe_stop_work);
|
||||
|
||||
destroy_workqueue(cs40l2x->vibe_workqueue);
|
||||
|
|
|
|||
|
|
@ -641,14 +641,31 @@
|
|||
|
||||
#define CS40L2X_TIMEOUT_MS_MAX 0x02AAAA /* ~174 sec */
|
||||
#define CS40L2X_HALO_STATE_RUNNING 0x2
|
||||
#define CS40L2X_STATUS_IDLE 0xFFFFFF
|
||||
|
||||
#define CS40L2X_MBOX_TRIGGERINDEX CS40L2X_DSP_VIRT1_MBOX_1
|
||||
#define CS40L2X_MBOX_TRIGGER_MS CS40L2X_DSP_VIRT1_MBOX_2
|
||||
#define CS40L2X_MBOX_STIMULUS_MODE CS40L2X_DSP_VIRT1_MBOX_3
|
||||
|
||||
#define CS40L2X_INDEX_MASK 0x7FFF
|
||||
#define CS40L2X_INDEX_VIBE 0x0000
|
||||
#define CS40L2X_INDEX_CLICK_MIN 0x0001
|
||||
#define CS40L2X_INDEX_CLICK_MAX 0x7FFF
|
||||
#define CS40L2X_INDEX_CONT_MIN 0x8000
|
||||
#define CS40L2X_INDEX_CONT_MAX 0xFFFD
|
||||
#define CS40L2X_INDEX_PBQ 0xFFFE
|
||||
#define CS40L2X_INDEX_DIAG 0xFFFF
|
||||
|
||||
#define CS40L2X_PBQ_SEG_LEN_MAX 10
|
||||
#define CS40L2X_PBQ_DEPTH_MAX 256
|
||||
#define CS40L2X_PBQ_SCALE_MAX 100
|
||||
#define CS40L2X_PBQ_DELAY_MAX 10000
|
||||
#define CS40L2X_PBQ_REPEAT_MAX 16
|
||||
#define CS40L2X_PBQ_POLL_NS 25000000
|
||||
#define CS40L2X_PBQ_STATE_IDLE 0x00
|
||||
#define CS40L2X_PBQ_STATE_PLAYING 0x01
|
||||
#define CS40L2X_PBQ_STATE_SILENT 0x02
|
||||
|
||||
#define CS40L2X_DIAG_STATE_INIT 0x00
|
||||
#define CS40L2X_DIAG_STATE_RUN 0x01
|
||||
#define CS40L2X_DIAG_STATE_DONE 0x02
|
||||
|
|
@ -665,6 +682,8 @@
|
|||
#define CS40L2X_TEST_KEY_RELOCK_CODE2 0x33
|
||||
|
||||
#define CS40L2X_DIG_SCALE_MAX 816 /* -102 dB */
|
||||
#define CS40L2X_DIG_SCALE_ZERO 0x800
|
||||
#define CS40L2X_DIG_SCALE_MASK 0x7FF
|
||||
|
||||
#define CS40L2X_MAX_WLEN 4096
|
||||
|
||||
|
|
@ -701,10 +720,23 @@ struct cs40l2x_coeff_desc {
|
|||
struct list_head list;
|
||||
};
|
||||
|
||||
struct cs40l2x_pbq_pair {
|
||||
unsigned int tag;
|
||||
unsigned int mag;
|
||||
};
|
||||
|
||||
struct cs40l2x_dig_scale_map {
|
||||
unsigned int encoded;
|
||||
unsigned int dig_scale;
|
||||
};
|
||||
|
||||
extern const unsigned char cs40l2x_bst_k1_table[4][5];
|
||||
extern const unsigned char cs40l2x_bst_k2_table[4][5];
|
||||
extern const unsigned char cs40l2x_bst_slope_table[4];
|
||||
|
||||
extern const struct cs40l2x_otp_desc cs40l2x_otp_map[CS40L2X_NUM_OTP_MAPS];
|
||||
|
||||
extern const struct cs40l2x_dig_scale_map
|
||||
cs40l2x_pbq_scale[CS40L2X_PBQ_SCALE_MAX + 1];
|
||||
|
||||
#endif /*__CS40L2X_H__*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue