diff options
| -rw-r--r-- | drivers/haptics/Kconfig | 17 | ||||
| -rw-r--r-- | drivers/haptics/Makefile | 1 | ||||
| -rw-r--r-- | drivers/haptics/drv2605.c | 988 | ||||
| -rw-r--r-- | include/linux/haptics/drv2605.h | 458 |
4 files changed, 1464 insertions, 0 deletions
diff --git a/drivers/haptics/Kconfig b/drivers/haptics/Kconfig new file mode 100644 index 00000000000..f3257f091bf --- /dev/null +++ b/drivers/haptics/Kconfig @@ -0,0 +1,17 @@ +# +# For a description of the syntax of this configuration file, +# see Documentation/kbuild/kconfig-language.txt. +# +# Haptics device driver configuration. +# + +menuconfig HAPTICS_DRV2605 + tristate "DRV2605 Haptics Controller" + default n + ---help--- + If you have a vibrator controller by the DRV2605, say Y. + + To compile this as a module, choose M here: + the module will be called drv2605. + + If unsure, say N.
\ No newline at end of file diff --git a/drivers/haptics/Makefile b/drivers/haptics/Makefile new file mode 100644 index 00000000000..a289790929c --- /dev/null +++ b/drivers/haptics/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_HAPTICS_DRV2605) += drv2605.o
\ No newline at end of file diff --git a/drivers/haptics/drv2605.c b/drivers/haptics/drv2605.c new file mode 100644 index 00000000000..8077bda216a --- /dev/null +++ b/drivers/haptics/drv2605.c @@ -0,0 +1,988 @@ +/* +** ============================================================================= +** Copyright (c) 2014 Texas Instruments Inc. +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** 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. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +** File: +** drv2605.c +** +** Description: +** DRV2605 chip driver +** +** ============================================================================= +*/ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/i2c.h> +#include <linux/semaphore.h> +#include <linux/device.h> +#include <linux/syscalls.h> +#include <asm/uaccess.h> +#include <linux/gpio.h> +#include <linux/sched.h> +#include <linux/spinlock_types.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/haptics/drv2605.h> + +static struct drv2605_data *pDRV2605data = NULL; + +static int drv2605_reg_read(struct drv2605_data *pDrv2605data, unsigned int reg) +{ + unsigned int val; + int ret; + + ret = regmap_read(pDrv2605data->regmap, reg, &val); + + if (ret < 0) + return ret; + else + return val; +} + +static int drv2605_reg_write(struct drv2605_data *pDrv2605data, unsigned char reg, char val) +{ + return regmap_write(pDrv2605data->regmap, reg, val); +} + +static int drv2605_bulk_read(struct drv2605_data *pDrv2605data, unsigned char reg, unsigned int count, u8 *buf) +{ + return regmap_bulk_read(pDrv2605data->regmap, reg, buf, count); +} + +static int drv2605_bulk_write(struct drv2605_data *pDrv2605data, unsigned char reg, unsigned int count, const u8 *buf) +{ + return regmap_bulk_write(pDrv2605data->regmap, reg, buf, count); +} + +static int drv2605_set_bits(struct drv2605_data *pDrv2605data, unsigned char reg, unsigned char mask, unsigned char val) +{ + return regmap_update_bits(pDrv2605data->regmap, reg, mask, val); +} + +static int drv2605_set_go_bit(struct drv2605_data *pDrv2605data, unsigned char val) +{ + return drv2605_reg_write(pDrv2605data, GO_REG, (val&0x01)); +} + +static void drv2605_poll_go_bit(struct drv2605_data *pDrv2605data) +{ + while (drv2605_reg_read(pDrv2605data, GO_REG) == GO) + schedule_timeout_interruptible(msecs_to_jiffies(GO_BIT_POLL_INTERVAL)); +} + +static int drv2605_select_library(struct drv2605_data *pDrv2605data, unsigned char lib) +{ + return drv2605_reg_write(pDrv2605data, LIBRARY_SELECTION_REG, (lib&0x07)); +} + +static int drv2605_set_rtp_val(struct drv2605_data *pDrv2605data, char value) +{ + /* please be noted: in unsigned mode, maximum is 0xff, in signed mode, maximum is 0x7f */ + return drv2605_reg_write(pDrv2605data, REAL_TIME_PLAYBACK_REG, value); +} + +static int drv2605_set_waveform_sequence(struct drv2605_data *pDrv2605data, unsigned char* seq, unsigned int size) +{ + return drv2605_bulk_write(pDrv2605data, WAVEFORM_SEQUENCER_REG, (size>WAVEFORM_SEQUENCER_MAX)?WAVEFORM_SEQUENCER_MAX:size, seq); +} + +static void drv2605_change_mode(struct drv2605_data *pDrv2605data, char work_mode, char dev_mode) +{ + /* please be noted : LRA open loop cannot be used with analog input mode */ + if(dev_mode == DEV_IDLE){ + pDrv2605data->dev_mode = dev_mode; + pDrv2605data->work_mode = work_mode; + }else if(dev_mode == DEV_STANDBY){ + if(pDrv2605data->dev_mode != DEV_STANDBY){ + pDrv2605data->dev_mode = DEV_STANDBY; + drv2605_reg_write(pDrv2605data, MODE_REG, MODE_STANDBY); + schedule_timeout_interruptible(msecs_to_jiffies(WAKE_STANDBY_DELAY)); + } + pDrv2605data->work_mode = WORK_IDLE; + }else if(dev_mode == DEV_READY){ + if((work_mode != pDrv2605data->work_mode) + ||(dev_mode != pDrv2605data->dev_mode)){ + pDrv2605data->work_mode = work_mode; + pDrv2605data->dev_mode = dev_mode; + if((pDrv2605data->work_mode == WORK_VIBRATOR) + ||(pDrv2605data->work_mode == WORK_PATTERN_RTP_ON) + ||(pDrv2605data->work_mode == WORK_SEQ_RTP_ON) + ||(pDrv2605data->work_mode == WORK_RTP)){ + drv2605_reg_write(pDrv2605data, MODE_REG, MODE_REAL_TIME_PLAYBACK); + }else if(pDrv2605data->work_mode == WORK_AUDIO2HAPTIC){ + drv2605_reg_write(pDrv2605data, MODE_REG, MODE_AUDIOHAPTIC); + }else if(pDrv2605data->work_mode == WORK_CALIBRATION){ + drv2605_reg_write(pDrv2605data, MODE_REG, AUTO_CALIBRATION); + }else{ + drv2605_reg_write(pDrv2605data, MODE_REG, MODE_INTERNAL_TRIGGER); + schedule_timeout_interruptible(msecs_to_jiffies(STANDBY_WAKE_DELAY)); + } + } + } +} + +static void setAudioHapticsEnabled(struct drv2605_data *pDrv2605data, int enable) +{ + if (enable) + { + if(pDrv2605data->work_mode != WORK_AUDIO2HAPTIC){ + pDrv2605data->vibrator_is_playing = YES; + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_READY); + + drv2605_set_bits(pDrv2605data, + Control1_REG, + Control1_REG_AC_COUPLE_MASK, + AC_COUPLE_ENABLED ); + + drv2605_set_bits(pDrv2605data, + Control3_REG, + Control3_REG_PWMANALOG_MASK, + INPUT_ANALOG); + + drv2605_change_mode(pDrv2605data, WORK_AUDIO2HAPTIC, DEV_READY); + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_AUDIO2HAPTIC); + } + } else { + // Chip needs to be brought out of standby to change the registers + if(pDrv2605data->work_mode == WORK_AUDIO2HAPTIC){ + pDrv2605data->vibrator_is_playing = NO; + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_READY); + + drv2605_set_bits(pDrv2605data, + Control1_REG, + Control1_REG_AC_COUPLE_MASK, + AC_COUPLE_DISABLED ); + + drv2605_set_bits(pDrv2605data, + Control3_REG, + Control3_REG_PWMANALOG_MASK, + INPUT_PWM); + + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_IDLE); + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_STANDBY); // Disable audio-to-haptics + } + } +} + +static void play_effect(struct drv2605_data *pDrv2605data) +{ + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_SEQUENCE_PLAYBACK); + drv2605_change_mode(pDrv2605data, WORK_SEQ_PLAYBACK, DEV_READY); + drv2605_set_waveform_sequence(pDrv2605data, pDrv2605data->sequence, WAVEFORM_SEQUENCER_MAX); + pDrv2605data->vibrator_is_playing = YES; + drv2605_set_go_bit(pDrv2605data, GO); + + while((drv2605_reg_read(pDrv2605data, GO_REG) == GO) && (pDrv2605data->should_stop == NO)){ + schedule_timeout_interruptible(msecs_to_jiffies(GO_BIT_POLL_INTERVAL)); + } + + if(pDrv2605data->should_stop == YES){ + drv2605_set_go_bit(pDrv2605data, STOP); + } + + if (pDrv2605data->audio_haptics_enabled){ + setAudioHapticsEnabled(pDrv2605data, YES); + } else { + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_STANDBY); + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_IDLE); + pDrv2605data->vibrator_is_playing = NO; + wake_unlock(&pDrv2605data->wklock); + } +} + +static void play_Pattern_RTP(struct drv2605_data *pDrv2605data) +{ + if(pDrv2605data->work_mode == WORK_PATTERN_RTP_ON){ + drv2605_change_mode(pDrv2605data, WORK_PATTERN_RTP_OFF, DEV_READY); + if(pDrv2605data->repeat_times == 0){ + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_STANDBY); + pDrv2605data->vibrator_is_playing = NO; + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_IDLE); + wake_unlock(&pDrv2605data->wklock); + }else{ + hrtimer_start(&pDrv2605data->timer, ns_to_ktime((u64)pDrv2605data->silience_time * NSEC_PER_MSEC), HRTIMER_MODE_REL); + } + }else if(pDrv2605data->work_mode == WORK_PATTERN_RTP_OFF){ + pDrv2605data->repeat_times--; + drv2605_change_mode(pDrv2605data, WORK_PATTERN_RTP_ON, DEV_READY); + hrtimer_start(&pDrv2605data->timer, ns_to_ktime((u64)pDrv2605data->vibration_time * NSEC_PER_MSEC), HRTIMER_MODE_REL); + } +} + +static void play_Seq_RTP(struct drv2605_data *pDrv2605data) +{ + if(pDrv2605data->RTPSeq.RTPindex < pDrv2605data->RTPSeq.RTPCounts){ + int RTPTime = pDrv2605data->RTPSeq.RTPData[pDrv2605data->RTPSeq.RTPindex] >> 8; + int RTPVal = pDrv2605data->RTPSeq.RTPData[pDrv2605data->RTPSeq.RTPindex] & 0x00ff ; + + pDrv2605data->vibrator_is_playing = YES; + pDrv2605data->RTPSeq.RTPindex++; + drv2605_change_mode(pDrv2605data, WORK_SEQ_RTP_ON, DEV_READY); + drv2605_set_rtp_val(pDrv2605data, RTPVal); + + hrtimer_start(&pDrv2605data->timer, ns_to_ktime((u64)RTPTime * NSEC_PER_MSEC), HRTIMER_MODE_REL); + }else{ + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_STANDBY); + pDrv2605data->vibrator_is_playing = NO; + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_IDLE); + wake_unlock(&pDrv2605data->wklock); + } +} + +static void vibrator_off(struct drv2605_data *pDrv2605data) +{ + if (pDrv2605data->vibrator_is_playing) { + if(pDrv2605data->audio_haptics_enabled == YES){ + setAudioHapticsEnabled(pDrv2605data, YES); + }else{ + pDrv2605data->vibrator_is_playing = NO; + drv2605_set_go_bit(pDrv2605data, STOP); + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_STANDBY); + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_IDLE); + wake_unlock(&pDrv2605data->wklock); + } + } +} + +static void drv2605_stop(struct drv2605_data *pDrv2605data) +{ + if(pDrv2605data->vibrator_is_playing){ + if(pDrv2605data->work_mode == WORK_AUDIO2HAPTIC){ + setAudioHapticsEnabled(pDrv2605data, NO); + }else if((pDrv2605data->work_mode == WORK_VIBRATOR) + ||(pDrv2605data->work_mode == WORK_PATTERN_RTP_ON) + ||(pDrv2605data->work_mode == WORK_PATTERN_RTP_OFF) + ||(pDrv2605data->work_mode == WORK_SEQ_RTP_ON) + ||(pDrv2605data->work_mode == WORK_SEQ_RTP_OFF) + ||(pDrv2605data->work_mode == WORK_RTP)){ + hrtimer_cancel(&pDrv2605data->timer); + cancel_work_sync(&pDrv2605data->vibrator_work); + vibrator_off(pDrv2605data); + }else if(pDrv2605data->work_mode == WORK_SEQ_PLAYBACK){ + pDrv2605data->should_stop = YES; + cancel_work_sync(&pDrv2605data->vibrator_work); + }else{ + printk("%s, err mode=%d \n", __FUNCTION__, pDrv2605data->work_mode); + } + } +} + +static int vibrator_get_time(struct timed_output_dev *dev) +{ + struct drv2605_data *pDrv2605data = container_of(dev, struct drv2605_data, to_dev); + + if (hrtimer_active(&pDrv2605data->timer)) { + ktime_t r = hrtimer_get_remaining(&pDrv2605data->timer); + return ktime_to_ms(r); + } + + return 0; +} + +static void vibrator_enable( struct timed_output_dev *dev, int value) +{ + struct drv2605_data *pDrv2605data = container_of(dev, struct drv2605_data, to_dev); + + mutex_lock(&pDrv2605data->lock); + + if(pDrv2605data->vibrator_is_playing){ + if(pDrv2605data->work_mode == WORK_AUDIO2HAPTIC){ + setAudioHapticsEnabled(pDrv2605data, NO); + }else if((pDrv2605data->work_mode == WORK_VIBRATOR) + ||(pDrv2605data->work_mode == WORK_PATTERN_RTP_ON) + ||(pDrv2605data->work_mode == WORK_PATTERN_RTP_OFF) + ||(pDrv2605data->work_mode == WORK_SEQ_RTP_ON) + ||(pDrv2605data->work_mode == WORK_SEQ_RTP_OFF) + ||(pDrv2605data->work_mode == WORK_RTP)){ + hrtimer_cancel(&pDrv2605data->timer); + cancel_work_sync(&pDrv2605data->vibrator_work); + vibrator_off(pDrv2605data); + }else if(pDrv2605data->work_mode == WORK_SEQ_PLAYBACK){ + pDrv2605data->should_stop = YES; + cancel_work_sync(&pDrv2605data->vibrator_work); + }else{ + printk("%s, err mode=%d \n", __FUNCTION__, pDrv2605data->work_mode); + } + } + + if (value > 0) { + if(pDrv2605data->audio_haptics_enabled == NO){ + wake_lock(&pDrv2605data->wklock); + } + + drv2605_change_mode(pDrv2605data, WORK_VIBRATOR, DEV_READY); + pDrv2605data->vibrator_is_playing = YES; + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_RTP_PLAYBACK); + + value = (value>MAX_TIMEOUT)?MAX_TIMEOUT:value; + hrtimer_start(&pDrv2605data->timer, ns_to_ktime((u64)value * NSEC_PER_MSEC), HRTIMER_MODE_REL); + } + + mutex_unlock(&pDrv2605data->lock); +} + +static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer) +{ + struct drv2605_data *pDrv2605data = container_of(timer, struct drv2605_data, timer); + mutex_lock(&pDrv2605data->lock); + schedule_work(&pDrv2605data->vibrator_work); + mutex_unlock(&pDrv2605data->lock); + return HRTIMER_NORESTART; +} + +static void vibrator_work_routine(struct work_struct *work) +{ + struct drv2605_data *pDrv2605data = container_of(work, struct drv2605_data, vibrator_work); + + mutex_lock(&pDrv2605data->lock); + if((pDrv2605data->work_mode == WORK_VIBRATOR) + ||(pDrv2605data->work_mode == WORK_RTP)){ + vibrator_off(pDrv2605data); + }else if(pDrv2605data->work_mode == WORK_SEQ_PLAYBACK){ + play_effect(pDrv2605data); + }else if((pDrv2605data->work_mode == WORK_PATTERN_RTP_ON)||(pDrv2605data->work_mode == WORK_PATTERN_RTP_OFF)){ + play_Pattern_RTP(pDrv2605data); + }else if((pDrv2605data->work_mode == WORK_SEQ_RTP_ON)||(pDrv2605data->work_mode == WORK_SEQ_RTP_OFF)){ + play_Seq_RTP(pDrv2605data); + } + mutex_unlock(&pDrv2605data->lock); +} + +static int dev2605_open (struct inode * i_node, struct file * filp) +{ + if(pDRV2605data == NULL){ + return -ENODEV; + } + + filp->private_data = pDRV2605data; + return 0; +} + +static ssize_t dev2605_read(struct file* filp, char* buff, size_t length, loff_t* offset) +{ + struct drv2605_data *pDrv2605data = (struct drv2605_data *)filp->private_data; + int ret = 0; + + if(pDrv2605data->ReadLen > 0){ + ret = copy_to_user(buff, pDrv2605data->ReadBuff, pDrv2605data->ReadLen); + if (ret != 0){ + printk("%s, copy_to_user err=%d \n", __FUNCTION__, ret); + }else{ + ret = pDrv2605data->ReadLen; + } + pDrv2605data->ReadLen = 0; + } + + return ret; +} + +static bool isforDebug(int cmd){ + return ((cmd == HAPTIC_CMDID_REG_WRITE) + ||(cmd == HAPTIC_CMDID_REG_READ) + ||(cmd == HAPTIC_CMDID_REG_SETBIT)); +} + +static ssize_t dev2605_write(struct file* filp, const char* buff, size_t len, loff_t* off) +{ + struct drv2605_data *pDrv2605data = (struct drv2605_data *)filp->private_data; + + mutex_lock(&pDrv2605data->lock); + + if(isforDebug(buff[0])){ + }else{ + if(pDrv2605data->vibrator_is_playing){ + if(pDrv2605data->work_mode == WORK_AUDIO2HAPTIC){ + setAudioHapticsEnabled(pDrv2605data, NO); + }else if((pDrv2605data->work_mode == WORK_VIBRATOR) + ||(pDrv2605data->work_mode == WORK_PATTERN_RTP_ON) + ||(pDrv2605data->work_mode == WORK_PATTERN_RTP_OFF) + ||(pDrv2605data->work_mode == WORK_SEQ_RTP_ON) + ||(pDrv2605data->work_mode == WORK_SEQ_RTP_OFF) + ||(pDrv2605data->work_mode == WORK_RTP)){ + hrtimer_cancel(&pDrv2605data->timer); + cancel_work_sync(&pDrv2605data->vibrator_work); + vibrator_off(pDrv2605data); + }else if(pDrv2605data->work_mode == WORK_SEQ_PLAYBACK){ + pDrv2605data->should_stop = YES; + cancel_work_sync(&pDrv2605data->vibrator_work); + }else{ + printk("%s, err mode=%d \n", __FUNCTION__, pDrv2605data->work_mode); + } + } + } + + switch(buff[0]) + { + case HAPTIC_CMDID_PLAY_SINGLE_EFFECT: + case HAPTIC_CMDID_PLAY_EFFECT_SEQUENCE: + { + memset(&pDrv2605data->sequence, 0, WAVEFORM_SEQUENCER_MAX); + if (!copy_from_user(&pDrv2605data->sequence, &buff[1], len - 1)) + { + if(pDrv2605data->audio_haptics_enabled == NO){ + wake_lock(&pDrv2605data->wklock); + } + pDrv2605data->should_stop = NO; + drv2605_change_mode(pDrv2605data, WORK_SEQ_PLAYBACK, DEV_IDLE); + schedule_work(&pDrv2605data->vibrator_work); + } + break; + } + case HAPTIC_CMDID_PLAY_TIMED_EFFECT: + { + unsigned int value = 0; + value = buff[2]; + value <<= 8; + value |= buff[1]; + + if (value > 0) + { + if(pDrv2605data->audio_haptics_enabled == NO){ + wake_lock(&pDrv2605data->wklock); + } + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_RTP_PLAYBACK); + pDrv2605data->vibrator_is_playing = YES; + value = (value > MAX_TIMEOUT)?MAX_TIMEOUT:value; + drv2605_change_mode(pDrv2605data, WORK_RTP, DEV_READY); + + hrtimer_start(&pDrv2605data->timer, ns_to_ktime((u64)value * NSEC_PER_MSEC), HRTIMER_MODE_REL); + } + break; + } + + case HAPTIC_CMDID_PATTERN_RTP: + { + unsigned char strength = 0; + + pDrv2605data->vibration_time = (int)((((int)buff[2])<<8) | (int)buff[1]); + pDrv2605data->silience_time = (int)((((int)buff[4])<<8) | (int)buff[3]); + strength = buff[5]; + pDrv2605data->repeat_times = buff[6]; + + if(pDrv2605data->vibration_time > 0){ + if(pDrv2605data->audio_haptics_enabled == NO){ + wake_lock(&pDrv2605data->wklock); + } + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_RTP_PLAYBACK); + pDrv2605data->vibrator_is_playing = YES; + if(pDrv2605data->repeat_times > 0) + pDrv2605data->repeat_times--; + if (pDrv2605data->vibration_time > MAX_TIMEOUT) + pDrv2605data->vibration_time = MAX_TIMEOUT; + drv2605_change_mode(pDrv2605data, WORK_PATTERN_RTP_ON, DEV_READY); + drv2605_set_rtp_val(pDrv2605data, strength); + + hrtimer_start(&pDrv2605data->timer, ns_to_ktime((u64)pDrv2605data->vibration_time * NSEC_PER_MSEC), HRTIMER_MODE_REL); + } + break; + } + + case HAPTIC_CMDID_RTP_SEQUENCE: + { + memset(&pDrv2605data->RTPSeq, 0, sizeof(struct RTP_Seq)); + if(((len-1)%2) == 0){ + pDrv2605data->RTPSeq.RTPCounts = (len-1)/2; + if((pDrv2605data->RTPSeq.RTPCounts <= MAX_RTP_SEQ)&&(pDrv2605data->RTPSeq.RTPCounts>0)){ + if(copy_from_user(pDrv2605data->RTPSeq.RTPData, &buff[1], pDrv2605data->RTPSeq.RTPCounts*2) != 0){ + printk("%s, rtp_seq copy seq err\n", __FUNCTION__); + break; + } + + if(pDrv2605data->audio_haptics_enabled == NO){ + wake_lock(&pDrv2605data->wklock); + } + switch_set_state(&pDrv2605data->sw_dev, SW_STATE_RTP_PLAYBACK); + drv2605_change_mode(pDrv2605data, WORK_SEQ_RTP_OFF, DEV_IDLE); + schedule_work(&pDrv2605data->vibrator_work); + }else{ + printk("%s, rtp_seq count error,maximum=%d\n", __FUNCTION__,MAX_RTP_SEQ); + } + }else{ + printk("%s, rtp_seq len error\n", __FUNCTION__); + } + break; + } + + case HAPTIC_CMDID_STOP: + { + break; + } + + case HAPTIC_CMDID_AUDIOHAPTIC_ENABLE: + { + if(pDrv2605data->audio_haptics_enabled == NO){ + wake_lock(&pDrv2605data->wklock); + } + pDrv2605data->audio_haptics_enabled = YES; + setAudioHapticsEnabled(pDrv2605data, YES); + break; + } + + case HAPTIC_CMDID_AUDIOHAPTIC_DISABLE: + { + if(pDrv2605data->audio_haptics_enabled == YES){ + pDrv2605data->audio_haptics_enabled = NO; + wake_unlock(&pDrv2605data->wklock); + } + break; + } + + case HAPTIC_CMDID_REG_READ: + { + if(len == 2){ + pDrv2605data->ReadLen = 1; + pDrv2605data->ReadBuff[0] = drv2605_reg_read(pDrv2605data, buff[1]); + }else if(len == 3){ + pDrv2605data->ReadLen = (buff[2]>MAX_READ_BYTES)?MAX_READ_BYTES:buff[2]; + drv2605_bulk_read(pDrv2605data, buff[1], pDrv2605data->ReadLen, pDrv2605data->ReadBuff); + }else{ + printk("%s, reg_read len error\n", __FUNCTION__); + } + break; + } + + case HAPTIC_CMDID_REG_WRITE: + { + if((len-1) == 2){ + drv2605_reg_write(pDrv2605data, buff[1], buff[2]); + }else if((len-1)>2){ + unsigned char *data = (unsigned char *)kzalloc(len-2, GFP_KERNEL); + if(data != NULL){ + if(copy_from_user(data, &buff[2], len-2) != 0){ + printk("%s, reg copy err\n", __FUNCTION__); + }else{ + drv2605_bulk_write(pDrv2605data, buff[1], len-2, data); + } + kfree(data); + } + }else{ + printk("%s, reg_write len error\n", __FUNCTION__); + } + break; + } + + case HAPTIC_CMDID_REG_SETBIT: + { + int i=1; + for(i=1; i< len; ){ + drv2605_set_bits(pDrv2605data, buff[i], buff[i+1], buff[i+2]); + i += 3; + } + break; + } + default: + printk("%s, unknown HAPTIC cmd\n", __FUNCTION__); + break; + } + + mutex_unlock(&pDrv2605data->lock); + + return len; +} + + +static struct file_operations fops = +{ + .open = dev2605_open, + .read = dev2605_read, + .write = dev2605_write, +}; + +static int Haptics_init(struct drv2605_data *pDrv2605data) +{ + int reval = -ENOMEM; + + pDrv2605data->version = MKDEV(0,0); + reval = alloc_chrdev_region(&pDrv2605data->version, 0, 1, HAPTICS_DEVICE_NAME); + if (reval < 0) + { + printk(KERN_ALERT"drv2605: error getting major number %d\n", reval); + goto fail0; + } + + pDrv2605data->class = class_create(THIS_MODULE, HAPTICS_DEVICE_NAME); + if (!pDrv2605data->class) + { + printk(KERN_ALERT"drv2605: error creating class\n"); + goto fail1; + } + + pDrv2605data->device = device_create(pDrv2605data->class, NULL, pDrv2605data->version, NULL, HAPTICS_DEVICE_NAME); + if (!pDrv2605data->device) + { + printk(KERN_ALERT"drv2605: error creating device 2605\n"); + goto fail2; + } + + cdev_init(&pDrv2605data->cdev, &fops); + pDrv2605data->cdev.owner = THIS_MODULE; + pDrv2605data->cdev.ops = &fops; + reval = cdev_add(&pDrv2605data->cdev, pDrv2605data->version, 1); + if (reval) + { + printk(KERN_ALERT"drv2605: fail to add cdev\n"); + goto fail3; + } + + pDrv2605data->sw_dev.name = "haptics"; + reval = switch_dev_register(&pDrv2605data->sw_dev); + if (reval < 0) { + printk(KERN_ALERT"drv2605: fail to register switch\n"); + goto fail4; + } + + pDrv2605data->to_dev.name = "vibrator"; + pDrv2605data->to_dev.get_time = vibrator_get_time; + pDrv2605data->to_dev.enable = vibrator_enable; + + if (timed_output_dev_register(&(pDrv2605data->to_dev)) < 0) + { + printk(KERN_ALERT"drv2605: fail to create timed output dev\n"); + goto fail3; + } + + hrtimer_init(&pDrv2605data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + pDrv2605data->timer.function = vibrator_timer_func; + INIT_WORK(&pDrv2605data->vibrator_work, vibrator_work_routine); + + wake_lock_init(&pDrv2605data->wklock, WAKE_LOCK_SUSPEND, "vibrator"); + mutex_init(&pDrv2605data->lock); + + return 0; + +fail4: + switch_dev_unregister(&pDrv2605data->sw_dev); +fail3: + device_destroy(pDrv2605data->class, pDrv2605data->version); +fail2: + class_destroy(pDrv2605data->class); +fail1: + unregister_chrdev_region(pDrv2605data->version, 1); +fail0: + return reval; +} + +static void dev_init_platform_data(struct drv2605_data *pDrv2605data) +{ + struct drv2605_platform_data *pDrv2605Platdata = &pDrv2605data->PlatData; + struct actuator_data actuator = pDrv2605Platdata->actuator; + struct audio2haptics_data a2h = pDrv2605Platdata->a2h; + unsigned char temp = 0; + + drv2605_select_library(pDrv2605data, actuator.g_effect_bank); + + //OTP memory saves data from 0x16 to 0x1a + if(pDrv2605data->OTP == 0) { + if(actuator.rated_vol != 0){ + drv2605_reg_write(pDrv2605data, RATED_VOLTAGE_REG, actuator.rated_vol); + }else{ + printk("%s, ERROR Rated ZERO\n", __FUNCTION__); + } + + if(actuator.over_drive_vol != 0){ + drv2605_reg_write(pDrv2605data, OVERDRIVE_CLAMP_VOLTAGE_REG, actuator.over_drive_vol); + }else{ + printk("%s, ERROR OverDriveVol ZERO\n", __FUNCTION__); + } + + drv2605_set_bits(pDrv2605data, + FEEDBACK_CONTROL_REG, + FEEDBACK_CONTROL_DEVICE_TYPE_MASK, + (actuator.device_type == LRA)?FEEDBACK_CONTROL_MODE_LRA:FEEDBACK_CONTROL_MODE_ERM); + }else{ + printk("%s, OTP programmed\n", __FUNCTION__); + } + + if(pDrv2605Platdata->loop == OPEN_LOOP){ + temp = BIDIR_INPUT_BIDIRECTIONAL; + }else{ + if(pDrv2605Platdata->BIDIRInput == UniDirectional){ + temp = BIDIR_INPUT_UNIDIRECTIONAL; + }else{ + temp = BIDIR_INPUT_BIDIRECTIONAL; + } + } + + if(actuator.device_type == LRA){ + unsigned char DriveTime = 5*(1000 - actuator.LRAFreq)/actuator.LRAFreq; + drv2605_set_bits(pDrv2605data, + Control1_REG, + Control1_REG_DRIVE_TIME_MASK, + DriveTime); + printk("%s, LRA = %d, DriveTime=0x%x\n", __FUNCTION__, actuator.LRAFreq, DriveTime); + } + + drv2605_set_bits(pDrv2605data, + Control2_REG, + Control2_REG_BIDIR_INPUT_MASK, + temp); + + if((pDrv2605Platdata->loop == OPEN_LOOP)&&(actuator.device_type == LRA)) + { + temp = LRA_OpenLoop_Enabled; + } + else if((pDrv2605Platdata->loop == OPEN_LOOP)&&(actuator.device_type == ERM)) + { + temp = ERM_OpenLoop_Enabled; + } + else + { + temp = ERM_OpenLoop_Disable|LRA_OpenLoop_Disable; + } + + if((pDrv2605Platdata->loop == CLOSE_LOOP) &&(pDrv2605Platdata->BIDIRInput == UniDirectional)) + { + temp |= RTP_FORMAT_UNSIGNED; + drv2605_reg_write(pDrv2605data, REAL_TIME_PLAYBACK_REG, 0xff); + } + else + { + if(pDrv2605Platdata->RTPFormat == Signed) + { + temp |= RTP_FORMAT_SIGNED; + drv2605_reg_write(pDrv2605data, REAL_TIME_PLAYBACK_REG, 0x7f); + } + else + { + temp |= RTP_FORMAT_UNSIGNED; + drv2605_reg_write(pDrv2605data, REAL_TIME_PLAYBACK_REG, 0xff); + } + } + drv2605_set_bits(pDrv2605data, + Control3_REG, + Control3_REG_LOOP_MASK|Control3_REG_FORMAT_MASK, + temp); + + //for audio to haptics + if(pDrv2605Platdata->GpioTrigger == 0) //not used as external trigger + { + drv2605_reg_write(pDrv2605data, AUDIO_HAPTICS_MIN_INPUT_REG, a2h.a2h_min_input); + drv2605_reg_write(pDrv2605data, AUDIO_HAPTICS_MAX_INPUT_REG, a2h.a2h_max_input); + drv2605_reg_write(pDrv2605data, AUDIO_HAPTICS_MIN_OUTPUT_REG, a2h.a2h_min_output); + drv2605_reg_write(pDrv2605data, AUDIO_HAPTICS_MAX_OUTPUT_REG, a2h.a2h_max_output); + } +} + +static int dev_auto_calibrate(struct drv2605_data *pDrv2605data) +{ + int err = 0, status=0; + + drv2605_change_mode(pDrv2605data, WORK_CALIBRATION, DEV_READY); + drv2605_set_go_bit(pDrv2605data, GO); + + /* Wait until the procedure is done */ + drv2605_poll_go_bit(pDrv2605data); + /* Read status */ + status = drv2605_reg_read(pDrv2605data, STATUS_REG); + + printk("%s, calibration status =0x%x\n", __FUNCTION__, status); + + /* Read calibration results */ + drv2605_reg_read(pDrv2605data, AUTO_CALI_RESULT_REG); + drv2605_reg_read(pDrv2605data, AUTO_CALI_BACK_EMF_RESULT_REG); + drv2605_reg_read(pDrv2605data, FEEDBACK_CONTROL_REG); + + return err; +} + +static struct regmap_config drv2605_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, +}; + +static int drv2605_probe(struct i2c_client* client, const struct i2c_device_id* id) +{ + struct drv2605_data *pDrv2605data; + struct drv2605_platform_data *pDrv2605Platdata = client->dev.platform_data; + + int err = 0; + int status = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + { + printk(KERN_ERR"%s:I2C check failed\n", __FUNCTION__); + return -ENODEV; + } + + pDrv2605data = devm_kzalloc(&client->dev, sizeof(struct drv2605_data), GFP_KERNEL); + if (pDrv2605data == NULL){ + printk(KERN_ERR"%s:no memory\n", __FUNCTION__); + return -ENOMEM; + } + + pDrv2605data->regmap = devm_regmap_init_i2c(client, &drv2605_i2c_regmap); + if (IS_ERR(pDrv2605data->regmap)) { + err = PTR_ERR(pDrv2605data->regmap); + printk(KERN_ERR"%s:Failed to allocate register map: %d\n",__FUNCTION__,err); + return err; + } + + memcpy(&pDrv2605data->PlatData, pDrv2605Platdata, sizeof(struct drv2605_platform_data)); + i2c_set_clientdata(client,pDrv2605data); + + if(pDrv2605data->PlatData.GpioTrigger){ + err = gpio_request(pDrv2605data->PlatData.GpioTrigger,HAPTICS_DEVICE_NAME"Trigger"); + if(err < 0){ + printk(KERN_ERR"%s: GPIO request Trigger error\n", __FUNCTION__); + goto exit_gpio_request_failed; + } + } + + if(pDrv2605data->PlatData.GpioEnable){ + err = gpio_request(pDrv2605data->PlatData.GpioEnable,HAPTICS_DEVICE_NAME"Enable"); + if(err < 0){ + printk(KERN_ERR"%s: GPIO request enable error\n", __FUNCTION__); + goto exit_gpio_request_failed; + } + + /* Enable power to the chip */ + gpio_direction_output(pDrv2605data->PlatData.GpioEnable, 1); + + /* Wait 30 us */ + udelay(30); + } + + err = drv2605_reg_read(pDrv2605data, STATUS_REG); + if(err < 0){ + printk("%s, i2c bus fail (%d)\n", __FUNCTION__, err); + goto exit_gpio_request_failed; + }else{ + printk("%s, i2c status (0x%x)\n", __FUNCTION__, err); + status = err; + } + /* Read device ID */ + pDrv2605data->device_id = (status & DEV_ID_MASK); + switch (pDrv2605data->device_id) + { + case DRV2605_VER_1DOT1: + printk("drv2605 driver found: drv2605 v1.1.\n"); + break; + case DRV2605_VER_1DOT0: + printk("drv2605 driver found: drv2605 v1.0.\n"); + break; + case DRV2604: + printk(KERN_ALERT"drv2605 driver found: drv2604.\n"); + break; + default: + printk(KERN_ERR"drv2605 driver found: unknown.\n"); + break; + } + + if((pDrv2605data->device_id != DRV2605_VER_1DOT1) + &&(pDrv2605data->device_id != DRV2605_VER_1DOT0)){ + printk("%s, status(0x%x),device_id(%d) fail\n", + __FUNCTION__, status, pDrv2605data->device_id); + goto exit_gpio_request_failed; + } + + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_READY); + schedule_timeout_interruptible(msecs_to_jiffies(STANDBY_WAKE_DELAY)); + + pDrv2605data->OTP = drv2605_reg_read(pDrv2605data, AUTOCAL_MEM_INTERFACE_REG) & AUTOCAL_MEM_INTERFACE_REG_OTP_MASK; + + dev_init_platform_data(pDrv2605data); + + if(pDrv2605data->OTP == 0){ + err = dev_auto_calibrate(pDrv2605data); + if(err < 0){ + printk("%s, ERROR, calibration fail\n", __FUNCTION__); + } + } + + /* Put hardware in standby */ + drv2605_change_mode(pDrv2605data, WORK_IDLE, DEV_STANDBY); + + Haptics_init(pDrv2605data); + + pDRV2605data = pDrv2605data; + printk("drv2605 probe succeeded\n"); + + return 0; + +exit_gpio_request_failed: + if(pDrv2605data->PlatData.GpioTrigger){ + gpio_free(pDrv2605data->PlatData.GpioTrigger); + } + + if(pDrv2605data->PlatData.GpioEnable){ + gpio_free(pDrv2605data->PlatData.GpioEnable); + } + + printk(KERN_ERR"%s failed, err=%d\n",__FUNCTION__, err); + return err; +} + +static int drv2605_remove(struct i2c_client* client) +{ + struct drv2605_data *pDrv2605data = i2c_get_clientdata(client); + + device_destroy(pDrv2605data->class, pDrv2605data->version); + class_destroy(pDrv2605data->class); + unregister_chrdev_region(pDrv2605data->version, 1); + + if(pDrv2605data->PlatData.GpioTrigger) + gpio_free(pDrv2605data->PlatData.GpioTrigger); + + if(pDrv2605data->PlatData.GpioEnable) + gpio_free(pDrv2605data->PlatData.GpioEnable); + + printk(KERN_ALERT"drv2605 remove"); + + return 0; +} + +static struct i2c_device_id drv2605_id_table[] = +{ + { HAPTICS_DEVICE_NAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, drv2605_id_table); + +static struct i2c_driver drv2605_driver = +{ + .driver = { + .name = HAPTICS_DEVICE_NAME, + .owner = THIS_MODULE, + }, + .id_table = drv2605_id_table, + .probe = drv2605_probe, + .remove = drv2605_remove, +}; + +static int __init drv2605_init(void) +{ + return i2c_add_driver(&drv2605_driver); +} + +static void __exit drv2605_exit(void) +{ + i2c_del_driver(&drv2605_driver); +} + +module_init(drv2605_init); +module_exit(drv2605_exit); + +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("Driver for "HAPTICS_DEVICE_NAME); diff --git a/include/linux/haptics/drv2605.h b/include/linux/haptics/drv2605.h new file mode 100644 index 00000000000..f448c657755 --- /dev/null +++ b/include/linux/haptics/drv2605.h @@ -0,0 +1,458 @@ +#ifndef __DRV2605_H__ +#define __DRV2605_H__ +/* +** ============================================================================= +** Copyright (c)2014 Texas Instruments Inc. +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** 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. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +** File: +** drv2605.h +** +** Description: +** Header file for drv2605.c +** +** ============================================================================= +*/ + +#include <linux/switch.h> +#include <linux/regmap.h> +#include <linux/timer.h> +#include <linux/workqueue.h> +#include <../../../drivers/staging/android/timed_output.h> +#include <linux/hrtimer.h> +#include <linux/wakelock.h> +#include <linux/mutex.h> +#include <linux/semaphore.h> +#include <linux/cdev.h> + +#define HAPTICS_DEVICE_NAME "drv2605" + +#define LIBRARY_A 0x01 +#define LIBRARY_B 0x02 +#define LIBRARY_C 0x03 +#define LIBRARY_D 0x04 +#define LIBRARY_E 0x05 +#define LIBRARY_F 0x06 + +#define GO_BIT_POLL_INTERVAL 15 +#define STANDBY_WAKE_DELAY 1 +#define WAKE_STANDBY_DELAY 3 + +/* Commands */ +#define HAPTIC_CMDID_PLAY_SINGLE_EFFECT 0x01 +#define HAPTIC_CMDID_PLAY_EFFECT_SEQUENCE 0x02 +#define HAPTIC_CMDID_PLAY_TIMED_EFFECT 0x03 +#define HAPTIC_CMDID_GET_DEV_ID 0x04 +#define HAPTIC_CMDID_RUN_DIAG 0x05 +#define HAPTIC_CMDID_AUDIOHAPTIC_ENABLE 0x06 +#define HAPTIC_CMDID_AUDIOHAPTIC_DISABLE 0x07 +#define HAPTIC_CMDID_AUDIOHAPTIC_GETSTATUS 0x08 + +#define HAPTIC_CMDID_REG_WRITE 0x09 +#define HAPTIC_CMDID_REG_READ 0x0a +#define HAPTIC_CMDID_REG_SETBIT 0x0b + +#define HAPTIC_CMDID_PATTERN_RTP 0x0c +#define HAPTIC_CMDID_RTP_SEQUENCE 0x0d +#define HAPTIC_CMDID_GET_EFFECT_COUNT 0x10 +#define HAPTIC_CMDID_UPDATE_FIRMWARE 0x11 +#define HAPTIC_CMDID_READ_FIRMWARE 0x12 +#define HAPTIC_CMDID_STOP 0xFF + +/* +** Go +*/ +#define GO_REG 0x0C +#define GO_MASK 0x01 +#define GO 0x01 +#define STOP 0x00 + +/* +** Status +*/ +#define STATUS_REG 0x00 +#define STATUS_DEFAULT 0x00 + +#define DIAG_RESULT_MASK (1 << 3) +#define AUTO_CAL_PASSED (0 << 3) +#define AUTO_CAL_FAILED (1 << 3) +#define DIAG_GOOD (0 << 3) +#define DIAG_BAD (1 << 3) + +#define DEV_ID_MASK (7 << 5) + +#define DRV2605_VER_1DOT1 (3 << 5) +#define DRV2605_VER_1DOT0 (5 << 5) +#define DRV2604 (4 << 5) +#define DRV2604L (6 << 5) +#define DRV2605L (7 << 5) + +/* +** Mode +*/ +#define MODE_REG 0x01 +#define MODE_STANDBY_MASK 0x40 +#define MODE_STANDBY 0x40 +#define MODE_RESET 0x80 +#define DRV2605_MODE_MASK 0x07 +#define MODE_INTERNAL_TRIGGER 0 +#define MODE_EXTERNAL_TRIGGER_EDGE 1 +#define MODE_EXTERNAL_TRIGGER_LEVEL 2 +#define MODE_PWM_OR_ANALOG_INPUT 3 +#define MODE_AUDIOHAPTIC 4 +#define MODE_REAL_TIME_PLAYBACK 5 +#define MODE_DIAGNOSTICS 6 +#define AUTO_CALIBRATION 7 + +/* +** Real Time Playback +*/ +#define REAL_TIME_PLAYBACK_REG 0x02 + +/* +** Library Selection +*/ +#define LIBRARY_SELECTION_REG 0x03 +#define LIBRARY_SELECTION_DEFAULT 0x00 +#define LIBRARY_SELECTION_HIZ_MASK 0x10 +#define LIBRARY_SELECTION_HIZ_EN 1 +#define LIBRARY_SELECTION_HIZ_DIS 0 + +/* +** Waveform Sequencer +*/ +#define WAVEFORM_SEQUENCER_REG 0x04 +#define WAVEFORM_SEQUENCER_REG2 0x05 +#define WAVEFORM_SEQUENCER_REG3 0x06 +#define WAVEFORM_SEQUENCER_REG4 0x07 +#define WAVEFORM_SEQUENCER_REG5 0x08 +#define WAVEFORM_SEQUENCER_REG6 0x09 +#define WAVEFORM_SEQUENCER_REG7 0x0A +#define WAVEFORM_SEQUENCER_REG8 0x0B +#define WAVEFORM_SEQUENCER_MAX 8 +#define WAVEFORM_SEQUENCER_DEFAULT 0x00 + +/* +** OverDrive Time Offset +*/ +#define OVERDRIVE_TIME_OFFSET_REG 0x0D + +/* +** Sustain Time Offset, postive +*/ +#define SUSTAIN_TIME_OFFSET_POS_REG 0x0E + +/* +** Sustain Time Offset, negative +*/ +#define SUSTAIN_TIME_OFFSET_NEG_REG 0x0F + +/* +** Brake Time Offset +*/ +#define BRAKE_TIME_OFFSET_REG 0x10 + +/* +** Audio to Haptics Control +*/ +#define AUDIO_HAPTICS_CONTROL_REG 0x11 + +#define AUDIO_HAPTICS_RECT_10MS (0 << 2) +#define AUDIO_HAPTICS_RECT_20MS (1 << 2) +#define AUDIO_HAPTICS_RECT_30MS (2 << 2) +#define AUDIO_HAPTICS_RECT_40MS (3 << 2) + +#define AUDIO_HAPTICS_FILTER_100HZ 0 +#define AUDIO_HAPTICS_FILTER_125HZ 1 +#define AUDIO_HAPTICS_FILTER_150HZ 2 +#define AUDIO_HAPTICS_FILTER_200HZ 3 + +/* +** Audio to Haptics Minimum Input Level +*/ +#define AUDIO_HAPTICS_MIN_INPUT_REG 0x12 + +/* +** Audio to Haptics Maximum Input Level +*/ +#define AUDIO_HAPTICS_MAX_INPUT_REG 0x13 + +/* +** Audio to Haptics Minimum Output Drive +*/ +#define AUDIO_HAPTICS_MIN_OUTPUT_REG 0x14 + +/* +** Audio to Haptics Maximum Output Drive +*/ +#define AUDIO_HAPTICS_MAX_OUTPUT_REG 0x15 + +/* +** Rated Voltage +*/ +#define RATED_VOLTAGE_REG 0x16 + +/* +** Overdrive Clamp Voltage +*/ +#define OVERDRIVE_CLAMP_VOLTAGE_REG 0x17 + +/* +** Auto Calibrationi Compensation Result +*/ +#define AUTO_CALI_RESULT_REG 0x18 + +/* +** Auto Calibration Back-EMF Result +*/ +#define AUTO_CALI_BACK_EMF_RESULT_REG 0x19 + +/* +** Feedback Control +*/ +#define FEEDBACK_CONTROL_REG 0x1A +#define FEEDBACK_CONTROL_DEVICE_TYPE_MASK 0x80 +#define FEEDBACK_CONTROL_BEMF_ERM_GAIN0 0 // 0.33x +#define FEEDBACK_CONTROL_BEMF_ERM_GAIN1 1 // 1.0x +#define FEEDBACK_CONTROL_BEMF_ERM_GAIN2 2 // 1.8x +#define FEEDBACK_CONTROL_BEMF_ERM_GAIN3 3 // 4.0x + +#define FEEDBACK_CONTROL_BEMF_LRA_GAIN0 0 // 5x +#define FEEDBACK_CONTROL_BEMF_LRA_GAIN1 1 // 10x +#define FEEDBACK_CONTROL_BEMF_LRA_GAIN2 2 // 20x +#define FEEDBACK_CONTROL_BEMF_LRA_GAIN3 3 // 30x + +#define LOOP_RESPONSE_SLOW (0 << 2) +#define LOOP_RESPONSE_MEDIUM (1 << 2) // default +#define LOOP_RESPONSE_FAST (2 << 2) +#define LOOP_RESPONSE_VERY_FAST (3 << 2) + +#define FB_BRAKE_FACTOR_1X (0 << 4) // 1x +#define FB_BRAKE_FACTOR_2X (1 << 4) // 2x +#define FB_BRAKE_FACTOR_3X (2 << 4) // 3x (default) +#define FB_BRAKE_FACTOR_4X (3 << 4) // 4x +#define FB_BRAKE_FACTOR_6X (4 << 4) // 6x +#define FB_BRAKE_FACTOR_8X (5 << 4) // 8x +#define FB_BRAKE_FACTOR_16X (6 << 4) // 16x +#define FB_BRAKE_DISABLED (7 << 4) + +#define FEEDBACK_CONTROL_MODE_ERM 0 // default +#define FEEDBACK_CONTROL_MODE_LRA (1 << 7) + +/* +** Control1 +*/ +#define Control1_REG 0x1B +#define Control1_REG_AC_COUPLE_MASK 0x20 +#define Control1_REG_DRIVE_TIME_MASK 0x1f + +#define STARTUP_BOOST_ENABLED (1 << 7) +#define STARTUP_BOOST_DISABLED (0 << 7) // default +#define AC_COUPLE_ENABLED (1 << 5) +#define AC_COUPLE_DISABLED (0 << 5) // default + +#define DEFAULT_DRIVE_TIME 0x13 +#define AUDIOHAPTIC_DRIVE_TIME 0x13 + +/* +** Control2 +*/ +#define Control2_REG 0x1C +#define Control2_REG_BIDIR_INPUT_MASK 0x80 + +#define BIDIR_INPUT_UNIDIRECTIONAL (0<<7) +#define BIDIR_INPUT_BIDIRECTIONAL (1<<7) +#define IDISS_TIME_MASK 0x03 +#define IDISS_TIME_VERY_SHORT 0 +#define IDISS_TIME_SHORT 1 +#define IDISS_TIME_MEDIUM 2 // default +#define IDISS_TIME_LONG 3 + +#define BLANKING_TIME_MASK 0x0C +#define BLANKING_TIME_VERY_SHORT (0 << 2) +#define BLANKING_TIME_SHORT (1 << 2) +#define BLANKING_TIME_MEDIUM (2 << 2) // default +#define BLANKING_TIME_VERY_LONG (3 << 2) + +#define AUTO_RES_GAIN_MASK 0x30 +#define AUTO_RES_GAIN_VERY_LOW (0 << 4) +#define AUTO_RES_GAIN_LOW (1 << 4) +#define AUTO_RES_GAIN_MEDIUM (2 << 4) // default +#define AUTO_RES_GAIN_HIGH (3 << 4) + +#define SOFT_BRAKE_MASK 0x40 + +#define BIDIR_INPUT_MASK 0x80 +#define UNIDIRECT_INPUT (0 << 7) +#define BRAKE_STABLIZER (1<<6) +#define BIDIRECT_INPUT (1 << 7) // default + +/* +** Control3 +*/ +#define Control3_REG 0x1D +#define Control3_REG_LOOP_MASK 0x21 +#define Control3_REG_PWMANALOG_MASK 0x02 +#define Control3_REG_FORMAT_MASK 0x08 +#define INPUT_PWM (0 << 1) // default +#define INPUT_ANALOG (1 << 1) +#define ERM_OpenLoop_Enabled (1 << 5) +#define ERM_OpenLoop_Disable (0 << 5) +#define LRA_OpenLoop_Enabled (1 << 0) +#define LRA_OpenLoop_Disable (0 << 0) +#define RTP_FORMAT_SIGNED (0 << 3) +#define RTP_FORMAT_UNSIGNED (1 << 3) +#define NG_Thresh_DISABLED (0 << 6) +#define NG_Thresh_1 (1 << 6) +#define NG_Thresh_2 (2 << 6) +#define NG_Thresh_3 (3 << 6) + +/* +** Auto Calibration Memory Interface +*/ +#define AUTOCAL_MEM_INTERFACE_REG 0x1E +#define AUTOCAL_MEM_INTERFACE_REG_OTP_MASK 0x04 + +#define AUTOCAL_TIME_150MS (0 << 4) +#define AUTOCAL_TIME_250MS (1 << 4) +#define AUTOCAL_TIME_500MS (2 << 4) +#define AUTOCAL_TIME_1000MS (3 << 4) + +#define SILICON_REVISION_REG 0x3B +#define SILICON_REVISION_MASK 0x07 + +//reset values +#define AUDIO_HAPTICS_MIN_INPUT_VOLTAGE 0x19 +#define AUDIO_HAPTICS_MAX_INPUT_VOLTAGE 0xff +#define AUDIO_HAPTICS_MIN_OUTPUT_VOLTAGE 0x19 +#define AUDIO_HAPTICS_MAX_OUTPUT_VOLTAGE 0xFF + +#define MAX_TIMEOUT 10000 /* 10s */ +#define MAX_READ_BYTES 0xff + +#define SW_STATE_IDLE 0x00 +#define SW_STATE_AUDIO2HAPTIC 0x01 +#define SW_STATE_SEQUENCE_PLAYBACK 0x02 +#define SW_STATE_RTP_PLAYBACK 0x04 + +#define DEV_IDLE 0 // default +#define DEV_STANDBY 1 +#define DEV_READY 2 + +#define WORK_IDLE 0x00 +#define WORK_AUDIO2HAPTIC 0x05 +#define WORK_RTP 0x06 +#define WORK_CALIBRATION 0x07 +#define WORK_VIBRATOR 0x08 +#define WORK_PATTERN_RTP_ON 0x09 +#define WORK_PATTERN_RTP_OFF 0x0a +#define WORK_SEQ_RTP_ON 0x0b +#define WORK_SEQ_RTP_OFF 0x0c +#define WORK_SEQ_PLAYBACK 0x0d + +#define YES 1 +#define NO 0 + +enum actuator_type { + ERM, + LRA +}; + +enum loop_type { + OPEN_LOOP, + CLOSE_LOOP +}; + +enum RTP_Format { + Signed, + Unsigned +}; + +enum BIDIR_Input { + UniDirectional, + BiDirectional +}; + +struct actuator_data { + enum actuator_type device_type; + unsigned char g_effect_bank; + unsigned char rated_vol; + unsigned char over_drive_vol; + unsigned char LRAFreq; +}; + +struct audio2haptics_data { + unsigned char a2h_min_input; + unsigned char a2h_max_input; + unsigned char a2h_min_output; + unsigned char a2h_max_output; +}; + +struct drv2605_platform_data { + int GpioEnable; + int GpioTrigger; + enum loop_type loop; + enum RTP_Format RTPFormat; + enum BIDIR_Input BIDIRInput; + struct actuator_data actuator; + struct audio2haptics_data a2h; +}; + +#define MAX_RTP_SEQ 16 + +struct RTP_Seq{ + unsigned short RTPData[MAX_RTP_SEQ]; //RTPTime<<8||RTPAmp + int RTPCounts; + int RTPindex; +}; + +struct drv2605_data { + struct drv2605_platform_data PlatData; + int device_id; + + struct regmap *regmap; + + struct class* class; + struct device* device; + dev_t version; + struct semaphore sem; + struct cdev cdev; + struct switch_dev sw_dev; + volatile int audio_haptics_enabled; + volatile int vibrator_is_playing; + char ReadBuff[MAX_READ_BYTES]; + int ReadLen; + + int vibration_time; + int silience_time; + char repeat_times; + volatile char work_mode; + char dev_mode; + + struct RTP_Seq RTPSeq; + + int OTP; + + struct wake_lock wklock; + struct hrtimer timer; + struct mutex lock; + struct work_struct vibrator_work; + unsigned char sequence[WAVEFORM_SEQUENCER_MAX]; + volatile int should_stop; + struct timed_output_dev to_dev; +}; + +#endif |