diff options
| author | Kimplul <kimi.h.kuparinen@gmail.com> | 2022-03-12 18:00:40 +0200 |
|---|---|---|
| committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2022-03-12 18:10:57 +0200 |
| commit | 60e90e0a96039c58968de551aadca0fab490d35d (patch) | |
| tree | 3812c42d3daadf964cc18ef06eaa7456b9abe84f /hid-tmff2.c | |
| parent | dc3f9610f24fe43540541f2ab1a990903be20ed3 (diff) | |
| download | hid-tmff2-60e90e0a96039c58968de551aadca0fab490d35d.tar.gz hid-tmff2-60e90e0a96039c58968de551aadca0fab490d35d.zip | |
allow for wheel 'backends'
Diffstat (limited to 'hid-tmff2.c')
| -rw-r--r-- | hid-tmff2.c | 577 |
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"); |
