vibrator: add immersion touchsense vibrator

Change-Id: I543987642578f35a3a923a2d1368bee0997fdec1
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 48300eb..e6f9aac 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -611,6 +611,12 @@
 	  This option enalbes device driver support for Android Vibrator driver.
 	  Some functions should be given by platform data for enable PWM, Motor IC, etc.
 
+config TOUCHSENSE_VIBRATOR
+	bool "Immersion Touchsense vibrator support"
+	help
+	  This option enables device driver support for Immersion Touchsense
+	  vibrator.
+
 config PMIC8XXX_NFC
 	tristate "Qualcomm PM8XXX support for Near Field Communication"
 	depends on MFD_PM8XXX
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 79b0827..a4a07ac 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -72,3 +72,4 @@
 obj-$(CONFIG_PMIC8058_XOADC) += pmic8058-xoadc.o
 obj-$(CONFIG_QSEECOM) += qseecom.o
 obj-$(CONFIG_QFP_FUSE) += qfp_fuse.o
+obj-y += tspdrv/
diff --git a/drivers/misc/tspdrv/ImmVibeSPI.c b/drivers/misc/tspdrv/ImmVibeSPI.c
new file mode 100644
index 0000000..35cc456
--- /dev/null
+++ b/drivers/misc/tspdrv/ImmVibeSPI.c
@@ -0,0 +1,426 @@
+/*
+** =========================================================================
+** 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/io.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/regulator/msm-gpio-regulator.h>
+
+#include <mach/irqs.h>
+#include <mach/gpiomux.h>
+#include <mach/msm_iomap.h>
+#include <mach/msm_xo.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 = false;
+
+/* gpio and clock control for vibrator */
+
+#define PM8921_GPIO_BASE                NR_GPIO_IRQS
+#define PM8921_GPIO_PM_TO_SYS(pm_gpio)  (pm_gpio - 1 + PM8921_GPIO_BASE)
+
+#define REG_WRITEL(value, reg)          writel(value, (MSM_CLK_CTL_BASE + reg))
+#define REG_READL(reg)                  readl((MSM_CLK_CTL_BASE + reg))
+
+#define GPn_MD_REG(n)                   (0x2D00+32*(n))
+#define GPn_NS_REG(n)                   (0x2D24+32*(n))
+
+/*
+** This SPI supports only one actuator.
+*/
+#define NUM_ACTUATORS                   1
+
+#define PWM_DUTY_MAX                    579 /* 13MHz / (579 + 1) = 22.4kHz */
+
+#define GPIO_LIN_MOTOR_EN               33
+#define GPIO_LIN_MOTOR_PWR              47
+#define GPIO_LIN_MOTOR_PWM              3
+
+#define GP_CLK_ID                       0 /* gp clk 0 */
+#define GP_CLK_M_DEFAULT                1
+#define GP_CLK_N_DEFAULT                166
+#define GP_CLK_D_MAX                    GP_CLK_N_DEFAULT
+#define GP_CLK_D_HALF                   (GP_CLK_N_DEFAULT >> 1)
+
+#define MOTOR_AMP                       120
+
+
+static struct gpiomux_setting vibrator_suspend_cfg = {
+	.func = GPIOMUX_FUNC_GPIO,
+	.drv = GPIOMUX_DRV_2MA,
+	.pull = GPIOMUX_PULL_NONE,
+};
+
+static struct gpiomux_setting vibrator_active_cfg_gpio3 = {
+	.func = GPIOMUX_FUNC_2, /*gp_mn:2 */
+	.drv = GPIOMUX_DRV_2MA,
+	.pull = GPIOMUX_PULL_NONE,
+};
+
+static struct msm_gpiomux_config gpio2_vibrator_configs[] = {
+	{
+		.gpio = 3,
+		.settings = {
+			[GPIOMUX_ACTIVE] = &vibrator_active_cfg_gpio3,
+			[GPIOMUX_SUSPENDED] = &vibrator_suspend_cfg,
+		},
+	},
+};
+
+static struct msm_xo_voter *vib_clock;
+static int vibrator_clock_init(void)
+{
+	int rc;
+	/*Vote for XO clock*/
+	vib_clock = msm_xo_get(MSM_XO_TCXO_D0, "vib_clock");
+
+	if (IS_ERR(vib_clock)) {
+		rc = PTR_ERR(vib_clock);
+		printk(KERN_ERR "%s: Couldn't get TCXO_D0 vote for Vib(%d)\n",
+							__func__, rc);
+	}
+	return rc;
+}
+
+static int vibrator_clock_on(void)
+{
+	int rc;
+	rc = msm_xo_mode_vote(vib_clock, MSM_XO_MODE_ON);
+	if (rc < 0) {
+		printk(KERN_ERR "%s: Failed to vote for TCX0_D0 ON (%d)\n",
+					__func__, rc);
+	}
+	return rc;
+}
+
+static int vibrator_clock_off(void)
+{
+	int rc;
+	rc = msm_xo_mode_vote(vib_clock, MSM_XO_MODE_OFF);
+	if (rc < 0) {
+		printk(KERN_ERR "%s: Failed to vote for TCX0_D0 OFF (%d)\n",
+					__func__, rc);
+	}
+	return rc;
+}
+
+static int vibrator_power_set(int enable)
+{
+	int rc = 0;
+	static struct regulator *vreg_l16 = NULL;
+	int enabled = 0;
+
+	if (unlikely(!vreg_l16)) {
+		vreg_l16 = regulator_get(NULL, "8921_l16"); /* 2.6 ~ 3V */
+
+		if (IS_ERR(vreg_l16)) {
+			pr_err("%s: regulator get of 8921_lvs6 failed (%ld)\n",
+						__func__, PTR_ERR(vreg_l16));
+			rc = PTR_ERR(vreg_l16);
+			return rc;
+		}
+	}
+
+	/* fix the unbalanced disables */
+	enabled = regulator_is_enabled(vreg_l16);
+	if (enabled > 0) {
+		if (enable) { /* already enabled */
+			printk("vibrator already enabled\n");
+			return 0;
+		}
+	} else if (enabled == 0) {
+		if (enable == 0) { /* already disabled */
+			printk("vibrator already disabled\n");
+			return 0;
+		}
+	} else { /*  (enabled < 0) */
+		pr_warn("%s: regulator_is_enabled failed\n", __func__);
+	}
+
+	//rc = regulator_set_voltage(vreg_l16, 3000000, 3000000);
+	rc = regulator_set_voltage(vreg_l16, 2800000, 2800000);
+
+	if(enable) {
+		printk("vibrator_power_set() : vibrator enable\n");
+		rc = regulator_enable(vreg_l16);
+	}
+	else {
+		printk("vibrator_power_set() : vibrator disable\n");
+		rc = regulator_disable(vreg_l16);
+	}
+
+	return rc;
+}
+
+static void vibrator_pwm_set(int enable, int amp, int n_value)
+{
+	uint M_VAL	= GP_CLK_M_DEFAULT;
+	uint D_VAL	= GP_CLK_D_MAX;
+	uint D_INV	= 0; /* QCT support invert bit for msm8960 */
+
+	if (enable) {
+		vibrator_clock_on();
+
+		D_VAL = (((GP_CLK_D_MAX -1) * amp) >> 8) + GP_CLK_D_HALF;
+
+		if (D_VAL > GP_CLK_D_HALF) {
+			if (D_VAL == GP_CLK_D_MAX)      /* Max duty is 99% */
+				D_VAL = 2;
+			else
+				D_VAL = GP_CLK_D_MAX - D_VAL;
+
+			D_INV = 1;
+		}
+
+		REG_WRITEL(
+			(((M_VAL & 0xffU) << 16U) +     /* M_VAL[23:16] */
+			((~(D_VAL << 1)) & 0xffU)),     /* D_VAL[7:0] */
+			GPn_MD_REG(GP_CLK_ID));
+
+		REG_WRITEL(
+			((((~(n_value-M_VAL)) & 0xffU) << 16U) + /* N_VAL[23:16] */
+			(1U << 11U) +  	/* CLK_ROOT_ENA[11]      : Enable(1) */
+			((D_INV & 0x01U) << 10U) +/* CLK_INV[10] : Disable(0) */
+			(1U << 9U) +    /* CLK_BRANCH_ENA[9]     : Enable(1) */
+			(1U << 8U) +    /* NMCNTR_EN[8]          : Enable(1) */
+			(0U << 7U) +    /* MNCNTR_RST[7]         : Not Active(0) */
+			(2U << 5U) +    /* MNCNTR_MODE[6:5]      : Dual-edge mode(2) */
+			(3U << 3U) +    /* PRE_DIV_SEL[4:3]      : Div-4 (3) */
+			(5U << 0U)),    /* SRC_SEL[2:0]          : CXO (5) */
+			GPn_NS_REG(GP_CLK_ID));
+	}
+	else {
+		vibrator_clock_off();
+
+		REG_WRITEL(
+			((((~(n_value-M_VAL)) & 0xffU) << 16U) + /* N_VAL[23:16] */
+			(0U << 11U) +	/* CLK_ROOT_ENA[11]	: Disable(0) */
+			(0U << 10U) +	/* CLK_INV[10]		: Disable(0) */
+			(0U << 9U) +	/* CLK_BRANCH_ENA[9]	: Disable(0) */
+			(0U << 8U) +	/* NMCNTR_EN[8]		: Disable(0) */
+			(0U << 7U) +	/* MNCNTR_RST[7]	: Not Active(0) */
+			(2U << 5U) +	/* MNCNTR_MODE[6:5]	: Dual-edge mode(2) */
+			(3U << 3U) +	/* PRE_DIV_SEL[4:3]	: Div-4 (3) */
+			(5U << 0U)),	/* SRC_SEL[2:0]		: CXO (5) */
+			GPn_NS_REG(GP_CLK_ID));
+	}
+}
+
+static void vibrator_ic_enable_set(int enable)
+{
+	int gpio_lin_motor_en = 0;
+	gpio_lin_motor_en = PM8921_GPIO_PM_TO_SYS(GPIO_LIN_MOTOR_EN);
+
+	if (enable)
+		gpio_direction_output(gpio_lin_motor_en, 1);
+	else
+		gpio_direction_output(gpio_lin_motor_en, 0);
+}
+
+/*
+** Called to disable amp (disable output force)
+*/
+IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_AmpDisable(VibeUInt8 nActuatorIndex)
+{
+
+	if (g_bAmpEnabled) {
+		DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_AmpDisable.\n"));
+
+		g_bAmpEnabled = false;
+
+		vibrator_ic_enable_set(0);
+		vibrator_pwm_set(0, 0, GP_CLK_N_DEFAULT);
+		vibrator_power_set(0);
+	}
+
+	return VIBE_S_SUCCESS;
+}
+
+/*
+** Called to enable amp (enable output force)
+*/
+IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_AmpEnable(VibeUInt8 nActuatorIndex)
+{
+
+	if (!g_bAmpEnabled) {
+		DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_AmpEnable.\n"));
+
+		g_bAmpEnabled = true;
+
+		vibrator_power_set(1);
+		vibrator_pwm_set(1, 0, GP_CLK_N_DEFAULT);
+		vibrator_ic_enable_set(1);
+	}
+	else {
+		DbgOut((KERN_DEBUG "[ImmVibeSPI] : ImmVibeSPI_ForceOut_AmpEnable [%d]\n", g_bAmpEnabled ));
+	}
+
+	return VIBE_S_SUCCESS;
+}
+
+/*
+** Called at initialization time to set PWM freq, disable amp, etc...
+*/
+IMMVIBESPIAPI VibeStatus ImmVibeSPI_ForceOut_Initialize(void)
+{
+	int rc;
+	int gpio_motor_en = 0;
+	int gpio_motor_pwm = 0;
+
+	DbgOut((KERN_DEBUG "ImmVibeSPI_ForceOut_Initialize.\n"));
+
+	/* to force ImmVibeSPI_ForceOut_AmpDisable disabling the amp */
+	g_bAmpEnabled = true;
+
+	/*
+	** 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).
+	*/
+
+	gpio_motor_en = GPIO_LIN_MOTOR_EN;
+	gpio_motor_pwm = GPIO_LIN_MOTOR_PWM;
+
+	/* GPIO function setting */
+	msm_gpiomux_install(gpio2_vibrator_configs, ARRAY_SIZE(gpio2_vibrator_configs));
+
+	/* GPIO setting for Motor EN in pmic8921 */
+	gpio_motor_en = PM8921_GPIO_PM_TO_SYS(GPIO_LIN_MOTOR_EN);
+	rc = gpio_request(gpio_motor_en, "lin_motor_en");
+
+	if (rc) {
+		DbgOut(("GPIO_LIN_MOTOR_EN %d request failed\n", gpio_motor_en));
+		return -1;
+	}
+
+	/* gpio init */
+	rc = gpio_request(gpio_motor_pwm, "lin_motor_pwm");
+
+	if (unlikely(rc < 0))
+		DbgOut(("not able to get gpio\n"));
+
+	vibrator_clock_init();
+
+	ImmVibeSPI_ForceOut_AmpDisable(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)
+{
+	VibeInt8 nForce;
+
+	switch (nOutputSignalBitDepth) {
+	case 8:
+		/* pForceOutputBuffer is expected to contain 1 byte */
+		if (nBufferSizeInBytes != 1)
+			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;
+	}
+	/* Check the Force value with Max and Min force value */
+
+	if (nForce > 127)
+		nForce = 127;
+	if (nForce < -127)
+		nForce = -127;
+
+	vibrator_pwm_set(1, nForce, GP_CLK_N_DEFAULT);
+
+	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)
+{
+#if 0
+	/* The following code is provided as a sample.
+	 * Please modify as required.
+	 */
+
+	if ((!szDevName) || (nSize < 1))
+		return VIBE_E_FAIL;
+
+	DbgOut((KERN_DEBUG "ImmVibeSPI_Device_GetName.\n"));
+
+	strncpy(szDevName, "Generic Linux Device", nSize-1);
+	/* make sure the string is NULL terminated */
+	szDevName[nSize - 1] = '\0';
+#endif
+
+	return VIBE_S_SUCCESS;
+}
diff --git a/drivers/misc/tspdrv/Makefile b/drivers/misc/tspdrv/Makefile
new file mode 100644
index 0000000..a6977f8
--- /dev/null
+++ b/drivers/misc/tspdrv/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for Immersion Touchsense vibrator devices.
+#
+
+obj-$(CONFIG_TOUCHSENSE_VIBRATOR)	+= tspdrv.o
diff --git a/drivers/misc/tspdrv/VibeOSKernelLinuxHRTime.c b/drivers/misc/tspdrv/VibeOSKernelLinuxHRTime.c
new file mode 100644
index 0000000..b84aee3
--- /dev/null
+++ b/drivers/misc/tspdrv/VibeOSKernelLinuxHRTime.c
@@ -0,0 +1,239 @@
+/*
+** =========================================================================
+** File:
+**     VibeOSKernelLinuxHRTime.c
+**
+** Description:
+**     High Resolution Time helper functions for Linux.
+**
+** Portions Copyright (c) 2010-2011 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 */
+
+/* For compatibility with older Kernels */
+#ifndef DEFINE_SEMAPHORE
+#define DEFINE_SEMAPHORE(name) \
+	struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
+#endif
+
+/* 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)
+	*/
+	/* wait for the mutex to be freed by the timer */
+	res = down_interruptible(&g_hMutex);
+	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/misc/tspdrv/VibeOSKernelLinuxTime.c b/drivers/misc/tspdrv/VibeOSKernelLinuxTime.c
new file mode 100644
index 0000000..39f7391
--- /dev/null
+++ b/drivers/misc/tspdrv/VibeOSKernelLinuxTime.c
@@ -0,0 +1,237 @@
+/*
+** =========================================================================
+** File:
+**     VibeOSKernelLinuxTime.c
+**
+** Description:
+**     Time helper functions for Linux.
+**
+** Portions Copyright (c) 2008-2011 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.
+** =========================================================================
+*/
+
+#error "Please read the following statement"
+/*
+** Kernel standard software timer is used as an example but another type
+** of timer (such as HW timer or high-resolution software timer) might be used
+** to achieve the 5ms required rate.
+*/
+#error "End of statement"
+
+#if (HZ != 1000)
+#error The Kernel timer is not configured at 1ms. Please update TIMER_INCR to generate a proper 5ms timer.
+#endif
+
+#include <linux/mutex.h>
+
+#define TIMER_INCR              5       /* run timer at 5 jiffies (== 5ms) */
+#define WATCHDOG_TIMEOUT        10      /* 10 timer cycles = 50ms */
+
+/* For compatibility with older Kernels */
+#ifndef DEFINE_SEMAPHORE
+#define DEFINE_SEMAPHORE(name) \
+	struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
+#endif
+
+/* Global variables */
+static bool g_bTimerStarted = false;
+static struct timer_list g_timerList;
+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 void tsp_timer_interrupt(unsigned long param)
+{
+	/* Scheduling next timeout value right away */
+	mod_timer(&g_timerList, jiffies + TIMER_INCR);
+
+	if(g_bTimerStarted) {
+		if (VibeSemIsLocked(&g_hMutex))
+			up(&g_hMutex);
+	}
+}
+
+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 */
+				mod_timer(&g_timerList, jiffies + TIMER_INCR);
+			}
+
+			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)
+{
+	/* Initialize a 5ms-timer with VibeOSKernelTimerProc as timer callback */
+	init_timer(&g_timerList);
+	g_timerList.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 */
+		g_timerList.expires = jiffies + TIMER_INCR;
+		add_timer(&g_timerList);
+
+		/* 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;
+
+		/*
+		** Stop the timer.
+		** Use del_timer vs. del_timer_sync
+		** del_timer_sync may cause a Kernel "soft lockup" on multi-CPU platforms
+		** as VibeOSKernelLinuxStopTimer is called from the timer tick handler.
+		*/
+		del_timer(&g_timerList);
+	}
+
+	/* 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/misc/tspdrv/tspdrv.c b/drivers/misc/tspdrv/tspdrv.c
new file mode 100644
index 0000000..71bce7b
--- /dev/null
+++ b/drivers/misc/tspdrv/tspdrv.c
@@ -0,0 +1,474 @@
+/*
+** =========================================================================
+** File:
+**     tspdrv.c
+**
+** Description:
+**     TouchSense Kernel Module main entry-point.
+**
+** Portions Copyright (c) 2008-2011 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 <asm/uaccess.h>
+#include <tspdrv.h>
+#include <ImmVibeSPI.c>
+#if defined(VIBE_DEBUG) && defined(VIBE_RECORD)
+#include <tspdrvRecorder.c>
+#endif
+
+/* Device name and version information */
+#define VERSION_STR " v3.4.55.8\n" /* DO NOT CHANGE - this is auto-generated */
+#define VERSION_STR_LEN 16         /* account extra space for future extra digits in version number */
+static char g_szDeviceName[  (VIBE_MAX_DEVICE_NAME_LENGTH
+                            + VERSION_STR_LEN)
+                            * NUM_ACTUATORS]; /* initialized in init_module */
+static size_t g_cchDeviceName;                /* initialized in init_module */
+
+/* Flag indicating whether the driver is in use */
+static char g_bIsPlaying = false;
+
+/* Buffer to store data sent to SPI */
+#define SPI_BUFFER_SIZE (NUM_ACTUATORS * (VIBE_OUTPUT_SAMPLE_SIZE + SPI_HEADER_SIZE))
+static int g_bStopRequested = false;
+static actuator_samples_buffer g_SamplesBuffer[NUM_ACTUATORS] = {{0}};
+static char g_cWriteBuffer[SPI_BUFFER_SIZE];
+
+/* For QA purposes */
+#ifdef QA_TEST
+#define FORCE_LOG_BUFFER_SIZE   128
+#define TIME_INCREMENT          5
+static int g_nTime = 0;
+static int g_nForceLogIndex = 0;
+static VibeInt8 g_nForceLog[FORCE_LOG_BUFFER_SIZE];
+#endif
+
+#if ((LINUX_VERSION_CODE & 0xFFFF00) < KERNEL_VERSION(2,6,0))
+#error Unsupported Kernel version
+#endif
+
+#ifndef HAVE_UNLOCKED_IOCTL
+#define HAVE_UNLOCKED_IOCTL 0
+#endif
+
+#ifdef IMPLEMENT_AS_CHAR_DRIVER
+static int g_nMajor = 0;
+#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);
+#if HAVE_UNLOCKED_IOCTL
+static long unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+#else
+static int ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
+#endif
+static struct file_operations fops =
+{
+	.owner = THIS_MODULE,
+	.read = read,
+	.write = write,
+#if HAVE_UNLOCKED_IOCTL
+	.unlocked_ioctl = unlocked_ioctl,
+#else
+	.ioctl = ioctl,
+#endif
+	.open = open,
+	.release = release,
+	.llseek = default_llseek /* using default implementation as declared in linux/fs.h */
+};
+
+#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,
+	.id =       -1,
+	.dev = {
+		.platform_data = NULL,
+		.release = platform_release,
+	},
+};
+
+
+int __init tspdrv_init(void)
+{
+	int nRet, i;   /* initialized below */
+
+	DbgOut((KERN_INFO "tspdrv: init_module.\n"));
+
+#ifdef IMPLEMENT_AS_CHAR_DRIVER
+	g_nMajor = register_chrdev(0, MODULE_NAME, &fops);
+	if (g_nMajor < 0) {
+		DbgOut((KERN_ERR "tspdrv: can't get major number.\n"));
+		return g_nMajor;
+	}
+#else
+	nRet = misc_register(&miscdev);
+	if (nRet) {
+		DbgOut((KERN_ERR "tspdrv: misc_register failed.\n"));
+		return nRet;
+	}
+#endif
+
+	nRet = platform_device_register(&platdev);
+	if (nRet) {
+		DbgOut((KERN_ERR "tspdrv: platform_device_register failed.\n"));
+	}
+
+	nRet = platform_driver_register(&platdrv);
+	if (nRet) {
+		DbgOut((KERN_ERR "tspdrv: platform_driver_register failed.\n"));
+	}
+
+	DbgRecorderInit(());
+
+	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;
+	}
+
+	return 0;
+}
+
+void __exit tspdrv_exit(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
+}
+
+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("<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;
+}
+
+#if HAVE_UNLOCKED_IOCTL
+static long unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+#else
+static int ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+#endif
+{
+#ifdef QA_TEST
+	int i;
+#endif
+
+	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("<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:
+		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);
+		}
+		break;
+
+	case TSPDRV_GET_NUM_ACTUATORS:
+		return NUM_ACTUATORS;
+	}
+
+	return 0;
+}
+
+static int suspend(struct platform_device *pdev, pm_message_t state)
+{
+	if (g_bIsPlaying) {
+	       	DbgOut((KERN_INFO "tspdrv: can't suspend, still playing effects.\n"));
+	       	return -EBUSY;
+	}
+	else {
+		DbgOut((KERN_INFO "tspdrv: suspend.\n"));
+		return 0;
+	}
+}
+
+static int resume(struct platform_device *pdev)
+{
+	DbgOut((KERN_INFO "tspdrv: resume.\n"));
+
+	return 0;   /* can resume */
+}
+
+static void platform_release(struct device *dev)
+{
+	DbgOut((KERN_INFO "tspdrv: platform_release.\n"));
+}
+
+module_init(tspdrv_init);
+module_exit(tspdrv_exit);
+
+/* Module info */
+MODULE_AUTHOR("Immersion Corporation");
+MODULE_DESCRIPTION("TouchSense Kernel Module");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/tspdrv/tspdrv.h b/drivers/misc/tspdrv/tspdrv.h
new file mode 100644
index 0000000..c5bd5b1
--- /dev/null
+++ b/drivers/misc/tspdrv/tspdrv.h
@@ -0,0 +1,106 @@
+/*
+** =========================================================================
+** 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__
+    #if 1 // def 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__ */
+
+#endif  /* _TSPDRV_H */