lineage_kernel_xcoverpro/drivers/vibrator/common/sec_vibrator.c

1198 lines
28 KiB
C
Executable File

/*
* sec vibrator driver for common code
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define pr_fmt(fmt) "[VIB] " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/vibrator/sec_vibrator.h>
#if defined(CONFIG_SSP_MOTOR_CALLBACK)
#include <linux/ssp_motorcallback.h>
#endif
static struct sec_vibrator_drvdata *g_ddata;
static int sec_vibrator_set_frequency(struct sec_vibrator_drvdata *ddata,
int frequency)
{
int ret = 0;
if (!ddata->vib_ops->set_frequency)
return -ENOSYS;
if (frequency < FREQ_ALERT ||
(frequency >= FREQ_MAX && frequency < HAPTIC_ENGINE_FREQ_MIN) ||
frequency > HAPTIC_ENGINE_FREQ_MAX) {
pr_err("%s out of range(%d)\n", __func__, frequency);
return -EINVAL;
}
ret = ddata->vib_ops->set_frequency(ddata->dev, frequency);
if (ret)
pr_err("%s error(%d)\n", __func__, ret);
return ret;
}
static int sec_vibrator_set_intensity(struct sec_vibrator_drvdata *ddata,
int intensity)
{
int ret = 0;
if (!ddata->vib_ops->set_intensity)
return -ENOSYS;
if (intensity < -(MAX_INTENSITY) || MAX_INTENSITY < intensity) {
pr_err("%s out of range(%d)\n", __func__, intensity);
return -EINVAL;
}
ret = ddata->vib_ops->set_intensity(ddata->dev, intensity);
if (ret)
pr_err("%s error(%d)\n", __func__, ret);
return ret;
}
static int sec_vibrator_set_force_touch_intensity(
struct sec_vibrator_drvdata *ddata, int intensity)
{
int ret = 0;
if (!ddata->vib_ops->set_force_touch_intensity)
return -ENOSYS;
if (intensity < -(MAX_INTENSITY) || MAX_INTENSITY < intensity) {
pr_err("%s out of range(%d)\n", __func__, intensity);
return -EINVAL;
}
ret = ddata->vib_ops->set_force_touch_intensity(ddata->dev, intensity);
if (ret)
pr_err("%s error(%d)\n", __func__, ret);
return ret;
}
static int sec_vibrator_set_enable(struct sec_vibrator_drvdata *ddata, bool en)
{
int ret = 0;
if (!ddata->vib_ops->enable) {
pr_err("%s cannot supported\n", __func__);
return -ENOSYS;
}
ret = ddata->vib_ops->enable(ddata->dev, en);
if (ret)
pr_err("%s error(%d)\n", __func__, ret);
#if defined(CONFIG_SSP_MOTOR_CALLBACK)
if (en && ddata->intensity > 0)
setSensorCallback(true, ddata->timeout);
else
setSensorCallback(false, 0);
#endif
return ret;
}
static int sec_vibrator_set_overdrive(struct sec_vibrator_drvdata *ddata,
bool en)
{
int ret = 0;
if (!ddata->vib_ops->set_overdrive)
return -ENOSYS;
ret = ddata->vib_ops->set_overdrive(ddata->dev, en);
if (ret)
pr_err("%s error(%d)\n", __func__, ret);
return ret;
}
static void sec_vibrator_haptic_enable(struct sec_vibrator_drvdata *ddata)
{
sec_vibrator_set_overdrive(ddata, false);
sec_vibrator_set_frequency(ddata, ddata->frequency);
sec_vibrator_set_intensity(ddata, ddata->intensity);
sec_vibrator_set_enable(ddata, true);
if (ddata->vib_ops->set_frequency)
pr_info("freq:%d, intensity:%d, %dms\n", ddata->frequency,
ddata->intensity, ddata->timeout);
else if (ddata->vib_ops->set_intensity)
pr_info("intensity:%d, %dms\n", ddata->intensity,
ddata->timeout);
else
pr_info("%dms\n", ddata->timeout);
}
static void sec_vibrator_haptic_disable(struct sec_vibrator_drvdata *ddata)
{
/* clear haptic engine variables */
ddata->f_packet_en = false;
ddata->packet_cnt = 0;
ddata->packet_size = 0;
/* clear led trigger variables */
ddata->state = 0;
ddata->duration = 0;
sec_vibrator_set_enable(ddata, false);
sec_vibrator_set_overdrive(ddata, false);
sec_vibrator_set_frequency(ddata, FREQ_ALERT);
sec_vibrator_set_intensity(ddata, 0);
if (ddata->timeout > 0)
pr_info("timeout, off\n");
else
pr_info("off\n");
}
static void sec_vibrator_engine_run_packet(struct sec_vibrator_drvdata *ddata,
struct vib_packet packet)
{
int frequency = packet.freq;
int intensity = packet.intensity;
int overdrive = packet.overdrive;
if (!ddata->f_packet_en) {
pr_err("haptic packet is empty\n");
return;
}
sec_vibrator_set_overdrive(ddata, overdrive);
sec_vibrator_set_frequency(ddata, frequency);
if (intensity) {
sec_vibrator_set_intensity(ddata, intensity);
if (!ddata->packet_running) {
pr_info("[haptic engine] motor run\n");
sec_vibrator_set_enable(ddata, true);
}
ddata->packet_running = true;
} else {
if (ddata->packet_running) {
pr_info("[haptic engine] motor stop\n");
sec_vibrator_set_enable(ddata, false);
}
ddata->packet_running = false;
sec_vibrator_set_intensity(ddata, intensity);
}
pr_info("%s [%d] freq:%d, intensity:%d, time:%d overdrive: %d\n",
__func__, ddata->packet_cnt, frequency, intensity,
ddata->timeout, overdrive);
}
static void timed_output_enable(struct sec_vibrator_drvdata *ddata, unsigned int value)
{
struct hrtimer *timer = &ddata->timer;
int ret = 0;
kthread_flush_worker(&ddata->kworker);
ret = hrtimer_cancel(timer);
mutex_lock(&ddata->vib_mutex);
value = min_t(int, value, MAX_TIMEOUT);
ddata->timeout = value;
if (value) {
if (ddata->f_packet_en) {
ddata->packet_running = false;
ddata->timeout = ddata->vib_pac[0].time;
sec_vibrator_engine_run_packet(ddata, ddata->vib_pac[0]);
} else {
sec_vibrator_haptic_enable(ddata);
}
hrtimer_start(timer,
ns_to_ktime((u64)ddata->timeout * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
} else {
sec_vibrator_haptic_disable(ddata);
}
mutex_unlock(&ddata->vib_mutex);
}
static enum hrtimer_restart haptic_timer_func(struct hrtimer *timer)
{
struct sec_vibrator_drvdata *ddata
= container_of(timer, struct sec_vibrator_drvdata, timer);
pr_info("%s\n", __func__);
kthread_queue_work(&ddata->kworker, &ddata->kwork);
return HRTIMER_NORESTART;
}
static void sec_vibrator_work(struct kthread_work *work)
{
struct sec_vibrator_drvdata *ddata
= container_of(work, struct sec_vibrator_drvdata, kwork);
struct hrtimer *timer = &ddata->timer;
mutex_lock(&ddata->vib_mutex);
if (ddata->f_packet_en) {
ddata->packet_cnt++;
if (ddata->packet_cnt < ddata->packet_size) {
ddata->timeout = ddata->vib_pac[ddata->packet_cnt].time;
sec_vibrator_engine_run_packet(ddata,
ddata->vib_pac[ddata->packet_cnt]);
hrtimer_start(timer, ns_to_ktime((u64)ddata->timeout * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
goto unlock_without_vib_off;
} else {
ddata->f_packet_en = false;
ddata->packet_cnt = 0;
ddata->packet_size = 0;
}
}
sec_vibrator_haptic_disable(ddata);
unlock_without_vib_off:
mutex_unlock(&ddata->vib_mutex);
}
static ssize_t intensity_store(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int intensity = 0, ret = 0;
ret = kstrtoint(buf, 0, &intensity);
if (ret) {
pr_err("fail to get intensity\n");
return -EINVAL;
}
pr_info("%s %d\n", __func__, intensity);
if ((intensity < 0) || (intensity > MAX_INTENSITY)) {
pr_err("[VIB]: %s out of range\n", __func__);
return -EINVAL;
}
ddata->intensity = intensity;
return count;
}
static ssize_t intensity_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
return snprintf(buf, VIB_BUFSIZE,
"intensity: %u\n", ddata->intensity);
}
static ssize_t force_touch_intensity_store(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int intensity = 0, ret = 0;
ret = kstrtoint(buf, 0, &intensity);
if (ret) {
pr_err("fail to get intensity\n");
return -EINVAL;
}
pr_info("%s %d\n", __func__, intensity);
if ((intensity < 0) || (intensity > MAX_INTENSITY)) {
pr_err("[VIB]: %s out of range\n", __func__);
return -EINVAL;
}
ddata->force_touch_intensity = intensity;
return count;
}
static ssize_t force_touch_intensity_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
return snprintf(buf, VIB_BUFSIZE,
"force touch intensity: %u\n", ddata->force_touch_intensity);
}
static ssize_t multi_freq_store(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int num, ret;
ret = kstrtoint(buf, 0, &num);
if (ret) {
pr_err("fail to get frequency\n");
return -EINVAL;
}
pr_info("%s %d\n", __func__, num);
ddata->frequency = num;
return count;
}
static ssize_t multi_freq_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
return snprintf(buf, VIB_BUFSIZE,
"frequency: %d\n", ddata->frequency);
}
// TODO: need to update
static ssize_t haptic_engine_store(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int i = 0, _data = 0, tmp = 0;
if (sscanf(buf, "%6d", &_data) != 1)
return count;
if (_data > PACKET_MAX_SIZE * VIB_PACKET_MAX)
pr_info("%s, [%d] packet size over\n", __func__, _data);
else {
ddata->packet_size = _data / VIB_PACKET_MAX;
ddata->packet_cnt = 0;
ddata->f_packet_en = true;
buf = strstr(buf, " ");
for (i = 0; i < ddata->packet_size; i++) {
for (tmp = 0; tmp < VIB_PACKET_MAX; tmp++) {
if (buf == NULL) {
pr_err("%s, buf is NULL, Please check packet data again\n",
__func__);
ddata->f_packet_en = false;
return count;
}
if (sscanf(buf++, "%6d", &_data) != 1) {
pr_err("%s, packet data error, Please check packet data again\n",
__func__);
ddata->f_packet_en = false;
return count;
}
switch (tmp) {
case VIB_PACKET_TIME:
ddata->vib_pac[i].time = _data;
break;
case VIB_PACKET_INTENSITY:
ddata->vib_pac[i].intensity = _data;
break;
case VIB_PACKET_FREQUENCY:
ddata->vib_pac[i].freq = _data;
break;
case VIB_PACKET_OVERDRIVE:
ddata->vib_pac[i].overdrive = _data;
break;
}
buf = strstr(buf, " ");
}
}
}
return count;
}
static ssize_t haptic_engine_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int i = 0;
size_t size = 0;
for (i = 0; i < ddata->packet_size && ddata->f_packet_en &&
((4 * VIB_BUFSIZE + size) < PAGE_SIZE); i++) {
size += snprintf(&buf[size], VIB_BUFSIZE, "%u,", ddata->vib_pac[i].time);
size += snprintf(&buf[size], VIB_BUFSIZE, "%u,", ddata->vib_pac[i].intensity);
size += snprintf(&buf[size], VIB_BUFSIZE, "%u,", ddata->vib_pac[i].freq);
size += snprintf(&buf[size], VIB_BUFSIZE, "%u,", ddata->vib_pac[i].overdrive);
}
return size;
}
#if 0
static ssize_t cs40l2x_cp_trigger_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int index;
mutex_lock(&cs40l2x->lock);
index = cs40l2x->cp_trigger_index;
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", index);
}
static ssize_t cs40l2x_cp_trigger_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
#ifdef CONFIG_CS40L2X_SAMSUNG_FEATURE
pr_info("%s index:%u (num_waves:%u)\n", __func__, index, cs40l2x->num_waves);
#endif
mutex_lock(&cs40l2x->lock);
switch (index) {
case CS40L2X_INDEX_QEST:
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
ret = -EPERM;
break;
}
/* intentionally fall through */
case CS40L2X_INDEX_DIAG:
if (cs40l2x->fw_desc->id == cs40l2x->fw_id_remap)
ret = cs40l2x_firmware_swap(cs40l2x,
CS40L2X_FW_ID_CAL);
break;
case CS40L2X_INDEX_PEAK:
case CS40L2X_INDEX_PBQ:
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL)
ret = cs40l2x_firmware_swap(cs40l2x,
cs40l2x->fw_id_remap);
break;
case CS40L2X_INDEX_IDLE:
ret = -EINVAL;
break;
default:
if ((index & CS40L2X_INDEX_MASK) >= cs40l2x->num_waves) {
ret = -EINVAL;
break;
}
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL)
ret = cs40l2x_firmware_swap(cs40l2x,
cs40l2x->fw_id_remap);
}
if (ret)
goto err_mutex;
cs40l2x->cp_trigger_index = index;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
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;
int i;
if (cs40l2x->pbq_depth == 0)
return -ENODATA;
mutex_lock(&cs40l2x->lock);
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 (i < (cs40l2x->pbq_depth - 1))
len += snprintf(buf + len, PAGE_SIZE - len, ", ");
}
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;
size_t pbq_seg_len;
unsigned int pbq_depth = 0;
unsigned int val, num_empty;
int pbq_marker = -1;
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;
strlcpy(pbq_str, buf, count);
#if DEBUG_VIB
pr_info("%s pbq_str=%s\n", __func__, pbq_str);
#endif
pbq_str_tok = strsep(&pbq_str, ",");
while (pbq_str_tok) {
pbq_seg = pbq_seg_alloc;
pbq_seg_len = strlcpy(pbq_seg, strim(pbq_str_tok),
CS40L2X_PBQ_SEG_LEN_MAX + 1);
if (pbq_seg_len > CS40L2X_PBQ_SEG_LEN_MAX) {
ret = -E2BIG;
goto err_mutex;
}
/* waveform specifier */
if (strnchr(pbq_seg, CS40L2X_PBQ_SEG_LEN_MAX, '.')) {
/* 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 (strnchr(pbq_seg, CS40L2X_PBQ_SEG_LEN_MAX, '!')) {
val = 0;
num_empty = 0;
pbq_seg_tok = strsep(&pbq_seg, "!");
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;
}
/* loop specifier */
} else if (strnchr(pbq_seg, CS40L2X_PBQ_SEG_LEN_MAX, '~')) {
if (cs40l2x->pbq_repeat) {
ret = -EINVAL;
goto err_mutex;
}
cs40l2x->pbq_repeat = -1;
/* duration specifier */
} else {
cs40l2x->pbq_pairs[pbq_depth].tag =
CS40L2X_PBQ_TAG_SILENCE;
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, ",");
}
#ifdef CONFIG_CS40L2X_SAMSUNG_FEATURE
if(count > DEBUG_PRINT_PLAYBACK_QUEUE) {
strlcpy(pbq_str_alloc, buf, DEBUG_PRINT_PLAYBACK_QUEUE);
pr_info("%s pbq_str=%s... (depth:%d)\n", __func__, pbq_str_alloc, pbq_depth);
} else {
strlcpy(pbq_str_alloc, buf, count);
pr_info("%s pbq_str=%s (depth:%d)\n", __func__, pbq_str_alloc, pbq_depth);
}
#endif
cs40l2x->pbq_depth = pbq_depth;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
kfree(pbq_str_alloc);
kfree(pbq_seg_alloc);
return ret;
}
#endif
static ssize_t enable_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
struct hrtimer *timer = &ddata->timer;
int remaining = 0;
if (hrtimer_active(timer)) {
ktime_t remain = hrtimer_get_remaining(timer);
struct timeval t = ktime_to_timeval(remain);
remaining = t.tv_sec * 1000 + t.tv_usec / 1000;
}
return sprintf(buf, "%d\n", remaining);
}
static ssize_t enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int value;
int ret;
ret = kstrtoint(buf, 0, &value);
if (ret != 0)
return -EINVAL;
timed_output_enable(ddata, value);
return size;
}
static ssize_t motor_type_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int ret = 0;
if (!ddata->vib_ops->get_motor_type)
return snprintf(buf, VIB_BUFSIZE, "NONE\n");
ret = ddata->vib_ops->get_motor_type(ddata->dev, buf);
return ret;
}
static ssize_t motor_type_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
/* do nothing */
return 0;
}
static DEVICE_ATTR_RW(haptic_engine);
static DEVICE_ATTR_RW(multi_freq);
static DEVICE_ATTR_RW(intensity);
static DEVICE_ATTR_RW(force_touch_intensity);
#if 0
static DEVICE_ATTR_RW(cs40l2x_cp_trigger_index);
static DEVICE_ATTR_RW(cs40l2x_cp_trigger_queue);
#endif
static DEVICE_ATTR_RW(enable);
static DEVICE_ATTR_RW(motor_type);
static struct attribute *sec_vibrator_attributes[] = {
&dev_attr_enable.attr,
&dev_attr_motor_type.attr,
NULL,
};
static struct attribute_group sec_vibrator_attr_group = {
.attrs = sec_vibrator_attributes,
};
static struct attribute *multi_freq_attributes[] = {
&dev_attr_haptic_engine.attr,
&dev_attr_multi_freq.attr,
NULL,
};
static struct attribute_group multi_freq_attr_group = {
.attrs = multi_freq_attributes,
};
#if 0
static struct attribute *cp_trigger_attributes[] = {
&dev_attr_cs40l2x_cp_trigger_index,
&dev_attr_cs40l2x_cp_trigger_queue,
NULL,
};
static struct attribute_group cp_trigger_attr_group = {
.attrs = cp_trigger_attributes,
};
#endif
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
return sprintf(buf, "%d\n", ddata->state);
}
static ssize_t state_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int value;
int ret;
ret = kstrtoint(buf, 0, &value);
if (ret != 0)
return -EINVAL;
ddata->state = value;
return size;
}
static ssize_t duration_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
return sprintf(buf, "%d\n", ddata->duration);
}
static ssize_t duration_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int value;
int ret;
ret = kstrtoint(buf, 0, &value);
if (ret != 0)
return -EINVAL;
ddata->duration = value;
return size;
}
static ssize_t activate_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
return sprintf(buf, "%d\n", ddata->state);
}
static ssize_t activate_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
int value;
int ret;
ret = kstrtoint(buf, 0, &value);
if (ret != 0)
return -EINVAL;
if (value > 0 && ddata->state > 0)
timed_output_enable(ddata, ddata->duration);
else
timed_output_enable(ddata, 0);
return size;
}
static DEVICE_ATTR_RW(state);
static DEVICE_ATTR_RW(duration);
static DEVICE_ATTR_RW(activate);
static struct attribute *led_vibrator_attributes[] = {
&dev_attr_state.attr,
&dev_attr_duration.attr,
&dev_attr_activate.attr,
&dev_attr_motor_type.attr,
NULL,
};
static struct attribute_group led_vibrator_attr_group = {
.attrs = led_vibrator_attributes,
};
int sec_vibrator_register(struct sec_vibrator_drvdata *ddata)
{
struct task_struct *kworker_task;
int ret = 0;
if (!ddata) {
pr_err("%s no ddata\n", __func__);
return -ENODEV;
}
g_ddata = ddata;
mutex_init(&ddata->vib_mutex);
kthread_init_worker(&ddata->kworker);
kworker_task = kthread_run(kthread_worker_fn,
&ddata->kworker, "sec_vibrator");
if (IS_ERR(kworker_task)) {
pr_err("Failed to create message pump task\n");
ret = -ENOMEM;
goto err_kthread;
}
kthread_init_work(&ddata->kwork, sec_vibrator_work);
hrtimer_init(&ddata->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ddata->timer.function = haptic_timer_func;
/* create /sys/class/leds/vibrator */
ddata->cdev.name = "vibrator";
ret = devm_led_classdev_register(ddata->dev, &ddata->cdev);
if (ret < 0) {
pr_err("led class register fail\n");
goto err_led_class;
}
ret = sysfs_create_group(&ddata->cdev.dev->kobj,
&led_vibrator_attr_group);
if (ret) {
ret = -ENODEV;
pr_err("Failed to create led sysfs1 %d\n", ret);
goto err_led_sysfs;
}
/* create /sys/class/timed_output/vibrator */
ddata->to_class = class_create(THIS_MODULE, "timed_output");
if (IS_ERR(ddata->to_class)) {
ret = PTR_ERR(ddata->to_class);
goto err_class_create;
}
ddata->to_dev = device_create(ddata->to_class, NULL,
MKDEV(0, 0), ddata, "vibrator");
if (IS_ERR(ddata->to_dev)) {
ret = PTR_ERR(ddata->to_dev);
goto err_device_create;
}
ret = sysfs_create_group(&ddata->to_dev->kobj,
&sec_vibrator_attr_group);
if (ret) {
ret = -ENODEV;
pr_err("Failed to create sysfs1 %d\n", ret);
goto err_sysfs1;
}
if (ddata->vib_ops->set_intensity) {
ret = sysfs_create_file(&ddata->to_dev->kobj,
&dev_attr_intensity.attr);
if (ret) {
ret = -ENODEV;
pr_err("Failed to create sysfs2 %d\n", ret);
goto err_sysfs2;
}
ret = sysfs_create_file(&ddata->cdev.dev->kobj,
&dev_attr_intensity.attr);
if (ret) {
ret = -ENODEV;
pr_err("Failed to create led sysfs2 %d\n", ret);
goto err_sysfs2;
}
ddata->intensity = MAX_INTENSITY;
}
if (ddata->vib_ops->set_force_touch_intensity) {
ret = sysfs_create_file(&ddata->to_dev->kobj,
&dev_attr_force_touch_intensity.attr);
if (ret) {
ret = -ENODEV;
pr_err("Failed to create sysfs3 %d\n", ret);
goto err_sysfs3;
}
ret = sysfs_create_file(&ddata->cdev.dev->kobj,
&dev_attr_force_touch_intensity.attr);
if (ret) {
ret = -ENODEV;
pr_err("Failed to create led sysfs3 %d\n", ret);
goto err_sysfs3;
}
ddata->force_touch_intensity = MAX_INTENSITY;
}
if (ddata->vib_ops->set_frequency) {
ret = sysfs_create_group(&ddata->to_dev->kobj,
&multi_freq_attr_group);
if (ret) {
ret = -ENODEV;
pr_err("Failed to create sysfs4 %d\n", ret);
goto err_sysfs4;
}
ret = sysfs_create_group(&ddata->cdev.dev->kobj,
&multi_freq_attr_group);
if (ret) {
ret = -ENODEV;
pr_err("Failed to create sysfs4 %d\n", ret);
goto err_sysfs4;
}
ddata->frequency = FREQ_ALERT;
}
pr_info("%s done\n", __func__);
return ret;
err_sysfs4:
if (ddata->vib_ops->set_force_touch_intensity) {
sysfs_remove_file(&ddata->to_dev->kobj,
&dev_attr_force_touch_intensity.attr);
sysfs_remove_file(&ddata->cdev.dev->kobj,
&dev_attr_force_touch_intensity.attr);
}
err_sysfs3:
if (ddata->vib_ops->set_intensity) {
sysfs_remove_file(&ddata->to_dev->kobj,
&dev_attr_intensity.attr);
sysfs_remove_file(&ddata->cdev.dev->kobj,
&dev_attr_intensity.attr);
}
err_sysfs2:
sysfs_remove_group(&ddata->to_dev->kobj, &sec_vibrator_attr_group);
err_sysfs1:
device_destroy(ddata->to_class, MKDEV(0, 0));
err_device_create:
class_destroy(ddata->to_class);
err_class_create:
sysfs_remove_group(&ddata->cdev.dev->kobj, &led_vibrator_attr_group);
err_led_sysfs:
devm_led_classdev_unregister(ddata->dev, &ddata->cdev);
err_led_class:
err_kthread:
mutex_destroy(&ddata->vib_mutex);
return ret;
}
int sec_vibrator_unregister(struct sec_vibrator_drvdata *ddata)
{
sec_vibrator_haptic_disable(ddata);
g_ddata = NULL;
if (ddata->vib_ops->set_frequency) {
sysfs_remove_group(&ddata->to_dev->kobj,
&multi_freq_attr_group);
sysfs_remove_group(&ddata->cdev.dev->kobj,
&multi_freq_attr_group);
}
if (ddata->vib_ops->set_force_touch_intensity) {
sysfs_remove_file(&ddata->to_dev->kobj,
&dev_attr_force_touch_intensity.attr);
sysfs_remove_file(&ddata->cdev.dev->kobj,
&dev_attr_force_touch_intensity.attr);
}
if (ddata->vib_ops->set_intensity) {
sysfs_remove_file(&ddata->to_dev->kobj,
&dev_attr_intensity.attr);
sysfs_remove_file(&ddata->cdev.dev->kobj,
&dev_attr_intensity.attr);
}
sysfs_remove_group(&ddata->to_dev->kobj, &sec_vibrator_attr_group);
device_destroy(ddata->to_class, MKDEV(0, 0));
class_destroy(ddata->to_class);
sysfs_remove_group(&ddata->cdev.dev->kobj, &led_vibrator_attr_group);
devm_led_classdev_unregister(ddata->dev, &ddata->cdev);
mutex_destroy(&ddata->vib_mutex);
return 0;
}
extern int haptic_homekey_press(void)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
struct hrtimer *timer;
if (ddata == NULL)
return -ENODEV;
if (!ddata->vib_ops->set_force_touch_intensity)
return -ENOSYS;
timer = &ddata->timer;
mutex_lock(&ddata->vib_mutex);
ddata->timeout = HOMEKEY_DURATION;
sec_vibrator_set_overdrive(ddata, true);
sec_vibrator_set_frequency(ddata, FREQ_PRESS);
sec_vibrator_set_force_touch_intensity(ddata,
ddata->force_touch_intensity);
sec_vibrator_set_enable(ddata, true);
pr_info("%s freq:%d, intensity:%d, time:%d\n", __func__, FREQ_PRESS,
ddata->force_touch_intensity, ddata->timeout);
mutex_unlock(&ddata->vib_mutex);
hrtimer_start(timer,
ns_to_ktime((u64)ddata->timeout * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
return 0;
}
extern int haptic_homekey_release(void)
{
struct sec_vibrator_drvdata *ddata = g_ddata;
struct hrtimer *timer;
if (ddata == NULL)
return -ENODEV;
if (!ddata->vib_ops->set_force_touch_intensity)
return -ENOSYS;
timer = &ddata->timer;
mutex_lock(&ddata->vib_mutex);
ddata->timeout = HOMEKEY_DURATION;
sec_vibrator_set_overdrive(ddata, true);
sec_vibrator_set_frequency(ddata, FREQ_RELEASE);
sec_vibrator_set_force_touch_intensity(ddata,
ddata->force_touch_intensity);
sec_vibrator_set_enable(ddata, true);
pr_info("%s freq:%d, intensity:%d, time:%d\n", __func__, FREQ_RELEASE,
ddata->force_touch_intensity, ddata->timeout);
mutex_unlock(&ddata->vib_mutex);
hrtimer_start(timer,
ns_to_ktime((u64)ddata->timeout * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
return 0;
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("sec vibrator driver");