diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/motor | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 |
samsung update 1
Diffstat (limited to 'drivers/motor')
-rw-r--r-- | drivers/motor/ImmVibeSPI.c | 268 | ||||
-rw-r--r-- | drivers/motor/ImmVibeSPI_isa1200.c | 561 | ||||
-rw-r--r-- | drivers/motor/Kconfig | 32 | ||||
-rw-r--r-- | drivers/motor/Makefile | 10 | ||||
-rw-r--r-- | drivers/motor/VibeOSKernelLinuxHRTime.c | 233 | ||||
-rw-r--r-- | drivers/motor/isa1200_vibrator.c | 386 | ||||
-rw-r--r-- | drivers/motor/max77693_haptic.c | 388 | ||||
-rw-r--r-- | drivers/motor/max8997_vibrator.c | 336 | ||||
-rw-r--r-- | drivers/motor/tspdrv.c | 587 | ||||
-rw-r--r-- | drivers/motor/tspdrv.h | 110 |
10 files changed, 2911 insertions, 0 deletions
diff --git a/drivers/motor/ImmVibeSPI.c b/drivers/motor/ImmVibeSPI.c new file mode 100644 index 0000000..dea4ea4 --- /dev/null +++ b/drivers/motor/ImmVibeSPI.c @@ -0,0 +1,268 @@ +/* +** ========================================================================= +** File: +** ImmVibeSPI.c +** +** Description: +** Device-dependent functions called by Immersion TSP API +** to control PWM duty cycle, amp enable/disable, save IVT file, etc... +** +** Portions Copyright (c) 2008-2010 Immersion Corporation. All Rights Reserved. +** +** This file contains Original Code and/or Modifications of Original Code +** as defined in and that are subject to the GNU Public License v2 - +** (the 'License'). You may not use this file except in compliance with the +** License. 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 or contact +** TouchSenseSales@immersion.com. +** +** The Original Code and all software distributed under the License are +** distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +** EXPRESS OR IMPLIED, AND IMMERSION HEREBY DISCLAIMS ALL SUCH WARRANTIES, +** INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS +** FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see +** the License for the specific language governing rights and limitations +** under the License. +** ========================================================================= +*/ +#include <linux/pwm.h> +#include <plat/gpio-cfg.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> + +#include "tspdrv.h" + +#ifdef IMMVIBESPIAPI +#undef IMMVIBESPIAPI +#endif +#define IMMVIBESPIAPI static + +/* +** This SPI supports only one actuator. +*/ +#define NUM_ACTUATORS 1 + +#define PWM_DUTY_MAX 579 /* 13MHz / (579 + 1) = 22.4kHz */ + +static bool g_bAmpEnabled; + +extern void vibtonz_en(bool en); +extern void vibtonz_pwm(int nForce); + +/* +** Called to disable amp (disable output force) +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_AmpDisable(VibeUInt8 nActuatorIndex) +{ + /*printk(KERN_ERR"[VIBRATOR] ImmVibeSPI_ForceOut_AmpDisable is called\n");*/ +#if 0 + if (g_bAmpEnabled) { + DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_AmpDisable.\n")); + + g_bAmpEnabled = false; + + /* Disable amp */ + + + /* Disable PWM CLK */ + + } +#endif + + if (g_bAmpEnabled) { + + g_bAmpEnabled = false; +#if !defined(CONFIG_MACH_P4NOTE) + vibtonz_pwm(0); + vibtonz_en(false); +#endif + if (regulator_hapticmotor_enabled == 1) { + regulator_hapticmotor_enabled = 0; + + printk(KERN_DEBUG "Motor:tspdrv: %s (%d)\n", __func__, + regulator_hapticmotor_enabled); + + } + } + + return VIBE_S_SUCCESS; +} + +/* +** Called to enable amp (enable output force) +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_AmpEnable(VibeUInt8 nActuatorIndex) +{ + /*printk(KERN_ERR"[VIBRATOR]ImmVibeSPI_ForceOut_AmpEnable is called\n");*/ +#if 0 + if (!g_bAmpEnabled) { + DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_AmpEnable.\n")); + + g_bAmpEnabled = true; + + /* Generate PWM CLK with proper frequency(ex. 22400Hz) and 50% duty cycle.*/ + + + /* Enable amp */ + + } +#endif + + if (!g_bAmpEnabled) { +#if !defined(CONFIG_MACH_P4NOTE) + vibtonz_en(true); +#endif + + g_bAmpEnabled = true; + regulator_hapticmotor_enabled = 1; + + printk(KERN_DEBUG "Motor:tspdrv: %s (%d)\n", __func__, + regulator_hapticmotor_enabled); + + } + + return VIBE_S_SUCCESS; +} + +/* +** Called at initialization time to set PWM freq, disable amp, etc... +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_Initialize(void) +{ + /*printk(KERN_ERR"[VIBRATOR]ImmVibeSPI_ForceOut_Initialize is called\n");*/ + DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_Initialize.\n")); + + g_bAmpEnabled = true; /* to force ImmVibeSPI_ForceOut_AmpDisable disabling the amp */ + + /* + ** Disable amp. + ** If multiple actuators are supported, please make sure to call + ** ImmVibeSPI_ForceOut_AmpDisable for each actuator (provide the actuator index as + ** input argument). + */ + + ImmVibeSPI_ForceOut_AmpDisable(0); + + regulator_hapticmotor_enabled = 0; + + return VIBE_S_SUCCESS; +} + +/* +** Called at termination time to set PWM freq, disable amp, etc... +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_Terminate(void) +{ + DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_Terminate.\n")); + + /* + ** Disable amp. + ** If multiple actuators are supported, please make sure to call + ** ImmVibeSPI_ForceOut_AmpDisable for each actuator (provide the actuator index as + ** input argument). + */ + ImmVibeSPI_ForceOut_AmpDisable(0); + + return VIBE_S_SUCCESS; +} + +/* +** Called by the real-time loop to set PWM duty cycle +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_SetSamples(VibeUInt8 nActuatorIndex, + VibeUInt16 nOutputSignalBitDepth, + VibeUInt16 nBufferSizeInBytes, + VibeInt8 * pForceOutputBuffer) +{ +#if 0 + VibeInt8 nForce; + + switch (nOutputSignalBitDepth) { + case 8: + /* pForceOutputBuffer is expected to contain 1 byte */ + if (nBufferSizeInBytes != 1) { + DbgOut((KERN_ERR "[ImmVibeSPI] ImmVibeSPI_ForceOut_SetSamples nBufferSizeInBytes = %d\n", nBufferSizeInBytes)); + return VIBE_E_FAIL; + } + nForce = pForceOutputBuffer[0]; + break; + case 16: + /* pForceOutputBuffer is expected to contain 2 byte */ + if (nBufferSizeInBytes != 2) + return VIBE_E_FAIL; + + /* Map 16-bit value to 8-bit */ + nForce = ((VibeInt16 *)pForceOutputBuffer)[0] >> 8; + break; + default: + /* Unexpected bit depth */ + return VIBE_E_FAIL; + } + + if (nForce == 0) + /* Set 50% duty cycle or disable amp */ + else + /* Map force from [-127, 127] to [0, PWM_DUTY_MAX] */ + + +#endif + + VibeInt8 nForce; + + switch (nOutputSignalBitDepth) { + case 8: + /* pForceOutputBuffer is expected to contain 1 byte */ + if (nBufferSizeInBytes != 1) { + DbgOut((KERN_ERR "[ImmVibeSPI] ImmVibeSPI_ForceOut_SetSamples nBufferSizeInBytes = %d\n", nBufferSizeInBytes)); + return VIBE_E_FAIL; + } + nForce = pForceOutputBuffer[0]; + break; + case 16: + /* pForceOutputBuffer is expected to contain 2 byte */ + if (nBufferSizeInBytes != 2) + return VIBE_E_FAIL; + + /* Map 16-bit value to 8-bit */ + nForce = ((VibeInt16 *)pForceOutputBuffer)[0] >> 8; + break; + default: + /* Unexpected bit depth */ + return VIBE_E_FAIL; + } + + if (nForce == 0) { + /* Set 50% duty cycle or disable amp */ + ImmVibeSPI_ForceOut_AmpDisable(0); + } else { + /* Map force from [-127, 127] to [0, PWM_DUTY_MAX] */ + ImmVibeSPI_ForceOut_AmpEnable(0); +#if !defined(CONFIG_MACH_P4NOTE) + vibtonz_pwm(nForce); +#endif + } + + return VIBE_S_SUCCESS; +} + +#if 0 /* Unused */ +/* +** Called to set force output frequency parameters +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_SetFrequency(VibeUInt8 nActuatorIndex, VibeUInt16 nFrequencyParameterID, VibeUInt32 nFrequencyParameterValue) +{ + return VIBE_S_SUCCESS; +} +#endif + +/* +** Called to get the device name (device name must be returned as ANSI char) +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_Device_GetName(VibeUInt8 nActuatorIndex, char *szDevName, int nSize) +{ + return VIBE_S_SUCCESS; +} + diff --git a/drivers/motor/ImmVibeSPI_isa1200.c b/drivers/motor/ImmVibeSPI_isa1200.c new file mode 100644 index 0000000..42c36b5 --- /dev/null +++ b/drivers/motor/ImmVibeSPI_isa1200.c @@ -0,0 +1,561 @@ +/* +** ========================================================================= +** File: +** ImmVibeSPI.c +** +** Description: +** Device-dependent functions called by Immersion TSP API +** to control PWM duty cycle, amp enable/disable, save IVT file, etc... +** +** Portions Copyright (c) 2008-2010 Immersion Corporation. All Rights Reserved. +** +** This file contains Original Code and/or Modifications of Original Code +** as defined in and that are subject to the GNU Public License v2 - +** (the 'License'). You may not use this file except in compliance with the +** License. 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 or contact +** TouchSenseSales@immersion.com. +** +** The Original Code and all software distributed under the License are +** distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +** EXPRESS OR IMPLIED, AND IMMERSION HEREBY DISCLAIMS ALL SUCH WARRANTIES, +** INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS +** FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see +** the License for the specific language governing rights and limitations +** under the License. +** ========================================================================= +*/ + +#ifdef IMMVIBESPIAPI +#undef IMMVIBESPIAPI +#endif +#define IMMVIBESPIAPI static + +#include <linux/isa1200_vibrator.h> + +/* +** This SPI supports only one actuator. +*/ +#define NUM_ACTUATORS 1 + +#define ISA1200_I2C_ADDRESS 0x90 +#define SCTRL (0) +#define HCTRL0 (0x30) +#define HCTRL1 (0x31) +#define HCTRL2 (0x32) +#define HCTRL3 (0x33) +#define HCTRL4 (0x34) +#define HCTRL5 (0x35) +#define HCTRL6 (0x36) +#define HCTRL7 (0x37) +#define HCTRL8 (0x38) +#define HCTRL9 (0x39) +#define HCTRLA (0x3A) +#define HCTRLB (0x3B) +#define HCTRLC (0x3C) +#define HCTRLD (0x3D) + +#define LDO_VOLTAGE_23V 0x08 +#define LDO_VOLTAGE_24V 0x09 +#define LDO_VOLTAGE_25V 0x0A +#define LDO_VOLTAGE_26V 0x0B +#define LDO_VOLTAGE_27V 0x0C +#define LDO_VOLTAGE_28V 0x0D +#define LDO_VOLTAGE_29V 0x0E +#define LDO_VOLTAGE_30V 0x0F +#define LDO_VOLTAGE_31V 0x00 +#define LDO_VOLTAGE_32V 0x01 +#define LDO_VOLTAGE_33V 0x02 +#define LDO_VOLTAGE_34V 0x03 +#define LDO_VOLTAGE_35V 0x04 +#define LDO_VOLTAGE_36V 0x05 +#define LDO_VOLTAGE_37V 0x06 +#define LDO_VOLTAGE_38V 0x07 + +static bool g_bAmpEnabled; + +/* Variable defined to allow for tuning of the handset. */ +/* For temporary section for Tuning Work */ +#if 0 +#define VIBETONZ_TUNING +#define ISA1200_GEN_MODE +#endif +#define MOTOR_TYPE_LRA + +#ifdef VIBETONZ_TUNING +extern VibeStatus ImmVibeGetDeviceKernelParameter(VibeInt32 nDeviceIndex, VibeInt32 nDeviceKernelParamID, VibeInt32 *pnDeviceKernelParamValue); +#endif /* VIBETONZ_TUNING */ + +/* PWM mode selection */ +#ifndef ISA1200_GEN_MODE +#define ISA1200_PWM_MODE +#if 0 +#define ISA1200_PWM_256DIV_MODE +#else +#define ISA1200_PWM_128DIV_MODE +#endif +#endif + +/* Actuator selection */ +#ifndef MOTOR_TYPE_LRA +#define MOTOR_TYPE_ERM +#endif + +#define ISA1200_HEN_ENABLE +#define RETRY_CNT 10 + +#define SYS_API_LEN_HIGH vibtonz_chip_enable(true) +#define SYS_API_LEN_LOW vibtonz_chip_enable(false) +#define SYS_API_HEN_HIGH +#define SYS_API_HEN_LOW +#define SYS_API_VDDP_ON +#define SYS_API_VDDP_OFF +#define SYS_API__I2C__Write( _addr, _dataLength, _data) \ + vibtonz_i2c_write(_addr, _dataLength, _data) +#define SLEEP(_us_time) + +#define PWM_CLK_ENABLE vibtonz_clk_enable(true) +#define PWM_CLK_DISABLE vibtonz_clk_enable(false) + +#define DEBUG_MSG(fmt, ...) \ + printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) + +#define SYS_API_SET_PWM_FREQ(freq) +#define SYS_API_SET_PWM_DUTY(ratio) vibtonz_clk_config(ratio) + +#ifdef ISA1200_GEN_MODE +#define PWM_PLLDIV_DEFAULT 0x02 +#define PWM_FREQ_DEFAULT 0x00 +#define PWM_PERIOD_DEFAULT 0x74 +#define PWM_DUTY_DEFAULT 0x3a +VibeUInt32 g_nPWM_PLLDiv = PWM_PLLDIV_DEFAULT; +VibeUInt32 g_nPWM_Freq = PWM_FREQ_DEFAULT; +VibeUInt32 g_nPWM_Period = PWM_PERIOD_DEFAULT; +VibeUInt32 g_nPWM_Duty = PWM_DUTY_DEFAULT; +#else +#ifdef ISA1200_PWM_256DIV_MODE +#define PWM_PERIOD_DEFAULT 44800 +#else +#define PWM_PERIOD_DEFAULT 22400 +#endif +VibeUInt32 g_nPWM_Freq = PWM_PERIOD_DEFAULT; +#endif + +#define LDO_VOLTAGE_DEFAULT LDO_VOLTAGE_30V +VibeUInt32 g_nLDO_Voltage = LDO_VOLTAGE_DEFAULT; + +#define DEVICE_NAME "Generic" + +/* +** Called to disable amp (disable output force) +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_AmpDisable(VibeUInt8 nActuatorIndex) +{ + int cnt = 0; + unsigned char I2C_data[2]; + int ret = VIBE_S_SUCCESS; + + if (g_bAmpEnabled) + { + DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_AmpDisable.\n")); + + g_bAmpEnabled = false; + + +#ifdef ISA1200_HEN_ENABLE + SYS_API_HEN_LOW; +#endif + + I2C_data[1] = 0x00; // Haptic drive disable + I2C_data[0]= HCTRL0; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_AmpDisable] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", HCTRL0, ret); + + PWM_CLK_DISABLE; + } + + return VIBE_S_SUCCESS; +} + +/* +** Called to enable amp (enable output force) +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_AmpEnable(VibeUInt8 nActuatorIndex) +{ + int cnt = 0; + unsigned char I2C_data[2]; + int ret = VIBE_S_SUCCESS; +#ifdef VIBETONZ_TUNING + VibeUInt32 nPWM_PLLDiv_KernelParam = 0; + VibeUInt32 nPWM_Freq_KernelParam = 0; + VibeUInt32 nPWM_Period_KernelParam = 0; + VibeUInt32 nPWM_Duty_KernelParam = 0; + VibeUInt32 nPWM_Update_KernelParam = 0; +#endif + + if (!g_bAmpEnabled) + { + DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_AmpEnable.\n")); + g_bAmpEnabled = true; + +#ifdef VIBETONZ_TUNING /* For Resonant Frequency Test */ + ImmVibeGetDeviceKernelParameter(0, 85, &nPWM_PLLDiv_KernelParam); /* use for PLL variable */ + ImmVibeGetDeviceKernelParameter(0, 86, &nPWM_Freq_KernelParam); /* use for Freq variable */ + ImmVibeGetDeviceKernelParameter(0, 87, &nPWM_Period_KernelParam); /* use for Period variable */ + ImmVibeGetDeviceKernelParameter(0, 88, &nPWM_Duty_KernelParam); /* use for Duty variable */ + ImmVibeGetDeviceKernelParameter(0, 89, &nPWM_Update_KernelParam); /* use for Update variable */ + + /* Update current Period and Duty values if those from the Kernel Parameters table are acceptables (value != 0) */ +#ifdef ISA1200_GEN_MODE + if(nPWM_Update_KernelParam) + { + g_nPWM_PLLDiv = nPWM_PLLDiv_KernelParam; + g_nPWM_Freq = nPWM_Freq_KernelParam; + g_nPWM_Period = nPWM_Period_KernelParam; + g_nPWM_Duty = nPWM_Duty_KernelParam; + +#ifdef MOTOR_TYPE_LRA + I2C_data[1] = 0xC0 ; // EXT clock + DAC inversion + LRA +#else + I2C_data[1] = 0xE0; // EXT clock + DAC inversion + ERM +#endif + I2C_data[0]= HCTRL1; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_AmpEnable] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = 0x00 ; // Disable Software Reset + I2C_data[0]= HCTRL2; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_AmpEnable] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = 0x03 + ( g_nPWM_PLLDiv<<4); // PLLLDIV + I2C_data[0]= HCTRL3; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_AmpEnable] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = g_nPWM_Freq; // PWM control + I2C_data[0]= HCTRL4; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_AmpEnable] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = g_nPWM_Duty; // PWM High Duty + I2C_data[0]= HCTRL5; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_AmpEnable] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = g_nPWM_Period; // PWM Period + I2C_data[0]= HCTRL6; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_AmpEnable] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + } +#else + if(nPWM_Update_KernelParam) + { + g_nPWM_Freq = nPWM_Freq_KernelParam; + } + SYS_API_SET_PWM_FREQ(g_nPWM_Freq); // 22.4Khz + SYS_API_SET_PWM_DUTY(500); // 50% Duty Cycle, 1000 ~ 0 +#endif +#endif /* VIBETONZ_TUNING */ + + PWM_CLK_ENABLE; + +#ifdef ISA1200_HEN_ENABLE + SYS_API_HEN_HIGH; +#endif + +#ifdef ISA1200_GEN_MODE + I2C_data[1] = 0x91; //Haptic Enable + PWM generation mode +#else +#ifdef ISA1200_PWM_256DIV_MODE + I2C_data[1] = 0x89; // Haptic Drive Disable + PWM Input mode + 44.8Khz mode +#else + I2C_data[1] = 0x88; // Haptic Drive Disable + PWM Input mode + 22.4Khz mode +#endif +#endif + I2C_data[0]= HCTRL0; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_AmpEnable] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + } + + return VIBE_S_SUCCESS; +} + +/* +** Called at initialization time to set PWM frequencies, disable amp, etc... +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_Initialize(void) +{ + int cnt = 0; + unsigned char I2C_data[2]; + int ret = VIBE_S_SUCCESS; + + DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_Initialize.\n")); + + SYS_API_VDDP_ON; + SYS_API_LEN_HIGH; + + SLEEP(200 /*us*/); + + I2C_data[1] = g_nLDO_Voltage; // LDO Voltage + I2C_data[0]= SCTRL; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + +#ifdef ISA1200_GEN_MODE + I2C_data[1] = 0x11; //Haptic Drive Disable + PWM generation mode + 44.8Khz mode + I2C_data[0]= HCTRL0; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + +#ifdef MOTOR_TYPE_LRA + I2C_data[1] = 0xC0 ; // EXT clock + DAC inversion + LRA +#else + I2C_data[1] = 0xE0; // EXT clock + DAC inversion + ERM +#endif + I2C_data[0]= HCTRL1; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = 0x00 ; // Disable Software Reset + I2C_data[0]= HCTRL2; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = 0x03 + ( g_nPWM_PLLDiv<<4); // PLLLDIV + I2C_data[0]= HCTRL3; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = g_nPWM_Freq; // PWM control + I2C_data[0]= HCTRL4; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = g_nPWM_Duty; // PWM High Duty + I2C_data[0]= HCTRL5; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = g_nPWM_Period; // PWM Period + I2C_data[0]= HCTRL6; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + +#else // PWM Input Mode + +#ifdef ISA1200_PWM_256DIV_MODE + I2C_data[1] = 0x09; // Haptic Drive Disable + PWM Input mode + 44.8Khz mode +#else + I2C_data[1] = 0x08; // Haptic Drive Disable + PWM Input mode + 22.4Khz mode +#endif + I2C_data[0]= HCTRL0; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + +#ifdef MOTOR_TYPE_LRA + I2C_data[1] = 0x43; // EXT clock + DAC inversion + LRA +#else + I2C_data[1] = 0x63; // EXT clock + DAC inversion + ERM +#endif + I2C_data[0]= HCTRL1; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = 0x00; // Disable Software Reset + I2C_data[0]= HCTRL2; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); + + I2C_data[1] = 0x00; // Default + I2C_data[0]= HCTRL4; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Initialize] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); +#endif + return VIBE_S_SUCCESS; +} + +/* +** Called at termination time to set PWM freq, disable amp, etc... +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_Terminate(void) +{ + DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_Terminate.\n")); + SYS_API_LEN_LOW; +#ifdef ISA1200_HEN_ENABLE + SYS_API_HEN_LOW; +#endif + SYS_API_VDDP_OFF; + return VIBE_S_SUCCESS; +} + +/* +** Called by the real-time loop to set PWM_MAG duty cycle +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_SetSamples(VibeUInt8 nActuatorIndex, VibeUInt16 nOutputSignalBitDepth, VibeUInt16 nBufferSizeInBytes, VibeInt8* pForceOutputBuffer) +{ + VibeInt8 nForce; + int cnt = 0; + unsigned char I2C_data[2]; + int ret = VIBE_S_SUCCESS; + + switch (nOutputSignalBitDepth) + { + case 8: + /* pForceOutputBuffer is expected to contain 1 byte */ + if (nBufferSizeInBytes != 1) + { + DbgOut((KERN_ERR "[ImmVibeSPI] ImmVibeSPI_ForceOut_SetSamples nBufferSizeInBytes = %d \n", nBufferSizeInBytes )); + return VIBE_E_FAIL; + } + nForce = pForceOutputBuffer[0]; + break; + case 16: + /* pForceOutputBuffer is expected to contain 2 byte */ + if (nBufferSizeInBytes != 2) return VIBE_E_FAIL; + + /* Map 16-bit value to 8-bit */ + nForce = ((VibeInt16*)pForceOutputBuffer)[0] >> 8; + break; + default: + /* Unexpected bit depth */ + return VIBE_E_FAIL; + } + + if(nForce == 0) + { +#ifdef ISA1200_GEN_MODE + I2C_data[1] = g_nPWM_Duty; // PWM High Duty 50% + I2C_data[0]= HCTRL5; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Set] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); +#else + SYS_API_SET_PWM_DUTY(500); //50% +#endif + if (g_bAmpEnabled == true) + { + ImmVibeSPI_ForceOut_AmpDisable(0); + } + } + else + { + if (g_bAmpEnabled != true) + { + ImmVibeSPI_ForceOut_AmpEnable(0); + } +#ifdef ISA1200_GEN_MODE + I2C_data[1] = g_nPWM_Duty + ((g_nPWM_Duty-1)*nForce)/127; + I2C_data[0]= HCTRL5; + do + { + ret = SYS_API__I2C__Write(ISA1200_I2C_ADDRESS, 2 /* data length*/, I2C_data); + cnt++; + }while(VIBE_S_SUCCESS != ret && cnt < RETRY_CNT); + if( VIBE_S_SUCCESS != ret) DEBUG_MSG("[ImmVibeSPI_ForceOut_Set] I2C_Write Error, Slave Address = [%d], ret = [%d]\n", I2C_data[0], ret); +#else + SYS_API_SET_PWM_DUTY(500 - (490 * nForce)/127); +#endif + } + return VIBE_S_SUCCESS; +} +/* +** Called to set force output frequency parameters +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_SetFrequency(VibeUInt8 nActuatorIndex, VibeUInt16 nFrequencyParameterID, VibeUInt32 nFrequencyParameterValue) +{ + return VIBE_S_SUCCESS; +} + +/* +** Called to get the device name (device name must be returned as ANSI char) +*/ +IMMVIBESPIAPI VibeStatus ImmVibeSPI_Device_GetName(VibeUInt8 nActuatorIndex, char *szDevName, int nSize) +{ + return VIBE_S_SUCCESS; +} diff --git a/drivers/motor/Kconfig b/drivers/motor/Kconfig new file mode 100644 index 0000000..6e333a7 --- /dev/null +++ b/drivers/motor/Kconfig @@ -0,0 +1,32 @@ +# +# +# +menuconfig VIBETONZ + tristate "Vibetonz" + default y + help + Say Y to enable Vibetonz support. + +config MOTOR_DRV_MAX77693 + tristate "Maxim MAX77693 motor" + default y + depends on MFD_MAX77693 + help + If you say yes here you will get support for the + motor of Maxim MAX77693 PMIC. + +config MOTOR_DRV_MAX8997 + tristate "Maxim MAX8997 motor" + default y + depends on MFD_MAX8997 + help + If you say yes here you will get support for the + motor of Maxim MAX8997 PMIC. + +config MOTOR_DRV_ISA1200 + tristate "ISA1200 motor" + default n + depends on I2C + help + Say Y to enalbe the ISA1200 IC. + diff --git a/drivers/motor/Makefile b/drivers/motor/Makefile new file mode 100644 index 0000000..8332949 --- /dev/null +++ b/drivers/motor/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the vibrator driver ICs with vibrator driver +# + +obj-$(CONFIG_MOTOR_DRV_MAX8997) += max8997_vibrator.o +obj-$(CONFIG_MOTOR_DRV_MAX77693) += max77693_haptic.o +obj-$(CONFIG_MOTOR_DRV_ISA1200) += isa1200_vibrator.o + +obj-$(CONFIG_VIBETONZ) += vibrator.o +vibrator-objs += tspdrv.o diff --git a/drivers/motor/VibeOSKernelLinuxHRTime.c b/drivers/motor/VibeOSKernelLinuxHRTime.c new file mode 100644 index 0000000..a7e1329 --- /dev/null +++ b/drivers/motor/VibeOSKernelLinuxHRTime.c @@ -0,0 +1,233 @@ +/* +** ========================================================================= +** File: +** VibeOSKernelLinuxHRTime.c +** +** Description: +** High Resolution Time helper functions for Linux. +** +** Portions Copyright (c) 2010 Immersion Corporation. All Rights Reserved. +** +** This file contains Original Code and/or Modifications of Original Code +** as defined in and that are subject to the GNU Public License v2 - +** (the 'License'). You may not use this file except in compliance with the +** License. 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 or contact +** TouchSenseSales@immersion.com. +** +** The Original Code and all software distributed under the License are +** distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +** EXPRESS OR IMPLIED, AND IMMERSION HEREBY DISCLAIMS ALL SUCH WARRANTIES, +** INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS +** FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see +** the License for the specific language governing rights and limitations +** under the License. +** ========================================================================= +*/ + +/* +** Kernel high-resolution software timer is used as an example but another type +** of timer (such as HW timer or standard software timer) might be used to achieve +** the 5ms required rate. +*/ + +#ifndef CONFIG_HIGH_RES_TIMERS +#warning "The Kernel does not have high resolution timers enabled. Either provide a non hr-timer implementation of VibeOSKernelLinuxTime.c or re-compile your kernel with CONFIG_HIGH_RES_TIMERS=y" +#endif + +#include <linux/hrtimer.h> +#include <linux/mutex.h> + +#define WATCHDOG_TIMEOUT 10 /* 10 timer cycles = 50ms */ + +/* Global variables */ +static bool g_bTimerStarted = false; +static struct hrtimer g_tspTimer; +static ktime_t g_ktFiveMs; +static int g_nWatchdogCounter = 0; + +DEFINE_SEMAPHORE(g_hMutex); + +/* Forward declarations */ +static void VibeOSKernelLinuxStartTimer(void); +static void VibeOSKernelLinuxStopTimer(void); +static int VibeOSKernelProcessData(void* data); +#define VIBEOSKERNELPROCESSDATA + +static inline int VibeSemIsLocked(struct semaphore *lock) +{ +#if ((LINUX_VERSION_CODE & 0xFFFFFF) < KERNEL_VERSION(2,6,27)) + return atomic_read(&lock->count) != 1; +#else + return (lock->count) != 1; +#endif +} + +static enum hrtimer_restart tsp_timer_interrupt(struct hrtimer *timer) +{ + /* Scheduling next timeout value right away */ + hrtimer_forward_now(timer, g_ktFiveMs); + + if(g_bTimerStarted) + { + if (VibeSemIsLocked(&g_hMutex)) + up(&g_hMutex); + } + + return HRTIMER_RESTART; +} + +static int VibeOSKernelProcessData(void* data) +{ + int i; + int nActuatorNotPlaying = 0; + + for (i = 0; i < NUM_ACTUATORS; i++) + { + actuator_samples_buffer *pCurrentActuatorSample = &(g_SamplesBuffer[i]); + + if (-1 == pCurrentActuatorSample->nIndexPlayingBuffer) + { + nActuatorNotPlaying++; + if ((NUM_ACTUATORS == nActuatorNotPlaying) && ((++g_nWatchdogCounter) > WATCHDOG_TIMEOUT)) + { + VibeInt8 cZero[1] = {0}; + + /* Nothing to play for all actuators, turn off the timer when we reach the watchdog tick count limit */ + ImmVibeSPI_ForceOut_SetSamples(i, 8, 1, cZero); + ImmVibeSPI_ForceOut_AmpDisable(i); + VibeOSKernelLinuxStopTimer(); + + /* Reset watchdog counter */ + g_nWatchdogCounter = 0; + } + } + else + { + /* Play the current buffer */ + if (VIBE_E_FAIL == ImmVibeSPI_ForceOut_SetSamples( + pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nActuatorIndex, + pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBitDepth, + pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBufferSize, + pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].dataBuffer)) + { + /* VIBE_E_FAIL means NAK has been handled. Schedule timer to restart 5 ms from now */ + hrtimer_forward_now(&g_tspTimer, g_ktFiveMs); + } + + pCurrentActuatorSample->nIndexOutputValue += pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBufferSize; + + if (pCurrentActuatorSample->nIndexOutputValue >= pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBufferSize) + { + /* Reach the end of the current buffer */ + pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBufferSize = 0; + + /* Switch buffer */ + (pCurrentActuatorSample->nIndexPlayingBuffer) ^= 1; + pCurrentActuatorSample->nIndexOutputValue = 0; + + /* Finished playing, disable amp for actuator (i) */ + if (g_bStopRequested) + { + pCurrentActuatorSample->nIndexPlayingBuffer = -1; + + ImmVibeSPI_ForceOut_AmpDisable(i); + } + } + } + } + + /* If finished playing, stop timer */ + if (g_bStopRequested) + { + VibeOSKernelLinuxStopTimer(); + + /* Reset watchdog counter */ + g_nWatchdogCounter = 0; + + if (VibeSemIsLocked(&g_hMutex)) up(&g_hMutex); + return 1; /* tell the caller this is the last iteration */ + } + + return 0; +} + +static void VibeOSKernelLinuxInitTimer(void) +{ + /* Get a 5,000,000ns = 5ms time value */ + g_ktFiveMs = ktime_set(0, 5000000); + + hrtimer_init(&g_tspTimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + /* Initialize a 5ms-timer with tsp_timer_interrupt as timer callback (interrupt driven)*/ + g_tspTimer.function = tsp_timer_interrupt; +} + +static void VibeOSKernelLinuxStartTimer(void) +{ + int i; + int res; + + /* Reset watchdog counter */ + g_nWatchdogCounter = 0; + + if (!g_bTimerStarted) + { + if (!VibeSemIsLocked(&g_hMutex)) res = down_interruptible(&g_hMutex); /* start locked */ + + g_bTimerStarted = true; + + /* Start the timer */ + hrtimer_start(&g_tspTimer, g_ktFiveMs, HRTIMER_MODE_REL); + + /* Don't block the write() function after the first sample to allow the host sending the next samples with no delay */ + for (i = 0; i < NUM_ACTUATORS; i++) + { + if ((g_SamplesBuffer[i].actuatorSamples[0].nBufferSize) || (g_SamplesBuffer[i].actuatorSamples[1].nBufferSize)) + { + g_SamplesBuffer[i].nIndexOutputValue = 0; + return; + } + } + } + + if (0 != VibeOSKernelProcessData(NULL)) return; + + /* + ** Use interruptible version of down to be safe + ** (try to not being stuck here if the mutex is not freed for any reason) + */ + res = down_interruptible(&g_hMutex); /* wait for the mutex to be freed by the timer */ + if (res != 0) + { + DbgOut((KERN_INFO "VibeOSKernelLinuxStartTimer: down_interruptible interrupted by a signal.\n")); + } +} + +static void VibeOSKernelLinuxStopTimer(void) +{ + int i; + + if (g_bTimerStarted) + { + g_bTimerStarted = false; + hrtimer_cancel(&g_tspTimer); + } + + /* Reset samples buffers */ + for (i = 0; i < NUM_ACTUATORS; i++) + { + g_SamplesBuffer[i].nIndexPlayingBuffer = -1; + g_SamplesBuffer[i].actuatorSamples[0].nBufferSize = 0; + g_SamplesBuffer[i].actuatorSamples[1].nBufferSize = 0; + } + g_bStopRequested = false; + g_bIsPlaying = false; +} + +static void VibeOSKernelLinuxTerminateTimer(void) +{ + VibeOSKernelLinuxStopTimer(); + if (VibeSemIsLocked(&g_hMutex)) up(&g_hMutex); +} diff --git a/drivers/motor/isa1200_vibrator.c b/drivers/motor/isa1200_vibrator.c new file mode 100644 index 0000000..97ccfe1 --- /dev/null +++ b/drivers/motor/isa1200_vibrator.c @@ -0,0 +1,386 @@ +/* drivers/motor/isa1200_vibrator.c + * + * Copyright (C) 2011 Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/hrtimer.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/pwm.h> +#include <linux/wakelock.h> +#include <linux/mutex.h> +#include <linux/clk.h> +#include <linux/pwm.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/timed_output.h> +#include <linux/isa1200_vibrator.h> +#include <linux/fs.h> +#include <asm/uaccess.h> + +#if 0 +#define MOTOR_DEBUG +#endif + +struct isa1200_vibrator_drvdata { + struct timed_output_dev dev; + struct hrtimer timer; + struct work_struct work; + struct clk *vib_clk; + struct pwm_device *pwm; + struct i2c_client *client; + int (*gpio_en) (bool) ; + spinlock_t lock; + bool running; + int timeout; + int max_timeout; + u8 ctrl0; + u8 ctrl1; + u8 ctrl2; + u8 ctrl4; + u8 pll; + u8 duty; + u8 period; + int pwm_id; + u16 pwm_duty; + u16 pwm_period; +}; + + +static struct isa1200_vibrator_drvdata *g_drvdata; + +static int isa1200_vibrator_i2c_write(struct i2c_client *client, + u8 addr, u8 val) +{ + int error = 0; + error = i2c_smbus_write_byte_data(client, addr, val); + if (error) + printk(KERN_ERR "[VIB] Failed to write addr=[0x%x], val=[0x%x]\n", + addr, val); + + return error; +} + +int vibtonz_i2c_write(u8 addr, int length, u8 *data) +{ + if (NULL == g_drvdata) { + printk(KERN_ERR "[VIB] driver is not ready\n"); + return -EFAULT; + } + + if (2 != length) + printk(KERN_ERR "[VIB] length should be 2(len:%d)\n", + length); + +#ifdef MOTOR_DEBUG + printk(KERN_DEBUG "[VIB] data : %x, %x\n", + data[0], data[1]); +#endif + + return isa1200_vibrator_i2c_write(g_drvdata->client, + data[0], data[1]); +} + +void vibtonz_clk_enable(bool en) +{ + if (NULL == g_drvdata) { + printk(KERN_ERR "[VIB] driver is not ready\n"); + return ; + } + + if (en) { + if (g_drvdata->vib_clk) + clk_enable(g_drvdata->vib_clk); + else + pwm_enable(g_drvdata->pwm); + } else { + if (g_drvdata->vib_clk) + clk_disable(g_drvdata->vib_clk); + else + pwm_disable(g_drvdata->pwm); + } +} + +void vibtonz_clk_config(int duty) +{ + u32 duty_ns = 0; + + duty_ns = (u32)(g_drvdata->pwm_period * duty) + / 1000; + + pwm_config(g_drvdata->pwm, + duty_ns, + g_drvdata->pwm_period); + +} + +void vibtonz_chip_enable(bool en) +{ + if (NULL == g_drvdata) { + printk(KERN_ERR "[VIB] driver is not ready\n"); + return ; + } + g_drvdata->gpio_en(en ? true : false); +} + +static void isa1200_vibrator_hw_init(struct isa1200_vibrator_drvdata *data) +{ + data->gpio_en(true); + msleep(20); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_CONTROL_REG0, data->ctrl0); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_CONTROL_REG1, data->ctrl1); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_CONTROL_REG2, data->ctrl2); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_PLL_REG, data->pll); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_CONTROL_REG4, data->ctrl4); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_PWM_DUTY_REG, data->period/2); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_PWM_PERIOD_REG, data->period); + +#ifdef MOTOR_DEBUG + printk(KERN_DEBUG "[VIB] ctrl0 = 0x%x\n", data->ctrl0); + printk(KERN_DEBUG "[VIB] ctrl1 = 0x%x\n", data->ctrl1); + printk(KERN_DEBUG "[VIB] ctrl2 = 0x%x\n", data->ctrl2); + printk(KERN_DEBUG "[VIB] pll = 0x%x\n", data->pll); + printk(KERN_DEBUG "[VIB] ctrl4 = 0x%x\n", data->ctrl4); + printk(KERN_DEBUG "[VIB] duty = 0x%x\n", data->period/2); + printk(KERN_DEBUG "[VIB] period = 0x%x\n", data->period); +#endif + +} + +static void isa1200_vibrator_on(struct isa1200_vibrator_drvdata *data) +{ + isa1200_vibrator_i2c_write(data->client, + HAPTIC_CONTROL_REG0, data->ctrl0 | CTL0_NORMAL_OP); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_PWM_DUTY_REG, data->duty - 3); +#ifdef MOTOR_DEBUG + printk(KERN_DEBUG "[VIB] ctrl0 = 0x%x\n", data->ctrl0 | CTL0_NORMAL_OP); + printk(KERN_DEBUG "[VIB] duty = 0x%x\n", data->duty); +#endif +} + +static void isa1200_vibrator_off(struct isa1200_vibrator_drvdata *data) +{ + isa1200_vibrator_i2c_write(data->client, + HAPTIC_PWM_DUTY_REG, data->period / 2); + isa1200_vibrator_i2c_write(data->client, + HAPTIC_CONTROL_REG0, data->ctrl0); +} + +static enum hrtimer_restart isa1200_vibrator_timer_func(struct hrtimer *_timer) +{ + struct isa1200_vibrator_drvdata *data = + container_of(_timer, struct isa1200_vibrator_drvdata, timer); + + data->timeout = 0; + + schedule_work(&data->work); + return HRTIMER_NORESTART; +} + +static void isa1200_vibrator_work(struct work_struct *_work) +{ + struct isa1200_vibrator_drvdata *data = + container_of(_work, struct isa1200_vibrator_drvdata, work); + + if (0 == data->timeout) { + if (!data->running) + return ; + + data->running = false; + isa1200_vibrator_off(data); + vibtonz_clk_config(500); + vibtonz_clk_enable(false); + } else { + if (data->running) + return ; + + data->running = true; + vibtonz_clk_config(999); + vibtonz_clk_enable(true); + mdelay(1); + isa1200_vibrator_on(data); + } +} + +static int isa1200_vibrator_get_time(struct timed_output_dev *_dev) +{ + struct isa1200_vibrator_drvdata *data = + container_of(_dev, struct isa1200_vibrator_drvdata, dev); + + if (hrtimer_active(&data->timer)) { + ktime_t r = hrtimer_get_remaining(&data->timer); + struct timeval t = ktime_to_timeval(r); + return t.tv_sec * 1000 + t.tv_usec / 1000; + } else + return 0; +} + +static void isa1200_vibrator_enable(struct timed_output_dev *_dev, int value) +{ + struct isa1200_vibrator_drvdata *data = + container_of(_dev, struct isa1200_vibrator_drvdata, dev); + unsigned long flags; + +#ifdef MOTOR_DEBUG + printk(KERN_DEBUG "[VIB] time = %dms\n", value); +#endif + cancel_work_sync(&data->work); + hrtimer_cancel(&data->timer); + data->timeout = value; + schedule_work(&data->work); + spin_lock_irqsave(&data->lock, flags); + if (value > 0) { + if (value > data->max_timeout) + value = data->max_timeout; + + hrtimer_start(&data->timer, + ns_to_ktime((u64)value * NSEC_PER_MSEC), + HRTIMER_MODE_REL); + } + spin_unlock_irqrestore(&data->lock, flags); +} + +static int __devinit isa1200_vibrator_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isa1200_vibrator_platform_data *pdata = + client->dev.platform_data; + struct isa1200_vibrator_drvdata *ddata; + int ret = 0; + + ddata = kzalloc(sizeof(struct isa1200_vibrator_drvdata), GFP_KERNEL); + if (NULL == ddata) { + printk(KERN_ERR "[VIB] Failed to alloc memory\n"); + ret = -ENOMEM; + goto err_free_mem; + } + + ddata->client = client; + ddata->ctrl0 = pdata->ctrl0; + ddata->ctrl1 = pdata->ctrl1; + ddata->ctrl2 = pdata->ctrl2; + ddata->ctrl4 = pdata->ctrl4; + ddata->gpio_en = pdata->gpio_en; + if (pdata->get_clk) { + ddata->vib_clk = pdata->get_clk(); + if (IS_ERR(ddata->vib_clk)) { + pr_err("[VIB] Failed to request vib_clk.\n"); + ret = -EFAULT; + goto err_request_clk; + } + } else { + ddata->vib_clk = NULL; + ddata->pwm = pwm_request(pdata->pwm_id, "vibrator"); + if (IS_ERR(ddata->pwm)) { + pr_err("[VIB] Failed to request pwm.\n"); + ret = -EFAULT; + goto err_request_clk; + } + ddata->pwm_id = pdata->pwm_id; + ddata->pwm_duty = pdata->pwm_duty; + ddata->pwm_period = pdata->pwm_period; + } + ddata->max_timeout = pdata->max_timeout; + ddata->pll = pdata->pll; + ddata->duty = pdata->duty; + ddata->period = pdata->period; + + ddata->dev.name = "vibrator"; + ddata->dev.get_time = isa1200_vibrator_get_time; + ddata->dev.enable = isa1200_vibrator_enable; + + hrtimer_init(&ddata->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ddata->timer.function = isa1200_vibrator_timer_func; + INIT_WORK(&ddata->work, isa1200_vibrator_work); + spin_lock_init(&ddata->lock); + + i2c_set_clientdata(client, ddata); + isa1200_vibrator_hw_init(ddata); + + ret = timed_output_dev_register(&ddata->dev); + if (ret < 0) { + printk(KERN_ERR "[VIB] Failed to register timed_output : -%d\n", ret); + goto err_to_dev_reg; + } + + g_drvdata = ddata; + + return 0; + +err_to_dev_reg: +err_request_clk: + kfree(ddata); +err_free_mem: + return ret; + +} + +static int isa1200_vibrator_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct isa1200_vibrator_drvdata *ddata = i2c_get_clientdata(client); + ddata->gpio_en(false); + return 0; +} + +static int isa1200_vibrator_resume(struct i2c_client *client) +{ + struct isa1200_vibrator_drvdata *ddata = i2c_get_clientdata(client); + isa1200_vibrator_hw_init(ddata); + return 0; +} + +static const struct i2c_device_id isa1200_vibrator_device_id[] = { + {"isa1200_vibrator", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, isa1200_vibrator_device_id); + +static struct i2c_driver isa1200_vibrator_i2c_driver = { + .driver = { + .name = "isa1200_vibrator", + .owner = THIS_MODULE, + }, + .probe = isa1200_vibrator_i2c_probe, + .id_table = isa1200_vibrator_device_id, + .suspend = isa1200_vibrator_suspend, + .resume = isa1200_vibrator_resume, +}; + +static int __init isa1200_vibrator_init(void) +{ + return i2c_add_driver(&isa1200_vibrator_i2c_driver); +} + +static void __exit isa1200_vibrator_exit(void) +{ + i2c_del_driver(&isa1200_vibrator_i2c_driver); +} + +module_init(isa1200_vibrator_init); +module_exit(isa1200_vibrator_exit); diff --git a/drivers/motor/max77693_haptic.c b/drivers/motor/max77693_haptic.c new file mode 100644 index 0000000..f1c816e --- /dev/null +++ b/drivers/motor/max77693_haptic.c @@ -0,0 +1,388 @@ +/* + * haptic motor driver for max77693 - max77673_haptic.c + * + * Copyright (C) 2011 ByungChang Cha <bc.cha@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/timed_output.h> +#include <linux/hrtimer.h> +#include <linux/pwm.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/max77693.h> +#include <linux/mfd/max77693-private.h> + +struct max77693_haptic_data { + struct max77693_dev *max77693; + struct i2c_client *i2c; + struct i2c_client *pmic_i2c; + struct max77693_haptic_platform_data *pdata; + + struct pwm_device *pwm; + struct regulator *regulator; + + struct timed_output_dev tout_dev; + struct hrtimer timer; + unsigned int timeout; + + struct workqueue_struct *workqueue; + struct work_struct work; + spinlock_t lock; + bool running; +}; + +struct max77693_haptic_data *g_hap_data; + +static void max77693_haptic_i2c(struct max77693_haptic_data *hap_data, bool en) +{ + int ret; + u8 value = hap_data->pdata->reg2; + u8 lscnfg_val = 0x00; + + pr_debug("[VIB] %s %d\n", __func__, en); + + if (en) { + value |= MOTOR_EN; + lscnfg_val = 0x80; + } + + ret = max77693_update_reg(hap_data->pmic_i2c, MAX77693_PMIC_REG_LSCNFG, + lscnfg_val, 0x80); + if (ret) + pr_err("[VIB] i2c update error %d\n", ret); + + ret = max77693_write_reg(hap_data->i2c, + MAX77693_HAPTIC_REG_CONFIG2, value); + if (ret) + pr_err("[VIB] i2c write error %d\n", ret); +} + +static int haptic_get_time(struct timed_output_dev *tout_dev) +{ + struct max77693_haptic_data *hap_data + = container_of(tout_dev, struct max77693_haptic_data, tout_dev); + + struct hrtimer *timer = &hap_data->timer; + if (hrtimer_active(timer)) { + ktime_t remain = hrtimer_get_remaining(timer); + struct timeval t = ktime_to_timeval(remain); + return t.tv_sec * 1000 + t.tv_usec / 1000; + } + return 0; +} + +static void haptic_enable(struct timed_output_dev *tout_dev, int value) +{ + struct max77693_haptic_data *hap_data + = container_of(tout_dev, struct max77693_haptic_data, tout_dev); + + struct hrtimer *timer = &hap_data->timer; + unsigned long flags; + + + cancel_work_sync(&hap_data->work); + hrtimer_cancel(timer); + hap_data->timeout = value; + queue_work(hap_data->workqueue, &hap_data->work); + spin_lock_irqsave(&hap_data->lock, flags); + if (value > 0) { + pr_debug("%s value %d\n", __func__, value); + value = min(value, (int)hap_data->pdata->max_timeout); + hrtimer_start(timer, ns_to_ktime((u64)value * NSEC_PER_MSEC), + HRTIMER_MODE_REL); + } + spin_unlock_irqrestore(&hap_data->lock, flags); +} + +static enum hrtimer_restart haptic_timer_func(struct hrtimer *timer) +{ + struct max77693_haptic_data *hap_data + = container_of(timer, struct max77693_haptic_data, timer); + unsigned long flags; + + hap_data->timeout = 0; + queue_work(hap_data->workqueue, &hap_data->work); + return HRTIMER_NORESTART; +} + +static int vibetonz_clk_on(struct device *dev, bool en) +{ + struct clk *vibetonz_clk = NULL; + vibetonz_clk = clk_get(dev, "timers"); + pr_debug("[VIB] DEV NAME %s %lu\n", + dev_name(dev), clk_get_rate(vibetonz_clk)); + + if (IS_ERR(vibetonz_clk)) { + pr_err("[VIB] failed to get clock for the motor\n"); + goto err_clk_get; + } + + if (en) + clk_enable(vibetonz_clk); + else + clk_disable(vibetonz_clk); + + clk_put(vibetonz_clk); + return 0; + +err_clk_get: + clk_put(vibetonz_clk); + return -EINVAL; +} + +static void haptic_work(struct work_struct *work) +{ + struct max77693_haptic_data *hap_data + = container_of(work, struct max77693_haptic_data, work); + + pr_debug("[VIB] %s\n", __func__); + if (hap_data->timeout > 0) { + if (hap_data->running) + return; + + max77693_haptic_i2c(hap_data, true); + + pwm_config(hap_data->pwm, hap_data->pdata->duty, + hap_data->pdata->period); + pwm_enable(hap_data->pwm); + + if (hap_data->pdata->motor_en) + hap_data->pdata->motor_en(true); + else + regulator_enable(hap_data->regulator); + + hap_data->running = true; + } else { + if (!hap_data->running) + return; + + if (hap_data->pdata->motor_en) + hap_data->pdata->motor_en(false); + else + regulator_force_disable(hap_data->regulator); + + pwm_disable(hap_data->pwm); + + max77693_haptic_i2c(hap_data, false); + + hap_data->running = false; + } + return; +} + +#ifdef CONFIG_VIBETONZ +void vibtonz_en(bool en) +{ + if (g_hap_data == NULL) { + printk(KERN_ERR "[VIB] the motor is not ready!!!"); + return ; + } + + if (en) { + if (g_hap_data->running) + return; + + max77693_haptic_i2c(g_hap_data, true); + pwm_enable(g_hap_data->pwm); + + if (g_hap_data->pdata->motor_en) + g_hap_data->pdata->motor_en(true); + else + regulator_enable(g_hap_data->regulator); + + g_hap_data->running = true; + } else { + if (!g_hap_data->running) + return; + + if (g_hap_data->pdata->motor_en) + g_hap_data->pdata->motor_en(false); + else + regulator_force_disable(g_hap_data->regulator); + + pwm_disable(g_hap_data->pwm); + + max77693_haptic_i2c(g_hap_data, false); + + g_hap_data->running = false; + } +} +EXPORT_SYMBOL(vibtonz_en); + +void vibtonz_pwm(int nForce) +{ + /* add to avoid the glitch issue */ + static int prev_duty; + int pwm_period = 0, pwm_duty = 0; + + if (g_hap_data == NULL) { + printk(KERN_ERR "[VIB] the motor is not ready!!!"); + return ; + } + + pwm_period = g_hap_data->pdata->period; + pwm_duty = pwm_period / 2 + ((pwm_period / 2 - 2) * nForce) / 127; + + if (pwm_duty > g_hap_data->pdata->duty) + pwm_duty = g_hap_data->pdata->duty; + else if (pwm_period - pwm_duty > g_hap_data->pdata->duty) + pwm_duty = pwm_period - g_hap_data->pdata->duty; + + /* add to avoid the glitch issue */ + if (prev_duty != pwm_duty) { + prev_duty = pwm_duty; + pwm_config(g_hap_data->pwm, pwm_duty, pwm_period); + } +} +EXPORT_SYMBOL(vibtonz_pwm); +#endif + +static int max77693_haptic_probe(struct platform_device *pdev) +{ + int error = 0; + struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent); + struct max77693_platform_data *max77693_pdata + = dev_get_platdata(max77693->dev); + struct max77693_haptic_platform_data *pdata + = max77693_pdata->haptic_data; + struct max77693_haptic_data *hap_data; + + pr_debug("[VIB] ++ %s\n", __func__); + if (pdata == NULL) { + pr_err("%s: no pdata\n", __func__); + return -ENODEV; + } + + hap_data = kzalloc(sizeof(struct max77693_haptic_data), GFP_KERNEL); + if (!hap_data) + return -ENOMEM; + + platform_set_drvdata(pdev, hap_data); + g_hap_data = hap_data; + hap_data->max77693 = max77693; + hap_data->i2c = max77693->haptic; + hap_data->pmic_i2c = max77693->i2c; + hap_data->pdata = pdata; + + hap_data->workqueue = create_singlethread_workqueue("hap_work"); + INIT_WORK(&(hap_data->work), haptic_work); + spin_lock_init(&(hap_data->lock)); + + hap_data->pwm = pwm_request(hap_data->pdata->pwm_id, "vibrator"); + if (IS_ERR(hap_data->pwm)) { + pr_err("[VIB] Failed to request pwm\n"); + error = -EFAULT; + goto err_pwm_request; + } + pwm_config(hap_data->pwm, pdata->period / 2, pdata->period); + + vibetonz_clk_on(&pdev->dev, true); + + if (pdata->init_hw) + pdata->init_hw(); + else + hap_data->regulator + = regulator_get(NULL, pdata->regulator_name); + + if (IS_ERR(hap_data->regulator)) { + pr_err("[VIB] Failed to get vmoter regulator.\n"); + error = -EFAULT; + goto err_regulator_get; + } + + /* hrtimer init */ + hrtimer_init(&hap_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hap_data->timer.function = haptic_timer_func; + + /* timed_output_dev init*/ + hap_data->tout_dev.name = "vibrator"; + hap_data->tout_dev.get_time = haptic_get_time; + hap_data->tout_dev.enable = haptic_enable; + +#ifdef CONFIG_ANDROID_TIMED_OUTPUT + error = timed_output_dev_register(&hap_data->tout_dev); + if (error < 0) { + pr_err("[VIB] Failed to register timed_output : %d\n", error); + error = -EFAULT; + goto err_timed_output_register; + } +#endif + + pr_debug("[VIB] -- %s\n", __func__); + + return error; + +err_timed_output_register: + regulator_put(hap_data->regulator); +err_regulator_get: + pwm_free(hap_data->pwm); +err_pwm_request: + kfree(hap_data); + g_hap_data = NULL; + return error; +} + +static int __devexit max77693_haptic_remove(struct platform_device *pdev) +{ + struct max77693_haptic_data *data = platform_get_drvdata(pdev); +#ifdef CONFIG_ANDROID_TIMED_OUTPUT + timed_output_dev_unregister(&data->tout_dev); +#endif + regulator_put(data->regulator); + pwm_free(data->pwm); + destroy_workqueue(data->workqueue); + kfree(data); + g_hap_data = NULL; + + return 0; +} + +static int max77693_haptic_suspend(struct platform_device *pdev, + pm_message_t state) +{ + vibetonz_clk_on(&pdev->dev, false); + return 0; +} +static int max77693_haptic_resume(struct platform_device *pdev) +{ + vibetonz_clk_on(&pdev->dev, true); + return 0; +} + +static struct platform_driver max77693_haptic_driver = { + .probe = max77693_haptic_probe, + .remove = max77693_haptic_remove, + .suspend = max77693_haptic_suspend, + .resume = max77693_haptic_resume, + .driver = { + .name = "max77693-haptic", + .owner = THIS_MODULE, + }, +}; + +static int __init max77693_haptic_init(void) +{ + pr_debug("[VIB] %s\n", __func__); + return platform_driver_register(&max77693_haptic_driver); +} +module_init(max77693_haptic_init); + +static void __exit max77693_haptic_exit(void) +{ + platform_driver_unregister(&max77693_haptic_driver); +} +module_exit(max77693_haptic_exit); + +MODULE_AUTHOR("ByungChang Cha <bc.cha@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MAX77693 haptic driver"); diff --git a/drivers/motor/max8997_vibrator.c b/drivers/motor/max8997_vibrator.c new file mode 100644 index 0000000..c34716b --- /dev/null +++ b/drivers/motor/max8997_vibrator.c @@ -0,0 +1,336 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/timed_output.h> +#include <linux/hrtimer.h> +#include <linux/pwm.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> + +#ifdef CONFIG_MACH_Q1_BD +#include "mach/gpio.h" +#endif + +struct vibrator_drvdata { + struct max8997_motor_data *pdata; + struct pwm_device *pwm; + struct regulator *regulator; + struct i2c_client *client; + struct timed_output_dev dev; + struct hrtimer timer; + struct work_struct work; + spinlock_t lock; + bool running; + int timeout; +}; + +#ifdef CONFIG_VIBETONZ +struct vibrator_drvdata *g_data; +#endif + +static int vibetonz_clk_on(struct device *dev, bool en) +{ + struct clk *vibetonz_clk = NULL; + vibetonz_clk = clk_get(dev, "timers"); + if (IS_ERR(vibetonz_clk)) { + pr_err("[VIB] failed to get clock for the motor\n"); + goto err_clk_get; + } + if (en) + clk_enable(vibetonz_clk); + else + clk_disable(vibetonz_clk); + clk_put(vibetonz_clk); + return 0; + +err_clk_get: + clk_put(vibetonz_clk); + return -EINVAL; +} + +static void i2c_max8997_hapticmotor(struct vibrator_drvdata *data, bool en) +{ + int ret; + u8 value = data->pdata->reg2; + + /* the setting of reg2 should be set */ + if (0 == value) +#if defined(CONFIG_MACH_P2) + value = MOTOR_LRA | EXT_PWM | DIVIDER_128; +#else + value = MOTOR_LRA | EXT_PWM | DIVIDER_256; +#endif + + if (en) + value |= MOTOR_EN; + + ret = max8997_write_reg(data->client, + MAX8997_MOTOR_REG_CONFIG2, value); + if (ret < 0) + pr_err("[VIB] i2c write error\n"); +} + +static enum hrtimer_restart vibrator_timer_func(struct hrtimer *_timer) +{ + struct vibrator_drvdata *data = + container_of(_timer, struct vibrator_drvdata, timer); + + data->timeout = 0; + + schedule_work(&data->work); + return HRTIMER_NORESTART; +} + +static void vibrator_work(struct work_struct *_work) +{ + struct vibrator_drvdata *data = + container_of(_work, struct vibrator_drvdata, work); + + printk(KERN_DEBUG "[VIB] time = %dms\n", data->timeout); + + if (0 == data->timeout) { + if (!data->running) + return ; + pwm_disable(data->pwm); + i2c_max8997_hapticmotor(data, false); + if (data->pdata->motor_en) + data->pdata->motor_en(false); + else + regulator_force_disable(data->regulator); + data->running = false; + + } else { + if (data->running) + return ; + if (data->pdata->motor_en) + data->pdata->motor_en(true); + else + regulator_enable(data->regulator); + i2c_max8997_hapticmotor(data, true); + pwm_config(data->pwm, + data->pdata->duty, data->pdata->period); + pwm_enable(data->pwm); + + data->running = true; + } +} + +static int vibrator_get_time(struct timed_output_dev *_dev) +{ + struct vibrator_drvdata *data = + container_of(_dev, struct vibrator_drvdata, dev); + + if (hrtimer_active(&data->timer)) { + ktime_t r = hrtimer_get_remaining(&data->timer); + struct timeval t = ktime_to_timeval(r); + return t.tv_sec * 1000 + t.tv_usec / 1000; + } else + return 0; +} + +static void vibrator_enable(struct timed_output_dev *_dev, int value) +{ + struct vibrator_drvdata *data = + container_of(_dev, struct vibrator_drvdata, dev); + unsigned long flags; + + cancel_work_sync(&data->work); + hrtimer_cancel(&data->timer); + data->timeout = value; + schedule_work(&data->work); + spin_lock_irqsave(&data->lock, flags); + if (value > 0) { + if (value > data->pdata->max_timeout) + value = data->pdata->max_timeout; + + hrtimer_start(&data->timer, + ns_to_ktime((u64)value * NSEC_PER_MSEC), + HRTIMER_MODE_REL); + } + spin_unlock_irqrestore(&data->lock, flags); +} + +#ifdef CONFIG_VIBETONZ +void vibtonz_en(bool en) +{ + struct vibrator_drvdata *data = g_data; + + if (en) { + if (data->running) + return ; + if (data->pdata->motor_en) + data->pdata->motor_en(true); + else + regulator_enable(data->regulator); + i2c_max8997_hapticmotor(data, true); + pwm_enable(data->pwm); + data->running = true; + } else { + if (!data->running) + return ; + + pwm_disable(data->pwm); + i2c_max8997_hapticmotor(data, false); + if (data->pdata->motor_en) + data->pdata->motor_en(false); + else + regulator_force_disable(data->regulator); + data->running = false; + } +} +EXPORT_SYMBOL(vibtonz_en); + +void vibtonz_pwm(int nForce) +{ + struct vibrator_drvdata *data = g_data; + /* add to avoid the glitch issue */ + static int prev_duty; + int pwm_period = data->pdata->period; + int pwm_duty = pwm_period/2 + ((pwm_period/2 - 2) * nForce)/127; + +#if defined(CONFIG_MACH_P4) + if (pwm_duty > data->pdata->duty) + pwm_duty = data->pdata->duty; + else if (pwm_period - pwm_duty > data->pdata->duty) + pwm_duty = pwm_period - data->pdata->duty; +#endif + + /* add to avoid the glitch issue */ + if (prev_duty != pwm_duty) { + prev_duty = pwm_duty; + pwm_config(data->pwm, pwm_duty, pwm_period); + } +} +EXPORT_SYMBOL(vibtonz_pwm); +#endif + +static int __devinit vibrator_probe(struct platform_device *pdev) +{ + struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *max8997_pdata + = dev_get_platdata(max8997->dev); + struct max8997_motor_data *pdata = max8997_pdata->motor; + struct vibrator_drvdata *ddata; + int error = 0; + + ddata = kzalloc(sizeof(struct vibrator_drvdata), GFP_KERNEL); + if (NULL == ddata) { + pr_err("[VIB] Failed to alloc memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + if (pdata->init_hw) + pdata->init_hw(); + else { + ddata->regulator = regulator_get(NULL, "vmotor"); + if (IS_ERR(ddata->regulator)) { + pr_err("[VIB] Failed to get vmoter regulator.\n"); + error = -EFAULT; + goto err_regulator_get; + } + } + + ddata->pdata = pdata; + ddata->dev.name = "vibrator"; + ddata->dev.get_time = vibrator_get_time; + ddata->dev.enable = vibrator_enable; + ddata->client = max8997->hmotor; + + platform_set_drvdata(pdev, ddata); + + hrtimer_init(&ddata->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ddata->timer.function = vibrator_timer_func; + INIT_WORK(&ddata->work, vibrator_work); + spin_lock_init(&ddata->lock); + + ddata->pwm = pwm_request(pdata->pwm_id, "vibrator"); + if (IS_ERR(ddata->pwm)) { + pr_err("[VIB] Failed to request pwm.\n"); + error = -EFAULT; + goto err_pwm_request; + } + pwm_config(ddata->pwm, + ddata->pdata->period/2, ddata->pdata->period); + + vibetonz_clk_on(&pdev->dev, true); + + error = timed_output_dev_register(&ddata->dev); + if (error < 0) { + pr_err("[VIB] Failed to register timed_output : %d\n", error); + error = -EFAULT; + goto err_timed_output_register; + } + +#ifdef CONFIG_VIBETONZ + g_data = ddata; +#endif + + return 0; + +err_timed_output_register: + timed_output_dev_unregister(&ddata->dev); +err_regulator_get: + regulator_put(ddata->regulator); +err_pwm_request: + pwm_free(ddata->pwm); +err_free_mem: + kfree(ddata); + return error; +} + +static int __devexit vibrator_remove(struct platform_device *pdev) +{ + struct vibrator_drvdata *data = platform_get_drvdata(pdev); + timed_output_dev_unregister(&data->dev); +#ifdef CONFIG_MACH_Q1_BD + gpio_free(GPIO_MOTOR_EN); +#else + regulator_put(data->regulator); +#endif + pwm_free(data->pwm); + kfree(data); + return 0; +} + +static int vibrator_suspend(struct platform_device *pdev, + pm_message_t state) +{ + vibetonz_clk_on(&pdev->dev, false); + return 0; +} +static int vibrator_resume(struct platform_device *pdev) +{ + vibetonz_clk_on(&pdev->dev, true); + return 0; +} + +static struct platform_driver vibrator_driver = { + .probe = vibrator_probe, + .remove = __devexit_p(vibrator_remove), + .suspend = vibrator_suspend, + .resume = vibrator_resume, + .driver = { + .name = "max8997-hapticmotor", + .owner = THIS_MODULE, + } +}; + +static int __init vibrator_init(void) +{ + return platform_driver_register(&vibrator_driver); +} + +static void __exit vibrator_exit(void) +{ + platform_driver_unregister(&vibrator_driver); +} + +late_initcall(vibrator_init); +module_exit(vibrator_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("vibrator driver"); diff --git a/drivers/motor/tspdrv.c b/drivers/motor/tspdrv.c new file mode 100644 index 0000000..257fbfc --- /dev/null +++ b/drivers/motor/tspdrv.c @@ -0,0 +1,587 @@ +/* +** ========================================================================= +** File: +** tspdrv.c +** +** Description: +** TouchSense Kernel Module main entry-point. +** +** Portions Copyright (c) 2008-2010 Immersion Corporation. All Rights Reserved. +** +** This file contains Original Code and/or Modifications of Original Code +** as defined in and that are subject to the GNU Public License v2 - +** (the 'License'). You may not use this file except in compliance with the +** License. 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 or contact +** TouchSenseSales@immersion.com. +** +** The Original Code and all software distributed under the License are +** distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +** EXPRESS OR IMPLIED, AND IMMERSION HEREBY DISCLAIMS ALL SUCH WARRANTIES, +** INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS +** FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see +** the License for the specific language governing rights and limitations +** under the License. +** ========================================================================= +*/ + +#ifndef __KERNEL__ +#define __KERNEL__ +#endif + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/fs.h> +#include <linux/version.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/hrtimer.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/wakelock.h> +#include <linux/io.h> +#include <mach/map.h> +#include "tspdrv.h" +#ifdef CONFIG_MOTOR_DRV_ISA1200 +#include "ImmVibeSPI_isa1200.c" +#else +#include "ImmVibeSPI.c" +#endif +#if defined(VIBE_DEBUG) && defined(VIBE_RECORD) +#include <tspdrvRecorder.c> +#endif + +/* Device name and version information */ + +/* DO NOT CHANGE - this is auto-generated */ +#define VERSION_STR " v3.4.55.8\n" + +/* account extra space for future extra digits in version number */ +#define VERSION_STR_LEN 16 + +/* initialized in init_module */ +static char g_szDeviceName[(VIBE_MAX_DEVICE_NAME_LENGTH + + VERSION_STR_LEN) + * NUM_ACTUATORS]; +/* initialized in init_module */ +static size_t g_cchDeviceName; + +static struct wake_lock vib_wake_lock; + +/* Flag indicating whether the driver is in use */ +static char g_bIsPlaying; + +/* Buffer to store data sent to SPI */ +#define SPI_BUFFER_SIZE (NUM_ACTUATORS * \ + (VIBE_OUTPUT_SAMPLE_SIZE + SPI_HEADER_SIZE)) +static int g_bStopRequested; +static actuator_samples_buffer g_SamplesBuffer[NUM_ACTUATORS] = { {0} }; +static char g_cWriteBuffer[SPI_BUFFER_SIZE]; + +/* #define VIBE_TUNING */ +/* #define VIBE_ENABLE_SYSTEM_TIMER */ +/* #define IMPLEMENT_AS_CHAR_DRIVER */ + +/* For QA purposes */ +#ifdef QA_TEST +#define FORCE_LOG_BUFFER_SIZE 128 +#define TIME_INCREMENT 5 +static int g_nTime; +static int g_nForceLogIndex; +static VibeInt8 g_nForceLog[FORCE_LOG_BUFFER_SIZE]; +#endif + +#if ((LINUX_VERSION_CODE & 0xFFFF00) < KERNEL_VERSION(2, 6, 0)) +#error Unsupported Kernel version +#endif + +#ifdef IMPLEMENT_AS_CHAR_DRIVER +static int g_nMajor; +#endif + +/* Needs to be included after the global variables because it uses them */ +#ifdef CONFIG_HIGH_RES_TIMERS +#include "VibeOSKernelLinuxHRTime.c" +#else +#include "VibeOSKernelLinuxTime.c" +#endif + +/* File IO */ +static int open(struct inode *inode, struct file *file); +static int release(struct inode *inode, struct file *file); +static ssize_t read(struct file *file, char *buf, size_t count, loff_t *ppos); +static ssize_t write(struct file *file, const char *buf, size_t count, + loff_t *ppos); +static long unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = read, + .write = write, + .unlocked_ioctl = unlocked_ioctl, + .open = open, + .release = release, + .llseek = default_llseek +}; + +#ifndef IMPLEMENT_AS_CHAR_DRIVER +static struct miscdevice miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = MODULE_NAME, + .fops = &fops +}; +#endif + +static int suspend(struct platform_device *pdev, pm_message_t state); +static int resume(struct platform_device *pdev); +static struct platform_driver platdrv = { + .suspend = suspend, + .resume = resume, + .driver = { + .name = MODULE_NAME, + }, +}; + +static void platform_release(struct device *dev); +static struct platform_device platdev = { + .name = MODULE_NAME, + /* means that there is only one device */ + .id = -1, + .dev = { + .platform_data = NULL, + /* a warning is thrown during rmmod if this is absent */ + .release = platform_release, + }, +}; + +/* Module info */ +MODULE_AUTHOR("Immersion Corporation"); +MODULE_DESCRIPTION("TouchSense Kernel Module"); +MODULE_LICENSE("GPL v2"); + +#ifdef VIBE_ENABLE_SYSTEM_TIMER +int vibetonz_clk_on(struct device *dev) +{ + struct clk *vibetonz_clk = NULL; + /*printk(KERN_ERR"[VIBRATOR]vibetonz_clk_on is called\n");*/ + vibetonz_clk = clk_get(dev, "timers"); + if (IS_ERR(vibetonz_clk)) { + printk(KERN_ERR "tspdrv: failed to get clock for vibetonz\n"); + goto err_clk0; + } + clk_enable(vibetonz_clk); + clk_put(vibetonz_clk); + + return 0; + +err_clk0: + clk_put(vibetonz_clk); + return -EINVAL; +} + +int vibetonz_clk_off(struct device *dev) +{ + struct clk *vibetonz_clk = NULL; + + vibetonz_clk = clk_get(dev, "timers"); + if (IS_ERR(vibetonz_clk)) { + DbgOut((KERN_ERR "tspdrv: failed to get clock for vibetonz\n")); + goto err_clk0; + } + clk_disable(vibetonz_clk); + clk_put(vibetonz_clk); + + return 0; + +err_clk0: + clk_put(vibetonz_clk); + + return -EINVAL; +} +#else +int vibetonz_clk_on(struct device *dev) +{ + return -EINVAL; +} + +int vibetonz_clk_off(struct device *dev) +{ + return -EINVAL; +} +#endif /* VIBE_ENABLE_SYSTEM_TIMER */ + +int init_module(void) +{ + int nRet, i; /* initialized below */ + nRet = 0; + +#ifdef IMPLEMENT_AS_CHAR_DRIVER + printk(KERN_ERR + "[VIBRATOR]IMPLEMENT_AS_CHAR_DRIVER\n"); + g_nMajor = register_chrdev(0, MODULE_NAME, &fops); + if (g_nMajor < 0) { + printk(KERN_ERR"[VIBRATOR]tspdrv: can't get major number.\n"); + return g_nMajor; + } +#else + nRet = misc_register(&miscdev); + if (nRet) { + printk(KERN_ERR "[VIBRATOR]tspdrv: misc_register failed.\n"); + return nRet; + } +#endif + + nRet = platform_device_register(&platdev); + if (nRet) { + printk(KERN_ERR "tspdrv: platform_device_register failed.\n"); + goto err_platform_dev_reg; + } + + nRet = platform_driver_register(&platdrv); + if (nRet) { + printk(KERN_ERR "tspdrv: platform_driver_register failed.\n"); + goto err_platform_drv_reg; + } + + DbgRecorderInit(()); + + vibetonz_clk_on(&platdev.dev); + + ImmVibeSPI_ForceOut_Initialize(); + VibeOSKernelLinuxInitTimer(); + + /* Get and concatenate device name and initialize data buffer */ + g_cchDeviceName = 0; + for (i = 0; i < NUM_ACTUATORS; i++) { + char *szName = g_szDeviceName + g_cchDeviceName; + ImmVibeSPI_Device_GetName(i, szName, + VIBE_MAX_DEVICE_NAME_LENGTH); + + /* Append version information and get buffer length */ + strcat(szName, VERSION_STR); + g_cchDeviceName += strlen(szName); + + g_SamplesBuffer[i].nIndexPlayingBuffer = -1; /* Not playing */ + g_SamplesBuffer[i].actuatorSamples[0].nBufferSize = 0; + g_SamplesBuffer[i].actuatorSamples[1].nBufferSize = 0; + } + + wake_lock_init(&vib_wake_lock, WAKE_LOCK_SUSPEND, "vib_present"); + return 0; + +err_platform_drv_reg: + platform_device_unregister(&platdev); +err_platform_dev_reg: +#ifdef IMPLEMENT_AS_CHAR_DRIVER + unregister_chrdev(g_nMajor, MODULE_NAME); +#else + misc_deregister(&miscdev); +#endif + return nRet; +} + +void cleanup_module(void) +{ + DbgOut((KERN_INFO "tspdrv: cleanup_module.\n")); + + DbgRecorderTerminate(()); + + VibeOSKernelLinuxTerminateTimer(); + ImmVibeSPI_ForceOut_Terminate(); + + platform_driver_unregister(&platdrv); + platform_device_unregister(&platdev); + +#ifdef IMPLEMENT_AS_CHAR_DRIVER + unregister_chrdev(g_nMajor, MODULE_NAME); +#else + misc_deregister(&miscdev); +#endif + wake_lock_destroy(&vib_wake_lock); +} + +static int open(struct inode *inode, struct file *file) +{ + DbgOut((KERN_INFO "tspdrv: open.\n")); + + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + + return 0; +} + +static int release(struct inode *inode, struct file *file) +{ + DbgOut((KERN_INFO "tspdrv: release.\n")); + + /* + ** Reset force and stop timer when the driver is closed, to make sure + ** no dangling semaphore remains in the system, especially when the + ** driver is run outside of immvibed for testing purposes. + */ + VibeOSKernelLinuxStopTimer(); + + /* + ** Clear the variable used to store the magic number to prevent + ** unauthorized caller to write data. TouchSense service is the only + ** valid caller. + */ + file->private_data = (void *)NULL; + + module_put(THIS_MODULE); + + return 0; +} + +static ssize_t read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + const size_t nBufSize = (g_cchDeviceName > (size_t)(*ppos)) ? + min(count, g_cchDeviceName - (size_t)(*ppos)) : 0; + + /* End of buffer, exit */ + if (0 == nBufSize) + return 0; + + if (0 != copy_to_user(buf, g_szDeviceName + (*ppos), nBufSize)) { + /* Failed to copy all the data, exit */ + DbgOut((KERN_ERR "tspdrv: copy_to_user failed.\n")); + return 0; + } + + /* Update file position and return copied buffer size */ + *ppos += nBufSize; + return nBufSize; +} + +static ssize_t write(struct file *file, const char *buf, size_t count, + loff_t *ppos) +{ + int i = 0; + + *ppos = 0; /* file position not used, always set to 0 */ + + /* + ** Prevent unauthorized caller to write data. + ** TouchSense service is the only valid caller. + */ + if (file->private_data != (void *)TSPDRV_MAGIC_NUMBER) { + DbgOut((KERN_ERR "tspdrv: unauthorized write.\n")); + return 0; + } + + /* Copy immediately the input buffer */ + if (0 != copy_from_user(g_cWriteBuffer, buf, count)) { + /* Failed to copy all the data, exit */ + DbgOut((KERN_ERR "tspdrv: copy_from_user failed.\n")); + return 0; + } + + /* Check buffer size */ + if ((count <= SPI_HEADER_SIZE) || (count > SPI_BUFFER_SIZE)) { + DbgOut((KERN_ERR "tspdrv: invalid write buffer size.\n")); + return 0; + } + + while (i < count) { + int nIndexFreeBuffer; /* initialized below */ + + samples_buffer* pInputBuffer = (samples_buffer *) + (&g_cWriteBuffer[i]); + + if ((i + SPI_HEADER_SIZE) >= count) { + /* + ** Index is about to go beyond the buffer size. + ** (Should never happen). + */ + DbgOut((KERN_EMERG "tspdrv: invalid buffer index.\n")); + } + + /* Check bit depth */ + if (8 != pInputBuffer->nBitDepth) + DbgOut((KERN_WARNING + "tspdrv: invalid bit depth." + "Use default value (8)\n")); + + /* The above code not valid if SPI header size is not 3 */ +#if (SPI_HEADER_SIZE != 3) +#error "SPI_HEADER_SIZE expected to be 3" +#endif + + /* Check buffer size */ + if ((i + SPI_HEADER_SIZE + pInputBuffer->nBufferSize) > count) { + /* + ** Index is about to go beyond the buffer size. + ** (Should never happen). + */ + DbgOut((KERN_EMERG "tspdrv: invalid data size.\n")); + } + + /* Check actuator index */ + if (NUM_ACTUATORS <= pInputBuffer->nActuatorIndex) { + DbgOut((KERN_ERR "tspdrv: invalid actuator index.\n")); + i += (SPI_HEADER_SIZE + pInputBuffer->nBufferSize); + continue; + } + + if (0 == g_SamplesBuffer[pInputBuffer->nActuatorIndex]. + actuatorSamples[0].nBufferSize) { + nIndexFreeBuffer = 0; + } else if (0 == g_SamplesBuffer[pInputBuffer->nActuatorIndex]. + actuatorSamples[1].nBufferSize) { + nIndexFreeBuffer = 1; + } else { + /* No room to store new samples */ + DbgOut((KERN_ERR + "tspdrv: no room to store new samples.\n")); + return 0; + } + + /* Store the data in the free buffer of the given actuator */ + memcpy(&(g_SamplesBuffer[pInputBuffer->nActuatorIndex]. + actuatorSamples[nIndexFreeBuffer]), &g_cWriteBuffer[i], + (SPI_HEADER_SIZE + pInputBuffer->nBufferSize)); + + /* if the no buffer is playing, prepare to play + * g_SamplesBuffer[pInputBuffer->nActuatorIndex]. + * actuatorSamples[nIndexFreeBuffer] */ + if (-1 == g_SamplesBuffer[pInputBuffer-> + nActuatorIndex].nIndexPlayingBuffer) { + g_SamplesBuffer[pInputBuffer->nActuatorIndex]. + nIndexPlayingBuffer = nIndexFreeBuffer; + g_SamplesBuffer[pInputBuffer->nActuatorIndex]. + nIndexOutputValue = 0; + } + + /* Increment buffer index */ + i += (SPI_HEADER_SIZE + pInputBuffer->nBufferSize); + } + +#ifdef QA_TEST + g_nForceLog[g_nForceLogIndex++] = g_cSPIBuffer[0]; + if (g_nForceLogIndex >= FORCE_LOG_BUFFER_SIZE) { + for (i = 0; i < FORCE_LOG_BUFFER_SIZE; i++) { + printk(KERN_DEBUG "<6>%d\t%d\n", + g_nTime, g_nForceLog[i]); + g_nTime += TIME_INCREMENT; + } + g_nForceLogIndex = 0; + } +#endif + + /* Start the timer after receiving new output force */ + g_bIsPlaying = true; + VibeOSKernelLinuxStartTimer(); + + return count; +} + +static long unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ +#ifdef QA_TEST + int i; +#endif + + DbgOut((KERN_INFO "tspdrv: ioctl cmd[0x%x].\n", cmd)); + + switch (cmd) { + case TSPDRV_STOP_KERNEL_TIMER: + /* + * As we send one sample ahead of time, + * we need to finish playing the last sample + * before stopping the timer. So we just set a flag here. + */ + if (true == g_bIsPlaying) + g_bStopRequested = true; + +#ifdef VIBEOSKERNELPROCESSDATA + /* Last data processing to disable amp and stop timer */ + VibeOSKernelProcessData(NULL); +#endif + +#ifdef QA_TEST + if (g_nForceLogIndex) { + for (i = 0; i < g_nForceLogIndex; i++) { + printk(KERN_DEBUG "<6>%d\t%d\n", + g_nTime, g_nForceLog[i]); + g_nTime += TIME_INCREMENT; + } + } + g_nTime = 0; + g_nForceLogIndex = 0; +#endif + break; + + case TSPDRV_MAGIC_NUMBER: + file->private_data = (void *)TSPDRV_MAGIC_NUMBER; + break; + + case TSPDRV_ENABLE_AMP: + wake_lock(&vib_wake_lock); + ImmVibeSPI_ForceOut_AmpEnable(arg); + DbgRecorderReset((arg)); + DbgRecord((arg, ";------- TSPDRV_ENABLE_AMP ---------\n")); + break; + + case TSPDRV_DISABLE_AMP: + /* Small fix for now to handle proper combination of + * TSPDRV_STOP_KERNEL_TIMER and TSPDRV_DISABLE_AMP together + * If a stop was requested, ignore the request as the amp + * will be disabled by the timer proc when it's ready + */ + if (!g_bStopRequested) + ImmVibeSPI_ForceOut_AmpDisable(arg); + wake_unlock(&vib_wake_lock); + break; + + case TSPDRV_GET_NUM_ACTUATORS: + return NUM_ACTUATORS; + } + + return 0; +} + +static int suspend(struct platform_device *pdev, pm_message_t state) +{ + int ret; + + if (g_bIsPlaying) { + ret = -EBUSY; + } else { + /* Disable system timers */ + vibetonz_clk_off(&pdev->dev); + + ret = 0; + } + + DbgOut((KERN_DEBUG "tspdrv: %s (%d).\n", __func__, ret)); + return ret; +} + +static int resume(struct platform_device *pdev) +{ + u32 __iomem *pram; + + /* Restart system timers */ + vibetonz_clk_on(&pdev->dev); + + /* Restore system timers configuration */ + pram = ioremap(S5P_PA_TIMER, 4); + writel(0x0F00, pram); + iounmap(pram); + + DbgOut((KERN_DEBUG "tspdrv: %s.\n", __func__)); + return 0; +} + +static void platform_release(struct device *dev) +{ + DbgOut((KERN_INFO "tspdrv: platform_release.\n")); +} + +late_initcall(init_module); +module_exit(cleanup_module); diff --git a/drivers/motor/tspdrv.h b/drivers/motor/tspdrv.h new file mode 100644 index 0000000..8bbb333 --- /dev/null +++ b/drivers/motor/tspdrv.h @@ -0,0 +1,110 @@ +/* +** ========================================================================= +** File: +** tspdrv.h +** +** Description: +** Constants and type definitions for the TouchSense Kernel Module. +** +** Portions Copyright (c) 2008-2010 Immersion Corporation. All Rights Reserved. +** +** This file contains Original Code and/or Modifications of Original Code +** as defined in and that are subject to the GNU Public License v2 - +** (the 'License'). You may not use this file except in compliance with the +** License. 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 or contact +** TouchSenseSales@immersion.com. +** +** The Original Code and all software distributed under the License are +** distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +** EXPRESS OR IMPLIED, AND IMMERSION HEREBY DISCLAIMS ALL SUCH WARRANTIES, +** INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS +** FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see +** the License for the specific language governing rights and limitations +** under the License. +** ========================================================================= +*/ + +#ifndef _TSPDRV_H +#define _TSPDRV_H + +/* Constants */ +#define MODULE_NAME "tspdrv" +#define TSPDRV "/dev/"MODULE_NAME +#define TSPDRV_MAGIC_NUMBER 0x494D4D52 +#define TSPDRV_STOP_KERNEL_TIMER _IO(TSPDRV_MAGIC_NUMBER & 0xFF, 1) +/* +** Obsolete IOCTL command +** #define TSPDRV_IDENTIFY_CALLER _IO(TSPDRV_MAGIC_NUMBER & 0xFF, 2) +*/ +#define TSPDRV_ENABLE_AMP _IO(TSPDRV_MAGIC_NUMBER & 0xFF, 3) +#define TSPDRV_DISABLE_AMP _IO(TSPDRV_MAGIC_NUMBER & 0xFF, 4) +#define TSPDRV_GET_NUM_ACTUATORS _IO(TSPDRV_MAGIC_NUMBER & 0xFF, 5) +#define VIBE_MAX_DEVICE_NAME_LENGTH 64 +#define SPI_HEADER_SIZE 3 /* DO NOT CHANGE - SPI buffer header size */ +#define VIBE_OUTPUT_SAMPLE_SIZE 50 /* DO NOT CHANGE - maximum number of samples */ + +/* Type definitions */ +#ifdef __KERNEL__ +typedef int8_t VibeInt8; +typedef u_int8_t VibeUInt8; +typedef int16_t VibeInt16; +typedef u_int16_t VibeUInt16; +typedef int32_t VibeInt32; +typedef u_int32_t VibeUInt32; +typedef u_int8_t VibeBool; +typedef VibeInt32 VibeStatus; + +typedef struct { + VibeUInt8 nActuatorIndex; /* 1st byte is actuator index */ + VibeUInt8 nBitDepth; /* 2nd byte is bit depth */ + VibeUInt8 nBufferSize; /* 3rd byte is data size */ + VibeUInt8 dataBuffer[VIBE_OUTPUT_SAMPLE_SIZE]; +} samples_buffer; + +typedef struct { + VibeInt8 nIndexPlayingBuffer; + VibeUInt8 nIndexOutputValue; + samples_buffer actuatorSamples[2]; /* Use 2 buffers to receive samples from user mode */ +} actuator_samples_buffer; + +#endif + +/* Error and Return value codes */ +#define VIBE_S_SUCCESS 0 /* Success */ +#define VIBE_E_FAIL -4 /* Generic error */ + +#if defined(VIBE_RECORD) && defined(VIBE_DEBUG) + void _RecorderInit(void); + void _RecorderTerminate(void); + void _RecorderReset(int nActuator); + void _Record(int actuatorIndex, const char *format, ...); +#endif + +/* Kernel Debug Macros */ +#ifdef __KERNEL__ + #ifdef VIBE_DEBUG + #define DbgOut(_x_) printk _x_ + #else /* VIBE_DEBUG */ + #define DbgOut(_x_) + #endif /* VIBE_DEBUG */ + + #if defined(VIBE_RECORD) && defined(VIBE_DEBUG) + #define DbgRecorderInit(_x_) _RecorderInit _x_ + #define DbgRecorderTerminate(_x_) _RecorderTerminate _x_ + #define DbgRecorderReset(_x_) _RecorderReset _x_ + #define DbgRecord(_x_) _Record _x_ + #else /* defined(VIBE_RECORD) && defined(VIBE_DEBUG) */ + #define DbgRecorderInit(_x_) + #define DbgRecorderTerminate(_x_) + #define DbgRecorderReset(_x_) + #define DbgRecord(_x_) + #endif /* defined(VIBE_RECORD) && defined(VIBE_DEBUG) */ +#endif /* __KERNEL__ */ + + +int regulator_hapticmotor_enabled; + + +#endif /* _TSPDRV_H */ |