aboutsummaryrefslogtreecommitdiff
path: root/hid-tmff2.c
diff options
context:
space:
mode:
Diffstat (limited to 'hid-tmff2.c')
-rw-r--r--hid-tmff2.c577
1 files changed, 577 insertions, 0 deletions
diff --git a/hid-tmff2.c b/hid-tmff2.c
new file mode 100644
index 0000000..f1d1f84
--- /dev/null
+++ b/hid-tmff2.c
@@ -0,0 +1,577 @@
+#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/hid.h>
+#include "hid-tmff2.h"
+
+static int timer_msecs = DEFAULT_TIMER_PERIOD;
+module_param(timer_msecs, int, 0660);
+MODULE_PARM_DESC(timer_msecs,
+ "Timer resolution in msecs");
+
+/* should these be removed and just rely on /sys? */
+static int spring_level = 30;
+module_param(spring_level, int, 0);
+MODULE_PARM_DESC(spring_level,
+ "Level of spring force (0-100), as per Oversteer standards");
+
+static int damper_level = 30;
+module_param(damper_level, int, 0);
+MODULE_PARM_DESC(damper_level,
+ "Level of damper force (0-100), as per Oversteer standards");
+
+static int friction_level = 30;
+module_param(friction_level, int, 0);
+MODULE_PARM_DESC(friction_level,
+ "Level of friction force (0-100), as per Oversteer standards");
+
+static int range = 900;
+module_param(range, int, 0);
+MODULE_PARM_DESC(range,
+ "Range of wheel, depends on the wheel. Invalid values are ignored.");
+
+static int alt_mode = 0;
+module_param(alt_mode, int, 0);
+MODULE_PARM_DESC(alt_mode,
+ "Alternate mode, eg. T300RS F1 mode.");
+
+static spinlock_t lock;
+static unsigned long lock_flags;
+
+static struct tmff2_device_entry *tmff2_from_hdev(struct hid_device *hdev)
+{
+ struct tmff2_device_entry *tmff2;
+ spin_lock_irqsave(&lock, lock_flags);
+ if (!(tmff2 = hid_get_drvdata(hdev))) {
+ dev_err(&hdev->dev, "hdev private data not found\n");
+ return NULL;
+ }
+ spin_unlock_irqrestore(&lock, lock_flags);
+
+ return tmff2;
+}
+
+static struct tmff2_device_entry *tmff2_from_input(struct input_dev *input_dev)
+{
+ struct tmff2_device_entry *tmff2;
+ spin_lock_irqsave(&lock, lock_flags);
+ if (!(tmff2 = input_get_drvdata(input_dev))) {
+ dev_err(&input_dev->dev, "input_dev private data not found\n");
+ return NULL;
+ }
+ spin_unlock_irqrestore(&lock, lock_flags);
+
+ return tmff2;
+}
+
+static ssize_t spring_level_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &value);
+ if (ret) {
+ dev_err(dev, "kstrtouint failed at spring_level_store: %i", ret);
+ return ret;
+ }
+
+ if (value > 100) {
+ dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value);
+ value = 100;
+ }
+
+ spring_level = value;
+
+ return count;
+}
+
+static ssize_t spring_level_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%u\n", spring_level);
+}
+static DEVICE_ATTR_RW(spring_level);
+
+static ssize_t damper_level_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &value);
+ if (ret) {
+ dev_err(dev, "kstrtouint failed at damper_level_store: %i", ret);
+ return ret;
+ }
+
+ if (value > 100) {
+ dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value);
+ value = 100;
+ }
+
+ damper_level = value;
+
+ return count;
+}
+
+static ssize_t damper_level_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%u\n", damper_level);
+}
+static DEVICE_ATTR_RW(damper_level);
+
+static ssize_t friction_level_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &value);
+ if (ret) {
+ dev_err(dev, "kstrtouint failed at friction_level_store: %i", ret);
+ return ret;
+ }
+
+ if (value > 100) {
+ dev_info(dev, "value %i larger than max 100, clamping to 100.\n", value);
+ value = 100;
+ }
+
+ friction_level = value;
+
+ return count;
+}
+
+static ssize_t friction_level_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ size_t count;
+
+ count = scnprintf(buf, PAGE_SIZE, "%u\n", friction_level);
+
+ return count;
+}
+static DEVICE_ATTR_RW(friction_level);
+
+static ssize_t range_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev));
+ unsigned int value;
+ int ret;
+
+ if (!tmff2)
+ return -ENODEV;
+
+ if ((ret = kstrtouint(buf, 0, &value))) {
+ hid_err(tmff2->hdev, "kstrtouint failed at range_store: %i", ret);
+ return ret;
+ }
+
+ if (tmff2->set_range) {
+ if ((ret = tmff2->set_range(tmff2->data, value)))
+ return ret;
+ }
+
+ return count;
+}
+
+static ssize_t range_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%u\n", range);
+}
+static DEVICE_ATTR_RW(range);
+
+static ssize_t alt_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev));
+ unsigned int value;
+ int ret;
+
+ if (!tmff2)
+ return -ENODEV;
+
+ if ((ret = kstrtouint(buf, 0, &value))) {
+ hid_err(tmff2->hdev, "kstrtouint failed at alt_mode_store: %i", ret);
+ return ret;
+ }
+
+ if (tmff2->switch_mode) {
+ if ((ret = tmff2->switch_mode(tmff2->data, value)))
+ return ret;
+ }
+
+ return count;
+}
+
+static ssize_t alt_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ /* TODO: could be cool to add in something like a small menu that gives
+ * names and corresponding index to modes, or maybe parsing modes
+ * directly? */
+ return scnprintf(buf, PAGE_SIZE, "%i\n", alt_mode);
+}
+static DEVICE_ATTR_RW(alt_mode);
+
+/* include each wheel */
+#include "hid-tmt300rs.c"
+
+static void tmff2_set_gain(struct input_dev *dev, uint16_t gain)
+{
+ struct tmff2_device_entry *tmff2 = tmff2_from_input(dev);
+ if (!tmff2)
+ return;
+
+ if (!tmff2->set_gain) {
+ hid_err(tmff2->hdev, "missing set_gain\n");
+ return;
+ }
+
+ if (tmff2->set_gain(tmff2->data, gain))
+ hid_warn(tmff2->hdev, "unable to set gain\n");
+}
+
+static void tmff2_set_autocenter(struct input_dev *dev, uint16_t autocenter)
+{
+ struct tmff2_device_entry *tmff2 = tmff2_from_input(dev);
+ if (!tmff2)
+ return;
+
+ if (!tmff2->set_autocenter) {
+ hid_err(tmff2->hdev, "missing set_autocenter\n");
+ return;
+ }
+
+ if (tmff2->set_autocenter(tmff2->data, autocenter))
+ hid_warn(tmff2->hdev, "unable to set autocenter\n");
+}
+
+static void tmff2_work_handler(struct work_struct *w)
+{
+ struct delayed_work *dw = container_of(w, struct delayed_work, work);
+ struct tmff2_device_entry *tmff2 = container_of(dw, struct tmff2_device_entry, work);
+ struct tmff2_effect_state *state;
+ int max_count = 0, effect_id;
+ unsigned long time_now;
+ __u16 effect_length;
+
+ if (!tmff2)
+ return;
+
+ for (effect_id = 0; effect_id < tmff2->max_effects; ++effect_id) {
+ spin_lock(&tmff2->lock);
+
+ time_now = JIFFIES2MS(jiffies);
+ state = &tmff2->states[effect_id];
+
+ effect_length = state->effect.replay.length;
+ if (test_bit(FF_EFFECT_PLAYING, &state->flags) && effect_length) {
+ if ((time_now - state->start_time) >= effect_length) {
+ __clear_bit(FF_EFFECT_PLAYING, &state->flags);
+ __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags);
+
+ if (state->count)
+ state->count--;
+
+ if (state->count)
+ __set_bit(FF_EFFECT_QUEUE_START, &state->flags);
+ }
+ }
+
+ if (test_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags)) {
+ if (tmff2->upload_effect(tmff2->data, state)) {
+ hid_warn(tmff2->hdev, "failed uploading effect\n");
+ } else {
+ __clear_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags);
+ /* if we're uploading an effect, it's bound to be the up
+ * to date available */
+ __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags);
+ }
+ }
+
+ if (test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags)) {
+ if (tmff2->update_effect(tmff2->data, state))
+ hid_warn(tmff2->hdev, "failed updating effect\n");
+ else
+ __clear_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags);
+ }
+
+ if (test_bit(FF_EFFECT_QUEUE_START, &state->flags)) {
+ if (tmff2->play_effect(tmff2, state)) {
+ hid_warn(tmff2->hdev, "failed starting effect\n");
+ } else {
+ __clear_bit(FF_EFFECT_QUEUE_START, &state->flags);
+ __set_bit(FF_EFFECT_PLAYING, &state->flags);
+ }
+
+ }
+
+ if (test_bit(FF_EFFECT_QUEUE_STOP, &state->flags)) {
+ if (tmff2->stop_effect(tmff2, state)) {
+ hid_warn(tmff2->hdev, "failed stopping effect\n");
+ } else {
+ __clear_bit(FF_EFFECT_PLAYING, &state->flags);
+ __clear_bit(FF_EFFECT_QUEUE_STOP, &state->flags);
+ }
+ }
+
+ if (state->count > max_count)
+ max_count = state->count;
+
+ spin_unlock(&tmff2->lock);
+ }
+
+ if (max_count)
+ schedule_delayed_work(&tmff2->work, msecs_to_jiffies(timer_msecs));
+}
+
+static int tmff2_upload(struct input_dev *dev,
+ struct ff_effect *effect, struct ff_effect *old)
+{
+ struct tmff2_effect_state *state;
+ struct tmff2_device_entry *tmff2 = tmff2_from_input(dev);
+ if (!tmff2)
+ return -ENODEV;
+
+ if (effect->type == FF_PERIODIC && effect->u.periodic.period == 0)
+ return -EINVAL;
+
+ state = &tmff2->states[effect->id];
+
+ spin_lock(&tmff2->lock);
+
+ state->effect = *effect;
+
+ if (old) {
+ if (test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags))
+ state->old = *old;
+
+ __set_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags);
+ } else {
+ __set_bit(FF_EFFECT_QUEUE_UPLOAD, &state->flags);
+ }
+
+ spin_unlock(&tmff2->lock);
+ return 0;
+}
+
+static int tmff2_play(struct input_dev *dev, int effect_id, int value)
+{
+ struct tmff2_effect_state *state;
+ struct tmff2_device_entry *tmff2 = tmff2_from_input(dev);
+ if (!tmff2)
+ return -ENODEV;
+
+ state = &tmff2->states[effect_id];
+ if (&state->effect == 0)
+ return 0;
+
+ spin_lock(&tmff2->lock);
+ if (value > 0) {
+ state->count = value;
+ state->start_time = JIFFIES2MS(jiffies);
+ __set_bit(FF_EFFECT_QUEUE_START, &state->flags);
+ __clear_bit(FF_EFFECT_QUEUE_STOP, &state->flags);
+ } else {
+ __set_bit(FF_EFFECT_QUEUE_STOP, &state->flags);
+ __clear_bit(FF_EFFECT_QUEUE_START, &state->flags);
+ }
+
+ spin_unlock(&tmff2->lock);
+
+ if (!delayed_work_pending(&tmff2->work))
+ schedule_delayed_work(&tmff2->work, 0);
+
+ return 0;
+}
+
+static int tmff2_open(struct input_dev *dev)
+{
+ struct tmff2_device_entry *tmff2 = tmff2_from_input(dev);
+ if (!tmff2)
+ return -ENODEV;
+
+ if (tmff2->open)
+ return tmff2->open(tmff2->data);
+
+ hid_err(tmff2->hdev, "no open callback set\n");
+ return -EINVAL;
+}
+
+static void tmff2_close(struct input_dev *dev)
+{
+ struct tmff2_device_entry *tmff2 = tmff2_from_input(dev);
+ if (!tmff2)
+ return;
+
+ if (tmff2->close) {
+ tmff2->close(tmff2->data);
+ return;
+ }
+
+ hid_err(tmff2->hdev, "no close callback set\n");
+}
+
+static int tmff2_wheel_init(struct tmff2_device_entry *tmff2)
+{
+ int ret, i;
+ struct ff_device *ff;
+ spin_lock_init(&tmff2->lock);
+ INIT_DELAYED_WORK(&tmff2->work, tmff2_work_handler);
+
+ tmff2->states = kzalloc(sizeof(struct tmff2_effect_state) * tmff2->max_effects,
+ GFP_KERNEL);
+
+ if (!tmff2->states) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* is this required or should it be done in t300rs_populate_api? */
+ if ((ret = tmff2->wheel_init(tmff2->data)))
+ goto err;
+
+ /* set supported effects into input_dev->ffbit */
+ for (i = 0; tmff2->supported_effects[i] >= 0; ++i)
+ __set_bit(tmff2->supported_effects[i], tmff2->input_dev->ffbit);
+
+ /* create actual ff device*/
+ if ((ret = input_ff_create(tmff2->input_dev, tmff2->max_effects))) {
+ hid_err(tmff2->hdev, "could not create input_ff\n");
+ goto err;
+ }
+
+ /* set ff callbacks */
+ ff = tmff2->input_dev->ff;
+ ff->upload = tmff2_upload;
+ ff->playback = tmff2_play;
+
+ if (tmff2->open)
+ tmff2->input_dev->open = tmff2_open;
+
+ if (tmff2->close)
+ tmff2->input_dev->close = tmff2_close;
+
+ if (tmff2->set_gain)
+ ff->set_gain = tmff2_set_gain;
+
+ if (tmff2->set_autocenter)
+ ff->set_autocenter = tmff2_set_autocenter;
+
+ return 0;
+
+err:
+ return ret;
+}
+
+static int tmff2_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct tmff2_device_entry *tmff2 =
+ kzalloc(sizeof(struct tmff2_device_entry), GFP_KERNEL);
+
+ int ret;
+
+ if (!tmff2) {
+ ret = -ENOMEM;
+ goto oom_err;
+ }
+
+ tmff2->hdev = hdev;
+ hid_set_drvdata(tmff2->hdev, tmff2);
+ tmff2->input_dev = list_entry(hdev->inputs.next, struct hid_input, list)->input;
+ input_set_drvdata(tmff2->input_dev, tmff2);
+
+ switch (tmff2->hdev->product) {
+ /* t300rs */
+ case 0xb66e:
+ case 0xb66f:
+ case 0xb66d:
+ if ((ret = t300rs_populate_api(tmff2)))
+ goto wheel_err;
+ break;
+ default:
+ ret = -ENODEV;
+ goto wheel_err;
+ }
+
+ if ((ret = hid_parse(tmff2->hdev))) {
+ hid_err(hdev, "parse failed\n");
+ goto hid_err;
+ }
+
+ if ((ret = hid_hw_start(tmff2->hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF))) {
+ hid_err(hdev, "hw start failed\n");
+ goto hid_err;
+ }
+
+ if ((ret = tmff2_wheel_init(tmff2))) {
+ hid_err(hdev, "init failed\n");
+ goto init_err;
+ }
+
+ return 0;
+
+init_err:
+ hid_hw_stop(hdev);
+
+hid_err:
+ tmff2->wheel_destroy(tmff2->data);
+
+wheel_err:
+ kfree(tmff2);
+
+oom_err:
+ return ret;
+}
+
+static __u8 *tmff2_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev);
+ if (!tmff2) /* not entirely sure what the best course of action would be here */
+ return rdesc;
+
+ if (tmff2->wheel_fixup)
+ return tmff2->wheel_fixup(hdev, rdesc, rsize);
+
+ return rdesc;
+}
+
+static void tmff2_remove(struct hid_device *hdev)
+{
+ struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev);
+ if (!tmff2)
+ return;
+
+ cancel_delayed_work_sync(&tmff2->work);
+ tmff2->wheel_destroy(tmff2->data);
+
+ hid_hw_stop(hdev);
+
+ kfree(tmff2->states);
+ kfree(tmff2);
+}
+
+static const struct hid_device_id tmff2_devices[] = {
+ /* t300rs and variations */
+ {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb66e)},
+ {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb66f)},
+ {HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb66d)},
+ {}
+};
+MODULE_DEVICE_TABLE(hid, tmff2_devices);
+
+static struct hid_driver tmff2_driver = {
+ .name = "tmff2",
+ .id_table = tmff2_devices,
+ .probe = tmff2_probe,
+ .remove = tmff2_remove,
+ .report_fixup = tmff2_report_fixup,
+};
+module_hid_driver(tmff2_driver);
+
+MODULE_LICENSE("GPL");