From: Adrian M. <ad...@mc...> - 2002-02-19 00:27:13
|
With further thanks to Paul... /* * Purupuru driver * for SEGA Dreamcast * Copyright (c) Adrian McMenamin, 2002 * * Licenced under the terms * of the Free Software Foundation * General Public Licence * Version 2 * See http://www.gnu.org * * Based on pre-existing Maple device coding * Copyright MR Brown, YAEGASHI Takeshi and others * * */ /* * First posted 18 Feburary 2002 * */ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/module.h> #include <linux/init.h> #include <linux/timer.h> #include <linux/maple.h> #include <linux/list.h> #include <asm/uaccess.h> #include <linux/mm.h> #include <asm/atomic.h> #define PURU_MINOR_BASE 64 #define PPP_VERSION 1 #define _DEBUG_ #ifdef _DEBUG_ #define DEBGM(fmt, args...) (printk(KERN_ERR fmt, ##args)) #else #define DEBGM(fmt, args...) ((void) 0) #endif #define CHECKFUNC EVIOCGBIT(EV_FF, 16) static int last_minor; static unsigned long last_jiffies = -1; /* one list shared through all devices */ LIST_HEAD(effects_list); /* may be more than one puru puru attached */ LIST_HEAD(devices_list); struct dc_puru_effect { int length_of_time; u16 sid; struct list_head list; }; struct dc_purupuru { struct input_dev dev; struct maple_driver_data *data; struct list_head list; int minor; atomic_t active; int timeleft; }; static struct input_handle *purupuru_connect(struct input_handler *handler, struct input_dev *dev); static struct input_handler purupuru_handler; static int dc_puru_open(struct input_dev *dev) { return 0; } static void dc_puru_close(struct input_dev *dev) { } static int dc_purupuru_connect(struct maple_driver_data *d) { /*unsigned long data = d->function_data; */ struct dc_purupuru *puru; if (!(puru = kmalloc(sizeof(struct dc_purupuru), GFP_KERNEL))) { DEBGM("Memory allocation failure\n"); return -ENOMEM; } memset(puru, 0, sizeof(struct dc_purupuru)); puru->data = d; d->private_data = puru; puru->dev.private = puru; puru->dev.open = dc_puru_open; puru->dev.close = dc_puru_close; puru->dev.name = d->dev->product_name; puru->dev.event = NULL; puru->dev.idbus = BUS_MAPLE; input_register_device(&puru->dev); input_register_handler(&purupuru_handler); puru->minor = ((puru->dev.handle)->handler)->minor; /* Add this device to the list */ list_add_tail(&puru->list, &devices_list); return 0; } static void dc_purupuru_disconnect(struct maple_driver_data *d) { struct dc_purupuru *puru = d->private_data; input_unregister_device(&puru->dev); /* Remove this device from list */ list_del(&puru->list); kfree(puru); } /* static void dc_purupuru_callback(struct maple_driver_data *data) */ /* { */ /* struct mapleq *mq = &data->mq; */ /* int res = mq->recvbuf[0]; */ /* printk */ /* ("Maple reply (%d, %d) cmd=%d => %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", */ /* mq->port, mq->unit, mq->command, res, mq->recvbuf[1], */ /* mq->recvbuf[2], mq->recvbuf[3], mq->recvbuf[4], */ /* mq->recvbuf[5], mq->recvbuf[6], mq->recvbuf[7], */ /* mq->recvbuf[8], mq->recvbuf[9], mq->recvbuf[10], */ /* mq->recvbuf[11]); */ /* } */ static int effect_number = 0; static int puru_ioctl(struct inode *inode, struct file *filip, unsigned int cmd, unsigned long arg) { struct ff_effect *ff_upload; struct ff_replay ff_time; struct dc_puru_effect *new_effect, *entry; struct list_head *ptr; switch (cmd) { case EVIOCGVERSION: return put_user(PPP_VERSION, (int *) arg); case CHECKFUNC: return put_user(FF_RUMBLE | FF_CONSTANT, (int *) arg); case EVIOCGEFFECTS: return put_user(1, (int *) arg); /* Save a FF effect */ case EVIOCSFF: /* * Currently * only supports: * length * */ ff_upload = kmalloc(sizeof(struct ff_effect), GFP_KERNEL); if (ff_upload == NULL) return -ENOMEM; copy_from_user(ff_upload, (void *) arg, sizeof(struct ff_effect)); /* Now get the length */ ff_time = ff_upload->replay; new_effect = kmalloc(sizeof(struct dc_puru_effect), GFP_KERNEL); if (new_effect == NULL) { kfree(ff_upload); return -ENOMEM; } new_effect->length_of_time = ff_time.length; new_effect->sid = effect_number++; list_add_tail(&new_effect->list, &effects_list); /* report the effect number back to the caller */ ff_upload->id = new_effect->sid; copy_to_user(arg, ff_upload, sizeof(struct ff_effect)); kfree(ff_upload); return 0; case EVIOCRMFF: if (list_empty(&effects_list)) return -EINVAL; /* walk through the list */ u16 find_id = (u16) arg; for (ptr = effects_list.next; ptr != &effects_list; ptr = ptr->next) { entry = list_entry(ptr, struct dc_puru_effect, list); if (entry->sid == find_id) { list_del(&entry->list); kfree(entry); return 0; } } return -EINVAL; default: return -1; } } static int puru_open(struct inode *inode, struct file *file) { file->private_data = MINOR(inode->i_rdev); return 0; } static void halt_puru(struct dc_purupuru *puru) { /* Halt the puru device immediately */ struct mapleq *halt = &(puru->data->mq); halt->command = 14; halt->length = 2; ((unsigned long *) (halt->recvbuf))[0] = cpu_to_be32(MAPLE_FUNC_PURUPURU); ((unsigned long *) (halt->recvbuf))[1] = 0x00; halt->sendbuf = halt->recvbuf; if (maple_add_packet(halt) != 0) DEBGM("Could not add packet\n"); } static void play_puru(struct dc_purupuru *puru) { /* Halt the puru device immediately */ struct mapleq *play = &(puru->data->mq); play->command = 14; play->length = 2; ((unsigned long *) (play->recvbuf))[0] = cpu_to_be32(MAPLE_FUNC_PURUPURU); ((unsigned long *) (play->recvbuf))[1] = 0x0000ffff; play->sendbuf = play->recvbuf; if (maple_add_packet(play) != 0) DEBGM("Could not add packet\n"); } /* 'Write' routine to play effects */ static ssize_t puru_play(struct file *file, const char *buffer, size_t count, loff_t * pos) { last_minor = file->private_data; /* buffer is a pointer to an input event */ struct input_event *play = kmalloc(sizeof(struct input_event), GFP_KERNEL); if (play == NULL) return -ENOMEM; memset(play, 0, sizeof(struct input_event)); copy_from_user(play, buffer, sizeof(struct input_event)); if (play->type != EV_FF) { kfree(play); return -EINVAL; } /* Which purupuru device are we playing with? */ struct dc_purupuru *select_me, *puru = NULL; struct list_head *ptr; for (ptr = devices_list.next; ptr != &devices_list; ptr = ptr->next) { select_me = list_entry(ptr, struct dc_purupuru, list); if (select_me->minor == last_minor) { puru = select_me; break; } } if (puru == NULL) { kfree(play); return -ENODEV; } /* Stop? In which case halt the Puru Puru immediately */ if (play->value == 0) { halt_puru(puru); kfree(play); return 0; } /* Walk through the list looking for the desired effect */ u16 find_id = play->code; struct dc_puru_effect *entry; for (ptr = effects_list.next; ptr != &effects_list; ptr = ptr->next) { entry = list_entry(ptr, struct dc_puru_effect, list); if (entry->sid == find_id) { play_puru(puru); /* Set up the timing measurements */ atomic_set(&puru->active, 1); puru->timeleft = entry->length_of_time; kfree(play); return 0; } } kfree(play); return -EINVAL; } void dc_purupuru_wake(struct maple_driver_data *data) { if (last_jiffies == -1) { last_jiffies = jiffies; return; } int gap; if (time_before(jiffies, last_jiffies)) { gap = ((jiffies + (0xffffffff - last_jiffies)) * 1000) / HZ; } else gap = ((jiffies - last_jiffies) * 1000) / HZ; last_jiffies = jiffies; /* which Puru Puru packs are active? */ struct dc_purupuru *select_me; struct list_head *ptr; for (ptr = devices_list.next; ptr != &devices_list; ptr = ptr->next) { select_me = list_entry(ptr, struct dc_purupuru, list); if (atomic_read(&select_me->active) == 1) { select_me->timeleft -= gap; if (select_me->timeleft <= 0) { atomic_set(&select_me->active, 0); halt_puru(select_me); } } } return; } /* * Link the maple, input and fops * functions of this driver */ static struct maple_driver dc_purupuru_driver = { function:MAPLE_FUNC_PURUPURU, name:"Puru Puru Pack", connect:dc_purupuru_connect, disconnect:dc_purupuru_disconnect, /* reply:dc_purupuru_callback, */ vblank:dc_purupuru_wake, }; static struct file_operations puru_ops = { owner:THIS_MODULE, ioctl:puru_ioctl, write:puru_play, open:puru_open, }; static struct input_handle *purupuru_connect(struct input_handler *handler, struct input_dev *dev) { struct input_handle *handle; /* Is this a Puru Puru device? */ if (dev->open != dc_puru_open) return NULL; if (!(handle = kmalloc(sizeof(struct input_handle), GFP_KERNEL))) return NULL; memset(handle, 0, sizeof(struct input_handle)); handle->dev = dev; handle->handler = handler; input_open_device(handle); printk(KERN_INFO "purupuru.c: adding Puru Puru Pack input%d\n", dev->number); handler->minor = PURU_MINOR_BASE + dev->number; return handle; } static void purupuru_disconnect(struct input_handle *handle) { input_close_device(handle); kfree(handle); } static struct input_handler purupuru_handler = { connect:purupuru_connect, disconnect:purupuru_disconnect, fops:&puru_ops, minor:PURU_MINOR_BASE, }; static int __init dc_purupuru_init(void) { maple_register_driver(&dc_purupuru_driver); printk("Puru Puru Pack driver for Dreamcast Linux version 0.1\n"); return 0; } static void __exit dc_purupuru_exit(void) { struct list_head *ptr = NULL; struct list_head *nxt = NULL; struct dc_puru_effect *effect; maple_unregister_driver(&dc_purupuru_driver); input_unregister_handler(&purupuru_handler); if (!list_empty(&effects_list)) { list_for_each_safe(ptr, nxt, &effects_list) { effect = list_entry(ptr, struct dc_puru_effect, list); if (effect) { list_del(&(effect->list)); kfree(effect); } } } } module_init(dc_purupuru_init); module_exit(dc_purupuru_exit); MODULE_AUTHOR("Adrian McMenamin <ad...@mc...>"); MODULE_DESCRIPTION("SEGA Dreamcast purupuru driver"); |