From 5ef7f0d84fe94a792e89df473d2b9c34b027c11e Mon Sep 17 00:00:00 2001 From: Fenglin Wu Date: Wed, 27 Nov 2019 14:40:37 +0800 Subject: [PATCH] input: misc: Add initial driver to support QTI HV haptics QTI HV (High Voltage) haptics is a module which can play haptics effects for LRA or ERM with drive voltage up to 10 V. It has several pattern sources built inside and each pattern source can be used for playing different vibration effects. Add the driver to support QTI HV haptics module, which will be registered as an input FF device and can be controlled by calling read/write/ ioctl APIs on the input device node. Change-Id: I8323bdf3f7a631570604577719d5b7876fc65ef1 Signed-off-by: Fenglin Wu --- drivers/input/misc/Kconfig | 11 + drivers/input/misc/Makefile | 1 + drivers/input/misc/qcom-hv-haptics.c | 1746 ++++++++++++++++++++++++++ 3 files changed, 1758 insertions(+) create mode 100644 drivers/input/misc/qcom-hv-haptics.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index dd5227cf8696..26016454e5ab 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -186,6 +186,17 @@ config INPUT_PMIC8XXX_PWRKEY To compile this driver as a module, choose M here: the module will be called pmic8xxx-pwrkey. +config INPUT_QCOM_HV_HAPTICS + tristate "QTI High Voltage Haptics support" + depends on MFD_SPMI_PMIC + help + This option enables device driver support for the high voltage haptics + peripheral found on Qualcomm Technologies, Inc. PMICs. The high voltage + haptics peripheral is capable of driving either LRA or ERM vibrators with + drive voltage up to 10 V, and its 5 integrated pattern sources can be used + for playing different vibration effects. To compile this driver as a module, + choose M here: the module will be called qcom-hv-haptics. + config INPUT_SPARCSPKR tristate "SPARC Speaker support" depends on PCI && SPARC64 diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index b92c53a6b5ae..ebd67c86cf58 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o obj-$(CONFIG_INPUT_PM8941_PWRKEY) += pm8941-pwrkey.o obj-$(CONFIG_INPUT_PM8XXX_VIBRATOR) += pm8xxx-vibrator.o obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o +obj-$(CONFIG_INPUT_QCOM_HV_HAPTICS) += qcom-hv-haptics.o obj-$(CONFIG_INPUT_POWERMATE) += powermate.o obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o diff --git a/drivers/input/misc/qcom-hv-haptics.c b/drivers/input/misc/qcom-hv-haptics.c new file mode 100644 index 000000000000..5b8d9d57c306 --- /dev/null +++ b/drivers/input/misc/qcom-hv-haptics.c @@ -0,0 +1,1746 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* status register definitions in HAPTICS_CFG module */ +#define HAP_CFG_STATUS_DATA_MSB_REG 0x09 +/* STATUS_DATA_MSB definitions while MOD_STATUS_SEL is 0 */ +#define AUTO_RES_CAL_DONE_BIT BIT(5) +#define CAL_TLRA_CL_STS_MSB_MASK GENMASK(4, 0) + +#define HAP_CFG_STATUS_DATA_LSB_REG 0x0A +/* STATUS_DATA_MSB definitions while MOD_STATUS_SEL is 0 */ +#define CAL_TLRA_CL_STS_LSB_MASK GENMASK(7, 0) + +#define HAP_CFG_FAULT_STATUS_REG 0x0C +#define SC_FLAG_BIT BIT(2) +#define AUTO_RES_ERROR_BIT BIT(1) +#define HPRW_RDY_FAULT_BIT BIT(0) + +/* config register definitions in HAPTICS_CFG module */ +#define HAP_CFG_DRV_CTRL_REG 0x47 +#define PSTG_DLY_MASK GENMASK(7, 6) +#define DRV_SLEW_RATE_MASK GENMASK(2, 0) + +#define HAP_CFG_VMAX_REG 0x48 +#define VMAX_STEP_MV 50 +#define MAX_VMAX_MV 11000 +#define DEFAULT_VMAX_MV 5000 + +#define HAP_CFG_DRV_WF_SEL_REG 0x49 +#define DRV_WF_SEL_MASK GENMASK(1, 0) + +#define HAP_CFG_AUTO_SHUTDOWN_CFG_REG 0x4A +#define HAP_CFG_TRIG_PRIORITY_REG 0x4B + +#define HAP_CFG_SPMI_PLAY_REG 0x4C +#define PLAY_EN_BIT BIT(7) +#define BRAKE_EN_BIT BIT(3) +#define PAT_SRC_MASK GENMASK(2, 0) + +#define HAP_CFG_EXT_TRIG_REG 0x4D + +#define HAP_CFG_SWR_ACCESS_REG 0x4E +#define SWR_PAT_CFG_EN_BIT BIT(7) +#define SWR_PAT_INPUT_EN BIT(6) +#define SWR_PAT_RES_N BIT(5) + +#define HAP_CFG_BRAKE_MODE_CFG_REG 0x50 +#define BRAKE_MODE_MASK GENMASK(7, 6) +#define BRAKE_MODE_SHIFT 6 +#define BRAKE_SINE_GAIN_MASK GENMASK(3, 2) +#define BRAKE_SINE_GAIN_SHIFT 2 +#define BRAKE_WF_SEL_MASK GENMASK(1, 0) + +#define HAP_CFG_CL_BRAKE_CFG_REG 0x51 +#define HAP_CFG_CL_BRAKE_CAL_PARAM_REG 0x52 +#define HAP_CFG_CL_BRAKE_RSET_REG 0x53 +#define HAP_CFG_PWM_CFG_REG 0x5A + +#define HAP_CFG_TLRA_OL_HIGH_REG 0x5C +#define TLRA_OL_MSB_MASK GENMASK(3, 0) +#define HAP_CFG_TLRA_OL_LOW_REG 0x5D +#define TLRA_OL_LSB_MASK GENMASK(7, 0) +#define TLRA_STEP_US 5 +#define TLRA_MAX_US 20475 + +#define HAP_CFG_RC_CLK_CAL_COUNT_MSB_REG 0x5E +#define RC_CLK_CAL_COUNT_MSB_MASK GENMASK(1, 0) +#define HAP_CFG_RC_CLK_CAL_COUNT_LSB_REG 0x5F +#define RC_CLK_CAL_COUNT_LSB_MASK GENMASK(7, 0) + +#define HAP_CFG_DRV_DUTY_CFG_REG 0x60 +#define HAP_CFG_ADT_DRV_DUTY_CFG_REG 0x61 +#define HAP_CFG_ZX_WIND_CFG_REG 0x62 + +#define HAP_CFG_AUTORES_CFG_REG 0x63 +#define AUTORES_EN_BIT BIT(7) + +#define HAP_CFG_AUTORES_ERR_RECOVERY_REG 0x64 +#define EN_HW_RECOVERY_BIT BIT(1) +#define SW_ERR_DRV_FREQ_BIT BIT(0) + +#define HAP_CFG_FAULT_CLR_REG 0x66 +#define SC_CLR_BIT BIT(2) +#define AUTO_RES_ERR_CLR_BIT BIT(1) +#define HPWR_RDY_FAULT_CLR_BIT BIT(0) + +#define HAP_CFG_VMAX_HDRM_REG 0x67 +#define VMAX_HDRM_MASK GENMASK(6, 0) +#define VMAX_HDRM_STEP_MV 50 + +/* status register definition for HAPTICS_PATTERN module */ +#define HAP_PTN_FIFO_READY_STS_REG 0x08 +#define FIFO_READY_BIT BIT(0) + +#define HAP_PTN_NUM_PAT_REG 0x09 + +/* config register definition for HAPTICS_PATTERN module */ +#define HAP_PTN_FIFO_DIN_MSB_REG 0x20 +#define HAP_PTN_FIFO_DIN_MSB_BIT BIT(0) +#define HAP_PTN_FIFO_DIN_LSB_REG 0x21 +#define HAP_PTN_FIFO_DIN_LSB_MASK GENMASK(7, 0) + +#define HAP_PTN_FIFO_PLAY_RATE_REG 0x22 +#define FIFO_PLAY_RATE_MASK GENMASK(3, 0) + +#define HAP_PTN_FIFO_EMPTY_CFG_REG 0x23 +#define EMPTY_THRESH_MASK GENMASK(3, 0) + +#define HAP_PTN_FIFO_DEPTH_CFG_REG 0x24 + +#define HAP_PTN_DIRECT_PLAY_REG 0x26 + +#define HAP_PTN_AUTORES_CAL_CFG_REG 0x28 + +#define HAP_PTN_PTRN1_TLRA_MSB_REG 0x30 +#define HAP_PTN_PTRN1_TLRA_LSB_REG 0x31 +#define HAP_PTN_PTRN2_TLRA_MSB_REG 0x32 +#define HAP_PTN_PTRN2_TLRA_LSB_REG 0x33 + +#define HAP_PTN_PTRN1_CFG_REG 0x34 +#define PTRN_FLRA2X_SHIFT 7 +#define PTRN_SAMPLE_PER_MASK GENMASK(2, 0) + +#define PTRN_AMP_MSB_MASK BIT(0) +#define PTRN_AMP_LSB_MASK GENMASK(7, 0) + +#define HAP_PTN_PTRN2_CFG_REG 0x50 + +#define HAP_PTN_BRAKE_AMP_REG 0x70 + +/* constant parameters */ +#define SAMPLES_PER_PATTERN 8 +#define BRAKE_SAMPLE_COUNT 8 +#define MAX_FIFO_SAMPLES 104 +#define DEFAULT_ERM_PLAY_RATE_US 5000 +#define MAX_EFFECT_COUNT 64 +#define FIFO_EMPTY_THRESHOLD 48 +#define FIFO_READY_TIMEOUT_MS 1000 +#define CHAR_PER_PATTERN_S 48 +#define CHAR_PER_SAMPLE 8 +#define CHAR_MSG_HEADER 16 +#define HW_BRAKE_CYCLES 5 + +enum drv_sig_shape { + WF_SQUARE, + WF_SINE, + WF_NO_MODULATION, + WF_RESERVED, +}; + +enum brake_mode { + OL_BRAKE, + CL_BRAKE, + PREDICT_BRAKE, + AUTO_BRAKE, +}; + +enum brake_sine_gain { + BRAKE_SINE_GAIN_X1, + BRAKE_SINE_GAIN_X2, + BRAKE_SINE_GAIN_X4, + BRAKE_SINE_GAIN_X8, +}; + +enum pattern_src { + FIFO, + DIRECT_PLAY, + PATTERN1, + PATTERN2, + SWR, + SRC_RESERVED, +}; + +enum s_period { + T_LRA = 0, + T_LRA_DIV_2, + T_LRA_DIV_4, + T_LRA_DIV_8, + T_LRA_X_2, + T_LRA_X_4, + T_LRA_X_8, + T_RESERVED, + /* F_xKHZ definitions are for FIFO only */ + F_8KHZ, + F_16HKZ, + F_24KHZ, + F_32KHZ, + F_44P1KHZ, + F_48KHZ, + F_RESERVED, +}; + +enum custom_effect_param { + CUSTOM_DATA_EFFECT_IDX, + CUSTOM_DATA_TIMEOUT_SEC_IDX, + CUSTOM_DATA_TIMEOUT_MSEC_IDX, + CUSTOM_DATA_LEN, +}; + +static const char * const src_str[] = { + "FIFO", + "DIRECT_PLAY", + "PATTERN1", + "PATTERN2", + "SWR", + "reserved", +}; + +static const char * const brake_str[] = { + "open-loop-brake", + "close-loop-brake", + "predictive-brake", + "auto-brake", +}; + +static const char * const period_str[] = { + "T_LRA", + "T_LRA_DIV_2", + "T_LRA_DIV_4", + "T_LRA_DIV_8", + "T_LRA_X_2", + "T_LRA_X_4", + "T_LRA_X_8", + "reserved_1", + "F_8KHZ", + "F_16HKZ", + "F_24KHZ", + "F_32KHZ", + "F_44P1KHZ", + "F_48KHZ", + "reserved_2", +}; + +struct pattern_s { + u16 amplitude; + enum s_period period; + bool f_lra_x2; +}; + +struct pattern_cfg { + struct pattern_s samples[SAMPLES_PER_PATTERN]; + u32 play_rate_us; + u32 play_length_us; + bool preload; +}; + +struct fifo_cfg { + u16 *samples; + u32 num_s; + enum s_period period_per_s; + u32 play_length_us; +}; + +struct brake_cfg { + u8 samples[BRAKE_SAMPLE_COUNT]; + enum brake_mode mode; + enum drv_sig_shape brake_wf; + enum brake_sine_gain sine_gain; + u32 play_length_us; + bool disabled; +}; + +struct haptics_effect { + struct pattern_cfg *pattern; + struct fifo_cfg *fifo; + struct brake_cfg *brake; + u32 id; + u32 vmax_mv; + u32 t_lra_us; + enum pattern_src src; + bool auto_res_disable; +}; + +struct fifo_play_status { + struct completion fifo_ready; + u32 samples_written; + atomic_t written_done; + atomic_t is_busy; +}; + +struct haptics_play_info { + struct haptics_effect *effect; + struct brake_cfg *brake; + struct fifo_play_status fifo_status; + u32 vmax_mv; + u32 length_us; + enum pattern_src pattern_src; +}; + +struct haptics_hw_config { + struct brake_cfg brake; + u32 vmax_mv; + u32 t_lra_us; + u32 preload_effect; + enum drv_sig_shape drv_wf; + bool is_erm; +}; + +struct haptics_chip { + struct device *dev; + struct regmap *regmap; + struct input_dev *input_dev; + struct haptics_hw_config config; + struct haptics_effect *effects; + struct haptics_play_info play; + struct work_struct fifo_work; + int fifo_empty_irq; + u32 effects_count; + u32 cfg_addr_base; + u32 ptn_addr_base; + bool fifo_empty_irq_en; +}; + +static int haptics_read(struct haptics_chip *chip, + u16 base, u8 offset, u8 *val, u32 length) +{ + int rc = 0; + u16 addr = base + offset; + + rc = regmap_bulk_read(chip->regmap, addr, val, length); + if (rc < 0) + dev_err(chip->dev, "read addr %d failed, rc=%d\n", addr, rc); + + return rc; +} + +static int haptics_write(struct haptics_chip *chip, + u16 base, u8 offset, u8 *val, u32 length) +{ + int rc = 0; + u16 addr = base + offset; + + rc = regmap_bulk_write(chip->regmap, addr, val, length); + if (rc < 0) + dev_err(chip->dev, "write addr %d failed, rc=%d\n", addr, rc); + + return rc; +} + +static int haptics_masked_write(struct haptics_chip *chip, + u16 base, u8 offset, u8 mask, u8 val) +{ + int rc = 0; + u16 addr = base + offset; + + regmap_update_bits(chip->regmap, addr, mask, val); + if (rc < 0) + dev_err(chip->dev, "update addr %d failed, rc=%d\n", addr, rc); + + return rc; +} + +static void __dump_effects(struct haptics_chip *chip) +{ + struct haptics_effect *effect; + struct pattern_s *sample; + char *str; + u32 size, pos; + int i, j; + + for (i = 0; i < chip->effects_count; i++) { + effect = &chip->effects[i]; + if (!effect) + return; + + dev_dbg(chip->dev, "effect %d\n", effect->id); + dev_dbg(chip->dev, "vmax_mv = %d\n", effect->vmax_mv); + if (effect->pattern) { + for (j = 0; j < SAMPLES_PER_PATTERN; j++) { + sample = &effect->pattern->samples[j]; + dev_dbg(chip->dev, "pattern = %d, period = %s, f_lra_x2 = %d\n", + sample->amplitude, + period_str[sample->period], + sample->f_lra_x2); + } + + dev_dbg(chip->dev, "pattern play_rate_us = %d\n", + effect->pattern->play_rate_us); + dev_dbg(chip->dev, "pattern play_length_us = %d\n", + effect->pattern->play_length_us); + dev_dbg(chip->dev, "pattern preload = %d\n", + effect->pattern->preload); + } + + if (effect->fifo) { + size = effect->fifo->num_s * CHAR_PER_SAMPLE + + CHAR_MSG_HEADER; + str = kzalloc(size, GFP_KERNEL); + if (str == NULL) + return; + + pos = 0; + pos += scnprintf(str, size, "%s", "FIFO data: "); + for (j = 0; j < effect->fifo->num_s; j++) + pos += scnprintf(str + pos, size - pos, "%#x ", + effect->fifo->samples[j]); + + dev_dbg(chip->dev, "%s\n", str); + kfree(str); + dev_dbg(chip->dev, "FIFO data play rate: %s\n", + period_str[effect->fifo->period_per_s]); + dev_dbg(chip->dev, "FIFO data play length: %dus\n", + effect->fifo->play_length_us); + } + + if (effect->brake && !effect->brake->disabled) { + size = BRAKE_SAMPLE_COUNT * CHAR_PER_SAMPLE + + CHAR_MSG_HEADER; + str = kzalloc(size, GFP_KERNEL); + if (str == NULL) + return; + + pos = 0; + pos += scnprintf(str, size, "%s", "brake pattern: "); + for (j = 0; j < BRAKE_SAMPLE_COUNT; j++) + pos += scnprintf(str + pos, size - pos, "%#x ", + effect->brake->samples[j]); + + dev_dbg(chip->dev, "%s\n", str); + kfree(str); + dev_dbg(chip->dev, "brake mode: %s\n", + brake_str[effect->brake->mode]); + dev_dbg(chip->dev, "brake play length: %dus\n", + effect->brake->play_length_us); + } + + dev_dbg(chip->dev, "pattern src: %s\n", src_str[effect->src]); + dev_dbg(chip->dev, "auto resonance %s\n", + effect->auto_res_disable ? + "disabled" : "enabled"); + } +} + +static void verify_brake_samples(struct brake_cfg *brake) +{ + int i; + + if (brake->mode == PREDICT_BRAKE || brake->mode == AUTO_BRAKE) + return; + + for (i = BRAKE_SAMPLE_COUNT - 1; i > 0; i--) { + if (brake->samples[i] != 0) { + brake->disabled = false; + return; + } + } + + brake->disabled = true; +} + +static int get_pattern_play_length_us(struct pattern_cfg *pattern) +{ + int i = SAMPLES_PER_PATTERN - 1, j; + u32 us_per_sample, total_length_us = 0; + + if (!pattern) + return -EINVAL; + + for (; i >= 0; i--) + if (pattern->samples[i].amplitude != 0) + break; + + for (j = 0; j < i; j++) { + us_per_sample = pattern->play_rate_us; + switch (pattern->samples[j].period) { + case T_LRA: + break; + case T_LRA_DIV_2: + us_per_sample /= 2; + break; + case T_LRA_DIV_4: + us_per_sample /= 4; + break; + case T_LRA_DIV_8: + us_per_sample /= 8; + break; + case T_LRA_X_2: + us_per_sample *= 2; + break; + case T_LRA_X_4: + us_per_sample *= 4; + break; + case T_LRA_X_8: + us_per_sample *= 8; + break; + default: + return -EINVAL; + } + + if (pattern->samples[j].f_lra_x2) + us_per_sample /= 2; + + total_length_us += us_per_sample; + } + + return total_length_us; +} + +static int get_fifo_play_length_us(struct fifo_cfg *fifo, u32 t_lra_us) +{ + u32 length_us; + int i; + + if (!fifo) + return -EINVAL; + + for (i = fifo->num_s; i > 0; i--) + if (fifo->samples[i] != 0) + break; + + length_us = (i + 1) * t_lra_us; + switch (fifo->period_per_s) { + case T_LRA: + break; + case T_LRA_DIV_2: + length_us /= 2; + break; + case T_LRA_DIV_4: + length_us /= 4; + break; + case T_LRA_DIV_8: + length_us /= 8; + break; + case F_8KHZ: + length_us = 1000 * fifo->num_s / 8; + break; + case F_16HKZ: + length_us = 1000 * fifo->num_s / 16; + break; + case F_24KHZ: + length_us = 1000 * fifo->num_s / 24; + break; + case F_32KHZ: + length_us = 1000 * fifo->num_s / 32; + break; + case F_44P1KHZ: + length_us = 10000 * fifo->num_s / 441; + break; + case F_48KHZ: + length_us = 1000 * fifo->num_s / 48; + break; + default: + length_us = -EINVAL; + break; + } + + return length_us; +} + +static int get_brake_play_length_us(struct brake_cfg *brake, u32 t_lra_us) +{ + int i = BRAKE_SAMPLE_COUNT - 1; + + if (!brake || brake->disabled) + return 0; + + if (brake->mode == PREDICT_BRAKE || brake->mode == AUTO_BRAKE) + return HW_BRAKE_CYCLES * t_lra_us; + + for (; i >= 0; i--) + if (brake->samples[i] != 0) + break; + + return t_lra_us * (i + 1); +} + +static int haptics_set_vmax_mv(struct haptics_chip *chip, u32 vmax_mv) +{ + int rc = 0; + u8 val; + + if (vmax_mv > MAX_VMAX_MV) { + dev_err(chip->dev, "vmax (%d) exceed the max value: %d\n", + vmax_mv, MAX_VMAX_MV); + return -EINVAL; + } + + val = vmax_mv / VMAX_STEP_MV; + rc = haptics_write(chip, chip->cfg_addr_base, + HAP_CFG_VMAX_REG, &val, 1); + if (rc < 0) + dev_err(chip->dev, "config VMAX failed, rc=%d\n", rc); + + return rc; +} + +static int haptics_enable_autores(struct haptics_chip *chip, bool en) +{ + int rc; + + rc = haptics_masked_write(chip, chip->cfg_addr_base, + HAP_CFG_AUTORES_CFG_REG, AUTORES_EN_BIT, + en ? AUTORES_EN_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "%s auto resonance failed, rc=%d\n", + en ? "enable" : "disable", rc); + + return rc; +} + +static int haptics_enable_play(struct haptics_chip *chip, bool en) +{ + struct haptics_play_info *play = &chip->play; + int rc; + u8 val; + + val = play->pattern_src; + if (play->brake && !play->brake->disabled) + val |= BRAKE_EN_BIT; + + if (en) + val |= PLAY_EN_BIT; + + rc = haptics_write(chip, chip->cfg_addr_base, + HAP_CFG_SPMI_PLAY_REG, &val, 1); + if (rc < 0) + dev_err(chip->dev, "Write SPMI_PLAY failed, rc=%d\n", rc); + + return rc; +} + +static int haptics_set_brake(struct haptics_chip *chip, struct brake_cfg *brake) +{ + int rc = 0; + u8 zero_samples[BRAKE_SAMPLE_COUNT] = {0}, val; + + if (brake->disabled) + return 0; + + val = brake->mode << BRAKE_MODE_SHIFT | + brake->sine_gain << BRAKE_SINE_GAIN_SHIFT; + rc = haptics_masked_write(chip, chip->cfg_addr_base, + HAP_CFG_BRAKE_MODE_CFG_REG, + BRAKE_MODE_MASK | BRAKE_SINE_GAIN_MASK, val); + if (rc < 0) { + dev_err(chip->dev, "set brake CFG failed, rc=%d\n", rc); + return rc; + } + + rc = haptics_write(chip, chip->ptn_addr_base, HAP_PTN_BRAKE_AMP_REG, + (brake->mode == OL_BRAKE || brake->mode == CL_BRAKE) ? + brake->samples : zero_samples, BRAKE_SAMPLE_COUNT); + if (rc < 0) { + dev_err(chip->dev, "set brake pattern failed, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int haptics_set_pattern(struct haptics_chip *chip, + struct pattern_cfg *pattern, + enum pattern_src src) +{ + struct pattern_s *sample; + u8 values[SAMPLES_PER_PATTERN * 3] = { 0 }; + u8 ptn_tlra_addr, ptn_cfg_addr; + int i, rc, tmp; + + if (src != PATTERN1 && src != PATTERN2) { + dev_err(chip->dev, "no pattern src specified!\n"); + return -EINVAL; + } + + ptn_tlra_addr = HAP_PTN_PTRN1_TLRA_MSB_REG; + ptn_cfg_addr = HAP_PTN_PTRN1_CFG_REG; + if (src == PATTERN2) { + ptn_tlra_addr = HAP_PTN_PTRN2_TLRA_MSB_REG; + ptn_cfg_addr = HAP_PTN_PTRN2_CFG_REG; + } + + /* Configure T_LRA for this pattern */ + tmp = pattern->play_rate_us / TLRA_STEP_US; + values[0] = (tmp >> 8) & TLRA_OL_MSB_MASK; + values[1] = tmp & TLRA_OL_LSB_MASK; + rc = haptics_write(chip, chip->ptn_addr_base, ptn_tlra_addr, + values, 2); + if (rc < 0) { + dev_err(chip->dev, "update pattern TLRA failed, rc=%d\n", rc); + return rc; + } + + /* Configure pattern registers */ + for (i = 0; i < SAMPLES_PER_PATTERN; i++) { + sample = &pattern->samples[i]; + values[i * 3] = sample->f_lra_x2 << PTRN_FLRA2X_SHIFT; + values[i * 3] |= sample->period & PTRN_SAMPLE_PER_MASK; + values[i * 3 + 1] = + (sample->amplitude >> 8) & PTRN_AMP_MSB_MASK; + values[i * 3 + 2] = sample->amplitude & PTRN_AMP_LSB_MASK; + } + + rc = haptics_write(chip, chip->ptn_addr_base, ptn_cfg_addr, + values, SAMPLES_PER_PATTERN * 3); + if (rc < 0) { + dev_err(chip->dev, "write pattern data failed, rc=%d\n", rc); + return rc; + } + + return 0; + +} + +static int haptics_update_fifo_sample(struct haptics_chip *chip, u16 sample) +{ + int rc = 0; + u8 val; + bool ready; + + rc = haptics_read(chip, chip->ptn_addr_base, + HAP_PTN_FIFO_READY_STS_REG, &val, 1); + if (rc < 0) { + dev_err(chip->dev, "read FIFO_READY_STS failed, rc=%d\n", + rc); + return rc; + } + + ready = !!(val & FIFO_READY_BIT); + /* sleep no more than 10us if FIFO memory is not ready */ + if (!ready) + usleep_range(1, 10); + + /* + * Fill FIFO_DIN registers to update FIFO memory, + * need to fill LSB first then MSB + */ + val = sample & HAP_PTN_FIFO_DIN_LSB_MASK; + rc = haptics_write(chip, chip->ptn_addr_base, + HAP_PTN_FIFO_DIN_LSB_REG, &val, 1); + if (rc < 0) { + dev_err(chip->dev, "write FIFO LSB failed, rc=%d\n", + rc); + return rc; + } + + val = (sample >> 8) & HAP_PTN_FIFO_DIN_MSB_BIT; + rc = haptics_write(chip, chip->ptn_addr_base, + HAP_PTN_FIFO_DIN_MSB_REG, &val, 1); + if (rc < 0) { + dev_err(chip->dev, "write FIFO MSB failed, rc=%d\n", + rc); + return rc; + } + + return 0; +} + +static int haptics_set_fifo(struct haptics_chip *chip, struct fifo_cfg *fifo) +{ + struct fifo_play_status *status = &chip->play.fifo_status; + u32 num, fifo_thresh; + int rc, i; + + if (atomic_read(&status->is_busy) == 1) { + dev_err(chip->dev, "FIFO is busy\n"); + return -EBUSY; + } + + /* Configure FIFO play rate */ + rc = haptics_masked_write(chip, chip->ptn_addr_base, + HAP_PTN_FIFO_PLAY_RATE_REG, + FIFO_PLAY_RATE_MASK, fifo->period_per_s); + if (rc < 0) + return rc; + + atomic_set(&status->written_done, 0); + status->samples_written = 0; + + /* + * Write the 1st set of the data into FIFO if there are + * more than 104 samples, the rest will be written if + * any FIFO memory is available after playing. + */ + if (fifo->num_s > MAX_FIFO_SAMPLES) + num = MAX_FIFO_SAMPLES; + else + num = fifo->num_s; + + for (i = 0; i < num; i++) { + rc = haptics_update_fifo_sample(chip, fifo->samples[i]); + if (rc < 0) + return rc; + } + + atomic_set(&status->is_busy, 1); + status->samples_written = num; + if (num == fifo->num_s) { + fifo_thresh = 0; + atomic_set(&status->written_done, 1); + } else { + reinit_completion(&status->fifo_ready); + fifo_thresh = FIFO_EMPTY_THRESHOLD; + } + + /* + * Set FIFO empty threshold and enable FIFO empty IRQ, + * more data can be written into FIFO memory after + * the IRQ is triggered. + */ + rc = haptics_masked_write(chip, chip->ptn_addr_base, + HAP_PTN_FIFO_EMPTY_CFG_REG, EMPTY_THRESH_MASK, + fifo_thresh / 4); + if (rc < 0) + return rc; + + if (!chip->fifo_empty_irq_en) { + enable_irq(chip->fifo_empty_irq); + chip->fifo_empty_irq_en = true; + } + + return 0; +} + +static int haptics_set_direct_play(struct haptics_chip *chip) +{ + struct haptics_play_info *play = &chip->play; + int rc = 0; + u8 val; + + /* configure VMAX in case it was changed in previous effect playing */ + rc = haptics_set_vmax_mv(chip, chip->config.vmax_mv); + if (rc < 0) + return rc; + + /* Set DIRECT_PLAY amplitude */ + val = play->vmax_mv / VMAX_STEP_MV; + rc = haptics_write(chip, chip->ptn_addr_base, + HAP_PTN_DIRECT_PLAY_REG, &val, 1); + if (rc < 0) + return rc; + + /* Config brake settings if it's necessary */ + play->brake = &chip->config.brake; + if (play->brake) { + rc = haptics_set_brake(chip, play->brake); + if (rc < 0) + return rc; + } + + /* Always enable LRA auto resonance for DIRECT_PLAY */ + rc = haptics_enable_autores(chip, !chip->config.is_erm); + if (rc < 0) + return rc; + + play->pattern_src = DIRECT_PLAY; + return 0; +} + +static int haptics_load_predefined_effect(struct haptics_chip *chip, + int effect_idx) +{ + struct haptics_play_info *play = &chip->play; + int rc; + + if (effect_idx >= chip->effects_count) + return -EINVAL; + + play->effect = &chip->effects[effect_idx]; + /* Clamp VMAX for different vibration strength */ + rc = haptics_set_vmax_mv(chip, play->vmax_mv); + if (rc < 0) + return rc; + + play->pattern_src = play->effect->src; + if (play->pattern_src != PATTERN1 || + play->pattern_src != PATTERN2 || + play->pattern_src != FIFO) { + dev_err(chip->dev, "pattern src %d can't be used for predefined effect\n", + play->pattern_src); + return -EINVAL; + } + + play->brake = play->effect->brake; + /* Config brake settings if it's necessary */ + if (play->brake) { + rc = haptics_set_brake(chip, play->brake); + if (rc < 0) + return rc; + } + + if (play->pattern_src == PATTERN1 || play->pattern_src == PATTERN2) { + if (play->effect->pattern->preload) { + dev_dbg(chip->dev, "Ignore preloaded effect: %d\n", + play->effect->id); + return 0; + } + + rc = haptics_set_pattern(chip, play->effect->pattern, + play->pattern_src); + if (rc < 0) + return rc; + } + + if (play->pattern_src == FIFO) { + rc = haptics_set_fifo(chip, play->effect->fifo); + if (rc < 0) + return rc; + } + + return 0; +} + +static u32 get_play_length_us(struct haptics_play_info *play) +{ + struct haptics_effect *effect = play->effect; + u32 length_us = 0; + + if (play->brake) + length_us = play->brake->play_length_us; + + if ((effect->src == PATTERN1 || effect->src == PATTERN2) + && effect->pattern) + length_us += effect->pattern->play_length_us; + else if (effect->src == FIFO && effect->fifo) + length_us += effect->fifo->play_length_us; + + return length_us; +} + +static int haptics_upload_effect(struct input_dev *dev, + struct ff_effect *effect, struct ff_effect *old) +{ + struct haptics_chip *chip = input_get_drvdata(dev); + struct haptics_hw_config *config = &chip->config; + struct haptics_play_info *play = &chip->play; + s16 level, data[CUSTOM_DATA_LEN]; + int rc = 0, tmp, i; + + switch (effect->type) { + case FF_CONSTANT: + play->length_us = effect->replay.length * USEC_PER_MSEC; + level = effect->u.constant.level; + tmp = level * config->vmax_mv; + play->vmax_mv = tmp / 0x7fff; + dev_dbg(chip->dev, "upload constant effect, length = %dus, vmax_mv = %d\n", + play->length_us, play->vmax_mv); + haptics_set_direct_play(chip); + if (rc < 0) { + dev_err(chip->dev, "set direct play failed, rc=%d\n", + rc); + return rc; + } + break; + + case FF_PERIODIC: + if (chip->effects_count == 0) + return -EINVAL; + + if (effect->u.periodic.waveform != FF_CUSTOM) { + dev_err(chip->dev, "Only support custom waveforms\n"); + return -EINVAL; + } + + if (copy_from_user(data, effect->u.periodic.custom_data, + sizeof(data))) + return -EFAULT; + + for (i = 0; i < chip->effects_count; i++) + if (chip->effects[i].id == data[CUSTOM_DATA_EFFECT_IDX]) + break; + + if (i == chip->effects_count) { + dev_err(chip->dev, "effect%d is not supported!\n", + data[CUSTOM_DATA_EFFECT_IDX]); + return -EINVAL; + } + + level = effect->u.periodic.magnitude; + tmp = level * chip->effects[i].vmax_mv; + play->vmax_mv = tmp / 0x7fff; + + dev_dbg(chip->dev, "upload effect %d, vmax_mv=%d\n", + chip->effects[i].id, play->vmax_mv); + rc = haptics_load_predefined_effect(chip, i); + if (rc < 0) { + dev_err(chip->dev, "Play predefined effect%d failed, rc=%d\n", + chip->effects[i].id, rc); + return rc; + } + + play->length_us = get_play_length_us(play); + data[CUSTOM_DATA_TIMEOUT_SEC_IDX] = + play->length_us / USEC_PER_SEC; + data[CUSTOM_DATA_TIMEOUT_MSEC_IDX] = + (play->length_us % USEC_PER_SEC) / USEC_PER_MSEC; + + if (copy_to_user(effect->u.periodic.custom_data, data, + sizeof(s16) * CUSTOM_DATA_LEN)) + return -EFAULT; + break; + default: + dev_err(chip->dev, "%d effect is not supported\n", + effect->type); + return -EINVAL; + } + + return 0; +} + +static int haptics_playback(struct input_dev *dev, int effect_id, int val) +{ + struct haptics_chip *chip = input_get_drvdata(dev); + struct haptics_play_info *play = &chip->play; + int rc; + + dev_dbg(chip->dev, "playback val = %d\n", val); + if (!!val) { + rc = haptics_enable_play(chip, true); + if (rc < 0) + return rc; + + if ((atomic_read(&play->fifo_status.written_done) == 0) + && play->pattern_src == FIFO) + schedule_work(&chip->fifo_work); + } else { + rc = haptics_enable_play(chip, false); + if (rc < 0) + return rc; + + if (chip->fifo_empty_irq_en) { + disable_irq_nosync(chip->fifo_empty_irq); + chip->fifo_empty_irq_en = false; + } + } + + return 0; +} + +static int haptics_erase(struct input_dev *dev, int effect_id) +{ + return 0; +} + +static void haptics_set_gain(struct input_dev *dev, u16 gain) +{ + struct haptics_chip *chip = input_get_drvdata(dev); + struct haptics_hw_config *config = &chip->config; + struct haptics_play_info *play = &chip->play; + + if (gain == 0) + return; + + if (gain > 0x7fff) + gain = 0x7fff; + + play->vmax_mv = ((u32)(gain * config->vmax_mv)) / 0x7fff; + haptics_set_vmax_mv(chip, play->vmax_mv); +} + +static int haptics_hw_init(struct haptics_chip *chip) +{ + struct haptics_hw_config *config = &chip->config; + struct haptics_effect *effect; + int rc = 0, tmp, i; + u8 val[2]; + + /* Config VMAX */ + rc = haptics_set_vmax_mv(chip, config->vmax_mv); + if (rc < 0) + return rc; + + /* Config driver waveform shape */ + rc = haptics_masked_write(chip, chip->cfg_addr_base, + HAP_CFG_DRV_WF_SEL_REG, + DRV_WF_SEL_MASK, config->drv_wf); + if (rc < 0) + return rc; + + /* Config brake mode and waveform shape */ + val[0] = (config->brake.mode << BRAKE_MODE_SHIFT) + | config->brake.sine_gain << BRAKE_SINE_GAIN_SHIFT + | config->brake.brake_wf; + rc = haptics_masked_write(chip, chip->cfg_addr_base, + HAP_CFG_BRAKE_MODE_CFG_REG, + BRAKE_MODE_MASK | BRAKE_SINE_GAIN_MASK + | BRAKE_WF_SEL_MASK, val[0]); + if (rc < 0) + return rc; + + if (config->is_erm) + return 0; + + /* Config T_LRA */ + tmp = config->t_lra_us / TLRA_STEP_US; + val[0] = (tmp >> 8) & TLRA_OL_MSB_MASK; + val[1] = tmp & TLRA_OL_LSB_MASK; + rc = haptics_write(chip, chip->cfg_addr_base, + HAP_CFG_TLRA_OL_HIGH_REG, val, 2); + if (rc < 0) + return rc; + + /* preload effect */ + if (config->preload_effect != -EINVAL) { + for (i = 0; i < chip->effects_count; i++) + if (chip->effects[i].id == config->preload_effect) + break; + + if (i == chip->effects_count) { + dev_err(chip->dev, "preload effect %d is not found\n", + config->preload_effect); + return -EINVAL; + } + + effect = &chip->effects[i]; + + rc = haptics_set_pattern(chip, effect->pattern, effect->src); + if (rc < 0) { + dev_err(chip->dev, "Preload effect failed, rc=%d\n", + rc); + return rc; + } + } + + return rc; +} + +static void update_fifo_work(struct work_struct *work) +{ + struct haptics_chip *chip = container_of(work, + struct haptics_chip, fifo_work); + struct fifo_cfg *fifo = chip->play.effect->fifo; + struct fifo_play_status *status = &chip->play.fifo_status; + u32 num, samples_written, samples_left; + int rc, i; + + samples_written = status->samples_written; + num = MAX_FIFO_SAMPLES - FIFO_EMPTY_THRESHOLD; + samples_left = fifo->num_s - samples_written; + + while (samples_left > 0) { + /* Waiting on FIFO empty IRQ triggered */ + rc = wait_for_completion_timeout(&status->fifo_ready, + msecs_to_jiffies(FIFO_READY_TIMEOUT_MS)); + if (!rc) { + dev_err(chip->dev, "Timeout on waiting FIFO ready!\n"); + return; + } + + if (samples_left <= num) + num = samples_left; + else + reinit_completion(&status->fifo_ready); + + /* Write more pattern data into FIFO memory */ + for (i = 0; i < num; i++) { + rc = haptics_update_fifo_sample(chip, + fifo->samples[samples_written + i]); + if (rc < 0) + return; + } + + samples_written += num; + samples_left -= num; + dev_dbg(chip->dev, "FIFO %d samples written, %d samples left\n", + samples_written, samples_left); + } + + /* + * If all pattern data is written, set FIFO empty + * threshold to 0 so that FIFO empty IRQ can be used + * for detecting FIFO playing done event. + */ + dev_dbg(chip->dev, "FIFO programmed done\n"); + atomic_set(&chip->play.fifo_status.written_done, 1); + rc = haptics_masked_write(chip, chip->ptn_addr_base, + HAP_PTN_FIFO_EMPTY_CFG_REG, + EMPTY_THRESH_MASK, 0); + if (rc < 0) + dev_err(chip->dev, "set FIFO empty threshold to 0 failed, rc=%d\n", + rc); +} + +static irqreturn_t fifo_empty_irq_handler(int irq, void *data) +{ + struct haptics_chip *chip = data; + int rc; + + if (atomic_read(&chip->play.fifo_status.written_done) == 1) { + dev_dbg(chip->dev, "FIFO data is done playing\n"); + rc = haptics_enable_play(chip, false); + if (rc < 0) + return IRQ_HANDLED; + + if (chip->fifo_empty_irq_en) { + disable_irq_nosync(chip->fifo_empty_irq); + chip->fifo_empty_irq_en = false; + } + + atomic_set(&chip->play.fifo_status.written_done, 0); + atomic_set(&chip->play.fifo_status.is_busy, 0); + } else { + complete(&chip->play.fifo_status.fifo_ready); + } + + return IRQ_HANDLED; +} + +static int haptics_parse_per_effect_dt(struct haptics_chip *chip, + struct device_node *node, struct haptics_effect *effect) +{ + struct haptics_hw_config *config = &chip->config; + u32 data[SAMPLES_PER_PATTERN * 3]; + int rc, tmp, i; + + if (!effect) + return -EINVAL; + + rc = of_property_read_u32(node, "qcom,effect-id", &effect->id); + if (rc < 0) { + dev_err(chip->dev, "Read qcom,effect-id failed, rc=%d\n", + rc); + return rc; + } + + effect->vmax_mv = config->vmax_mv; + rc = of_property_read_u32(node, "qcom,wf-vmax-mv", &tmp); + if (rc < 0) + dev_dbg(chip->dev, "Read qcom,wf-vmax-mv failed, rc=%d\n", + rc); + else + effect->vmax_mv = tmp; + + if (effect->vmax_mv > MAX_VMAX_MV) { + dev_err(chip->dev, "qcom,wf-vmax-mv (%d) exceed the max value: %d\n", + effect->vmax_mv, MAX_VMAX_MV); + return -EINVAL; + } + + effect->t_lra_us = config->t_lra_us; + tmp = of_property_count_elems_of_size(node, + "qcom,wf-pattern-data", sizeof(u32)); + if (tmp > SAMPLES_PER_PATTERN * 3) { + dev_err(chip->dev, "Pattern src can only play 8 samples at max\n"); + return -EINVAL; + } + + if (tmp > 0) { + effect->pattern = devm_kzalloc(chip->dev, + sizeof(*effect->pattern), GFP_KERNEL); + if (!effect->pattern) + return -ENOMEM; + + rc = of_property_read_u32_array(node, + "qcom,wf-pattern-data", data, tmp); + if (rc < 0) { + dev_err(chip->dev, "Read wf-pattern-data failed, rc=%d\n", + rc); + return rc; + } + + for (i = 0; i < tmp / 3; i++) { + if (data[3 * i] > 0x1ff || data[3 * i + 1] > T_LRA_X_8 + || data[3 * i + 2] > 1) { + dev_err(chip->dev, "allowed tuples: [amplitude(<= 0x1ff) period(<=6(T_LRA_X_8)) f_lra_x2(0,1)]\n"); + return -EINVAL; + } + + effect->pattern->samples[i].amplitude = + (u16)data[3 * i]; + effect->pattern->samples[i].period = + (enum s_period)data[3 * i + 1]; + effect->pattern->samples[i].f_lra_x2 = + (bool)data[3 * i + 2]; + } + + effect->pattern->preload = of_property_read_bool(node, + "qcom,wf-pattern-preload"); + /* + * Use PATTERN1 src by default, effect with preloaded + * pattern will use PATTERN2 by default and only the + * 1st preloaded pattern will be served. + */ + effect->src = PATTERN1; + if (effect->pattern->preload) { + if (config->preload_effect != -EINVAL) { + dev_err(chip->dev, "effect %d has been defined as preloaded\n", + config->preload_effect); + effect->pattern->preload = false; + } else { + config->preload_effect = effect->id; + effect->src = PATTERN2; + } + } + } + + tmp = of_property_count_u16_elems(node, "qcom,wf-fifo-data"); + if (tmp > 0) { + effect->fifo = devm_kzalloc(chip->dev, + sizeof(*effect->fifo), GFP_KERNEL); + if (!effect->fifo) + return -ENOMEM; + + effect->fifo->samples = devm_kcalloc(chip->dev, + tmp, sizeof(u16), GFP_KERNEL); + if (!effect->fifo->samples) + return -ENOMEM; + + rc = of_property_read_u16_array(node, "qcom,wf-fifo-data", + effect->fifo->samples, tmp); + if (rc < 0) { + dev_err(chip->dev, "Read wf-fifo-data failed, rc=%d\n", + rc); + return rc; + } + + effect->fifo->num_s = tmp; + } + + if (!effect->pattern && !effect->fifo) { + dev_err(chip->dev, "no pattern specified for effect %d\n", + effect->id); + return -EINVAL; + } + + if (effect->pattern) { + if (config->is_erm) + effect->pattern->play_rate_us = + DEFAULT_ERM_PLAY_RATE_US; + else + effect->pattern->play_rate_us = config->t_lra_us; + + rc = of_property_read_u32(node, "qcom,wf-pattern-period-us", + &tmp); + if (rc < 0) + dev_dbg(chip->dev, "Read qcom,wf-pattern-period-us failed, rc=%d\n", + rc); + else + effect->pattern->play_rate_us = tmp; + + if (effect->pattern->play_rate_us > TLRA_MAX_US) { + dev_err(chip->dev, "qcom,wf-pattern-period-us (%d) exceed the max value: %d\n", + effect->pattern->play_rate_us, + TLRA_MAX_US); + return -EINVAL; + } + + effect->pattern->play_length_us = + get_pattern_play_length_us(effect->pattern); + if (effect->pattern->play_length_us == -EINVAL) { + dev_err(chip->dev, "get pattern play length failed\n"); + return -EINVAL; + } + + if (effect->fifo) + dev_dbg(chip->dev, "Ignore FIFO data if pattern is specified!\n"); + + } else if (effect->fifo) { + effect->fifo->period_per_s = T_LRA; + rc = of_property_read_u32(node, "qcom,wf-fifo-period", &tmp); + if (tmp > F_48KHZ) { + dev_err(chip->dev, "FIFO playing period %d is not supported\n", + tmp); + return -EINVAL; + } else if (!rc) { + effect->fifo->period_per_s = tmp; + } + + effect->fifo->play_length_us = + get_fifo_play_length_us(effect->fifo, config->t_lra_us); + if (effect->fifo->play_length_us == -EINVAL) { + dev_err(chip->dev, "get fifo play length failed\n"); + return -EINVAL; + } + + effect->src = FIFO; + } + + effect->brake = devm_kzalloc(chip->dev, + sizeof(*effect->brake), GFP_KERNEL); + if (!effect->brake) + return -ENOMEM; + + memcpy(effect->brake, &config->brake, sizeof(*effect->brake)); + + of_property_read_u32(node, "qcom,wf-brake-mode", &effect->brake->mode); + if (effect->brake->mode > AUTO_BRAKE) { + dev_err(chip->dev, "can't support brake mode: %d\n", + effect->brake->mode); + return -EINVAL; + } + + if (effect->brake->brake_wf == WF_SINE) { + of_property_read_u32(node, "qcom,wf-brake-sine-gain", + &effect->brake->sine_gain); + if (effect->brake->sine_gain > BRAKE_SINE_GAIN_X8) { + dev_err(chip->dev, "can't support brake sine gain: %d\n", + effect->brake->sine_gain); + return -EINVAL; + } + } + + effect->brake->disabled = + of_property_read_bool(node, "qcom,wf-brake-disable"); + tmp = of_property_count_u8_elems(node, "qcom,wf-brake-pattern"); + if (tmp > BRAKE_SAMPLE_COUNT) { + dev_err(chip->dev, "more than %d brake samples\n", + BRAKE_SAMPLE_COUNT); + return -EINVAL; + } + + if (tmp > 0) { + memset(effect->brake->samples, 0, + sizeof(u8) * BRAKE_SAMPLE_COUNT); + rc = of_property_read_u8_array(node, "qcom,wf-brake-pattern", + effect->brake->samples, tmp); + if (rc < 0) { + dev_err(chip->dev, "Read wf-brake-pattern failed, rc=%d\n", + rc); + return rc; + } + verify_brake_samples(effect->brake); + } else { + if (effect->brake->mode == OL_BRAKE || + effect->brake->mode == CL_BRAKE) + effect->brake->disabled = true; + } + + effect->brake->play_length_us = + get_brake_play_length_us(effect->brake, config->t_lra_us); + + if (config->is_erm) + return 0; + + /* LRA specific per-effect settings are parsed below */ + effect->auto_res_disable = of_property_read_bool(node, + "qcom,wf-auto-res-disable"); + + return 0; +} + +static int haptics_parse_effects_dt(struct haptics_chip *chip) +{ + struct device_node *node = chip->dev->of_node; + struct device_node *child; + int rc, i = 0, num; + + num = of_get_available_child_count(node); + if (num == 0) + return 0; + + chip->effects = devm_kcalloc(chip->dev, num, + sizeof(*chip->effects), GFP_KERNEL); + if (!chip->effects) + return -ENOMEM; + + for_each_available_child_of_node(node, child) { + rc = haptics_parse_per_effect_dt(chip, child, + &chip->effects[i]); + if (rc < 0) { + dev_err(chip->dev, "parse effect %d failed, rc=%d\n", + i); + of_node_put(child); + return rc; + } + i++; + } + + chip->effects_count = i; + __dump_effects(chip); + + return 0; +} + +static int haptics_parse_lra_dt(struct haptics_chip *chip) +{ + struct haptics_hw_config *config = &chip->config; + struct device_node *node = chip->dev->of_node; + int rc; + + rc = of_property_read_u32(node, "qcom,lra-period-us", + &config->t_lra_us); + if (rc < 0) { + dev_err(chip->dev, "Read T-LRA failed, rc=%d\n", rc); + return rc; + } + + if (config->t_lra_us > TLRA_MAX_US) { + dev_err(chip->dev, "qcom,lra-period-us (%d) exceed the max value: %d\n", + config->t_lra_us, TLRA_MAX_US); + return -EINVAL; + } + + config->drv_wf = WF_SINE; + of_property_read_u32(node, "qcom,drv-sig-shape", &config->drv_wf); + if (config->drv_wf >= WF_RESERVED) { + dev_err(chip->dev, "Can't support drive shape: %d\n", + config->drv_wf); + return -EINVAL; + } + + config->brake.brake_wf = WF_SINE; + of_property_read_u32(node, "qcom,brake-sig-shape", + &config->brake.brake_wf); + if (config->brake.brake_wf >= WF_RESERVED) { + dev_err(chip->dev, "Can't support brake shape: %d\n", + config->brake.brake_wf); + return -EINVAL; + } + + if (config->brake.brake_wf == WF_SINE) { + config->brake.sine_gain = BRAKE_SINE_GAIN_X1; + of_property_read_u32(node, "qcom,brake-sine-gain", + &config->brake.sine_gain); + if (config->brake.sine_gain > BRAKE_SINE_GAIN_X8) { + dev_err(chip->dev, "Can't support brake sine gain: %d\n", + config->brake.sine_gain); + return -EINVAL; + } + } + + return 0; +} + +static int haptics_parse_dt(struct haptics_chip *chip) +{ + struct haptics_hw_config *config = &chip->config; + struct device_node *node = chip->dev->of_node; + struct platform_device *pdev = to_platform_device(chip->dev); + const __be32 *addr; + int rc = 0, tmp; + + addr = of_get_address(node, 0, NULL, NULL); + if (!addr) { + dev_err(chip->dev, "Read HAPTICS_CFG address failed, rc = %d\n", + rc); + return rc; + } + + chip->cfg_addr_base = be32_to_cpu(*addr); + addr = of_get_address(node, 1, NULL, NULL); + if (!addr) { + dev_err(chip->dev, "Read HAPTICS_PATTERN address failed, rc = %d\n", + rc); + return rc; + } + + chip->ptn_addr_base = be32_to_cpu(*addr); + chip->fifo_empty_irq = platform_get_irq_byname(pdev, "fifo-empty"); + if (!chip->fifo_empty_irq) { + dev_err(chip->dev, "Get fifo-empty IRQ failed\n"); + return -EINVAL; + } + + config->vmax_mv = DEFAULT_VMAX_MV; + of_property_read_u32(node, "qcom,vmax-mv", &config->vmax_mv); + if (config->vmax_mv >= MAX_VMAX_MV) { + dev_err(chip->dev, "qcom,vmax-mv (%d) exceed the max value: %d\n", + config->vmax_mv, MAX_VMAX_MV); + return -EINVAL; + } + + config->brake.mode = AUTO_BRAKE; + of_property_read_u32(node, "qcom,brake-mode", &config->brake.mode); + if (config->brake.mode > AUTO_BRAKE) { + dev_err(chip->dev, "Can't support brake mode: %d\n", + config->brake.mode); + return -EINVAL; + } + + config->brake.disabled = + of_property_read_bool(node, "qcom,brake-disable"); + tmp = of_property_count_u8_elems(node, "qcom,brake-pattern"); + if (tmp > BRAKE_SAMPLE_COUNT) { + dev_err(chip->dev, "more than %d brake samples\n", + BRAKE_SAMPLE_COUNT); + return -EINVAL; + } + + if (tmp > 0) { + rc = of_property_read_u8_array(node, "qcom,brake-pattern", + config->brake.samples, tmp); + if (rc < 0) { + dev_err(chip->dev, "Read brake-pattern failed, rc=%d\n", + rc); + return rc; + } + verify_brake_samples(&config->brake); + } else { + if (config->brake.mode == OL_BRAKE || + config->brake.mode == CL_BRAKE) + config->brake.disabled = true; + } + + config->is_erm = of_property_read_bool(node, "qcom,use-erm"); + if (config->is_erm) { + config->drv_wf = WF_NO_MODULATION; + config->brake.brake_wf = WF_NO_MODULATION; + } else { + rc = haptics_parse_lra_dt(chip); + if (rc < 0) { + dev_err(chip->dev, "Parse device-tree for LRA failed, rc=%d\n", + rc); + return rc; + } + } + + config->preload_effect = -EINVAL; + rc = haptics_parse_effects_dt(chip); + if (rc < 0) { + dev_err(chip->dev, "Parse device-tree for effects failed, rc=%d\n", + rc); + return rc; + } + + return 0; +} + +static int haptics_probe(struct platform_device *pdev) +{ + struct haptics_chip *chip; + struct input_dev *input_dev; + struct ff_device *ff_dev; + int rc, count; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + input_dev = devm_input_allocate_device(&pdev->dev); + if (!input_dev) + return -ENOMEM; + + chip->dev = &pdev->dev; + chip->regmap = dev_get_regmap(chip->dev->parent, NULL); + if (!chip->regmap) { + dev_err(chip->dev, "Get regmap failed\n"); + return -ENXIO; + } + + rc = haptics_parse_dt(chip); + if (rc < 0) { + dev_err(chip->dev, "Parse device-tree failed, rc = %d\n", rc); + return rc; + } + + rc = haptics_hw_init(chip); + if (rc < 0) { + dev_err(chip->dev, "Initialize HW failed, rc = %d\n", rc); + return rc; + } + + rc = devm_request_threaded_irq(chip->dev, chip->fifo_empty_irq, + NULL, fifo_empty_irq_handler, + IRQF_ONESHOT, "fifo-empty", chip); + if (rc < 0) { + dev_err(chip->dev, "request fifo-empty IRQ failed, rc=%d\n", + rc); + return rc; + } + + disable_irq_nosync(chip->fifo_empty_irq); + chip->fifo_empty_irq_en = false; + + init_completion(&chip->play.fifo_status.fifo_ready); + atomic_set(&chip->play.fifo_status.is_busy, 0); + atomic_set(&chip->play.fifo_status.written_done, 0); + INIT_WORK(&chip->fifo_work, update_fifo_work); + input_dev->name = "qcom-hv-haptics"; + input_set_drvdata(input_dev, chip); + chip->input_dev = input_dev; + + input_set_capability(input_dev, EV_FF, FF_CONSTANT); + input_set_capability(input_dev, EV_FF, FF_GAIN); + if (chip->effects_count != 0) { + input_set_capability(input_dev, EV_FF, FF_PERIODIC); + input_set_capability(input_dev, EV_FF, FF_CUSTOM); + } + + if (chip->effects_count < MAX_EFFECT_COUNT) + count = chip->effects_count + 1; + else + count = MAX_EFFECT_COUNT; + + rc = input_ff_create(input_dev, count); + if (rc < 0) { + dev_err(chip->dev, "create input FF device failed, rc=%d\n", + rc); + return rc; + } + + ff_dev = input_dev->ff; + ff_dev->upload = haptics_upload_effect; + ff_dev->playback = haptics_playback; + ff_dev->erase = haptics_erase; + ff_dev->set_gain = haptics_set_gain; + rc = input_register_device(input_dev); + if (rc < 0) { + dev_err(chip->dev, "register input device failed, rc=%d\n", + rc); + goto destroy_ff; + } + + dev_set_drvdata(chip->dev, chip); + return 0; +destroy_ff: + input_ff_destroy(chip->input_dev); + return rc; +} + +static int haptics_remove(struct platform_device *pdev) +{ + struct haptics_chip *chip = dev_get_drvdata(&pdev->dev); + + input_ff_destroy(chip->input_dev); + dev_set_drvdata(chip->dev, NULL); + + return 0; +} + +static const struct of_device_id haptics_match_table[] = { + { .compatible = "qcom,hv-haptics" }, + { .compatible = "qcom,pm8350b-haptics" }, + {}, +}; + +static struct platform_driver haptics_driver = { + .driver = { + .name = "qcom-hv-haptics", + .of_match_table = haptics_match_table, + }, + .probe = haptics_probe, + .remove = haptics_remove, +}; +module_platform_driver(haptics_driver); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. High-Voltage Haptics driver"); +MODULE_LICENSE("GPL v2");