2095 lines
51 KiB
C
2095 lines
51 KiB
C
/* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/init.h>
|
|
#include <linux/input.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pwm.h>
|
|
#include <linux/qpnp/qpnp-misc.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
enum actutor_type {
|
|
ACT_LRA,
|
|
ACT_ERM,
|
|
};
|
|
|
|
enum lra_res_sig_shape {
|
|
RES_SIG_SINE,
|
|
RES_SIG_SQUARE,
|
|
};
|
|
|
|
enum lra_auto_res_mode {
|
|
AUTO_RES_MODE_ZXD,
|
|
AUTO_RES_MODE_QWD,
|
|
};
|
|
|
|
enum wf_src {
|
|
INT_WF_VMAX,
|
|
INT_WF_BUFFER,
|
|
EXT_WF_AUDIO,
|
|
EXT_WF_PWM,
|
|
};
|
|
|
|
enum haptics_custom_effect_param {
|
|
CUSTOM_DATA_EFFECT_IDX,
|
|
CUSTOM_DATA_TIMEOUT_SEC_IDX,
|
|
CUSTOM_DATA_TIMEOUT_MSEC_IDX,
|
|
CUSTOM_DATA_LEN,
|
|
};
|
|
|
|
/* common definitions */
|
|
#define HAP_BRAKE_PATTERN_MAX 4
|
|
#define HAP_WAVEFORM_BUFFER_MAX 8
|
|
#define HAP_VMAX_MV_DEFAULT 1800
|
|
#define HAP_VMAX_MV_MAX 3596
|
|
#define HAP_PLAY_RATE_US_DEFAULT 5715
|
|
#define HAP_PLAY_RATE_US_MAX 20475
|
|
#define HAP_PLAY_RATE_US_LSB 5
|
|
#define VMAX_MIN_PLAY_TIME_US 20000
|
|
#define HAP_SC_DET_MAX_COUNT 5
|
|
#define HAP_SC_DET_TIME_US 1000000
|
|
#define FF_EFFECT_COUNT_MAX 32
|
|
#define HAP_DISABLE_DELAY_USEC 1000
|
|
|
|
/* haptics module register definitions */
|
|
#define REG_HAP_STATUS1 0x0A
|
|
#define HAP_SC_DET_BIT BIT(3)
|
|
#define HAP_BUSY_BIT BIT(1)
|
|
|
|
#define REG_HAP_EN_CTL1 0x46
|
|
#define HAP_EN_BIT BIT(7)
|
|
|
|
#define REG_HAP_EN_CTL2 0x48
|
|
#define HAP_AUTO_STANDBY_EN_BIT BIT(1)
|
|
#define HAP_BRAKE_EN_BIT BIT(0)
|
|
|
|
#define REG_HAP_EN_CTL3 0x4A
|
|
#define HAP_HBRIDGE_EN_BIT BIT(7)
|
|
#define HAP_PWM_SIGNAL_EN_BIT BIT(6)
|
|
#define HAP_ILIM_EN_BIT BIT(5)
|
|
#define HAP_ILIM_CC_EN_BIT BIT(4)
|
|
#define HAP_AUTO_RES_RBIAS_EN_BIT BIT(3)
|
|
#define HAP_DAC_EN_BIT BIT(2)
|
|
#define HAP_ZX_HYST_EN_BIT BIT(1)
|
|
#define HAP_PWM_CTL_EN_BIT BIT(0)
|
|
|
|
#define REG_HAP_AUTO_RES_CTRL 0x4B
|
|
#define HAP_AUTO_RES_EN_BIT BIT(7)
|
|
#define HAP_SEL_AUTO_RES_PERIOD BIT(6)
|
|
#define HAP_AUTO_RES_CNT_ERR_DELTA_MASK GENMASK(5, 4)
|
|
#define HAP_AUTO_RES_CNT_ERR_DELTA_SHIFT 4
|
|
#define HAP_AUTO_RES_ERR_RECOVERY_BIT BIT(3)
|
|
#define HAP_AUTO_RES_EN_DLY_MASK GENMASK(2, 0)
|
|
#define AUTO_RES_CNT_ERR_DELTA(x) (x << HAP_AUTO_RES_CNT_ERR_DELTA_SHIFT)
|
|
#define AUTO_RES_EN_DLY(x) x
|
|
|
|
#define REG_HAP_CFG1 0x4C
|
|
#define REG_HAP_CFG2 0x4D
|
|
#define HAP_LRA_RES_TYPE_BIT BIT(0)
|
|
|
|
#define REG_HAP_SEL 0x4E
|
|
#define HAP_WF_SOURCE_MASK GENMASK(5, 4)
|
|
#define HAP_WF_SOURCE_SHIFT 4
|
|
#define HAP_WF_TRIGGER_BIT BIT(0)
|
|
#define HAP_WF_SOURCE_VMAX (0 << HAP_WF_SOURCE_SHIFT)
|
|
#define HAP_WF_SOURCE_BUFFER (1 << HAP_WF_SOURCE_SHIFT)
|
|
#define HAP_WF_SOURCE_AUDIO (2 << HAP_WF_SOURCE_SHIFT)
|
|
#define HAP_WF_SOURCE_PWM (3 << HAP_WF_SOURCE_SHIFT)
|
|
|
|
#define REG_HAP_AUTO_RES_CFG 0x4F
|
|
#define HAP_AUTO_RES_MODE_BIT BIT(7)
|
|
#define HAP_AUTO_RES_MODE_SHIFT 7
|
|
#define HAP_AUTO_RES_CAL_DURATON_MASK GENMASK(6, 5)
|
|
#define HAP_CAL_EOP_EN_BIT BIT(3)
|
|
#define HAP_CAL_PERIOD_MASK GENMASK(2, 0)
|
|
#define HAP_CAL_OPT3_EVERY_8_PERIOD 2
|
|
|
|
#define REG_HAP_SLEW_CFG 0x50
|
|
#define REG_HAP_VMAX_CFG 0x51
|
|
#define HAP_VMAX_SIGN_BIT BIT(7)
|
|
#define HAP_VMAX_OVD_BIT BIT(6)
|
|
#define HAP_VMAX_MV_MASK GENMASK(5, 1)
|
|
#define HAP_VMAX_MV_SHIFT 1
|
|
#define HAP_VMAX_MV_LSB 116
|
|
|
|
#define REG_HAP_ILIM_CFG 0x52
|
|
#define HAP_ILIM_SEL_1000MA BIT(1)
|
|
#define HAP_ILIM_DEFAULT_SEL HAP_ILIM_SEL_1000MA
|
|
#define REG_HAP_SC_DEB_CFG 0x53
|
|
#define REG_HAP_RATE_CFG1 0x54
|
|
#define REG_HAP_RATE_CFG2 0x55
|
|
#define REG_HAP_INTERNAL_PWM 0x56
|
|
#define REG_HAP_EXTERNAL_PWM 0x57
|
|
#define REG_HAP_PWM 0x58
|
|
|
|
#define REG_HAP_SC_CLR 0x59
|
|
#define HAP_SC_CLR_BIT BIT(0)
|
|
|
|
#define REG_HAP_ZX_CFG 0x5A
|
|
#define HAP_ZX_DET_DEB_MASK GENMASK(2, 0)
|
|
#define ZX_DET_DEB_10US 0
|
|
#define ZX_DET_DEB_20US 1
|
|
#define ZX_DET_DEB_40US 2
|
|
#define ZX_DET_DEB_80US 3
|
|
|
|
#define REG_HAP_BRAKE 0x5C
|
|
#define HAP_BRAKE_PATTERN_MASK 0x3
|
|
#define HAP_BRAKE_PATTERN_SHIFT 2
|
|
|
|
#define REG_HAP_WF_REPEAT 0x5E
|
|
#define HAP_WF_REPEAT_MASK GENMASK(6, 4)
|
|
#define HAP_WF_REPEAT_SHIFT 4
|
|
#define HAP_WF_S_REPEAT_MASK GENMASK(1, 0)
|
|
|
|
#define REG_HAP_WF_S1 0x60
|
|
#define HAP_WF_SIGN_BIT BIT(7)
|
|
#define HAP_WF_OVD_BIT BIT(6)
|
|
#define HAP_WF_AMP_BIT GENMASK(5, 1)
|
|
#define HAP_WF_AMP_SHIFT 1
|
|
|
|
#define REG_HAP_PLAY 0x70
|
|
#define HAP_PLAY_BIT BIT(7)
|
|
|
|
#define REG_HAP_SEC_ACCESS 0xD0
|
|
#define REG_HAP_PERPH_RESET_CTL3 0xDA
|
|
|
|
struct qti_hap_effect {
|
|
int id;
|
|
u8 *pattern;
|
|
int pattern_length;
|
|
u16 play_rate_us;
|
|
u16 vmax_mv;
|
|
u8 wf_repeat_n;
|
|
u8 wf_s_repeat_n;
|
|
u8 brake[HAP_BRAKE_PATTERN_MAX];
|
|
int brake_pattern_length;
|
|
bool brake_en;
|
|
bool lra_auto_res_disable;
|
|
};
|
|
|
|
struct qti_hap_play_info {
|
|
struct qti_hap_effect *effect;
|
|
u16 vmax_mv;
|
|
int length_us;
|
|
int playing_pos;
|
|
bool playing_pattern;
|
|
};
|
|
|
|
struct qti_hap_config {
|
|
enum actutor_type act_type;
|
|
enum lra_res_sig_shape lra_shape;
|
|
enum lra_auto_res_mode lra_auto_res_mode;
|
|
enum wf_src ext_src;
|
|
u16 vmax_mv;
|
|
u16 play_rate_us;
|
|
bool lra_allow_variable_play_rate;
|
|
bool use_ext_wf_src;
|
|
};
|
|
|
|
struct qti_hap_chip {
|
|
struct platform_device *pdev;
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct input_dev *input_dev;
|
|
struct pwm_device *pwm_dev;
|
|
struct qti_hap_config config;
|
|
struct qti_hap_play_info play;
|
|
struct qti_hap_effect *predefined;
|
|
struct qti_hap_effect constant;
|
|
struct regulator *vdd_supply;
|
|
struct hrtimer stop_timer;
|
|
struct hrtimer hap_disable_timer;
|
|
struct dentry *hap_debugfs;
|
|
struct notifier_block twm_nb;
|
|
spinlock_t bus_lock;
|
|
ktime_t last_sc_time;
|
|
int play_irq;
|
|
int sc_irq;
|
|
int effects_count;
|
|
int sc_det_count;
|
|
u16 reg_base;
|
|
bool perm_disable;
|
|
bool play_irq_en;
|
|
bool vdd_enabled;
|
|
bool twm_state;
|
|
bool haptics_ext_pin_twm;
|
|
};
|
|
|
|
struct hap_addr_val {
|
|
u16 addr;
|
|
u8 value;
|
|
};
|
|
|
|
static struct hap_addr_val twm_ext_cfg[] = {
|
|
{REG_HAP_PLAY, 0x00}, /* Stop playing haptics waveform */
|
|
{REG_HAP_PERPH_RESET_CTL3, 0x0D}, /* Disable SHUTDOWN1_RB reset */
|
|
{REG_HAP_SEL, 0x01}, /* Configure for external-pin mode */
|
|
{REG_HAP_EN_CTL1, 0x80}, /* Enable haptics driver */
|
|
};
|
|
|
|
static struct hap_addr_val twm_cfg[] = {
|
|
{REG_HAP_PLAY, 0x00}, /* Stop playing haptics waveform */
|
|
{REG_HAP_SEL, 0x00}, /* Configure for cmd mode */
|
|
{REG_HAP_EN_CTL1, 0x00}, /* Enable haptics driver */
|
|
{REG_HAP_PERPH_RESET_CTL3, 0x0D}, /* Disable SHUTDOWN1_RB reset */
|
|
};
|
|
|
|
static int wf_repeat[8] = {1, 2, 4, 8, 16, 32, 64, 128};
|
|
static int wf_s_repeat[4] = {1, 2, 4, 8};
|
|
|
|
static int twm_sys_enable;
|
|
module_param_named(
|
|
haptics_twm, twm_sys_enable, int, 0600
|
|
);
|
|
|
|
static inline bool is_secure(u8 addr)
|
|
{
|
|
return ((addr & 0xFF) > 0xD0);
|
|
}
|
|
|
|
static int qti_haptics_read(struct qti_hap_chip *chip,
|
|
u8 addr, u8 *val, int len)
|
|
{
|
|
int rc = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&chip->bus_lock, flags);
|
|
|
|
rc = regmap_bulk_read(chip->regmap, chip->reg_base + addr, val, len);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Reading addr 0x%x failed, rc=%d\n",
|
|
addr, rc);
|
|
spin_unlock_irqrestore(&chip->bus_lock, flags);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_write(struct qti_hap_chip *chip,
|
|
u8 addr, u8 *val, int len)
|
|
{
|
|
int rc = 0, i;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&chip->bus_lock, flags);
|
|
if (is_secure(addr)) {
|
|
for (i = 0; i < len; i++) {
|
|
rc = regmap_write(chip->regmap,
|
|
chip->reg_base + REG_HAP_SEC_ACCESS,
|
|
0xA5);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write SEC_ACCESS failed, rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
|
|
rc = regmap_write(chip->regmap,
|
|
chip->reg_base + addr + i, val[i]);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write val 0x%x to addr 0x%x failed, rc=%d\n",
|
|
val[i], addr + i, rc);
|
|
goto unlock;
|
|
}
|
|
}
|
|
} else {
|
|
if (len > 1)
|
|
rc = regmap_bulk_write(chip->regmap,
|
|
chip->reg_base + addr, val, len);
|
|
else
|
|
rc = regmap_write(chip->regmap,
|
|
chip->reg_base + addr, *val);
|
|
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "write addr 0x%x failed, rc=%d\n",
|
|
addr, rc);
|
|
}
|
|
|
|
for (i = 0; i < len; i++)
|
|
dev_dbg(chip->dev, "Update addr 0x%x to val 0x%x\n",
|
|
addr + i, val[i]);
|
|
|
|
unlock:
|
|
spin_unlock_irqrestore(&chip->bus_lock, flags);
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_masked_write(struct qti_hap_chip *chip, u8 addr,
|
|
u8 mask, u8 val)
|
|
{
|
|
int rc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&chip->bus_lock, flags);
|
|
if (is_secure(addr)) {
|
|
rc = regmap_write(chip->regmap,
|
|
chip->reg_base + REG_HAP_SEC_ACCESS,
|
|
0xA5);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write SEC_ACCESS failed, rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
rc = regmap_update_bits(chip->regmap, chip->reg_base + addr, mask, val);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Update addr 0x%x to val 0x%x with mask 0x%x failed, rc=%d\n",
|
|
addr, val, mask, rc);
|
|
|
|
dev_dbg(chip->dev, "Update addr 0x%x to val 0x%x with mask 0x%x\n",
|
|
addr, val, mask);
|
|
unlock:
|
|
spin_unlock_irqrestore(&chip->bus_lock, flags);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void construct_constant_waveform_in_pattern(
|
|
struct qti_hap_play_info *play)
|
|
{
|
|
struct qti_hap_chip *chip = container_of(play,
|
|
struct qti_hap_chip, play);
|
|
struct qti_hap_config *config = &chip->config;
|
|
struct qti_hap_effect *effect = play->effect;
|
|
int total_samples, samples, left, magnitude, i, j, k;
|
|
int delta = INT_MAX, delta_min = INT_MAX;
|
|
|
|
/* Using play_rate_us in config for constant waveform */
|
|
effect->play_rate_us = config->play_rate_us;
|
|
total_samples = play->length_us / effect->play_rate_us;
|
|
left = play->length_us % effect->play_rate_us;
|
|
|
|
if (total_samples <= HAP_WAVEFORM_BUFFER_MAX) {
|
|
effect->pattern_length = total_samples;
|
|
effect->wf_s_repeat_n = 0;
|
|
effect->wf_repeat_n = 0;
|
|
} else {
|
|
/*
|
|
* Find a closest setting to achieve the constant waveform
|
|
* with the required length by using buffer waveform source:
|
|
* play_length_us = pattern_length * wf_s_repeat_n
|
|
* * wf_repeat_n * play_rate_us
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(wf_repeat); i++) {
|
|
for (j = 0; j < ARRAY_SIZE(wf_s_repeat); j++) {
|
|
for (k = 1; k <= HAP_WAVEFORM_BUFFER_MAX; k++) {
|
|
samples = k * wf_s_repeat[j] *
|
|
wf_repeat[i];
|
|
delta = abs(total_samples - samples);
|
|
if (delta < delta_min) {
|
|
delta_min = delta;
|
|
effect->pattern_length = k;
|
|
effect->wf_s_repeat_n = j;
|
|
effect->wf_repeat_n = i;
|
|
}
|
|
if (samples > total_samples)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (left > 0 && effect->pattern_length < HAP_WAVEFORM_BUFFER_MAX)
|
|
effect->pattern_length++;
|
|
|
|
play->length_us = effect->pattern_length * effect->play_rate_us;
|
|
dev_dbg(chip->dev, "total_samples = %d, pattern_length = %d, wf_s_repeat = %d, wf_repeat = %d\n",
|
|
total_samples, effect->pattern_length,
|
|
wf_s_repeat[effect->wf_s_repeat_n],
|
|
wf_repeat[effect->wf_repeat_n]);
|
|
|
|
for (i = 0; i < effect->pattern_length; i++) {
|
|
magnitude = play->vmax_mv / HAP_VMAX_MV_LSB;
|
|
effect->pattern[i] = (u8)magnitude << HAP_WF_AMP_SHIFT;
|
|
}
|
|
}
|
|
|
|
static int qti_haptics_config_wf_buffer(struct qti_hap_chip *chip)
|
|
{
|
|
struct qti_hap_play_info *play = &chip->play;
|
|
struct qti_hap_effect *effect = play->effect;
|
|
u8 addr, pattern[HAP_WAVEFORM_BUFFER_MAX] = {0};
|
|
int rc = 0;
|
|
size_t len;
|
|
|
|
if (play->playing_pos == effect->pattern_length) {
|
|
dev_dbg(chip->dev, "pattern playing done\n");
|
|
return 0;
|
|
}
|
|
|
|
if (effect->pattern_length - play->playing_pos
|
|
>= HAP_WAVEFORM_BUFFER_MAX)
|
|
len = HAP_WAVEFORM_BUFFER_MAX;
|
|
else
|
|
len = effect->pattern_length - play->playing_pos;
|
|
|
|
dev_dbg(chip->dev, "copy %d bytes start from %d\n",
|
|
(int)len, play->playing_pos);
|
|
memcpy(pattern, &effect->pattern[play->playing_pos], len);
|
|
|
|
play->playing_pos += len;
|
|
|
|
addr = REG_HAP_WF_S1;
|
|
rc = qti_haptics_write(chip, REG_HAP_WF_S1, pattern,
|
|
HAP_WAVEFORM_BUFFER_MAX);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Program WF_SAMPLE failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_config_wf_repeat(struct qti_hap_chip *chip)
|
|
{
|
|
struct qti_hap_effect *effect = chip->play.effect;
|
|
u8 addr, mask, val;
|
|
int rc = 0;
|
|
|
|
addr = REG_HAP_WF_REPEAT;
|
|
mask = HAP_WF_REPEAT_MASK | HAP_WF_S_REPEAT_MASK;
|
|
val = effect->wf_repeat_n << HAP_WF_REPEAT_SHIFT;
|
|
val |= effect->wf_s_repeat_n;
|
|
rc = qti_haptics_masked_write(chip, addr, mask, val);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Program WF_REPEAT failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_play(struct qti_hap_chip *chip, bool play)
|
|
{
|
|
int rc = 0;
|
|
u8 val = play ? HAP_PLAY_BIT : 0;
|
|
|
|
rc = qti_haptics_write(chip,
|
|
REG_HAP_PLAY, &val, 1);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "%s playing haptics failed, rc=%d\n",
|
|
play ? "start" : "stop", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_module_en(struct qti_hap_chip *chip, bool en)
|
|
{
|
|
int rc = 0;
|
|
u8 val = en ? HAP_EN_BIT : 0;
|
|
|
|
rc = qti_haptics_write(chip,
|
|
REG_HAP_EN_CTL1, &val, 1);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "%s haptics failed, rc=%d\n",
|
|
en ? "enable" : "disable", rc);
|
|
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_config_vmax(struct qti_hap_chip *chip, int vmax_mv)
|
|
{
|
|
u8 addr, mask, val;
|
|
int rc;
|
|
|
|
addr = REG_HAP_VMAX_CFG;
|
|
mask = HAP_VMAX_MV_MASK;
|
|
val = (vmax_mv / HAP_VMAX_MV_LSB) << HAP_VMAX_MV_SHIFT;
|
|
rc = qti_haptics_masked_write(chip, addr, mask, val);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "write VMAX_CFG failed, rc=%d\n",
|
|
rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_config_wf_src(struct qti_hap_chip *chip,
|
|
enum wf_src src)
|
|
{
|
|
u8 addr, mask, val = 0;
|
|
int rc;
|
|
|
|
addr = REG_HAP_SEL;
|
|
mask = HAP_WF_SOURCE_MASK | HAP_WF_TRIGGER_BIT;
|
|
val = src << HAP_WF_SOURCE_SHIFT;
|
|
if (src == EXT_WF_AUDIO || src == EXT_WF_PWM)
|
|
val |= HAP_WF_TRIGGER_BIT;
|
|
|
|
rc = qti_haptics_masked_write(chip, addr, mask, val);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "set HAP_SEL failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_config_play_rate_us(struct qti_hap_chip *chip,
|
|
int play_rate_us)
|
|
{
|
|
u8 addr, val[2];
|
|
int tmp, rc;
|
|
|
|
addr = REG_HAP_RATE_CFG1;
|
|
tmp = play_rate_us / HAP_PLAY_RATE_US_LSB;
|
|
val[0] = tmp & 0xff;
|
|
val[1] = (tmp >> 8) & 0xf;
|
|
rc = qti_haptics_write(chip, addr, val, 2);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "write play_rate failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_brake_enable(struct qti_hap_chip *chip, bool en)
|
|
{
|
|
u8 addr, mask, val;
|
|
int rc;
|
|
|
|
addr = REG_HAP_EN_CTL2;
|
|
mask = HAP_BRAKE_EN_BIT;
|
|
val = en ? HAP_BRAKE_EN_BIT : 0;
|
|
rc = qti_haptics_masked_write(chip, addr, mask, val);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "write BRAKE_EN failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_config_brake(struct qti_hap_chip *chip, u8 *brake)
|
|
{
|
|
u8 addr, val;
|
|
int i, rc;
|
|
|
|
addr = REG_HAP_BRAKE;
|
|
for (val = 0, i = 0; i < HAP_BRAKE_PATTERN_MAX; i++)
|
|
val |= (brake[i] & HAP_BRAKE_PATTERN_MASK) <<
|
|
i * HAP_BRAKE_PATTERN_SHIFT;
|
|
|
|
rc = qti_haptics_write(chip, addr, &val, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write brake pattern failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
/*
|
|
* Set BRAKE_EN regardless of the brake pattern, this helps to stop
|
|
* playing immediately once the valid values in WF_Sx are played.
|
|
*/
|
|
rc = qti_haptics_brake_enable(chip, true);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_lra_auto_res_enable(struct qti_hap_chip *chip, bool en)
|
|
{
|
|
int rc;
|
|
u8 addr, val, mask;
|
|
|
|
addr = REG_HAP_AUTO_RES_CTRL;
|
|
mask = HAP_AUTO_RES_EN_BIT;
|
|
val = en ? HAP_AUTO_RES_EN_BIT : 0;
|
|
rc = qti_haptics_masked_write(chip, addr, mask, val);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "set AUTO_RES_CTRL failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define HAP_CLEAR_PLAYING_RATE_US 15
|
|
static int qti_haptics_clear_settings(struct qti_hap_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 pattern[HAP_WAVEFORM_BUFFER_MAX] = {1, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
rc = qti_haptics_brake_enable(chip, false);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_lra_auto_res_enable(chip, false);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_config_play_rate_us(chip, HAP_CLEAR_PLAYING_RATE_US);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_write(chip, REG_HAP_WF_S1, pattern,
|
|
HAP_WAVEFORM_BUFFER_MAX);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_play(chip, true);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_play(chip, false);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qti_haptics_load_constant_waveform(struct qti_hap_chip *chip)
|
|
{
|
|
struct qti_hap_play_info *play = &chip->play;
|
|
struct qti_hap_config *config = &chip->config;
|
|
int rc = 0;
|
|
|
|
rc = qti_haptics_config_play_rate_us(chip, config->play_rate_us);
|
|
if (rc < 0)
|
|
return rc;
|
|
/*
|
|
* Using VMAX waveform source if playing length is >= 20ms,
|
|
* otherwise using buffer waveform source and calculate the
|
|
* pattern length and repeating times to achieve accurate
|
|
* playing time accuracy.
|
|
*/
|
|
if (play->length_us >= VMAX_MIN_PLAY_TIME_US) {
|
|
rc = qti_haptics_config_vmax(chip, play->vmax_mv);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/* Enable Auto-Resonance when VMAX wf-src is selected */
|
|
if (config->act_type == ACT_LRA) {
|
|
rc = qti_haptics_lra_auto_res_enable(chip, true);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
/* Set WF_SOURCE to VMAX */
|
|
rc = qti_haptics_config_wf_src(chip, INT_WF_VMAX);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
play->playing_pattern = false;
|
|
play->effect = NULL;
|
|
} else {
|
|
rc = qti_haptics_config_vmax(chip, config->vmax_mv);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
play->effect = &chip->constant;
|
|
play->playing_pos = 0;
|
|
/* Format and config waveform in patterns */
|
|
construct_constant_waveform_in_pattern(play);
|
|
rc = qti_haptics_config_wf_buffer(chip);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_config_wf_repeat(chip);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/* Set WF_SOURCE to buffer */
|
|
rc = qti_haptics_config_wf_src(chip, INT_WF_BUFFER);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
play->playing_pattern = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qti_haptics_load_predefined_effect(struct qti_hap_chip *chip,
|
|
int effect_idx)
|
|
{
|
|
struct qti_hap_play_info *play = &chip->play;
|
|
struct qti_hap_config *config = &chip->config;
|
|
int rc = 0;
|
|
|
|
if (effect_idx >= chip->effects_count)
|
|
return -EINVAL;
|
|
|
|
play->effect = &chip->predefined[effect_idx];
|
|
play->playing_pos = 0;
|
|
rc = qti_haptics_config_vmax(chip, play->vmax_mv);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_config_play_rate_us(chip, play->effect->play_rate_us);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (config->act_type == ACT_LRA) {
|
|
rc = qti_haptics_lra_auto_res_enable(chip,
|
|
!play->effect->lra_auto_res_disable);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
/* Set brake pattern in the effect */
|
|
rc = qti_haptics_config_brake(chip, play->effect->brake);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_config_wf_buffer(chip);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_config_wf_repeat(chip);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/* Set WF_SOURCE to buffer */
|
|
rc = qti_haptics_config_wf_src(chip, INT_WF_BUFFER);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
play->playing_pattern = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t qti_haptics_play_irq_handler(int irq, void *data)
|
|
{
|
|
struct qti_hap_chip *chip = (struct qti_hap_chip *)data;
|
|
struct qti_hap_play_info *play = &chip->play;
|
|
struct qti_hap_effect *effect = play->effect;
|
|
int rc;
|
|
|
|
dev_dbg(chip->dev, "play_irq triggered\n");
|
|
|
|
if (effect == NULL)
|
|
goto handled;
|
|
|
|
if (play->playing_pos == effect->pattern_length) {
|
|
dev_dbg(chip->dev, "waveform playing done\n");
|
|
if (chip->play_irq_en) {
|
|
disable_irq_nosync(chip->play_irq);
|
|
chip->play_irq_en = false;
|
|
}
|
|
|
|
/* Clear PLAY after all pattern bytes are queued */
|
|
qti_haptics_play(chip, false);
|
|
|
|
goto handled;
|
|
}
|
|
|
|
/* Config to play remaining patterns */
|
|
rc = qti_haptics_config_wf_repeat(chip);
|
|
if (rc < 0)
|
|
goto handled;
|
|
|
|
rc = qti_haptics_config_wf_buffer(chip);
|
|
if (rc < 0)
|
|
goto handled;
|
|
|
|
handled:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t qti_haptics_sc_irq_handler(int irq, void *data)
|
|
{
|
|
struct qti_hap_chip *chip = (struct qti_hap_chip *)data;
|
|
u8 addr, val;
|
|
ktime_t temp;
|
|
s64 sc_delta_time_us;
|
|
int rc;
|
|
|
|
dev_dbg(chip->dev, "sc_irq triggered\n");
|
|
addr = REG_HAP_STATUS1;
|
|
rc = qti_haptics_read(chip, addr, &val, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "read HAP_STATUS1 failed, rc=%d\n", rc);
|
|
goto handled;
|
|
}
|
|
|
|
if (!(val & HAP_SC_DET_BIT))
|
|
goto handled;
|
|
|
|
temp = ktime_get();
|
|
sc_delta_time_us = ktime_us_delta(temp, chip->last_sc_time);
|
|
chip->last_sc_time = temp;
|
|
|
|
if (sc_delta_time_us > HAP_SC_DET_TIME_US)
|
|
chip->sc_det_count = 0;
|
|
else
|
|
chip->sc_det_count++;
|
|
|
|
addr = REG_HAP_SC_CLR;
|
|
val = HAP_SC_CLR_BIT;
|
|
rc = qti_haptics_write(chip, addr, &val, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write SC_CLR failed, rc=%d\n", rc);
|
|
goto handled;
|
|
}
|
|
|
|
if (chip->sc_det_count > HAP_SC_DET_MAX_COUNT) {
|
|
rc = qti_haptics_module_en(chip, false);
|
|
if (rc < 0)
|
|
goto handled;
|
|
|
|
dev_crit(chip->dev, "Short circuit persists, disable haptics\n");
|
|
chip->perm_disable = true;
|
|
}
|
|
|
|
handled:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static inline void get_play_length(struct qti_hap_play_info *play,
|
|
int *length_us)
|
|
{
|
|
struct qti_hap_effect *effect = play->effect;
|
|
int tmp;
|
|
|
|
tmp = effect->pattern_length * effect->play_rate_us;
|
|
tmp *= wf_s_repeat[effect->wf_s_repeat_n];
|
|
tmp *= wf_repeat[effect->wf_repeat_n];
|
|
if (effect->brake_en)
|
|
tmp += effect->play_rate_us * effect->brake_pattern_length;
|
|
|
|
*length_us = tmp;
|
|
}
|
|
|
|
static int qti_haptics_upload_effect(struct input_dev *dev,
|
|
struct ff_effect *effect, struct ff_effect *old)
|
|
{
|
|
struct qti_hap_chip *chip = input_get_drvdata(dev);
|
|
struct qti_hap_config *config = &chip->config;
|
|
struct qti_hap_play_info *play = &chip->play;
|
|
int rc = 0, tmp, i;
|
|
s16 level, data[CUSTOM_DATA_LEN];
|
|
ktime_t rem;
|
|
s64 time_us;
|
|
|
|
if (hrtimer_active(&chip->hap_disable_timer)) {
|
|
rem = hrtimer_get_remaining(&chip->hap_disable_timer);
|
|
time_us = ktime_to_us(rem);
|
|
dev_dbg(chip->dev, "waiting for playing clear sequence: %lld us\n",
|
|
time_us);
|
|
usleep_range(time_us, time_us + 100);
|
|
}
|
|
|
|
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);
|
|
|
|
rc = qti_haptics_load_constant_waveform(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Play constant waveform 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 accept custom waveforms\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (copy_from_user(data, effect->u.periodic.custom_data,
|
|
sizeof(s16) * CUSTOM_DATA_LEN))
|
|
return -EFAULT;
|
|
|
|
for (i = 0; i < chip->effects_count; i++)
|
|
if (chip->predefined[i].id ==
|
|
data[CUSTOM_DATA_EFFECT_IDX])
|
|
break;
|
|
|
|
if (i == chip->effects_count) {
|
|
dev_err(chip->dev, "predefined effect %d is NOT supported\n",
|
|
data[0]);
|
|
return -EINVAL;
|
|
}
|
|
|
|
level = effect->u.periodic.magnitude;
|
|
tmp = level * chip->predefined[i].vmax_mv;
|
|
play->vmax_mv = tmp / 0x7fff;
|
|
|
|
dev_dbg(chip->dev, "upload effect %d, vmax_mv=%d\n",
|
|
chip->predefined[i].id, play->vmax_mv);
|
|
rc = qti_haptics_load_predefined_effect(chip, i);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Play predefined effect %d failed, rc=%d\n",
|
|
chip->predefined[i].id, rc);
|
|
return rc;
|
|
}
|
|
|
|
get_play_length(play, &play->length_us);
|
|
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;
|
|
|
|
/*
|
|
* Copy the custom data contains the play length back to
|
|
* userspace so that the userspace client can wait and
|
|
* send stop playing command after it's done.
|
|
*/
|
|
if (copy_to_user(effect->u.periodic.custom_data, data,
|
|
sizeof(s16) * CUSTOM_DATA_LEN))
|
|
return -EFAULT;
|
|
break;
|
|
|
|
default:
|
|
dev_err(chip->dev, "Unsupported effect type: %d\n",
|
|
effect->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (chip->vdd_supply && !chip->vdd_enabled) {
|
|
rc = regulator_enable(chip->vdd_supply);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Enable VDD supply failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
chip->vdd_enabled = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qti_haptics_playback(struct input_dev *dev, int effect_id, int val)
|
|
{
|
|
struct qti_hap_chip *chip = input_get_drvdata(dev);
|
|
struct qti_hap_play_info *play = &chip->play;
|
|
s64 secs;
|
|
unsigned long nsecs;
|
|
int rc = 0;
|
|
|
|
dev_dbg(chip->dev, "playback, val = %d\n", val);
|
|
if (!!val) {
|
|
rc = qti_haptics_module_en(chip, true);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = qti_haptics_play(chip, true);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (play->playing_pattern) {
|
|
if (!chip->play_irq_en) {
|
|
enable_irq(chip->play_irq);
|
|
chip->play_irq_en = true;
|
|
}
|
|
} else {
|
|
if (chip->play_irq_en) {
|
|
disable_irq_nosync(chip->play_irq);
|
|
chip->play_irq_en = false;
|
|
}
|
|
secs = play->length_us / USEC_PER_SEC;
|
|
nsecs = (play->length_us % USEC_PER_SEC) *
|
|
NSEC_PER_USEC;
|
|
hrtimer_start(&chip->stop_timer, ktime_set(secs, nsecs),
|
|
HRTIMER_MODE_REL);
|
|
}
|
|
} else {
|
|
play->length_us = 0;
|
|
rc = qti_haptics_play(chip, false);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (chip->play_irq_en) {
|
|
disable_irq_nosync(chip->play_irq);
|
|
chip->play_irq_en = false;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_erase(struct input_dev *dev, int effect_id)
|
|
{
|
|
struct qti_hap_chip *chip = input_get_drvdata(dev);
|
|
int delay_us, rc = 0;
|
|
|
|
if (chip->vdd_supply && chip->vdd_enabled) {
|
|
rc = regulator_disable(chip->vdd_supply);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Disable VDD supply failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
chip->vdd_enabled = false;
|
|
}
|
|
|
|
rc = qti_haptics_clear_settings(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "clear setting failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (chip->play.effect)
|
|
delay_us = chip->play.effect->play_rate_us;
|
|
else
|
|
delay_us = chip->config.play_rate_us;
|
|
|
|
delay_us += HAP_DISABLE_DELAY_USEC;
|
|
hrtimer_start(&chip->hap_disable_timer,
|
|
ktime_set(0, delay_us * NSEC_PER_USEC),
|
|
HRTIMER_MODE_REL);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void qti_haptics_set_gain(struct input_dev *dev, u16 gain)
|
|
{
|
|
struct qti_hap_chip *chip = input_get_drvdata(dev);
|
|
struct qti_hap_config *config = &chip->config;
|
|
struct qti_hap_play_info *play = &chip->play;
|
|
|
|
if (gain == 0)
|
|
return;
|
|
|
|
if (gain > 0x7fff)
|
|
gain = 0x7fff;
|
|
|
|
play->vmax_mv = ((u32)(gain * config->vmax_mv)) / 0x7fff;
|
|
qti_haptics_config_vmax(chip, play->vmax_mv);
|
|
}
|
|
|
|
static int qti_haptics_twm_config(struct qti_hap_chip *chip, bool ext_pin)
|
|
{
|
|
int rc = 0, i;
|
|
|
|
if (ext_pin) {
|
|
for (i = 0; i < ARRAY_SIZE(twm_ext_cfg); i++) {
|
|
rc = qti_haptics_write(chip, twm_ext_cfg[i].addr,
|
|
&twm_ext_cfg[i].value, 1);
|
|
if (rc < 0)
|
|
break;
|
|
}
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(twm_cfg); i++) {
|
|
rc = qti_haptics_write(chip, twm_cfg[i].addr,
|
|
&twm_cfg[i].value, 1);
|
|
if (rc < 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc < 0)
|
|
pr_err("Failed to write twm_config rc=%d\n", rc);
|
|
else
|
|
pr_debug("Enabled haptics for TWM mode\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qti_haptics_hw_init(struct qti_hap_chip *chip)
|
|
{
|
|
struct qti_hap_config *config = &chip->config;
|
|
u8 addr, val, mask;
|
|
int rc = 0;
|
|
|
|
/* Config actuator type */
|
|
addr = REG_HAP_CFG1;
|
|
val = config->act_type;
|
|
rc = qti_haptics_write(chip, addr, &val, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write actuator type failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Config ilim_ma */
|
|
addr = REG_HAP_ILIM_CFG;
|
|
val = HAP_ILIM_DEFAULT_SEL;
|
|
rc = qti_haptics_write(chip, addr, &val, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write ilim_ma failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Set HAP_EN_CTL3 */
|
|
addr = REG_HAP_EN_CTL3;
|
|
val = HAP_HBRIDGE_EN_BIT | HAP_PWM_SIGNAL_EN_BIT | HAP_ILIM_EN_BIT |
|
|
HAP_ILIM_CC_EN_BIT | HAP_AUTO_RES_RBIAS_EN_BIT |
|
|
HAP_DAC_EN_BIT | HAP_PWM_CTL_EN_BIT;
|
|
rc = qti_haptics_write(chip, addr, &val, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "set EN_CTL3 failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Set ZX_CFG */
|
|
addr = REG_HAP_ZX_CFG;
|
|
mask = HAP_ZX_DET_DEB_MASK;
|
|
val = ZX_DET_DEB_80US;
|
|
rc = qti_haptics_masked_write(chip, addr, mask, val);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write ZX_CFG failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Config play rate: this is the resonance period for LRA,
|
|
* or the play duration of each waveform sample for ERM.
|
|
*/
|
|
rc = qti_haptics_config_play_rate_us(chip, config->play_rate_us);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/* Set external waveform source if it's used */
|
|
if (config->use_ext_wf_src) {
|
|
rc = qti_haptics_config_wf_src(chip, config->ext_src);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Skip configurations below for ERM actuator
|
|
* as they're only for LRA actuators
|
|
*/
|
|
if (config->act_type == ACT_ERM) {
|
|
/* Disable AUTO_RES for ERM */
|
|
rc = qti_haptics_lra_auto_res_enable(chip, false);
|
|
return rc;
|
|
}
|
|
|
|
addr = REG_HAP_CFG2;
|
|
val = config->lra_shape;
|
|
rc = qti_haptics_write(chip, addr, &val, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "write lra_sig_shape failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
addr = REG_HAP_AUTO_RES_CFG;
|
|
mask = HAP_AUTO_RES_MODE_BIT | HAP_CAL_EOP_EN_BIT | HAP_CAL_PERIOD_MASK;
|
|
val = config->lra_auto_res_mode << HAP_AUTO_RES_MODE_SHIFT;
|
|
val |= HAP_CAL_EOP_EN_BIT | HAP_CAL_OPT3_EVERY_8_PERIOD;
|
|
rc = qti_haptics_masked_write(chip, addr, mask, val);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "set AUTO_RES_CFG failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
addr = REG_HAP_AUTO_RES_CTRL;
|
|
val = HAP_AUTO_RES_EN_BIT | HAP_SEL_AUTO_RES_PERIOD |
|
|
AUTO_RES_CNT_ERR_DELTA(2) | HAP_AUTO_RES_ERR_RECOVERY_BIT |
|
|
AUTO_RES_EN_DLY(4);
|
|
rc = qti_haptics_write(chip, addr, &val, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "set AUTO_RES_CTRL failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum hrtimer_restart qti_hap_stop_timer(struct hrtimer *timer)
|
|
{
|
|
struct qti_hap_chip *chip = container_of(timer, struct qti_hap_chip,
|
|
stop_timer);
|
|
int rc;
|
|
|
|
chip->play.length_us = 0;
|
|
rc = qti_haptics_play(chip, false);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Stop playing failed, rc=%d\n", rc);
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
static enum hrtimer_restart qti_hap_disable_timer(struct hrtimer *timer)
|
|
{
|
|
struct qti_hap_chip *chip = container_of(timer, struct qti_hap_chip,
|
|
hap_disable_timer);
|
|
int rc;
|
|
|
|
rc = qti_haptics_module_en(chip, false);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Disable haptics module failed, rc=%d\n",
|
|
rc);
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
static void verify_brake_setting(struct qti_hap_effect *effect)
|
|
{
|
|
int i = effect->brake_pattern_length - 1;
|
|
u8 val = 0;
|
|
|
|
for (; i >= 0; i--) {
|
|
if (effect->brake[i] != 0)
|
|
break;
|
|
|
|
effect->brake_pattern_length--;
|
|
}
|
|
|
|
for (i = 0; i < effect->brake_pattern_length; i++) {
|
|
effect->brake[i] &= HAP_BRAKE_PATTERN_MASK;
|
|
val |= effect->brake[i] << (i * HAP_BRAKE_PATTERN_SHIFT);
|
|
}
|
|
|
|
effect->brake_en = (val != 0);
|
|
}
|
|
|
|
static int twm_notifier_cb(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
struct qti_hap_chip *chip = container_of(nb,
|
|
struct qti_hap_chip, twm_nb);
|
|
|
|
if (action != PMIC_TWM_CLEAR &&
|
|
action != PMIC_TWM_ENABLE)
|
|
pr_debug("Unsupported option %lu\n", action);
|
|
else
|
|
chip->twm_state = (u8)action;
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int qti_haptics_parse_dt(struct qti_hap_chip *chip)
|
|
{
|
|
struct qti_hap_config *config = &chip->config;
|
|
const struct device_node *node = chip->dev->of_node;
|
|
struct device_node *child_node;
|
|
struct qti_hap_effect *effect;
|
|
const char *str;
|
|
int rc = 0, tmp, i = 0, j, m;
|
|
|
|
rc = of_property_read_u32(node, "reg", &tmp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Failed to reg base, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
chip->reg_base = (u16)tmp;
|
|
|
|
chip->sc_irq = platform_get_irq_byname(chip->pdev, "hap-sc-irq");
|
|
if (chip->sc_irq < 0) {
|
|
dev_err(chip->dev, "Failed to get hap-sc-irq\n");
|
|
return chip->sc_irq;
|
|
}
|
|
|
|
chip->play_irq = platform_get_irq_byname(chip->pdev, "hap-play-irq");
|
|
if (chip->play_irq < 0) {
|
|
dev_err(chip->dev, "Failed to get hap-play-irq\n");
|
|
return chip->play_irq;
|
|
}
|
|
|
|
config->act_type = ACT_LRA;
|
|
rc = of_property_read_string(node, "qcom,actuator-type", &str);
|
|
if (!rc) {
|
|
if (strcmp(str, "erm") == 0) {
|
|
config->act_type = ACT_ERM;
|
|
} else if (strcmp(str, "lra") == 0) {
|
|
config->act_type = ACT_LRA;
|
|
} else {
|
|
dev_err(chip->dev, "Invalid actuator type: %s\n",
|
|
str);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
config->vmax_mv = HAP_VMAX_MV_DEFAULT;
|
|
rc = of_property_read_u32(node, "qcom,vmax-mv", &tmp);
|
|
if (!rc)
|
|
config->vmax_mv = (tmp > HAP_VMAX_MV_MAX) ?
|
|
HAP_VMAX_MV_MAX : tmp;
|
|
|
|
config->play_rate_us = HAP_PLAY_RATE_US_DEFAULT;
|
|
rc = of_property_read_u32(node, "qcom,play-rate-us", &tmp);
|
|
if (!rc)
|
|
config->play_rate_us = (tmp >= HAP_PLAY_RATE_US_MAX) ?
|
|
HAP_PLAY_RATE_US_MAX : tmp;
|
|
|
|
chip->haptics_ext_pin_twm = of_property_read_bool(node,
|
|
"qcom,haptics-ext-pin-twm");
|
|
|
|
if (of_find_property(node, "qcom,external-waveform-source", NULL)) {
|
|
if (!of_property_read_string(node,
|
|
"qcom,external-waveform-source", &str)) {
|
|
if (strcmp(str, "audio") == 0) {
|
|
config->ext_src = EXT_WF_AUDIO;
|
|
} else if (strcmp(str, "pwm") == 0) {
|
|
config->ext_src = EXT_WF_PWM;
|
|
} else {
|
|
dev_err(chip->dev, "Invalid external waveform source: %s\n",
|
|
str);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
config->use_ext_wf_src = true;
|
|
}
|
|
|
|
if (of_find_property(node, "vdd-supply", NULL)) {
|
|
chip->vdd_supply = devm_regulator_get(chip->dev, "vdd");
|
|
if (IS_ERR(chip->vdd_supply)) {
|
|
rc = PTR_ERR(chip->vdd_supply);
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(chip->dev, "Failed to get vdd regulator");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (config->act_type == ACT_LRA) {
|
|
config->lra_shape = RES_SIG_SINE;
|
|
rc = of_property_read_string(node,
|
|
"qcom,lra-resonance-sig-shape", &str);
|
|
if (!rc) {
|
|
if (strcmp(str, "sine") == 0) {
|
|
config->lra_shape = RES_SIG_SINE;
|
|
} else if (strcmp(str, "square") == 0) {
|
|
config->lra_shape = RES_SIG_SQUARE;
|
|
} else {
|
|
dev_err(chip->dev, "Invalid resonance signal shape: %s\n",
|
|
str);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
config->lra_allow_variable_play_rate = of_property_read_bool(
|
|
node, "qcom,lra-allow-variable-play-rate");
|
|
|
|
config->lra_auto_res_mode = AUTO_RES_MODE_ZXD;
|
|
rc = of_property_read_string(node,
|
|
"qcom,lra-auto-resonance-mode", &str);
|
|
if (!rc) {
|
|
if (strcmp(str, "zxd") == 0) {
|
|
config->lra_auto_res_mode = AUTO_RES_MODE_ZXD;
|
|
} else if (strcmp(str, "qwd") == 0) {
|
|
config->lra_auto_res_mode = AUTO_RES_MODE_QWD;
|
|
} else {
|
|
dev_err(chip->dev, "Invalid auto resonance mode: %s\n",
|
|
str);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
chip->constant.pattern = devm_kcalloc(chip->dev,
|
|
HAP_WAVEFORM_BUFFER_MAX,
|
|
sizeof(u8), GFP_KERNEL);
|
|
if (!chip->constant.pattern)
|
|
return -ENOMEM;
|
|
|
|
tmp = of_get_available_child_count(node);
|
|
if (tmp == 0)
|
|
return 0;
|
|
|
|
chip->predefined = devm_kcalloc(chip->dev, tmp,
|
|
sizeof(*chip->predefined), GFP_KERNEL);
|
|
if (!chip->predefined)
|
|
return -ENOMEM;
|
|
|
|
chip->effects_count = tmp;
|
|
|
|
for_each_available_child_of_node(node, child_node) {
|
|
effect = &chip->predefined[i++];
|
|
rc = of_property_read_u32(child_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(child_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 > HAP_VMAX_MV_MAX) ?
|
|
HAP_VMAX_MV_MAX : tmp;
|
|
|
|
rc = of_property_count_elems_of_size(child_node,
|
|
"qcom,wf-pattern", sizeof(u8));
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Count qcom,wf-pattern property failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
} else if (rc == 0) {
|
|
dev_dbg(chip->dev, "qcom,wf-pattern has no data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
effect->pattern_length = rc;
|
|
effect->pattern = devm_kcalloc(chip->dev,
|
|
effect->pattern_length, sizeof(u8), GFP_KERNEL);
|
|
if (!effect->pattern)
|
|
return -ENOMEM;
|
|
|
|
rc = of_property_read_u8_array(child_node, "qcom,wf-pattern",
|
|
effect->pattern, effect->pattern_length);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Read qcom,wf-pattern property failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
effect->play_rate_us = config->play_rate_us;
|
|
rc = of_property_read_u32(child_node, "qcom,wf-play-rate-us",
|
|
&tmp);
|
|
if (rc < 0)
|
|
dev_dbg(chip->dev, "Read qcom,wf-play-rate-us failed, rc=%d\n",
|
|
rc);
|
|
else
|
|
effect->play_rate_us = tmp;
|
|
|
|
if (config->act_type == ACT_LRA &&
|
|
!config->lra_allow_variable_play_rate &&
|
|
config->play_rate_us != effect->play_rate_us) {
|
|
dev_warn(chip->dev, "play rate should match with LRA resonance frequency\n");
|
|
effect->play_rate_us = config->play_rate_us;
|
|
}
|
|
|
|
rc = of_property_read_u32(child_node, "qcom,wf-repeat-count",
|
|
&tmp);
|
|
if (rc < 0) {
|
|
dev_dbg(chip->dev, "Read qcom,wf-repeat-count failed, rc=%d\n",
|
|
rc);
|
|
} else {
|
|
for (j = 0; j < ARRAY_SIZE(wf_repeat); j++)
|
|
if (tmp <= wf_repeat[j])
|
|
break;
|
|
|
|
effect->wf_repeat_n = j;
|
|
}
|
|
|
|
rc = of_property_read_u32(child_node, "qcom,wf-s-repeat-count",
|
|
&tmp);
|
|
if (rc < 0) {
|
|
dev_dbg(chip->dev, "Read qcom,wf-s-repeat-count failed, rc=%d\n",
|
|
rc);
|
|
} else {
|
|
for (j = 0; j < ARRAY_SIZE(wf_s_repeat); j++)
|
|
if (tmp <= wf_s_repeat[j])
|
|
break;
|
|
|
|
effect->wf_s_repeat_n = j;
|
|
}
|
|
|
|
effect->lra_auto_res_disable = of_property_read_bool(child_node,
|
|
"qcom,lra-auto-resonance-disable");
|
|
|
|
tmp = of_property_count_elems_of_size(child_node,
|
|
"qcom,wf-brake-pattern", sizeof(u8));
|
|
if (tmp <= 0)
|
|
continue;
|
|
|
|
if (tmp > HAP_BRAKE_PATTERN_MAX) {
|
|
dev_err(chip->dev, "wf-brake-pattern shouldn't be more than %d bytes\n",
|
|
HAP_BRAKE_PATTERN_MAX);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_read_u8_array(child_node,
|
|
"qcom,wf-brake-pattern", effect->brake, tmp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Failed to get wf-brake-pattern, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
effect->brake_pattern_length = tmp;
|
|
verify_brake_setting(effect);
|
|
}
|
|
|
|
for (j = 0; j < i; j++) {
|
|
dev_dbg(chip->dev, "effect: %d\n", chip->predefined[j].id);
|
|
dev_dbg(chip->dev, " vmax: %d mv\n",
|
|
chip->predefined[j].vmax_mv);
|
|
dev_dbg(chip->dev, " play_rate: %d us\n",
|
|
chip->predefined[j].play_rate_us);
|
|
for (m = 0; m < chip->predefined[j].pattern_length; m++)
|
|
dev_dbg(chip->dev, " pattern[%d]: 0x%x\n",
|
|
m, chip->predefined[j].pattern[m]);
|
|
for (m = 0; m < chip->predefined[j].brake_pattern_length; m++)
|
|
dev_dbg(chip->dev, " brake_pattern[%d]: 0x%x\n",
|
|
m, chip->predefined[j].brake[m]);
|
|
dev_dbg(chip->dev, " brake_en: %d\n",
|
|
chip->predefined[j].brake_en);
|
|
dev_dbg(chip->dev, " wf_repeat_n: %d\n",
|
|
chip->predefined[j].wf_repeat_n);
|
|
dev_dbg(chip->dev, " wf_s_repeat_n: %d\n",
|
|
chip->predefined[j].wf_s_repeat_n);
|
|
dev_dbg(chip->dev, " lra_auto_res_disable: %d\n",
|
|
chip->predefined[j].lra_auto_res_disable);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int play_rate_dbgfs_read(void *data, u64 *val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
|
|
*val = effect->play_rate_us;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int play_rate_dbgfs_write(void *data, u64 val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
|
|
if (val > HAP_PLAY_RATE_US_MAX)
|
|
val = HAP_PLAY_RATE_US_MAX;
|
|
|
|
effect->play_rate_us = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vmax_dbgfs_read(void *data, u64 *val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
|
|
*val = effect->vmax_mv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vmax_dbgfs_write(void *data, u64 val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
|
|
if (val > HAP_VMAX_MV_MAX)
|
|
val = HAP_VMAX_MV_MAX;
|
|
|
|
effect->vmax_mv = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wf_repeat_n_dbgfs_read(void *data, u64 *val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
|
|
*val = wf_repeat[effect->wf_repeat_n];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wf_repeat_n_dbgfs_write(void *data, u64 val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(wf_repeat); i++)
|
|
if (val == wf_repeat[i])
|
|
break;
|
|
|
|
if (i == ARRAY_SIZE(wf_repeat))
|
|
pr_err("wf_repeat value %llu is invalid\n", val);
|
|
else
|
|
effect->wf_repeat_n = i;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wf_s_repeat_n_dbgfs_read(void *data, u64 *val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
|
|
*val = wf_s_repeat[effect->wf_s_repeat_n];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wf_s_repeat_n_dbgfs_write(void *data, u64 val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(wf_s_repeat); i++)
|
|
if (val == wf_s_repeat[i])
|
|
break;
|
|
|
|
if (i == ARRAY_SIZE(wf_s_repeat))
|
|
pr_err("wf_s_repeat value %llu is invalid\n", val);
|
|
else
|
|
effect->wf_s_repeat_n = i;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int auto_res_dbgfs_read(void *data, u64 *val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
|
|
*val = !effect->lra_auto_res_disable;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int auto_res_dbgfs_write(void *data, u64 val)
|
|
{
|
|
struct qti_hap_effect *effect = (struct qti_hap_effect *)data;
|
|
|
|
effect->lra_auto_res_disable = !val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(play_rate_debugfs_ops, play_rate_dbgfs_read,
|
|
play_rate_dbgfs_write, "%llu\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(vmax_debugfs_ops, vmax_dbgfs_read,
|
|
vmax_dbgfs_write, "%llu\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(wf_repeat_n_debugfs_ops, wf_repeat_n_dbgfs_read,
|
|
wf_repeat_n_dbgfs_write, "%llu\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(wf_s_repeat_n_debugfs_ops, wf_s_repeat_n_dbgfs_read,
|
|
wf_s_repeat_n_dbgfs_write, "%llu\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(auto_res_debugfs_ops, auto_res_dbgfs_read,
|
|
auto_res_dbgfs_write, "%llu\n");
|
|
|
|
#define CHAR_PER_PATTERN 8
|
|
static ssize_t brake_pattern_dbgfs_read(struct file *filep,
|
|
char __user *buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct qti_hap_effect *effect =
|
|
(struct qti_hap_effect *)filep->private_data;
|
|
char *kbuf, *tmp;
|
|
int rc, length, i, len;
|
|
|
|
kbuf = kcalloc(CHAR_PER_PATTERN, HAP_BRAKE_PATTERN_MAX, GFP_KERNEL);
|
|
if (!kbuf)
|
|
return -ENOMEM;
|
|
|
|
tmp = kbuf;
|
|
for (length = 0, i = 0; i < HAP_BRAKE_PATTERN_MAX; i++) {
|
|
len = snprintf(tmp, CHAR_PER_PATTERN, "0x%x ",
|
|
effect->brake[i]);
|
|
tmp += len;
|
|
length += len;
|
|
}
|
|
|
|
kbuf[length++] = '\n';
|
|
kbuf[length++] = '\0';
|
|
|
|
rc = simple_read_from_buffer(buf, count, ppos, kbuf, length);
|
|
|
|
kfree(kbuf);
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t brake_pattern_dbgfs_write(struct file *filep,
|
|
const char __user *buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct qti_hap_effect *effect =
|
|
(struct qti_hap_effect *)filep->private_data;
|
|
char *kbuf, *token;
|
|
int rc = 0, i = 0, j;
|
|
u32 val;
|
|
|
|
kbuf = kmalloc(count + 1, GFP_KERNEL);
|
|
if (!kbuf)
|
|
return -ENOMEM;
|
|
|
|
rc = copy_from_user(kbuf, buf, count);
|
|
if (rc > 0) {
|
|
rc = -EFAULT;
|
|
goto err;
|
|
}
|
|
|
|
kbuf[count] = '\0';
|
|
*ppos += count;
|
|
|
|
while ((token = strsep(&kbuf, " ")) != NULL) {
|
|
rc = kstrtouint(token, 0, &val);
|
|
if (rc < 0) {
|
|
rc = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
effect->brake[i++] = val & HAP_BRAKE_PATTERN_MASK;
|
|
|
|
if (i >= HAP_BRAKE_PATTERN_MAX)
|
|
break;
|
|
}
|
|
|
|
for (j = i; j < HAP_BRAKE_PATTERN_MAX; j++)
|
|
effect->brake[j] = 0;
|
|
|
|
effect->brake_pattern_length = i;
|
|
verify_brake_setting(effect);
|
|
|
|
rc = count;
|
|
err:
|
|
kfree(kbuf);
|
|
return rc;
|
|
}
|
|
|
|
static const struct file_operations brake_pattern_dbgfs_ops = {
|
|
.read = brake_pattern_dbgfs_read,
|
|
.write = brake_pattern_dbgfs_write,
|
|
.owner = THIS_MODULE,
|
|
.open = simple_open,
|
|
};
|
|
|
|
static ssize_t pattern_dbgfs_read(struct file *filep,
|
|
char __user *buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct qti_hap_effect *effect =
|
|
(struct qti_hap_effect *)filep->private_data;
|
|
char *kbuf, *tmp;
|
|
int rc, length, i, len;
|
|
|
|
kbuf = kcalloc(CHAR_PER_PATTERN, effect->pattern_length, GFP_KERNEL);
|
|
if (!kbuf)
|
|
return -ENOMEM;
|
|
|
|
tmp = kbuf;
|
|
for (length = 0, i = 0; i < effect->pattern_length; i++) {
|
|
len = snprintf(tmp, CHAR_PER_PATTERN, "0x%x ",
|
|
effect->pattern[i]);
|
|
tmp += len;
|
|
length += len;
|
|
}
|
|
|
|
kbuf[length++] = '\n';
|
|
kbuf[length++] = '\0';
|
|
|
|
rc = simple_read_from_buffer(buf, count, ppos, kbuf, length);
|
|
|
|
kfree(kbuf);
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t pattern_dbgfs_write(struct file *filep,
|
|
const char __user *buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct qti_hap_effect *effect =
|
|
(struct qti_hap_effect *)filep->private_data;
|
|
char *kbuf, *token;
|
|
int rc = 0, i = 0, j;
|
|
u32 val;
|
|
|
|
kbuf = kmalloc(count + 1, GFP_KERNEL);
|
|
if (!kbuf)
|
|
return -ENOMEM;
|
|
|
|
rc = copy_from_user(kbuf, buf, count);
|
|
if (rc > 0) {
|
|
rc = -EFAULT;
|
|
goto err;
|
|
}
|
|
|
|
kbuf[count] = '\0';
|
|
*ppos += count;
|
|
|
|
while ((token = strsep(&kbuf, " ")) != NULL) {
|
|
rc = kstrtouint(token, 0, &val);
|
|
if (rc < 0) {
|
|
rc = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
effect->pattern[i++] = val & 0xff;
|
|
|
|
if (i >= effect->pattern_length)
|
|
break;
|
|
}
|
|
|
|
for (j = i; j < effect->pattern_length; j++)
|
|
effect->pattern[j] = 0;
|
|
|
|
rc = count;
|
|
err:
|
|
kfree(kbuf);
|
|
return rc;
|
|
}
|
|
|
|
static const struct file_operations pattern_dbgfs_ops = {
|
|
.read = pattern_dbgfs_read,
|
|
.write = pattern_dbgfs_write,
|
|
.owner = THIS_MODULE,
|
|
.open = simple_open,
|
|
};
|
|
|
|
static int create_effect_debug_files(struct qti_hap_effect *effect,
|
|
struct dentry *dir)
|
|
{
|
|
struct dentry *file;
|
|
|
|
file = debugfs_create_file("play_rate_us", 0644, dir,
|
|
effect, &play_rate_debugfs_ops);
|
|
if (!file) {
|
|
pr_err("create play-rate debugfs node failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file = debugfs_create_file("vmax_mv", 0644, dir,
|
|
effect, &vmax_debugfs_ops);
|
|
if (!file) {
|
|
pr_err("create vmax debugfs node failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file = debugfs_create_file("wf_repeat_n", 0644, dir,
|
|
effect, &wf_repeat_n_debugfs_ops);
|
|
if (!file) {
|
|
pr_err("create wf-repeat debugfs node failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file = debugfs_create_file("wf_s_repeat_n", 0644, dir,
|
|
effect, &wf_s_repeat_n_debugfs_ops);
|
|
if (!file) {
|
|
pr_err("create wf-s-repeat debugfs node failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file = debugfs_create_file("lra_auto_res_en", 0644, dir,
|
|
effect, &auto_res_debugfs_ops);
|
|
if (!file) {
|
|
pr_err("create lra-auto-res-en debugfs node failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file = debugfs_create_file("brake", 0644, dir,
|
|
effect, &brake_pattern_dbgfs_ops);
|
|
if (!file) {
|
|
pr_err("create brake debugfs node failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file = debugfs_create_file("pattern", 0644, dir,
|
|
effect, &pattern_dbgfs_ops);
|
|
if (!file) {
|
|
pr_err("create pattern debugfs node failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qti_haptics_add_debugfs(struct qti_hap_chip *chip)
|
|
{
|
|
struct dentry *hap_dir, *effect_dir;
|
|
char str[12] = {0};
|
|
int i, rc = 0;
|
|
|
|
hap_dir = debugfs_create_dir("haptics", NULL);
|
|
if (!hap_dir) {
|
|
pr_err("create haptics debugfs directory failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < chip->effects_count; i++) {
|
|
snprintf(str, ARRAY_SIZE(str), "effect%d", i);
|
|
effect_dir = debugfs_create_dir(str, hap_dir);
|
|
if (!effect_dir) {
|
|
pr_err("create %s debugfs directory failed\n", str);
|
|
rc = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = create_effect_debug_files(&chip->predefined[i],
|
|
effect_dir);
|
|
if (rc < 0) {
|
|
rc = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
chip->hap_debugfs = hap_dir;
|
|
return 0;
|
|
|
|
cleanup:
|
|
debugfs_remove_recursive(hap_dir);
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
static int qti_haptics_probe(struct platform_device *pdev)
|
|
{
|
|
struct qti_hap_chip *chip;
|
|
struct input_dev *input_dev;
|
|
struct ff_device *ff;
|
|
int rc = 0, effect_count_max;
|
|
|
|
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->pdev = pdev;
|
|
chip->dev = &pdev->dev;
|
|
chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
|
|
if (!chip->regmap) {
|
|
dev_err(chip->dev, "Failed to get regmap handle\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
rc = qti_haptics_parse_dt(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "parse device-tree failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
spin_lock_init(&chip->bus_lock);
|
|
|
|
rc = qti_haptics_hw_init(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "parse device-tree failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = devm_request_threaded_irq(chip->dev, chip->play_irq, NULL,
|
|
qti_haptics_play_irq_handler,
|
|
IRQF_ONESHOT, "hap_play_irq", chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "request play-irq failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
disable_irq(chip->play_irq);
|
|
chip->play_irq_en = false;
|
|
|
|
rc = devm_request_threaded_irq(chip->dev, chip->sc_irq, NULL,
|
|
qti_haptics_sc_irq_handler,
|
|
IRQF_ONESHOT, "hap_sc_irq", chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "request sc-irq failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->twm_nb.notifier_call = twm_notifier_cb;
|
|
rc = qpnp_misc_twm_notifier_register(&chip->twm_nb);
|
|
if (rc < 0)
|
|
pr_err("Failed to register twm_notifier_cb rc=%d\n", rc);
|
|
|
|
hrtimer_init(&chip->stop_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
chip->stop_timer.function = qti_hap_stop_timer;
|
|
hrtimer_init(&chip->hap_disable_timer, CLOCK_MONOTONIC,
|
|
HRTIMER_MODE_REL);
|
|
chip->hap_disable_timer.function = qti_hap_disable_timer;
|
|
input_dev->name = "qti-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 + 1 > FF_EFFECT_COUNT_MAX)
|
|
effect_count_max = chip->effects_count + 1;
|
|
else
|
|
effect_count_max = FF_EFFECT_COUNT_MAX;
|
|
rc = input_ff_create(input_dev, effect_count_max);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "create FF input device failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
ff = input_dev->ff;
|
|
ff->upload = qti_haptics_upload_effect;
|
|
ff->playback = qti_haptics_playback;
|
|
ff->erase = qti_haptics_erase;
|
|
ff->set_gain = qti_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);
|
|
#ifdef CONFIG_DEBUG_FS
|
|
rc = qti_haptics_add_debugfs(chip);
|
|
if (rc < 0)
|
|
dev_dbg(chip->dev, "create debugfs failed, rc=%d\n", rc);
|
|
#endif
|
|
return 0;
|
|
|
|
destroy_ff:
|
|
input_ff_destroy(chip->input_dev);
|
|
qpnp_misc_twm_notifier_unregister(&chip->twm_nb);
|
|
return rc;
|
|
}
|
|
|
|
static int qti_haptics_remove(struct platform_device *pdev)
|
|
{
|
|
struct qti_hap_chip *chip = dev_get_drvdata(&pdev->dev);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
debugfs_remove_recursive(chip->hap_debugfs);
|
|
#endif
|
|
input_ff_destroy(chip->input_dev);
|
|
qpnp_misc_twm_notifier_unregister(&chip->twm_nb);
|
|
dev_set_drvdata(chip->dev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void qti_haptics_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct qti_hap_chip *chip = dev_get_drvdata(&pdev->dev);
|
|
int rc;
|
|
|
|
dev_dbg(chip->dev, "Shutdown!\n");
|
|
|
|
qti_haptics_module_en(chip, false);
|
|
|
|
if (chip->vdd_supply && chip->vdd_enabled) {
|
|
rc = regulator_disable(chip->vdd_supply);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Disable VDD supply failed, rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
chip->vdd_enabled = false;
|
|
}
|
|
|
|
if (chip->twm_state == PMIC_TWM_ENABLE && twm_sys_enable) {
|
|
rc = qti_haptics_twm_config(chip, chip->haptics_ext_pin_twm);
|
|
if (rc < 0)
|
|
pr_err("Haptics TWM config failed rc=%d\n", rc);
|
|
}
|
|
}
|
|
|
|
static const struct of_device_id haptics_match_table[] = {
|
|
{ .compatible = "qcom,haptics" },
|
|
{ .compatible = "qcom,pm660-haptics" },
|
|
{ .compatible = "qcom,pm8150b-haptics" },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver qti_haptics_driver = {
|
|
.driver = {
|
|
.name = "qcom,haptics",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = haptics_match_table,
|
|
},
|
|
.probe = qti_haptics_probe,
|
|
.remove = qti_haptics_remove,
|
|
.shutdown = qti_haptics_shutdown,
|
|
};
|
|
module_platform_driver(qti_haptics_driver);
|
|
|
|
MODULE_DESCRIPTION("QTI haptics driver");
|
|
MODULE_LICENSE("GPL v2");
|