diff --git a/drivers/input/misc/qcom-hv-haptics.c b/drivers/input/misc/qcom-hv-haptics.c index 5b8d9d57c306..f2de3443779d 100644 --- a/drivers/input/misc/qcom-hv-haptics.c +++ b/drivers/input/misc/qcom-hv-haptics.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -158,6 +159,7 @@ #define CHAR_PER_PATTERN_S 48 #define CHAR_PER_SAMPLE 8 #define CHAR_MSG_HEADER 16 +#define CHAR_BRAKE_MODE 24 #define HW_BRAKE_CYCLES 5 enum drv_sig_shape { @@ -323,6 +325,7 @@ struct haptics_chip { struct haptics_effect *effects; struct haptics_play_info play; struct work_struct fifo_work; + struct dentry *debugfs_dir; int fifo_empty_irq; u32 effects_count; u32 cfg_addr_base; @@ -1214,6 +1217,616 @@ static irqreturn_t fifo_empty_irq_handler(int irq, void *data) return IRQ_HANDLED; } +#ifdef CONFIG_DEBUG_FS +static int vmax_dbgfs_read(void *data, u64 *val) +{ + struct haptics_effect *effect = data; + + *val = effect->vmax_mv; + + return 0; +} + +static int vmax_dbgfs_write(void *data, u64 val) +{ + struct haptics_effect *effect = data; + + if (val > MAX_VMAX_MV) + val = MAX_VMAX_MV; + + effect->vmax_mv = (u32) val; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(vmax_debugfs_ops, vmax_dbgfs_read, + vmax_dbgfs_write, "%llu\n"); + +static int auto_res_en_dbgfs_read(void *data, u64 *val) +{ + struct haptics_effect *effect = data; + + *val = !effect->auto_res_disable; + + return 0; +} + +static int auto_res_en_dbgfs_write(void *data, u64 val) +{ + struct haptics_effect *effect = data; + + effect->auto_res_disable = !val; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(auto_res_en_debugfs_ops, auto_res_en_dbgfs_read, + auto_res_en_dbgfs_write, "%llu\n"); + +static ssize_t pattern_s_dbgfs_read(struct file *fp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct haptics_effect *effect = fp->private_data; + u32 pos = 0, size = CHAR_PER_PATTERN_S * SAMPLES_PER_PATTERN; + char *str; + int i = 0, rc; + + if (!effect->pattern) + return 0; + + str = kzalloc(size, GFP_KERNEL); + if (!str) + return -ENOMEM; + + for (i = 0; i < SAMPLES_PER_PATTERN; i++) { + pos += scnprintf(str + pos, size - pos, "0x%03x ", + effect->pattern->samples[i].amplitude); + pos += scnprintf(str + pos, size - pos, "%s(0x%02x) ", + period_str[effect->pattern->samples[i].period], + effect->pattern->samples[i].period); + pos += scnprintf(str + pos, size - pos, "F_LRA_X2(%1d)\n", + effect->pattern->samples[i].f_lra_x2); + } + + rc = simple_read_from_buffer(buf, count, ppos, str, pos); + kfree(str); + + return rc; +} + +static ssize_t pattern_s_dbgfs_write(struct file *fp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct haptics_effect *effect = fp->private_data; + struct pattern_s patterns[SAMPLES_PER_PATTERN] = {{0, 0, 0},}; + char *str, *token; + u32 val, tmp[3 * SAMPLES_PER_PATTERN] = {0}; + int rc, i = 0, j = 0; + + if (count > CHAR_PER_PATTERN_S * SAMPLES_PER_PATTERN) + return -EINVAL; + + str = kzalloc(CHAR_PER_PATTERN_S * SAMPLES_PER_PATTERN + 1, GFP_KERNEL); + if (!str) + return -ENOMEM; + + rc = copy_from_user(str, buf, count); + if (rc > 0) { + rc = -EFAULT; + goto exit; + } + + str[count] = '\0'; + *ppos += count; + + while ((token = strsep((char **)&str, " ")) != NULL) { + rc = kstrtouint(token, 0, &val); + if (rc < 0) { + rc = -EINVAL; + goto exit; + } + + tmp[i++] = val; + } + + if (i % 3) + pr_warn("Tuple should be having 3 elements, discarding tuple %d\n", + i / 3); + + for (j = 0; j < i / 3; j++) { + if (tmp[3 * j] > 0x1ff || tmp[3 * j + 1] > T_LRA_X_8 || + tmp[3 * j + 2] > 1) { + pr_err("allowed tuples: [amplitude(<= 0x1ff) period(<=6(T_LRA_X_8)) f_lra_x2(0,1)]\n"); + rc = -EINVAL; + goto exit; + } + + patterns[j].amplitude = (u16)tmp[3 * j]; + patterns[j].period = (enum s_period)tmp[3 * j + 1]; + patterns[j].f_lra_x2 = !!tmp[3 * j + 2]; + } + + memcpy(effect->pattern->samples, patterns, + sizeof(effect->pattern->samples)); + + /* recalculate the play length */ + effect->pattern->play_length_us = + get_pattern_play_length_us(effect->pattern); + if (effect->pattern->play_length_us == -EINVAL) { + pr_err("get pattern play length failed\n"); + rc = -EINVAL; + goto exit; + } + + rc = count; +exit: + kfree(str); + return rc; +} + +static const struct file_operations pattern_s_dbgfs_ops = { + .read = pattern_s_dbgfs_read, + .write = pattern_s_dbgfs_write, + .open = simple_open, +}; + +static int pattern_play_rate_us_dbgfs_read(void *data, u64 *val) +{ + struct haptics_effect *effect = data; + + *val = effect->pattern->play_rate_us; + + return 0; +} + +static int pattern_play_rate_us_dbgfs_write(void *data, u64 val) +{ + struct haptics_effect *effect = data; + + if (val > TLRA_MAX_US) + val = TLRA_MAX_US; + + effect->pattern->play_rate_us = (u32)val; + /* recalculate the play length */ + effect->pattern->play_length_us = + get_pattern_play_length_us(effect->pattern); + if (effect->pattern->play_length_us == -EINVAL) { + pr_err("get pattern play length failed\n"); + return -EINVAL; + } + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pattern_play_rate_dbgfs_ops, + pattern_play_rate_us_dbgfs_read, + pattern_play_rate_us_dbgfs_write, "%lld\n"); + +static ssize_t fifo_s_dbgfs_read(struct file *fp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct haptics_effect *effect = fp->private_data; + struct fifo_cfg *fifo = effect->fifo; + char *kbuf; + int rc, i; + u32 size, pos = 0; + + size = CHAR_PER_SAMPLE * fifo->num_s + 1; + kbuf = kzalloc(size, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + for (i = 0; i < fifo->num_s; i++) + pos += scnprintf(kbuf + pos, size - pos, + "0x%03x ", fifo->samples[i]); + + pos += scnprintf(kbuf + pos, size - pos, "%s", "\n"); + rc = simple_read_from_buffer(buf, count, ppos, kbuf, pos); + kfree(kbuf); + + return rc; +} + +static ssize_t fifo_s_dbgfs_write(struct file *fp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct haptics_effect *effect = fp->private_data; + struct fifo_cfg *fifo = effect->fifo; + char *kbuf, *token; + int rc, i = 0; + u32 val; + u16 *samples; + + kbuf = kzalloc(count + 1, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + rc = copy_from_user(kbuf, buf, count); + if (rc > 0) { + rc = -EFAULT; + goto exit; + } + + kbuf[count] = '\0'; + *ppos += count; + + samples = kcalloc(fifo->num_s, sizeof(u16), GFP_KERNEL); + if (!samples) { + rc = -ENOMEM; + goto exit; + } + + while ((token = strsep(&kbuf, " ")) != NULL) { + rc = kstrtouint(token, 0, &val); + if (rc < 0) { + rc = -EINVAL; + goto exit2; + } + + if (val > 0x1ff) + val = 0x1ff; + + samples[i++] = (u16)val; + /* only support fifo pattern no longer than before */ + if (i >= fifo->num_s) + break; + } + + memcpy(fifo->samples, samples, sizeof(*fifo->samples) * fifo->num_s); + fifo->play_length_us = get_fifo_play_length_us(fifo, effect->t_lra_us); + if (fifo->play_length_us == -EINVAL) { + pr_err("get fifo play length failed\n"); + rc = -EINVAL; + goto exit2; + } + + rc = count; +exit2: + kfree(samples); +exit: + kfree(kbuf); + return rc; +} + +static const struct file_operations fifo_s_dbgfs_ops = { + .read = fifo_s_dbgfs_read, + .write = fifo_s_dbgfs_write, + .owner = THIS_MODULE, + .open = simple_open, +}; + +static int fifo_period_dbgfs_read(void *data, u64 *val) +{ + struct haptics_effect *effect = data; + + *val = effect->fifo->period_per_s; + + return 0; +} + +static int fifo_period_dbgfs_write(void *data, u64 val) +{ + struct haptics_effect *effect = data; + struct fifo_cfg *fifo = effect->fifo; + + if (val > F_48KHZ) + return -EINVAL; + + fifo->period_per_s = (enum s_period)val; + fifo->play_length_us = get_fifo_play_length_us(fifo, effect->t_lra_us); + if (fifo->play_length_us == -EINVAL) { + pr_err("get fifo play length failed\n"); + return -EINVAL; + } + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(fifo_period_dbgfs_ops, + fifo_period_dbgfs_read, + fifo_period_dbgfs_write, "%lld\n"); + +static ssize_t brake_s_dbgfs_read(struct file *fp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct haptics_effect *effect = fp->private_data; + struct brake_cfg *brake = effect->brake; + char *str; + int rc, i; + u32 size, pos = 0; + + size = CHAR_PER_SAMPLE * BRAKE_SAMPLE_COUNT + 1; + str = kzalloc(size, GFP_KERNEL); + if (!str) + return -ENOMEM; + + for (i = 0; i < BRAKE_SAMPLE_COUNT; i++) + pos += scnprintf(str + pos, size - pos, "0x%02x ", + brake->samples[i]); + + pos += scnprintf(str + pos, size - pos, "%s", "\n"); + rc = simple_read_from_buffer(buf, count, ppos, str, pos); + kfree(str); + + return rc; +} + +static ssize_t brake_s_dbgfs_write(struct file *fp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct haptics_effect *effect = fp->private_data; + struct brake_cfg *brake = effect->brake; + char *str, *token; + int rc, i = 0; + u32 val; + u8 samples[BRAKE_SAMPLE_COUNT] = {0}; + + if (count > CHAR_PER_SAMPLE * BRAKE_SAMPLE_COUNT) + return -EINVAL; + + str = kzalloc(CHAR_PER_SAMPLE * BRAKE_SAMPLE_COUNT + 1, GFP_KERNEL); + if (!str) + return -ENOMEM; + + rc = copy_from_user(str, buf, count); + if (rc > 0) { + rc = -EFAULT; + goto exit; + } + + str[count] = '\0'; + *ppos += count; + + while ((token = strsep((char **)&str, " ")) != NULL) { + rc = kstrtouint(token, 0, &val); + if (rc < 0) { + rc = -EINVAL; + goto exit; + } + + if (val > 0xff) + val = 0xff; + + samples[i++] = (u8)val; + if (i >= BRAKE_SAMPLE_COUNT) + break; + } + + memcpy(brake->samples, samples, BRAKE_SAMPLE_COUNT); + verify_brake_samples(brake); + brake->play_length_us = + get_brake_play_length_us(brake, effect->t_lra_us); + + rc = count; +exit: + kfree(str); + return rc; +} + +static const struct file_operations brake_s_dbgfs_ops = { + .read = brake_s_dbgfs_read, + .write = brake_s_dbgfs_write, + .open = simple_open, +}; + +static ssize_t brake_mode_dbgfs_read(struct file *fp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct haptics_effect *effect = fp->private_data; + struct brake_cfg *brake = effect->brake; + char str[CHAR_BRAKE_MODE] = {0}; + u32 size; + int rc; + + size = scnprintf(str, ARRAY_SIZE(str), "%s\n", brake_str[brake->mode]); + rc = simple_read_from_buffer(buf, count, ppos, str, size); + + return rc; +} + +static ssize_t brake_mode_dbgfs_write(struct file *fp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct haptics_effect *effect = fp->private_data; + struct brake_cfg *brake = effect->brake; + char *kbuf; + int rc; + + kbuf = kzalloc(count + 1, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + rc = copy_from_user(kbuf, buf, count); + if (rc > 0) { + rc = -EFAULT; + goto exit; + } + + kbuf[count] = '\0'; + *ppos += count; + rc = count; + if (strcmp(kbuf, "open-loop") == 0) { + brake->mode = OL_BRAKE; + } else if (strcmp(kbuf, "close-loop") == 0) { + brake->mode = CL_BRAKE; + } else if (strcmp(kbuf, "predictive") == 0) { + brake->mode = PREDICT_BRAKE; + } else if (strcmp(kbuf, "auto") == 0) { + brake->mode = AUTO_BRAKE; + } else { + pr_err("%s brake mode is not supported\n", kbuf); + rc = -EINVAL; + } + +exit: + kfree(kbuf); + return rc; +} + +static const struct file_operations brake_mode_dbgfs_ops = { + .read = brake_mode_dbgfs_read, + .write = brake_mode_dbgfs_write, + .open = simple_open, +}; + +static int preload_effect_idx_dbgfs_read(void *data, u64 *val) +{ + struct haptics_chip *chip = data; + + *val = chip->config.preload_effect; + + return 0; +} + +static int preload_effect_idx_dbgfs_write(void *data, u64 val) +{ + struct haptics_chip *chip = data; + struct haptics_effect *new, *old; + int rc, i; + + for (i = 0; i < chip->effects_count; i++) + if (chip->effects[i].id == val) + break; + + if (i == chip->effects_count) + return -EINVAL; + + new = &chip->effects[i]; + + for (i = 0; i < chip->effects_count; i++) + if (chip->effects[i].id == chip->config.preload_effect) + break; + + old = &chip->effects[i]; + + chip->config.preload_effect = (u32)val; + + new->pattern->preload = true; + new->src = PATTERN2; + rc = haptics_set_pattern(chip, new->pattern, new->src); + if (rc < 0) + return rc; + + old->src = PATTERN1; + old->pattern->preload = false; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(preload_effect_idx_dbgfs_ops, + preload_effect_idx_dbgfs_read, + preload_effect_idx_dbgfs_write, "%lld\n"); + +static int haptics_add_effects_debugfs(struct haptics_effect *effect, + struct dentry *dir) +{ + struct dentry *file, *pattern_dir, *fifo_dir, *brake_dir; + + file = debugfs_create_file_unsafe("vmax_mv", 0644, dir, + effect, &vmax_debugfs_ops); + if (!file) + return -ENOMEM; + + file = debugfs_create_file_unsafe("lra_auto_res_en", 0644, dir, + effect, &auto_res_en_debugfs_ops); + if (!file) + return -ENOMEM; + + /* effect can have either pattern or FIFO */ + if (effect->pattern) { + pattern_dir = debugfs_create_dir("pattern", dir); + if (!pattern_dir) + return -ENOMEM; + + file = debugfs_create_file("samples", 0644, pattern_dir, + effect, &pattern_s_dbgfs_ops); + if (!file) + return -ENOMEM; + + file = debugfs_create_file_unsafe("play_rate_us", 0644, + pattern_dir, effect, + &pattern_play_rate_dbgfs_ops); + if (!file) + return -ENOMEM; + } else if (effect->fifo) { + fifo_dir = debugfs_create_dir("fifo", dir); + if (!fifo_dir) + return -ENOMEM; + + file = debugfs_create_file("samples", 0644, fifo_dir, + effect, &fifo_s_dbgfs_ops); + if (!file) + return -ENOMEM; + + file = debugfs_create_file_unsafe("period", 0644, fifo_dir, + effect, &fifo_period_dbgfs_ops); + if (!file) + return -ENOMEM; + } + + if (effect->brake) { + brake_dir = debugfs_create_dir("brake", dir); + if (!brake_dir) + return -ENOMEM; + + file = debugfs_create_file("samples", 0644, brake_dir, + effect, &brake_s_dbgfs_ops); + if (!file) + return -ENOMEM; + + file = debugfs_create_file("mode", 0644, brake_dir, + effect, &brake_mode_dbgfs_ops); + if (!file) + return -ENOMEM; + } + + return 0; +} + +#define EFFECT_NAME_SIZE 12 +static int haptics_create_debugfs(struct haptics_chip *chip) +{ + struct dentry *hap_dir, *effect_dir, *file; + char str[EFFECT_NAME_SIZE] = {0}; + int rc, i; + + hap_dir = debugfs_create_dir("haptics", NULL); + if (!hap_dir) { + dev_err(chip->dev, "create haptics debugfs directory failed\n"); + return -ENOMEM; + } + + for (i = 0; i < chip->effects_count; i++) { + scnprintf(str, ARRAY_SIZE(str), "effect%d", + chip->effects[i].id); + effect_dir = debugfs_create_dir(str, hap_dir); + if (!effect_dir) { + dev_err(chip->dev, "create %s debugfs directory failed\n", + str); + rc = -ENOMEM; + goto exit; + } + + rc = haptics_add_effects_debugfs(&chip->effects[i], effect_dir); + if (rc < 0) { + rc = -ENOMEM; + goto exit; + } + } + + file = debugfs_create_file_unsafe("preload_effect_idx", 0644, hap_dir, + chip, &preload_effect_idx_dbgfs_ops); + if (!file) { + rc = -ENOMEM; + goto exit; + } + + chip->debugfs_dir = hap_dir; + return 0; + +exit: + debugfs_remove_recursive(hap_dir); + return rc; +} +#endif + static int haptics_parse_per_effect_dt(struct haptics_chip *chip, struct device_node *node, struct haptics_effect *effect) { @@ -1710,6 +2323,11 @@ static int haptics_probe(struct platform_device *pdev) } dev_set_drvdata(chip->dev, chip); +#ifdef CONFIG_DEBUG_FS + rc = haptics_create_debugfs(chip); + if (rc < 0) + dev_err(chip->dev, "Creating debugfs failed, rc=%d\n", rc); +#endif return 0; destroy_ff: input_ff_destroy(chip->input_dev); @@ -1720,6 +2338,9 @@ static int haptics_remove(struct platform_device *pdev) { struct haptics_chip *chip = dev_get_drvdata(&pdev->dev); +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(chip->debugfs_dir); +#endif input_ff_destroy(chip->input_dev); dev_set_drvdata(chip->dev, NULL);