blob: 71bce7b8a16167a7ca97ded55c5231119afe9614 [file] [log] [blame]
Devin Kimba0e9452012-06-21 14:09:34 -07001/*
2** =========================================================================
3** File:
4** tspdrv.c
5**
6** Description:
7** TouchSense Kernel Module main entry-point.
8**
9** Portions Copyright (c) 2008-2011 Immersion Corporation. All Rights Reserved.
10**
11** This file contains Original Code and/or Modifications of Original Code
12** as defined in and that are subject to the GNU Public License v2 -
13** (the 'License'). You may not use this file except in compliance with the
14** License. You should have received a copy of the GNU General Public License
15** along with this program; if not, write to the Free Software Foundation, Inc.,
16** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or contact
17** TouchSenseSales@immersion.com.
18**
19** The Original Code and all software distributed under the License are
20** distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
21** EXPRESS OR IMPLIED, AND IMMERSION HEREBY DISCLAIMS ALL SUCH WARRANTIES,
22** INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
23** FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see
24** the License for the specific language governing rights and limitations
25** under the License.
26** =========================================================================
27*/
28
29#ifndef __KERNEL__
30#define __KERNEL__
31#endif
32
33#include <linux/module.h>
34#include <linux/kernel.h>
35#include <linux/timer.h>
36#include <linux/fs.h>
37#include <linux/version.h>
38#include <linux/miscdevice.h>
39#include <linux/platform_device.h>
40#include <asm/uaccess.h>
41#include <tspdrv.h>
42#include <ImmVibeSPI.c>
43#if defined(VIBE_DEBUG) && defined(VIBE_RECORD)
44#include <tspdrvRecorder.c>
45#endif
46
47/* Device name and version information */
48#define VERSION_STR " v3.4.55.8\n" /* DO NOT CHANGE - this is auto-generated */
49#define VERSION_STR_LEN 16 /* account extra space for future extra digits in version number */
50static char g_szDeviceName[ (VIBE_MAX_DEVICE_NAME_LENGTH
51 + VERSION_STR_LEN)
52 * NUM_ACTUATORS]; /* initialized in init_module */
53static size_t g_cchDeviceName; /* initialized in init_module */
54
55/* Flag indicating whether the driver is in use */
56static char g_bIsPlaying = false;
57
58/* Buffer to store data sent to SPI */
59#define SPI_BUFFER_SIZE (NUM_ACTUATORS * (VIBE_OUTPUT_SAMPLE_SIZE + SPI_HEADER_SIZE))
60static int g_bStopRequested = false;
61static actuator_samples_buffer g_SamplesBuffer[NUM_ACTUATORS] = {{0}};
62static char g_cWriteBuffer[SPI_BUFFER_SIZE];
63
64/* For QA purposes */
65#ifdef QA_TEST
66#define FORCE_LOG_BUFFER_SIZE 128
67#define TIME_INCREMENT 5
68static int g_nTime = 0;
69static int g_nForceLogIndex = 0;
70static VibeInt8 g_nForceLog[FORCE_LOG_BUFFER_SIZE];
71#endif
72
73#if ((LINUX_VERSION_CODE & 0xFFFF00) < KERNEL_VERSION(2,6,0))
74#error Unsupported Kernel version
75#endif
76
77#ifndef HAVE_UNLOCKED_IOCTL
78#define HAVE_UNLOCKED_IOCTL 0
79#endif
80
81#ifdef IMPLEMENT_AS_CHAR_DRIVER
82static int g_nMajor = 0;
83#endif
84
85/* Needs to be included after the global variables because it uses them */
86#ifdef CONFIG_HIGH_RES_TIMERS
87#include <VibeOSKernelLinuxHRTime.c>
88#else
89#include <VibeOSKernelLinuxTime.c>
90#endif
91
92/* File IO */
93static int open(struct inode *inode, struct file *file);
94static int release(struct inode *inode, struct file *file);
95static ssize_t read(struct file *file, char *buf, size_t count, loff_t *ppos);
96static ssize_t write(struct file *file, const char *buf, size_t count, loff_t *ppos);
97#if HAVE_UNLOCKED_IOCTL
98static long unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
99#else
100static int ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
101#endif
102static struct file_operations fops =
103{
104 .owner = THIS_MODULE,
105 .read = read,
106 .write = write,
107#if HAVE_UNLOCKED_IOCTL
108 .unlocked_ioctl = unlocked_ioctl,
109#else
110 .ioctl = ioctl,
111#endif
112 .open = open,
113 .release = release,
114 .llseek = default_llseek /* using default implementation as declared in linux/fs.h */
115};
116
117#ifndef IMPLEMENT_AS_CHAR_DRIVER
118static struct miscdevice miscdev =
119{
120 .minor = MISC_DYNAMIC_MINOR,
121 .name = MODULE_NAME,
122 .fops = &fops
123};
124#endif
125
126static int suspend(struct platform_device *pdev, pm_message_t state);
127static int resume(struct platform_device *pdev);
128static struct platform_driver platdrv =
129{
130 .suspend = suspend,
131 .resume = resume,
132 .driver = {
133 .name = MODULE_NAME,
134 },
135};
136
137static void platform_release(struct device *dev);
138static struct platform_device platdev =
139{
140 .name = MODULE_NAME,
141 .id = -1,
142 .dev = {
143 .platform_data = NULL,
144 .release = platform_release,
145 },
146};
147
148
149int __init tspdrv_init(void)
150{
151 int nRet, i; /* initialized below */
152
153 DbgOut((KERN_INFO "tspdrv: init_module.\n"));
154
155#ifdef IMPLEMENT_AS_CHAR_DRIVER
156 g_nMajor = register_chrdev(0, MODULE_NAME, &fops);
157 if (g_nMajor < 0) {
158 DbgOut((KERN_ERR "tspdrv: can't get major number.\n"));
159 return g_nMajor;
160 }
161#else
162 nRet = misc_register(&miscdev);
163 if (nRet) {
164 DbgOut((KERN_ERR "tspdrv: misc_register failed.\n"));
165 return nRet;
166 }
167#endif
168
169 nRet = platform_device_register(&platdev);
170 if (nRet) {
171 DbgOut((KERN_ERR "tspdrv: platform_device_register failed.\n"));
172 }
173
174 nRet = platform_driver_register(&platdrv);
175 if (nRet) {
176 DbgOut((KERN_ERR "tspdrv: platform_driver_register failed.\n"));
177 }
178
179 DbgRecorderInit(());
180
181 ImmVibeSPI_ForceOut_Initialize();
182 VibeOSKernelLinuxInitTimer();
183
184 /* Get and concatenate device name and initialize data buffer */
185 g_cchDeviceName = 0;
186 for (i = 0; i < NUM_ACTUATORS; i++) {
187 char *szName = g_szDeviceName + g_cchDeviceName;
188 ImmVibeSPI_Device_GetName(i, szName, VIBE_MAX_DEVICE_NAME_LENGTH);
189
190 /* Append version information and get buffer length */
191 strcat(szName, VERSION_STR);
192 g_cchDeviceName += strlen(szName);
193
194 g_SamplesBuffer[i].nIndexPlayingBuffer = -1; /* Not playing */
195 g_SamplesBuffer[i].actuatorSamples[0].nBufferSize = 0;
196 g_SamplesBuffer[i].actuatorSamples[1].nBufferSize = 0;
197 }
198
199 return 0;
200}
201
202void __exit tspdrv_exit(void)
203{
204 DbgOut((KERN_INFO "tspdrv: cleanup_module.\n"));
205
206 DbgRecorderTerminate(());
207
208 VibeOSKernelLinuxTerminateTimer();
209 ImmVibeSPI_ForceOut_Terminate();
210
211 platform_driver_unregister(&platdrv);
212 platform_device_unregister(&platdev);
213
214#ifdef IMPLEMENT_AS_CHAR_DRIVER
215 unregister_chrdev(g_nMajor, MODULE_NAME);
216#else
217 misc_deregister(&miscdev);
218#endif
219}
220
221static int open(struct inode *inode, struct file *file)
222{
223 DbgOut((KERN_INFO "tspdrv: open.\n"));
224
225 if (!try_module_get(THIS_MODULE)) return -ENODEV;
226
227 return 0;
228}
229
230static int release(struct inode *inode, struct file *file)
231{
232 DbgOut((KERN_INFO "tspdrv: release.\n"));
233
234 /*
235 ** Reset force and stop timer when the driver is closed, to make sure
236 ** no dangling semaphore remains in the system, especially when the
237 ** driver is run outside of immvibed for testing purposes.
238 */
239 VibeOSKernelLinuxStopTimer();
240
241 /*
242 ** Clear the variable used to store the magic number to prevent
243 ** unauthorized caller to write data. TouchSense service is the only
244 ** valid caller.
245 */
246 file->private_data = (void*)NULL;
247
248 module_put(THIS_MODULE);
249
250 return 0;
251}
252
253static ssize_t read(struct file *file, char *buf, size_t count, loff_t *ppos)
254{
255 const size_t nBufSize = (g_cchDeviceName > (size_t)(*ppos)) ?
256 min(count, g_cchDeviceName - (size_t)(*ppos)) : 0;
257
258 /* End of buffer, exit */
259 if (0 == nBufSize)
260 return 0;
261
262 if (0 != copy_to_user(buf, g_szDeviceName + (*ppos), nBufSize)) {
263 /* Failed to copy all the data, exit */
264 DbgOut((KERN_ERR "tspdrv: copy_to_user failed.\n"));
265 return 0;
266 }
267
268 /* Update file position and return copied buffer size */
269 *ppos += nBufSize;
270 return nBufSize;
271}
272
273static ssize_t write(struct file *file, const char *buf, size_t count, loff_t *ppos)
274{
275 int i = 0;
276
277 *ppos = 0; /* file position not used, always set to 0 */
278
279 /*
280 ** Prevent unauthorized caller to write data.
281 ** TouchSense service is the only valid caller.
282 */
283 if (file->private_data != (void*)TSPDRV_MAGIC_NUMBER) {
284 DbgOut((KERN_ERR "tspdrv: unauthorized write.\n"));
285 return 0;
286 }
287
288 /* Copy immediately the input buffer */
289 if (0 != copy_from_user(g_cWriteBuffer, buf, count)) {
290 /* Failed to copy all the data, exit */
291 DbgOut((KERN_ERR "tspdrv: copy_from_user failed.\n"));
292 return 0;
293 }
294
295 /* Check buffer size */
296 if ((count <= SPI_HEADER_SIZE) || (count > SPI_BUFFER_SIZE)) {
297 DbgOut((KERN_ERR "tspdrv: invalid write buffer size.\n"));
298 return 0;
299 }
300
301 while (i < count) {
302 int nIndexFreeBuffer; /* initialized below */
303
304 samples_buffer* pInputBuffer = (samples_buffer*)(&g_cWriteBuffer[i]);
305
306 if ((i + SPI_HEADER_SIZE) >= count) {
307 /*
308 ** Index is about to go beyond the buffer size.
309 ** (Should never happen).
310 */
311 DbgOut((KERN_EMERG "tspdrv: invalid buffer index.\n"));
312 }
313
314 /* Check bit depth */
315 if (8 != pInputBuffer->nBitDepth) {
316 DbgOut((KERN_WARNING "tspdrv: invalid bit depth. Use default value (8).\n"));
317 }
318
319 /* The above code not valid if SPI header size is not 3 */
320#if (SPI_HEADER_SIZE != 3)
321#error "SPI_HEADER_SIZE expected to be 3"
322#endif
323
324 /* Check buffer size */
325 if ((i + SPI_HEADER_SIZE + pInputBuffer->nBufferSize) > count) {
326 /*
327 ** Index is about to go beyond the buffer size.
328 ** (Should never happen).
329 */
330 DbgOut((KERN_EMERG "tspdrv: invalid data size.\n"));
331 }
332
333 /* Check actuator index */
334 if (NUM_ACTUATORS <= pInputBuffer->nActuatorIndex) {
335 DbgOut((KERN_ERR "tspdrv: invalid actuator index.\n"));
336 i += (SPI_HEADER_SIZE + pInputBuffer->nBufferSize);
337 continue;
338 }
339
340 if (0 == g_SamplesBuffer[pInputBuffer->nActuatorIndex].actuatorSamples[0].nBufferSize) {
341 nIndexFreeBuffer = 0;
342 }
343 else if (0 == g_SamplesBuffer[pInputBuffer->nActuatorIndex].actuatorSamples[1].nBufferSize) {
344 nIndexFreeBuffer = 1;
345 }
346 else {
347 /* No room to store new samples */
348 DbgOut((KERN_ERR "tspdrv: no room to store new samples.\n"));
349 return 0;
350 }
351
352 /* Store the data in the free buffer of the given actuator */
353 memcpy(&(g_SamplesBuffer[pInputBuffer->nActuatorIndex].actuatorSamples[nIndexFreeBuffer]), &g_cWriteBuffer[i], (SPI_HEADER_SIZE + pInputBuffer->nBufferSize));
354
355 /* If the no buffer is playing, prepare to play g_SamplesBuffer[pInputBuffer->nActuatorIndex].actuatorSamples[nIndexFreeBuffer] */
356 if ( -1 == g_SamplesBuffer[pInputBuffer->nActuatorIndex].nIndexPlayingBuffer) {
357 g_SamplesBuffer[pInputBuffer->nActuatorIndex].nIndexPlayingBuffer = nIndexFreeBuffer;
358 g_SamplesBuffer[pInputBuffer->nActuatorIndex].nIndexOutputValue = 0;
359 }
360
361 /* Increment buffer index */
362 i += (SPI_HEADER_SIZE + pInputBuffer->nBufferSize);
363 }
364
365#ifdef QA_TEST
366 g_nForceLog[g_nForceLogIndex++] = g_cSPIBuffer[0];
367 if (g_nForceLogIndex >= FORCE_LOG_BUFFER_SIZE) {
368 for (i = 0; i < FORCE_LOG_BUFFER_SIZE; i++) {
369 printk("<6>%d\t%d\n", g_nTime, g_nForceLog[i]);
370 g_nTime += TIME_INCREMENT;
371 }
372 g_nForceLogIndex = 0;
373 }
374#endif
375
376 /* Start the timer after receiving new output force */
377 g_bIsPlaying = true;
378 VibeOSKernelLinuxStartTimer();
379
380 return count;
381}
382
383#if HAVE_UNLOCKED_IOCTL
384static long unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
385#else
386static int ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
387#endif
388{
389#ifdef QA_TEST
390 int i;
391#endif
392
393 switch (cmd) {
394 case TSPDRV_STOP_KERNEL_TIMER:
395 /*
396 ** As we send one sample ahead of time, we need to finish playing the last sample
397 ** before stopping the timer. So we just set a flag here.
398 */
399 if (true == g_bIsPlaying)
400 g_bStopRequested = true;
401
402#ifdef VIBEOSKERNELPROCESSDATA
403 /* Last data processing to disable amp and stop timer */
404 VibeOSKernelProcessData(NULL);
405#endif
406
407#ifdef QA_TEST
408 if (g_nForceLogIndex) {
409 for (i=0; i<g_nForceLogIndex; i++) {
410 printk("<6>%d\t%d\n", g_nTime, g_nForceLog[i]);
411 g_nTime += TIME_INCREMENT;
412 }
413 }
414 g_nTime = 0;
415 g_nForceLogIndex = 0;
416#endif
417 break;
418
419 case TSPDRV_MAGIC_NUMBER:
420 file->private_data = (void*)TSPDRV_MAGIC_NUMBER;
421 break;
422
423 case TSPDRV_ENABLE_AMP:
424 ImmVibeSPI_ForceOut_AmpEnable(arg);
425 DbgRecorderReset((arg));
426 DbgRecord((arg,";------- TSPDRV_ENABLE_AMP ---------\n"));
427 break;
428
429 case TSPDRV_DISABLE_AMP:
430 /* Small fix for now to handle proper combination of TSPDRV_STOP_KERNEL_TIMER and TSPDRV_DISABLE_AMP together */
431 /* If a stop was requested, ignore the request as the amp will be disabled by the timer proc when it's ready */
432 if (!g_bStopRequested) {
433 ImmVibeSPI_ForceOut_AmpDisable(arg);
434 }
435 break;
436
437 case TSPDRV_GET_NUM_ACTUATORS:
438 return NUM_ACTUATORS;
439 }
440
441 return 0;
442}
443
444static int suspend(struct platform_device *pdev, pm_message_t state)
445{
446 if (g_bIsPlaying) {
447 DbgOut((KERN_INFO "tspdrv: can't suspend, still playing effects.\n"));
448 return -EBUSY;
449 }
450 else {
451 DbgOut((KERN_INFO "tspdrv: suspend.\n"));
452 return 0;
453 }
454}
455
456static int resume(struct platform_device *pdev)
457{
458 DbgOut((KERN_INFO "tspdrv: resume.\n"));
459
460 return 0; /* can resume */
461}
462
463static void platform_release(struct device *dev)
464{
465 DbgOut((KERN_INFO "tspdrv: platform_release.\n"));
466}
467
468module_init(tspdrv_init);
469module_exit(tspdrv_exit);
470
471/* Module info */
472MODULE_AUTHOR("Immersion Corporation");
473MODULE_DESCRIPTION("TouchSense Kernel Module");
474MODULE_LICENSE("GPL v2");