blob: 2eac14b20049c8841acdc9adfa48e8518d05b50b [file] [log] [blame]
Devin Kimba0e9452012-06-21 14:09:34 -07001/*
Devin Kimc68f4162012-09-18 11:48:20 -07002 * File: VibeOSKernelLinuxHRTime.c
3 *
4 * Description:
5 * High Resolution Time helper functions for Linux.
6 *
7 * Portions Copyright (c) 2010-2011 Immersion Corporation. All Rights Reserved.
8 *
9 * This file contains Original Code and/or Modifications of Original Code
10 * as defined in and that are subject to the GNU Public License v2 -
11 * (the 'License'). You may not use this file except in compliance with the
12 * License. You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation, Inc.,
14 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or contact
15 * TouchSenseSales@immersion.com.
16 *
17 * The Original Code and all software distributed under the License are
18 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
19 * EXPRESS OR IMPLIED, AND IMMERSION HEREBY DISCLAIMS ALL SUCH WARRANTIES,
20 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
21 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see
22 * the License for the specific language governing rights and limitations
23 * under the License.
24 */
Devin Kimba0e9452012-06-21 14:09:34 -070025
26/*
Devin Kimc68f4162012-09-18 11:48:20 -070027 * Kernel high-resolution software timer is used as an example but another type
28 * of timer (such as HW timer or standard software timer) might be used to achieve
29 * the 5ms required rate.
30 */
Devin Kimba0e9452012-06-21 14:09:34 -070031
32#ifndef CONFIG_HIGH_RES_TIMERS
33#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"
34#endif
35
36#include <linux/hrtimer.h>
37#include <linux/mutex.h>
38
39#define WATCHDOG_TIMEOUT 10 /* 10 timer cycles = 50ms */
40
41/* For compatibility with older Kernels */
42#ifndef DEFINE_SEMAPHORE
43#define DEFINE_SEMAPHORE(name) \
44 struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
45#endif
46
47/* Global variables */
48static bool g_bTimerStarted = false;
49static struct hrtimer g_tspTimer;
50static ktime_t g_ktFiveMs;
51static int g_nWatchdogCounter = 0;
52
53DEFINE_SEMAPHORE(g_hMutex);
54
55/* Forward declarations */
56static void VibeOSKernelLinuxStartTimer(void);
57static void VibeOSKernelLinuxStopTimer(void);
58static int VibeOSKernelProcessData(void* data);
59#define VIBEOSKERNELPROCESSDATA
60
61static inline int VibeSemIsLocked(struct semaphore *lock)
62{
63#if ((LINUX_VERSION_CODE & 0xFFFFFF) < KERNEL_VERSION(2,6,27))
64 return atomic_read(&lock->count) != 1;
65#else
66 return (lock->count) != 1;
67#endif
68}
69
70static enum hrtimer_restart tsp_timer_interrupt(struct hrtimer *timer)
71{
72 /* Scheduling next timeout value right away */
73 hrtimer_forward_now(timer, g_ktFiveMs);
74
75 if(g_bTimerStarted) {
76 if (VibeSemIsLocked(&g_hMutex))
77 up(&g_hMutex);
78 }
79
80 return HRTIMER_RESTART;
81}
82
83static int VibeOSKernelProcessData(void* data)
84{
85 int i;
86 int nActuatorNotPlaying = 0;
87
88 for (i = 0; i < NUM_ACTUATORS; i++) {
89 actuator_samples_buffer *pCurrentActuatorSample = &(g_SamplesBuffer[i]);
90
91 if (-1 == pCurrentActuatorSample->nIndexPlayingBuffer) {
92 nActuatorNotPlaying++;
93 if ((NUM_ACTUATORS == nActuatorNotPlaying) &&
94 ((++g_nWatchdogCounter) > WATCHDOG_TIMEOUT)) {
95 VibeInt8 cZero[1] = {0};
96
97 /* Nothing to play for all actuators,
98 * turn off the timer when we reach
99 * the watchdog tick count limit
100 */
101 ImmVibeSPI_ForceOut_SetSamples(i, 8, 1, cZero);
102 ImmVibeSPI_ForceOut_AmpDisable(i);
103 VibeOSKernelLinuxStopTimer();
104
105 /* Reset watchdog counter */
106 g_nWatchdogCounter = 0;
107 }
108 }
109 else {
110 /* Play the current buffer */
111 if (VIBE_E_FAIL == ImmVibeSPI_ForceOut_SetSamples(
112 pCurrentActuatorSample-> actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nActuatorIndex,
113 pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBitDepth,
114 pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBufferSize,
115 pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].dataBuffer)) {
116 /* VIBE_E_FAIL means NAK has been handled.
117 * Schedule timer to restart 5 ms from now
118 */
119 hrtimer_forward_now(&g_tspTimer, g_ktFiveMs);
120 }
121
122 pCurrentActuatorSample->nIndexOutputValue += pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBufferSize;
123
124 if (pCurrentActuatorSample->nIndexOutputValue >= pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBufferSize) {
125 /* Reach the end of the current buffer */
126 pCurrentActuatorSample->actuatorSamples[(int)pCurrentActuatorSample->nIndexPlayingBuffer].nBufferSize = 0;
127
128 /* Switch buffer */
129 (pCurrentActuatorSample->nIndexPlayingBuffer) ^= 1;
130 pCurrentActuatorSample->nIndexOutputValue = 0;
131
132 /* Finished playing, disable amp for actuator (i) */
133 if (g_bStopRequested) {
134 pCurrentActuatorSample->nIndexPlayingBuffer = -1;
135
136 ImmVibeSPI_ForceOut_AmpDisable(i);
137 }
138 }
139 }
140 }
141
142 /* If finished playing, stop timer */
143 if (g_bStopRequested) {
144 VibeOSKernelLinuxStopTimer();
145
146 /* Reset watchdog counter */
147 g_nWatchdogCounter = 0;
148
149 if (VibeSemIsLocked(&g_hMutex))
150 up(&g_hMutex);
151 return 1; /* tell the caller this is the last iteration */
152 }
153
154 return 0;
155}
156
157static void VibeOSKernelLinuxInitTimer(void)
158{
159 /* Get a 5,000,000ns = 5ms time value */
160 g_ktFiveMs = ktime_set(0, 5000000);
161
162 hrtimer_init(&g_tspTimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
163
164 /* Initialize a 5ms-timer with tsp_timer_interrupt as timer callback
165 (interrupt driven)*/
166 g_tspTimer.function = tsp_timer_interrupt;
167}
168
169static void VibeOSKernelLinuxStartTimer(void)
170{
171 int i;
172 int res;
173
174 /* Reset watchdog counter */
175 g_nWatchdogCounter = 0;
176
177 if (!g_bTimerStarted) {
178 if (!VibeSemIsLocked(&g_hMutex))
179 res = down_interruptible(&g_hMutex); /* start locked */
180
181 g_bTimerStarted = true;
182
183 /* Start the timer */
184 hrtimer_start(&g_tspTimer, g_ktFiveMs, HRTIMER_MODE_REL);
185
186 /* Don't block the write() function
187 * after the first sample to allow the host sending
188 * the next samples with no delay
189 */
190 for (i = 0; i < NUM_ACTUATORS; i++) {
191 if ((g_SamplesBuffer[i].actuatorSamples[0].nBufferSize) || (g_SamplesBuffer[i].actuatorSamples[1].nBufferSize)) {
192 g_SamplesBuffer[i].nIndexOutputValue = 0;
193 return;
194 }
195 }
196 }
197
198 if (0 != VibeOSKernelProcessData(NULL))
199 return;
200
201 /*
Devin Kimc68f4162012-09-18 11:48:20 -0700202 * Use interruptible version of down to be safe
203 * (try to not being stuck here
204 * if the mutex is not freed for any reason)
205 * wait for the mutex to be freed by the timer
206 */
Devin Kimba0e9452012-06-21 14:09:34 -0700207 res = down_interruptible(&g_hMutex);
208 if (res != 0) {
209 DbgOut((KERN_INFO "VibeOSKernelLinuxStartTimer: down_interruptible interrupted by a signal.\n"));
210 }
211}
212
213static void VibeOSKernelLinuxStopTimer(void)
214{
215 int i;
216
217 if (g_bTimerStarted) {
218 g_bTimerStarted = false;
219 hrtimer_cancel(&g_tspTimer);
220 }
221
222 /* Reset samples buffers */
223 for (i = 0; i < NUM_ACTUATORS; i++) {
224 g_SamplesBuffer[i].nIndexPlayingBuffer = -1;
225 g_SamplesBuffer[i].actuatorSamples[0].nBufferSize = 0;
226 g_SamplesBuffer[i].actuatorSamples[1].nBufferSize = 0;
227 }
228 g_bStopRequested = false;
229 g_bIsPlaying = false;
230}
231
232static void VibeOSKernelLinuxTerminateTimer(void)
233{
234 VibeOSKernelLinuxStopTimer();
235 if (VibeSemIsLocked(&g_hMutex))
236 up(&g_hMutex);
237}