diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/input/joystick/iforce | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/input/joystick/iforce')
-rw-r--r-- | drivers/input/joystick/iforce/Kconfig | 33 | ||||
-rw-r--r-- | drivers/input/joystick/iforce/Makefile | 11 | ||||
-rw-r--r-- | drivers/input/joystick/iforce/iforce-ff.c | 524 | ||||
-rw-r--r-- | drivers/input/joystick/iforce/iforce-main.c | 399 | ||||
-rw-r--r-- | drivers/input/joystick/iforce/iforce-packets.c | 211 | ||||
-rw-r--r-- | drivers/input/joystick/iforce/iforce-serio.c | 257 | ||||
-rw-r--r-- | drivers/input/joystick/iforce/iforce-usb.c | 299 | ||||
-rw-r--r-- | drivers/input/joystick/iforce/iforce.h | 147 |
8 files changed, 1881 insertions, 0 deletions
diff --git a/drivers/input/joystick/iforce/Kconfig b/drivers/input/joystick/iforce/Kconfig new file mode 100644 index 000000000..f002fb88f --- /dev/null +++ b/drivers/input/joystick/iforce/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# I-Force driver configuration +# +config JOYSTICK_IFORCE + tristate "I-Force devices" + depends on INPUT && INPUT_JOYSTICK + help + Say Y here if you have an I-Force joystick or steering wheel + + You also must choose at least one of the two options below. + + To compile this driver as a module, choose M here: the + module will be called iforce. + +config JOYSTICK_IFORCE_USB + tristate "I-Force USB joysticks and wheels" + depends on JOYSTICK_IFORCE && USB + help + Say Y here if you have an I-Force joystick or steering wheel + connected to your USB port. + +config JOYSTICK_IFORCE_232 + tristate "I-Force Serial joysticks and wheels" + depends on JOYSTICK_IFORCE && SERIO + help + Say Y here if you have an I-Force joystick or steering wheel + connected to your serial (COM) port. + + You will need an additional utility called inputattach, see + <file:Documentation/input/joydev/joystick.rst> + and <file:Documentation/input/ff.rst>. + diff --git a/drivers/input/joystick/iforce/Makefile b/drivers/input/joystick/iforce/Makefile new file mode 100644 index 000000000..dbbe7c040 --- /dev/null +++ b/drivers/input/joystick/iforce/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the I-Force driver +# +# By Johann Deneux <johann.deneux@gmail.com> +# + +obj-$(CONFIG_JOYSTICK_IFORCE) += iforce.o +iforce-y := iforce-ff.o iforce-main.o iforce-packets.o +obj-$(CONFIG_JOYSTICK_IFORCE_232) += iforce-serio.o +obj-$(CONFIG_JOYSTICK_IFORCE_USB) += iforce-usb.o diff --git a/drivers/input/joystick/iforce/iforce-ff.c b/drivers/input/joystick/iforce/iforce-ff.c new file mode 100644 index 000000000..95c034884 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-ff.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +#include "iforce.h" + +/* + * Set the magnitude of a constant force effect + * Return error code + * + * Note: caller must ensure exclusive access to device + */ + +static int make_magnitude_modifier(struct iforce* iforce, + struct resource* mod_chunk, int no_alloc, __s16 level) +{ + unsigned char data[3]; + + if (!no_alloc) { + mutex_lock(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 2, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + mutex_unlock(&iforce->mem_mutex); + return -ENOSPC; + } + mutex_unlock(&iforce->mem_mutex); + } + + data[0] = LO(mod_chunk->start); + data[1] = HI(mod_chunk->start); + data[2] = HIFIX80(level); + + iforce_send_packet(iforce, FF_CMD_MAGNITUDE, data); + + iforce_dump_packet(iforce, "magnitude", FF_CMD_MAGNITUDE, data); + return 0; +} + +/* + * Upload the component of an effect dealing with the period, phase and magnitude + */ + +static int make_period_modifier(struct iforce* iforce, + struct resource* mod_chunk, int no_alloc, + __s16 magnitude, __s16 offset, u16 period, u16 phase) +{ + unsigned char data[7]; + + period = TIME_SCALE(period); + + if (!no_alloc) { + mutex_lock(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 0x0c, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + mutex_unlock(&iforce->mem_mutex); + return -ENOSPC; + } + mutex_unlock(&iforce->mem_mutex); + } + + data[0] = LO(mod_chunk->start); + data[1] = HI(mod_chunk->start); + + data[2] = HIFIX80(magnitude); + data[3] = HIFIX80(offset); + data[4] = HI(phase); + + data[5] = LO(period); + data[6] = HI(period); + + iforce_send_packet(iforce, FF_CMD_PERIOD, data); + + return 0; +} + +/* + * Uploads the part of an effect setting the envelope of the force + */ + +static int make_envelope_modifier(struct iforce* iforce, + struct resource* mod_chunk, int no_alloc, + u16 attack_duration, __s16 initial_level, + u16 fade_duration, __s16 final_level) +{ + unsigned char data[8]; + + attack_duration = TIME_SCALE(attack_duration); + fade_duration = TIME_SCALE(fade_duration); + + if (!no_alloc) { + mutex_lock(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 0x0e, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + mutex_unlock(&iforce->mem_mutex); + return -ENOSPC; + } + mutex_unlock(&iforce->mem_mutex); + } + + data[0] = LO(mod_chunk->start); + data[1] = HI(mod_chunk->start); + + data[2] = LO(attack_duration); + data[3] = HI(attack_duration); + data[4] = HI(initial_level); + + data[5] = LO(fade_duration); + data[6] = HI(fade_duration); + data[7] = HI(final_level); + + iforce_send_packet(iforce, FF_CMD_ENVELOPE, data); + + return 0; +} + +/* + * Component of spring, friction, inertia... effects + */ + +static int make_condition_modifier(struct iforce* iforce, + struct resource* mod_chunk, int no_alloc, + __u16 rsat, __u16 lsat, __s16 rk, __s16 lk, u16 db, __s16 center) +{ + unsigned char data[10]; + + if (!no_alloc) { + mutex_lock(&iforce->mem_mutex); + if (allocate_resource(&(iforce->device_memory), mod_chunk, 8, + iforce->device_memory.start, iforce->device_memory.end, 2L, + NULL, NULL)) { + mutex_unlock(&iforce->mem_mutex); + return -ENOSPC; + } + mutex_unlock(&iforce->mem_mutex); + } + + data[0] = LO(mod_chunk->start); + data[1] = HI(mod_chunk->start); + + data[2] = (100 * rk) >> 15; /* Dangerous: the sign is extended by gcc on plateforms providing an arith shift */ + data[3] = (100 * lk) >> 15; /* This code is incorrect on cpus lacking arith shift */ + + center = (500 * center) >> 15; + data[4] = LO(center); + data[5] = HI(center); + + db = (1000 * db) >> 16; + data[6] = LO(db); + data[7] = HI(db); + + data[8] = (100 * rsat) >> 16; + data[9] = (100 * lsat) >> 16; + + iforce_send_packet(iforce, FF_CMD_CONDITION, data); + iforce_dump_packet(iforce, "condition", FF_CMD_CONDITION, data); + + return 0; +} + +static unsigned char find_button(struct iforce *iforce, signed short button) +{ + int i; + + for (i = 1; iforce->type->btn[i] >= 0; i++) + if (iforce->type->btn[i] == button) + return i + 1; + return 0; +} + +/* + * Analyse the changes in an effect, and tell if we need to send an condition + * parameter packet + */ +static int need_condition_modifier(struct iforce *iforce, + struct ff_effect *old, + struct ff_effect *new) +{ + int ret = 0; + int i; + + if (new->type != FF_SPRING && new->type != FF_FRICTION) { + dev_warn(&iforce->dev->dev, "bad effect type in %s\n", + __func__); + return 0; + } + + for (i = 0; i < 2; i++) { + ret |= old->u.condition[i].right_saturation != new->u.condition[i].right_saturation + || old->u.condition[i].left_saturation != new->u.condition[i].left_saturation + || old->u.condition[i].right_coeff != new->u.condition[i].right_coeff + || old->u.condition[i].left_coeff != new->u.condition[i].left_coeff + || old->u.condition[i].deadband != new->u.condition[i].deadband + || old->u.condition[i].center != new->u.condition[i].center; + } + return ret; +} + +/* + * Analyse the changes in an effect, and tell if we need to send a magnitude + * parameter packet + */ +static int need_magnitude_modifier(struct iforce *iforce, + struct ff_effect *old, + struct ff_effect *effect) +{ + if (effect->type != FF_CONSTANT) { + dev_warn(&iforce->dev->dev, "bad effect type in %s\n", + __func__); + return 0; + } + + return old->u.constant.level != effect->u.constant.level; +} + +/* + * Analyse the changes in an effect, and tell if we need to send an envelope + * parameter packet + */ +static int need_envelope_modifier(struct iforce *iforce, struct ff_effect *old, + struct ff_effect *effect) +{ + switch (effect->type) { + case FF_CONSTANT: + if (old->u.constant.envelope.attack_length != effect->u.constant.envelope.attack_length + || old->u.constant.envelope.attack_level != effect->u.constant.envelope.attack_level + || old->u.constant.envelope.fade_length != effect->u.constant.envelope.fade_length + || old->u.constant.envelope.fade_level != effect->u.constant.envelope.fade_level) + return 1; + break; + + case FF_PERIODIC: + if (old->u.periodic.envelope.attack_length != effect->u.periodic.envelope.attack_length + || old->u.periodic.envelope.attack_level != effect->u.periodic.envelope.attack_level + || old->u.periodic.envelope.fade_length != effect->u.periodic.envelope.fade_length + || old->u.periodic.envelope.fade_level != effect->u.periodic.envelope.fade_level) + return 1; + break; + + default: + dev_warn(&iforce->dev->dev, "bad effect type in %s\n", + __func__); + } + + return 0; +} + +/* + * Analyse the changes in an effect, and tell if we need to send a periodic + * parameter effect + */ +static int need_period_modifier(struct iforce *iforce, struct ff_effect *old, + struct ff_effect *new) +{ + if (new->type != FF_PERIODIC) { + dev_warn(&iforce->dev->dev, "bad effect type in %s\n", + __func__); + return 0; + } + return (old->u.periodic.period != new->u.periodic.period + || old->u.periodic.magnitude != new->u.periodic.magnitude + || old->u.periodic.offset != new->u.periodic.offset + || old->u.periodic.phase != new->u.periodic.phase); +} + +/* + * Analyse the changes in an effect, and tell if we need to send an effect + * packet + */ +static int need_core(struct ff_effect *old, struct ff_effect *new) +{ + if (old->direction != new->direction + || old->trigger.button != new->trigger.button + || old->trigger.interval != new->trigger.interval + || old->replay.length != new->replay.length + || old->replay.delay != new->replay.delay) + return 1; + + return 0; +} +/* + * Send the part common to all effects to the device + */ +static int make_core(struct iforce* iforce, u16 id, u16 mod_id1, u16 mod_id2, + u8 effect_type, u8 axes, u16 duration, u16 delay, u16 button, + u16 interval, u16 direction) +{ + unsigned char data[14]; + + duration = TIME_SCALE(duration); + delay = TIME_SCALE(delay); + interval = TIME_SCALE(interval); + + data[0] = LO(id); + data[1] = effect_type; + data[2] = LO(axes) | find_button(iforce, button); + + data[3] = LO(duration); + data[4] = HI(duration); + + data[5] = HI(direction); + + data[6] = LO(interval); + data[7] = HI(interval); + + data[8] = LO(mod_id1); + data[9] = HI(mod_id1); + data[10] = LO(mod_id2); + data[11] = HI(mod_id2); + + data[12] = LO(delay); + data[13] = HI(delay); + + /* Stop effect */ +/* iforce_control_playback(iforce, id, 0);*/ + + iforce_send_packet(iforce, FF_CMD_EFFECT, data); + + /* If needed, restart effect */ + if (test_bit(FF_CORE_SHOULD_PLAY, iforce->core_effects[id].flags)) { + /* BUG: perhaps we should replay n times, instead of 1. But we do not know n */ + iforce_control_playback(iforce, id, 1); + } + + return 0; +} + +/* + * Upload a periodic effect to the device + * See also iforce_upload_constant. + */ +int iforce_upload_periodic(struct iforce *iforce, struct ff_effect *effect, struct ff_effect *old) +{ + u8 wave_code; + int core_id = effect->id; + struct iforce_core_effect* core_effect = iforce->core_effects + core_id; + struct resource* mod1_chunk = &(iforce->core_effects[core_id].mod1_chunk); + struct resource* mod2_chunk = &(iforce->core_effects[core_id].mod2_chunk); + int param1_err = 1; + int param2_err = 1; + int core_err = 0; + + if (!old || need_period_modifier(iforce, old, effect)) { + param1_err = make_period_modifier(iforce, mod1_chunk, + old != NULL, + effect->u.periodic.magnitude, effect->u.periodic.offset, + effect->u.periodic.period, effect->u.periodic.phase); + if (param1_err) + return param1_err; + set_bit(FF_MOD1_IS_USED, core_effect->flags); + } + + if (!old || need_envelope_modifier(iforce, old, effect)) { + param2_err = make_envelope_modifier(iforce, mod2_chunk, + old !=NULL, + effect->u.periodic.envelope.attack_length, + effect->u.periodic.envelope.attack_level, + effect->u.periodic.envelope.fade_length, + effect->u.periodic.envelope.fade_level); + if (param2_err) + return param2_err; + set_bit(FF_MOD2_IS_USED, core_effect->flags); + } + + switch (effect->u.periodic.waveform) { + case FF_SQUARE: wave_code = 0x20; break; + case FF_TRIANGLE: wave_code = 0x21; break; + case FF_SINE: wave_code = 0x22; break; + case FF_SAW_UP: wave_code = 0x23; break; + case FF_SAW_DOWN: wave_code = 0x24; break; + default: wave_code = 0x20; break; + } + + if (!old || need_core(old, effect)) { + core_err = make_core(iforce, effect->id, + mod1_chunk->start, + mod2_chunk->start, + wave_code, + 0x20, + effect->replay.length, + effect->replay.delay, + effect->trigger.button, + effect->trigger.interval, + effect->direction); + } + + /* If one of the parameter creation failed, we already returned an + * error code. + * If the core creation failed, we return its error code. + * Else: if one parameter at least was created, we return 0 + * else we return 1; + */ + return core_err < 0 ? core_err : (param1_err && param2_err); +} + +/* + * Upload a constant force effect + * Return value: + * <0 Error code + * 0 Ok, effect created or updated + * 1 effect did not change since last upload, and no packet was therefore sent + */ +int iforce_upload_constant(struct iforce *iforce, struct ff_effect *effect, struct ff_effect *old) +{ + int core_id = effect->id; + struct iforce_core_effect* core_effect = iforce->core_effects + core_id; + struct resource* mod1_chunk = &(iforce->core_effects[core_id].mod1_chunk); + struct resource* mod2_chunk = &(iforce->core_effects[core_id].mod2_chunk); + int param1_err = 1; + int param2_err = 1; + int core_err = 0; + + if (!old || need_magnitude_modifier(iforce, old, effect)) { + param1_err = make_magnitude_modifier(iforce, mod1_chunk, + old != NULL, + effect->u.constant.level); + if (param1_err) + return param1_err; + set_bit(FF_MOD1_IS_USED, core_effect->flags); + } + + if (!old || need_envelope_modifier(iforce, old, effect)) { + param2_err = make_envelope_modifier(iforce, mod2_chunk, + old != NULL, + effect->u.constant.envelope.attack_length, + effect->u.constant.envelope.attack_level, + effect->u.constant.envelope.fade_length, + effect->u.constant.envelope.fade_level); + if (param2_err) + return param2_err; + set_bit(FF_MOD2_IS_USED, core_effect->flags); + } + + if (!old || need_core(old, effect)) { + core_err = make_core(iforce, effect->id, + mod1_chunk->start, + mod2_chunk->start, + 0x00, + 0x20, + effect->replay.length, + effect->replay.delay, + effect->trigger.button, + effect->trigger.interval, + effect->direction); + } + + /* If one of the parameter creation failed, we already returned an + * error code. + * If the core creation failed, we return its error code. + * Else: if one parameter at least was created, we return 0 + * else we return 1; + */ + return core_err < 0 ? core_err : (param1_err && param2_err); +} + +/* + * Upload an condition effect. Those are for example friction, inertia, springs... + */ +int iforce_upload_condition(struct iforce *iforce, struct ff_effect *effect, struct ff_effect *old) +{ + int core_id = effect->id; + struct iforce_core_effect* core_effect = iforce->core_effects + core_id; + struct resource* mod1_chunk = &(core_effect->mod1_chunk); + struct resource* mod2_chunk = &(core_effect->mod2_chunk); + u8 type; + int param_err = 1; + int core_err = 0; + + switch (effect->type) { + case FF_SPRING: type = 0x40; break; + case FF_DAMPER: type = 0x41; break; + default: return -1; + } + + if (!old || need_condition_modifier(iforce, old, effect)) { + param_err = make_condition_modifier(iforce, mod1_chunk, + old != NULL, + effect->u.condition[0].right_saturation, + effect->u.condition[0].left_saturation, + effect->u.condition[0].right_coeff, + effect->u.condition[0].left_coeff, + effect->u.condition[0].deadband, + effect->u.condition[0].center); + if (param_err) + return param_err; + set_bit(FF_MOD1_IS_USED, core_effect->flags); + + param_err = make_condition_modifier(iforce, mod2_chunk, + old != NULL, + effect->u.condition[1].right_saturation, + effect->u.condition[1].left_saturation, + effect->u.condition[1].right_coeff, + effect->u.condition[1].left_coeff, + effect->u.condition[1].deadband, + effect->u.condition[1].center); + if (param_err) + return param_err; + set_bit(FF_MOD2_IS_USED, core_effect->flags); + + } + + if (!old || need_core(old, effect)) { + core_err = make_core(iforce, effect->id, + mod1_chunk->start, mod2_chunk->start, + type, 0xc0, + effect->replay.length, effect->replay.delay, + effect->trigger.button, effect->trigger.interval, + effect->direction); + } + + /* If the parameter creation failed, we already returned an + * error code. + * If the core creation failed, we return its error code. + * Else: if a parameter was created, we return 0 + * else we return 1; + */ + return core_err < 0 ? core_err : param_err; +} diff --git a/drivers/input/joystick/iforce/iforce-main.c b/drivers/input/joystick/iforce/iforce-main.c new file mode 100644 index 000000000..84b87526b --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-main.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +#include <asm/unaligned.h> +#include "iforce.h" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>, Johann Deneux <johann.deneux@gmail.com>"); +MODULE_DESCRIPTION("Core I-Force joysticks and wheels driver"); +MODULE_LICENSE("GPL"); + +static signed short btn_joystick[] = +{ BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_TOP2, BTN_BASE, + BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_BASE5, BTN_A, + BTN_B, BTN_C, BTN_DEAD, -1 }; + +static signed short btn_joystick_avb[] = +{ BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, + BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_DEAD, -1 }; + +static signed short btn_wheel[] = +{ BTN_GEAR_DOWN, BTN_GEAR_UP, BTN_BASE, BTN_BASE2, BTN_BASE3, + BTN_BASE4, BTN_BASE5, BTN_BASE6, -1 }; + +static signed short abs_joystick[] = +{ ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, -1 }; + +static signed short abs_joystick_rudder[] = +{ ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER, ABS_HAT0X, ABS_HAT0Y, -1 }; + +static signed short abs_avb_pegasus[] = +{ ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER, ABS_HAT0X, ABS_HAT0Y, + ABS_HAT1X, ABS_HAT1Y, -1 }; + +static signed short abs_wheel[] = +{ ABS_WHEEL, ABS_GAS, ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y, -1 }; + +static signed short ff_iforce[] = +{ FF_PERIODIC, FF_CONSTANT, FF_SPRING, FF_DAMPER, + FF_SQUARE, FF_TRIANGLE, FF_SINE, FF_SAW_UP, FF_SAW_DOWN, FF_GAIN, + FF_AUTOCENTER, -1 }; + +static struct iforce_device iforce_device[] = { + { 0x044f, 0xa01c, "Thrustmaster Motor Sport GT", btn_wheel, abs_wheel, ff_iforce }, + { 0x046d, 0xc281, "Logitech WingMan Force", btn_joystick, abs_joystick, ff_iforce }, + { 0x046d, 0xc291, "Logitech WingMan Formula Force", btn_wheel, abs_wheel, ff_iforce }, + { 0x05ef, 0x020a, "AVB Top Shot Pegasus", btn_joystick_avb, abs_avb_pegasus, ff_iforce }, + { 0x05ef, 0x8884, "AVB Mag Turbo Force", btn_wheel, abs_wheel, ff_iforce }, + { 0x05ef, 0x8886, "Boeder Force Feedback Wheel", btn_wheel, abs_wheel, ff_iforce }, + { 0x05ef, 0x8888, "AVB Top Shot Force Feedback Racing Wheel", btn_wheel, abs_wheel, ff_iforce }, //? + { 0x061c, 0xc0a4, "ACT LABS Force RS", btn_wheel, abs_wheel, ff_iforce }, //? + { 0x061c, 0xc084, "ACT LABS Force RS", btn_wheel, abs_wheel, ff_iforce }, + { 0x06a3, 0xff04, "Saitek R440 Force Wheel", btn_wheel, abs_wheel, ff_iforce }, //? + { 0x06f8, 0x0001, "Guillemot Race Leader Force Feedback", btn_wheel, abs_wheel, ff_iforce }, //? + { 0x06f8, 0x0001, "Guillemot Jet Leader Force Feedback", btn_joystick, abs_joystick_rudder, ff_iforce }, + { 0x06f8, 0x0004, "Guillemot Force Feedback Racing Wheel", btn_wheel, abs_wheel, ff_iforce }, //? + { 0x06f8, 0xa302, "Guillemot Jet Leader 3D", btn_joystick, abs_joystick, ff_iforce }, //? + { 0x06d6, 0x29bc, "Trust Force Feedback Race Master", btn_wheel, abs_wheel, ff_iforce }, + { 0x0000, 0x0000, "Unknown I-Force Device [%04x:%04x]", btn_joystick, abs_joystick, ff_iforce } +}; + +static int iforce_playback(struct input_dev *dev, int effect_id, int value) +{ + struct iforce *iforce = input_get_drvdata(dev); + struct iforce_core_effect *core_effect = &iforce->core_effects[effect_id]; + + if (value > 0) + set_bit(FF_CORE_SHOULD_PLAY, core_effect->flags); + else + clear_bit(FF_CORE_SHOULD_PLAY, core_effect->flags); + + iforce_control_playback(iforce, effect_id, value); + return 0; +} + +static void iforce_set_gain(struct input_dev *dev, u16 gain) +{ + struct iforce *iforce = input_get_drvdata(dev); + unsigned char data[3]; + + data[0] = gain >> 9; + iforce_send_packet(iforce, FF_CMD_GAIN, data); +} + +static void iforce_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + struct iforce *iforce = input_get_drvdata(dev); + unsigned char data[3]; + + data[0] = 0x03; + data[1] = magnitude >> 9; + iforce_send_packet(iforce, FF_CMD_AUTOCENTER, data); + + data[0] = 0x04; + data[1] = 0x01; + iforce_send_packet(iforce, FF_CMD_AUTOCENTER, data); +} + +/* + * Function called when an ioctl is performed on the event dev entry. + * It uploads an effect to the device + */ +static int iforce_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) +{ + struct iforce *iforce = input_get_drvdata(dev); + struct iforce_core_effect *core_effect = &iforce->core_effects[effect->id]; + int ret; + + if (__test_and_set_bit(FF_CORE_IS_USED, core_effect->flags)) { + /* Check the effect is not already being updated */ + if (test_bit(FF_CORE_UPDATE, core_effect->flags)) + return -EAGAIN; + } + +/* + * Upload the effect + */ + switch (effect->type) { + case FF_PERIODIC: + ret = iforce_upload_periodic(iforce, effect, old); + break; + + case FF_CONSTANT: + ret = iforce_upload_constant(iforce, effect, old); + break; + + case FF_SPRING: + case FF_DAMPER: + ret = iforce_upload_condition(iforce, effect, old); + break; + + default: + return -EINVAL; + } + + if (ret == 0) { + /* A packet was sent, forbid new updates until we are notified + * that the packet was updated + */ + set_bit(FF_CORE_UPDATE, core_effect->flags); + } + return ret; +} + +/* + * Erases an effect: it frees the effect id and mark as unused the memory + * allocated for the parameters + */ +static int iforce_erase_effect(struct input_dev *dev, int effect_id) +{ + struct iforce *iforce = input_get_drvdata(dev); + struct iforce_core_effect *core_effect = &iforce->core_effects[effect_id]; + int err = 0; + + if (test_bit(FF_MOD1_IS_USED, core_effect->flags)) + err = release_resource(&core_effect->mod1_chunk); + + if (!err && test_bit(FF_MOD2_IS_USED, core_effect->flags)) + err = release_resource(&core_effect->mod2_chunk); + + /* TODO: remember to change that if more FF_MOD* bits are added */ + core_effect->flags[0] = 0; + + return err; +} + +static int iforce_open(struct input_dev *dev) +{ + struct iforce *iforce = input_get_drvdata(dev); + + iforce->xport_ops->start_io(iforce); + + if (test_bit(EV_FF, dev->evbit)) { + /* Enable force feedback */ + iforce_send_packet(iforce, FF_CMD_ENABLE, "\004"); + } + + return 0; +} + +static void iforce_close(struct input_dev *dev) +{ + struct iforce *iforce = input_get_drvdata(dev); + int i; + + if (test_bit(EV_FF, dev->evbit)) { + /* Check: no effects should be present in memory */ + for (i = 0; i < dev->ff->max_effects; i++) { + if (test_bit(FF_CORE_IS_USED, iforce->core_effects[i].flags)) { + dev_warn(&dev->dev, + "%s: Device still owns effects\n", + __func__); + break; + } + } + + /* Disable force feedback playback */ + iforce_send_packet(iforce, FF_CMD_ENABLE, "\001"); + /* Wait for the command to complete */ + wait_event_interruptible(iforce->wait, + !test_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags)); + } + + iforce->xport_ops->stop_io(iforce); +} + +int iforce_init_device(struct device *parent, u16 bustype, + struct iforce *iforce) +{ + struct input_dev *input_dev; + struct ff_device *ff; + u8 c[] = "CEOV"; + u8 buf[IFORCE_MAX_LENGTH]; + size_t len; + int i, error; + int ff_effects = 0; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + init_waitqueue_head(&iforce->wait); + spin_lock_init(&iforce->xmit_lock); + mutex_init(&iforce->mem_mutex); + iforce->xmit.buf = iforce->xmit_data; + iforce->dev = input_dev; + +/* + * Input device fields. + */ + + input_dev->id.bustype = bustype; + input_dev->dev.parent = parent; + + input_set_drvdata(input_dev, iforce); + + input_dev->name = "Unknown I-Force device"; + input_dev->open = iforce_open; + input_dev->close = iforce_close; + +/* + * On-device memory allocation. + */ + + iforce->device_memory.name = "I-Force device effect memory"; + iforce->device_memory.start = 0; + iforce->device_memory.end = 200; + iforce->device_memory.flags = IORESOURCE_MEM; + iforce->device_memory.parent = NULL; + iforce->device_memory.child = NULL; + iforce->device_memory.sibling = NULL; + +/* + * Wait until device ready - until it sends its first response. + */ + + for (i = 0; i < 20; i++) + if (!iforce_get_id_packet(iforce, 'O', buf, &len)) + break; + + if (i == 20) { /* 5 seconds */ + dev_err(&input_dev->dev, + "Timeout waiting for response from device.\n"); + error = -ENODEV; + goto fail; + } + +/* + * Get device info. + */ + + if (!iforce_get_id_packet(iforce, 'M', buf, &len) && len >= 3) + input_dev->id.vendor = get_unaligned_le16(buf + 1); + else + dev_warn(&iforce->dev->dev, "Device does not respond to id packet M\n"); + + if (!iforce_get_id_packet(iforce, 'P', buf, &len) && len >= 3) + input_dev->id.product = get_unaligned_le16(buf + 1); + else + dev_warn(&iforce->dev->dev, "Device does not respond to id packet P\n"); + + if (!iforce_get_id_packet(iforce, 'B', buf, &len) && len >= 3) + iforce->device_memory.end = get_unaligned_le16(buf + 1); + else + dev_warn(&iforce->dev->dev, "Device does not respond to id packet B\n"); + + if (!iforce_get_id_packet(iforce, 'N', buf, &len) && len >= 2) + ff_effects = buf[1]; + else + dev_warn(&iforce->dev->dev, "Device does not respond to id packet N\n"); + + /* Check if the device can store more effects than the driver can really handle */ + if (ff_effects > IFORCE_EFFECTS_MAX) { + dev_warn(&iforce->dev->dev, "Limiting number of effects to %d (device reports %d)\n", + IFORCE_EFFECTS_MAX, ff_effects); + ff_effects = IFORCE_EFFECTS_MAX; + } + +/* + * Display additional info. + */ + + for (i = 0; c[i]; i++) + if (!iforce_get_id_packet(iforce, c[i], buf, &len)) + iforce_dump_packet(iforce, "info", + (FF_CMD_QUERY & 0xff00) | len, buf); + +/* + * Disable spring, enable force feedback. + */ + iforce_set_autocenter(input_dev, 0); + +/* + * Find appropriate device entry + */ + + for (i = 0; iforce_device[i].idvendor; i++) + if (iforce_device[i].idvendor == input_dev->id.vendor && + iforce_device[i].idproduct == input_dev->id.product) + break; + + iforce->type = iforce_device + i; + input_dev->name = iforce->type->name; + +/* + * Set input device bitfields and ranges. + */ + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) | + BIT_MASK(EV_FF_STATUS); + + for (i = 0; iforce->type->btn[i] >= 0; i++) + set_bit(iforce->type->btn[i], input_dev->keybit); + + for (i = 0; iforce->type->abs[i] >= 0; i++) { + + signed short t = iforce->type->abs[i]; + + switch (t) { + case ABS_X: + case ABS_Y: + case ABS_WHEEL: + input_set_abs_params(input_dev, t, -1920, 1920, 16, 128); + set_bit(t, input_dev->ffbit); + break; + + case ABS_THROTTLE: + case ABS_GAS: + case ABS_BRAKE: + input_set_abs_params(input_dev, t, 0, 255, 0, 0); + break; + + case ABS_RUDDER: + input_set_abs_params(input_dev, t, -128, 127, 0, 0); + break; + + case ABS_HAT0X: + case ABS_HAT0Y: + case ABS_HAT1X: + case ABS_HAT1Y: + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + break; + } + } + + if (ff_effects) { + + for (i = 0; iforce->type->ff[i] >= 0; i++) + set_bit(iforce->type->ff[i], input_dev->ffbit); + + error = input_ff_create(input_dev, ff_effects); + if (error) + goto fail; + + ff = input_dev->ff; + ff->upload = iforce_upload_effect; + ff->erase = iforce_erase_effect; + ff->set_gain = iforce_set_gain; + ff->set_autocenter = iforce_set_autocenter; + ff->playback = iforce_playback; + } +/* + * Register input device. + */ + + error = input_register_device(iforce->dev); + if (error) + goto fail; + + return 0; + + fail: input_free_device(input_dev); + return error; +} +EXPORT_SYMBOL(iforce_init_device); diff --git a/drivers/input/joystick/iforce/iforce-packets.c b/drivers/input/joystick/iforce/iforce-packets.c new file mode 100644 index 000000000..763642c8c --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-packets.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +#include <asm/unaligned.h> +#include "iforce.h" + +static struct { + __s32 x; + __s32 y; +} iforce_hat_to_axis[16] = {{ 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + + +void iforce_dump_packet(struct iforce *iforce, char *msg, u16 cmd, unsigned char *data) +{ + dev_dbg(iforce->dev->dev.parent, "%s %s cmd = %04x, data = %*ph\n", + __func__, msg, cmd, LO(cmd), data); +} + +/* + * Send a packet of bytes to the device + */ +int iforce_send_packet(struct iforce *iforce, u16 cmd, unsigned char* data) +{ + /* Copy data to buffer */ + int n = LO(cmd); + int c; + int empty; + int head, tail; + unsigned long flags; + +/* + * Update head and tail of xmit buffer + */ + spin_lock_irqsave(&iforce->xmit_lock, flags); + + head = iforce->xmit.head; + tail = iforce->xmit.tail; + + + if (CIRC_SPACE(head, tail, XMIT_SIZE) < n+2) { + dev_warn(&iforce->dev->dev, + "not enough space in xmit buffer to send new packet\n"); + spin_unlock_irqrestore(&iforce->xmit_lock, flags); + return -1; + } + + empty = head == tail; + XMIT_INC(iforce->xmit.head, n+2); + +/* + * Store packet in xmit buffer + */ + iforce->xmit.buf[head] = HI(cmd); + XMIT_INC(head, 1); + iforce->xmit.buf[head] = LO(cmd); + XMIT_INC(head, 1); + + c = CIRC_SPACE_TO_END(head, tail, XMIT_SIZE); + if (n < c) c=n; + + memcpy(&iforce->xmit.buf[head], + data, + c); + if (n != c) { + memcpy(&iforce->xmit.buf[0], + data + c, + n - c); + } + XMIT_INC(head, n); + + spin_unlock_irqrestore(&iforce->xmit_lock, flags); +/* + * If necessary, start the transmission + */ + if (empty) + iforce->xport_ops->xmit(iforce); + + return 0; +} +EXPORT_SYMBOL(iforce_send_packet); + +/* Start or stop an effect */ +int iforce_control_playback(struct iforce* iforce, u16 id, unsigned int value) +{ + unsigned char data[3]; + + data[0] = LO(id); + data[1] = (value > 0) ? ((value > 1) ? 0x41 : 0x01) : 0; + data[2] = LO(value); + return iforce_send_packet(iforce, FF_CMD_PLAY, data); +} + +/* Mark an effect that was being updated as ready. That means it can be updated + * again */ +static int mark_core_as_ready(struct iforce *iforce, unsigned short addr) +{ + int i; + + if (!iforce->dev->ff) + return 0; + + for (i = 0; i < iforce->dev->ff->max_effects; ++i) { + if (test_bit(FF_CORE_IS_USED, iforce->core_effects[i].flags) && + (iforce->core_effects[i].mod1_chunk.start == addr || + iforce->core_effects[i].mod2_chunk.start == addr)) { + clear_bit(FF_CORE_UPDATE, iforce->core_effects[i].flags); + return 0; + } + } + dev_warn(&iforce->dev->dev, "unused effect %04x updated !!!\n", addr); + return -1; +} + +static void iforce_report_hats_buttons(struct iforce *iforce, u8 *data) +{ + struct input_dev *dev = iforce->dev; + int i; + + input_report_abs(dev, ABS_HAT0X, iforce_hat_to_axis[data[6] >> 4].x); + input_report_abs(dev, ABS_HAT0Y, iforce_hat_to_axis[data[6] >> 4].y); + + for (i = 0; iforce->type->btn[i] >= 0; i++) + input_report_key(dev, iforce->type->btn[i], + data[(i >> 3) + 5] & (1 << (i & 7))); + + /* If there are untouched bits left, interpret them as the second hat */ + if (i <= 8) { + u8 btns = data[6]; + + if (test_bit(ABS_HAT1X, dev->absbit)) { + if (btns & BIT(3)) + input_report_abs(dev, ABS_HAT1X, -1); + else if (btns & BIT(1)) + input_report_abs(dev, ABS_HAT1X, 1); + else + input_report_abs(dev, ABS_HAT1X, 0); + } + + if (test_bit(ABS_HAT1Y, dev->absbit)) { + if (btns & BIT(0)) + input_report_abs(dev, ABS_HAT1Y, -1); + else if (btns & BIT(2)) + input_report_abs(dev, ABS_HAT1Y, 1); + else + input_report_abs(dev, ABS_HAT1Y, 0); + } + } +} + +void iforce_process_packet(struct iforce *iforce, + u8 packet_id, u8 *data, size_t len) +{ + struct input_dev *dev = iforce->dev; + int i, j; + + switch (packet_id) { + + case 0x01: /* joystick position data */ + input_report_abs(dev, ABS_X, + (__s16) get_unaligned_le16(data)); + input_report_abs(dev, ABS_Y, + (__s16) get_unaligned_le16(data + 2)); + input_report_abs(dev, ABS_THROTTLE, 255 - data[4]); + + if (len >= 8 && test_bit(ABS_RUDDER ,dev->absbit)) + input_report_abs(dev, ABS_RUDDER, (__s8)data[7]); + + iforce_report_hats_buttons(iforce, data); + + input_sync(dev); + break; + + case 0x03: /* wheel position data */ + input_report_abs(dev, ABS_WHEEL, + (__s16) get_unaligned_le16(data)); + input_report_abs(dev, ABS_GAS, 255 - data[2]); + input_report_abs(dev, ABS_BRAKE, 255 - data[3]); + + iforce_report_hats_buttons(iforce, data); + + input_sync(dev); + break; + + case 0x02: /* status report */ + input_report_key(dev, BTN_DEAD, data[0] & 0x02); + input_sync(dev); + + /* Check if an effect was just started or stopped */ + i = data[1] & 0x7f; + if (data[1] & 0x80) { + if (!test_and_set_bit(FF_CORE_IS_PLAYED, iforce->core_effects[i].flags)) { + /* Report play event */ + input_report_ff_status(dev, i, FF_STATUS_PLAYING); + } + } else if (test_and_clear_bit(FF_CORE_IS_PLAYED, iforce->core_effects[i].flags)) { + /* Report stop event */ + input_report_ff_status(dev, i, FF_STATUS_STOPPED); + } + + for (j = 3; j < len; j += 2) + mark_core_as_ready(iforce, get_unaligned_le16(data + j)); + + break; + } +} +EXPORT_SYMBOL(iforce_process_packet); diff --git a/drivers/input/joystick/iforce/iforce-serio.c b/drivers/input/joystick/iforce/iforce-serio.c new file mode 100644 index 000000000..2380546d7 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-serio.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2000-2001 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001, 2007 Johann Deneux <johann.deneux@gmail.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +#include <linux/serio.h> +#include "iforce.h" + +struct iforce_serio { + struct iforce iforce; + + struct serio *serio; + int idx, pkt, len, id; + u8 csum; + u8 expect_packet; + u8 cmd_response[IFORCE_MAX_LENGTH]; + u8 cmd_response_len; + u8 data_in[IFORCE_MAX_LENGTH]; +}; + +static void iforce_serio_xmit(struct iforce *iforce) +{ + struct iforce_serio *iforce_serio = container_of(iforce, + struct iforce_serio, + iforce); + unsigned char cs; + int i; + unsigned long flags; + + if (test_and_set_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags)) { + set_bit(IFORCE_XMIT_AGAIN, iforce->xmit_flags); + return; + } + + spin_lock_irqsave(&iforce->xmit_lock, flags); + +again: + if (iforce->xmit.head == iforce->xmit.tail) { + iforce_clear_xmit_and_wake(iforce); + spin_unlock_irqrestore(&iforce->xmit_lock, flags); + return; + } + + cs = 0x2b; + + serio_write(iforce_serio->serio, 0x2b); + + serio_write(iforce_serio->serio, iforce->xmit.buf[iforce->xmit.tail]); + cs ^= iforce->xmit.buf[iforce->xmit.tail]; + XMIT_INC(iforce->xmit.tail, 1); + + for (i=iforce->xmit.buf[iforce->xmit.tail]; i >= 0; --i) { + serio_write(iforce_serio->serio, + iforce->xmit.buf[iforce->xmit.tail]); + cs ^= iforce->xmit.buf[iforce->xmit.tail]; + XMIT_INC(iforce->xmit.tail, 1); + } + + serio_write(iforce_serio->serio, cs); + + if (test_and_clear_bit(IFORCE_XMIT_AGAIN, iforce->xmit_flags)) + goto again; + + iforce_clear_xmit_and_wake(iforce); + + spin_unlock_irqrestore(&iforce->xmit_lock, flags); +} + +static int iforce_serio_get_id(struct iforce *iforce, u8 id, + u8 *response_data, size_t *response_len) +{ + struct iforce_serio *iforce_serio = container_of(iforce, + struct iforce_serio, + iforce); + + iforce_serio->expect_packet = HI(FF_CMD_QUERY); + iforce_serio->cmd_response_len = 0; + + iforce_send_packet(iforce, FF_CMD_QUERY, &id); + + wait_event_interruptible_timeout(iforce->wait, + !iforce_serio->expect_packet, HZ); + + if (iforce_serio->expect_packet) { + iforce_serio->expect_packet = 0; + return -ETIMEDOUT; + } + + if (iforce_serio->cmd_response[0] != id) + return -EIO; + + memcpy(response_data, iforce_serio->cmd_response, + iforce_serio->cmd_response_len); + *response_len = iforce_serio->cmd_response_len; + + return 0; +} + +static int iforce_serio_start_io(struct iforce *iforce) +{ + /* No special handling required */ + return 0; +} + +static void iforce_serio_stop_io(struct iforce *iforce) +{ + //TODO: Wait for the last packets to be sent +} + +static const struct iforce_xport_ops iforce_serio_xport_ops = { + .xmit = iforce_serio_xmit, + .get_id = iforce_serio_get_id, + .start_io = iforce_serio_start_io, + .stop_io = iforce_serio_stop_io, +}; + +static void iforce_serio_write_wakeup(struct serio *serio) +{ + struct iforce *iforce = serio_get_drvdata(serio); + + iforce_serio_xmit(iforce); +} + +static irqreturn_t iforce_serio_irq(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct iforce_serio *iforce_serio = serio_get_drvdata(serio); + struct iforce *iforce = &iforce_serio->iforce; + + if (!iforce_serio->pkt) { + if (data == 0x2b) + iforce_serio->pkt = 1; + goto out; + } + + if (!iforce_serio->id) { + if (data > 3 && data != 0xff) + iforce_serio->pkt = 0; + else + iforce_serio->id = data; + goto out; + } + + if (!iforce_serio->len) { + if (data > IFORCE_MAX_LENGTH) { + iforce_serio->pkt = 0; + iforce_serio->id = 0; + } else { + iforce_serio->len = data; + } + goto out; + } + + if (iforce_serio->idx < iforce_serio->len) { + iforce_serio->data_in[iforce_serio->idx++] = data; + iforce_serio->csum += data; + goto out; + } + + if (iforce_serio->idx == iforce_serio->len) { + /* Handle command completion */ + if (iforce_serio->expect_packet == iforce_serio->id) { + iforce_serio->expect_packet = 0; + memcpy(iforce_serio->cmd_response, + iforce_serio->data_in, IFORCE_MAX_LENGTH); + iforce_serio->cmd_response_len = iforce_serio->len; + + /* Signal that command is done */ + wake_up_all(&iforce->wait); + } else if (likely(iforce->type)) { + iforce_process_packet(iforce, iforce_serio->id, + iforce_serio->data_in, + iforce_serio->len); + } + + iforce_serio->pkt = 0; + iforce_serio->id = 0; + iforce_serio->len = 0; + iforce_serio->idx = 0; + iforce_serio->csum = 0; + } +out: + return IRQ_HANDLED; +} + +static int iforce_serio_connect(struct serio *serio, struct serio_driver *drv) +{ + struct iforce_serio *iforce_serio; + int err; + + iforce_serio = kzalloc(sizeof(*iforce_serio), GFP_KERNEL); + if (!iforce_serio) + return -ENOMEM; + + iforce_serio->iforce.xport_ops = &iforce_serio_xport_ops; + + iforce_serio->serio = serio; + serio_set_drvdata(serio, iforce_serio); + + err = serio_open(serio, drv); + if (err) + goto fail1; + + err = iforce_init_device(&serio->dev, BUS_RS232, &iforce_serio->iforce); + if (err) + goto fail2; + + return 0; + + fail2: serio_close(serio); + fail1: serio_set_drvdata(serio, NULL); + kfree(iforce_serio); + return err; +} + +static void iforce_serio_disconnect(struct serio *serio) +{ + struct iforce_serio *iforce_serio = serio_get_drvdata(serio); + + input_unregister_device(iforce_serio->iforce.dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(iforce_serio); +} + +static const struct serio_device_id iforce_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_IFORCE, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, iforce_serio_ids); + +struct serio_driver iforce_serio_drv = { + .driver = { + .name = "iforce", + }, + .description = "RS232 I-Force joysticks and wheels driver", + .id_table = iforce_serio_ids, + .write_wakeup = iforce_serio_write_wakeup, + .interrupt = iforce_serio_irq, + .connect = iforce_serio_connect, + .disconnect = iforce_serio_disconnect, +}; + +module_serio_driver(iforce_serio_drv); + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>, Johann Deneux <johann.deneux@gmail.com>"); +MODULE_DESCRIPTION("RS232 I-Force joysticks and wheels driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/joystick/iforce/iforce-usb.c b/drivers/input/joystick/iforce/iforce-usb.c new file mode 100644 index 000000000..cba92bd59 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-usb.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + /* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +#include <linux/usb.h> +#include "iforce.h" + +struct iforce_usb { + struct iforce iforce; + + struct usb_device *usbdev; + struct usb_interface *intf; + struct urb *irq, *out; + + u8 data_in[IFORCE_MAX_LENGTH] ____cacheline_aligned; + u8 data_out[IFORCE_MAX_LENGTH] ____cacheline_aligned; +}; + +static void __iforce_usb_xmit(struct iforce *iforce) +{ + struct iforce_usb *iforce_usb = container_of(iforce, struct iforce_usb, + iforce); + int n, c; + unsigned long flags; + + spin_lock_irqsave(&iforce->xmit_lock, flags); + + if (iforce->xmit.head == iforce->xmit.tail) { + iforce_clear_xmit_and_wake(iforce); + spin_unlock_irqrestore(&iforce->xmit_lock, flags); + return; + } + + ((char *)iforce_usb->out->transfer_buffer)[0] = iforce->xmit.buf[iforce->xmit.tail]; + XMIT_INC(iforce->xmit.tail, 1); + n = iforce->xmit.buf[iforce->xmit.tail]; + XMIT_INC(iforce->xmit.tail, 1); + + iforce_usb->out->transfer_buffer_length = n + 1; + iforce_usb->out->dev = iforce_usb->usbdev; + + /* Copy rest of data then */ + c = CIRC_CNT_TO_END(iforce->xmit.head, iforce->xmit.tail, XMIT_SIZE); + if (n < c) c=n; + + memcpy(iforce_usb->out->transfer_buffer + 1, + &iforce->xmit.buf[iforce->xmit.tail], + c); + if (n != c) { + memcpy(iforce_usb->out->transfer_buffer + 1 + c, + &iforce->xmit.buf[0], + n-c); + } + XMIT_INC(iforce->xmit.tail, n); + + if ( (n=usb_submit_urb(iforce_usb->out, GFP_ATOMIC)) ) { + dev_warn(&iforce_usb->intf->dev, + "usb_submit_urb failed %d\n", n); + iforce_clear_xmit_and_wake(iforce); + } + + /* The IFORCE_XMIT_RUNNING bit is not cleared here. That's intended. + * As long as the urb completion handler is not called, the transmiting + * is considered to be running */ + spin_unlock_irqrestore(&iforce->xmit_lock, flags); +} + +static void iforce_usb_xmit(struct iforce *iforce) +{ + if (!test_and_set_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags)) + __iforce_usb_xmit(iforce); +} + +static int iforce_usb_get_id(struct iforce *iforce, u8 id, + u8 *response_data, size_t *response_len) +{ + struct iforce_usb *iforce_usb = container_of(iforce, struct iforce_usb, + iforce); + u8 *buf; + int status; + + buf = kmalloc(IFORCE_MAX_LENGTH, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + status = usb_control_msg(iforce_usb->usbdev, + usb_rcvctrlpipe(iforce_usb->usbdev, 0), + id, + USB_TYPE_VENDOR | USB_DIR_IN | + USB_RECIP_INTERFACE, + 0, 0, buf, IFORCE_MAX_LENGTH, 1000); + if (status < 0) { + dev_err(&iforce_usb->intf->dev, + "usb_submit_urb failed: %d\n", status); + } else if (buf[0] != id) { + status = -EIO; + } else { + memcpy(response_data, buf, status); + *response_len = status; + status = 0; + } + + kfree(buf); + return status; +} + +static int iforce_usb_start_io(struct iforce *iforce) +{ + struct iforce_usb *iforce_usb = container_of(iforce, struct iforce_usb, + iforce); + + if (usb_submit_urb(iforce_usb->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void iforce_usb_stop_io(struct iforce *iforce) +{ + struct iforce_usb *iforce_usb = container_of(iforce, struct iforce_usb, + iforce); + + usb_kill_urb(iforce_usb->irq); + usb_kill_urb(iforce_usb->out); +} + +static const struct iforce_xport_ops iforce_usb_xport_ops = { + .xmit = iforce_usb_xmit, + .get_id = iforce_usb_get_id, + .start_io = iforce_usb_start_io, + .stop_io = iforce_usb_stop_io, +}; + +static void iforce_usb_irq(struct urb *urb) +{ + struct iforce_usb *iforce_usb = urb->context; + struct iforce *iforce = &iforce_usb->iforce; + struct device *dev = &iforce_usb->intf->dev; + int status; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shutting down with status: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(dev, "%s - urb has status of: %d\n", + __func__, urb->status); + goto exit; + } + + iforce_process_packet(iforce, iforce_usb->data_in[0], + iforce_usb->data_in + 1, urb->actual_length - 1); + +exit: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + dev_err(dev, "%s - usb_submit_urb failed with result %d\n", + __func__, status); +} + +static void iforce_usb_out(struct urb *urb) +{ + struct iforce_usb *iforce_usb = urb->context; + struct iforce *iforce = &iforce_usb->iforce; + + if (urb->status) { + dev_dbg(&iforce_usb->intf->dev, "urb->status %d, exiting\n", + urb->status); + iforce_clear_xmit_and_wake(iforce); + return; + } + + __iforce_usb_xmit(iforce); + + wake_up_all(&iforce->wait); +} + +static int iforce_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *epirq, *epout; + struct iforce_usb *iforce_usb; + int err = -ENOMEM; + + interface = intf->cur_altsetting; + + if (interface->desc.bNumEndpoints < 2) + return -ENODEV; + + epirq = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(epirq)) + return -ENODEV; + + epout = &interface->endpoint[1].desc; + if (!usb_endpoint_is_int_out(epout)) + return -ENODEV; + + iforce_usb = kzalloc(sizeof(*iforce_usb), GFP_KERNEL); + if (!iforce_usb) + goto fail; + + iforce_usb->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!iforce_usb->irq) + goto fail; + + iforce_usb->out = usb_alloc_urb(0, GFP_KERNEL); + if (!iforce_usb->out) + goto fail; + + iforce_usb->iforce.xport_ops = &iforce_usb_xport_ops; + + iforce_usb->usbdev = dev; + iforce_usb->intf = intf; + + usb_fill_int_urb(iforce_usb->irq, dev, + usb_rcvintpipe(dev, epirq->bEndpointAddress), + iforce_usb->data_in, sizeof(iforce_usb->data_in), + iforce_usb_irq, iforce_usb, epirq->bInterval); + + usb_fill_int_urb(iforce_usb->out, dev, + usb_sndintpipe(dev, epout->bEndpointAddress), + iforce_usb->data_out, sizeof(iforce_usb->data_out), + iforce_usb_out, iforce_usb, epout->bInterval); + + err = iforce_init_device(&intf->dev, BUS_USB, &iforce_usb->iforce); + if (err) + goto fail; + + usb_set_intfdata(intf, iforce_usb); + return 0; + +fail: + if (iforce_usb) { + usb_free_urb(iforce_usb->irq); + usb_free_urb(iforce_usb->out); + kfree(iforce_usb); + } + + return err; +} + +static void iforce_usb_disconnect(struct usb_interface *intf) +{ + struct iforce_usb *iforce_usb = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + + input_unregister_device(iforce_usb->iforce.dev); + + usb_free_urb(iforce_usb->irq); + usb_free_urb(iforce_usb->out); + + kfree(iforce_usb); +} + +static const struct usb_device_id iforce_usb_ids[] = { + { USB_DEVICE(0x044f, 0xa01c) }, /* Thrustmaster Motor Sport GT */ + { USB_DEVICE(0x046d, 0xc281) }, /* Logitech WingMan Force */ + { USB_DEVICE(0x046d, 0xc291) }, /* Logitech WingMan Formula Force */ + { USB_DEVICE(0x05ef, 0x020a) }, /* AVB Top Shot Pegasus */ + { USB_DEVICE(0x05ef, 0x8884) }, /* AVB Mag Turbo Force */ + { USB_DEVICE(0x05ef, 0x8888) }, /* AVB Top Shot FFB Racing Wheel */ + { USB_DEVICE(0x061c, 0xc0a4) }, /* ACT LABS Force RS */ + { USB_DEVICE(0x061c, 0xc084) }, /* ACT LABS Force RS */ + { USB_DEVICE(0x06a3, 0xff04) }, /* Saitek R440 Force Wheel */ + { USB_DEVICE(0x06f8, 0x0001) }, /* Guillemot Race Leader Force Feedback */ + { USB_DEVICE(0x06f8, 0x0003) }, /* Guillemot Jet Leader Force Feedback */ + { USB_DEVICE(0x06f8, 0x0004) }, /* Guillemot Force Feedback Racing Wheel */ + { USB_DEVICE(0x06f8, 0xa302) }, /* Guillemot Jet Leader 3D */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, iforce_usb_ids); + +struct usb_driver iforce_usb_driver = { + .name = "iforce", + .probe = iforce_usb_probe, + .disconnect = iforce_usb_disconnect, + .id_table = iforce_usb_ids, +}; + +module_usb_driver(iforce_usb_driver); + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>, Johann Deneux <johann.deneux@gmail.com>"); +MODULE_DESCRIPTION("USB I-Force joysticks and wheels driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/joystick/iforce/iforce.h b/drivers/input/joystick/iforce/iforce.h new file mode 100644 index 000000000..9ccb9107c --- /dev/null +++ b/drivers/input/joystick/iforce/iforce.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2000-2002 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2001-2002, 2007 Johann Deneux <johann.deneux@gmail.com> + * + * USB/RS232 I-Force joysticks and wheels. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/circ_buf.h> +#include <linux/mutex.h> + +/* This module provides arbitrary resource management routines. + * I use it to manage the device's memory. + * Despite the name of this module, I am *not* going to access the ioports. + */ +#include <linux/ioport.h> + + +#define IFORCE_MAX_LENGTH 16 + +#define IFORCE_EFFECTS_MAX 32 + +/* Each force feedback effect is made of one core effect, which can be + * associated to at most to effect modifiers + */ +#define FF_MOD1_IS_USED 0 +#define FF_MOD2_IS_USED 1 +#define FF_CORE_IS_USED 2 +#define FF_CORE_IS_PLAYED 3 /* Effect is currently being played */ +#define FF_CORE_SHOULD_PLAY 4 /* User wants the effect to be played */ +#define FF_CORE_UPDATE 5 /* Effect is being updated */ +#define FF_MODCORE_CNT 6 + +struct iforce_core_effect { + /* Information about where modifiers are stored in the device's memory */ + struct resource mod1_chunk; + struct resource mod2_chunk; + unsigned long flags[BITS_TO_LONGS(FF_MODCORE_CNT)]; +}; + +#define FF_CMD_EFFECT 0x010e +#define FF_CMD_ENVELOPE 0x0208 +#define FF_CMD_MAGNITUDE 0x0303 +#define FF_CMD_PERIOD 0x0407 +#define FF_CMD_CONDITION 0x050a + +#define FF_CMD_AUTOCENTER 0x4002 +#define FF_CMD_PLAY 0x4103 +#define FF_CMD_ENABLE 0x4201 +#define FF_CMD_GAIN 0x4301 + +#define FF_CMD_QUERY 0xff01 + +/* Buffer for async write */ +#define XMIT_SIZE 256 +#define XMIT_INC(var, n) (var)+=n; (var)&= XMIT_SIZE -1 +/* iforce::xmit_flags */ +#define IFORCE_XMIT_RUNNING 0 +#define IFORCE_XMIT_AGAIN 1 + +struct iforce_device { + u16 idvendor; + u16 idproduct; + char *name; + signed short *btn; + signed short *abs; + signed short *ff; +}; + +struct iforce; + +struct iforce_xport_ops { + void (*xmit)(struct iforce *iforce); + int (*get_id)(struct iforce *iforce, u8 id, + u8 *response_data, size_t *response_len); + int (*start_io)(struct iforce *iforce); + void (*stop_io)(struct iforce *iforce); +}; + +struct iforce { + struct input_dev *dev; /* Input device interface */ + struct iforce_device *type; + const struct iforce_xport_ops *xport_ops; + + spinlock_t xmit_lock; + /* Buffer used for asynchronous sending of bytes to the device */ + struct circ_buf xmit; + unsigned char xmit_data[XMIT_SIZE]; + unsigned long xmit_flags[1]; + + /* Force Feedback */ + wait_queue_head_t wait; + struct resource device_memory; + struct iforce_core_effect core_effects[IFORCE_EFFECTS_MAX]; + struct mutex mem_mutex; +}; + +/* Get hi and low bytes of a 16-bits int */ +#define HI(a) ((unsigned char)((a) >> 8)) +#define LO(a) ((unsigned char)((a) & 0xff)) + +/* For many parameters, it seems that 0x80 is a special value that should + * be avoided. Instead, we replace this value by 0x7f + */ +#define HIFIX80(a) ((unsigned char)(((a)<0? (a)+255 : (a))>>8)) + +/* Encode a time value */ +#define TIME_SCALE(a) (a) + +static inline int iforce_get_id_packet(struct iforce *iforce, u8 id, + u8 *response_data, size_t *response_len) +{ + return iforce->xport_ops->get_id(iforce, id, + response_data, response_len); +} + +static inline void iforce_clear_xmit_and_wake(struct iforce *iforce) +{ + clear_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags); + wake_up_all(&iforce->wait); +} + +/* Public functions */ +/* iforce-main.c */ +int iforce_init_device(struct device *parent, u16 bustype, + struct iforce *iforce); + +/* iforce-packets.c */ +int iforce_control_playback(struct iforce*, u16 id, unsigned int); +void iforce_process_packet(struct iforce *iforce, + u8 packet_id, u8 *data, size_t len); +int iforce_send_packet(struct iforce *iforce, u16 cmd, unsigned char* data); +void iforce_dump_packet(struct iforce *iforce, char *msg, u16 cmd, unsigned char *data); + +/* iforce-ff.c */ +int iforce_upload_periodic(struct iforce *, struct ff_effect *, struct ff_effect *); +int iforce_upload_constant(struct iforce *, struct ff_effect *, struct ff_effect *); +int iforce_upload_condition(struct iforce *, struct ff_effect *, struct ff_effect *); + +/* Public variables */ +extern struct serio_driver iforce_serio_drv; +extern struct usb_driver iforce_usb_driver; |