summaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/qpnp-haptic.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/qcom/qpnp-haptic.c')
-rw-r--r--drivers/soc/qcom/qpnp-haptic.c3124
1 files changed, 3124 insertions, 0 deletions
diff --git a/drivers/soc/qcom/qpnp-haptic.c b/drivers/soc/qcom/qpnp-haptic.c
new file mode 100644
index 000000000000..38cc86963181
--- /dev/null
+++ b/drivers/soc/qcom/qpnp-haptic.c
@@ -0,0 +1,3124 @@
+/* Copyright (c) 2014-2015, 2017, 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.
+ */
+
+#define pr_fmt(fmt) "haptic: %s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/hrtimer.h>
+#include <linux/of_device.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/qpnp/pwm.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/log2.h>
+#include <linux/qpnp-misc.h>
+#include <linux/qpnp/qpnp-haptic.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include "../../staging/android/timed_output.h"
+
+#define QPNP_HAP_STATUS(b) (b + 0x0A)
+#define QPNP_HAP_LRA_AUTO_RES_LO(b) (b + 0x0B)
+#define QPNP_HAP_LRA_AUTO_RES_HI(b) (b + 0x0C)
+#define QPNP_HAP_EN_CTL_REG(b) (b + 0x46)
+#define QPNP_HAP_EN_CTL2_REG(b) (b + 0x48)
+#define QPNP_HAP_AUTO_RES_CTRL(b) (b + 0x4B)
+#define QPNP_HAP_CFG1_REG(b) (b + 0x4C)
+#define QPNP_HAP_CFG2_REG(b) (b + 0x4D)
+#define QPNP_HAP_SEL_REG(b) (b + 0x4E)
+#define QPNP_HAP_LRA_AUTO_RES_REG(b) (b + 0x4F)
+#define QPNP_HAP_VMAX_REG(b) (b + 0x51)
+#define QPNP_HAP_ILIM_REG(b) (b + 0x52)
+#define QPNP_HAP_SC_DEB_REG(b) (b + 0x53)
+#define QPNP_HAP_RATE_CFG1_REG(b) (b + 0x54)
+#define QPNP_HAP_RATE_CFG2_REG(b) (b + 0x55)
+#define QPNP_HAP_INT_PWM_REG(b) (b + 0x56)
+#define QPNP_HAP_EXT_PWM_REG(b) (b + 0x57)
+#define QPNP_HAP_PWM_CAP_REG(b) (b + 0x58)
+#define QPNP_HAP_SC_CLR_REG(b) (b + 0x59)
+#define QPNP_HAP_SC_IRQ_STATUS_DELAY msecs_to_jiffies(1000)
+#define QPNP_HAP_BRAKE_REG(b) (b + 0x5C)
+#define QPNP_HAP_WAV_REP_REG(b) (b + 0x5E)
+#define QPNP_HAP_WAV_S_REG_BASE(b) (b + 0x60)
+#define QPNP_HAP_PLAY_REG(b) (b + 0x70)
+#define QPNP_HAP_SEC_ACCESS_REG(b) (b + 0xD0)
+#define QPNP_HAP_TEST2_REG(b) (b + 0xE3)
+
+#define QPNP_HAP_STATUS_BUSY 0x02
+#define QPNP_HAP_ACT_TYPE_MASK BIT(0)
+#define QPNP_HAP_LRA 0x0
+#define QPNP_HAP_ERM 0x1
+#define QPNP_HAP_PM660_HW_AUTO_RES_MODE_BIT BIT(3)
+#define QPNP_HAP_AUTO_RES_MODE_MASK GENMASK(6, 4)
+#define QPNP_HAP_AUTO_RES_MODE_SHIFT 4
+#define QPNP_HAP_PM660_AUTO_RES_MODE_BIT BIT(7)
+#define QPNP_HAP_PM660_AUTO_RES_MODE_SHIFT 7
+#define QPNP_HAP_PM660_CALIBRATE_DURATION_MASK GENMASK(6, 5)
+#define QPNP_HAP_PM660_CALIBRATE_DURATION_SHIFT 5
+#define QPNP_HAP_PM660_QWD_DRIVE_DURATION_BIT BIT(4)
+#define QPNP_HAP_PM660_QWD_DRIVE_DURATION_SHIFT 4
+#define QPNP_HAP_PM660_CALIBRATE_AT_EOP_BIT BIT(3)
+#define QPNP_HAP_PM660_CALIBRATE_AT_EOP_SHIFT 3
+#define QPNP_HAP_PM660_LRA_ZXD_CAL_PERIOD_BIT GENMASK(2, 0)
+#define QPNP_HAP_LRA_HIGH_Z_MASK GENMASK(3, 2)
+#define QPNP_HAP_LRA_HIGH_Z_SHIFT 2
+#define QPNP_HAP_LRA_RES_CAL_PER_MASK GENMASK(1, 0)
+#define QPNP_HAP_PM660_LRA_RES_CAL_PER_MASK GENMASK(2, 0)
+#define QPNP_HAP_RES_CAL_PERIOD_MIN 4
+#define QPNP_HAP_RES_CAL_PERIOD_MAX 32
+#define QPNP_HAP_PM660_RES_CAL_PERIOD_MAX 256
+#define QPNP_HAP_WF_SOURCE_MASK GENMASK(5, 4)
+#define QPNP_HAP_WF_SOURCE_SHIFT 4
+#define QPNP_HAP_VMAX_OVD_BIT BIT(6)
+#define QPNP_HAP_VMAX_MASK GENMASK(5, 1)
+#define QPNP_HAP_VMAX_SHIFT 1
+#define QPNP_HAP_VMAX_MIN_MV 116
+#define QPNP_HAP_VMAX_MAX_MV 3596
+#define QPNP_HAP_ILIM_MASK BIT(0)
+#define QPNP_HAP_ILIM_MIN_MV 400
+#define QPNP_HAP_ILIM_MAX_MV 800
+#define QPNP_HAP_SC_DEB_MASK GENMASK(2, 0)
+#define QPNP_HAP_SC_DEB_CYCLES_MIN 0
+#define QPNP_HAP_DEF_SC_DEB_CYCLES 8
+#define QPNP_HAP_SC_DEB_CYCLES_MAX 32
+#define QPNP_HAP_SC_CLR 1
+#define QPNP_HAP_INT_PWM_MASK GENMASK(1, 0)
+#define QPNP_HAP_INT_PWM_FREQ_253_KHZ 253
+#define QPNP_HAP_INT_PWM_FREQ_505_KHZ 505
+#define QPNP_HAP_INT_PWM_FREQ_739_KHZ 739
+#define QPNP_HAP_INT_PWM_FREQ_1076_KHZ 1076
+#define QPNP_HAP_WAV_SHAPE_MASK BIT(0)
+#define QPNP_HAP_RATE_CFG1_MASK 0xFF
+#define QPNP_HAP_RATE_CFG2_MASK 0xF0
+#define QPNP_HAP_RATE_CFG2_SHFT 8
+#define QPNP_HAP_RATE_CFG_STEP_US 5
+#define QPNP_HAP_WAV_PLAY_RATE_US_MIN 0
+#define QPNP_HAP_DEF_WAVE_PLAY_RATE_US 5715
+#define QPNP_HAP_WAV_PLAY_RATE_US_MAX 20475
+#define QPNP_HAP_WAV_REP_MASK GENMASK(6, 4)
+#define QPNP_HAP_WAV_S_REP_MASK GENMASK(1, 0)
+#define QPNP_HAP_WAV_REP_SHIFT 4
+#define QPNP_HAP_WAV_REP_MIN 1
+#define QPNP_HAP_WAV_REP_MAX 128
+#define QPNP_HAP_WAV_S_REP_MIN 1
+#define QPNP_HAP_WAV_S_REP_MAX 8
+#define QPNP_HAP_WF_AMP_MASK GENMASK(5, 1)
+#define QPNP_HAP_WF_OVD_BIT BIT(6)
+#define QPNP_HAP_BRAKE_PAT_MASK 0x3
+#define QPNP_HAP_ILIM_MIN_MA 400
+#define QPNP_HAP_ILIM_MAX_MA 800
+#define QPNP_HAP_EXT_PWM_MASK GENMASK(1, 0)
+#define QPNP_HAP_EXT_PWM_FREQ_25_KHZ 25
+#define QPNP_HAP_EXT_PWM_FREQ_50_KHZ 50
+#define QPNP_HAP_EXT_PWM_FREQ_75_KHZ 75
+#define QPNP_HAP_EXT_PWM_FREQ_100_KHZ 100
+#define PWM_MAX_DTEST_LINES 4
+#define QPNP_HAP_EXT_PWM_DTEST_MASK GENMASK(6, 4)
+#define QPNP_HAP_EXT_PWM_DTEST_SHFT 4
+#define QPNP_HAP_EXT_PWM_PEAK_DATA 0x7F
+#define QPNP_HAP_EXT_PWM_HALF_DUTY 50
+#define QPNP_HAP_EXT_PWM_FULL_DUTY 100
+#define QPNP_HAP_EXT_PWM_DATA_FACTOR 39
+#define QPNP_HAP_WAV_SINE 0
+#define QPNP_HAP_WAV_SQUARE 1
+#define QPNP_HAP_WAV_SAMP_LEN 8
+#define QPNP_HAP_WAV_SAMP_MAX 0x3E
+#define QPNP_HAP_BRAKE_PAT_LEN 4
+#define QPNP_HAP_PLAY_EN_BIT BIT(7)
+#define QPNP_HAP_EN_BIT BIT(7)
+#define QPNP_HAP_BRAKE_MASK BIT(0)
+#define QPNP_HAP_AUTO_RES_MASK BIT(7)
+#define AUTO_RES_ENABLE BIT(7)
+#define AUTO_RES_ERR_BIT 0x10
+#define SC_FOUND_BIT 0x08
+#define SC_MAX_COUNT 5
+
+#define QPNP_HAP_TIMEOUT_MS_MAX 15000
+#define QPNP_HAP_STR_SIZE 20
+#define QPNP_HAP_MAX_RETRIES 5
+#define QPNP_TEST_TIMER_MS 5
+
+#define QPNP_HAP_TIME_REQ_FOR_BACK_EMF_GEN 20000
+#define POLL_TIME_AUTO_RES_ERR_NS (20 * NSEC_PER_MSEC)
+
+#define MAX_POSITIVE_VARIATION_LRA_FREQ 30
+#define MAX_NEGATIVE_VARIATION_LRA_FREQ -30
+#define FREQ_VARIATION_STEP 5
+#define AUTO_RES_ERROR_CAPTURE_RES 5
+#define AUTO_RES_ERROR_MAX 30
+#define ADJUSTED_LRA_PLAY_RATE_CODE_ARRSIZE \
+ ((MAX_POSITIVE_VARIATION_LRA_FREQ - MAX_NEGATIVE_VARIATION_LRA_FREQ) \
+ / FREQ_VARIATION_STEP)
+#define LRA_DRIVE_PERIOD_POS_ERR(hap, rc_clk_err_percent) \
+ (hap->init_drive_period_code = (hap->init_drive_period_code * \
+ (1000 + rc_clk_err_percent_x10)) / 1000)
+#define LRA_DRIVE_PERIOD_NEG_ERR(hap, rc_clk_err_percent) \
+ (hap->init_drive_period_code = (hap->init_drive_period_code * \
+ (1000 - rc_clk_err_percent_x10)) / 1000)
+
+u32 adjusted_lra_play_rate_code[ADJUSTED_LRA_PLAY_RATE_CODE_ARRSIZE];
+
+/* haptic debug register set */
+static u8 qpnp_hap_dbg_regs[] = {
+ 0x0a, 0x0b, 0x0c, 0x46, 0x48, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
+ 0x54, 0x55, 0x56, 0x57, 0x58, 0x5c, 0x5e, 0x60, 0x61, 0x62, 0x63, 0x64,
+ 0x65, 0x66, 0x67, 0x70, 0xE3,
+};
+
+/* ramp up/down test sequence */
+static u8 qpnp_hap_ramp_test_data[] = {
+ 0x0, 0x19, 0x32, 0x4C, 0x65, 0x7F, 0x65, 0x4C, 0x32, 0x19,
+ 0x0, 0x99, 0xB2, 0xCC, 0xE5, 0xFF, 0xE5, 0xCC, 0xB2, 0x99,
+ 0x0, 0x19, 0x32, 0x4C, 0x65, 0x7F, 0x65, 0x4C, 0x32, 0x19,
+ 0x0, 0x99, 0xB2, 0xCC, 0xE5, 0xFF, 0xE5, 0xCC, 0xB2, 0x99,
+ 0x0, 0x19, 0x32, 0x4C, 0x65, 0x7F, 0x65, 0x4C, 0x32, 0x19,
+ 0x0, 0x99, 0xB2, 0xCC, 0xE5, 0xFF, 0xE5, 0xCC, 0xB2, 0x99,
+ 0x0, 0x19, 0x32, 0x4C, 0x65, 0x7F, 0x65, 0x4C, 0x32, 0x19,
+ 0x0, 0x99, 0xB2, 0xCC, 0xE5, 0xFF, 0xE5, 0xCC, 0xB2, 0x99,
+ 0x0, 0x19, 0x32, 0x4C, 0x65, 0x7F, 0x65, 0x4C, 0x32, 0x19,
+ 0x0, 0x99, 0xB2, 0xCC, 0xE5, 0xFF, 0xE5, 0xCC, 0xB2, 0x99,
+ 0x0, 0x19, 0x32, 0x4C, 0x65, 0x7F, 0x65, 0x4C, 0x32, 0x19,
+ 0x0, 0x99, 0xB2, 0xCC, 0xE5, 0xFF, 0xE5, 0xCC, 0xB2, 0x99,
+ 0x0, 0x19, 0x32, 0x4C, 0x65, 0x7F, 0x65, 0x4C, 0x32, 0x19,
+ 0x0, 0x99, 0xB2, 0xCC, 0xE5, 0xFF, 0xE5, 0xCC, 0xB2, 0x99,
+};
+
+/* alternate max and min sequence */
+static u8 qpnp_hap_min_max_test_data[] = {
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+ 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF, 0x0, 0x7F, 0x0, 0xFF,
+};
+
+/*
+ * auto resonance mode
+ * ZXD - Zero Cross Detect
+ * QWD - Quarter Wave Drive
+ * ZXD_EOP - ZXD with End Of Pattern
+ */
+enum qpnp_hap_auto_res_mode {
+ QPNP_HAP_AUTO_RES_NONE,
+ QPNP_HAP_AUTO_RES_ZXD,
+ QPNP_HAP_AUTO_RES_QWD,
+ QPNP_HAP_AUTO_RES_MAX_QWD,
+ QPNP_HAP_AUTO_RES_ZXD_EOP,
+};
+
+enum qpnp_hap_pm660_auto_res_mode {
+ QPNP_HAP_PM660_AUTO_RES_ZXD,
+ QPNP_HAP_PM660_AUTO_RES_QWD,
+};
+
+/* high Z option lines */
+enum qpnp_hap_high_z {
+ QPNP_HAP_LRA_HIGH_Z_NONE, /* opt0 for PM660 */
+ QPNP_HAP_LRA_HIGH_Z_OPT1,
+ QPNP_HAP_LRA_HIGH_Z_OPT2,
+ QPNP_HAP_LRA_HIGH_Z_OPT3,
+};
+
+/* play modes */
+enum qpnp_hap_mode {
+ QPNP_HAP_DIRECT,
+ QPNP_HAP_BUFFER,
+ QPNP_HAP_AUDIO,
+ QPNP_HAP_PWM,
+};
+
+/* status flags */
+enum qpnp_hap_status {
+ AUTO_RESONANCE_ENABLED = BIT(0),
+};
+
+/* pwm channel info */
+struct qpnp_pwm_info {
+ struct pwm_device *pwm_dev;
+ u32 pwm_channel;
+ u32 duty_us;
+ u32 period_us;
+};
+
+/*
+ * qpnp_hap_lra_ares_cfg - Haptic auto_resonance configuration
+ * @ lra_qwd_drive_duration - LRA QWD drive duration
+ * @ calibrate_at_eop - Calibrate at EOP
+ * @ lra_res_cal_period - LRA resonance calibration period
+ * @ auto_res_mode - auto resonace mode
+ * @ lra_high_z - high z option line
+ */
+struct qpnp_hap_lra_ares_cfg {
+ int lra_qwd_drive_duration;
+ int calibrate_at_eop;
+ enum qpnp_hap_high_z lra_high_z;
+ u16 lra_res_cal_period;
+ u8 auto_res_mode;
+};
+
+/*
+ * qpnp_hap - Haptic data structure
+ * @ spmi - spmi device
+ * @ hap_timer - hrtimer
+ * @ auto_res_err_poll_timer - hrtimer for auto-resonance error
+ * @ timed_dev - timed output device
+ * @ work - worker
+ * @ sc_work - worker to handle short circuit condition
+ * @ pwm_info - pwm info
+ * @ ares_cfg - auto resonance configuration
+ * @ lock - mutex lock
+ * @ wf_lock - mutex lock for waveform
+ * @ init_drive_period_code - the initial lra drive period code
+ * @ drive_period_code_max_limit_percent_variation - maximum limit of
+ percentage variation of drive period code
+ * @ drive_period_code_min_limit_percent_variation - minimum limit og
+ percentage variation of drive period code
+ * @ drive_period_code_max_limit - calculated drive period code with
+ percentage variation on the higher side.
+ * @ drive_period_code_min_limit - calculated drive period code with
+ percentage variation on the lower side
+ * @ play_mode - play mode
+ * @ timeout_ms - max timeout in ms
+ * @ time_required_to_generate_back_emf_us - the time required for sufficient
+ back-emf to be generated for auto resonance to be successful
+ * @ vmax_mv - max voltage in mv
+ * @ ilim_ma - limiting current in ma
+ * @ sc_deb_cycles - short circuit debounce cycles
+ * @ int_pwm_freq_khz - internal pwm frequency in khz
+ * @ wave_play_rate_us - play rate for waveform
+ * @ play_time_ms - play time set by the user
+ * @ ext_pwm_freq_khz - external pwm frequency in khz
+ * @ wave_rep_cnt - waveform repeat count
+ * @ wave_s_rep_cnt - waveform sample repeat count
+ * @ play_irq - irq for play
+ * @ sc_irq - irq for short circuit
+ * @ status_flags - status
+ * @ base - base address
+ * @ act_type - actuator type
+ * @ wave_shape - waveform shape
+ * @ wave_samp - array of wave samples
+ * @ shadow_wave_samp - shadow array of wave samples
+ * @ brake_pat - pattern for active breaking
+ * @ sc_count - counter to determine the duration of short circuit condition
+ * @ lra_hw_auto_resonance - enable hardware auto resonance
+ * @ state - current state of haptics
+ * @ module_en - haptics module enable status
+ * @ wf_update - waveform update flag
+ * @ pwm_cfg_state - pwm mode configuration state
+ * @ en_brake - brake state
+ * @ sup_brake_pat - support custom brake pattern
+ * @ correct_lra_drive_freq - correct LRA Drive Frequency
+ * @ misc_clk_trim_error_reg - MISC clock trim error register if present
+ * @ clk_trim_error_code - MISC clock trim error code
+ * @ perform_lra_auto_resonance_search - whether lra auto resonance search
+ * algorithm should be performed or not.
+ * @ auto_mode - Auto mode selection
+ * @ override_auto_mode_config - Flag to override auto mode configuration with
+ * user specified values through sysfs.
+ */
+struct qpnp_hap {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct regulator *vcc_pon;
+ struct hrtimer hap_timer;
+ struct hrtimer auto_res_err_poll_timer;
+ struct timed_output_dev timed_dev;
+ struct work_struct work;
+ struct delayed_work sc_work;
+ struct hrtimer hap_test_timer;
+ struct work_struct test_work;
+ struct qpnp_pwm_info pwm_info;
+ struct qpnp_hap_lra_ares_cfg ares_cfg;
+ struct mutex lock;
+ struct mutex wf_lock;
+ spinlock_t bus_lock;
+ struct completion completion;
+ enum qpnp_hap_mode play_mode;
+ u32 misc_clk_trim_error_reg;
+ u32 init_drive_period_code;
+ u32 timeout_ms;
+ u32 time_required_to_generate_back_emf_us;
+ u32 vmax_mv;
+ u32 ilim_ma;
+ u32 sc_deb_cycles;
+ u32 int_pwm_freq_khz;
+ u32 wave_play_rate_us;
+ u32 play_time_ms;
+ u32 ext_pwm_freq_khz;
+ u32 wave_rep_cnt;
+ u32 wave_s_rep_cnt;
+ u32 play_irq;
+ u32 sc_irq;
+ u32 status_flags;
+ u16 base;
+ u16 last_rate_cfg;
+ u16 drive_period_code_max_limit;
+ u16 drive_period_code_min_limit;
+ u8 drive_period_code_max_limit_percent_variation;
+ u8 drive_period_code_min_limit_percent_variation;
+ u8 act_type;
+ u8 wave_shape;
+ u8 wave_samp[QPNP_HAP_WAV_SAMP_LEN];
+ u8 shadow_wave_samp[QPNP_HAP_WAV_SAMP_LEN];
+ u8 brake_pat[QPNP_HAP_BRAKE_PAT_LEN];
+ u8 sc_count;
+ u8 ext_pwm_dtest_line;
+ u8 pmic_subtype;
+ u8 clk_trim_error_code;
+ bool lra_hw_auto_resonance;
+ bool vcc_pon_enabled;
+ bool state;
+ bool module_en;
+ bool manage_pon_supply;
+ bool wf_update;
+ bool pwm_cfg_state;
+ bool en_brake;
+ bool sup_brake_pat;
+ bool correct_lra_drive_freq;
+ bool perform_lra_auto_resonance_search;
+ bool auto_mode;
+ bool override_auto_mode_config;
+ bool play_irq_en;
+};
+
+static struct qpnp_hap *ghap;
+
+/* helper to read a pmic register */
+static int qpnp_hap_read_mult_reg(struct qpnp_hap *hap, u16 addr, u8 *val,
+ int len)
+{
+ int rc;
+
+ rc = regmap_bulk_read(hap->regmap, addr, val, len);
+ if (rc < 0)
+ pr_err("Error reading address: %X - ret %X\n", addr, rc);
+
+ return rc;
+}
+
+static int qpnp_hap_read_reg(struct qpnp_hap *hap, u16 addr, u8 *val)
+{
+ int rc;
+ uint tmp;
+
+ rc = regmap_read(hap->regmap, addr, &tmp);
+ if (rc < 0)
+ pr_err("Error reading address: %X - ret %X\n", addr, rc);
+ else
+ *val = (u8)tmp;
+
+ return rc;
+}
+
+/* helper to write a pmic register */
+static int qpnp_hap_write_mult_reg(struct qpnp_hap *hap, u16 addr, u8 *val,
+ int len)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&hap->bus_lock, flags);
+ rc = regmap_bulk_write(hap->regmap, addr, val, len);
+ if (rc < 0)
+ pr_err("Error writing address: %X - ret %X\n", addr, rc);
+
+ spin_unlock_irqrestore(&hap->bus_lock, flags);
+ return rc;
+}
+
+static int qpnp_hap_write_reg(struct qpnp_hap *hap, u16 addr, u8 val)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&hap->bus_lock, flags);
+ rc = regmap_write(hap->regmap, addr, val);
+ if (rc < 0)
+ pr_err("Error writing address: %X - ret %X\n", addr, rc);
+
+ spin_unlock_irqrestore(&hap->bus_lock, flags);
+ if (!rc)
+ pr_debug("wrote: HAP_0x%x = 0x%x\n", addr, val);
+ return rc;
+}
+
+/* helper to access secure registers */
+#define QPNP_HAP_SEC_UNLOCK 0xA5
+static int qpnp_hap_sec_masked_write_reg(struct qpnp_hap *hap, u16 addr,
+ u8 mask, u8 val)
+{
+ unsigned long flags;
+ int rc;
+ u8 tmp = QPNP_HAP_SEC_UNLOCK;
+
+ spin_lock_irqsave(&hap->bus_lock, flags);
+ rc = regmap_write(hap->regmap, QPNP_HAP_SEC_ACCESS_REG(hap->base), tmp);
+ if (rc < 0) {
+ pr_err("Error writing sec_code - ret %X\n", rc);
+ goto out;
+ }
+
+ rc = regmap_update_bits(hap->regmap, addr, mask, val);
+ if (rc < 0)
+ pr_err("Error writing address: %X - ret %X\n", addr, rc);
+
+out:
+ spin_unlock_irqrestore(&hap->bus_lock, flags);
+ if (!rc)
+ pr_debug("wrote: HAP_0x%x = 0x%x\n", addr, val);
+ return rc;
+}
+
+static int qpnp_hap_masked_write_reg(struct qpnp_hap *hap, u16 addr, u8 mask,
+ u8 val)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&hap->bus_lock, flags);
+ rc = regmap_update_bits(hap->regmap, addr, mask, val);
+ if (rc < 0)
+ pr_err("Error writing address: %X - ret %X\n", addr, rc);
+
+ spin_unlock_irqrestore(&hap->bus_lock, flags);
+ if (!rc)
+ pr_debug("wrote: HAP_0x%x = 0x%x\n", addr, val);
+ return rc;
+}
+
+static void qpnp_handle_sc_irq(struct work_struct *work)
+{
+ struct qpnp_hap *hap = container_of(work,
+ struct qpnp_hap, sc_work.work);
+ u8 val;
+
+ qpnp_hap_read_reg(hap, QPNP_HAP_STATUS(hap->base), &val);
+
+ /* clear short circuit register */
+ if (val & SC_FOUND_BIT) {
+ hap->sc_count++;
+ val = QPNP_HAP_SC_CLR;
+ qpnp_hap_write_reg(hap, QPNP_HAP_SC_CLR_REG(hap->base), val);
+ }
+}
+
+#define QPNP_HAP_CYCLES 4
+static int qpnp_hap_mod_enable(struct qpnp_hap *hap, bool on)
+{
+ unsigned long wait_time_us;
+ u8 val;
+ int rc, i;
+
+ if (hap->module_en == on)
+ return 0;
+
+ if (!on) {
+ /*
+ * Wait for 4 cycles of play rate for play time is > 20 ms and
+ * wait for play time when play time is < 20 ms. This way, there
+ * will be an improvement in waiting time polling BUSY status.
+ */
+ if (hap->play_time_ms <= 20)
+ wait_time_us = hap->play_time_ms * 1000;
+ else
+ wait_time_us = QPNP_HAP_CYCLES * hap->wave_play_rate_us;
+
+ for (i = 0; i < QPNP_HAP_MAX_RETRIES; i++) {
+ rc = qpnp_hap_read_reg(hap, QPNP_HAP_STATUS(hap->base),
+ &val);
+ if (rc < 0)
+ return rc;
+
+ pr_debug("HAP_STATUS=0x%x\n", val);
+
+ /* wait for play_rate cycles */
+ if (val & QPNP_HAP_STATUS_BUSY) {
+ usleep_range(wait_time_us, wait_time_us + 1);
+ if (hap->play_mode == QPNP_HAP_DIRECT ||
+ hap->play_mode == QPNP_HAP_PWM)
+ break;
+ } else {
+ break;
+ }
+ }
+
+ if (i >= QPNP_HAP_MAX_RETRIES)
+ pr_debug("Haptics Busy. Force disable\n");
+ }
+
+ val = on ? QPNP_HAP_EN_BIT : 0;
+ rc = qpnp_hap_write_reg(hap, QPNP_HAP_EN_CTL_REG(hap->base), val);
+ if (rc < 0)
+ return rc;
+
+ hap->module_en = on;
+ return 0;
+}
+
+static int qpnp_hap_play(struct qpnp_hap *hap, bool on)
+{
+ u8 val;
+ int rc;
+
+ val = on ? QPNP_HAP_PLAY_EN_BIT : 0;
+ rc = qpnp_hap_write_reg(hap, QPNP_HAP_PLAY_REG(hap->base), val);
+ return rc;
+}
+
+/* sysfs show debug registers */
+static ssize_t qpnp_hap_dump_regs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ int count = 0, i;
+ u8 val;
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_hap_dbg_regs); i++) {
+ qpnp_hap_read_reg(hap, hap->base + qpnp_hap_dbg_regs[i], &val);
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "qpnp_haptics: REG_0x%x = 0x%x\n",
+ hap->base + qpnp_hap_dbg_regs[i],
+ val);
+
+ if (count >= PAGE_SIZE)
+ return PAGE_SIZE - 1;
+ }
+
+ return count;
+}
+
+/* play irq handler */
+static irqreturn_t qpnp_hap_play_irq(int irq, void *_hap)
+{
+ struct qpnp_hap *hap = _hap;
+ int i, rc;
+ u8 val;
+
+ mutex_lock(&hap->wf_lock);
+
+ /* Configure WAVE_SAMPLE1 to WAVE_SAMPLE8 register */
+ for (i = 0; i < QPNP_HAP_WAV_SAMP_LEN && hap->wf_update; i++) {
+ val = hap->wave_samp[i] = hap->shadow_wave_samp[i];
+ rc = qpnp_hap_write_reg(hap,
+ QPNP_HAP_WAV_S_REG_BASE(hap->base) + i, val);
+ if (rc)
+ goto unlock;
+ }
+ hap->wf_update = false;
+
+unlock:
+ mutex_unlock(&hap->wf_lock);
+
+ return IRQ_HANDLED;
+}
+
+/* short circuit irq handler */
+static irqreturn_t qpnp_hap_sc_irq(int irq, void *_hap)
+{
+ struct qpnp_hap *hap = _hap;
+ int rc;
+ u8 val;
+
+ pr_debug("Short circuit detected\n");
+
+ if (hap->sc_count < SC_MAX_COUNT) {
+ qpnp_hap_read_reg(hap, QPNP_HAP_STATUS(hap->base), &val);
+ if (val & SC_FOUND_BIT)
+ schedule_delayed_work(&hap->sc_work,
+ QPNP_HAP_SC_IRQ_STATUS_DELAY);
+ else
+ hap->sc_count = 0;
+ } else {
+ /* Disable haptics module if the duration of short circuit
+ * exceeds the maximum limit (5 secs).
+ */
+ val = 0;
+ rc = qpnp_hap_write_reg(hap, QPNP_HAP_EN_CTL_REG(hap->base),
+ val);
+ pr_err("Haptics disabled permanently due to short circuit\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+/* configuration api for buffer mode */
+static int qpnp_hap_buffer_config(struct qpnp_hap *hap, u8 *wave_samp,
+ bool overdrive)
+{
+ u8 buf[QPNP_HAP_WAV_SAMP_LEN], val;
+ u8 *ptr;
+ int rc, i;
+
+ /* Configure the WAVE_REPEAT register */
+ if (hap->wave_rep_cnt < QPNP_HAP_WAV_REP_MIN)
+ hap->wave_rep_cnt = QPNP_HAP_WAV_REP_MIN;
+ else if (hap->wave_rep_cnt > QPNP_HAP_WAV_REP_MAX)
+ hap->wave_rep_cnt = QPNP_HAP_WAV_REP_MAX;
+
+ if (hap->wave_s_rep_cnt < QPNP_HAP_WAV_S_REP_MIN)
+ hap->wave_s_rep_cnt = QPNP_HAP_WAV_S_REP_MIN;
+ else if (hap->wave_s_rep_cnt > QPNP_HAP_WAV_S_REP_MAX)
+ hap->wave_s_rep_cnt = QPNP_HAP_WAV_S_REP_MAX;
+
+ val = ilog2(hap->wave_rep_cnt) << QPNP_HAP_WAV_REP_SHIFT |
+ ilog2(hap->wave_s_rep_cnt);
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_WAV_REP_REG(hap->base),
+ QPNP_HAP_WAV_REP_MASK | QPNP_HAP_WAV_S_REP_MASK, val);
+ if (rc)
+ return rc;
+
+ /* Don't set override bit in waveform sample for PM660 */
+ if (hap->pmic_subtype == PM660_SUBTYPE)
+ overdrive = false;
+
+ if (wave_samp)
+ ptr = wave_samp;
+ else
+ ptr = hap->wave_samp;
+
+ /* Configure WAVE_SAMPLE1 to WAVE_SAMPLE8 register */
+ for (i = 0; i < QPNP_HAP_WAV_SAMP_LEN; i++) {
+ buf[i] = ptr[i] & QPNP_HAP_WF_AMP_MASK;
+ if (buf[i])
+ buf[i] |= (overdrive ? QPNP_HAP_WF_OVD_BIT : 0);
+ }
+
+ rc = qpnp_hap_write_mult_reg(hap, QPNP_HAP_WAV_S_REG_BASE(hap->base),
+ buf, QPNP_HAP_WAV_SAMP_LEN);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+/* configuration api for pwm */
+static int qpnp_hap_pwm_config(struct qpnp_hap *hap)
+{
+ u8 val = 0;
+ int rc;
+
+ /* Configure the EXTERNAL_PWM register */
+ if (hap->ext_pwm_freq_khz <= QPNP_HAP_EXT_PWM_FREQ_25_KHZ) {
+ hap->ext_pwm_freq_khz = QPNP_HAP_EXT_PWM_FREQ_25_KHZ;
+ val = 0;
+ } else if (hap->ext_pwm_freq_khz <=
+ QPNP_HAP_EXT_PWM_FREQ_50_KHZ) {
+ hap->ext_pwm_freq_khz = QPNP_HAP_EXT_PWM_FREQ_50_KHZ;
+ val = 1;
+ } else if (hap->ext_pwm_freq_khz <=
+ QPNP_HAP_EXT_PWM_FREQ_75_KHZ) {
+ hap->ext_pwm_freq_khz = QPNP_HAP_EXT_PWM_FREQ_75_KHZ;
+ val = 2;
+ } else {
+ hap->ext_pwm_freq_khz = QPNP_HAP_EXT_PWM_FREQ_100_KHZ;
+ val = 3;
+ }
+
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_EXT_PWM_REG(hap->base),
+ QPNP_HAP_EXT_PWM_MASK, val);
+ if (rc)
+ return rc;
+
+ if (!hap->ext_pwm_dtest_line ||
+ hap->ext_pwm_dtest_line > PWM_MAX_DTEST_LINES) {
+ pr_err("invalid dtest line\n");
+ return -EINVAL;
+ }
+
+ /* disable auto res for PWM mode */
+ val = hap->ext_pwm_dtest_line << QPNP_HAP_EXT_PWM_DTEST_SHFT;
+ rc = qpnp_hap_sec_masked_write_reg(hap, QPNP_HAP_TEST2_REG(hap->base),
+ QPNP_HAP_EXT_PWM_DTEST_MASK | QPNP_HAP_AUTO_RES_MASK, val);
+ if (rc)
+ return rc;
+
+ rc = pwm_config(hap->pwm_info.pwm_dev,
+ hap->pwm_info.duty_us * NSEC_PER_USEC,
+ hap->pwm_info.period_us * NSEC_PER_USEC);
+ if (rc < 0) {
+ pr_err("hap pwm config failed\n");
+ pwm_free(hap->pwm_info.pwm_dev);
+ return -ENODEV;
+ }
+
+ hap->pwm_cfg_state = true;
+
+ return 0;
+}
+
+static int qpnp_hap_lra_auto_res_config(struct qpnp_hap *hap,
+ struct qpnp_hap_lra_ares_cfg *tmp_cfg)
+{
+ struct qpnp_hap_lra_ares_cfg *ares_cfg;
+ int rc;
+ u8 val = 0, mask = 0;
+
+ /* disable auto resonance for ERM */
+ if (hap->act_type == QPNP_HAP_ERM) {
+ val = 0x00;
+ rc = qpnp_hap_write_reg(hap,
+ QPNP_HAP_LRA_AUTO_RES_REG(hap->base), val);
+ return rc;
+ }
+
+ if (hap->lra_hw_auto_resonance) {
+ rc = qpnp_hap_masked_write_reg(hap,
+ QPNP_HAP_AUTO_RES_CTRL(hap->base),
+ QPNP_HAP_PM660_HW_AUTO_RES_MODE_BIT,
+ QPNP_HAP_PM660_HW_AUTO_RES_MODE_BIT);
+ if (rc)
+ return rc;
+ }
+
+ if (tmp_cfg)
+ ares_cfg = tmp_cfg;
+ else
+ ares_cfg = &hap->ares_cfg;
+
+ if (ares_cfg->lra_res_cal_period < QPNP_HAP_RES_CAL_PERIOD_MIN)
+ ares_cfg->lra_res_cal_period = QPNP_HAP_RES_CAL_PERIOD_MIN;
+
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ if (ares_cfg->lra_res_cal_period >
+ QPNP_HAP_PM660_RES_CAL_PERIOD_MAX)
+ ares_cfg->lra_res_cal_period =
+ QPNP_HAP_PM660_RES_CAL_PERIOD_MAX;
+
+ if (ares_cfg->auto_res_mode == QPNP_HAP_PM660_AUTO_RES_QWD)
+ ares_cfg->lra_res_cal_period = 0;
+
+ if (ares_cfg->lra_res_cal_period)
+ val = ilog2(ares_cfg->lra_res_cal_period /
+ QPNP_HAP_RES_CAL_PERIOD_MIN) + 1;
+ } else {
+ if (ares_cfg->lra_res_cal_period > QPNP_HAP_RES_CAL_PERIOD_MAX)
+ ares_cfg->lra_res_cal_period =
+ QPNP_HAP_RES_CAL_PERIOD_MAX;
+
+ if (ares_cfg->lra_res_cal_period)
+ val = ilog2(ares_cfg->lra_res_cal_period /
+ QPNP_HAP_RES_CAL_PERIOD_MIN);
+ }
+
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ val |= ares_cfg->auto_res_mode <<
+ QPNP_HAP_PM660_AUTO_RES_MODE_SHIFT;
+ mask = QPNP_HAP_PM660_AUTO_RES_MODE_BIT;
+ val |= ares_cfg->lra_high_z <<
+ QPNP_HAP_PM660_CALIBRATE_DURATION_SHIFT;
+ mask |= QPNP_HAP_PM660_CALIBRATE_DURATION_MASK;
+ if (ares_cfg->lra_qwd_drive_duration != -EINVAL) {
+ val |= ares_cfg->lra_qwd_drive_duration <<
+ QPNP_HAP_PM660_QWD_DRIVE_DURATION_SHIFT;
+ mask |= QPNP_HAP_PM660_QWD_DRIVE_DURATION_BIT;
+ }
+ if (ares_cfg->calibrate_at_eop != -EINVAL) {
+ val |= ares_cfg->calibrate_at_eop <<
+ QPNP_HAP_PM660_CALIBRATE_AT_EOP_SHIFT;
+ mask |= QPNP_HAP_PM660_CALIBRATE_AT_EOP_BIT;
+ }
+ mask |= QPNP_HAP_PM660_LRA_RES_CAL_PER_MASK;
+ } else {
+ val |= (ares_cfg->auto_res_mode <<
+ QPNP_HAP_AUTO_RES_MODE_SHIFT);
+ val |= (ares_cfg->lra_high_z << QPNP_HAP_LRA_HIGH_Z_SHIFT);
+ mask = QPNP_HAP_AUTO_RES_MODE_MASK | QPNP_HAP_LRA_HIGH_Z_MASK |
+ QPNP_HAP_LRA_RES_CAL_PER_MASK;
+ }
+
+ pr_debug("mode: %d hi_z period: %d cal_period: %d\n",
+ ares_cfg->auto_res_mode, ares_cfg->lra_high_z,
+ ares_cfg->lra_res_cal_period);
+
+ rc = qpnp_hap_masked_write_reg(hap,
+ QPNP_HAP_LRA_AUTO_RES_REG(hap->base), mask, val);
+ return rc;
+}
+
+/* configuration api for play mode */
+static int qpnp_hap_play_mode_config(struct qpnp_hap *hap)
+{
+ u8 val = 0;
+ int rc;
+
+ val = hap->play_mode << QPNP_HAP_WF_SOURCE_SHIFT;
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_SEL_REG(hap->base),
+ QPNP_HAP_WF_SOURCE_MASK, val);
+ return rc;
+}
+
+/* configuration api for max voltage */
+static int qpnp_hap_vmax_config(struct qpnp_hap *hap, int vmax_mv,
+ bool overdrive)
+{
+ u8 val = 0;
+ int rc;
+
+ if (vmax_mv < 0)
+ return -EINVAL;
+
+ /* Allow setting override bit in VMAX_CFG only for PM660 */
+ if (hap->pmic_subtype != PM660_SUBTYPE)
+ overdrive = false;
+
+ if (vmax_mv < QPNP_HAP_VMAX_MIN_MV)
+ vmax_mv = QPNP_HAP_VMAX_MIN_MV;
+ else if (vmax_mv > QPNP_HAP_VMAX_MAX_MV)
+ vmax_mv = QPNP_HAP_VMAX_MAX_MV;
+
+ val = (vmax_mv / QPNP_HAP_VMAX_MIN_MV) << QPNP_HAP_VMAX_SHIFT;
+ if (overdrive)
+ val |= QPNP_HAP_VMAX_OVD_BIT;
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_VMAX_REG(hap->base),
+ QPNP_HAP_VMAX_MASK | QPNP_HAP_VMAX_OVD_BIT, val);
+ return rc;
+}
+
+/* configuration api for ilim */
+static int qpnp_hap_ilim_config(struct qpnp_hap *hap)
+{
+ u8 val = 0;
+ int rc;
+
+ if (hap->ilim_ma < QPNP_HAP_ILIM_MIN_MA)
+ hap->ilim_ma = QPNP_HAP_ILIM_MIN_MA;
+ else if (hap->ilim_ma > QPNP_HAP_ILIM_MAX_MA)
+ hap->ilim_ma = QPNP_HAP_ILIM_MAX_MA;
+
+ val = (hap->ilim_ma / QPNP_HAP_ILIM_MIN_MA) - 1;
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_ILIM_REG(hap->base),
+ QPNP_HAP_ILIM_MASK, val);
+ return rc;
+}
+
+/* configuration api for short circuit debounce */
+static int qpnp_hap_sc_deb_config(struct qpnp_hap *hap)
+{
+ u8 val = 0;
+ int rc;
+
+ if (hap->sc_deb_cycles < QPNP_HAP_SC_DEB_CYCLES_MIN)
+ hap->sc_deb_cycles = QPNP_HAP_SC_DEB_CYCLES_MIN;
+ else if (hap->sc_deb_cycles > QPNP_HAP_SC_DEB_CYCLES_MAX)
+ hap->sc_deb_cycles = QPNP_HAP_SC_DEB_CYCLES_MAX;
+
+ if (hap->sc_deb_cycles != QPNP_HAP_SC_DEB_CYCLES_MIN)
+ val = ilog2(hap->sc_deb_cycles /
+ QPNP_HAP_DEF_SC_DEB_CYCLES) + 1;
+ else
+ val = QPNP_HAP_SC_DEB_CYCLES_MIN;
+
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_SC_DEB_REG(hap->base),
+ QPNP_HAP_SC_DEB_MASK, val);
+
+ return rc;
+}
+
+static int qpnp_hap_int_pwm_config(struct qpnp_hap *hap)
+{
+ int rc;
+ u8 val;
+
+ if (hap->int_pwm_freq_khz <= QPNP_HAP_INT_PWM_FREQ_253_KHZ) {
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ hap->int_pwm_freq_khz = QPNP_HAP_INT_PWM_FREQ_505_KHZ;
+ val = 1;
+ } else {
+ hap->int_pwm_freq_khz = QPNP_HAP_INT_PWM_FREQ_253_KHZ;
+ val = 0;
+ }
+ } else if (hap->int_pwm_freq_khz <= QPNP_HAP_INT_PWM_FREQ_505_KHZ) {
+ hap->int_pwm_freq_khz = QPNP_HAP_INT_PWM_FREQ_505_KHZ;
+ val = 1;
+ } else if (hap->int_pwm_freq_khz <= QPNP_HAP_INT_PWM_FREQ_739_KHZ) {
+ hap->int_pwm_freq_khz = QPNP_HAP_INT_PWM_FREQ_739_KHZ;
+ val = 2;
+ } else {
+ hap->int_pwm_freq_khz = QPNP_HAP_INT_PWM_FREQ_1076_KHZ;
+ val = 3;
+ }
+
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_INT_PWM_REG(hap->base),
+ QPNP_HAP_INT_PWM_MASK, val);
+ if (rc)
+ return rc;
+
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_PWM_CAP_REG(hap->base),
+ QPNP_HAP_INT_PWM_MASK, val);
+ return rc;
+}
+
+static int qpnp_hap_brake_config(struct qpnp_hap *hap, u8 *brake_pat)
+{
+ int rc, i;
+ u32 temp;
+ u8 *pat_ptr, val;
+
+ if (!hap->en_brake)
+ return 0;
+
+ /* Configure BRAKE register */
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_EN_CTL2_REG(hap->base),
+ QPNP_HAP_BRAKE_MASK, (u8)hap->en_brake);
+ if (rc)
+ return rc;
+
+ if (!brake_pat)
+ pat_ptr = hap->brake_pat;
+ else
+ pat_ptr = brake_pat;
+
+ if (hap->sup_brake_pat) {
+ for (i = QPNP_HAP_BRAKE_PAT_LEN - 1, val = 0; i >= 0; i--) {
+ pat_ptr[i] &= QPNP_HAP_BRAKE_PAT_MASK;
+ temp = i << 1;
+ val |= pat_ptr[i] << temp;
+ }
+ rc = qpnp_hap_write_reg(hap, QPNP_HAP_BRAKE_REG(hap->base),
+ val);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+/* DT parsing api for buffer mode */
+static int qpnp_hap_parse_buffer_dt(struct qpnp_hap *hap)
+{
+ struct platform_device *pdev = hap->pdev;
+ struct property *prop;
+ u32 temp;
+ int rc, i;
+
+ if (hap->wave_rep_cnt > 0 || hap->wave_s_rep_cnt > 0)
+ return 0;
+
+ hap->wave_rep_cnt = QPNP_HAP_WAV_REP_MIN;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,wave-rep-cnt", &temp);
+ if (!rc) {
+ hap->wave_rep_cnt = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read rep cnt\n");
+ return rc;
+ }
+
+ hap->wave_s_rep_cnt = QPNP_HAP_WAV_S_REP_MIN;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,wave-samp-rep-cnt", &temp);
+ if (!rc) {
+ hap->wave_s_rep_cnt = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read samp rep cnt\n");
+ return rc;
+ }
+
+ prop = of_find_property(pdev->dev.of_node,
+ "qcom,wave-samples", &temp);
+ if (!prop || temp != QPNP_HAP_WAV_SAMP_LEN) {
+ pr_err("Invalid wave samples, use default");
+ for (i = 0; i < QPNP_HAP_WAV_SAMP_LEN; i++)
+ hap->wave_samp[i] = QPNP_HAP_WAV_SAMP_MAX;
+ } else {
+ memcpy(hap->wave_samp, prop->value, QPNP_HAP_WAV_SAMP_LEN);
+ }
+
+ return 0;
+}
+
+/* DT parsing api for PWM mode */
+static int qpnp_hap_parse_pwm_dt(struct qpnp_hap *hap)
+{
+ struct platform_device *pdev = hap->pdev;
+ u32 temp;
+ int rc;
+
+ hap->ext_pwm_freq_khz = QPNP_HAP_EXT_PWM_FREQ_25_KHZ;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,ext-pwm-freq-khz", &temp);
+ if (!rc) {
+ hap->ext_pwm_freq_khz = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read ext pwm freq\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,pwm-channel", &temp);
+ if (!rc)
+ hap->pwm_info.pwm_channel = temp;
+ else
+ return rc;
+
+ hap->pwm_info.pwm_dev = of_pwm_get(pdev->dev.of_node, NULL);
+
+ if (IS_ERR(hap->pwm_info.pwm_dev)) {
+ rc = PTR_ERR(hap->pwm_info.pwm_dev);
+ pr_err("Cannot get PWM device rc:(%d)\n", rc);
+ hap->pwm_info.pwm_dev = NULL;
+ return rc;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node, "qcom,period-us", &temp);
+ if (!rc)
+ hap->pwm_info.period_us = temp;
+ else
+ return rc;
+
+ rc = of_property_read_u32(pdev->dev.of_node, "qcom,duty-us", &temp);
+ if (!rc)
+ hap->pwm_info.duty_us = temp;
+ else
+ return rc;
+
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,ext-pwm-dtest-line", &temp);
+ if (!rc)
+ hap->ext_pwm_dtest_line = temp;
+ else
+ return rc;
+
+ return 0;
+}
+
+/* sysfs show for wave samples */
+static ssize_t qpnp_hap_wf_samp_show(struct device *dev, char *buf, int index)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ if (index < 0 || index >= QPNP_HAP_WAV_SAMP_LEN) {
+ pr_err("Invalid sample index(%d)\n", index);
+ return -EINVAL;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "0x%x\n",
+ hap->shadow_wave_samp[index]);
+}
+
+static ssize_t qpnp_hap_wf_s0_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qpnp_hap_wf_samp_show(dev, buf, 0);
+}
+
+static ssize_t qpnp_hap_wf_s1_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qpnp_hap_wf_samp_show(dev, buf, 1);
+}
+
+static ssize_t qpnp_hap_wf_s2_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qpnp_hap_wf_samp_show(dev, buf, 2);
+}
+
+static ssize_t qpnp_hap_wf_s3_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qpnp_hap_wf_samp_show(dev, buf, 3);
+}
+
+static ssize_t qpnp_hap_wf_s4_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qpnp_hap_wf_samp_show(dev, buf, 4);
+}
+
+static ssize_t qpnp_hap_wf_s5_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qpnp_hap_wf_samp_show(dev, buf, 5);
+}
+
+static ssize_t qpnp_hap_wf_s6_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qpnp_hap_wf_samp_show(dev, buf, 6);
+}
+
+static ssize_t qpnp_hap_wf_s7_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qpnp_hap_wf_samp_show(dev, buf, 7);
+}
+
+/* sysfs store for wave samples */
+static ssize_t qpnp_hap_wf_samp_store(struct device *dev,
+ const char *buf, size_t count, int index)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ int data, rc;
+
+ if (index < 0 || index >= QPNP_HAP_WAV_SAMP_LEN) {
+ pr_err("Invalid sample index(%d)\n", index);
+ return -EINVAL;
+ }
+
+ rc = kstrtoint(buf, 16, &data);
+ if (rc)
+ return rc;
+
+ if (data < 0 || data > 0xff) {
+ pr_err("Invalid sample wf_%d (%d)\n", index, data);
+ return -EINVAL;
+ }
+
+ hap->shadow_wave_samp[index] = (u8) data;
+ return count;
+}
+
+static ssize_t qpnp_hap_wf_s0_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return qpnp_hap_wf_samp_store(dev, buf, count, 0);
+}
+
+static ssize_t qpnp_hap_wf_s1_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return qpnp_hap_wf_samp_store(dev, buf, count, 1);
+}
+
+static ssize_t qpnp_hap_wf_s2_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return qpnp_hap_wf_samp_store(dev, buf, count, 2);
+}
+
+static ssize_t qpnp_hap_wf_s3_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return qpnp_hap_wf_samp_store(dev, buf, count, 3);
+}
+
+static ssize_t qpnp_hap_wf_s4_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return qpnp_hap_wf_samp_store(dev, buf, count, 4);
+}
+
+static ssize_t qpnp_hap_wf_s5_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return qpnp_hap_wf_samp_store(dev, buf, count, 5);
+}
+
+static ssize_t qpnp_hap_wf_s6_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return qpnp_hap_wf_samp_store(dev, buf, count, 6);
+}
+
+static ssize_t qpnp_hap_wf_s7_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return qpnp_hap_wf_samp_store(dev, buf, count, 7);
+}
+
+/* sysfs show for wave form update */
+static ssize_t qpnp_hap_wf_update_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", hap->wf_update);
+}
+
+/* sysfs store for updating wave samples */
+static ssize_t qpnp_hap_wf_update_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ mutex_lock(&hap->wf_lock);
+ hap->wf_update = true;
+ mutex_unlock(&hap->wf_lock);
+
+ return count;
+}
+
+/* sysfs show for wave repeat */
+static ssize_t qpnp_hap_wf_rep_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", hap->wave_rep_cnt);
+}
+
+/* sysfs store for wave repeat */
+static ssize_t qpnp_hap_wf_rep_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ int data, rc;
+ u8 val;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ if (data < QPNP_HAP_WAV_REP_MIN)
+ data = QPNP_HAP_WAV_REP_MIN;
+ else if (data > QPNP_HAP_WAV_REP_MAX)
+ data = QPNP_HAP_WAV_REP_MAX;
+
+ val = ilog2(data) << QPNP_HAP_WAV_REP_SHIFT;
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_WAV_REP_REG(hap->base),
+ QPNP_HAP_WAV_REP_MASK, val);
+ if (!rc)
+ hap->wave_rep_cnt = data;
+
+ return count;
+}
+
+/* sysfs show for wave samples repeat */
+static ssize_t qpnp_hap_wf_s_rep_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", hap->wave_s_rep_cnt);
+}
+
+/* sysfs store for wave samples repeat */
+static ssize_t qpnp_hap_wf_s_rep_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ int data, rc;
+ u8 val;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ if (data < QPNP_HAP_WAV_S_REP_MIN)
+ data = QPNP_HAP_WAV_S_REP_MIN;
+ else if (data > QPNP_HAP_WAV_S_REP_MAX)
+ data = QPNP_HAP_WAV_S_REP_MAX;
+
+ val = ilog2(hap->wave_s_rep_cnt);
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_WAV_REP_REG(hap->base),
+ QPNP_HAP_WAV_S_REP_MASK, val);
+ if (!rc)
+ hap->wave_s_rep_cnt = data;
+
+ return count;
+}
+
+static int parse_string(const char *in_buf, char *out_buf)
+{
+ int i;
+
+ if (snprintf(out_buf, QPNP_HAP_STR_SIZE, "%s", in_buf)
+ > QPNP_HAP_STR_SIZE)
+ return -EINVAL;
+
+ for (i = 0; i < strlen(out_buf); i++) {
+ if (out_buf[i] == ' ' || out_buf[i] == '\n' ||
+ out_buf[i] == '\t') {
+ out_buf[i] = '\0';
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* sysfs store function for play mode*/
+static ssize_t qpnp_hap_play_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ char str[QPNP_HAP_STR_SIZE + 1];
+ int rc = 0, temp, old_mode;
+
+ rc = parse_string(buf, str);
+ if (rc < 0)
+ return rc;
+
+ if (strcmp(str, "buffer") == 0)
+ temp = QPNP_HAP_BUFFER;
+ else if (strcmp(str, "direct") == 0)
+ temp = QPNP_HAP_DIRECT;
+ else if (strcmp(str, "audio") == 0)
+ temp = QPNP_HAP_AUDIO;
+ else if (strcmp(str, "pwm") == 0)
+ temp = QPNP_HAP_PWM;
+ else
+ return -EINVAL;
+
+ if (temp == hap->play_mode)
+ return count;
+
+ if (temp == QPNP_HAP_BUFFER) {
+ rc = qpnp_hap_parse_buffer_dt(hap);
+ if (!rc)
+ rc = qpnp_hap_buffer_config(hap, NULL, false);
+ } else if (temp == QPNP_HAP_PWM && !hap->pwm_cfg_state) {
+ rc = qpnp_hap_parse_pwm_dt(hap);
+ if (!rc)
+ rc = qpnp_hap_pwm_config(hap);
+ }
+
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_hap_mod_enable(hap, false);
+ if (rc < 0)
+ return rc;
+
+ old_mode = hap->play_mode;
+ hap->play_mode = temp;
+ /* Configure the PLAY MODE register */
+ rc = qpnp_hap_play_mode_config(hap);
+ if (rc) {
+ hap->play_mode = old_mode;
+ return rc;
+ }
+
+ if (hap->play_mode == QPNP_HAP_AUDIO) {
+ rc = qpnp_hap_mod_enable(hap, true);
+ if (rc < 0) {
+ hap->play_mode = old_mode;
+ return rc;
+ }
+ }
+
+ return count;
+}
+
+/* sysfs show function for play mode */
+static ssize_t qpnp_hap_play_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ char *str;
+
+ if (hap->play_mode == QPNP_HAP_BUFFER)
+ str = "buffer";
+ else if (hap->play_mode == QPNP_HAP_DIRECT)
+ str = "direct";
+ else if (hap->play_mode == QPNP_HAP_AUDIO)
+ str = "audio";
+ else if (hap->play_mode == QPNP_HAP_PWM)
+ str = "pwm";
+ else
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+/* sysfs store for ramp test data */
+static ssize_t qpnp_hap_min_max_test_data_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ int value = QPNP_TEST_TIMER_MS, i;
+
+ mutex_lock(&hap->lock);
+ qpnp_hap_mod_enable(hap, true);
+ for (i = 0; i < ARRAY_SIZE(qpnp_hap_min_max_test_data); i++) {
+ hrtimer_start(&hap->hap_test_timer,
+ ktime_set(value / 1000, (value % 1000) * 1000000),
+ HRTIMER_MODE_REL);
+ qpnp_hap_play_byte(qpnp_hap_min_max_test_data[i], true);
+ wait_for_completion(&hap->completion);
+ }
+
+ qpnp_hap_play_byte(0, false);
+ qpnp_hap_mod_enable(hap, false);
+ mutex_unlock(&hap->lock);
+
+ return count;
+}
+
+/* sysfs show function for min max test data */
+static ssize_t qpnp_hap_min_max_test_data_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int count = 0, i;
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_hap_min_max_test_data); i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "qpnp_haptics: min_max_test_data[%d] = 0x%x\n",
+ i, qpnp_hap_min_max_test_data[i]);
+
+ if (count >= PAGE_SIZE)
+ return PAGE_SIZE - 1;
+ }
+
+ return count;
+
+}
+
+/* sysfs store for ramp test data */
+static ssize_t qpnp_hap_ramp_test_data_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ int value = QPNP_TEST_TIMER_MS, i;
+
+ mutex_lock(&hap->lock);
+ qpnp_hap_mod_enable(hap, true);
+ for (i = 0; i < ARRAY_SIZE(qpnp_hap_ramp_test_data); i++) {
+ hrtimer_start(&hap->hap_test_timer,
+ ktime_set(value / 1000, (value % 1000) * 1000000),
+ HRTIMER_MODE_REL);
+ qpnp_hap_play_byte(qpnp_hap_ramp_test_data[i], true);
+ wait_for_completion(&hap->completion);
+ }
+
+ qpnp_hap_play_byte(0, false);
+ qpnp_hap_mod_enable(hap, false);
+ mutex_unlock(&hap->lock);
+
+ return count;
+}
+
+/* sysfs show function for ramp test data */
+static ssize_t qpnp_hap_ramp_test_data_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int count = 0, i;
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_hap_ramp_test_data); i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "qpnp_haptics: ramp_test_data[%d] = 0x%x\n",
+ i, qpnp_hap_ramp_test_data[i]);
+
+ if (count >= PAGE_SIZE)
+ return PAGE_SIZE - 1;
+ }
+
+ return count;
+
+}
+
+static ssize_t qpnp_hap_auto_res_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ char *str;
+
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ switch (hap->ares_cfg.auto_res_mode) {
+ case QPNP_HAP_PM660_AUTO_RES_ZXD:
+ str = "ZXD";
+ break;
+ case QPNP_HAP_PM660_AUTO_RES_QWD:
+ str = "QWD";
+ break;
+ default:
+ str = "None";
+ break;
+ }
+ } else {
+ switch (hap->ares_cfg.auto_res_mode) {
+ case QPNP_HAP_AUTO_RES_NONE:
+ str = "None";
+ break;
+ case QPNP_HAP_AUTO_RES_ZXD:
+ str = "ZXD";
+ break;
+ case QPNP_HAP_AUTO_RES_QWD:
+ str = "QWD";
+ break;
+ case QPNP_HAP_AUTO_RES_MAX_QWD:
+ str = "MAX_QWD";
+ break;
+ case QPNP_HAP_AUTO_RES_ZXD_EOP:
+ str = "ZXD_EOP";
+ break;
+ default:
+ str = "None";
+ break;
+ }
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+static ssize_t qpnp_hap_auto_res_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ char str[QPNP_HAP_STR_SIZE + 1];
+ int rc = 0, temp;
+
+ rc = parse_string(buf, str);
+ if (rc < 0)
+ return rc;
+
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ if (strcmp(str, "ZXD") == 0 ||
+ strcmp(str, "zxd") == 0)
+ temp = QPNP_HAP_PM660_AUTO_RES_ZXD;
+ else if (strcmp(str, "QWD") == 0 ||
+ strcmp(str, "qwd") == 0)
+ temp = QPNP_HAP_PM660_AUTO_RES_QWD;
+ else {
+ pr_err("Should be ZXD or QWD\n");
+ return -EINVAL;
+ }
+ } else {
+ if (strcmp(str, "None") == 0)
+ temp = QPNP_HAP_AUTO_RES_NONE;
+ else if (strcmp(str, "ZXD") == 0 ||
+ strcmp(str, "zxd") == 0)
+ temp = QPNP_HAP_AUTO_RES_ZXD;
+ else if (strcmp(str, "QWD") == 0 ||
+ strcmp(str, "qwd") == 0)
+ temp = QPNP_HAP_AUTO_RES_QWD;
+ else if (strcmp(str, "ZXD_EOP") == 0 ||
+ strcmp(str, "zxd_eop") == 0)
+ temp = QPNP_HAP_AUTO_RES_ZXD_EOP;
+ else if (strcmp(str, "MAX_QWD") == 0 ||
+ strcmp(str, "max_qwd") == 0)
+ temp = QPNP_HAP_AUTO_RES_MAX_QWD;
+ else {
+ pr_err("Should be None or ZXD or QWD or ZXD_EOP or MAX_QWD\n");
+ return -EINVAL;
+ }
+ }
+
+ hap->ares_cfg.auto_res_mode = temp;
+ return count;
+}
+
+static ssize_t qpnp_hap_hi_z_period_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ char *str;
+
+ switch (hap->ares_cfg.lra_high_z) {
+ case QPNP_HAP_LRA_HIGH_Z_NONE:
+ str = "high_z_none";
+ break;
+ case QPNP_HAP_LRA_HIGH_Z_OPT1:
+ str = "high_z_opt1";
+ break;
+ case QPNP_HAP_LRA_HIGH_Z_OPT2:
+ str = "high_z_opt2";
+ break;
+ case QPNP_HAP_LRA_HIGH_Z_OPT3:
+ str = "high_z_opt3";
+ break;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+static ssize_t qpnp_hap_hi_z_period_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ int data, rc;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ if (data < QPNP_HAP_LRA_HIGH_Z_NONE
+ || data > QPNP_HAP_LRA_HIGH_Z_OPT3) {
+ pr_err("Invalid high Z configuration\n");
+ return -EINVAL;
+ }
+
+ hap->ares_cfg.lra_high_z = data;
+ return count;
+}
+
+static ssize_t qpnp_hap_calib_period_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ hap->ares_cfg.lra_res_cal_period);
+}
+
+static ssize_t qpnp_hap_calib_period_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ int data, rc;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ if (data < QPNP_HAP_RES_CAL_PERIOD_MIN) {
+ pr_err("Invalid auto resonance calibration period\n");
+ return -EINVAL;
+ }
+
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ if (data > QPNP_HAP_PM660_RES_CAL_PERIOD_MAX) {
+ pr_err("Invalid auto resonance calibration period\n");
+ return -EINVAL;
+ }
+ } else {
+ if (data > QPNP_HAP_RES_CAL_PERIOD_MAX) {
+ pr_err("Invalid auto resonance calibration period\n");
+ return -EINVAL;
+ }
+ }
+
+ hap->ares_cfg.lra_res_cal_period = data;
+ return count;
+}
+
+static ssize_t qpnp_hap_override_auto_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", hap->override_auto_mode_config);
+}
+
+static ssize_t qpnp_hap_override_auto_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ int data, rc;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ hap->override_auto_mode_config = data;
+ return count;
+}
+
+static ssize_t qpnp_hap_vmax_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", hap->vmax_mv);
+}
+
+static ssize_t qpnp_hap_vmax_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct timed_output_dev *timed_dev = dev_get_drvdata(dev);
+ struct qpnp_hap *hap = container_of(timed_dev, struct qpnp_hap,
+ timed_dev);
+ int data, rc;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ hap->vmax_mv = data;
+ return count;
+}
+
+/* sysfs attributes */
+static struct device_attribute qpnp_hap_attrs[] = {
+ __ATTR(wf_s0, 0664, qpnp_hap_wf_s0_show, qpnp_hap_wf_s0_store),
+ __ATTR(wf_s1, 0664, qpnp_hap_wf_s1_show, qpnp_hap_wf_s1_store),
+ __ATTR(wf_s2, 0664, qpnp_hap_wf_s2_show, qpnp_hap_wf_s2_store),
+ __ATTR(wf_s3, 0664, qpnp_hap_wf_s3_show, qpnp_hap_wf_s3_store),
+ __ATTR(wf_s4, 0664, qpnp_hap_wf_s4_show, qpnp_hap_wf_s4_store),
+ __ATTR(wf_s5, 0664, qpnp_hap_wf_s5_show, qpnp_hap_wf_s5_store),
+ __ATTR(wf_s6, 0664, qpnp_hap_wf_s6_show, qpnp_hap_wf_s6_store),
+ __ATTR(wf_s7, 0664, qpnp_hap_wf_s7_show, qpnp_hap_wf_s7_store),
+ __ATTR(wf_update, 0664, qpnp_hap_wf_update_show,
+ qpnp_hap_wf_update_store),
+ __ATTR(wf_rep, 0664, qpnp_hap_wf_rep_show, qpnp_hap_wf_rep_store),
+ __ATTR(wf_s_rep, 0664, qpnp_hap_wf_s_rep_show, qpnp_hap_wf_s_rep_store),
+ __ATTR(play_mode, 0664, qpnp_hap_play_mode_show,
+ qpnp_hap_play_mode_store),
+ __ATTR(dump_regs, 0664, qpnp_hap_dump_regs_show, NULL),
+ __ATTR(ramp_test, 0664, qpnp_hap_ramp_test_data_show,
+ qpnp_hap_ramp_test_data_store),
+ __ATTR(min_max_test, 0664, qpnp_hap_min_max_test_data_show,
+ qpnp_hap_min_max_test_data_store),
+ __ATTR(auto_res_mode, 0664, qpnp_hap_auto_res_mode_show,
+ qpnp_hap_auto_res_mode_store),
+ __ATTR(high_z_period, 0664, qpnp_hap_hi_z_period_show,
+ qpnp_hap_hi_z_period_store),
+ __ATTR(calib_period, 0664, qpnp_hap_calib_period_show,
+ qpnp_hap_calib_period_store),
+ __ATTR(override_auto_mode_config, 0664,
+ qpnp_hap_override_auto_mode_show,
+ qpnp_hap_override_auto_mode_store),
+ __ATTR(vmax_mv, 0664, qpnp_hap_vmax_show, qpnp_hap_vmax_store),
+};
+
+static int calculate_lra_code(struct qpnp_hap *hap)
+{
+ u8 lra_drive_period_code_lo = 0, lra_drive_period_code_hi = 0;
+ u32 lra_drive_period_code, lra_drive_frequency_hz, freq_variation;
+ u8 start_variation = AUTO_RES_ERROR_MAX, i;
+ u8 neg_idx = 0, pos_idx = ADJUSTED_LRA_PLAY_RATE_CODE_ARRSIZE - 1;
+ int rc = 0;
+
+ rc = qpnp_hap_read_reg(hap, QPNP_HAP_RATE_CFG1_REG(hap->base),
+ &lra_drive_period_code_lo);
+ if (rc) {
+ pr_err("Error while reading RATE_CFG1 register\n");
+ return rc;
+ }
+
+ rc = qpnp_hap_read_reg(hap, QPNP_HAP_RATE_CFG2_REG(hap->base),
+ &lra_drive_period_code_hi);
+ if (rc) {
+ pr_err("Error while reading RATE_CFG2 register\n");
+ return rc;
+ }
+
+ if (!lra_drive_period_code_lo && !lra_drive_period_code_hi) {
+ pr_err("Unexpected Error: both RATE_CFG1 and RATE_CFG2 read 0\n");
+ return -EINVAL;
+ }
+
+ lra_drive_period_code =
+ (lra_drive_period_code_hi << 8) | (lra_drive_period_code_lo & 0xff);
+ lra_drive_frequency_hz = 200000 / lra_drive_period_code;
+
+ while (start_variation >= AUTO_RES_ERROR_CAPTURE_RES) {
+ freq_variation =
+ (lra_drive_frequency_hz * start_variation) / 100;
+ adjusted_lra_play_rate_code[neg_idx++] =
+ 200000 / (lra_drive_frequency_hz - freq_variation);
+ adjusted_lra_play_rate_code[pos_idx--] =
+ 200000 / (lra_drive_frequency_hz + freq_variation);
+ start_variation -= AUTO_RES_ERROR_CAPTURE_RES;
+ }
+
+ pr_debug("lra_drive_period_code_lo = 0x%x lra_drive_period_code_hi = 0x%x\n"
+ "lra_drive_period_code = 0x%x, lra_drive_frequency_hz = 0x%x\n"
+ "Calculated play rate code values are :\n",
+ lra_drive_period_code_lo, lra_drive_period_code_hi,
+ lra_drive_period_code, lra_drive_frequency_hz);
+
+ for (i = 0; i < ADJUSTED_LRA_PLAY_RATE_CODE_ARRSIZE; ++i)
+ pr_debug(" 0x%x", adjusted_lra_play_rate_code[i]);
+
+ return 0;
+}
+
+static int qpnp_hap_auto_res_enable(struct qpnp_hap *hap, int enable)
+{
+ int rc = 0;
+ u32 back_emf_delay_us = hap->time_required_to_generate_back_emf_us;
+ u8 val, auto_res_mode_qwd;
+
+ if (hap->act_type != QPNP_HAP_LRA)
+ return 0;
+
+ if (hap->pmic_subtype == PM660_SUBTYPE)
+ auto_res_mode_qwd = (hap->ares_cfg.auto_res_mode ==
+ QPNP_HAP_PM660_AUTO_RES_QWD);
+ else
+ auto_res_mode_qwd = (hap->ares_cfg.auto_res_mode ==
+ QPNP_HAP_AUTO_RES_QWD);
+
+ /*
+ * Do not enable auto resonance if auto mode is enabled and auto
+ * resonance mode is QWD, meaning short pattern.
+ */
+ if (hap->auto_mode && auto_res_mode_qwd && enable) {
+ pr_debug("auto_mode enabled, not enabling auto_res\n");
+ return 0;
+ }
+
+ if (!hap->correct_lra_drive_freq && !auto_res_mode_qwd) {
+ pr_debug("correct_lra_drive_freq: %d auto_res_mode_qwd: %d\n",
+ hap->correct_lra_drive_freq, auto_res_mode_qwd);
+ return 0;
+ }
+
+ val = enable ? AUTO_RES_ENABLE : 0;
+ /*
+ * For auto resonance detection to work properly, sufficient back-emf
+ * has to be generated. In general, back-emf takes some time to build
+ * up. When the auto resonance mode is chosen as QWD, high-z will be
+ * applied for every LRA cycle and hence there won't be enough back-emf
+ * at the start-up. Hence, the motor needs to vibrate for few LRA cycles
+ * after the PLAY bit is asserted. Enable the auto resonance after
+ * 'time_required_to_generate_back_emf_us' is completed.
+ */
+ if (enable)
+ usleep_range(back_emf_delay_us, back_emf_delay_us + 1);
+
+ if (hap->pmic_subtype == PM660_SUBTYPE)
+ rc = qpnp_hap_masked_write_reg(hap,
+ QPNP_HAP_AUTO_RES_CTRL(hap->base),
+ QPNP_HAP_AUTO_RES_MASK, val);
+ else
+ rc = qpnp_hap_sec_masked_write_reg(hap,
+ QPNP_HAP_TEST2_REG(hap->base),
+ QPNP_HAP_AUTO_RES_MASK, val);
+ if (rc < 0)
+ return rc;
+
+ if (enable)
+ hap->status_flags |= AUTO_RESONANCE_ENABLED;
+ else
+ hap->status_flags &= ~AUTO_RESONANCE_ENABLED;
+
+ pr_debug("auto_res %sabled\n", enable ? "en" : "dis");
+ return rc;
+}
+
+static void update_lra_frequency(struct qpnp_hap *hap)
+{
+ u8 lra_auto_res[2], val;
+ u32 play_rate_code;
+ u16 rate_cfg;
+ int rc;
+
+ rc = qpnp_hap_read_mult_reg(hap, QPNP_HAP_LRA_AUTO_RES_LO(hap->base),
+ lra_auto_res, 2);
+ if (rc < 0) {
+ pr_err("Error in reading LRA_AUTO_RES_LO/HI, rc=%d\n", rc);
+ return;
+ }
+
+ play_rate_code =
+ (lra_auto_res[1] & 0xF0) << 4 | (lra_auto_res[0] & 0xFF);
+
+ pr_debug("lra_auto_res_lo = 0x%x lra_auto_res_hi = 0x%x play_rate_code = 0x%x\n",
+ lra_auto_res[0], lra_auto_res[1], play_rate_code);
+
+ rc = qpnp_hap_read_reg(hap, QPNP_HAP_STATUS(hap->base), &val);
+ if (rc < 0)
+ return;
+
+ /*
+ * If the drive period code read from AUTO_RES_LO and AUTO_RES_HI
+ * registers is more than the max limit percent variation or less
+ * than the min limit percent variation specified through DT, then
+ * auto-resonance is disabled.
+ */
+
+ if ((val & AUTO_RES_ERR_BIT) ||
+ ((play_rate_code <= hap->drive_period_code_min_limit) ||
+ (play_rate_code >= hap->drive_period_code_max_limit))) {
+ if (val & AUTO_RES_ERR_BIT)
+ pr_debug("Auto-resonance error %x\n", val);
+ else
+ pr_debug("play rate %x out of bounds [min: 0x%x, max: 0x%x]\n",
+ play_rate_code,
+ hap->drive_period_code_min_limit,
+ hap->drive_period_code_max_limit);
+ rc = qpnp_hap_auto_res_enable(hap, 0);
+ if (rc < 0)
+ pr_debug("Auto-resonance write failed\n");
+ return;
+ }
+
+ lra_auto_res[1] >>= 4;
+ rate_cfg = lra_auto_res[1] << 8 | lra_auto_res[0];
+ if (hap->last_rate_cfg == rate_cfg) {
+ pr_debug("Same rate_cfg, skip updating\n");
+ return;
+ }
+
+ rc = qpnp_hap_write_mult_reg(hap, QPNP_HAP_RATE_CFG1_REG(hap->base),
+ lra_auto_res, 2);
+ if (rc < 0) {
+ pr_err("Error in writing to RATE_CFG1/2, rc=%d\n", rc);
+ } else {
+ pr_debug("Update RATE_CFG with [0x%x]\n", rate_cfg);
+ hap->last_rate_cfg = rate_cfg;
+ }
+}
+
+static enum hrtimer_restart detect_auto_res_error(struct hrtimer *timer)
+{
+ struct qpnp_hap *hap = container_of(timer, struct qpnp_hap,
+ auto_res_err_poll_timer);
+ ktime_t currtime;
+
+ if (!(hap->status_flags & AUTO_RESONANCE_ENABLED))
+ return HRTIMER_NORESTART;
+
+ update_lra_frequency(hap);
+ currtime = ktime_get();
+ hrtimer_forward(&hap->auto_res_err_poll_timer, currtime,
+ ktime_set(0, POLL_TIME_AUTO_RES_ERR_NS));
+ return HRTIMER_RESTART;
+}
+
+static bool is_sw_lra_auto_resonance_control(struct qpnp_hap *hap)
+{
+ if (hap->act_type != QPNP_HAP_LRA)
+ return false;
+
+ if (hap->lra_hw_auto_resonance)
+ return false;
+
+ if (!hap->correct_lra_drive_freq)
+ return false;
+
+ if (hap->auto_mode && hap->play_mode == QPNP_HAP_BUFFER)
+ return false;
+
+ return true;
+}
+
+/* set api for haptics */
+static int qpnp_hap_set(struct qpnp_hap *hap, bool on)
+{
+ int rc = 0;
+ unsigned long timeout_ns = POLL_TIME_AUTO_RES_ERR_NS;
+
+ if (hap->play_mode == QPNP_HAP_PWM) {
+ if (on) {
+ rc = pwm_enable(hap->pwm_info.pwm_dev);
+ if (rc < 0)
+ return rc;
+ } else {
+ pwm_disable(hap->pwm_info.pwm_dev);
+ }
+ } else if (hap->play_mode == QPNP_HAP_BUFFER ||
+ hap->play_mode == QPNP_HAP_DIRECT) {
+ if (on) {
+ rc = qpnp_hap_auto_res_enable(hap, 0);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_hap_mod_enable(hap, on);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_hap_play(hap, on);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_hap_auto_res_enable(hap, 1);
+ if (rc < 0)
+ return rc;
+
+ if (is_sw_lra_auto_resonance_control(hap)) {
+ /*
+ * Start timer to poll Auto Resonance error bit
+ */
+ mutex_lock(&hap->lock);
+ hrtimer_cancel(&hap->auto_res_err_poll_timer);
+ hrtimer_start(&hap->auto_res_err_poll_timer,
+ ktime_set(0, timeout_ns),
+ HRTIMER_MODE_REL);
+ mutex_unlock(&hap->lock);
+ }
+ } else {
+ rc = qpnp_hap_play(hap, on);
+ if (rc < 0)
+ return rc;
+
+ if (is_sw_lra_auto_resonance_control(hap) &&
+ (hap->status_flags & AUTO_RESONANCE_ENABLED))
+ update_lra_frequency(hap);
+
+ rc = qpnp_hap_mod_enable(hap, on);
+ if (rc < 0)
+ return rc;
+
+ if (is_sw_lra_auto_resonance_control(hap))
+ hrtimer_cancel(&hap->auto_res_err_poll_timer);
+ }
+ }
+
+ return rc;
+}
+
+static int qpnp_hap_auto_mode_config(struct qpnp_hap *hap, int time_ms)
+{
+ struct qpnp_hap_lra_ares_cfg ares_cfg;
+ enum qpnp_hap_mode old_play_mode;
+ u8 old_ares_mode;
+ u8 brake_pat[QPNP_HAP_BRAKE_PAT_LEN] = {0};
+ u8 wave_samp[QPNP_HAP_WAV_SAMP_LEN] = {0};
+ int rc, vmax_mv;
+
+ /* For now, this is for LRA only */
+ if (hap->act_type == QPNP_HAP_ERM)
+ return 0;
+
+ old_ares_mode = hap->ares_cfg.auto_res_mode;
+ old_play_mode = hap->play_mode;
+ pr_debug("auto_mode, time_ms: %d\n", time_ms);
+ if (time_ms <= 20) {
+ wave_samp[0] = QPNP_HAP_WAV_SAMP_MAX;
+ wave_samp[1] = QPNP_HAP_WAV_SAMP_MAX;
+ if (time_ms > 15)
+ wave_samp[2] = QPNP_HAP_WAV_SAMP_MAX;
+
+ /* short pattern */
+ rc = qpnp_hap_parse_buffer_dt(hap);
+ if (!rc)
+ rc = qpnp_hap_buffer_config(hap, wave_samp, true);
+ if (rc < 0) {
+ pr_err("Error in configuring buffer mode %d\n",
+ rc);
+ return rc;
+ }
+
+ ares_cfg.lra_high_z = QPNP_HAP_LRA_HIGH_Z_OPT1;
+ ares_cfg.lra_res_cal_period = QPNP_HAP_RES_CAL_PERIOD_MIN;
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ ares_cfg.auto_res_mode =
+ QPNP_HAP_PM660_AUTO_RES_QWD;
+ ares_cfg.lra_qwd_drive_duration = 0;
+ ares_cfg.calibrate_at_eop = 0;
+ } else {
+ ares_cfg.auto_res_mode = QPNP_HAP_AUTO_RES_QWD;
+ ares_cfg.lra_qwd_drive_duration = -EINVAL;
+ ares_cfg.calibrate_at_eop = -EINVAL;
+ }
+
+ vmax_mv = QPNP_HAP_VMAX_MAX_MV;
+ rc = qpnp_hap_vmax_config(hap, vmax_mv, true);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_hap_brake_config(hap, brake_pat);
+ if (rc < 0)
+ return rc;
+
+ /* enable play_irq for buffer mode */
+ if (hap->play_irq >= 0 && !hap->play_irq_en) {
+ enable_irq(hap->play_irq);
+ hap->play_irq_en = true;
+ }
+
+ hap->play_mode = QPNP_HAP_BUFFER;
+ hap->wave_shape = QPNP_HAP_WAV_SQUARE;
+ } else {
+ /* long pattern */
+ ares_cfg.lra_high_z = QPNP_HAP_LRA_HIGH_Z_OPT1;
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ ares_cfg.auto_res_mode =
+ QPNP_HAP_PM660_AUTO_RES_ZXD;
+ ares_cfg.lra_res_cal_period =
+ QPNP_HAP_PM660_RES_CAL_PERIOD_MAX;
+ ares_cfg.lra_qwd_drive_duration = 0;
+ ares_cfg.calibrate_at_eop = 1;
+ } else {
+ ares_cfg.auto_res_mode = QPNP_HAP_AUTO_RES_ZXD_EOP;
+ ares_cfg.lra_res_cal_period =
+ QPNP_HAP_RES_CAL_PERIOD_MAX;
+ ares_cfg.lra_qwd_drive_duration = -EINVAL;
+ ares_cfg.calibrate_at_eop = -EINVAL;
+ }
+
+ vmax_mv = hap->vmax_mv;
+ rc = qpnp_hap_vmax_config(hap, vmax_mv, false);
+ if (rc < 0)
+ return rc;
+
+ brake_pat[0] = 0x3;
+ rc = qpnp_hap_brake_config(hap, brake_pat);
+ if (rc < 0)
+ return rc;
+
+ /* enable play_irq for direct mode */
+ if (hap->play_irq >= 0 && hap->play_irq_en) {
+ disable_irq(hap->play_irq);
+ hap->play_irq_en = false;
+ }
+
+ hap->play_mode = QPNP_HAP_DIRECT;
+ hap->wave_shape = QPNP_HAP_WAV_SINE;
+ }
+
+ if (hap->override_auto_mode_config) {
+ rc = qpnp_hap_lra_auto_res_config(hap, NULL);
+ } else {
+ hap->ares_cfg.auto_res_mode = ares_cfg.auto_res_mode;
+ rc = qpnp_hap_lra_auto_res_config(hap, &ares_cfg);
+ }
+
+ if (rc < 0) {
+ hap->ares_cfg.auto_res_mode = old_ares_mode;
+ return rc;
+ }
+
+ rc = qpnp_hap_play_mode_config(hap);
+ if (rc < 0) {
+ hap->play_mode = old_play_mode;
+ return rc;
+ }
+
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_CFG2_REG(hap->base),
+ QPNP_HAP_WAV_SHAPE_MASK, hap->wave_shape);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+/* enable interface from timed output class */
+static void qpnp_hap_td_enable(struct timed_output_dev *dev, int time_ms)
+{
+ struct qpnp_hap *hap = container_of(dev, struct qpnp_hap,
+ timed_dev);
+ int rc;
+
+ if (time_ms < 0)
+ return;
+
+ mutex_lock(&hap->lock);
+
+ if (time_ms == 0) {
+ /* disable haptics */
+ hrtimer_cancel(&hap->hap_timer);
+ hap->state = 0;
+ schedule_work(&hap->work);
+ mutex_unlock(&hap->lock);
+ return;
+ }
+
+ if (time_ms < 10)
+ time_ms = 10;
+
+ if (is_sw_lra_auto_resonance_control(hap))
+ hrtimer_cancel(&hap->auto_res_err_poll_timer);
+
+ hrtimer_cancel(&hap->hap_timer);
+
+ if (hap->auto_mode) {
+ rc = qpnp_hap_auto_mode_config(hap, time_ms);
+ if (rc < 0) {
+ pr_err("Unable to do auto mode config\n");
+ mutex_unlock(&hap->lock);
+ return;
+ }
+ }
+
+ time_ms = (time_ms > hap->timeout_ms ? hap->timeout_ms : time_ms);
+ hap->play_time_ms = time_ms;
+ hap->state = 1;
+ hrtimer_start(&hap->hap_timer,
+ ktime_set(time_ms / 1000, (time_ms % 1000) * 1000000),
+ HRTIMER_MODE_REL);
+ mutex_unlock(&hap->lock);
+ schedule_work(&hap->work);
+}
+
+/* play pwm bytes */
+int qpnp_hap_play_byte(u8 data, bool on)
+{
+ struct qpnp_hap *hap = ghap;
+ int duty_ns, period_ns, duty_percent, rc;
+
+ if (!hap) {
+ pr_err("Haptics is not initialized\n");
+ return -EINVAL;
+ }
+
+ if (hap->play_mode != QPNP_HAP_PWM) {
+ pr_err("only PWM mode is supported\n");
+ return -EINVAL;
+ }
+
+ rc = qpnp_hap_set(hap, false);
+ if (rc)
+ return rc;
+
+ if (!on) {
+ /*
+ * Set the pwm back to original duty for normal operations.
+ * This is not required if standard interface is not used.
+ */
+ rc = pwm_config(hap->pwm_info.pwm_dev,
+ hap->pwm_info.duty_us * NSEC_PER_USEC,
+ hap->pwm_info.period_us * NSEC_PER_USEC);
+ return rc;
+ }
+
+ /*
+ * pwm values range from 0x00 to 0xff. The range from 0x00 to 0x7f
+ * provides a postive amplitude in the sin wave form for 0 to 100%.
+ * The range from 0x80 to 0xff provides a negative amplitude in the
+ * sin wave form for 0 to 100%. Here the duty percentage is calculated
+ * based on the incoming data to accommodate this.
+ */
+ if (data <= QPNP_HAP_EXT_PWM_PEAK_DATA)
+ duty_percent = QPNP_HAP_EXT_PWM_HALF_DUTY +
+ ((data * QPNP_HAP_EXT_PWM_DATA_FACTOR) / 100);
+ else
+ duty_percent = QPNP_HAP_EXT_PWM_FULL_DUTY -
+ ((data * QPNP_HAP_EXT_PWM_DATA_FACTOR) / 100);
+
+ period_ns = hap->pwm_info.period_us * NSEC_PER_USEC;
+ duty_ns = (period_ns * duty_percent) / 100;
+ rc = pwm_config(hap->pwm_info.pwm_dev,
+ duty_ns,
+ hap->pwm_info.period_us * NSEC_PER_USEC);
+ if (rc)
+ return rc;
+
+ pr_debug("data=0x%x duty_per=%d\n", data, duty_percent);
+
+ rc = qpnp_hap_set(hap, true);
+
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_hap_play_byte);
+
+/* worker to opeate haptics */
+static void qpnp_hap_worker(struct work_struct *work)
+{
+ struct qpnp_hap *hap = container_of(work, struct qpnp_hap,
+ work);
+ u8 val = 0x00;
+ int rc;
+
+ if (hap->vcc_pon && hap->state && !hap->vcc_pon_enabled) {
+ rc = regulator_enable(hap->vcc_pon);
+ if (rc < 0)
+ pr_err("could not enable vcc_pon regulator rc=%d\n",
+ rc);
+ else
+ hap->vcc_pon_enabled = true;
+ }
+
+ /* Disable haptics module if the duration of short circuit
+ * exceeds the maximum limit (5 secs).
+ */
+ if (hap->sc_count >= SC_MAX_COUNT) {
+ rc = qpnp_hap_write_reg(hap, QPNP_HAP_EN_CTL_REG(hap->base),
+ val);
+ } else {
+ if (hap->play_mode == QPNP_HAP_PWM)
+ qpnp_hap_mod_enable(hap, hap->state);
+ qpnp_hap_set(hap, hap->state);
+ }
+
+ if (hap->vcc_pon && !hap->state && hap->vcc_pon_enabled) {
+ rc = regulator_disable(hap->vcc_pon);
+ if (rc)
+ pr_err("could not disable vcc_pon regulator rc=%d\n",
+ rc);
+ else
+ hap->vcc_pon_enabled = false;
+ }
+}
+
+/* get time api to know the remaining time */
+static int qpnp_hap_get_time(struct timed_output_dev *dev)
+{
+ struct qpnp_hap *hap = container_of(dev, struct qpnp_hap,
+ timed_dev);
+
+ if (hrtimer_active(&hap->hap_timer)) {
+ ktime_t r = hrtimer_get_remaining(&hap->hap_timer);
+
+ return (int)ktime_to_us(r);
+ } else {
+ return 0;
+ }
+}
+
+/* hrtimer function handler */
+static enum hrtimer_restart qpnp_hap_timer(struct hrtimer *timer)
+{
+ struct qpnp_hap *hap = container_of(timer, struct qpnp_hap,
+ hap_timer);
+
+ hap->state = 0;
+ schedule_work(&hap->work);
+
+ return HRTIMER_NORESTART;
+}
+
+/* hrtimer function handler */
+static enum hrtimer_restart qpnp_hap_test_timer(struct hrtimer *timer)
+{
+ struct qpnp_hap *hap = container_of(timer, struct qpnp_hap,
+ hap_test_timer);
+
+ complete(&hap->completion);
+
+ return HRTIMER_NORESTART;
+}
+
+/* suspend routines to turn off haptics */
+#ifdef CONFIG_PM
+static int qpnp_haptic_suspend(struct device *dev)
+{
+ struct qpnp_hap *hap = dev_get_drvdata(dev);
+
+ hrtimer_cancel(&hap->hap_timer);
+ cancel_work_sync(&hap->work);
+ /* turn-off haptic */
+ qpnp_hap_set(hap, false);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(qpnp_haptic_pm_ops, qpnp_haptic_suspend, NULL);
+
+/* Configuration api for haptics registers */
+static int qpnp_hap_config(struct qpnp_hap *hap)
+{
+ u8 val = 0;
+ int rc;
+
+ /*
+ * This denotes the percentage error in rc clock multiplied by 10
+ */
+ u8 rc_clk_err_percent_x10;
+
+ /* Configure the CFG1 register for actuator type */
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_CFG1_REG(hap->base),
+ QPNP_HAP_ACT_TYPE_MASK, hap->act_type);
+ if (rc)
+ return rc;
+
+ /* Configure auto resonance parameters */
+ rc = qpnp_hap_lra_auto_res_config(hap, NULL);
+ if (rc)
+ return rc;
+
+ /* Configure the PLAY MODE register */
+ rc = qpnp_hap_play_mode_config(hap);
+ if (rc)
+ return rc;
+
+ /* Configure the VMAX register */
+ rc = qpnp_hap_vmax_config(hap, hap->vmax_mv, false);
+ if (rc)
+ return rc;
+
+ /* Configure the ILIM register */
+ rc = qpnp_hap_ilim_config(hap);
+ if (rc)
+ return rc;
+
+ /* Configure the short circuit debounce register */
+ rc = qpnp_hap_sc_deb_config(hap);
+ if (rc)
+ return rc;
+
+ /* Configure the INTERNAL_PWM register */
+ rc = qpnp_hap_int_pwm_config(hap);
+ if (rc)
+ return rc;
+
+ /* Configure the WAVE SHAPE register */
+ rc = qpnp_hap_masked_write_reg(hap, QPNP_HAP_CFG2_REG(hap->base),
+ QPNP_HAP_WAV_SHAPE_MASK, hap->wave_shape);
+ if (rc)
+ return rc;
+
+ /*
+ * Configure RATE_CFG1 and RATE_CFG2 registers.
+ * Note: For ERM these registers act as play rate and
+ * for LRA these represent resonance period
+ */
+ if (hap->wave_play_rate_us < QPNP_HAP_WAV_PLAY_RATE_US_MIN)
+ hap->wave_play_rate_us = QPNP_HAP_WAV_PLAY_RATE_US_MIN;
+ else if (hap->wave_play_rate_us > QPNP_HAP_WAV_PLAY_RATE_US_MAX)
+ hap->wave_play_rate_us = QPNP_HAP_WAV_PLAY_RATE_US_MAX;
+
+ hap->init_drive_period_code =
+ hap->wave_play_rate_us / QPNP_HAP_RATE_CFG_STEP_US;
+
+ /*
+ * The frequency of 19.2Mzhz RC clock is subject to variation. Currently
+ * a few PMI modules have MISC_TRIM_ERROR_RC19P2_CLK register
+ * present in their MISC block. This register holds the frequency error
+ * in 19.2 MHz RC clock.
+ */
+ if ((hap->act_type == QPNP_HAP_LRA) && hap->correct_lra_drive_freq
+ && hap->misc_clk_trim_error_reg) {
+ pr_debug("TRIM register = 0x%x\n", hap->clk_trim_error_code);
+
+ /*
+ * Extract the 4 LSBs and multiply by 7 to get
+ * the %error in RC clock multiplied by 10
+ */
+ rc_clk_err_percent_x10 = (hap->clk_trim_error_code & 0x0F) * 7;
+
+ /*
+ * If the TRIM register holds value less than 0x80,
+ * then there is a positive error in the RC clock.
+ * If the TRIM register holds value greater than or equal to
+ * 0x80, then there is a negative error in the RC clock. Bit 7
+ * is the sign bit for error code.
+ *
+ * The adjusted play rate code is calculated as follows:
+ * LRA drive period code (RATE_CFG) =
+ * 200KHz * 1 / LRA drive frequency * ( 1 + %error/ 100)
+ *
+ * This can be rewritten as:
+ * LRA drive period code (RATE_CFG) =
+ * 200KHz * 1/LRA drive frequency *( 1 + %error * 10/1000)
+ *
+ * Since 200KHz * 1/LRA drive frequency is already calculated
+ * above we only do rest of the scaling here.
+ */
+ if (hap->clk_trim_error_code & BIT(7))
+ LRA_DRIVE_PERIOD_NEG_ERR(hap, rc_clk_err_percent_x10);
+ else
+ LRA_DRIVE_PERIOD_POS_ERR(hap, rc_clk_err_percent_x10);
+ }
+
+ pr_debug("Play rate code 0x%x\n", hap->init_drive_period_code);
+
+ val = hap->init_drive_period_code & QPNP_HAP_RATE_CFG1_MASK;
+ rc = qpnp_hap_write_reg(hap, QPNP_HAP_RATE_CFG1_REG(hap->base), val);
+ if (rc)
+ return rc;
+
+ val = (hap->init_drive_period_code & 0xF00) >> QPNP_HAP_RATE_CFG2_SHFT;
+ rc = qpnp_hap_write_reg(hap, QPNP_HAP_RATE_CFG2_REG(hap->base), val);
+ if (rc)
+ return rc;
+
+ hap->last_rate_cfg = hap->init_drive_period_code;
+
+ if (hap->act_type == QPNP_HAP_LRA &&
+ hap->perform_lra_auto_resonance_search)
+ calculate_lra_code(hap);
+
+ if (hap->act_type == QPNP_HAP_LRA && hap->correct_lra_drive_freq) {
+ hap->drive_period_code_max_limit =
+ (hap->init_drive_period_code * (100 +
+ hap->drive_period_code_max_limit_percent_variation))
+ / 100;
+ hap->drive_period_code_min_limit =
+ (hap->init_drive_period_code * (100 -
+ hap->drive_period_code_min_limit_percent_variation))
+ / 100;
+ pr_debug("Drive period code max limit %x min limit %x\n",
+ hap->drive_period_code_max_limit,
+ hap->drive_period_code_min_limit);
+ }
+
+ rc = qpnp_hap_brake_config(hap, NULL);
+ if (rc < 0)
+ return rc;
+
+ if (hap->play_mode == QPNP_HAP_BUFFER)
+ rc = qpnp_hap_buffer_config(hap, NULL, false);
+ else if (hap->play_mode == QPNP_HAP_PWM)
+ rc = qpnp_hap_pwm_config(hap);
+ else if (hap->play_mode == QPNP_HAP_AUDIO)
+ rc = qpnp_hap_mod_enable(hap, true);
+
+ if (rc)
+ return rc;
+
+ /* setup play irq */
+ if (hap->play_irq >= 0) {
+ rc = devm_request_threaded_irq(&hap->pdev->dev, hap->play_irq,
+ NULL, qpnp_hap_play_irq, IRQF_ONESHOT, "qpnp_hap_play",
+ hap);
+ if (rc < 0) {
+ pr_err("Unable to request play(%d) IRQ(err:%d)\n",
+ hap->play_irq, rc);
+ return rc;
+ }
+
+ /* use play_irq only for buffer mode */
+ if (hap->play_mode != QPNP_HAP_BUFFER) {
+ disable_irq(hap->play_irq);
+ hap->play_irq_en = false;
+ }
+ }
+
+ /* setup short circuit irq */
+ if (hap->sc_irq >= 0) {
+ rc = devm_request_threaded_irq(&hap->pdev->dev, hap->sc_irq,
+ NULL, qpnp_hap_sc_irq, IRQF_ONESHOT, "qpnp_hap_sc",
+ hap);
+ if (rc < 0) {
+ pr_err("Unable to request sc(%d) IRQ(err:%d)\n",
+ hap->sc_irq, rc);
+ return rc;
+ }
+ }
+
+ hap->sc_count = 0;
+
+ return rc;
+}
+
+/* DT parsing for haptics parameters */
+static int qpnp_hap_parse_dt(struct qpnp_hap *hap)
+{
+ struct platform_device *pdev = hap->pdev;
+ struct device_node *misc_node;
+ struct property *prop;
+ const char *temp_str;
+ u32 temp;
+ int rc;
+
+ if (of_find_property(pdev->dev.of_node, "qcom,pmic-misc", NULL)) {
+ misc_node = of_parse_phandle(pdev->dev.of_node,
+ "qcom,pmic-misc", 0);
+ if (!misc_node)
+ return -EINVAL;
+
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,misc-clk-trim-error-reg", &temp);
+ if (rc < 0) {
+ pr_err("Missing misc-clk-trim-error-reg\n");
+ return rc;
+ }
+
+ if (!temp || temp > 0xFF) {
+ pr_err("Invalid misc-clk-trim-error-reg\n");
+ return -EINVAL;
+ }
+
+ hap->misc_clk_trim_error_reg = temp;
+ rc = qpnp_misc_read_reg(misc_node, hap->misc_clk_trim_error_reg,
+ &hap->clk_trim_error_code);
+ if (rc < 0) {
+ pr_err("Couldn't get clk_trim_error_code, rc=%d\n", rc);
+ return -EPROBE_DEFER;
+ }
+ }
+
+ hap->timeout_ms = QPNP_HAP_TIMEOUT_MS_MAX;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,timeout-ms", &temp);
+ if (!rc) {
+ hap->timeout_ms = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read timeout\n");
+ return rc;
+ }
+
+ hap->act_type = QPNP_HAP_LRA;
+ rc = of_property_read_string(pdev->dev.of_node,
+ "qcom,actuator-type", &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "erm") == 0)
+ hap->act_type = QPNP_HAP_ERM;
+ else if (strcmp(temp_str, "lra") == 0)
+ hap->act_type = QPNP_HAP_LRA;
+ else {
+ pr_err("Invalid actuator type\n");
+ return -EINVAL;
+ }
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read actuator type\n");
+ return rc;
+ }
+
+ if (hap->act_type == QPNP_HAP_LRA) {
+ rc = of_property_read_string(pdev->dev.of_node,
+ "qcom,lra-auto-res-mode", &temp_str);
+ if (!rc) {
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_PM660_AUTO_RES_QWD;
+ if (strcmp(temp_str, "zxd") == 0)
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_PM660_AUTO_RES_ZXD;
+ else if (strcmp(temp_str, "qwd") == 0)
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_PM660_AUTO_RES_QWD;
+ } else {
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_AUTO_RES_ZXD_EOP;
+ if (strcmp(temp_str, "none") == 0)
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_AUTO_RES_NONE;
+ else if (strcmp(temp_str, "zxd") == 0)
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_AUTO_RES_ZXD;
+ else if (strcmp(temp_str, "qwd") == 0)
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_AUTO_RES_QWD;
+ else if (strcmp(temp_str, "max-qwd") == 0)
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_AUTO_RES_MAX_QWD;
+ else
+ hap->ares_cfg.auto_res_mode =
+ QPNP_HAP_AUTO_RES_ZXD_EOP;
+ }
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read auto res mode\n");
+ return rc;
+ }
+
+ hap->ares_cfg.lra_high_z = QPNP_HAP_LRA_HIGH_Z_OPT3;
+ rc = of_property_read_string(pdev->dev.of_node,
+ "qcom,lra-high-z", &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "none") == 0)
+ hap->ares_cfg.lra_high_z =
+ QPNP_HAP_LRA_HIGH_Z_NONE;
+ else if (strcmp(temp_str, "opt1") == 0)
+ hap->ares_cfg.lra_high_z =
+ QPNP_HAP_LRA_HIGH_Z_OPT1;
+ else if (strcmp(temp_str, "opt2") == 0)
+ hap->ares_cfg.lra_high_z =
+ QPNP_HAP_LRA_HIGH_Z_OPT2;
+ else
+ hap->ares_cfg.lra_high_z =
+ QPNP_HAP_LRA_HIGH_Z_OPT3;
+
+ if (hap->pmic_subtype == PM660_SUBTYPE) {
+ if (strcmp(temp_str, "opt0") == 0)
+ hap->ares_cfg.lra_high_z =
+ QPNP_HAP_LRA_HIGH_Z_NONE;
+ }
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read LRA high-z\n");
+ return rc;
+ }
+
+ hap->ares_cfg.lra_qwd_drive_duration = -EINVAL;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,lra-qwd-drive-duration",
+ &hap->ares_cfg.lra_qwd_drive_duration);
+
+ hap->ares_cfg.calibrate_at_eop = -EINVAL;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,lra-calibrate-at-eop",
+ &hap->ares_cfg.calibrate_at_eop);
+
+ hap->ares_cfg.lra_res_cal_period = QPNP_HAP_RES_CAL_PERIOD_MAX;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,lra-res-cal-period", &temp);
+ if (!rc) {
+ hap->ares_cfg.lra_res_cal_period = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read cal period\n");
+ return rc;
+ }
+
+ hap->lra_hw_auto_resonance =
+ of_property_read_bool(pdev->dev.of_node,
+ "qcom,lra-hw-auto-resonance");
+
+ hap->perform_lra_auto_resonance_search =
+ of_property_read_bool(pdev->dev.of_node,
+ "qcom,perform-lra-auto-resonance-search");
+
+ hap->correct_lra_drive_freq =
+ of_property_read_bool(pdev->dev.of_node,
+ "qcom,correct-lra-drive-freq");
+
+ hap->drive_period_code_max_limit_percent_variation = 25;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,drive-period-code-max-limit-percent-variation", &temp);
+ if (!rc)
+ hap->drive_period_code_max_limit_percent_variation =
+ (u8) temp;
+
+ hap->drive_period_code_min_limit_percent_variation = 25;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,drive-period-code-min-limit-percent-variation", &temp);
+ if (!rc)
+ hap->drive_period_code_min_limit_percent_variation =
+ (u8) temp;
+
+ if (hap->ares_cfg.auto_res_mode == QPNP_HAP_AUTO_RES_QWD) {
+ hap->time_required_to_generate_back_emf_us =
+ QPNP_HAP_TIME_REQ_FOR_BACK_EMF_GEN;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,time-required-to-generate-back-emf-us",
+ &temp);
+ if (!rc)
+ hap->time_required_to_generate_back_emf_us =
+ temp;
+ } else {
+ hap->time_required_to_generate_back_emf_us = 0;
+ }
+ }
+
+ rc = of_property_read_string(pdev->dev.of_node,
+ "qcom,play-mode", &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "direct") == 0)
+ hap->play_mode = QPNP_HAP_DIRECT;
+ else if (strcmp(temp_str, "buffer") == 0)
+ hap->play_mode = QPNP_HAP_BUFFER;
+ else if (strcmp(temp_str, "pwm") == 0)
+ hap->play_mode = QPNP_HAP_PWM;
+ else if (strcmp(temp_str, "audio") == 0)
+ hap->play_mode = QPNP_HAP_AUDIO;
+ else {
+ pr_err("Invalid play mode\n");
+ return -EINVAL;
+ }
+ } else {
+ pr_err("Unable to read play mode\n");
+ return rc;
+ }
+
+ hap->vmax_mv = QPNP_HAP_VMAX_MAX_MV;
+ rc = of_property_read_u32(pdev->dev.of_node, "qcom,vmax-mv", &temp);
+ if (!rc) {
+ hap->vmax_mv = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read vmax\n");
+ return rc;
+ }
+
+ hap->ilim_ma = QPNP_HAP_ILIM_MIN_MV;
+ rc = of_property_read_u32(pdev->dev.of_node, "qcom,ilim-ma", &temp);
+ if (!rc) {
+ hap->ilim_ma = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read ILim\n");
+ return rc;
+ }
+
+ hap->sc_deb_cycles = QPNP_HAP_DEF_SC_DEB_CYCLES;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,sc-deb-cycles", &temp);
+ if (!rc) {
+ hap->sc_deb_cycles = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read sc debounce\n");
+ return rc;
+ }
+
+ hap->int_pwm_freq_khz = QPNP_HAP_INT_PWM_FREQ_505_KHZ;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,int-pwm-freq-khz", &temp);
+ if (!rc) {
+ hap->int_pwm_freq_khz = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read int pwm freq\n");
+ return rc;
+ }
+
+ hap->wave_shape = QPNP_HAP_WAV_SQUARE;
+ rc = of_property_read_string(pdev->dev.of_node,
+ "qcom,wave-shape", &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "sine") == 0)
+ hap->wave_shape = QPNP_HAP_WAV_SINE;
+ else if (strcmp(temp_str, "square") == 0)
+ hap->wave_shape = QPNP_HAP_WAV_SQUARE;
+ else {
+ pr_err("Unsupported wav shape\n");
+ return -EINVAL;
+ }
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read wav shape\n");
+ return rc;
+ }
+
+ hap->wave_play_rate_us = QPNP_HAP_DEF_WAVE_PLAY_RATE_US;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,wave-play-rate-us", &temp);
+ if (!rc) {
+ hap->wave_play_rate_us = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read play rate\n");
+ return rc;
+ }
+
+ if (hap->play_mode == QPNP_HAP_BUFFER)
+ rc = qpnp_hap_parse_buffer_dt(hap);
+ else if (hap->play_mode == QPNP_HAP_PWM)
+ rc = qpnp_hap_parse_pwm_dt(hap);
+
+ if (rc < 0)
+ return rc;
+
+ hap->en_brake = of_property_read_bool(pdev->dev.of_node,
+ "qcom,en-brake");
+
+ if (hap->en_brake) {
+ prop = of_find_property(pdev->dev.of_node,
+ "qcom,brake-pattern", &temp);
+ if (!prop) {
+ pr_info("brake pattern not found");
+ } else if (temp != QPNP_HAP_BRAKE_PAT_LEN) {
+ pr_err("Invalid len of brake pattern\n");
+ return -EINVAL;
+ } else {
+ hap->sup_brake_pat = true;
+ memcpy(hap->brake_pat, prop->value,
+ QPNP_HAP_BRAKE_PAT_LEN);
+ }
+ }
+
+ hap->play_irq = platform_get_irq_byname(hap->pdev, "play-irq");
+ if (hap->play_irq < 0)
+ pr_warn("Unable to get play irq\n");
+
+ hap->sc_irq = platform_get_irq_byname(hap->pdev, "sc-irq");
+ if (hap->sc_irq < 0) {
+ pr_err("Unable to get sc irq\n");
+ return hap->sc_irq;
+ }
+
+ if (of_find_property(pdev->dev.of_node, "vcc_pon-supply", NULL))
+ hap->manage_pon_supply = true;
+
+ hap->auto_mode = of_property_read_bool(pdev->dev.of_node,
+ "qcom,lra-auto-mode");
+ return 0;
+}
+
+static int qpnp_hap_get_pmic_revid(struct qpnp_hap *hap)
+{
+ struct pmic_revid_data *pmic_rev_id;
+ struct device_node *revid_dev_node;
+
+ revid_dev_node = of_parse_phandle(hap->pdev->dev.of_node,
+ "qcom,pmic-revid", 0);
+ if (!revid_dev_node) {
+ pr_err("Missing qcom,pmic-revid property - driver failed\n");
+ return -EINVAL;
+ }
+ pmic_rev_id = get_revid_data(revid_dev_node);
+ if (IS_ERR_OR_NULL(pmic_rev_id)) {
+ pr_err("Unable to get pmic_revid rc=%ld\n",
+ PTR_ERR(pmic_rev_id));
+ /*
+ * the revid peripheral must be registered, any failure
+ * here only indicates that the rev-id module has not
+ * probed yet.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ hap->pmic_subtype = pmic_rev_id->pmic_subtype;
+
+ return 0;
+}
+
+static int qpnp_haptic_probe(struct platform_device *pdev)
+{
+ struct qpnp_hap *hap;
+ unsigned int base;
+ struct regulator *vcc_pon;
+ int rc, i;
+
+ hap = devm_kzalloc(&pdev->dev, sizeof(*hap), GFP_KERNEL);
+ if (!hap)
+ return -ENOMEM;
+ hap->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!hap->regmap) {
+ pr_err("Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ hap->pdev = pdev;
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ pr_err("Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ return rc;
+ }
+ hap->base = base;
+
+ dev_set_drvdata(&pdev->dev, hap);
+
+ rc = qpnp_hap_get_pmic_revid(hap);
+ if (rc) {
+ pr_err("Unable to check PMIC version rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_hap_parse_dt(hap);
+ if (rc) {
+ pr_err("DT parsing failed\n");
+ return rc;
+ }
+
+ spin_lock_init(&hap->bus_lock);
+ rc = qpnp_hap_config(hap);
+ if (rc) {
+ pr_err("hap config failed\n");
+ return rc;
+ }
+
+ mutex_init(&hap->lock);
+ mutex_init(&hap->wf_lock);
+ INIT_WORK(&hap->work, qpnp_hap_worker);
+ INIT_DELAYED_WORK(&hap->sc_work, qpnp_handle_sc_irq);
+ init_completion(&hap->completion);
+
+ hrtimer_init(&hap->hap_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ hap->hap_timer.function = qpnp_hap_timer;
+
+ hrtimer_init(&hap->hap_test_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ hap->hap_test_timer.function = qpnp_hap_test_timer;
+
+ hap->timed_dev.name = "vibrator";
+ hap->timed_dev.get_time = qpnp_hap_get_time;
+ hap->timed_dev.enable = qpnp_hap_td_enable;
+
+ hrtimer_init(&hap->auto_res_err_poll_timer, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+ hap->auto_res_err_poll_timer.function = detect_auto_res_error;
+
+ rc = timed_output_dev_register(&hap->timed_dev);
+ if (rc < 0) {
+ pr_err("timed_output registration failed\n");
+ goto timed_output_fail;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_hap_attrs); i++) {
+ rc = sysfs_create_file(&hap->timed_dev.dev->kobj,
+ &qpnp_hap_attrs[i].attr);
+ if (rc < 0) {
+ pr_err("sysfs creation failed\n");
+ goto sysfs_fail;
+ }
+ }
+
+ if (hap->manage_pon_supply) {
+ vcc_pon = regulator_get(&pdev->dev, "vcc_pon");
+ if (IS_ERR(vcc_pon)) {
+ rc = PTR_ERR(vcc_pon);
+ pr_err("regulator get failed vcc_pon rc=%d\n", rc);
+ goto sysfs_fail;
+ }
+ hap->vcc_pon = vcc_pon;
+ }
+
+ ghap = hap;
+
+ return 0;
+
+sysfs_fail:
+ for (i--; i >= 0; i--)
+ sysfs_remove_file(&hap->timed_dev.dev->kobj,
+ &qpnp_hap_attrs[i].attr);
+ timed_output_dev_unregister(&hap->timed_dev);
+timed_output_fail:
+ cancel_work_sync(&hap->work);
+ hrtimer_cancel(&hap->auto_res_err_poll_timer);
+ hrtimer_cancel(&hap->hap_timer);
+ mutex_destroy(&hap->lock);
+ mutex_destroy(&hap->wf_lock);
+
+ return rc;
+}
+
+static int qpnp_haptic_remove(struct platform_device *pdev)
+{
+ struct qpnp_hap *hap = dev_get_drvdata(&pdev->dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_hap_attrs); i++)
+ sysfs_remove_file(&hap->timed_dev.dev->kobj,
+ &qpnp_hap_attrs[i].attr);
+
+ cancel_work_sync(&hap->work);
+ hrtimer_cancel(&hap->auto_res_err_poll_timer);
+ hrtimer_cancel(&hap->hap_timer);
+ timed_output_dev_unregister(&hap->timed_dev);
+ mutex_destroy(&hap->lock);
+ mutex_destroy(&hap->wf_lock);
+ if (hap->vcc_pon)
+ regulator_put(hap->vcc_pon);
+
+ return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,qpnp-haptic", },
+ { },
+};
+
+static struct platform_driver qpnp_haptic_driver = {
+ .driver = {
+ .name = "qcom,qpnp-haptic",
+ .of_match_table = spmi_match_table,
+ .pm = &qpnp_haptic_pm_ops,
+ },
+ .probe = qpnp_haptic_probe,
+ .remove = qpnp_haptic_remove,
+};
+
+static int __init qpnp_haptic_init(void)
+{
+ return platform_driver_register(&qpnp_haptic_driver);
+}
+module_init(qpnp_haptic_init);
+
+static void __exit qpnp_haptic_exit(void)
+{
+ return platform_driver_unregister(&qpnp_haptic_driver);
+}
+module_exit(qpnp_haptic_exit);
+
+MODULE_DESCRIPTION("qpnp haptic driver");
+MODULE_LICENSE("GPL v2");