diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
| commit | 034dd340b08be1f2f0477ad16131d609f9dbd53c (patch) | |
| tree | 2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /drivers/input/joystick | |
| download | linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip | |
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Fix error output of boot instance creation failure
Currently if a boot instance creation fails, instead of printing out
the name of the instance that failed, it prints "(null)". That is
because it prints "cur_str" that had already been processed by
strsep(). Print the saved name instead.
While at it, print the error code of the failure.
- Fix use-after-free for same named historgrams
Histograms can be named so that they can be used in multiple events.
But if the named histogram has a variable attached, the second event
that uses the named histogram which duplicates it and needs to free
the original after duplication leaves the old variable in place and
still visible. If another histogram uses than variable, it will use
the stale one which will try to reference the freed duplicate
histogram and crash the kernel.
Free the duplicate variables along with the duplicated histogram
data.
- Check return value of kthread_run() in event self test
The events self tests uses a kthread for testing but does not check
if it succeeded in creating a kthread. If the kthread creation were
to fail, the code will still try to call kthread_stop() on the error
returned.
- Fix race between reading trace_pipe and updating subbuffer size
If a user is reading the trace_pipe file at the same time they update
the ring buffer sub-buffer size, can cause the trace_pipe read to
read stale data. Add trace_access_lock() around updating the ring
buffer sub-buffer size.
- Fix eventfs_inode on failure path in creation of the events directory
In the creation of the "events" directory, if after allocating the
eventfs_inode a failure is detected, it calls cleanup_ei() which
calls free_ei(). The free_ei() will test if eventfs_inode being freed
has no children. It is a bug if it does. But on the failure case of
the creation of the "events" directory, the children lists have not
yet been initialized and the free will trigger a warning because
list_empty() on an uninitialized list returns false.
Move the initialization into init_ei() where it makes more sense and
makes sure that a created eventfs_inode has its lists initialized
upon creation.
- Check return value of kthread_run() in ftrace direct sample code
The sample code that shows how to use the ftrace direct calls does
not test the return of kthread_run() to see if it succeeds. Return a
failure if the kthread_run() doesn't succeed.
- Clear user events state on fork in case of alloc failure
On fork, the child gets a pointer to the parent's user events state.
It makes a copy of it then updates the child's pointer to it. But if
the allocation fails, the duplication function leaves the child with
a pointer to its parent's descriptor. When the child cleans up its
data, it will free the parent's descriptor while the parent is still
using it.
In the duplication function, set the child's user_event_mm to NULL
before testing if the allocation succeeded, and when it exits it will
not free the parent's descriptor.
- Fix retry exhaustion in simple ring buffer reader swap
simple_ring_buffer_swap_reader_page() starts with retry set to 8 and
post-decrements it only after a failed link replacement. On the final
attempt, a successful replacement leaves retry at zero, while a
failed replacement leaves it at -1.
But the check for success expects the retry value to be non-zero and
exits with an error on zero. This is the opposite result. Fix it.
- Fail nicely when the remote swap_reader_page() returns an error
Currently, if the swap_reader_page() of a remote buffer fails, it
triggers a WARN_ON_ONCE() and continues normally. Instead, have it
exit with an error and a pr_warn() print instead of a full WARNING.
* tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
ring-buffer: Stop remote reader update when page swap fails
tracing: Fix retry exhaustion in simple ring buffer reader swap
tracing/user_events: Clear copied tracing state before fork duplication
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify
eventfs: Initialize ei->children and ei->list in init_ei()
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
tracing: Fix crash passing ERR_PTR to kthread_stop()
tracing: Fix use-after-free with same-name named triggers
tracing: Fix logged instance name on creation failure
Diffstat (limited to 'drivers/input/joystick')
45 files changed, 16767 insertions, 0 deletions
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig new file mode 100644 index 000000000..7755e5b45 --- /dev/null +++ b/drivers/input/joystick/Kconfig @@ -0,0 +1,425 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Joystick driver configuration +# +menuconfig INPUT_JOYSTICK + bool "Joysticks/Gamepads" + depends on !UML + help + If you have a joystick, 6dof controller, gamepad, steering wheel, + weapon control system or something like that you can say Y here + and the list of supported devices will be displayed. This option + doesn't affect the kernel. + + Please read the file <file:Documentation/input/joydev/joystick.rst> which + contains more information. + +if INPUT_JOYSTICK + +config JOYSTICK_ANALOG + tristate "Classic PC analog joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a joystick that connects to the PC + gameport. In addition to the usual PC analog joystick, this driver + supports many extensions, including joysticks with throttle control, + with rudders, additional hats and buttons compatible with CH + Flightstick Pro, ThrustMaster FCS, 6 and 8 button gamepads, or + Saitek Cyborg joysticks. + + Please read the file <file:Documentation/input/joydev/joystick.rst> which + contains more information. + + To compile this driver as a module, choose M here: the + module will be called analog. + +config JOYSTICK_A3D + tristate "Assassin 3D and MadCatz Panther devices" + select GAMEPORT + help + Say Y here if you have an FPGaming or MadCatz controller using the + A3D protocol over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called a3d. + +config JOYSTICK_ADC + tristate "Simple joystick connected over ADC" + depends on IIO + select IIO_BUFFER + select IIO_BUFFER_CB + help + Say Y here if you have a simple joystick connected over ADC. + + To compile this driver as a module, choose M here: the + module will be called adc-joystick. + +config JOYSTICK_ADI + tristate "Logitech ADI digital joysticks and gamepads" + select GAMEPORT + depends on ADI!=m # avoid module name conflict + help + Say Y here if you have a Logitech controller using the ADI + protocol over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called adi. + +config JOYSTICK_COBRA + tristate "Creative Labs Blaster Cobra gamepad" + select GAMEPORT + help + Say Y here if you have a Creative Labs Blaster Cobra gamepad. + + To compile this driver as a module, choose M here: the + module will be called cobra. + +config JOYSTICK_GF2K + tristate "Genius Flight2000 Digital joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Genius Flight2000 or MaxFighter digitally + communicating joystick or gamepad. + + To compile this driver as a module, choose M here: the + module will be called gf2k. + +config JOYSTICK_GRIP + tristate "Gravis GrIP joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Gravis controller using the GrIP protocol + over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called grip. + +config JOYSTICK_GRIP_MP + tristate "Gravis GrIP MultiPort" + select GAMEPORT + help + Say Y here if you have the original Gravis GrIP MultiPort, a hub + that connects to the gameport and you connect gamepads to it. + + To compile this driver as a module, choose M here: the + module will be called grip_mp. + +config JOYSTICK_GUILLEMOT + tristate "Guillemot joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Guillemot joystick using a digital + protocol over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called guillemot. + +config JOYSTICK_INTERACT + tristate "InterAct digital joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have an InterAct gameport or joystick + communicating digitally over the gameport. + + To compile this driver as a module, choose M here: the + module will be called interact. + +config JOYSTICK_SIDEWINDER + tristate "Microsoft SideWinder digital joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a Microsoft controller using the Digital + Overdrive protocol over PC gameport. + + To compile this driver as a module, choose M here: the + module will be called sidewinder. + +config JOYSTICK_TMDC + tristate "ThrustMaster DirectConnect joysticks and gamepads" + select GAMEPORT + help + Say Y here if you have a ThrustMaster controller using the + DirectConnect (BSP) protocol over the PC gameport. + + To compile this driver as a module, choose M here: the + module will be called tmdc. + +source "drivers/input/joystick/iforce/Kconfig" + +config JOYSTICK_WARRIOR + tristate "Logitech WingMan Warrior joystick" + select SERIO + help + Say Y here if you have a Logitech WingMan Warrior joystick connected + to your computer's serial port. + + To compile this driver as a module, choose M here: the + module will be called warrior. + +config JOYSTICK_MAGELLAN + tristate "LogiCad3d Magellan/SpaceMouse 6dof controllers" + select SERIO + help + Say Y here if you have a Magellan or Space Mouse 6DOF controller + connected to your computer's serial port. + + To compile this driver as a module, choose M here: the + module will be called magellan. + +config JOYSTICK_SPACEORB + tristate "SpaceTec SpaceOrb/Avenger 6dof controllers" + select SERIO + help + Say Y here if you have a SpaceOrb 360 or SpaceBall Avenger 6DOF + controller connected to your computer's serial port. + + To compile this driver as a module, choose M here: the + module will be called spaceorb. + +config JOYSTICK_SPACEBALL + tristate "SpaceTec SpaceBall 6dof controllers" + select SERIO + help + Say Y here if you have a SpaceTec SpaceBall 2003/3003/4000 FLX + controller connected to your computer's serial port. For the + SpaceBall 4000 USB model, use the USB HID driver. + + To compile this driver as a module, choose M here: the + module will be called spaceball. + +config JOYSTICK_STINGER + tristate "Gravis Stinger gamepad" + select SERIO + help + Say Y here if you have a Gravis Stinger connected to one of your + serial ports. + + To compile this driver as a module, choose M here: the + module will be called stinger. + +config JOYSTICK_TWIDJOY + tristate "Twiddler as a joystick" + select SERIO + help + Say Y here if you have a Handykey Twiddler connected to your + computer's serial port and want to use it as a joystick. + + To compile this driver as a module, choose M here: the + module will be called twidjoy. + +config JOYSTICK_ZHENHUA + tristate "5-byte Zhenhua RC transmitter" + select SERIO + select BITREVERSE + help + Say Y here if you have a Zhen Hua PPM-4CH transmitter which is + supplied with a ready to fly micro electric indoor helicopters + such as EasyCopter, Lama, MiniCopter, DragonFly or Jabo and want + to use it via serial cable as a joystick. + + To compile this driver as a module, choose M here: the + module will be called zhenhua. + +config JOYSTICK_DB9 + tristate "Multisystem, Sega Genesis, Saturn joysticks and gamepads" + depends on PARPORT + help + Say Y here if you have a Sega Master System gamepad, Sega Genesis + gamepad, Sega Saturn gamepad, or a Multisystem -- Atari, Amiga, + Commodore, Amstrad CPC joystick connected to your parallel port. + For more information on how to use the driver please read + <file:Documentation/input/devices/joystick-parport.rst>. + + To compile this driver as a module, choose M here: the + module will be called db9. + +config JOYSTICK_GAMECON + tristate "Multisystem, NES, SNES, N64, PSX joysticks and gamepads" + depends on PARPORT + select INPUT_FF_MEMLESS + help + Say Y here if you have a Nintendo Entertainment System gamepad, + Super Nintendo Entertainment System gamepad, Nintendo 64 gamepad, + Sony PlayStation gamepad or a Multisystem -- Atari, Amiga, + Commodore, Amstrad CPC joystick connected to your parallel port. + For more information on how to use the driver please read + <file:Documentation/input/devices/joystick-parport.rst>. + + To compile this driver as a module, choose M here: the + module will be called gamecon. + +config JOYSTICK_TURBOGRAFX + tristate "Multisystem joysticks via TurboGraFX device" + depends on PARPORT + help + Say Y here if you have the TurboGraFX interface by Steffen Schwenke, + and want to use it with Multisystem -- Atari, Amiga, Commodore, + Amstrad CPC joystick. For more information on how to use the driver + please read <file:Documentation/input/devices/joystick-parport.rst>. + + To compile this driver as a module, choose M here: the + module will be called turbografx. + +config JOYSTICK_AMIGA + tristate "Amiga joysticks" + depends on AMIGA + help + Say Y here if you have an Amiga with a digital joystick connected + to it. + + To compile this driver as a module, choose M here: the + module will be called amijoy. + +config JOYSTICK_AS5011 + tristate "Austria Microsystem AS5011 joystick" + depends on I2C + help + Say Y here if you have an AS5011 digital joystick connected to your + system. + + To compile this driver as a module, choose M here: the + module will be called as5011. + +config JOYSTICK_JOYDUMP + tristate "Gameport data dumper" + select GAMEPORT + help + Say Y here if you want to dump data from your joystick into the system + log for debugging purposes. Say N if you are making a production + configuration or aren't sure. + + To compile this driver as a module, choose M here: the + module will be called joydump. + +config JOYSTICK_XPAD + tristate "Xbox gamepad support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use Xbox pads with your computer. + Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV) + and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well. + + For information about how to connect the Xbox pad to USB, see + <file:Documentation/input/devices/xpad.rst>. + + To compile this driver as a module, choose M here: the + module will be called xpad. + +config JOYSTICK_XPAD_FF + bool "Xbox gamepad rumble support" + depends on JOYSTICK_XPAD && INPUT + select INPUT_FF_MEMLESS + help + Say Y here if you want to take advantage of Xbox 360 rumble features. + +config JOYSTICK_XPAD_LEDS + bool "LED Support for the Xbox 360 controller Guide button" + depends on JOYSTICK_XPAD && (LEDS_CLASS=y || LEDS_CLASS=JOYSTICK_XPAD) + help + This option enables support for the LED which surrounds the Big X on + Xbox 360 controllers. + +config JOYSTICK_WALKERA0701 + tristate "Walkera WK-0701 RC transmitter" + depends on HIGH_RES_TIMERS && PARPORT + help + Say Y or M here if you have a Walkera WK-0701 transmitter which is + supplied with a ready to fly Walkera helicopters such as HM36, + HM37, HM60 and want to use it via parport as a joystick. More + information is available: <file:Documentation/input/devices/walkera0701.rst> + + To compile this driver as a module, choose M here: the + module will be called walkera0701. + +config JOYSTICK_MAPLE + tristate "Dreamcast control pad" + depends on MAPLE + help + Say Y here if you have a SEGA Dreamcast and want to use your + controller as a joystick. + + Most Dreamcast users will say Y. + + To compile this as a module choose M here: the module will be called + maplecontrol. + +config JOYSTICK_PSXPAD_SPI + tristate "PlayStation 1/2 joypads via SPI interface" + depends on SPI + help + Say Y here if you wish to connect PlayStation 1/2 joypads + via SPI interface. + + To compile this driver as a module, choose M here: the + module will be called psxpad-spi. + +config JOYSTICK_PSXPAD_SPI_FF + bool "PlayStation 1/2 joypads force feedback (rumble) support" + depends on JOYSTICK_PSXPAD_SPI + select INPUT_FF_MEMLESS + help + Say Y here if you want to take advantage of PlayStation 1/2 + joypads rumble features. + + To drive rumble motor a dedicated power supply is required. + +config JOYSTICK_PXRC + tristate "PhoenixRC Flight Controller Adapter" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use the PhoenixRC Flight Controller Adapter. + + To compile this driver as a module, choose M here: the + module will be called pxrc. + +config JOYSTICK_QWIIC + tristate "SparkFun Qwiic Joystick" + depends on I2C + help + Say Y here if you want to use the SparkFun Qwiic Joystick. + + To compile this driver as a module, choose M here: the + module will be called qwiic-joystick. + +config JOYSTICK_FSIA6B + tristate "FlySky FS-iA6B RC Receiver" + select SERIO + help + Say Y here if you use a FlySky FS-i6 RC remote control along with the + FS-iA6B RC receiver as a joystick input device. + + To compile this driver as a module, choose M here: the + module will be called fsia6b. + +config JOYSTICK_N64 + bool "N64 controller" + depends on MACH_NINTENDO64 + help + Say Y here if you want enable support for the four + built-in controller ports on the Nintendo 64 console. + +config JOYSTICK_SENSEHAT + tristate "Raspberry Pi Sense HAT joystick" + depends on INPUT && I2C + depends on HAS_IOMEM + select MFD_SIMPLE_MFD_I2C + help + Say Y here if you want to enable the driver for the + the Raspberry Pi Sense HAT. + + To compile this driver as a module, choose M here: the + module will be called sensehat_joystick. + +config JOYSTICK_SEESAW + tristate "Adafruit Mini I2C Gamepad with Seesaw" + depends on I2C + select INPUT_SPARSEKMAP + help + Say Y here if you want to use the Adafruit Mini I2C Gamepad. + + To compile this driver as a module, choose M here: the module will be + called adafruit-seesaw. + +endif diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile new file mode 100644 index 000000000..9976f596a --- /dev/null +++ b/drivers/input/joystick/Makefile @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_JOYSTICK_A3D) += a3d.o +obj-$(CONFIG_JOYSTICK_ADC) += adc-joystick.o +obj-$(CONFIG_JOYSTICK_ADI) += adi.o +obj-$(CONFIG_JOYSTICK_AMIGA) += amijoy.o +obj-$(CONFIG_JOYSTICK_AS5011) += as5011.o +obj-$(CONFIG_JOYSTICK_ANALOG) += analog.o +obj-$(CONFIG_JOYSTICK_COBRA) += cobra.o +obj-$(CONFIG_JOYSTICK_DB9) += db9.o +obj-$(CONFIG_JOYSTICK_FSIA6B) += fsia6b.o +obj-$(CONFIG_JOYSTICK_GAMECON) += gamecon.o +obj-$(CONFIG_JOYSTICK_GF2K) += gf2k.o +obj-$(CONFIG_JOYSTICK_GRIP) += grip.o +obj-$(CONFIG_JOYSTICK_GRIP_MP) += grip_mp.o +obj-$(CONFIG_JOYSTICK_GUILLEMOT) += guillemot.o +obj-$(CONFIG_JOYSTICK_IFORCE) += iforce/ +obj-$(CONFIG_JOYSTICK_INTERACT) += interact.o +obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o +obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o +obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o +obj-$(CONFIG_JOYSTICK_N64) += n64joy.o +obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o +obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o +obj-$(CONFIG_JOYSTICK_QWIIC) += qwiic-joystick.o +obj-$(CONFIG_JOYSTICK_SEESAW) += adafruit-seesaw.o +obj-$(CONFIG_JOYSTICK_SENSEHAT) += sensehat-joystick.o +obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o +obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o +obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o +obj-$(CONFIG_JOYSTICK_STINGER) += stinger.o +obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o +obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o +obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o +obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o +obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o +obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o +obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o diff --git a/drivers/input/joystick/a3d.c b/drivers/input/joystick/a3d.c new file mode 100644 index 000000000..48a145796 --- /dev/null +++ b/drivers/input/joystick/a3d.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * FP-Gaming Assassin 3D joystick driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/gameport.h> +#include <linux/input.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "FP-Gaming Assassin 3D joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define A3D_MAX_START 600 /* 600 us */ +#define A3D_MAX_STROBE 80 /* 80 us */ +#define A3D_MAX_LENGTH 40 /* 40*3 bits */ + +#define A3D_MODE_A3D 1 /* Assassin 3D */ +#define A3D_MODE_PAN 2 /* Panther */ +#define A3D_MODE_OEM 3 /* Panther OEM version */ +#define A3D_MODE_PXL 4 /* Panther XL */ + +static char *a3d_names[] = { NULL, "FP-Gaming Assassin 3D", "MadCatz Panther", "OEM Panther", + "MadCatz Panther XL", "MadCatz Panther XL w/ rudder" }; + +struct a3d { + struct gameport *gameport; + struct gameport *adc; + struct input_dev *dev; + int axes[4]; + int buttons; + int mode; + int length; + int reads; + int bads; + char phys[32]; +}; + +/* + * a3d_read_packet() reads an Assassin 3D packet. + */ + +static int a3d_read_packet(struct gameport *gameport, int length, char *data) +{ + unsigned long flags; + unsigned char u, v; + unsigned int t, s; + int i; + + i = 0; + t = gameport_time(gameport, A3D_MAX_START); + s = gameport_time(gameport, A3D_MAX_STROBE); + + local_irq_save(flags); + gameport_trigger(gameport); + v = gameport_read(gameport); + + while (t > 0 && i < length) { + t--; + u = v; v = gameport_read(gameport); + if (~v & u & 0x10) { + data[i++] = v >> 5; + t = s; + } + } + + local_irq_restore(flags); + + return i; +} + +/* + * a3d_csum() computes checksum of triplet packet + */ + +static int a3d_csum(char *data, int count) +{ + int i, csum = 0; + + for (i = 0; i < count - 2; i++) + csum += data[i]; + return (csum & 0x3f) != ((data[count - 2] << 3) | data[count - 1]); +} + +static void a3d_read(struct a3d *a3d, unsigned char *data) +{ + struct input_dev *dev = a3d->dev; + + switch (a3d->mode) { + + case A3D_MODE_A3D: + case A3D_MODE_OEM: + case A3D_MODE_PAN: + + input_report_rel(dev, REL_X, ((data[5] << 6) | (data[6] << 3) | data[ 7]) - ((data[5] & 4) << 7)); + input_report_rel(dev, REL_Y, ((data[8] << 6) | (data[9] << 3) | data[10]) - ((data[8] & 4) << 7)); + + input_report_key(dev, BTN_RIGHT, data[2] & 1); + input_report_key(dev, BTN_LEFT, data[3] & 2); + input_report_key(dev, BTN_MIDDLE, data[3] & 4); + + input_sync(dev); + + a3d->axes[0] = ((signed char)((data[11] << 6) | (data[12] << 3) | (data[13]))) + 128; + a3d->axes[1] = ((signed char)((data[14] << 6) | (data[15] << 3) | (data[16]))) + 128; + a3d->axes[2] = ((signed char)((data[17] << 6) | (data[18] << 3) | (data[19]))) + 128; + a3d->axes[3] = ((signed char)((data[20] << 6) | (data[21] << 3) | (data[22]))) + 128; + + a3d->buttons = ((data[3] << 3) | data[4]) & 0xf; + + break; + + case A3D_MODE_PXL: + + input_report_rel(dev, REL_X, ((data[ 9] << 6) | (data[10] << 3) | data[11]) - ((data[ 9] & 4) << 7)); + input_report_rel(dev, REL_Y, ((data[12] << 6) | (data[13] << 3) | data[14]) - ((data[12] & 4) << 7)); + + input_report_key(dev, BTN_RIGHT, data[2] & 1); + input_report_key(dev, BTN_LEFT, data[3] & 2); + input_report_key(dev, BTN_MIDDLE, data[3] & 4); + input_report_key(dev, BTN_SIDE, data[7] & 2); + input_report_key(dev, BTN_EXTRA, data[7] & 4); + + input_report_abs(dev, ABS_X, ((signed char)((data[15] << 6) | (data[16] << 3) | (data[17]))) + 128); + input_report_abs(dev, ABS_Y, ((signed char)((data[18] << 6) | (data[19] << 3) | (data[20]))) + 128); + input_report_abs(dev, ABS_RUDDER, ((signed char)((data[21] << 6) | (data[22] << 3) | (data[23]))) + 128); + input_report_abs(dev, ABS_THROTTLE, ((signed char)((data[24] << 6) | (data[25] << 3) | (data[26]))) + 128); + + input_report_abs(dev, ABS_HAT0X, ( data[5] & 1) - ((data[5] >> 2) & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[5] >> 1) & 1) - ((data[6] >> 2) & 1)); + input_report_abs(dev, ABS_HAT1X, ((data[4] >> 1) & 1) - ( data[3] & 1)); + input_report_abs(dev, ABS_HAT1Y, ((data[4] >> 2) & 1) - ( data[4] & 1)); + + input_report_key(dev, BTN_TRIGGER, data[8] & 1); + input_report_key(dev, BTN_THUMB, data[8] & 2); + input_report_key(dev, BTN_TOP, data[8] & 4); + input_report_key(dev, BTN_PINKIE, data[7] & 1); + + input_sync(dev); + + break; + } +} + + +/* + * a3d_poll() reads and analyzes A3D joystick data. + */ + +static void a3d_poll(struct gameport *gameport) +{ + struct a3d *a3d = gameport_get_drvdata(gameport); + unsigned char data[A3D_MAX_LENGTH]; + + a3d->reads++; + if (a3d_read_packet(a3d->gameport, a3d->length, data) != a3d->length || + data[0] != a3d->mode || a3d_csum(data, a3d->length)) + a3d->bads++; + else + a3d_read(a3d, data); +} + +/* + * a3d_adc_cooked_read() copies the acis and button data to the + * callers arrays. It could do the read itself, but the caller could + * call this more than 50 times a second, which would use too much CPU. + */ + +static int a3d_adc_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + struct a3d *a3d = gameport->port_data; + int i; + + for (i = 0; i < 4; i++) + axes[i] = (a3d->axes[i] < 254) ? a3d->axes[i] : -1; + *buttons = a3d->buttons; + return 0; +} + +/* + * a3d_adc_open() is the gameport open routine. It refuses to serve + * any but cooked data. + */ + +static int a3d_adc_open(struct gameport *gameport, int mode) +{ + struct a3d *a3d = gameport->port_data; + + if (mode != GAMEPORT_MODE_COOKED) + return -1; + + gameport_start_polling(a3d->gameport); + return 0; +} + +/* + * a3d_adc_close() is a callback from the input close routine. + */ + +static void a3d_adc_close(struct gameport *gameport) +{ + struct a3d *a3d = gameport->port_data; + + gameport_stop_polling(a3d->gameport); +} + +/* + * a3d_open() is a callback from the input open routine. + */ + +static int a3d_open(struct input_dev *dev) +{ + struct a3d *a3d = input_get_drvdata(dev); + + gameport_start_polling(a3d->gameport); + return 0; +} + +/* + * a3d_close() is a callback from the input close routine. + */ + +static void a3d_close(struct input_dev *dev) +{ + struct a3d *a3d = input_get_drvdata(dev); + + gameport_stop_polling(a3d->gameport); +} + +/* + * a3d_connect() probes for A3D joysticks. + */ + +static int a3d_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct a3d *a3d; + struct input_dev *input_dev; + struct gameport *adc; + unsigned char data[A3D_MAX_LENGTH]; + int i; + int err; + + a3d = kzalloc_obj(*a3d); + input_dev = input_allocate_device(); + if (!a3d || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + a3d->dev = input_dev; + a3d->gameport = gameport; + + gameport_set_drvdata(gameport, a3d); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + i = a3d_read_packet(gameport, A3D_MAX_LENGTH, data); + + if (!i || a3d_csum(data, i)) { + err = -ENODEV; + goto fail2; + } + + a3d->mode = data[0]; + + if (!a3d->mode || a3d->mode > 5) { + printk(KERN_WARNING "a3d.c: Unknown A3D device detected " + "(%s, id=%d), contact <vojtech@ucw.cz>\n", gameport->phys, a3d->mode); + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, a3d_poll); + gameport_set_poll_interval(gameport, 20); + + snprintf(a3d->phys, sizeof(a3d->phys), "%s/input0", gameport->phys); + + input_dev->name = a3d_names[a3d->mode]; + input_dev->phys = a3d->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_MADCATZ; + input_dev->id.product = a3d->mode; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + input_dev->open = a3d_open; + input_dev->close = a3d_close; + + input_set_drvdata(input_dev, a3d); + + if (a3d->mode == A3D_MODE_PXL) { + + int axes[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER }; + + a3d->length = 33; + + input_dev->evbit[0] |= BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY) | + BIT_MASK(EV_REL); + input_dev->relbit[0] |= BIT_MASK(REL_X) | BIT_MASK(REL_Y); + input_dev->absbit[0] |= BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_THROTTLE) | BIT_MASK(ABS_RUDDER) | + BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_RIGHT) | + BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE) | + BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA); + input_dev->keybit[BIT_WORD(BTN_JOYSTICK)] |= + BIT_MASK(BTN_TRIGGER) | BIT_MASK(BTN_THUMB) | + BIT_MASK(BTN_TOP) | BIT_MASK(BTN_PINKIE); + + a3d_read(a3d, data); + + for (i = 0; i < 4; i++) { + if (i < 2) + input_set_abs_params(input_dev, axes[i], + 48, input_abs_get_val(input_dev, axes[i]) * 2 - 48, 0, 8); + else + input_set_abs_params(input_dev, axes[i], 2, 253, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0X + i, -1, 1, 0, 0); + } + + } else { + a3d->length = 29; + + input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input_dev->relbit[0] |= BIT_MASK(REL_X) | BIT_MASK(REL_Y); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_RIGHT) | + BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE); + + a3d_read(a3d, data); + + if (!(a3d->adc = adc = gameport_allocate_port())) + printk(KERN_ERR "a3d: Not enough memory for ADC port\n"); + else { + adc->port_data = a3d; + adc->open = a3d_adc_open; + adc->close = a3d_adc_close; + adc->cooked_read = a3d_adc_cooked_read; + adc->fuzz = 1; + + gameport_set_name(adc, a3d_names[a3d->mode]); + gameport_set_phys(adc, "%s/gameport0", gameport->phys); + adc->dev.parent = &gameport->dev; + + gameport_register_port(adc); + } + } + + err = input_register_device(a3d->dev); + if (err) + goto fail3; + + return 0; + + fail3: if (a3d->adc) + gameport_unregister_port(a3d->adc); + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + input_free_device(input_dev); + kfree(a3d); + return err; +} + +static void a3d_disconnect(struct gameport *gameport) +{ + struct a3d *a3d = gameport_get_drvdata(gameport); + + input_unregister_device(a3d->dev); + if (a3d->adc) + gameport_unregister_port(a3d->adc); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(a3d); +} + +static struct gameport_driver a3d_drv = { + .driver = { + .name = "adc", + .owner = THIS_MODULE, + }, + .description = DRIVER_DESC, + .connect = a3d_connect, + .disconnect = a3d_disconnect, +}; + +module_gameport_driver(a3d_drv); diff --git a/drivers/input/joystick/adafruit-seesaw.c b/drivers/input/joystick/adafruit-seesaw.c new file mode 100644 index 000000000..5835ed1e2 --- /dev/null +++ b/drivers/input/joystick/adafruit-seesaw.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com> + * + * Driver for Adafruit Mini I2C Gamepad + * + * Based on the work of: + * Oleh Kravchenko (Sparkfun Qwiic Joystick driver) + * + * Datasheet: https://cdn-learn.adafruit.com/downloads/pdf/gamepad-qt.pdf + * Product page: https://www.adafruit.com/product/5743 + * Firmware and hardware sources: https://github.com/adafruit/Adafruit_Seesaw + * + * TODO: + * - Add interrupt support + */ + +#include <linux/unaligned.h> +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#define SEESAW_DEVICE_NAME "seesaw-gamepad" + +#define SEESAW_ADC_BASE 0x0900 + +#define SEESAW_GPIO_DIRCLR_BULK 0x0103 +#define SEESAW_GPIO_BULK 0x0104 +#define SEESAW_GPIO_BULK_SET 0x0105 +#define SEESAW_GPIO_PULLENSET 0x010b + +#define SEESAW_STATUS_HW_ID 0x0001 +#define SEESAW_STATUS_SWRST 0x007f + +#define SEESAW_ADC_OFFSET 0x07 + +#define SEESAW_BUTTON_A 0x05 +#define SEESAW_BUTTON_B 0x01 +#define SEESAW_BUTTON_X 0x06 +#define SEESAW_BUTTON_Y 0x02 +#define SEESAW_BUTTON_START 0x10 +#define SEESAW_BUTTON_SELECT 0x00 + +#define SEESAW_ANALOG_X 0x0e +#define SEESAW_ANALOG_Y 0x0f + +#define SEESAW_JOYSTICK_MAX_AXIS 1023 +#define SEESAW_JOYSTICK_FUZZ 2 +#define SEESAW_JOYSTICK_FLAT 4 + +#define SEESAW_GAMEPAD_POLL_INTERVAL_MS 16 +#define SEESAW_GAMEPAD_POLL_MIN 8 +#define SEESAW_GAMEPAD_POLL_MAX 32 + +static const u32 SEESAW_BUTTON_MASK = + BIT(SEESAW_BUTTON_A) | BIT(SEESAW_BUTTON_B) | BIT(SEESAW_BUTTON_X) | + BIT(SEESAW_BUTTON_Y) | BIT(SEESAW_BUTTON_START) | + BIT(SEESAW_BUTTON_SELECT); + +struct seesaw_gamepad { + struct input_dev *input_dev; + struct i2c_client *i2c_client; + u32 button_state; +}; + +struct seesaw_data { + u16 x; + u16 y; + u32 button_state; +}; + +static const struct key_entry seesaw_buttons_new[] = { + { KE_KEY, SEESAW_BUTTON_A, .keycode = BTN_SOUTH }, + { KE_KEY, SEESAW_BUTTON_B, .keycode = BTN_EAST }, + { KE_KEY, SEESAW_BUTTON_X, .keycode = BTN_NORTH }, + { KE_KEY, SEESAW_BUTTON_Y, .keycode = BTN_WEST }, + { KE_KEY, SEESAW_BUTTON_START, .keycode = BTN_START }, + { KE_KEY, SEESAW_BUTTON_SELECT, .keycode = BTN_SELECT }, + { KE_END, 0 } +}; + +static int seesaw_register_read(struct i2c_client *client, u16 reg, void *buf, + int count) +{ + __be16 register_buf = cpu_to_be16(reg); + struct i2c_msg message_buf[2] = { + { + .addr = client->addr, + .flags = client->flags, + .len = sizeof(register_buf), + .buf = (u8 *)®ister_buf, + }, + { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .len = count, + .buf = (u8 *)buf, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, message_buf, + ARRAY_SIZE(message_buf)); + if (ret < 0) + return ret; + + return 0; +} + +static int seesaw_register_write_u8(struct i2c_client *client, u16 reg, + u8 value) +{ + u8 write_buf[sizeof(reg) + sizeof(value)]; + int ret; + + put_unaligned_be16(reg, write_buf); + write_buf[sizeof(reg)] = value; + + ret = i2c_master_send(client, write_buf, sizeof(write_buf)); + if (ret < 0) + return ret; + + return 0; +} + +static int seesaw_register_write_u32(struct i2c_client *client, u16 reg, + u32 value) +{ + u8 write_buf[sizeof(reg) + sizeof(value)]; + int ret; + + put_unaligned_be16(reg, write_buf); + put_unaligned_be32(value, write_buf + sizeof(reg)); + ret = i2c_master_send(client, write_buf, sizeof(write_buf)); + if (ret < 0) + return ret; + + return 0; +} + +static int seesaw_read_data(struct i2c_client *client, struct seesaw_data *data) +{ + __be16 adc_data; + __be32 read_buf; + int err; + + err = seesaw_register_read(client, SEESAW_GPIO_BULK, + &read_buf, sizeof(read_buf)); + if (err) + return err; + + data->button_state = ~be32_to_cpu(read_buf); + + err = seesaw_register_read(client, + SEESAW_ADC_BASE | + (SEESAW_ADC_OFFSET + SEESAW_ANALOG_X), + &adc_data, sizeof(adc_data)); + if (err) + return err; + /* + * ADC reads left as max and right as 0, must be reversed since kernel + * expects reports in opposite order. + */ + data->x = SEESAW_JOYSTICK_MAX_AXIS - be16_to_cpu(adc_data); + + err = seesaw_register_read(client, + SEESAW_ADC_BASE | + (SEESAW_ADC_OFFSET + SEESAW_ANALOG_Y), + &adc_data, sizeof(adc_data)); + if (err) + return err; + + data->y = be16_to_cpu(adc_data); + + return 0; +} + +static int seesaw_open(struct input_dev *input) +{ + struct seesaw_gamepad *private = input_get_drvdata(input); + + private->button_state = 0; + + return 0; +} + +static void seesaw_poll(struct input_dev *input) +{ + struct seesaw_gamepad *private = input_get_drvdata(input); + struct seesaw_data data; + unsigned long changed; + int err, i; + + err = seesaw_read_data(private->i2c_client, &data); + if (err) { + dev_err_ratelimited(&input->dev, + "failed to read joystick state: %d\n", err); + return; + } + + input_report_abs(input, ABS_X, data.x); + input_report_abs(input, ABS_Y, data.y); + + data.button_state &= SEESAW_BUTTON_MASK; + changed = private->button_state ^ data.button_state; + private->button_state = data.button_state; + + for_each_set_bit(i, &changed, fls(SEESAW_BUTTON_MASK)) { + if (!sparse_keymap_report_event(input, i, + data.button_state & BIT(i), + false)) + dev_err_ratelimited(&input->dev, + "failed to report keymap event"); + } + + input_sync(input); +} + +static int seesaw_probe(struct i2c_client *client) +{ + struct seesaw_gamepad *seesaw; + u8 hardware_id; + int err; + + err = seesaw_register_write_u8(client, SEESAW_STATUS_SWRST, 0xFF); + if (err) + return err; + + /* Wait for the registers to reset before proceeding */ + usleep_range(10000, 15000); + + seesaw = devm_kzalloc(&client->dev, sizeof(*seesaw), GFP_KERNEL); + if (!seesaw) + return -ENOMEM; + + err = seesaw_register_read(client, SEESAW_STATUS_HW_ID, + &hardware_id, sizeof(hardware_id)); + if (err) + return err; + + dev_dbg(&client->dev, "Adafruit Seesaw Gamepad, Hardware ID: %02x\n", + hardware_id); + + /* Set Pin Mode to input and enable pull-up resistors */ + err = seesaw_register_write_u32(client, SEESAW_GPIO_DIRCLR_BULK, + SEESAW_BUTTON_MASK); + if (err) + return err; + err = seesaw_register_write_u32(client, SEESAW_GPIO_PULLENSET, + SEESAW_BUTTON_MASK); + if (err) + return err; + err = seesaw_register_write_u32(client, SEESAW_GPIO_BULK_SET, + SEESAW_BUTTON_MASK); + if (err) + return err; + + seesaw->i2c_client = client; + seesaw->input_dev = devm_input_allocate_device(&client->dev); + if (!seesaw->input_dev) + return -ENOMEM; + + seesaw->input_dev->id.bustype = BUS_I2C; + seesaw->input_dev->name = "Adafruit Seesaw Gamepad"; + seesaw->input_dev->phys = "i2c/" SEESAW_DEVICE_NAME; + seesaw->input_dev->open = seesaw_open; + input_set_drvdata(seesaw->input_dev, seesaw); + input_set_abs_params(seesaw->input_dev, ABS_X, + 0, SEESAW_JOYSTICK_MAX_AXIS, + SEESAW_JOYSTICK_FUZZ, SEESAW_JOYSTICK_FLAT); + input_set_abs_params(seesaw->input_dev, ABS_Y, + 0, SEESAW_JOYSTICK_MAX_AXIS, + SEESAW_JOYSTICK_FUZZ, SEESAW_JOYSTICK_FLAT); + + err = sparse_keymap_setup(seesaw->input_dev, seesaw_buttons_new, NULL); + if (err) { + dev_err(&client->dev, + "failed to set up input device keymap: %d\n", err); + return err; + } + + err = input_setup_polling(seesaw->input_dev, seesaw_poll); + if (err) { + dev_err(&client->dev, "failed to set up polling: %d\n", err); + return err; + } + + input_set_poll_interval(seesaw->input_dev, + SEESAW_GAMEPAD_POLL_INTERVAL_MS); + input_set_max_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MAX); + input_set_min_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MIN); + + err = input_register_device(seesaw->input_dev); + if (err) { + dev_err(&client->dev, "failed to register joystick: %d\n", err); + return err; + } + + return 0; +} + +static const struct i2c_device_id seesaw_id_table[] = { + { .name = SEESAW_DEVICE_NAME }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, seesaw_id_table); + +static const struct of_device_id seesaw_of_table[] = { + { .compatible = "adafruit,seesaw-gamepad"}, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, seesaw_of_table); + +static struct i2c_driver seesaw_driver = { + .driver = { + .name = SEESAW_DEVICE_NAME, + .of_match_table = seesaw_of_table, + }, + .id_table = seesaw_id_table, + .probe = seesaw_probe, +}; +module_i2c_driver(seesaw_driver); + +MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>"); +MODULE_DESCRIPTION("Adafruit Mini I2C Gamepad driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/joystick/adc-joystick.c b/drivers/input/joystick/adc-joystick.c new file mode 100644 index 000000000..4fa42f88b --- /dev/null +++ b/drivers/input/joystick/adc-joystick.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Input driver for joysticks connected over ADC. + * Copyright (c) 2019-2020 Artur Rojek <contact@artur-rojek.eu> + */ +#include <linux/ctype.h> +#include <linux/input.h> +#include <linux/iio/iio.h> +#include <linux/iio/consumer.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +#include <linux/unaligned.h> + +struct adc_joystick_axis { + u32 code; + bool inverted; +}; + +struct adc_joystick { + struct input_dev *input; + struct iio_cb_buffer *buffer; + struct iio_channel *chans; + unsigned int num_chans; + struct adc_joystick_axis axes[] __counted_by(num_chans); +}; + +static int adc_joystick_invert(struct input_dev *dev, + unsigned int axis, int val) +{ + int min = input_abs_get_min(dev, axis); + int max = input_abs_get_max(dev, axis); + + return (max + min) - val; +} + +static void adc_joystick_poll(struct input_dev *input) +{ + struct adc_joystick *joy = input_get_drvdata(input); + int i, val, ret; + + for (i = 0; i < joy->num_chans; i++) { + ret = iio_read_channel_raw(&joy->chans[i], &val); + if (ret < 0) + return; + if (joy->axes[i].inverted) + val = adc_joystick_invert(input, i, val); + input_report_abs(input, joy->axes[i].code, val); + } + input_sync(input); +} + +static int adc_joystick_handle(const void *data, void *private) +{ + struct adc_joystick *joy = private; + enum iio_endian endianness; + int bytes, msb, val, idx, i; + const u16 *data_u16; + bool sign; + + bytes = joy->chans[0].channel->scan_type.storagebits >> 3; + + for (i = 0; i < joy->num_chans; ++i) { + idx = joy->chans[i].channel->scan_index; + endianness = joy->chans[i].channel->scan_type.endianness; + msb = joy->chans[i].channel->scan_type.realbits - 1; + sign = tolower(joy->chans[i].channel->scan_type.sign) == 's'; + + switch (bytes) { + case 1: + val = ((const u8 *)data)[idx]; + break; + case 2: + data_u16 = (const u16 *)data + idx; + + /* + * Data is aligned to the sample size by IIO core. + * Call `get_unaligned_xe16` to hide type casting. + */ + if (endianness == IIO_BE) + val = get_unaligned_be16(data_u16); + else if (endianness == IIO_LE) + val = get_unaligned_le16(data_u16); + else /* IIO_CPU */ + val = *data_u16; + break; + default: + return -EINVAL; + } + + val >>= joy->chans[i].channel->scan_type.shift; + if (sign) + val = sign_extend32(val, msb); + else + val &= GENMASK(msb, 0); + if (joy->axes[i].inverted) + val = adc_joystick_invert(joy->input, i, val); + input_report_abs(joy->input, joy->axes[i].code, val); + } + + input_sync(joy->input); + + return 0; +} + +static int adc_joystick_open(struct input_dev *dev) +{ + struct adc_joystick *joy = input_get_drvdata(dev); + struct device *devp = &dev->dev; + int ret; + + ret = iio_channel_start_all_cb(joy->buffer); + if (ret) + dev_err(devp, "Unable to start callback buffer: %d\n", ret); + + return ret; +} + +static void adc_joystick_close(struct input_dev *dev) +{ + struct adc_joystick *joy = input_get_drvdata(dev); + + iio_channel_stop_all_cb(joy->buffer); +} + +static void adc_joystick_cleanup(void *data) +{ + iio_channel_release_all_cb(data); +} + +static int adc_joystick_set_axes(struct device *dev, struct adc_joystick *joy) +{ + struct adc_joystick_axis *axes = joy->axes; + s32 range[2], fuzz, flat; + unsigned int num_axes; + int error, i; + + num_axes = device_get_child_node_count(dev); + if (!num_axes) { + dev_err(dev, "Unable to find child nodes\n"); + return -EINVAL; + } + + if (num_axes != joy->num_chans) { + dev_err(dev, "Got %d child nodes for %d channels\n", + num_axes, joy->num_chans); + return -EINVAL; + } + + device_for_each_child_node_scoped(dev, child) { + error = fwnode_property_read_u32(child, "reg", &i); + if (error) { + dev_err(dev, "reg invalid or missing\n"); + return error; + } + + if (i >= num_axes) { + dev_err(dev, "No matching axis for reg %d\n", i); + return -EINVAL; + } + + error = fwnode_property_read_u32(child, "linux,code", + &axes[i].code); + if (error) { + dev_err(dev, "linux,code invalid or missing\n"); + return error; + } + + error = fwnode_property_read_u32_array(child, "abs-range", + range, 2); + if (error) { + dev_err(dev, "abs-range invalid or missing\n"); + return error; + } + + if (range[0] > range[1]) { + dev_dbg(dev, "abs-axis %d inverted\n", i); + axes[i].inverted = true; + swap(range[0], range[1]); + } + + if (fwnode_property_read_u32(child, "abs-fuzz", &fuzz)) + fuzz = 0; + + if (fwnode_property_read_u32(child, "abs-flat", &flat)) + flat = 0; + + input_set_abs_params(joy->input, axes[i].code, + range[0], range[1], fuzz, flat); + } + + return 0; +} + + +static int adc_joystick_count_channels(struct device *dev, + const struct iio_channel *chans, + bool polled, + unsigned int *num_chans) +{ + int bits; + int i; + + /* + * Count how many channels we got. NULL terminated. + * Do not check the storage size if using polling. + */ + for (i = 0; chans[i].indio_dev; i++) { + if (polled) + continue; + bits = chans[i].channel->scan_type.storagebits; + if (!bits || bits > 16) { + dev_err(dev, "Unsupported channel storage size\n"); + return -EINVAL; + } + if (bits != chans[0].channel->scan_type.storagebits) { + dev_err(dev, "Channels must have equal storage size\n"); + return -EINVAL; + } + } + + *num_chans = i; + return 0; +} + +static int adc_joystick_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_channel *chans; + struct adc_joystick *joy; + struct input_dev *input; + unsigned int poll_interval = 0; + unsigned int num_chans; + int error; + + chans = devm_iio_channel_get_all(dev); + error = PTR_ERR_OR_ZERO(chans); + if (error) { + if (error != -EPROBE_DEFER) + dev_err(dev, "Unable to get IIO channels"); + return error; + } + + error = device_property_read_u32(dev, "poll-interval", &poll_interval); + if (error) { + /* -EINVAL means the property is absent. */ + if (error != -EINVAL) + return error; + } else if (poll_interval == 0) { + dev_err(dev, "Unable to get poll-interval\n"); + return -EINVAL; + } + + error = adc_joystick_count_channels(dev, chans, poll_interval != 0, + &num_chans); + if (error) + return error; + + joy = devm_kzalloc(dev, struct_size(joy, axes, num_chans), GFP_KERNEL); + if (!joy) + return -ENOMEM; + + joy->chans = chans; + joy->num_chans = num_chans; + + input = devm_input_allocate_device(dev); + if (!input) { + dev_err(dev, "Unable to allocate input device\n"); + return -ENOMEM; + } + + joy->input = input; + input->name = pdev->name; + input->id.bustype = BUS_HOST; + + error = adc_joystick_set_axes(dev, joy); + if (error) + return error; + + if (poll_interval != 0) { + input_setup_polling(input, adc_joystick_poll); + input_set_poll_interval(input, poll_interval); + } else { + input->open = adc_joystick_open; + input->close = adc_joystick_close; + + joy->buffer = iio_channel_get_all_cb(dev, adc_joystick_handle, + joy); + if (IS_ERR(joy->buffer)) { + dev_err(dev, "Unable to allocate callback buffer\n"); + return PTR_ERR(joy->buffer); + } + + error = devm_add_action_or_reset(dev, adc_joystick_cleanup, + joy->buffer); + if (error) { + dev_err(dev, "Unable to add action\n"); + return error; + } + } + + input_set_drvdata(input, joy); + + error = input_register_device(input); + if (error) { + dev_err(dev, "Unable to register input device\n"); + return error; + } + + return 0; +} + +static const struct of_device_id adc_joystick_of_match[] = { + { .compatible = "adc-joystick", }, + { } +}; +MODULE_DEVICE_TABLE(of, adc_joystick_of_match); + +static struct platform_driver adc_joystick_driver = { + .driver = { + .name = "adc-joystick", + .of_match_table = adc_joystick_of_match, + }, + .probe = adc_joystick_probe, +}; +module_platform_driver(adc_joystick_driver); + +MODULE_DESCRIPTION("Input driver for joysticks connected over ADC"); +MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/input/joystick/adi.c b/drivers/input/joystick/adi.c new file mode 100644 index 000000000..e07b71978 --- /dev/null +++ b/drivers/input/joystick/adi.c @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1998-2005 Vojtech Pavlik + */ + +/* + * Logitech ADI joystick family driver for Linux + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/gameport.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "Logitech ADI joystick family driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Times, array sizes, flags, ids. + */ + +#define ADI_MAX_START 200 /* Trigger to packet timeout [200us] */ +#define ADI_MAX_STROBE 40 /* Single bit timeout [40us] */ +#define ADI_INIT_DELAY 10 /* Delay after init packet [10ms] */ +#define ADI_DATA_DELAY 4 /* Delay after data packet [4ms] */ + +#define ADI_MAX_LENGTH 256 +#define ADI_MIN_LENGTH 8 +#define ADI_MIN_LEN_LENGTH 10 +#define ADI_MIN_ID_LENGTH 66 +#define ADI_MAX_NAME_LENGTH 64 +#define ADI_MAX_CNAME_LENGTH 16 +#define ADI_MAX_PHYS_LENGTH 64 + +#define ADI_FLAG_HAT 0x04 +#define ADI_FLAG_10BIT 0x08 + +#define ADI_ID_TPD 0x01 +#define ADI_ID_WGP 0x06 +#define ADI_ID_WGPE 0x08 +#define ADI_ID_MAX 0x0a + +/* + * Names, buttons, axes ... + */ + +static char *adi_names[] = { "WingMan Extreme Digital", "ThunderPad Digital", "SideCar", "CyberMan 2", + "WingMan Interceptor", "WingMan Formula", "WingMan GamePad", + "WingMan Extreme Digital 3D", "WingMan GamePad Extreme", + "WingMan GamePad USB", "Unknown Device %#x" }; + +static char adi_wmgpe_abs[] = { ABS_X, ABS_Y, ABS_HAT0X, ABS_HAT0Y }; +static char adi_wmi_abs[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y }; +static char adi_wmed3d_abs[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RZ, ABS_HAT0X, ABS_HAT0Y }; +static char adi_cm2_abs[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; +static char adi_wmf_abs[] = { ABS_WHEEL, ABS_GAS, ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y }; + +static short adi_wmgpe_key[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START, BTN_MODE, BTN_SELECT }; +static short adi_wmi_key[] = { BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_EXTRA }; +static short adi_wmed3d_key[] = { BTN_TRIGGER, BTN_THUMB, BTN_THUMB2, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2 }; +static short adi_cm2_key[] = { BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7, BTN_8 }; + +static char* adi_abs[] = { adi_wmi_abs, adi_wmgpe_abs, adi_wmf_abs, adi_cm2_abs, adi_wmi_abs, adi_wmf_abs, + adi_wmgpe_abs, adi_wmed3d_abs, adi_wmgpe_abs, adi_wmgpe_abs, adi_wmi_abs }; + +static short* adi_key[] = { adi_wmi_key, adi_wmgpe_key, adi_cm2_key, adi_cm2_key, adi_wmi_key, adi_cm2_key, + adi_wmgpe_key, adi_wmed3d_key, adi_wmgpe_key, adi_wmgpe_key, adi_wmi_key }; + +/* + * Hat to axis conversion arrays. + */ + +static struct { + int x; + int y; +} adi_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + +/* + * Per-port information. + */ + +struct adi { + struct input_dev *dev; + int length; + int ret; + int idx; + unsigned char id; + char buttons; + char axes10; + char axes8; + signed char pad; + char hats; + char *abs; + short *key; + char name[ADI_MAX_NAME_LENGTH]; + char cname[ADI_MAX_CNAME_LENGTH]; + char phys[ADI_MAX_PHYS_LENGTH]; + unsigned char data[ADI_MAX_LENGTH]; +}; + +struct adi_port { + struct gameport *gameport; + struct adi adi[2]; + int bad; + int reads; +}; + +/* + * adi_read_packet() reads a Logitech ADI packet. + */ + +static void adi_read_packet(struct adi_port *port) +{ + struct adi *adi = port->adi; + struct gameport *gameport = port->gameport; + unsigned char u, v, w, x; + int t[2], s[2], i; + unsigned long flags; + + for (i = 0; i < 2; i++) { + adi[i].ret = -1; + t[i] = gameport_time(gameport, ADI_MAX_START); + s[i] = 0; + } + + local_irq_save(flags); + + gameport_trigger(gameport); + v = gameport_read(gameport); + + do { + u = v; + w = u ^ (v = x = gameport_read(gameport)); + for (i = 0; i < 2; i++, w >>= 2, x >>= 2) { + t[i]--; + if ((w & 0x30) && s[i]) { + if ((w & 0x30) < 0x30 && adi[i].ret < ADI_MAX_LENGTH && t[i] > 0) { + adi[i].data[++adi[i].ret] = w; + t[i] = gameport_time(gameport, ADI_MAX_STROBE); + } else t[i] = 0; + } else if (!(x & 0x30)) s[i] = 1; + } + } while (t[0] > 0 || t[1] > 0); + + local_irq_restore(flags); + + return; +} + +/* + * adi_move_bits() detects a possible 2-stream mode, and moves + * the bits accordingly. + */ + +static void adi_move_bits(struct adi_port *port, int length) +{ + int i; + struct adi *adi = port->adi; + + adi[0].idx = adi[1].idx = 0; + + if (adi[0].ret <= 0 || adi[1].ret <= 0) return; + if (adi[0].data[0] & 0x20 || ~adi[1].data[0] & 0x20) return; + + for (i = 1; i <= adi[1].ret; i++) + adi[0].data[((length - 1) >> 1) + i + 1] = adi[1].data[i]; + + adi[0].ret += adi[1].ret; + adi[1].ret = -1; +} + +/* + * adi_get_bits() gathers bits from the data packet. + */ + +static inline int adi_get_bits(struct adi *adi, int count) +{ + int bits = 0; + int i; + if ((adi->idx += count) > adi->ret) return 0; + for (i = 0; i < count; i++) + bits |= ((adi->data[adi->idx - i] >> 5) & 1) << i; + return bits; +} + +/* + * adi_decode() decodes Logitech joystick data into input events. + */ + +static int adi_decode(struct adi *adi) +{ + struct input_dev *dev = adi->dev; + char *abs = adi->abs; + short *key = adi->key; + int i, t; + + if (adi->ret < adi->length || adi->id != (adi_get_bits(adi, 4) | (adi_get_bits(adi, 4) << 4))) + return -1; + + for (i = 0; i < adi->axes10; i++) + input_report_abs(dev, *abs++, adi_get_bits(adi, 10)); + + for (i = 0; i < adi->axes8; i++) + input_report_abs(dev, *abs++, adi_get_bits(adi, 8)); + + for (i = 0; i < adi->buttons && i < 63; i++) { + if (i == adi->pad) { + t = adi_get_bits(adi, 4); + input_report_abs(dev, *abs++, ((t >> 2) & 1) - ( t & 1)); + input_report_abs(dev, *abs++, ((t >> 1) & 1) - ((t >> 3) & 1)); + } + input_report_key(dev, *key++, adi_get_bits(adi, 1)); + } + + for (i = 0; i < adi->hats; i++) { + if ((t = adi_get_bits(adi, 4)) > 8) t = 0; + input_report_abs(dev, *abs++, adi_hat_to_axis[t].x); + input_report_abs(dev, *abs++, adi_hat_to_axis[t].y); + } + + for (i = 63; i < adi->buttons; i++) + input_report_key(dev, *key++, adi_get_bits(adi, 1)); + + input_sync(dev); + + return 0; +} + +/* + * adi_read() reads the data packet and decodes it. + */ + +static int adi_read(struct adi_port *port) +{ + int i; + int result = 0; + + adi_read_packet(port); + adi_move_bits(port, port->adi[0].length); + + for (i = 0; i < 2; i++) + if (port->adi[i].length) + result |= adi_decode(port->adi + i); + + return result; +} + +/* + * adi_poll() repeatedly polls the Logitech joysticks. + */ + +static void adi_poll(struct gameport *gameport) +{ + struct adi_port *port = gameport_get_drvdata(gameport); + + port->bad -= adi_read(port); + port->reads++; +} + +/* + * adi_open() is a callback from the input open routine. + */ + +static int adi_open(struct input_dev *dev) +{ + struct adi_port *port = input_get_drvdata(dev); + + gameport_start_polling(port->gameport); + return 0; +} + +/* + * adi_close() is a callback from the input close routine. + */ + +static void adi_close(struct input_dev *dev) +{ + struct adi_port *port = input_get_drvdata(dev); + + gameport_stop_polling(port->gameport); +} + +/* + * adi_init_digital() sends a trigger & delay sequence + * to reset and initialize a Logitech joystick into digital mode. + */ + +static void adi_init_digital(struct gameport *gameport) +{ + static const int seq[] = { 4, -2, -3, 10, -6, -11, -7, -9, 11, 0 }; + int i; + + for (i = 0; seq[i]; i++) { + gameport_trigger(gameport); + if (seq[i] > 0) + msleep(seq[i]); + if (seq[i] < 0) { + mdelay(-seq[i]); + udelay(-seq[i]*14); /* It looks like mdelay() is off by approx 1.4% */ + } + } +} + +static void adi_id_decode(struct adi *adi, struct adi_port *port) +{ + int i, t; + + if (adi->ret < ADI_MIN_ID_LENGTH) /* Minimum ID packet length */ + return; + + if (adi->ret < (t = adi_get_bits(adi, 10))) { + printk(KERN_WARNING "adi: Short ID packet: reported: %d != read: %d\n", t, adi->ret); + return; + } + + adi->id = adi_get_bits(adi, 4) | (adi_get_bits(adi, 4) << 4); + + if ((t = adi_get_bits(adi, 4)) & ADI_FLAG_HAT) adi->hats++; + + adi->length = adi_get_bits(adi, 10); + + if (adi->length >= ADI_MAX_LENGTH || adi->length < ADI_MIN_LENGTH) { + printk(KERN_WARNING "adi: Bad data packet length (%d).\n", adi->length); + adi->length = 0; + return; + } + + adi->axes8 = adi_get_bits(adi, 4); + adi->buttons = adi_get_bits(adi, 6); + + if (adi_get_bits(adi, 6) != 8 && adi->hats) { + printk(KERN_WARNING "adi: Other than 8-dir POVs not supported yet.\n"); + adi->length = 0; + return; + } + + adi->buttons += adi_get_bits(adi, 6); + adi->hats += adi_get_bits(adi, 4); + + i = adi_get_bits(adi, 4); + + if (t & ADI_FLAG_10BIT) { + adi->axes10 = adi->axes8 - i; + adi->axes8 = i; + } + + t = adi_get_bits(adi, 4); + + for (i = 0; i < t; i++) + adi->cname[i] = adi_get_bits(adi, 8); + adi->cname[i] = 0; + + t = 8 + adi->buttons + adi->axes10 * 10 + adi->axes8 * 8 + adi->hats * 4; + if (adi->length != t && adi->length != t + (t & 1)) { + printk(KERN_WARNING "adi: Expected length %d != data length %d\n", t, adi->length); + adi->length = 0; + return; + } + + switch (adi->id) { + case ADI_ID_TPD: + adi->pad = 4; + adi->buttons -= 4; + break; + case ADI_ID_WGP: + adi->pad = 0; + adi->buttons -= 4; + break; + default: + adi->pad = -1; + break; + } +} + +static int adi_init_input(struct adi *adi, struct adi_port *port, int half) +{ + struct input_dev *input_dev; + char buf[ADI_MAX_NAME_LENGTH]; + int i, t; + + adi->dev = input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + t = adi->id < ADI_ID_MAX ? adi->id : ADI_ID_MAX; + + snprintf(buf, ADI_MAX_PHYS_LENGTH, adi_names[t], adi->id); + snprintf(adi->name, ADI_MAX_NAME_LENGTH, "Logitech %s [%s]", buf, adi->cname); + snprintf(adi->phys, ADI_MAX_PHYS_LENGTH, "%s/input%d", port->gameport->phys, half); + + adi->abs = adi_abs[t]; + adi->key = adi_key[t]; + + input_dev->name = adi->name; + input_dev->phys = adi->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_LOGITECH; + input_dev->id.product = adi->id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &port->gameport->dev; + + input_set_drvdata(input_dev, port); + + input_dev->open = adi_open; + input_dev->close = adi_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < adi->axes10 + adi->axes8 + (adi->hats + (adi->pad != -1)) * 2; i++) + set_bit(adi->abs[i], input_dev->absbit); + + for (i = 0; i < adi->buttons; i++) + set_bit(adi->key[i], input_dev->keybit); + + return 0; +} + +static void adi_init_center(struct adi *adi) +{ + int i, t, x; + + if (!adi->length) + return; + + for (i = 0; i < adi->axes10 + adi->axes8 + (adi->hats + (adi->pad != -1)) * 2; i++) { + + t = adi->abs[i]; + x = input_abs_get_val(adi->dev, t); + + if (t == ABS_THROTTLE || t == ABS_RUDDER || adi->id == ADI_ID_WGPE) + x = i < adi->axes10 ? 512 : 128; + + if (i < adi->axes10) + input_set_abs_params(adi->dev, t, 64, x * 2 - 64, 2, 16); + else if (i < adi->axes10 + adi->axes8) + input_set_abs_params(adi->dev, t, 48, x * 2 - 48, 1, 16); + else + input_set_abs_params(adi->dev, t, -1, 1, 0, 0); + } +} + +/* + * adi_connect() probes for Logitech ADI joysticks. + */ + +static int adi_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct adi_port *port; + int i; + int err; + + port = kzalloc_obj(*port); + if (!port) + return -ENOMEM; + + port->gameport = gameport; + + gameport_set_drvdata(gameport, port); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + adi_init_digital(gameport); + adi_read_packet(port); + + if (port->adi[0].ret >= ADI_MIN_LEN_LENGTH) + adi_move_bits(port, adi_get_bits(port->adi, 10)); + + for (i = 0; i < 2; i++) { + adi_id_decode(port->adi + i, port); + + if (!port->adi[i].length) + continue; + + err = adi_init_input(port->adi + i, port, i); + if (err) + goto fail2; + } + + if (!port->adi[0].length && !port->adi[1].length) { + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, adi_poll); + gameport_set_poll_interval(gameport, 20); + + msleep(ADI_INIT_DELAY); + if (adi_read(port)) { + msleep(ADI_DATA_DELAY); + adi_read(port); + } + + for (i = 0; i < 2; i++) + if (port->adi[i].length > 0) { + adi_init_center(port->adi + i); + err = input_register_device(port->adi[i].dev); + if (err) + goto fail3; + } + + return 0; + + fail3: while (--i >= 0) { + if (port->adi[i].length > 0) { + input_unregister_device(port->adi[i].dev); + port->adi[i].dev = NULL; + } + } + fail2: for (i = 0; i < 2; i++) + input_free_device(port->adi[i].dev); + gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(port); + return err; +} + +static void adi_disconnect(struct gameport *gameport) +{ + int i; + struct adi_port *port = gameport_get_drvdata(gameport); + + for (i = 0; i < 2; i++) + if (port->adi[i].length > 0) + input_unregister_device(port->adi[i].dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(port); +} + +static struct gameport_driver adi_drv = { + .driver = { + .name = "adi", + }, + .description = DRIVER_DESC, + .connect = adi_connect, + .disconnect = adi_disconnect, +}; + +module_gameport_driver(adi_drv); diff --git a/drivers/input/joystick/amijoy.c b/drivers/input/joystick/amijoy.c new file mode 100644 index 000000000..3752dc2a2 --- /dev/null +++ b/drivers/input/joystick/amijoy.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * Driver for Amiga joysticks for Linux/m68k + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> + +#include <asm/amigahw.h> +#include <asm/amigaints.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Driver for Amiga joysticks"); +MODULE_LICENSE("GPL"); + +static int amijoy[2] = { 0, 1 }; +module_param_array_named(map, amijoy, uint, NULL, 0); +MODULE_PARM_DESC(map, "Map of attached joysticks in form of <a>,<b> (default is 0,1)"); + +static int amijoy_used; +static DEFINE_MUTEX(amijoy_mutex); +static struct input_dev *amijoy_dev[2]; +static char *amijoy_phys[2] = { "amijoy/input0", "amijoy/input1" }; + +static irqreturn_t amijoy_interrupt(int irq, void *dummy) +{ + int i, data = 0, button = 0; + + for (i = 0; i < 2; i++) + if (amijoy[i]) { + + switch (i) { + case 0: data = ~amiga_custom.joy0dat; button = (~ciaa.pra >> 6) & 1; break; + case 1: data = ~amiga_custom.joy1dat; button = (~ciaa.pra >> 7) & 1; break; + } + + input_report_key(amijoy_dev[i], BTN_TRIGGER, button); + + input_report_abs(amijoy_dev[i], ABS_X, ((data >> 1) & 1) - ((data >> 9) & 1)); + data = ~(data ^ (data << 1)); + input_report_abs(amijoy_dev[i], ABS_Y, ((data >> 1) & 1) - ((data >> 9) & 1)); + + input_sync(amijoy_dev[i]); + } + return IRQ_HANDLED; +} + +static int amijoy_open(struct input_dev *dev) +{ + int err; + + err = mutex_lock_interruptible(&amijoy_mutex); + if (err) + return err; + + if (!amijoy_used && request_irq(IRQ_AMIGA_VERTB, amijoy_interrupt, 0, "amijoy", amijoy_interrupt)) { + printk(KERN_ERR "amijoy.c: Can't allocate irq %d\n", IRQ_AMIGA_VERTB); + err = -EBUSY; + goto out; + } + + amijoy_used++; +out: + mutex_unlock(&amijoy_mutex); + return err; +} + +static void amijoy_close(struct input_dev *dev) +{ + mutex_lock(&amijoy_mutex); + if (!--amijoy_used) + free_irq(IRQ_AMIGA_VERTB, amijoy_interrupt); + mutex_unlock(&amijoy_mutex); +} + +static int __init amijoy_init(void) +{ + int i, j; + int err; + + if (!MACH_IS_AMIGA) + return -ENODEV; + + for (i = 0; i < 2; i++) { + if (!amijoy[i]) + continue; + + amijoy_dev[i] = input_allocate_device(); + if (!amijoy_dev[i]) { + err = -ENOMEM; + goto fail; + } + + if (!request_mem_region(CUSTOM_PHYSADDR + 10 + i * 2, 2, "amijoy [Denise]")) { + input_free_device(amijoy_dev[i]); + err = -EBUSY; + goto fail; + } + + amijoy_dev[i]->name = "Amiga joystick"; + amijoy_dev[i]->phys = amijoy_phys[i]; + amijoy_dev[i]->id.bustype = BUS_AMIGA; + amijoy_dev[i]->id.vendor = 0x0001; + amijoy_dev[i]->id.product = 0x0003; + amijoy_dev[i]->id.version = 0x0100; + + amijoy_dev[i]->open = amijoy_open; + amijoy_dev[i]->close = amijoy_close; + + amijoy_dev[i]->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + amijoy_dev[i]->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y); + amijoy_dev[i]->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + for (j = 0; j < 2; j++) { + input_set_abs_params(amijoy_dev[i], ABS_X + j, + -1, 1, 0, 0); + } + + err = input_register_device(amijoy_dev[i]); + if (err) { + input_free_device(amijoy_dev[i]); + goto fail; + } + } + return 0; + + fail: while (--i >= 0) + if (amijoy[i]) { + input_unregister_device(amijoy_dev[i]); + release_mem_region(CUSTOM_PHYSADDR + 10 + i * 2, 2); + } + return err; +} + +static void __exit amijoy_exit(void) +{ + int i; + + for (i = 0; i < 2; i++) + if (amijoy[i]) { + input_unregister_device(amijoy_dev[i]); + release_mem_region(CUSTOM_PHYSADDR + 10 + i * 2, 2); + } +} + +module_init(amijoy_init); +module_exit(amijoy_exit); diff --git a/drivers/input/joystick/analog.c b/drivers/input/joystick/analog.c new file mode 100644 index 000000000..b6f7bce1c --- /dev/null +++ b/drivers/input/joystick/analog.c @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1996-2001 Vojtech Pavlik + */ + +/* + * Analog joystick and gamepad driver for Linux + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/gameport.h> +#include <linux/jiffies.h> +#include <linux/seq_buf.h> +#include <linux/timex.h> +#include <linux/timekeeping.h> + +#define DRIVER_DESC "Analog joystick and gamepad driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Option parsing. + */ + +#define ANALOG_PORTS 16 + +static char *js[ANALOG_PORTS]; +static unsigned int js_nargs; +static int analog_options[ANALOG_PORTS]; +module_param_array_named(map, js, charp, &js_nargs, 0); +MODULE_PARM_DESC(map, "Describes analog joysticks type/capabilities"); + +/* + * Times, feature definitions. + */ + +#define ANALOG_RUDDER 0x00004 +#define ANALOG_THROTTLE 0x00008 +#define ANALOG_AXES_STD 0x0000f +#define ANALOG_BTNS_STD 0x000f0 + +#define ANALOG_BTNS_CHF 0x00100 +#define ANALOG_HAT1_CHF 0x00200 +#define ANALOG_HAT2_CHF 0x00400 +#define ANALOG_HAT_FCS 0x00800 +#define ANALOG_HATS_ALL 0x00e00 +#define ANALOG_BTN_TL 0x01000 +#define ANALOG_BTN_TR 0x02000 +#define ANALOG_BTN_TL2 0x04000 +#define ANALOG_BTN_TR2 0x08000 +#define ANALOG_BTNS_TLR 0x03000 +#define ANALOG_BTNS_TLR2 0x0c000 +#define ANALOG_BTNS_GAMEPAD 0x0f000 + +#define ANALOG_HBTN_CHF 0x10000 +#define ANALOG_ANY_CHF 0x10700 +#define ANALOG_SAITEK 0x20000 +#define ANALOG_EXTENSIONS 0x7ff00 +#define ANALOG_GAMEPAD 0x80000 + +#define ANALOG_MAX_TIME 3 /* 3 ms */ +#define ANALOG_LOOP_TIME 2000 /* 2 * loop */ +#define ANALOG_SAITEK_DELAY 200 /* 200 us */ +#define ANALOG_SAITEK_TIME 2000 /* 2000 us */ +#define ANALOG_AXIS_TIME 2 /* 2 * refresh */ +#define ANALOG_INIT_RETRIES 8 /* 8 times */ +#define ANALOG_FUZZ_BITS 2 /* 2 bit more */ +#define ANALOG_FUZZ_MAGIC 36 /* 36 u*ms/loop */ + +#define ANALOG_MAX_NAME_LENGTH 128 +#define ANALOG_MAX_PHYS_LENGTH 32 + +static short analog_axes[] = { ABS_X, ABS_Y, ABS_RUDDER, ABS_THROTTLE }; +static short analog_hats[] = { ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y }; +static short analog_pads[] = { BTN_Y, BTN_Z, BTN_TL, BTN_TR }; +static short analog_exts[] = { ANALOG_HAT1_CHF, ANALOG_HAT2_CHF, ANALOG_HAT_FCS }; +static short analog_pad_btn[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_TL2, BTN_TR2, BTN_SELECT, BTN_START, BTN_MODE, BTN_BASE }; +static short analog_joy_btn[] = { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, + BTN_BASE3, BTN_BASE4, BTN_BASE5, BTN_BASE6 }; + +static unsigned char analog_chf[] = { 0xf, 0x0, 0x1, 0x9, 0x2, 0x4, 0xc, 0x8, 0x3, 0x5, 0xb, 0x7, 0xd, 0xe, 0xa, 0x6 }; + +struct analog { + struct input_dev *dev; + int mask; + short *buttons; + char name[ANALOG_MAX_NAME_LENGTH]; + char phys[ANALOG_MAX_PHYS_LENGTH]; +}; + +struct analog_port { + struct gameport *gameport; + struct analog analog[2]; + unsigned char mask; + char saitek; + char cooked; + int bads; + int reads; + int loop; + int fuzz; + int axes[4]; + int buttons; + int initial[4]; + int axtime; +}; + +/* + * analog_decode() decodes analog joystick data and reports input events. + */ + +static void analog_decode(struct analog *analog, int *axes, int *initial, int buttons) +{ + struct input_dev *dev = analog->dev; + int i, j; + + if (analog->mask & ANALOG_HAT_FCS) + for (i = 0; i < 4; i++) + if (axes[3] < ((initial[3] * ((i << 1) + 1)) >> 3)) { + buttons |= 1 << (i + 14); + break; + } + + for (i = j = 0; i < 6; i++) + if (analog->mask & (0x10 << i)) + input_report_key(dev, analog->buttons[j++], (buttons >> i) & 1); + + if (analog->mask & ANALOG_HBTN_CHF) + for (i = 0; i < 4; i++) + input_report_key(dev, analog->buttons[j++], (buttons >> (i + 10)) & 1); + + if (analog->mask & ANALOG_BTN_TL) + input_report_key(dev, analog_pads[0], axes[2] < (initial[2] >> 1)); + if (analog->mask & ANALOG_BTN_TR) + input_report_key(dev, analog_pads[1], axes[3] < (initial[3] >> 1)); + if (analog->mask & ANALOG_BTN_TL2) + input_report_key(dev, analog_pads[2], axes[2] > (initial[2] + (initial[2] >> 1))); + if (analog->mask & ANALOG_BTN_TR2) + input_report_key(dev, analog_pads[3], axes[3] > (initial[3] + (initial[3] >> 1))); + + for (i = j = 0; i < 4; i++) + if (analog->mask & (1 << i)) + input_report_abs(dev, analog_axes[j++], axes[i]); + + for (i = j = 0; i < 3; i++) + if (analog->mask & analog_exts[i]) { + input_report_abs(dev, analog_hats[j++], + ((buttons >> ((i << 2) + 7)) & 1) - ((buttons >> ((i << 2) + 9)) & 1)); + input_report_abs(dev, analog_hats[j++], + ((buttons >> ((i << 2) + 8)) & 1) - ((buttons >> ((i << 2) + 6)) & 1)); + } + + input_sync(dev); +} + +/* + * analog_cooked_read() reads analog joystick data. + */ + +static int analog_cooked_read(struct analog_port *port) +{ + struct gameport *gameport = port->gameport; + ktime_t time[4], start, loop, now; + unsigned int loopout, timeout; + unsigned char data[4], this, last; + unsigned long flags; + int i, j; + + loopout = (ANALOG_LOOP_TIME * port->loop) / 1000; + timeout = ANALOG_MAX_TIME * NSEC_PER_MSEC; + + local_irq_save(flags); + gameport_trigger(gameport); + now = ktime_get(); + local_irq_restore(flags); + + start = now; + this = port->mask; + i = 0; + + do { + loop = now; + last = this; + + local_irq_disable(); + this = gameport_read(gameport) & port->mask; + now = ktime_get(); + local_irq_restore(flags); + + if ((last ^ this) && (ktime_sub(now, loop) < loopout)) { + data[i] = last ^ this; + time[i] = now; + i++; + } + + } while (this && (i < 4) && (ktime_sub(now, start) < timeout)); + + this <<= 4; + + for (--i; i >= 0; i--) { + this |= data[i]; + for (j = 0; j < 4; j++) + if (data[i] & (1 << j)) + port->axes[j] = ((u32)ktime_sub(time[i], start) << ANALOG_FUZZ_BITS) / port->loop; + } + + return -(this != port->mask); +} + +static int analog_button_read(struct analog_port *port, char saitek, char chf) +{ + unsigned char u; + int t = 1, i = 0; + int strobe = gameport_time(port->gameport, ANALOG_SAITEK_TIME); + + u = gameport_read(port->gameport); + + if (!chf) { + port->buttons = (~u >> 4) & 0xf; + return 0; + } + + port->buttons = 0; + + while ((~u & 0xf0) && (i < 16) && t) { + port->buttons |= 1 << analog_chf[(~u >> 4) & 0xf]; + if (!saitek) return 0; + udelay(ANALOG_SAITEK_DELAY); + t = strobe; + gameport_trigger(port->gameport); + while (((u = gameport_read(port->gameport)) & port->mask) && t) t--; + i++; + } + + return -(!t || (i == 16)); +} + +/* + * analog_poll() repeatedly polls the Analog joysticks. + */ + +static void analog_poll(struct gameport *gameport) +{ + struct analog_port *port = gameport_get_drvdata(gameport); + int i; + + char saitek = !!(port->analog[0].mask & ANALOG_SAITEK); + char chf = !!(port->analog[0].mask & ANALOG_ANY_CHF); + + if (port->cooked) { + port->bads -= gameport_cooked_read(port->gameport, port->axes, &port->buttons); + if (chf) + port->buttons = port->buttons ? (1 << analog_chf[port->buttons]) : 0; + port->reads++; + } else { + if (!port->axtime--) { + port->bads -= analog_cooked_read(port); + port->bads -= analog_button_read(port, saitek, chf); + port->reads++; + port->axtime = ANALOG_AXIS_TIME - 1; + } else { + if (!saitek) + analog_button_read(port, saitek, chf); + } + } + + for (i = 0; i < 2; i++) + if (port->analog[i].mask) + analog_decode(port->analog + i, port->axes, port->initial, port->buttons); +} + +/* + * analog_open() is a callback from the input open routine. + */ + +static int analog_open(struct input_dev *dev) +{ + struct analog_port *port = input_get_drvdata(dev); + + gameport_start_polling(port->gameport); + return 0; +} + +/* + * analog_close() is a callback from the input close routine. + */ + +static void analog_close(struct input_dev *dev) +{ + struct analog_port *port = input_get_drvdata(dev); + + gameport_stop_polling(port->gameport); +} + +/* + * analog_calibrate_timer() calibrates the timer and computes loop + * and timeout values for a joystick port. + */ + +static void analog_calibrate_timer(struct analog_port *port) +{ + struct gameport *gameport = port->gameport; + unsigned int i, t, tx; + ktime_t t1, t2, t3; + unsigned long flags; + + tx = ~0; + + for (i = 0; i < 50; i++) { + local_irq_save(flags); + t1 = ktime_get(); + for (t = 0; t < 50; t++) { + gameport_read(gameport); + t2 = ktime_get(); + } + t3 = ktime_get(); + local_irq_restore(flags); + udelay(i); + t = ktime_sub(t2, t1) - ktime_sub(t3, t2); + if (t < tx) tx = t; + } + + port->loop = tx / 50; +} + +/* + * analog_name() constructs a name for an analog joystick. + */ + +static void analog_name(struct analog *analog) +{ + struct seq_buf s; + + seq_buf_init(&s, analog->name, sizeof(analog->name)); + seq_buf_printf(&s, "Analog %d-axis %d-button", + hweight8(analog->mask & ANALOG_AXES_STD), + hweight8(analog->mask & ANALOG_BTNS_STD) + !!(analog->mask & ANALOG_BTNS_CHF) * 2 + + hweight16(analog->mask & ANALOG_BTNS_GAMEPAD) + !!(analog->mask & ANALOG_HBTN_CHF) * 4); + + if (analog->mask & ANALOG_HATS_ALL) + seq_buf_printf(&s, " %d-hat", + hweight16(analog->mask & ANALOG_HATS_ALL)); + + if (analog->mask & ANALOG_HAT_FCS) + seq_buf_printf(&s, " FCS"); + if (analog->mask & ANALOG_ANY_CHF) + seq_buf_printf(&s, (analog->mask & ANALOG_SAITEK) ? " Saitek" : " CHF"); + + seq_buf_printf(&s, (analog->mask & ANALOG_GAMEPAD) ? " gamepad" : " joystick"); +} + +/* + * analog_init_device() + */ + +static int analog_init_device(struct analog_port *port, struct analog *analog, int index) +{ + struct input_dev *input_dev; + int i, j, t, v, w, x, y, z; + int error; + + analog_name(analog); + snprintf(analog->phys, sizeof(analog->phys), + "%s/input%d", port->gameport->phys, index); + analog->buttons = (analog->mask & ANALOG_GAMEPAD) ? analog_pad_btn : analog_joy_btn; + + analog->dev = input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = analog->name; + input_dev->phys = analog->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_ANALOG; + input_dev->id.product = analog->mask >> 4; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &port->gameport->dev; + + input_set_drvdata(input_dev, port); + + input_dev->open = analog_open; + input_dev->close = analog_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = j = 0; i < 4; i++) + if (analog->mask & (1 << i)) { + + t = analog_axes[j]; + x = port->axes[i]; + y = (port->axes[0] + port->axes[1]) >> 1; + z = y - port->axes[i]; + z = z > 0 ? z : -z; + v = (x >> 3); + w = (x >> 3); + + if ((i == 2 || i == 3) && (j == 2 || j == 3) && (z > (y >> 3))) + x = y; + + if (analog->mask & ANALOG_SAITEK) { + if (i == 2) x = port->axes[i]; + v = x - (x >> 2); + w = (x >> 4); + } + + input_set_abs_params(input_dev, t, v, (x << 1) - v, port->fuzz, w); + j++; + } + + for (i = j = 0; i < 3; i++) + if (analog->mask & analog_exts[i]) + for (x = 0; x < 2; x++) { + t = analog_hats[j++]; + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + } + + for (i = j = 0; i < 4; i++) + if (analog->mask & (0x10 << i)) + set_bit(analog->buttons[j++], input_dev->keybit); + + if (analog->mask & ANALOG_BTNS_CHF) + for (i = 0; i < 2; i++) + set_bit(analog->buttons[j++], input_dev->keybit); + + if (analog->mask & ANALOG_HBTN_CHF) + for (i = 0; i < 4; i++) + set_bit(analog->buttons[j++], input_dev->keybit); + + for (i = 0; i < 4; i++) + if (analog->mask & (ANALOG_BTN_TL << i)) + set_bit(analog_pads[i], input_dev->keybit); + + analog_decode(analog, port->axes, port->initial, port->buttons); + + error = input_register_device(analog->dev); + if (error) { + input_free_device(analog->dev); + return error; + } + + return 0; +} + +/* + * analog_init_devices() sets up device-specific values and registers the input devices. + */ + +static int analog_init_masks(struct analog_port *port) +{ + int i; + struct analog *analog = port->analog; + int max[4]; + + if (!port->mask) + return -1; + + if ((port->mask & 3) != 3 && port->mask != 0xc) { + printk(KERN_WARNING "analog.c: Unknown joystick device found " + "(data=%#x, %s), probably not analog joystick.\n", + port->mask, port->gameport->phys); + return -1; + } + + + i = analog_options[0]; /* FIXME !!! - need to specify options for different ports */ + + analog[0].mask = i & 0xfffff; + + analog[0].mask &= ~(ANALOG_AXES_STD | ANALOG_HAT_FCS | ANALOG_BTNS_GAMEPAD) + | port->mask | ((port->mask << 8) & ANALOG_HAT_FCS) + | ((port->mask << 10) & ANALOG_BTNS_TLR) | ((port->mask << 12) & ANALOG_BTNS_TLR2); + + analog[0].mask &= ~(ANALOG_HAT2_CHF) + | ((analog[0].mask & ANALOG_HBTN_CHF) ? 0 : ANALOG_HAT2_CHF); + + analog[0].mask &= ~(ANALOG_THROTTLE | ANALOG_BTN_TR | ANALOG_BTN_TR2) + | ((~analog[0].mask & ANALOG_HAT_FCS) >> 8) + | ((~analog[0].mask & ANALOG_HAT_FCS) << 2) + | ((~analog[0].mask & ANALOG_HAT_FCS) << 4); + + analog[0].mask &= ~(ANALOG_THROTTLE | ANALOG_RUDDER) + | (((~analog[0].mask & ANALOG_BTNS_TLR ) >> 10) + & ((~analog[0].mask & ANALOG_BTNS_TLR2) >> 12)); + + analog[1].mask = ((i >> 20) & 0xff) | ((i >> 12) & 0xf0000); + + analog[1].mask &= (analog[0].mask & ANALOG_EXTENSIONS) ? ANALOG_GAMEPAD + : (((ANALOG_BTNS_STD | port->mask) & ~analog[0].mask) | ANALOG_GAMEPAD); + + if (port->cooked) { + + for (i = 0; i < 4; i++) max[i] = port->axes[i] << 1; + + if ((analog[0].mask & 0x7) == 0x7) max[2] = (max[0] + max[1]) >> 1; + if ((analog[0].mask & 0xb) == 0xb) max[3] = (max[0] + max[1]) >> 1; + if ((analog[0].mask & ANALOG_BTN_TL) && !(analog[0].mask & ANALOG_BTN_TL2)) max[2] >>= 1; + if ((analog[0].mask & ANALOG_BTN_TR) && !(analog[0].mask & ANALOG_BTN_TR2)) max[3] >>= 1; + if ((analog[0].mask & ANALOG_HAT_FCS)) max[3] >>= 1; + + gameport_calibrate(port->gameport, port->axes, max); + } + + for (i = 0; i < 4; i++) + port->initial[i] = port->axes[i]; + + return -!(analog[0].mask || analog[1].mask); +} + +static int analog_init_port(struct gameport *gameport, struct gameport_driver *drv, struct analog_port *port) +{ + int i, t, u, v; + + port->gameport = gameport; + + gameport_set_drvdata(gameport, port); + + if (!gameport_open(gameport, drv, GAMEPORT_MODE_RAW)) { + + analog_calibrate_timer(port); + + gameport_trigger(gameport); + t = gameport_read(gameport); + msleep(ANALOG_MAX_TIME); + port->mask = (gameport_read(gameport) ^ t) & t & 0xf; + port->fuzz = (NSEC_PER_MSEC * ANALOG_FUZZ_MAGIC) / port->loop / 1000 + ANALOG_FUZZ_BITS; + + for (i = 0; i < ANALOG_INIT_RETRIES; i++) { + if (!analog_cooked_read(port)) + break; + msleep(ANALOG_MAX_TIME); + } + + u = v = 0; + + msleep(ANALOG_MAX_TIME); + t = gameport_time(gameport, ANALOG_MAX_TIME * 1000); + gameport_trigger(gameport); + while ((gameport_read(port->gameport) & port->mask) && (u < t)) + u++; + udelay(ANALOG_SAITEK_DELAY); + t = gameport_time(gameport, ANALOG_SAITEK_TIME); + gameport_trigger(gameport); + while ((gameport_read(port->gameport) & port->mask) && (v < t)) + v++; + + if (v < (u >> 1)) { /* FIXME - more than one port */ + analog_options[0] |= /* FIXME - more than one port */ + ANALOG_SAITEK | ANALOG_BTNS_CHF | ANALOG_HBTN_CHF | ANALOG_HAT1_CHF; + return 0; + } + + gameport_close(gameport); + } + + if (!gameport_open(gameport, drv, GAMEPORT_MODE_COOKED)) { + + for (i = 0; i < ANALOG_INIT_RETRIES; i++) + if (!gameport_cooked_read(gameport, port->axes, &port->buttons)) + break; + for (i = 0; i < 4; i++) + if (port->axes[i] != -1) + port->mask |= 1 << i; + + port->fuzz = gameport->fuzz; + port->cooked = 1; + return 0; + } + + return gameport_open(gameport, drv, GAMEPORT_MODE_RAW); +} + +static int analog_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct analog_port *port; + int i; + int err; + + port = kzalloc_obj(*port); + if (!port) + return -ENOMEM; + + err = analog_init_port(gameport, drv, port); + if (err) + goto fail1; + + err = analog_init_masks(port); + if (err) + goto fail2; + + gameport_set_poll_handler(gameport, analog_poll); + gameport_set_poll_interval(gameport, 10); + + for (i = 0; i < 2; i++) + if (port->analog[i].mask) { + err = analog_init_device(port, port->analog + i, i); + if (err) + goto fail3; + } + + return 0; + + fail3: while (--i >= 0) + if (port->analog[i].mask) + input_unregister_device(port->analog[i].dev); + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(port); + return err; +} + +static void analog_disconnect(struct gameport *gameport) +{ + struct analog_port *port = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 2; i++) + if (port->analog[i].mask) + input_unregister_device(port->analog[i].dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + printk(KERN_INFO "analog.c: %d out of %d reads (%d%%) on %s failed\n", + port->bads, port->reads, port->reads ? (port->bads * 100 / port->reads) : 0, + port->gameport->phys); + kfree(port); +} + +struct analog_types { + char *name; + int value; +}; + +static struct analog_types analog_types[] = { + { "none", 0x00000000 }, + { "auto", 0x000000ff }, + { "2btn", 0x0000003f }, + { "y-joy", 0x0cc00033 }, + { "y-pad", 0x8cc80033 }, + { "fcs", 0x000008f7 }, + { "chf", 0x000002ff }, + { "fullchf", 0x000007ff }, + { "gamepad", 0x000830f3 }, + { "gamepad8", 0x0008f0f3 }, + { NULL, 0 } +}; + +static void analog_parse_options(void) +{ + int i, j; + char *end; + + for (i = 0; i < js_nargs; i++) { + + for (j = 0; analog_types[j].name; j++) + if (!strcmp(analog_types[j].name, js[i])) { + analog_options[i] = analog_types[j].value; + break; + } + if (analog_types[j].name) continue; + + analog_options[i] = simple_strtoul(js[i], &end, 0); + if (end != js[i]) continue; + + analog_options[i] = 0xff; + if (!strlen(js[i])) continue; + + printk(KERN_WARNING "analog.c: Bad config for port %d - \"%s\"\n", i, js[i]); + } + + for (; i < ANALOG_PORTS; i++) + analog_options[i] = 0xff; +} + +/* + * The gameport device structure. + */ + +static struct gameport_driver analog_drv = { + .driver = { + .name = "analog", + }, + .description = DRIVER_DESC, + .connect = analog_connect, + .disconnect = analog_disconnect, +}; + +static int __init analog_init(void) +{ + analog_parse_options(); + return gameport_register_driver(&analog_drv); +} + +static void __exit analog_exit(void) +{ + gameport_unregister_driver(&analog_drv); +} + +module_init(analog_init); +module_exit(analog_exit); diff --git a/drivers/input/joystick/as5011.c b/drivers/input/joystick/as5011.c new file mode 100644 index 000000000..5fca92bf9 --- /dev/null +++ b/drivers/input/joystick/as5011.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2010, 2011 Fabien Marteau <fabien.marteau@armadeus.com> + * Sponsored by ARMadeus Systems + * + * Driver for Austria Microsystems joysticks AS5011 + * + * TODO: + * - Power on the chip when open() and power down when close() + * - Manage power mode + */ + +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/gpio/consumer.h> +#include <linux/delay.h> +#include <linux/input/as5011.h> +#include <linux/slab.h> +#include <linux/module.h> + +#define DRIVER_DESC "Driver for Austria Microsystems AS5011 joystick" +#define MODULE_DEVICE_ALIAS "as5011" + +MODULE_AUTHOR("Fabien Marteau <fabien.marteau@armadeus.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* registers */ +#define AS5011_CTRL1 0x76 +#define AS5011_CTRL2 0x75 +#define AS5011_XP 0x43 +#define AS5011_XN 0x44 +#define AS5011_YP 0x53 +#define AS5011_YN 0x54 +#define AS5011_X_REG 0x41 +#define AS5011_Y_REG 0x42 +#define AS5011_X_RES_INT 0x51 +#define AS5011_Y_RES_INT 0x52 + +/* CTRL1 bits */ +#define AS5011_CTRL1_LP_PULSED 0x80 +#define AS5011_CTRL1_LP_ACTIVE 0x40 +#define AS5011_CTRL1_LP_CONTINUE 0x20 +#define AS5011_CTRL1_INT_WUP_EN 0x10 +#define AS5011_CTRL1_INT_ACT_EN 0x08 +#define AS5011_CTRL1_EXT_CLK_EN 0x04 +#define AS5011_CTRL1_SOFT_RST 0x02 +#define AS5011_CTRL1_DATA_VALID 0x01 + +/* CTRL2 bits */ +#define AS5011_CTRL2_EXT_SAMPLE_EN 0x08 +#define AS5011_CTRL2_RC_BIAS_ON 0x04 +#define AS5011_CTRL2_INV_SPINNING 0x02 + +#define AS5011_MAX_AXIS 80 +#define AS5011_MIN_AXIS (-80) +#define AS5011_FUZZ 8 +#define AS5011_FLAT 40 + +struct as5011_device { + struct input_dev *input_dev; + struct i2c_client *i2c_client; + struct gpio_desc *button_gpiod; + unsigned int button_irq; + unsigned int axis_irq; +}; + +static int as5011_i2c_write(struct i2c_client *client, + uint8_t aregaddr, + uint8_t avalue) +{ + uint8_t data[2] = { aregaddr, avalue }; + struct i2c_msg msg = { + .addr = client->addr, + .flags = I2C_M_IGNORE_NAK, + .len = 2, + .buf = (uint8_t *)data + }; + int error; + + error = i2c_transfer(client->adapter, &msg, 1); + return error < 0 ? error : 0; +} + +static int as5011_i2c_read(struct i2c_client *client, + uint8_t aregaddr, signed char *value) +{ + uint8_t data[2] = { aregaddr }; + struct i2c_msg msg_set[2] = { + { + .addr = client->addr, + .flags = I2C_M_REV_DIR_ADDR, + .len = 1, + .buf = (uint8_t *)data + }, + { + .addr = client->addr, + .flags = I2C_M_RD | I2C_M_NOSTART, + .len = 1, + .buf = (uint8_t *)data + } + }; + int error; + + error = i2c_transfer(client->adapter, msg_set, 2); + if (error < 0) + return error; + + *value = data[0] & 0x80 ? -1 * (1 + ~data[0]) : data[0]; + return 0; +} + +static irqreturn_t as5011_button_interrupt(int irq, void *dev_id) +{ + struct as5011_device *as5011 = dev_id; + int val = gpiod_get_value_cansleep(as5011->button_gpiod); + + input_report_key(as5011->input_dev, BTN_JOYSTICK, !val); + input_sync(as5011->input_dev); + + return IRQ_HANDLED; +} + +static irqreturn_t as5011_axis_interrupt(int irq, void *dev_id) +{ + struct as5011_device *as5011 = dev_id; + int error; + signed char x, y; + + error = as5011_i2c_read(as5011->i2c_client, AS5011_X_RES_INT, &x); + if (error < 0) + goto out; + + error = as5011_i2c_read(as5011->i2c_client, AS5011_Y_RES_INT, &y); + if (error < 0) + goto out; + + input_report_abs(as5011->input_dev, ABS_X, x); + input_report_abs(as5011->input_dev, ABS_Y, y); + input_sync(as5011->input_dev); + +out: + return IRQ_HANDLED; +} + +static int as5011_configure_chip(struct as5011_device *as5011, + const struct as5011_platform_data *plat_dat) +{ + struct i2c_client *client = as5011->i2c_client; + int error; + signed char value; + + /* chip soft reset */ + error = as5011_i2c_write(client, AS5011_CTRL1, + AS5011_CTRL1_SOFT_RST); + if (error < 0) { + dev_err(&client->dev, "Soft reset failed\n"); + return error; + } + + mdelay(10); + + error = as5011_i2c_write(client, AS5011_CTRL1, + AS5011_CTRL1_LP_PULSED | + AS5011_CTRL1_LP_ACTIVE | + AS5011_CTRL1_INT_ACT_EN); + if (error < 0) { + dev_err(&client->dev, "Power config failed\n"); + return error; + } + + error = as5011_i2c_write(client, AS5011_CTRL2, + AS5011_CTRL2_INV_SPINNING); + if (error < 0) { + dev_err(&client->dev, "Can't invert spinning\n"); + return error; + } + + /* write threshold */ + error = as5011_i2c_write(client, AS5011_XP, plat_dat->xp); + if (error < 0) { + dev_err(&client->dev, "Can't write threshold\n"); + return error; + } + + error = as5011_i2c_write(client, AS5011_XN, plat_dat->xn); + if (error < 0) { + dev_err(&client->dev, "Can't write threshold\n"); + return error; + } + + error = as5011_i2c_write(client, AS5011_YP, plat_dat->yp); + if (error < 0) { + dev_err(&client->dev, "Can't write threshold\n"); + return error; + } + + error = as5011_i2c_write(client, AS5011_YN, plat_dat->yn); + if (error < 0) { + dev_err(&client->dev, "Can't write threshold\n"); + return error; + } + + /* to free irq gpio in chip */ + error = as5011_i2c_read(client, AS5011_X_RES_INT, &value); + if (error < 0) { + dev_err(&client->dev, "Can't read i2c X resolution value\n"); + return error; + } + + return 0; +} + +static int as5011_probe(struct i2c_client *client) +{ + const struct as5011_platform_data *plat_data; + struct as5011_device *as5011; + struct input_dev *input_dev; + int irq; + int error; + + plat_data = dev_get_platdata(&client->dev); + if (!plat_data) + return -EINVAL; + + if (!plat_data->axis_irq) { + dev_err(&client->dev, "No axis IRQ?\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_NOSTART | + I2C_FUNC_PROTOCOL_MANGLING)) { + dev_err(&client->dev, + "need i2c bus that supports protocol mangling\n"); + return -ENODEV; + } + + as5011 = kmalloc_obj(*as5011); + input_dev = input_allocate_device(); + if (!as5011 || !input_dev) { + dev_err(&client->dev, + "Can't allocate memory for device structure\n"); + error = -ENOMEM; + goto err_free_mem; + } + + as5011->i2c_client = client; + as5011->input_dev = input_dev; + as5011->axis_irq = plat_data->axis_irq; + + input_dev->name = "Austria Microsystem as5011 joystick"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + + input_set_capability(input_dev, EV_KEY, BTN_JOYSTICK); + + input_set_abs_params(input_dev, ABS_X, + AS5011_MIN_AXIS, AS5011_MAX_AXIS, AS5011_FUZZ, AS5011_FLAT); + input_set_abs_params(as5011->input_dev, ABS_Y, + AS5011_MIN_AXIS, AS5011_MAX_AXIS, AS5011_FUZZ, AS5011_FLAT); + + as5011->button_gpiod = devm_gpiod_get(&client->dev, NULL, GPIOD_IN); + if (IS_ERR(as5011->button_gpiod)) { + error = PTR_ERR(as5011->button_gpiod); + dev_err(&client->dev, "Failed to request button GPIO\n"); + goto err_free_mem; + } + gpiod_set_consumer_name(as5011->button_gpiod, "AS5011 button"); + + irq = gpiod_to_irq(as5011->button_gpiod); + if (irq < 0) { + dev_err(&client->dev, + "Failed to get irq number for button gpio\n"); + error = irq; + goto err_free_mem; + } + + as5011->button_irq = irq; + + error = request_threaded_irq(as5011->button_irq, + NULL, as5011_button_interrupt, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "as5011_button", as5011); + if (error < 0) { + dev_err(&client->dev, + "Can't allocate button irq %d\n", as5011->button_irq); + goto err_free_mem; + } + + error = as5011_configure_chip(as5011, plat_data); + if (error) + goto err_free_button_irq; + + error = request_threaded_irq(as5011->axis_irq, NULL, + as5011_axis_interrupt, + plat_data->axis_irqflags | IRQF_ONESHOT, + "as5011_joystick", as5011); + if (error) { + dev_err(&client->dev, + "Can't allocate axis irq %d\n", plat_data->axis_irq); + goto err_free_button_irq; + } + + error = input_register_device(as5011->input_dev); + if (error) { + dev_err(&client->dev, "Failed to register input device\n"); + goto err_free_axis_irq; + } + + i2c_set_clientdata(client, as5011); + + return 0; + +err_free_axis_irq: + free_irq(as5011->axis_irq, as5011); +err_free_button_irq: + free_irq(as5011->button_irq, as5011); +err_free_mem: + input_free_device(input_dev); + kfree(as5011); + + return error; +} + +static void as5011_remove(struct i2c_client *client) +{ + struct as5011_device *as5011 = i2c_get_clientdata(client); + + free_irq(as5011->axis_irq, as5011); + free_irq(as5011->button_irq, as5011); + + input_unregister_device(as5011->input_dev); + kfree(as5011); +} + +static const struct i2c_device_id as5011_id[] = { + { .name = MODULE_DEVICE_ALIAS }, + { } +}; +MODULE_DEVICE_TABLE(i2c, as5011_id); + +static struct i2c_driver as5011_driver = { + .driver = { + .name = "as5011", + }, + .probe = as5011_probe, + .remove = as5011_remove, + .id_table = as5011_id, +}; + +module_i2c_driver(as5011_driver); diff --git a/drivers/input/joystick/cobra.c b/drivers/input/joystick/cobra.c new file mode 100644 index 000000000..ffa6d49c8 --- /dev/null +++ b/drivers/input/joystick/cobra.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Creative Labs Blaster GamePad Cobra driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/gameport.h> +#include <linux/input.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "Creative Labs Blaster GamePad Cobra driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define COBRA_MAX_STROBE 45 /* 45 us max wait for first strobe */ +#define COBRA_LENGTH 36 + +static int cobra_btn[] = { BTN_START, BTN_SELECT, BTN_TL, BTN_TR, BTN_X, BTN_Y, BTN_Z, BTN_A, BTN_B, BTN_C, BTN_TL2, BTN_TR2, 0 }; + +struct cobra { + struct gameport *gameport; + struct input_dev *dev[2]; + int reads; + int bads; + unsigned char exists; + char phys[2][32]; +}; + +static unsigned char cobra_read_packet(struct gameport *gameport, unsigned int *data) +{ + unsigned long flags; + unsigned char u, v, w; + __u64 buf[2]; + int r[2], t[2]; + int i, j, ret; + + int strobe = gameport_time(gameport, COBRA_MAX_STROBE); + + for (i = 0; i < 2; i++) { + r[i] = buf[i] = 0; + t[i] = COBRA_MAX_STROBE; + } + + local_irq_save(flags); + + u = gameport_read(gameport); + + do { + t[0]--; t[1]--; + v = gameport_read(gameport); + for (i = 0, w = u ^ v; i < 2 && w; i++, w >>= 2) + if (w & 0x30) { + if ((w & 0x30) < 0x30 && r[i] < COBRA_LENGTH && t[i] > 0) { + buf[i] |= (__u64)((w >> 5) & 1) << r[i]++; + t[i] = strobe; + u = v; + } else t[i] = 0; + } + } while (t[0] > 0 || t[1] > 0); + + local_irq_restore(flags); + + ret = 0; + + for (i = 0; i < 2; i++) { + + if (r[i] != COBRA_LENGTH) continue; + + for (j = 0; j < COBRA_LENGTH && (buf[i] & 0x04104107f) ^ 0x041041040; j++) + buf[i] = (buf[i] >> 1) | ((__u64)(buf[i] & 1) << (COBRA_LENGTH - 1)); + + if (j < COBRA_LENGTH) ret |= (1 << i); + + data[i] = ((buf[i] >> 7) & 0x000001f) | ((buf[i] >> 8) & 0x00003e0) + | ((buf[i] >> 9) & 0x0007c00) | ((buf[i] >> 10) & 0x00f8000) + | ((buf[i] >> 11) & 0x1f00000); + + } + + return ret; +} + +static void cobra_poll(struct gameport *gameport) +{ + struct cobra *cobra = gameport_get_drvdata(gameport); + struct input_dev *dev; + unsigned int data[2]; + int i, j, r; + + cobra->reads++; + + if ((r = cobra_read_packet(gameport, data)) != cobra->exists) { + cobra->bads++; + return; + } + + for (i = 0; i < 2; i++) + if (cobra->exists & r & (1 << i)) { + + dev = cobra->dev[i]; + + input_report_abs(dev, ABS_X, ((data[i] >> 4) & 1) - ((data[i] >> 3) & 1)); + input_report_abs(dev, ABS_Y, ((data[i] >> 2) & 1) - ((data[i] >> 1) & 1)); + + for (j = 0; cobra_btn[j]; j++) + input_report_key(dev, cobra_btn[j], data[i] & (0x20 << j)); + + input_sync(dev); + + } +} + +static int cobra_open(struct input_dev *dev) +{ + struct cobra *cobra = input_get_drvdata(dev); + + gameport_start_polling(cobra->gameport); + return 0; +} + +static void cobra_close(struct input_dev *dev) +{ + struct cobra *cobra = input_get_drvdata(dev); + + gameport_stop_polling(cobra->gameport); +} + +static int cobra_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct cobra *cobra; + struct input_dev *input_dev; + unsigned int data[2]; + int i, j; + int err; + + cobra = kzalloc_obj(*cobra); + if (!cobra) + return -ENOMEM; + + cobra->gameport = gameport; + + gameport_set_drvdata(gameport, cobra); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + cobra->exists = cobra_read_packet(gameport, data); + + for (i = 0; i < 2; i++) + if ((cobra->exists >> i) & data[i] & 1) { + printk(KERN_WARNING "cobra.c: Device %d on %s has the Ext bit set. ID is: %d" + " Contact vojtech@ucw.cz\n", i, gameport->phys, (data[i] >> 2) & 7); + cobra->exists &= ~(1 << i); + } + + if (!cobra->exists) { + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, cobra_poll); + gameport_set_poll_interval(gameport, 20); + + for (i = 0; i < 2; i++) { + if (~(cobra->exists >> i) & 1) + continue; + + cobra->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto fail3; + } + + snprintf(cobra->phys[i], sizeof(cobra->phys[i]), + "%s/input%d", gameport->phys, i); + + input_dev->name = "Creative Labs Blaster GamePad Cobra"; + input_dev->phys = cobra->phys[i]; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_CREATIVE; + input_dev->id.product = 0x0008; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, cobra); + + input_dev->open = cobra_open; + input_dev->close = cobra_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0); + for (j = 0; cobra_btn[j]; j++) + set_bit(cobra_btn[j], input_dev->keybit); + + err = input_register_device(cobra->dev[i]); + if (err) + goto fail4; + } + + return 0; + + fail4: input_free_device(cobra->dev[i]); + fail3: while (--i >= 0) + if (cobra->dev[i]) + input_unregister_device(cobra->dev[i]); + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(cobra); + return err; +} + +static void cobra_disconnect(struct gameport *gameport) +{ + struct cobra *cobra = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 2; i++) + if ((cobra->exists >> i) & 1) + input_unregister_device(cobra->dev[i]); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(cobra); +} + +static struct gameport_driver cobra_drv = { + .driver = { + .name = "cobra", + }, + .description = DRIVER_DESC, + .connect = cobra_connect, + .disconnect = cobra_disconnect, +}; + +module_gameport_driver(cobra_drv); diff --git a/drivers/input/joystick/db9.c b/drivers/input/joystick/db9.c new file mode 100644 index 000000000..e5d9b71aa --- /dev/null +++ b/drivers/input/joystick/db9.c @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * Andree Borrmann Mats Sjövall + */ + +/* + * Atari, Amstrad, Commodore, Amiga, Sega, etc. joystick driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/parport.h> +#include <linux/input.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Atari, Amstrad, Commodore, Amiga, Sega, etc. joystick driver"); +MODULE_LICENSE("GPL"); + +struct db9_config { + int args[2]; + unsigned int nargs; +}; + +#define DB9_MAX_PORTS 3 +static struct db9_config db9_cfg[DB9_MAX_PORTS]; + +module_param_array_named(dev, db9_cfg[0].args, int, &db9_cfg[0].nargs, 0); +MODULE_PARM_DESC(dev, "Describes first attached device (<parport#>,<type>)"); +module_param_array_named(dev2, db9_cfg[1].args, int, &db9_cfg[1].nargs, 0); +MODULE_PARM_DESC(dev2, "Describes second attached device (<parport#>,<type>)"); +module_param_array_named(dev3, db9_cfg[2].args, int, &db9_cfg[2].nargs, 0); +MODULE_PARM_DESC(dev3, "Describes third attached device (<parport#>,<type>)"); + +#define DB9_ARG_PARPORT 0 +#define DB9_ARG_MODE 1 + +#define DB9_MULTI_STICK 0x01 +#define DB9_MULTI2_STICK 0x02 +#define DB9_GENESIS_PAD 0x03 +#define DB9_GENESIS5_PAD 0x05 +#define DB9_GENESIS6_PAD 0x06 +#define DB9_SATURN_PAD 0x07 +#define DB9_MULTI_0802 0x08 +#define DB9_MULTI_0802_2 0x09 +#define DB9_CD32_PAD 0x0A +#define DB9_SATURN_DPP 0x0B +#define DB9_SATURN_DPP_2 0x0C +#define DB9_MAX_PAD 0x0D + +#define DB9_UP 0x01 +#define DB9_DOWN 0x02 +#define DB9_LEFT 0x04 +#define DB9_RIGHT 0x08 +#define DB9_FIRE1 0x10 +#define DB9_FIRE2 0x20 +#define DB9_FIRE3 0x40 +#define DB9_FIRE4 0x80 + +#define DB9_NORMAL 0x0a +#define DB9_NOSELECT 0x08 + +#define DB9_GENESIS6_DELAY 14 +#define DB9_REFRESH_TIME HZ/100 + +#define DB9_MAX_DEVICES 2 + +struct db9_mode_data { + const char *name; + const short *buttons; + int n_buttons; + int n_pads; + int n_axis; + int bidirectional; + int reverse; +}; + +struct db9 { + struct input_dev *dev[DB9_MAX_DEVICES]; + struct timer_list timer; + struct pardevice *pd; + int mode; + int used; + int parportno; + struct mutex mutex; + char phys[DB9_MAX_DEVICES][32]; +}; + +static struct db9 *db9_base[3]; + +static const short db9_multi_btn[] = { BTN_TRIGGER, BTN_THUMB }; +static const short db9_genesis_btn[] = { BTN_START, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_MODE }; +static const short db9_cd32_btn[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START }; +static const short db9_abs[] = { ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_RZ, ABS_Z, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y }; + +static const struct db9_mode_data db9_modes[] = { + { NULL, NULL, 0, 0, 0, 0, 0 }, + { "Multisystem joystick", db9_multi_btn, 1, 1, 2, 1, 1 }, + { "Multisystem joystick (2 fire)", db9_multi_btn, 2, 1, 2, 1, 1 }, + { "Genesis pad", db9_genesis_btn, 4, 1, 2, 1, 1 }, + { NULL, NULL, 0, 0, 0, 0, 0 }, + { "Genesis 5 pad", db9_genesis_btn, 6, 1, 2, 1, 1 }, + { "Genesis 6 pad", db9_genesis_btn, 8, 1, 2, 1, 1 }, + { "Saturn pad", db9_cd32_btn, 9, 6, 7, 0, 1 }, + { "Multisystem (0.8.0.2) joystick", db9_multi_btn, 1, 1, 2, 1, 1 }, + { "Multisystem (0.8.0.2-dual) joystick", db9_multi_btn, 1, 2, 2, 1, 1 }, + { "Amiga CD-32 pad", db9_cd32_btn, 7, 1, 2, 1, 1 }, + { "Saturn dpp", db9_cd32_btn, 9, 6, 7, 0, 0 }, + { "Saturn dpp dual", db9_cd32_btn, 9, 12, 7, 0, 0 }, +}; + +/* + * Saturn controllers + */ +#define DB9_SATURN_DELAY 300 +static const int db9_saturn_byte[] = { 1, 1, 1, 2, 2, 2, 2, 2, 1 }; +static const unsigned char db9_saturn_mask[] = { 0x04, 0x01, 0x02, 0x40, 0x20, 0x10, 0x08, 0x80, 0x08 }; + +/* + * db9_saturn_write_sub() writes 2 bit data. + */ +static void db9_saturn_write_sub(struct parport *port, int type, unsigned char data, int powered, int pwr_sub) +{ + unsigned char c; + + switch (type) { + case 1: /* DPP1 */ + c = 0x80 | 0x30 | (powered ? 0x08 : 0) | (pwr_sub ? 0x04 : 0) | data; + parport_write_data(port, c); + break; + case 2: /* DPP2 */ + c = 0x40 | data << 4 | (powered ? 0x08 : 0) | (pwr_sub ? 0x04 : 0) | 0x03; + parport_write_data(port, c); + break; + case 0: /* DB9 */ + c = ((((data & 2) ? 2 : 0) | ((data & 1) ? 4 : 0)) ^ 0x02) | !powered; + parport_write_control(port, c); + break; + } +} + +/* + * gc_saturn_read_sub() reads 4 bit data. + */ +static unsigned char db9_saturn_read_sub(struct parport *port, int type) +{ + unsigned char data; + + if (type) { + /* DPP */ + data = parport_read_status(port) ^ 0x80; + return (data & 0x80 ? 1 : 0) | (data & 0x40 ? 2 : 0) + | (data & 0x20 ? 4 : 0) | (data & 0x10 ? 8 : 0); + } else { + /* DB9 */ + data = parport_read_data(port) & 0x0f; + return (data & 0x8 ? 1 : 0) | (data & 0x4 ? 2 : 0) + | (data & 0x2 ? 4 : 0) | (data & 0x1 ? 8 : 0); + } +} + +/* + * db9_saturn_read_analog() sends clock and reads 8 bit data. + */ +static unsigned char db9_saturn_read_analog(struct parport *port, int type, int powered) +{ + unsigned char data; + + db9_saturn_write_sub(port, type, 0, powered, 0); + udelay(DB9_SATURN_DELAY); + data = db9_saturn_read_sub(port, type) << 4; + db9_saturn_write_sub(port, type, 2, powered, 0); + udelay(DB9_SATURN_DELAY); + data |= db9_saturn_read_sub(port, type); + return data; +} + +/* + * db9_saturn_read_packet() reads whole saturn packet at connector + * and returns device identifier code. + */ +static unsigned char db9_saturn_read_packet(struct parport *port, unsigned char *data, int type, int powered) +{ + int i, j; + unsigned char tmp; + + db9_saturn_write_sub(port, type, 3, powered, 0); + data[0] = db9_saturn_read_sub(port, type); + switch (data[0] & 0x0f) { + case 0xf: + /* 1111 no pad */ + return data[0] = 0xff; + case 0x4: case 0x4 | 0x8: + /* ?100 : digital controller */ + db9_saturn_write_sub(port, type, 0, powered, 1); + data[2] = db9_saturn_read_sub(port, type) << 4; + db9_saturn_write_sub(port, type, 2, powered, 1); + data[1] = db9_saturn_read_sub(port, type) << 4; + db9_saturn_write_sub(port, type, 1, powered, 1); + data[1] |= db9_saturn_read_sub(port, type); + db9_saturn_write_sub(port, type, 3, powered, 1); + /* data[2] |= db9_saturn_read_sub(port, type); */ + data[2] |= data[0]; + return data[0] = 0x02; + case 0x1: + /* 0001 : analog controller or multitap */ + db9_saturn_write_sub(port, type, 2, powered, 0); + udelay(DB9_SATURN_DELAY); + data[0] = db9_saturn_read_analog(port, type, powered); + if (data[0] != 0x41) { + /* read analog controller */ + for (i = 0; i < (data[0] & 0x0f); i++) + data[i + 1] = db9_saturn_read_analog(port, type, powered); + db9_saturn_write_sub(port, type, 3, powered, 0); + return data[0]; + } else { + /* read multitap */ + if (db9_saturn_read_analog(port, type, powered) != 0x60) + return data[0] = 0xff; + for (i = 0; i < 60; i += 10) { + data[i] = db9_saturn_read_analog(port, type, powered); + if (data[i] != 0xff) + /* read each pad */ + for (j = 0; j < (data[i] & 0x0f); j++) + data[i + j + 1] = db9_saturn_read_analog(port, type, powered); + } + db9_saturn_write_sub(port, type, 3, powered, 0); + return 0x41; + } + case 0x0: + /* 0000 : mouse */ + db9_saturn_write_sub(port, type, 2, powered, 0); + udelay(DB9_SATURN_DELAY); + tmp = db9_saturn_read_analog(port, type, powered); + if (tmp == 0xff) { + for (i = 0; i < 3; i++) + data[i + 1] = db9_saturn_read_analog(port, type, powered); + db9_saturn_write_sub(port, type, 3, powered, 0); + return data[0] = 0xe3; + } + fallthrough; + default: + return data[0]; + } +} + +/* + * db9_saturn_report() analyzes packet and reports. + */ +static int db9_saturn_report(unsigned char id, unsigned char data[60], struct input_dev *devs[], int n, int max_pads) +{ + struct input_dev *dev; + int tmp, i, j; + + tmp = (id == 0x41) ? 60 : 10; + for (j = 0; j < tmp && n < max_pads; j += 10, n++) { + dev = devs[n]; + switch (data[j]) { + case 0x16: /* multi controller (analog 4 axis) */ + input_report_abs(dev, db9_abs[5], data[j + 6]); + fallthrough; + case 0x15: /* mission stick (analog 3 axis) */ + input_report_abs(dev, db9_abs[3], data[j + 4]); + input_report_abs(dev, db9_abs[4], data[j + 5]); + fallthrough; + case 0x13: /* racing controller (analog 1 axis) */ + input_report_abs(dev, db9_abs[2], data[j + 3]); + fallthrough; + case 0x34: /* saturn keyboard (udlr ZXC ASD QE Esc) */ + case 0x02: /* digital pad (digital 2 axis + buttons) */ + input_report_abs(dev, db9_abs[0], !(data[j + 1] & 128) - !(data[j + 1] & 64)); + input_report_abs(dev, db9_abs[1], !(data[j + 1] & 32) - !(data[j + 1] & 16)); + for (i = 0; i < 9; i++) + input_report_key(dev, db9_cd32_btn[i], ~data[j + db9_saturn_byte[i]] & db9_saturn_mask[i]); + break; + case 0x19: /* mission stick x2 (analog 6 axis + buttons) */ + input_report_abs(dev, db9_abs[0], !(data[j + 1] & 128) - !(data[j + 1] & 64)); + input_report_abs(dev, db9_abs[1], !(data[j + 1] & 32) - !(data[j + 1] & 16)); + for (i = 0; i < 9; i++) + input_report_key(dev, db9_cd32_btn[i], ~data[j + db9_saturn_byte[i]] & db9_saturn_mask[i]); + input_report_abs(dev, db9_abs[2], data[j + 3]); + input_report_abs(dev, db9_abs[3], data[j + 4]); + input_report_abs(dev, db9_abs[4], data[j + 5]); + /* + input_report_abs(dev, db9_abs[8], (data[j + 6] & 128 ? 0 : 1) - (data[j + 6] & 64 ? 0 : 1)); + input_report_abs(dev, db9_abs[9], (data[j + 6] & 32 ? 0 : 1) - (data[j + 6] & 16 ? 0 : 1)); + */ + input_report_abs(dev, db9_abs[6], data[j + 7]); + input_report_abs(dev, db9_abs[7], data[j + 8]); + input_report_abs(dev, db9_abs[5], data[j + 9]); + break; + case 0xd3: /* sankyo ff (analog 1 axis + stop btn) */ + input_report_key(dev, BTN_A, data[j + 3] & 0x80); + input_report_abs(dev, db9_abs[2], data[j + 3] & 0x7f); + break; + case 0xe3: /* shuttle mouse (analog 2 axis + buttons. signed value) */ + input_report_key(dev, BTN_START, data[j + 1] & 0x08); + input_report_key(dev, BTN_A, data[j + 1] & 0x04); + input_report_key(dev, BTN_C, data[j + 1] & 0x02); + input_report_key(dev, BTN_B, data[j + 1] & 0x01); + input_report_abs(dev, db9_abs[2], data[j + 2] ^ 0x80); + input_report_abs(dev, db9_abs[3], (0xff-(data[j + 3] ^ 0x80))+1); /* */ + break; + case 0xff: + default: /* no pad */ + input_report_abs(dev, db9_abs[0], 0); + input_report_abs(dev, db9_abs[1], 0); + for (i = 0; i < 9; i++) + input_report_key(dev, db9_cd32_btn[i], 0); + break; + } + } + return n; +} + +static int db9_saturn(int mode, struct parport *port, struct input_dev *devs[]) +{ + unsigned char id, data[60]; + int type, n, max_pads; + int tmp, i; + + switch (mode) { + case DB9_SATURN_PAD: + type = 0; + n = 1; + break; + case DB9_SATURN_DPP: + type = 1; + n = 1; + break; + case DB9_SATURN_DPP_2: + type = 1; + n = 2; + break; + default: + return -1; + } + max_pads = min(db9_modes[mode].n_pads, DB9_MAX_DEVICES); + for (tmp = 0, i = 0; i < n; i++) { + id = db9_saturn_read_packet(port, data, type + i, 1); + tmp = db9_saturn_report(id, data, devs, tmp, max_pads); + } + return 0; +} + +static void db9_timer(struct timer_list *t) +{ + struct db9 *db9 = timer_container_of(db9, t, timer); + struct parport *port = db9->pd->port; + struct input_dev *dev = db9->dev[0]; + struct input_dev *dev2 = db9->dev[1]; + int data, i; + + switch (db9->mode) { + case DB9_MULTI_0802_2: + + data = parport_read_data(port) >> 3; + + input_report_abs(dev2, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev2, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev2, BTN_TRIGGER, ~data & DB9_FIRE1); + fallthrough; + + case DB9_MULTI_0802: + + data = parport_read_status(port) >> 3; + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_TRIGGER, data & DB9_FIRE1); + break; + + case DB9_MULTI_STICK: + + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_TRIGGER, ~data & DB9_FIRE1); + break; + + case DB9_MULTI2_STICK: + + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_TRIGGER, ~data & DB9_FIRE1); + input_report_key(dev, BTN_THUMB, ~data & DB9_FIRE2); + break; + + case DB9_GENESIS_PAD: + + parport_write_control(port, DB9_NOSELECT); + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_B, ~data & DB9_FIRE1); + input_report_key(dev, BTN_C, ~data & DB9_FIRE2); + + parport_write_control(port, DB9_NORMAL); + data = parport_read_data(port); + + input_report_key(dev, BTN_A, ~data & DB9_FIRE1); + input_report_key(dev, BTN_START, ~data & DB9_FIRE2); + break; + + case DB9_GENESIS5_PAD: + + parport_write_control(port, DB9_NOSELECT); + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_B, ~data & DB9_FIRE1); + input_report_key(dev, BTN_C, ~data & DB9_FIRE2); + + parport_write_control(port, DB9_NORMAL); + data = parport_read_data(port); + + input_report_key(dev, BTN_A, ~data & DB9_FIRE1); + input_report_key(dev, BTN_X, ~data & DB9_FIRE2); + input_report_key(dev, BTN_Y, ~data & DB9_LEFT); + input_report_key(dev, BTN_START, ~data & DB9_RIGHT); + break; + + case DB9_GENESIS6_PAD: + + parport_write_control(port, DB9_NOSELECT); /* 1 */ + udelay(DB9_GENESIS6_DELAY); + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + input_report_key(dev, BTN_B, ~data & DB9_FIRE1); + input_report_key(dev, BTN_C, ~data & DB9_FIRE2); + + parport_write_control(port, DB9_NORMAL); + udelay(DB9_GENESIS6_DELAY); + data = parport_read_data(port); + + input_report_key(dev, BTN_A, ~data & DB9_FIRE1); + input_report_key(dev, BTN_START, ~data & DB9_FIRE2); + + parport_write_control(port, DB9_NOSELECT); /* 2 */ + udelay(DB9_GENESIS6_DELAY); + parport_write_control(port, DB9_NORMAL); + udelay(DB9_GENESIS6_DELAY); + parport_write_control(port, DB9_NOSELECT); /* 3 */ + udelay(DB9_GENESIS6_DELAY); + data=parport_read_data(port); + + input_report_key(dev, BTN_X, ~data & DB9_LEFT); + input_report_key(dev, BTN_Y, ~data & DB9_DOWN); + input_report_key(dev, BTN_Z, ~data & DB9_UP); + input_report_key(dev, BTN_MODE, ~data & DB9_RIGHT); + + parport_write_control(port, DB9_NORMAL); + udelay(DB9_GENESIS6_DELAY); + parport_write_control(port, DB9_NOSELECT); /* 4 */ + udelay(DB9_GENESIS6_DELAY); + parport_write_control(port, DB9_NORMAL); + break; + + case DB9_SATURN_PAD: + case DB9_SATURN_DPP: + case DB9_SATURN_DPP_2: + + db9_saturn(db9->mode, port, db9->dev); + break; + + case DB9_CD32_PAD: + + data = parport_read_data(port); + + input_report_abs(dev, ABS_X, (data & DB9_RIGHT ? 0 : 1) - (data & DB9_LEFT ? 0 : 1)); + input_report_abs(dev, ABS_Y, (data & DB9_DOWN ? 0 : 1) - (data & DB9_UP ? 0 : 1)); + + parport_write_control(port, 0x0a); + + for (i = 0; i < 7; i++) { + data = parport_read_data(port); + parport_write_control(port, 0x02); + parport_write_control(port, 0x0a); + input_report_key(dev, db9_cd32_btn[i], ~data & DB9_FIRE2); + } + + parport_write_control(port, 0x00); + break; + } + + input_sync(dev); + + mod_timer(&db9->timer, jiffies + DB9_REFRESH_TIME); +} + +static int db9_open(struct input_dev *dev) +{ + struct db9 *db9 = input_get_drvdata(dev); + struct parport *port = db9->pd->port; + + scoped_guard(mutex_intr, &db9->mutex) { + if (!db9->used++) { + parport_claim(db9->pd); + parport_write_data(port, 0xff); + if (db9_modes[db9->mode].reverse) { + parport_data_reverse(port); + parport_write_control(port, DB9_NORMAL); + } + mod_timer(&db9->timer, jiffies + DB9_REFRESH_TIME); + } + + return 0; + } + + return -EINTR; +} + +static void db9_close(struct input_dev *dev) +{ + struct db9 *db9 = input_get_drvdata(dev); + struct parport *port = db9->pd->port; + + guard(mutex)(&db9->mutex); + + if (!--db9->used) { + timer_delete_sync(&db9->timer); + parport_write_control(port, 0x00); + parport_data_forward(port); + parport_release(db9->pd); + } +} + +static void db9_attach(struct parport *pp) +{ + struct db9 *db9; + const struct db9_mode_data *db9_mode; + struct pardevice *pd; + struct input_dev *input_dev; + int i, j, port_idx; + int mode; + struct pardev_cb db9_parport_cb; + + for (port_idx = 0; port_idx < DB9_MAX_PORTS; port_idx++) { + if (db9_cfg[port_idx].nargs == 0 || + db9_cfg[port_idx].args[DB9_ARG_PARPORT] < 0) + continue; + + if (db9_cfg[port_idx].args[DB9_ARG_PARPORT] == pp->number) + break; + } + + if (port_idx == DB9_MAX_PORTS) { + pr_debug("Not using parport%d.\n", pp->number); + return; + } + + mode = db9_cfg[port_idx].args[DB9_ARG_MODE]; + + if (mode < 1 || mode >= DB9_MAX_PAD || !db9_modes[mode].n_buttons) { + printk(KERN_ERR "db9.c: Bad device type %d\n", mode); + return; + } + + db9_mode = &db9_modes[mode]; + + if (db9_mode->bidirectional && !(pp->modes & PARPORT_MODE_TRISTATE)) { + printk(KERN_ERR "db9.c: specified parport is not bidirectional\n"); + return; + } + + memset(&db9_parport_cb, 0, sizeof(db9_parport_cb)); + db9_parport_cb.flags = PARPORT_FLAG_EXCL; + + pd = parport_register_dev_model(pp, "db9", &db9_parport_cb, port_idx); + if (!pd) { + printk(KERN_ERR "db9.c: parport busy already - lp.o loaded?\n"); + return; + } + + db9 = kzalloc_obj(*db9); + if (!db9) + goto err_unreg_pardev; + + mutex_init(&db9->mutex); + db9->pd = pd; + db9->mode = mode; + db9->parportno = pp->number; + timer_setup(&db9->timer, db9_timer, 0); + + for (i = 0; i < (min(db9_mode->n_pads, DB9_MAX_DEVICES)); i++) { + + db9->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + printk(KERN_ERR "db9.c: Not enough memory for input device\n"); + goto err_unreg_devs; + } + + snprintf(db9->phys[i], sizeof(db9->phys[i]), + "%s/input%d", db9->pd->port->name, i); + + input_dev->name = db9_mode->name; + input_dev->phys = db9->phys[i]; + input_dev->id.bustype = BUS_PARPORT; + input_dev->id.vendor = 0x0002; + input_dev->id.product = mode; + input_dev->id.version = 0x0100; + + input_set_drvdata(input_dev, db9); + + input_dev->open = db9_open; + input_dev->close = db9_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + for (j = 0; j < db9_mode->n_buttons; j++) + set_bit(db9_mode->buttons[j], input_dev->keybit); + for (j = 0; j < db9_mode->n_axis; j++) { + if (j < 2) + input_set_abs_params(input_dev, db9_abs[j], -1, 1, 0, 0); + else + input_set_abs_params(input_dev, db9_abs[j], 1, 255, 0, 0); + } + + if (input_register_device(input_dev)) + goto err_free_dev; + } + + db9_base[port_idx] = db9; + return; + + err_free_dev: + input_free_device(db9->dev[i]); + err_unreg_devs: + while (--i >= 0) + input_unregister_device(db9->dev[i]); + kfree(db9); + err_unreg_pardev: + parport_unregister_device(pd); +} + +static void db9_detach(struct parport *port) +{ + int i; + struct db9 *db9; + + for (i = 0; i < DB9_MAX_PORTS; i++) { + if (db9_base[i] && db9_base[i]->parportno == port->number) + break; + } + + if (i == DB9_MAX_PORTS) + return; + + db9 = db9_base[i]; + db9_base[i] = NULL; + + for (i = 0; i < min(db9_modes[db9->mode].n_pads, DB9_MAX_DEVICES); i++) + input_unregister_device(db9->dev[i]); + parport_unregister_device(db9->pd); + kfree(db9); +} + +static struct parport_driver db9_parport_driver = { + .name = "db9", + .match_port = db9_attach, + .detach = db9_detach, +}; + +static int __init db9_init(void) +{ + int i; + int have_dev = 0; + + for (i = 0; i < DB9_MAX_PORTS; i++) { + if (db9_cfg[i].nargs == 0 || db9_cfg[i].args[DB9_ARG_PARPORT] < 0) + continue; + + if (db9_cfg[i].nargs < 2) { + printk(KERN_ERR "db9.c: Device type must be specified.\n"); + return -EINVAL; + } + + have_dev = 1; + } + + if (!have_dev) + return -ENODEV; + + return parport_register_driver(&db9_parport_driver); +} + +static void __exit db9_exit(void) +{ + parport_unregister_driver(&db9_parport_driver); +} + +module_init(db9_init); +module_exit(db9_exit); diff --git a/drivers/input/joystick/fsia6b.c b/drivers/input/joystick/fsia6b.c new file mode 100644 index 000000000..b0d736484 --- /dev/null +++ b/drivers/input/joystick/fsia6b.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FS-iA6B iBus RC receiver driver + * + * This driver provides all 14 channels of the FlySky FS-ia6B RC receiver + * as analog values. + * + * Additionally, the channels can be converted to discrete switch values. + * By default, it is configured for the offical FS-i6 remote control. + * If you use a different hardware configuration, you can configure it + * using the `switch_config` parameter. + */ + +#include <linux/device.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/slab.h> +#include <linux/types.h> + +#define DRIVER_DESC "FS-iA6B iBus RC receiver" + +MODULE_AUTHOR("Markus Koch <markus@notsyncing.net>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define IBUS_SERVO_COUNT 14 + +static char *switch_config = "00000022320000"; +module_param(switch_config, charp, 0444); +MODULE_PARM_DESC(switch_config, + "Amount of switch positions per channel (14 characters, 0-3)"); + +static int fsia6b_axes[IBUS_SERVO_COUNT] = { + ABS_X, ABS_Y, + ABS_Z, ABS_RX, + ABS_RY, ABS_RZ, + ABS_HAT0X, ABS_HAT0Y, + ABS_HAT1X, ABS_HAT1Y, + ABS_HAT2X, ABS_HAT2Y, + ABS_HAT3X, ABS_HAT3Y +}; + +enum ibus_state { SYNC, COLLECT, PROCESS }; + +struct ibus_packet { + enum ibus_state state; + + int offset; + u16 ibuf; + u16 channel[IBUS_SERVO_COUNT]; +}; + +struct fsia6b { + struct input_dev *dev; + struct ibus_packet packet; + + char phys[32]; +}; + +static irqreturn_t fsia6b_serio_irq(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct fsia6b *fsia6b = serio_get_drvdata(serio); + int i; + int sw_state; + int sw_id = BTN_0; + + fsia6b->packet.ibuf = (data << 8) | ((fsia6b->packet.ibuf >> 8) & 0xFF); + + switch (fsia6b->packet.state) { + case SYNC: + if (fsia6b->packet.ibuf == 0x4020) + fsia6b->packet.state = COLLECT; + break; + + case COLLECT: + fsia6b->packet.state = PROCESS; + break; + + case PROCESS: + fsia6b->packet.channel[fsia6b->packet.offset] = + fsia6b->packet.ibuf; + fsia6b->packet.offset++; + + if (fsia6b->packet.offset == IBUS_SERVO_COUNT) { + fsia6b->packet.offset = 0; + fsia6b->packet.state = SYNC; + for (i = 0; i < IBUS_SERVO_COUNT; ++i) { + input_report_abs(fsia6b->dev, fsia6b_axes[i], + fsia6b->packet.channel[i]); + + sw_state = 0; + if (fsia6b->packet.channel[i] > 1900) + sw_state = 1; + else if (fsia6b->packet.channel[i] < 1100) + sw_state = 2; + + switch (switch_config[i]) { + case '3': + input_report_key(fsia6b->dev, + sw_id++, + sw_state == 0); + fallthrough; + case '2': + input_report_key(fsia6b->dev, + sw_id++, + sw_state == 1); + fallthrough; + case '1': + input_report_key(fsia6b->dev, + sw_id++, + sw_state == 2); + } + } + input_sync(fsia6b->dev); + } else { + fsia6b->packet.state = COLLECT; + } + break; + } + + return IRQ_HANDLED; +} + +static int fsia6b_serio_connect(struct serio *serio, struct serio_driver *drv) +{ + struct fsia6b *fsia6b; + struct input_dev *input_dev; + int err; + int i, j; + int sw_id = 0; + + fsia6b = kzalloc_obj(*fsia6b); + if (!fsia6b) + return -ENOMEM; + + fsia6b->packet.ibuf = 0; + fsia6b->packet.offset = 0; + fsia6b->packet.state = SYNC; + + serio_set_drvdata(serio, fsia6b); + + input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto fail1; + } + fsia6b->dev = input_dev; + + scnprintf(fsia6b->phys, sizeof(fsia6b->phys), "%s/input0", serio->phys); + + input_dev->name = DRIVER_DESC; + input_dev->phys = fsia6b->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_FSIA6B; + input_dev->id.product = serio->id.id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + for (i = 0; i < IBUS_SERVO_COUNT; i++) + input_set_abs_params(input_dev, fsia6b_axes[i], + 1000, 2000, 2, 2); + + /* Register switch configuration */ + for (i = 0; i < IBUS_SERVO_COUNT; i++) { + if (switch_config[i] < '0' || switch_config[i] > '3') { + dev_err(&fsia6b->dev->dev, + "Invalid switch configuration supplied for fsia6b.\n"); + err = -EINVAL; + goto fail2; + } + + for (j = '1'; j <= switch_config[i]; j++) { + input_set_capability(input_dev, EV_KEY, BTN_0 + sw_id); + sw_id++; + } + } + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(fsia6b->dev); + if (err) + goto fail3; + + return 0; + +fail3: serio_close(serio); +fail2: input_free_device(input_dev); +fail1: serio_set_drvdata(serio, NULL); + kfree(fsia6b); + return err; +} + +static void fsia6b_serio_disconnect(struct serio *serio) +{ + struct fsia6b *fsia6b = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(fsia6b->dev); + kfree(fsia6b); +} + +static const struct serio_device_id fsia6b_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_FSIA6B, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, fsia6b_serio_ids); + +static struct serio_driver fsia6b_serio_drv = { + .driver = { + .name = "fsia6b" + }, + .description = DRIVER_DESC, + .id_table = fsia6b_serio_ids, + .interrupt = fsia6b_serio_irq, + .connect = fsia6b_serio_connect, + .disconnect = fsia6b_serio_disconnect +}; + +module_serio_driver(fsia6b_serio_drv) diff --git a/drivers/input/joystick/gamecon.c b/drivers/input/joystick/gamecon.c new file mode 100644 index 000000000..d84d453b5 --- /dev/null +++ b/drivers/input/joystick/gamecon.c @@ -0,0 +1,1048 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NES, SNES, N64, MultiSystem, PSX gamepad driver for Linux + * + * Copyright (c) 1999-2004 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2004 Peter Nelson <rufus-kernel@hackish.org> + * + * Based on the work of: + * Andree Borrmann John Dahlstrom + * David Kuder Nathan Hand + * Raphael Assenat + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/parport.h> +#include <linux/input.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("NES, SNES, N64, MultiSystem, PSX gamepad driver"); +MODULE_LICENSE("GPL"); + +#define GC_MAX_PORTS 3 +#define GC_MAX_DEVICES 5 + +struct gc_config { + int args[GC_MAX_DEVICES + 1]; + unsigned int nargs; +}; + +static struct gc_config gc_cfg[GC_MAX_PORTS]; + +module_param_array_named(map, gc_cfg[0].args, int, &gc_cfg[0].nargs, 0); +MODULE_PARM_DESC(map, "Describes first set of devices (<parport#>,<pad1>,<pad2>,..<pad5>)"); +module_param_array_named(map2, gc_cfg[1].args, int, &gc_cfg[1].nargs, 0); +MODULE_PARM_DESC(map2, "Describes second set of devices"); +module_param_array_named(map3, gc_cfg[2].args, int, &gc_cfg[2].nargs, 0); +MODULE_PARM_DESC(map3, "Describes third set of devices"); + +/* see also gs_psx_delay parameter in PSX support section */ + +enum gc_type { + GC_NONE = 0, + GC_SNES, + GC_NES, + GC_NES4, + GC_MULTI, + GC_MULTI2, + GC_N64, + GC_PSX, + GC_DDR, + GC_SNESMOUSE, + GC_MAX +}; + +#define GC_REFRESH_TIME HZ/100 + +struct gc_pad { + struct input_dev *dev; + enum gc_type type; + char phys[32]; +}; + +struct gc { + struct pardevice *pd; + struct gc_pad pads[GC_MAX_DEVICES]; + struct timer_list timer; + int pad_count[GC_MAX]; + int used; + int parportno; + struct mutex mutex; +}; + +struct gc_subdev { + unsigned int idx; +}; + +static struct gc *gc_base[3]; + +static const int gc_status_bit[] = { 0x40, 0x80, 0x20, 0x10, 0x08 }; + +static const char *gc_names[] = { + NULL, "SNES pad", "NES pad", "NES FourPort", "Multisystem joystick", + "Multisystem 2-button joystick", "N64 controller", "PSX controller", + "PSX DDR controller", "SNES mouse" +}; + +/* + * N64 support. + */ + +static const unsigned char gc_n64_bytes[] = { 0, 1, 13, 15, 14, 12, 10, 11, 2, 3 }; +static const short gc_n64_btn[] = { + BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, + BTN_TL, BTN_TR, BTN_TRIGGER, BTN_START +}; + +#define GC_N64_LENGTH 32 /* N64 bit length, not including stop bit */ +#define GC_N64_STOP_LENGTH 5 /* Length of encoded stop bit */ +#define GC_N64_CMD_00 0x11111111UL +#define GC_N64_CMD_01 0xd1111111UL +#define GC_N64_CMD_03 0xdd111111UL +#define GC_N64_CMD_1b 0xdd1dd111UL +#define GC_N64_CMD_c0 0x111111ddUL +#define GC_N64_CMD_80 0x1111111dUL +#define GC_N64_STOP_BIT 0x1d /* Encoded stop bit */ +#define GC_N64_REQUEST_DATA GC_N64_CMD_01 /* the request data command */ +#define GC_N64_DELAY 133 /* delay between transmit request, and response ready (us) */ +#define GC_N64_DWS 3 /* delay between write segments (required for sound playback because of ISA DMA) */ + /* GC_N64_DWS > 24 is known to fail */ +#define GC_N64_POWER_W 0xe2 /* power during write (transmit request) */ +#define GC_N64_POWER_R 0xfd /* power during read */ +#define GC_N64_OUT 0x1d /* output bits to the 4 pads */ + /* Reading the main axes of any N64 pad is known to fail if the corresponding bit */ + /* in GC_N64_OUT is pulled low on the output port (by any routine) for more */ + /* than 123 us */ +#define GC_N64_CLOCK 0x02 /* clock bits for read */ + +/* + * Used for rumble code. + */ + +/* Send encoded command */ +static void gc_n64_send_command(struct gc *gc, unsigned long cmd, + unsigned char target) +{ + struct parport *port = gc->pd->port; + int i; + + for (i = 0; i < GC_N64_LENGTH; i++) { + unsigned char data = (cmd >> i) & 1 ? target : 0; + parport_write_data(port, GC_N64_POWER_W | data); + udelay(GC_N64_DWS); + } +} + +/* Send stop bit */ +static void gc_n64_send_stop_bit(struct gc *gc, unsigned char target) +{ + struct parport *port = gc->pd->port; + int i; + + for (i = 0; i < GC_N64_STOP_LENGTH; i++) { + unsigned char data = (GC_N64_STOP_BIT >> i) & 1 ? target : 0; + parport_write_data(port, GC_N64_POWER_W | data); + udelay(GC_N64_DWS); + } +} + +/* + * gc_n64_read_packet() reads an N64 packet. + * Each pad uses one bit per byte. So all pads connected to this port + * are read in parallel. + */ + +static void gc_n64_read_packet(struct gc *gc, unsigned char *data) +{ + int i; + unsigned long flags; + +/* + * Request the pad to transmit data + */ + + local_irq_save(flags); + gc_n64_send_command(gc, GC_N64_REQUEST_DATA, GC_N64_OUT); + gc_n64_send_stop_bit(gc, GC_N64_OUT); + local_irq_restore(flags); + +/* + * Wait for the pad response to be loaded into the 33-bit register + * of the adapter. + */ + + udelay(GC_N64_DELAY); + +/* + * Grab data (ignoring the last bit, which is a stop bit) + */ + + for (i = 0; i < GC_N64_LENGTH; i++) { + parport_write_data(gc->pd->port, GC_N64_POWER_R); + udelay(2); + data[i] = parport_read_status(gc->pd->port); + parport_write_data(gc->pd->port, GC_N64_POWER_R | GC_N64_CLOCK); + } + +/* + * We must wait 200 ms here for the controller to reinitialize before + * the next read request. No worries as long as gc_read is polled less + * frequently than this. + */ + +} + +static void gc_n64_process_packet(struct gc *gc) +{ + unsigned char data[GC_N64_LENGTH]; + struct input_dev *dev; + int i, j, s; + signed char x, y; + + gc_n64_read_packet(gc, data); + + for (i = 0; i < GC_MAX_DEVICES; i++) { + + if (gc->pads[i].type != GC_N64) + continue; + + dev = gc->pads[i].dev; + s = gc_status_bit[i]; + + if (s & ~(data[8] | data[9])) { + + x = y = 0; + + for (j = 0; j < 8; j++) { + if (data[23 - j] & s) + x |= 1 << j; + if (data[31 - j] & s) + y |= 1 << j; + } + + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, -y); + + input_report_abs(dev, ABS_HAT0X, + !(s & data[6]) - !(s & data[7])); + input_report_abs(dev, ABS_HAT0Y, + !(s & data[4]) - !(s & data[5])); + + for (j = 0; j < 10; j++) + input_report_key(dev, gc_n64_btn[j], + s & data[gc_n64_bytes[j]]); + + input_sync(dev); + } + } +} + +static int gc_n64_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + int i; + unsigned long flags; + struct gc *gc = input_get_drvdata(dev); + struct gc_subdev *sdev = data; + unsigned char target = 1 << sdev->idx; /* select desired pin */ + + if (effect->type == FF_RUMBLE) { + struct ff_rumble_effect *rumble = &effect->u.rumble; + unsigned int cmd = + rumble->strong_magnitude || rumble->weak_magnitude ? + GC_N64_CMD_01 : GC_N64_CMD_00; + + local_irq_save(flags); + + /* Init Rumble - 0x03, 0x80, 0x01, (34)0x80 */ + gc_n64_send_command(gc, GC_N64_CMD_03, target); + gc_n64_send_command(gc, GC_N64_CMD_80, target); + gc_n64_send_command(gc, GC_N64_CMD_01, target); + for (i = 0; i < 32; i++) + gc_n64_send_command(gc, GC_N64_CMD_80, target); + gc_n64_send_stop_bit(gc, target); + + udelay(GC_N64_DELAY); + + /* Now start or stop it - 0x03, 0xc0, 0zx1b, (32)0x01/0x00 */ + gc_n64_send_command(gc, GC_N64_CMD_03, target); + gc_n64_send_command(gc, GC_N64_CMD_c0, target); + gc_n64_send_command(gc, GC_N64_CMD_1b, target); + for (i = 0; i < 32; i++) + gc_n64_send_command(gc, cmd, target); + gc_n64_send_stop_bit(gc, target); + + local_irq_restore(flags); + + } + + return 0; +} + +static int gc_n64_init_ff(struct input_dev *dev, int i) +{ + struct gc_subdev *sdev; + int err; + + sdev = kmalloc_obj(*sdev); + if (!sdev) + return -ENOMEM; + + sdev->idx = i; + + input_set_capability(dev, EV_FF, FF_RUMBLE); + + err = input_ff_create_memless(dev, sdev, gc_n64_play_effect); + if (err) { + kfree(sdev); + return err; + } + + return 0; +} + +/* + * NES/SNES support. + */ + +#define GC_NES_DELAY 6 /* Delay between bits - 6us */ +#define GC_NES_LENGTH 8 /* The NES pads use 8 bits of data */ +#define GC_SNES_LENGTH 12 /* The SNES true length is 16, but the + last 4 bits are unused */ +#define GC_SNESMOUSE_LENGTH 32 /* The SNES mouse uses 32 bits, the first + 16 bits are equivalent to a gamepad */ + +#define GC_NES_POWER 0xfc +#define GC_NES_CLOCK 0x01 +#define GC_NES_LATCH 0x02 + +static const unsigned char gc_nes_bytes[] = { 0, 1, 2, 3 }; +static const unsigned char gc_snes_bytes[] = { 8, 0, 2, 3, 9, 1, 10, 11 }; +static const short gc_snes_btn[] = { + BTN_A, BTN_B, BTN_SELECT, BTN_START, BTN_X, BTN_Y, BTN_TL, BTN_TR +}; + +/* + * gc_nes_read_packet() reads a NES/SNES packet. + * Each pad uses one bit per byte. So all pads connected to + * this port are read in parallel. + */ + +static void gc_nes_read_packet(struct gc *gc, int length, unsigned char *data) +{ + int i; + + parport_write_data(gc->pd->port, GC_NES_POWER | GC_NES_CLOCK | GC_NES_LATCH); + udelay(GC_NES_DELAY * 2); + parport_write_data(gc->pd->port, GC_NES_POWER | GC_NES_CLOCK); + + for (i = 0; i < length; i++) { + udelay(GC_NES_DELAY); + parport_write_data(gc->pd->port, GC_NES_POWER); + data[i] = parport_read_status(gc->pd->port) ^ 0x7f; + udelay(GC_NES_DELAY); + parport_write_data(gc->pd->port, GC_NES_POWER | GC_NES_CLOCK); + } +} + +static void gc_nes_process_packet(struct gc *gc) +{ + unsigned char data[GC_SNESMOUSE_LENGTH]; + struct gc_pad *pad; + struct input_dev *dev; + int i, j, s, len; + char x_rel, y_rel; + + len = gc->pad_count[GC_SNESMOUSE] ? GC_SNESMOUSE_LENGTH : + (gc->pad_count[GC_SNES] ? GC_SNES_LENGTH : GC_NES_LENGTH); + + gc_nes_read_packet(gc, len, data); + + for (i = 0; i < GC_MAX_DEVICES; i++) { + + pad = &gc->pads[i]; + dev = pad->dev; + s = gc_status_bit[i]; + + switch (pad->type) { + + case GC_NES: + + input_report_abs(dev, ABS_X, !(s & data[6]) - !(s & data[7])); + input_report_abs(dev, ABS_Y, !(s & data[4]) - !(s & data[5])); + + for (j = 0; j < 4; j++) + input_report_key(dev, gc_snes_btn[j], + s & data[gc_nes_bytes[j]]); + input_sync(dev); + break; + + case GC_SNES: + + input_report_abs(dev, ABS_X, !(s & data[6]) - !(s & data[7])); + input_report_abs(dev, ABS_Y, !(s & data[4]) - !(s & data[5])); + + for (j = 0; j < 8; j++) + input_report_key(dev, gc_snes_btn[j], + s & data[gc_snes_bytes[j]]); + input_sync(dev); + break; + + case GC_SNESMOUSE: + /* + * The 4 unused bits from SNES controllers appear + * to be ID bits so use them to make sure we are + * dealing with a mouse. + * gamepad is connected. This is important since + * my SNES gamepad sends 1's for bits 16-31, which + * cause the mouse pointer to quickly move to the + * upper left corner of the screen. + */ + if (!(s & data[12]) && !(s & data[13]) && + !(s & data[14]) && (s & data[15])) { + input_report_key(dev, BTN_LEFT, s & data[9]); + input_report_key(dev, BTN_RIGHT, s & data[8]); + + x_rel = y_rel = 0; + for (j = 0; j < 7; j++) { + x_rel <<= 1; + if (data[25 + j] & s) + x_rel |= 1; + + y_rel <<= 1; + if (data[17 + j] & s) + y_rel |= 1; + } + + if (x_rel) { + if (data[24] & s) + x_rel = -x_rel; + input_report_rel(dev, REL_X, x_rel); + } + + if (y_rel) { + if (data[16] & s) + y_rel = -y_rel; + input_report_rel(dev, REL_Y, y_rel); + } + + input_sync(dev); + } + break; + + default: + break; + } + } +} + +/* + * Multisystem joystick support + */ + +#define GC_MULTI_LENGTH 5 /* Multi system joystick packet length is 5 */ +#define GC_MULTI2_LENGTH 6 /* One more bit for one more button */ + +/* + * gc_multi_read_packet() reads a Multisystem joystick packet. + */ + +static void gc_multi_read_packet(struct gc *gc, int length, unsigned char *data) +{ + int i; + + for (i = 0; i < length; i++) { + parport_write_data(gc->pd->port, ~(1 << i)); + data[i] = parport_read_status(gc->pd->port) ^ 0x7f; + } +} + +static void gc_multi_process_packet(struct gc *gc) +{ + unsigned char data[GC_MULTI2_LENGTH]; + int data_len = gc->pad_count[GC_MULTI2] ? GC_MULTI2_LENGTH : GC_MULTI_LENGTH; + struct gc_pad *pad; + struct input_dev *dev; + int i, s; + + gc_multi_read_packet(gc, data_len, data); + + for (i = 0; i < GC_MAX_DEVICES; i++) { + pad = &gc->pads[i]; + dev = pad->dev; + s = gc_status_bit[i]; + + switch (pad->type) { + case GC_MULTI2: + input_report_key(dev, BTN_THUMB, s & data[5]); + fallthrough; + + case GC_MULTI: + input_report_abs(dev, ABS_X, + !(s & data[2]) - !(s & data[3])); + input_report_abs(dev, ABS_Y, + !(s & data[0]) - !(s & data[1])); + input_report_key(dev, BTN_TRIGGER, s & data[4]); + input_sync(dev); + break; + + default: + break; + } + } +} + +/* + * PSX support + * + * See documentation at: + * http://www.geocities.co.jp/Playtown/2004/psx/ps_eng.txt + * http://www.gamesx.com/controldata/psxcont/psxcont.htm + * + */ + +#define GC_PSX_DELAY 25 /* 25 usec */ +#define GC_PSX_LENGTH 8 /* talk to the controller in bits */ +#define GC_PSX_BYTES 6 /* the maximum number of bytes to read off the controller */ + +#define GC_PSX_MOUSE 1 /* Mouse */ +#define GC_PSX_NEGCON 2 /* NegCon */ +#define GC_PSX_NORMAL 4 /* Digital / Analog or Rumble in Digital mode */ +#define GC_PSX_ANALOG 5 /* Analog in Analog mode / Rumble in Green mode */ +#define GC_PSX_RUMBLE 7 /* Rumble in Red mode */ + +#define GC_PSX_CLOCK 0x04 /* Pin 4 */ +#define GC_PSX_COMMAND 0x01 /* Pin 2 */ +#define GC_PSX_POWER 0xf8 /* Pins 5-9 */ +#define GC_PSX_SELECT 0x02 /* Pin 3 */ + +#define GC_PSX_ID(x) ((x) >> 4) /* High nibble is device type */ +#define GC_PSX_LEN(x) (((x) & 0xf) << 1) /* Low nibble is length in bytes/2 */ + +static int gc_psx_delay = GC_PSX_DELAY; +module_param_named(psx_delay, gc_psx_delay, uint, 0); +MODULE_PARM_DESC(psx_delay, "Delay when accessing Sony PSX controller (usecs)"); + +static const short gc_psx_abs[] = { + ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_HAT0X, ABS_HAT0Y +}; +static const short gc_psx_btn[] = { + BTN_TL, BTN_TR, BTN_TL2, BTN_TR2, BTN_A, BTN_B, BTN_X, BTN_Y, + BTN_START, BTN_SELECT, BTN_THUMBL, BTN_THUMBR +}; +static const short gc_psx_ddr_btn[] = { BTN_0, BTN_1, BTN_2, BTN_3 }; + +/* + * gc_psx_command() writes 8bit command and reads 8bit data from + * the psx pad. + */ + +static void gc_psx_command(struct gc *gc, int b, unsigned char *data) +{ + struct parport *port = gc->pd->port; + int i, j, cmd, read; + + memset(data, 0, GC_MAX_DEVICES); + + for (i = 0; i < GC_PSX_LENGTH; i++, b >>= 1) { + cmd = (b & 1) ? GC_PSX_COMMAND : 0; + parport_write_data(port, cmd | GC_PSX_POWER); + udelay(gc_psx_delay); + + read = parport_read_status(port) ^ 0x80; + + for (j = 0; j < GC_MAX_DEVICES; j++) { + struct gc_pad *pad = &gc->pads[j]; + + if (pad->type == GC_PSX || pad->type == GC_DDR) + data[j] |= (read & gc_status_bit[j]) ? (1 << i) : 0; + } + + parport_write_data(gc->pd->port, cmd | GC_PSX_CLOCK | GC_PSX_POWER); + udelay(gc_psx_delay); + } +} + +/* + * gc_psx_read_packet() reads a whole psx packet and returns + * device identifier code. + */ + +static void gc_psx_read_packet(struct gc *gc, + unsigned char data[GC_MAX_DEVICES][GC_PSX_BYTES], + unsigned char id[GC_MAX_DEVICES]) +{ + int i, j, max_len = 0; + unsigned long flags; + unsigned char data2[GC_MAX_DEVICES]; + + /* Select pad */ + parport_write_data(gc->pd->port, GC_PSX_CLOCK | GC_PSX_SELECT | GC_PSX_POWER); + udelay(gc_psx_delay); + /* Deselect, begin command */ + parport_write_data(gc->pd->port, GC_PSX_CLOCK | GC_PSX_POWER); + udelay(gc_psx_delay); + + local_irq_save(flags); + + gc_psx_command(gc, 0x01, data2); /* Access pad */ + gc_psx_command(gc, 0x42, id); /* Get device ids */ + gc_psx_command(gc, 0, data2); /* Dump status */ + + /* Find the longest pad */ + for (i = 0; i < GC_MAX_DEVICES; i++) { + struct gc_pad *pad = &gc->pads[i]; + + if ((pad->type == GC_PSX || pad->type == GC_DDR) && + GC_PSX_LEN(id[i]) > max_len && + GC_PSX_LEN(id[i]) <= GC_PSX_BYTES) { + max_len = GC_PSX_LEN(id[i]); + } + } + + /* Read in all the data */ + for (i = 0; i < max_len; i++) { + gc_psx_command(gc, 0, data2); + for (j = 0; j < GC_MAX_DEVICES; j++) + data[j][i] = data2[j]; + } + + local_irq_restore(flags); + + parport_write_data(gc->pd->port, GC_PSX_CLOCK | GC_PSX_SELECT | GC_PSX_POWER); + + /* Set id's to the real value */ + for (i = 0; i < GC_MAX_DEVICES; i++) + id[i] = GC_PSX_ID(id[i]); +} + +static void gc_psx_report_one(struct gc_pad *pad, unsigned char psx_type, + unsigned char *data) +{ + struct input_dev *dev = pad->dev; + int i; + + switch (psx_type) { + + case GC_PSX_RUMBLE: + + input_report_key(dev, BTN_THUMBL, ~data[0] & 0x04); + input_report_key(dev, BTN_THUMBR, ~data[0] & 0x02); + fallthrough; + + case GC_PSX_NEGCON: + case GC_PSX_ANALOG: + + if (pad->type == GC_DDR) { + for (i = 0; i < 4; i++) + input_report_key(dev, gc_psx_ddr_btn[i], + ~data[0] & (0x10 << i)); + } else { + for (i = 0; i < 4; i++) + input_report_abs(dev, gc_psx_abs[i + 2], + data[i + 2]); + + input_report_abs(dev, ABS_X, + !!(data[0] & 0x80) * 128 + !(data[0] & 0x20) * 127); + input_report_abs(dev, ABS_Y, + !!(data[0] & 0x10) * 128 + !(data[0] & 0x40) * 127); + } + + for (i = 0; i < 8; i++) + input_report_key(dev, gc_psx_btn[i], ~data[1] & (1 << i)); + + input_report_key(dev, BTN_START, ~data[0] & 0x08); + input_report_key(dev, BTN_SELECT, ~data[0] & 0x01); + + input_sync(dev); + + break; + + case GC_PSX_NORMAL: + + if (pad->type == GC_DDR) { + for (i = 0; i < 4; i++) + input_report_key(dev, gc_psx_ddr_btn[i], + ~data[0] & (0x10 << i)); + } else { + input_report_abs(dev, ABS_X, + !!(data[0] & 0x80) * 128 + !(data[0] & 0x20) * 127); + input_report_abs(dev, ABS_Y, + !!(data[0] & 0x10) * 128 + !(data[0] & 0x40) * 127); + + /* + * For some reason if the extra axes are left unset + * they drift. + * for (i = 0; i < 4; i++) + input_report_abs(dev, gc_psx_abs[i + 2], 128); + * This needs to be debugged properly, + * maybe fuzz processing needs to be done + * in input_sync() + * --vojtech + */ + } + + for (i = 0; i < 8; i++) + input_report_key(dev, gc_psx_btn[i], ~data[1] & (1 << i)); + + input_report_key(dev, BTN_START, ~data[0] & 0x08); + input_report_key(dev, BTN_SELECT, ~data[0] & 0x01); + + input_sync(dev); + + break; + + default: /* not a pad, ignore */ + break; + } +} + +static void gc_psx_process_packet(struct gc *gc) +{ + unsigned char data[GC_MAX_DEVICES][GC_PSX_BYTES]; + unsigned char id[GC_MAX_DEVICES]; + struct gc_pad *pad; + int i; + + gc_psx_read_packet(gc, data, id); + + for (i = 0; i < GC_MAX_DEVICES; i++) { + pad = &gc->pads[i]; + if (pad->type == GC_PSX || pad->type == GC_DDR) + gc_psx_report_one(pad, id[i], data[i]); + } +} + +/* + * gc_timer() initiates reads of console pads data. + */ + +static void gc_timer(struct timer_list *t) +{ + struct gc *gc = timer_container_of(gc, t, timer); + +/* + * N64 pads - must be read first, any read confuses them for 200 us + */ + + if (gc->pad_count[GC_N64]) + gc_n64_process_packet(gc); + +/* + * NES and SNES pads or mouse + */ + + if (gc->pad_count[GC_NES] || + gc->pad_count[GC_SNES] || + gc->pad_count[GC_SNESMOUSE]) { + gc_nes_process_packet(gc); + } + +/* + * Multi and Multi2 joysticks + */ + + if (gc->pad_count[GC_MULTI] || gc->pad_count[GC_MULTI2]) + gc_multi_process_packet(gc); + +/* + * PSX controllers + */ + + if (gc->pad_count[GC_PSX] || gc->pad_count[GC_DDR]) + gc_psx_process_packet(gc); + + mod_timer(&gc->timer, jiffies + GC_REFRESH_TIME); +} + +static int gc_open(struct input_dev *dev) +{ + struct gc *gc = input_get_drvdata(dev); + + scoped_guard(mutex_intr, &gc->mutex) { + if (!gc->used++) { + parport_claim(gc->pd); + parport_write_control(gc->pd->port, 0x04); + mod_timer(&gc->timer, jiffies + GC_REFRESH_TIME); + } + + return 0; + } + + return -EINTR; +} + +static void gc_close(struct input_dev *dev) +{ + struct gc *gc = input_get_drvdata(dev); + + guard(mutex)(&gc->mutex); + + if (!--gc->used) { + timer_delete_sync(&gc->timer); + parport_write_control(gc->pd->port, 0x00); + parport_release(gc->pd); + } +} + +static int gc_setup_pad(struct gc *gc, int idx, int pad_type) +{ + struct gc_pad *pad = &gc->pads[idx]; + struct input_dev *input_dev; + int i; + int err; + + if (pad_type < 1 || pad_type >= GC_MAX) { + pr_err("Pad type %d unknown\n", pad_type); + return -EINVAL; + } + + pad->dev = input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("Not enough memory for input device\n"); + return -ENOMEM; + } + + pad->type = pad_type; + + snprintf(pad->phys, sizeof(pad->phys), + "%s/input%d", gc->pd->port->name, idx); + + input_dev->name = gc_names[pad_type]; + input_dev->phys = pad->phys; + input_dev->id.bustype = BUS_PARPORT; + input_dev->id.vendor = 0x0001; + input_dev->id.product = pad_type; + input_dev->id.version = 0x0100; + + input_set_drvdata(input_dev, gc); + + input_dev->open = gc_open; + input_dev->close = gc_close; + + if (pad_type != GC_SNESMOUSE) { + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < 2; i++) + input_set_abs_params(input_dev, ABS_X + i, -1, 1, 0, 0); + } else + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + + gc->pad_count[pad_type]++; + + switch (pad_type) { + + case GC_N64: + for (i = 0; i < 10; i++) + input_set_capability(input_dev, EV_KEY, gc_n64_btn[i]); + + for (i = 0; i < 2; i++) { + input_set_abs_params(input_dev, ABS_X + i, -127, 126, 0, 2); + input_set_abs_params(input_dev, ABS_HAT0X + i, -1, 1, 0, 0); + } + + err = gc_n64_init_ff(input_dev, idx); + if (err) { + pr_warn("Failed to initiate rumble for N64 device %d\n", + idx); + goto err_free_dev; + } + + break; + + case GC_SNESMOUSE: + input_set_capability(input_dev, EV_KEY, BTN_LEFT); + input_set_capability(input_dev, EV_KEY, BTN_RIGHT); + input_set_capability(input_dev, EV_REL, REL_X); + input_set_capability(input_dev, EV_REL, REL_Y); + break; + + case GC_SNES: + for (i = 4; i < 8; i++) + input_set_capability(input_dev, EV_KEY, gc_snes_btn[i]); + fallthrough; + + case GC_NES: + for (i = 0; i < 4; i++) + input_set_capability(input_dev, EV_KEY, gc_snes_btn[i]); + break; + + case GC_MULTI2: + input_set_capability(input_dev, EV_KEY, BTN_THUMB); + fallthrough; + + case GC_MULTI: + input_set_capability(input_dev, EV_KEY, BTN_TRIGGER); + break; + + case GC_PSX: + for (i = 0; i < 6; i++) + input_set_abs_params(input_dev, + gc_psx_abs[i], 4, 252, 0, 2); + for (i = 0; i < 12; i++) + input_set_capability(input_dev, EV_KEY, gc_psx_btn[i]); + break; + + break; + + case GC_DDR: + for (i = 0; i < 4; i++) + input_set_capability(input_dev, EV_KEY, + gc_psx_ddr_btn[i]); + for (i = 0; i < 12; i++) + input_set_capability(input_dev, EV_KEY, gc_psx_btn[i]); + + break; + } + + err = input_register_device(pad->dev); + if (err) + goto err_free_dev; + + return 0; + +err_free_dev: + input_free_device(pad->dev); + pad->dev = NULL; + return err; +} + +static void gc_attach(struct parport *pp) +{ + struct gc *gc; + struct pardevice *pd; + int i, port_idx; + int count = 0; + int *pads, n_pads; + struct pardev_cb gc_parport_cb; + + for (port_idx = 0; port_idx < GC_MAX_PORTS; port_idx++) { + if (gc_cfg[port_idx].nargs == 0 || gc_cfg[port_idx].args[0] < 0) + continue; + + if (gc_cfg[port_idx].args[0] == pp->number) + break; + } + + if (port_idx == GC_MAX_PORTS) { + pr_debug("Not using parport%d.\n", pp->number); + return; + } + pads = gc_cfg[port_idx].args + 1; + n_pads = gc_cfg[port_idx].nargs - 1; + + memset(&gc_parport_cb, 0, sizeof(gc_parport_cb)); + gc_parport_cb.flags = PARPORT_FLAG_EXCL; + + pd = parport_register_dev_model(pp, "gamecon", &gc_parport_cb, + port_idx); + if (!pd) { + pr_err("parport busy already - lp.o loaded?\n"); + return; + } + + gc = kzalloc_obj(*gc); + if (!gc) { + pr_err("Not enough memory\n"); + goto err_unreg_pardev; + } + + mutex_init(&gc->mutex); + gc->pd = pd; + gc->parportno = pp->number; + timer_setup(&gc->timer, gc_timer, 0); + + for (i = 0; i < n_pads && i < GC_MAX_DEVICES; i++) { + if (!pads[i]) + continue; + + if (gc_setup_pad(gc, i, pads[i])) + goto err_unreg_devs; + + count++; + } + + if (count == 0) { + pr_err("No valid devices specified\n"); + goto err_free_gc; + } + + gc_base[port_idx] = gc; + return; + + err_unreg_devs: + while (--i >= 0) + if (gc->pads[i].dev) + input_unregister_device(gc->pads[i].dev); + err_free_gc: + kfree(gc); + err_unreg_pardev: + parport_unregister_device(pd); +} + +static void gc_detach(struct parport *port) +{ + int i; + struct gc *gc; + + for (i = 0; i < GC_MAX_PORTS; i++) { + if (gc_base[i] && gc_base[i]->parportno == port->number) + break; + } + + if (i == GC_MAX_PORTS) + return; + + gc = gc_base[i]; + gc_base[i] = NULL; + + for (i = 0; i < GC_MAX_DEVICES; i++) + if (gc->pads[i].dev) + input_unregister_device(gc->pads[i].dev); + parport_unregister_device(gc->pd); + kfree(gc); +} + +static struct parport_driver gc_parport_driver = { + .name = "gamecon", + .match_port = gc_attach, + .detach = gc_detach, +}; + +static int __init gc_init(void) +{ + int i; + int have_dev = 0; + + for (i = 0; i < GC_MAX_PORTS; i++) { + if (gc_cfg[i].nargs == 0 || gc_cfg[i].args[0] < 0) + continue; + + if (gc_cfg[i].nargs < 2) { + pr_err("at least one device must be specified\n"); + return -EINVAL; + } + + have_dev = 1; + } + + if (!have_dev) + return -ENODEV; + + return parport_register_driver(&gc_parport_driver); +} + +static void __exit gc_exit(void) +{ + parport_unregister_driver(&gc_parport_driver); +} + +module_init(gc_init); +module_exit(gc_exit); diff --git a/drivers/input/joystick/gf2k.c b/drivers/input/joystick/gf2k.c new file mode 100644 index 000000000..1d843115d --- /dev/null +++ b/drivers/input/joystick/gf2k.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * Genius Flight 2000 joystick driver for Linux + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/gameport.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "Genius Flight 2000 joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define GF2K_START 400 /* The time we wait for the first bit [400 us] */ +#define GF2K_STROBE 40 /* The time we wait for the first bit [40 us] */ +#define GF2K_TIMEOUT 4 /* Wait for everything to settle [4 ms] */ +#define GF2K_LENGTH 80 /* Max number of triplets in a packet */ + +/* + * Genius joystick ids ... + */ + +#define GF2K_ID_G09 1 +#define GF2K_ID_F30D 2 +#define GF2K_ID_F30 3 +#define GF2K_ID_F31D 4 +#define GF2K_ID_F305 5 +#define GF2K_ID_F23P 6 +#define GF2K_ID_F31 7 +#define GF2K_ID_MAX 7 + +static char gf2k_length[] = { 40, 40, 40, 40, 40, 40, 40, 40 }; +static char gf2k_hat_to_axis[][2] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + +static char *gf2k_names[] = {"", "Genius G-09D", "Genius F-30D", "Genius F-30", "Genius MaxFighter F-31D", + "Genius F-30-5", "Genius Flight2000 F-23", "Genius F-31"}; +static unsigned char gf2k_hats[] = { 0, 2, 0, 0, 2, 0, 2, 0 }; +static unsigned char gf2k_axes[] = { 0, 2, 0, 0, 4, 0, 4, 0 }; +static unsigned char gf2k_joys[] = { 0, 0, 0, 0,10, 0, 8, 0 }; +static unsigned char gf2k_pads[] = { 0, 6, 0, 0, 0, 0, 0, 0 }; +static unsigned char gf2k_lens[] = { 0,18, 0, 0,18, 0,18, 0 }; + +static unsigned char gf2k_abs[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER, ABS_GAS, ABS_BRAKE }; +static short gf2k_btn_joy[] = { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4 }; +static short gf2k_btn_pad[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_TL2, BTN_TR2, BTN_START, BTN_SELECT }; + + +static short gf2k_seq_reset[] = { 240, 340, 0 }; +static short gf2k_seq_digital[] = { 590, 320, 860, 0 }; + +struct gf2k { + struct gameport *gameport; + struct input_dev *dev; + int reads; + int bads; + unsigned char id; + unsigned char length; + char phys[32]; +}; + +/* + * gf2k_read_packet() reads a Genius Flight2000 packet. + */ + +static int gf2k_read_packet(struct gameport *gameport, int length, char *data) +{ + unsigned char u, v; + int i; + unsigned int t, p; + unsigned long flags; + + t = gameport_time(gameport, GF2K_START); + p = gameport_time(gameport, GF2K_STROBE); + + i = 0; + + local_irq_save(flags); + + gameport_trigger(gameport); + v = gameport_read(gameport); + + while (t > 0 && i < length) { + t--; u = v; + v = gameport_read(gameport); + if (v & ~u & 0x10) { + data[i++] = v >> 5; + t = p; + } + } + + local_irq_restore(flags); + + return i; +} + +/* + * gf2k_trigger_seq() initializes a Genius Flight2000 joystick + * into digital mode. + */ + +static void gf2k_trigger_seq(struct gameport *gameport, short *seq) +{ + + unsigned long flags; + int i, t; + + local_irq_save(flags); + + i = 0; + do { + gameport_trigger(gameport); + t = gameport_time(gameport, GF2K_TIMEOUT * 1000); + while ((gameport_read(gameport) & 1) && t) t--; + udelay(seq[i]); + } while (seq[++i]); + + gameport_trigger(gameport); + + local_irq_restore(flags); +} + +/* + * js_sw_get_bits() composes bits from the triplet buffer into a __u64. + * Parameter 'pos' is bit number inside packet where to start at, 'num' is number + * of bits to be read, 'shift' is offset in the resulting __u64 to start at, bits + * is number of bits per triplet. + */ + +#define GB(p,n,s) gf2k_get_bits(data, p, n, s) + +static int gf2k_get_bits(unsigned char *buf, int pos, int num, int shift) +{ + __u64 data = 0; + int i; + + for (i = 0; i < num / 3 + 2; i++) + data |= buf[pos / 3 + i] << (i * 3); + data >>= pos % 3; + data &= (1 << num) - 1; + data <<= shift; + + return data; +} + +static void gf2k_read(struct gf2k *gf2k, unsigned char *data) +{ + struct input_dev *dev = gf2k->dev; + int i, t; + + for (i = 0; i < 4 && i < gf2k_axes[gf2k->id]; i++) + input_report_abs(dev, gf2k_abs[i], GB(i<<3,8,0) | GB(i+46,1,8) | GB(i+50,1,9)); + + for (i = 0; i < 2 && i < gf2k_axes[gf2k->id] - 4; i++) + input_report_abs(dev, gf2k_abs[i], GB(i*9+60,8,0) | GB(i+54,1,9)); + + t = GB(40,4,0); + + if (t < ARRAY_SIZE(gf2k_hat_to_axis)) + for (i = 0; i < gf2k_hats[gf2k->id]; i++) + input_report_abs(dev, ABS_HAT0X + i, + gf2k_hat_to_axis[t][i]); + + t = GB(44,2,0) | GB(32,8,2) | GB(78,2,10); + + for (i = 0; i < gf2k_joys[gf2k->id]; i++) + input_report_key(dev, gf2k_btn_joy[i], (t >> i) & 1); + + for (i = 0; i < gf2k_pads[gf2k->id]; i++) + input_report_key(dev, gf2k_btn_pad[i], (t >> i) & 1); + + input_sync(dev); +} + +/* + * gf2k_poll() reads and analyzes Genius joystick data. + */ + +static void gf2k_poll(struct gameport *gameport) +{ + struct gf2k *gf2k = gameport_get_drvdata(gameport); + unsigned char data[GF2K_LENGTH]; + + gf2k->reads++; + + if (gf2k_read_packet(gf2k->gameport, gf2k_length[gf2k->id], data) < gf2k_length[gf2k->id]) + gf2k->bads++; + else + gf2k_read(gf2k, data); +} + +static int gf2k_open(struct input_dev *dev) +{ + struct gf2k *gf2k = input_get_drvdata(dev); + + gameport_start_polling(gf2k->gameport); + return 0; +} + +static void gf2k_close(struct input_dev *dev) +{ + struct gf2k *gf2k = input_get_drvdata(dev); + + gameport_stop_polling(gf2k->gameport); +} + +/* + * gf2k_connect() probes for Genius id joysticks. + */ + +static int gf2k_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct gf2k *gf2k; + struct input_dev *input_dev; + unsigned char data[GF2K_LENGTH]; + int i, err; + + gf2k = kzalloc_obj(*gf2k); + input_dev = input_allocate_device(); + if (!gf2k || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + gf2k->gameport = gameport; + gf2k->dev = input_dev; + + gameport_set_drvdata(gameport, gf2k); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + gf2k_trigger_seq(gameport, gf2k_seq_reset); + + msleep(GF2K_TIMEOUT); + + gf2k_trigger_seq(gameport, gf2k_seq_digital); + + msleep(GF2K_TIMEOUT); + + if (gf2k_read_packet(gameport, GF2K_LENGTH, data) < 12) { + err = -ENODEV; + goto fail2; + } + + if (!(gf2k->id = GB(7,2,0) | GB(3,3,2) | GB(0,3,5))) { + err = -ENODEV; + goto fail2; + } + +#ifdef RESET_WORKS + if ((gf2k->id != (GB(19,2,0) | GB(15,3,2) | GB(12,3,5))) && + (gf2k->id != (GB(31,2,0) | GB(27,3,2) | GB(24,3,5)))) { + err = -ENODEV; + goto fail2; + } +#else + gf2k->id = 6; +#endif + + if (gf2k->id > GF2K_ID_MAX || !gf2k_axes[gf2k->id]) { + printk(KERN_WARNING "gf2k.c: Not yet supported joystick on %s. [id: %d type:%s]\n", + gameport->phys, gf2k->id, gf2k->id > GF2K_ID_MAX ? "Unknown" : gf2k_names[gf2k->id]); + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, gf2k_poll); + gameport_set_poll_interval(gameport, 20); + + snprintf(gf2k->phys, sizeof(gf2k->phys), "%s/input0", gameport->phys); + + gf2k->length = gf2k_lens[gf2k->id]; + + input_dev->name = gf2k_names[gf2k->id]; + input_dev->phys = gf2k->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_GENIUS; + input_dev->id.product = gf2k->id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, gf2k); + + input_dev->open = gf2k_open; + input_dev->close = gf2k_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < gf2k_axes[gf2k->id]; i++) + set_bit(gf2k_abs[i], input_dev->absbit); + + for (i = 0; i < gf2k_hats[gf2k->id]; i++) + input_set_abs_params(input_dev, ABS_HAT0X + i, -1, 1, 0, 0); + + for (i = 0; i < gf2k_joys[gf2k->id]; i++) + set_bit(gf2k_btn_joy[i], input_dev->keybit); + + for (i = 0; i < gf2k_pads[gf2k->id]; i++) + set_bit(gf2k_btn_pad[i], input_dev->keybit); + + gf2k_read_packet(gameport, gf2k->length, data); + gf2k_read(gf2k, data); + + for (i = 0; i < gf2k_axes[gf2k->id]; i++) { + int max = i < 2 ? + input_abs_get_val(input_dev, gf2k_abs[i]) * 2 : + input_abs_get_val(input_dev, gf2k_abs[0]) + + input_abs_get_val(input_dev, gf2k_abs[1]); + int flat = i < 2 ? 24 : 0; + + input_set_abs_params(input_dev, gf2k_abs[i], + 32, max - 32, 8, flat); + } + + err = input_register_device(gf2k->dev); + if (err) + goto fail2; + + return 0; + + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + input_free_device(input_dev); + kfree(gf2k); + return err; +} + +static void gf2k_disconnect(struct gameport *gameport) +{ + struct gf2k *gf2k = gameport_get_drvdata(gameport); + + input_unregister_device(gf2k->dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(gf2k); +} + +static struct gameport_driver gf2k_drv = { + .driver = { + .name = "gf2k", + }, + .description = DRIVER_DESC, + .connect = gf2k_connect, + .disconnect = gf2k_disconnect, +}; + +module_gameport_driver(gf2k_drv); diff --git a/drivers/input/joystick/grip.c b/drivers/input/joystick/grip.c new file mode 100644 index 000000000..2a3f768a7 --- /dev/null +++ b/drivers/input/joystick/grip.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1998-2001 Vojtech Pavlik + */ + +/* + * Gravis/Kensington GrIP protocol joystick and gamepad driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/gameport.h> +#include <linux/input.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "Gravis GrIP protocol joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define GRIP_MODE_GPP 1 +#define GRIP_MODE_BD 2 +#define GRIP_MODE_XT 3 +#define GRIP_MODE_DC 4 + +#define GRIP_LENGTH_GPP 24 +#define GRIP_STROBE_GPP 200 /* 200 us */ +#define GRIP_LENGTH_XT 4 +#define GRIP_STROBE_XT 64 /* 64 us */ +#define GRIP_MAX_CHUNKS_XT 10 +#define GRIP_MAX_BITS_XT 30 + +struct grip { + struct gameport *gameport; + struct input_dev *dev[2]; + unsigned char mode[2]; + int reads; + int bads; + char phys[2][32]; +}; + +static int grip_btn_gpp[] = { BTN_START, BTN_SELECT, BTN_TR2, BTN_Y, 0, BTN_TL2, BTN_A, BTN_B, BTN_X, 0, BTN_TL, BTN_TR, -1 }; +static int grip_btn_bd[] = { BTN_THUMB, BTN_THUMB2, BTN_TRIGGER, BTN_TOP, BTN_BASE, -1 }; +static int grip_btn_xt[] = { BTN_TRIGGER, BTN_THUMB, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_SELECT, BTN_START, BTN_MODE, -1 }; +static int grip_btn_dc[] = { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_BASE5, -1 }; + +static int grip_abs_gpp[] = { ABS_X, ABS_Y, -1 }; +static int grip_abs_bd[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, -1 }; +static int grip_abs_xt[] = { ABS_X, ABS_Y, ABS_BRAKE, ABS_GAS, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, -1 }; +static int grip_abs_dc[] = { ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, -1 }; + +static char *grip_name[] = { NULL, "Gravis GamePad Pro", "Gravis Blackhawk Digital", + "Gravis Xterminator Digital", "Gravis Xterminator DualControl" }; +static int *grip_abs[] = { NULL, grip_abs_gpp, grip_abs_bd, grip_abs_xt, grip_abs_dc }; +static int *grip_btn[] = { NULL, grip_btn_gpp, grip_btn_bd, grip_btn_xt, grip_btn_dc }; +static char grip_anx[] = { 0, 0, 3, 5, 5 }; +static char grip_cen[] = { 0, 0, 2, 2, 4 }; + +/* + * grip_gpp_read_packet() reads a Gravis GamePad Pro packet. + */ + +static int grip_gpp_read_packet(struct gameport *gameport, int shift, unsigned int *data) +{ + unsigned long flags; + unsigned char u, v; + unsigned int t; + int i; + + int strobe = gameport_time(gameport, GRIP_STROBE_GPP); + + data[0] = 0; + t = strobe; + i = 0; + + local_irq_save(flags); + + v = gameport_read(gameport) >> shift; + + do { + t--; + u = v; v = (gameport_read(gameport) >> shift) & 3; + if (~v & u & 1) { + data[0] |= (v >> 1) << i++; + t = strobe; + } + } while (i < GRIP_LENGTH_GPP && t > 0); + + local_irq_restore(flags); + + if (i < GRIP_LENGTH_GPP) return -1; + + for (i = 0; i < GRIP_LENGTH_GPP && (data[0] & 0xfe4210) ^ 0x7c0000; i++) + data[0] = data[0] >> 1 | (data[0] & 1) << (GRIP_LENGTH_GPP - 1); + + return -(i == GRIP_LENGTH_GPP); +} + +/* + * grip_xt_read_packet() reads a Gravis Xterminator packet. + */ + +static int grip_xt_read_packet(struct gameport *gameport, int shift, unsigned int *data) +{ + unsigned int i, j, buf, crc; + unsigned char u, v, w; + unsigned long flags; + unsigned int t; + char status; + + int strobe = gameport_time(gameport, GRIP_STROBE_XT); + + data[0] = data[1] = data[2] = data[3] = 0; + status = buf = i = j = 0; + t = strobe; + + local_irq_save(flags); + + v = w = (gameport_read(gameport) >> shift) & 3; + + do { + t--; + u = (gameport_read(gameport) >> shift) & 3; + + if (u ^ v) { + + if ((u ^ v) & 1) { + buf = (buf << 1) | (u >> 1); + t = strobe; + i++; + } else + + if ((((u ^ v) & (v ^ w)) >> 1) & ~(u | v | w) & 1) { + if (i == 20) { + crc = buf ^ (buf >> 7) ^ (buf >> 14); + if (!((crc ^ (0x25cb9e70 >> ((crc >> 2) & 0x1c))) & 0xf)) { + data[buf >> 18] = buf >> 4; + status |= 1 << (buf >> 18); + } + j++; + } + t = strobe; + buf = 0; + i = 0; + } + w = v; + v = u; + } + + } while (status != 0xf && i < GRIP_MAX_BITS_XT && j < GRIP_MAX_CHUNKS_XT && t > 0); + + local_irq_restore(flags); + + return -(status != 0xf); +} + +/* + * grip_timer() repeatedly polls the joysticks and generates events. + */ + +static void grip_poll(struct gameport *gameport) +{ + struct grip *grip = gameport_get_drvdata(gameport); + unsigned int data[GRIP_LENGTH_XT]; + struct input_dev *dev; + int i, j; + + for (i = 0; i < 2; i++) { + + dev = grip->dev[i]; + if (!dev) + continue; + + grip->reads++; + + switch (grip->mode[i]) { + + case GRIP_MODE_GPP: + + if (grip_gpp_read_packet(grip->gameport, (i << 1) + 4, data)) { + grip->bads++; + break; + } + + input_report_abs(dev, ABS_X, ((*data >> 15) & 1) - ((*data >> 16) & 1)); + input_report_abs(dev, ABS_Y, ((*data >> 13) & 1) - ((*data >> 12) & 1)); + + for (j = 0; j < 12; j++) + if (grip_btn_gpp[j]) + input_report_key(dev, grip_btn_gpp[j], (*data >> j) & 1); + + break; + + case GRIP_MODE_BD: + + if (grip_xt_read_packet(grip->gameport, (i << 1) + 4, data)) { + grip->bads++; + break; + } + + input_report_abs(dev, ABS_X, (data[0] >> 2) & 0x3f); + input_report_abs(dev, ABS_Y, 63 - ((data[0] >> 8) & 0x3f)); + input_report_abs(dev, ABS_THROTTLE, (data[2] >> 8) & 0x3f); + + input_report_abs(dev, ABS_HAT0X, ((data[2] >> 1) & 1) - ( data[2] & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[2] >> 2) & 1) - ((data[2] >> 3) & 1)); + + for (j = 0; j < 5; j++) + input_report_key(dev, grip_btn_bd[j], (data[3] >> (j + 4)) & 1); + + break; + + case GRIP_MODE_XT: + + if (grip_xt_read_packet(grip->gameport, (i << 1) + 4, data)) { + grip->bads++; + break; + } + + input_report_abs(dev, ABS_X, (data[0] >> 2) & 0x3f); + input_report_abs(dev, ABS_Y, 63 - ((data[0] >> 8) & 0x3f)); + input_report_abs(dev, ABS_BRAKE, (data[1] >> 2) & 0x3f); + input_report_abs(dev, ABS_GAS, (data[1] >> 8) & 0x3f); + input_report_abs(dev, ABS_THROTTLE, (data[2] >> 8) & 0x3f); + + input_report_abs(dev, ABS_HAT0X, ((data[2] >> 1) & 1) - ( data[2] & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[2] >> 2) & 1) - ((data[2] >> 3) & 1)); + input_report_abs(dev, ABS_HAT1X, ((data[2] >> 5) & 1) - ((data[2] >> 4) & 1)); + input_report_abs(dev, ABS_HAT1Y, ((data[2] >> 6) & 1) - ((data[2] >> 7) & 1)); + + for (j = 0; j < 11; j++) + input_report_key(dev, grip_btn_xt[j], (data[3] >> (j + 3)) & 1); + break; + + case GRIP_MODE_DC: + + if (grip_xt_read_packet(grip->gameport, (i << 1) + 4, data)) { + grip->bads++; + break; + } + + input_report_abs(dev, ABS_X, (data[0] >> 2) & 0x3f); + input_report_abs(dev, ABS_Y, (data[0] >> 8) & 0x3f); + input_report_abs(dev, ABS_RX, (data[1] >> 2) & 0x3f); + input_report_abs(dev, ABS_RY, (data[1] >> 8) & 0x3f); + input_report_abs(dev, ABS_THROTTLE, (data[2] >> 8) & 0x3f); + + input_report_abs(dev, ABS_HAT0X, ((data[2] >> 1) & 1) - ( data[2] & 1)); + input_report_abs(dev, ABS_HAT0Y, ((data[2] >> 2) & 1) - ((data[2] >> 3) & 1)); + + for (j = 0; j < 9; j++) + input_report_key(dev, grip_btn_dc[j], (data[3] >> (j + 3)) & 1); + break; + + + } + + input_sync(dev); + } +} + +static int grip_open(struct input_dev *dev) +{ + struct grip *grip = input_get_drvdata(dev); + + gameport_start_polling(grip->gameport); + return 0; +} + +static void grip_close(struct input_dev *dev) +{ + struct grip *grip = input_get_drvdata(dev); + + gameport_stop_polling(grip->gameport); +} + +static int grip_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct grip *grip; + struct input_dev *input_dev; + unsigned int data[GRIP_LENGTH_XT]; + int i, j, t; + int err; + + grip = kzalloc_obj(*grip); + if (!grip) + return -ENOMEM; + + grip->gameport = gameport; + + gameport_set_drvdata(gameport, grip); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + for (i = 0; i < 2; i++) { + if (!grip_gpp_read_packet(gameport, (i << 1) + 4, data)) { + grip->mode[i] = GRIP_MODE_GPP; + continue; + } + if (!grip_xt_read_packet(gameport, (i << 1) + 4, data)) { + if (!(data[3] & 7)) { + grip->mode[i] = GRIP_MODE_BD; + continue; + } + if (!(data[2] & 0xf0)) { + grip->mode[i] = GRIP_MODE_XT; + continue; + } + grip->mode[i] = GRIP_MODE_DC; + continue; + } + } + + if (!grip->mode[0] && !grip->mode[1]) { + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, grip_poll); + gameport_set_poll_interval(gameport, 20); + + for (i = 0; i < 2; i++) { + if (!grip->mode[i]) + continue; + + grip->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto fail3; + } + + snprintf(grip->phys[i], sizeof(grip->phys[i]), + "%s/input%d", gameport->phys, i); + + input_dev->name = grip_name[grip->mode[i]]; + input_dev->phys = grip->phys[i]; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_GRAVIS; + input_dev->id.product = grip->mode[i]; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, grip); + + input_dev->open = grip_open; + input_dev->close = grip_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (j = 0; (t = grip_abs[grip->mode[i]][j]) >= 0; j++) { + + if (j < grip_cen[grip->mode[i]]) + input_set_abs_params(input_dev, t, 14, 52, 1, 2); + else if (j < grip_anx[grip->mode[i]]) + input_set_abs_params(input_dev, t, 3, 57, 1, 0); + else + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + } + + for (j = 0; (t = grip_btn[grip->mode[i]][j]) >= 0; j++) + if (t > 0) + set_bit(t, input_dev->keybit); + + err = input_register_device(grip->dev[i]); + if (err) + goto fail4; + } + + return 0; + + fail4: input_free_device(grip->dev[i]); + fail3: while (--i >= 0) + if (grip->dev[i]) + input_unregister_device(grip->dev[i]); + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(grip); + return err; +} + +static void grip_disconnect(struct gameport *gameport) +{ + struct grip *grip = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 2; i++) + if (grip->dev[i]) + input_unregister_device(grip->dev[i]); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(grip); +} + +static struct gameport_driver grip_drv = { + .driver = { + .name = "grip", + .owner = THIS_MODULE, + }, + .description = DRIVER_DESC, + .connect = grip_connect, + .disconnect = grip_disconnect, +}; + +module_gameport_driver(grip_drv); diff --git a/drivers/input/joystick/grip_mp.c b/drivers/input/joystick/grip_mp.c new file mode 100644 index 000000000..62fdd0831 --- /dev/null +++ b/drivers/input/joystick/grip_mp.c @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Gravis Grip Multiport, a gamepad "hub" that + * connects up to four 9-pin digital gamepads/joysticks. + * Driver tested on SMP and UP kernel versions 2.4.18-4 and 2.4.18-5. + * + * Thanks to Chris Gassib for helpful advice. + * + * Copyright (c) 2002 Brian Bonnlander, Bill Soudan + * Copyright (c) 1998-2000 Vojtech Pavlik + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/gameport.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "Gravis Grip Multiport driver" + +MODULE_AUTHOR("Brian Bonnlander"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#ifdef GRIP_DEBUG +#define dbg(format, arg...) printk(KERN_ERR __FILE__ ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +#define GRIP_MAX_PORTS 4 +/* + * Grip multiport state + */ + +struct grip_port { + struct input_dev *dev; + int mode; + int registered; + + /* individual gamepad states */ + int buttons; + int xaxes; + int yaxes; + int dirty; /* has the state been updated? */ +}; + +struct grip_mp { + struct gameport *gameport; + struct grip_port *port[GRIP_MAX_PORTS]; + int reads; + int bads; +}; + +/* + * Multiport packet interpretation + */ + +#define PACKET_FULL 0x80000000 /* packet is full */ +#define PACKET_IO_FAST 0x40000000 /* 3 bits per gameport read */ +#define PACKET_IO_SLOW 0x20000000 /* 1 bit per gameport read */ +#define PACKET_MP_MORE 0x04000000 /* multiport wants to send more */ +#define PACKET_MP_DONE 0x02000000 /* multiport done sending */ + +/* + * Packet status code interpretation + */ + +#define IO_GOT_PACKET 0x0100 /* Got a packet */ +#define IO_MODE_FAST 0x0200 /* Used 3 data bits per gameport read */ +#define IO_SLOT_CHANGE 0x0800 /* Multiport physical slot status changed */ +#define IO_DONE 0x1000 /* Multiport is done sending packets */ +#define IO_RETRY 0x4000 /* Try again later to get packet */ +#define IO_RESET 0x8000 /* Force multiport to resend all packets */ + +/* + * Gamepad configuration data. Other 9-pin digital joystick devices + * may work with the multiport, so this may not be an exhaustive list! + * Commodore 64 joystick remains untested. + */ + +#define GRIP_INIT_DELAY 2000 /* 2 ms */ + +#define GRIP_MODE_NONE 0 +#define GRIP_MODE_RESET 1 +#define GRIP_MODE_GP 2 +#define GRIP_MODE_C64 3 + +static const int grip_btn_gp[] = { BTN_TR, BTN_TL, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, -1 }; +static const int grip_btn_c64[] = { BTN_JOYSTICK, -1 }; + +static const int grip_abs_gp[] = { ABS_X, ABS_Y, -1 }; +static const int grip_abs_c64[] = { ABS_X, ABS_Y, -1 }; + +static const int *grip_abs[] = { NULL, NULL, grip_abs_gp, grip_abs_c64 }; +static const int *grip_btn[] = { NULL, NULL, grip_btn_gp, grip_btn_c64 }; + +static const char *grip_name[] = { NULL, NULL, "Gravis Grip Pad", "Commodore 64 Joystick" }; + +static const int init_seq[] = { + 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1 }; + +/* Maps multiport directional values to X,Y axis values (each axis encoded in 3 bits) */ + +static const int axis_map[] = { 5, 9, 1, 5, 6, 10, 2, 6, 4, 8, 0, 4, 5, 9, 1, 5 }; + +static int register_slot(int i, struct grip_mp *grip); + +/* + * Returns whether an odd or even number of bits are on in pkt. + */ + +static int bit_parity(u32 pkt) +{ + int x = pkt ^ (pkt >> 16); + x ^= x >> 8; + x ^= x >> 4; + x ^= x >> 2; + x ^= x >> 1; + return x & 1; +} + +/* + * Poll gameport; return true if all bits set in 'onbits' are on and + * all bits set in 'offbits' are off. + */ + +static inline int poll_until(u8 onbits, u8 offbits, int u_sec, struct gameport* gp, u8 *data) +{ + int i, nloops; + + nloops = gameport_time(gp, u_sec); + for (i = 0; i < nloops; i++) { + *data = gameport_read(gp); + if ((*data & onbits) == onbits && + (~(*data) & offbits) == offbits) + return 1; + } + dbg("gameport timed out after %d microseconds.\n", u_sec); + return 0; +} + +/* + * Gets a 28-bit packet from the multiport. + * + * After getting a packet successfully, commands encoded by sendcode may + * be sent to the multiport. + * + * The multiport clock value is reflected in gameport bit B4. + * + * Returns a packet status code indicating whether packet is valid, the transfer + * mode, and any error conditions. + * + * sendflags: current I/O status + * sendcode: data to send to the multiport if sendflags is nonzero + */ + +static int mp_io(struct gameport* gameport, int sendflags, int sendcode, u32 *packet) +{ + u8 raw_data; /* raw data from gameport */ + u8 data_mask; /* packet data bits from raw_data */ + u32 pkt; /* packet temporary storage */ + int bits_per_read; /* num packet bits per gameport read */ + int portvals = 0; /* used for port value sanity check */ + int i; + + /* Gameport bits B0, B4, B5 should first be off, then B4 should come on. */ + + *packet = 0; + raw_data = gameport_read(gameport); + if (raw_data & 1) + return IO_RETRY; + + for (i = 0; i < 64; i++) { + raw_data = gameport_read(gameport); + portvals |= 1 << ((raw_data >> 4) & 3); /* Demux B4, B5 */ + } + + if (portvals == 1) { /* B4, B5 off */ + raw_data = gameport_read(gameport); + portvals = raw_data & 0xf0; + + if (raw_data & 0x31) + return IO_RESET; + gameport_trigger(gameport); + + if (!poll_until(0x10, 0, 308, gameport, &raw_data)) + return IO_RESET; + } else + return IO_RETRY; + + /* Determine packet transfer mode and prepare for packet construction. */ + + if (raw_data & 0x20) { /* 3 data bits/read */ + portvals |= raw_data >> 4; /* Compare B4-B7 before & after trigger */ + + if (portvals != 0xb) + return 0; + data_mask = 7; + bits_per_read = 3; + pkt = (PACKET_FULL | PACKET_IO_FAST) >> 28; + } else { /* 1 data bit/read */ + data_mask = 1; + bits_per_read = 1; + pkt = (PACKET_FULL | PACKET_IO_SLOW) >> 28; + } + + /* Construct a packet. Final data bits must be zero. */ + + while (1) { + if (!poll_until(0, 0x10, 77, gameport, &raw_data)) + return IO_RESET; + raw_data = (raw_data >> 5) & data_mask; + + if (pkt & PACKET_FULL) + break; + pkt = (pkt << bits_per_read) | raw_data; + + if (!poll_until(0x10, 0, 77, gameport, &raw_data)) + return IO_RESET; + } + + if (raw_data) + return IO_RESET; + + /* If 3 bits/read used, drop from 30 bits to 28. */ + + if (bits_per_read == 3) { + pkt = (pkt & 0xffff0000) | ((pkt << 1) & 0xffff); + pkt = (pkt >> 2) | 0xf0000000; + } + + if (bit_parity(pkt) == 1) + return IO_RESET; + + /* Acknowledge packet receipt */ + + if (!poll_until(0x30, 0, 77, gameport, &raw_data)) + return IO_RESET; + + raw_data = gameport_read(gameport); + + if (raw_data & 1) + return IO_RESET; + + gameport_trigger(gameport); + + if (!poll_until(0, 0x20, 77, gameport, &raw_data)) + return IO_RESET; + + /* Return if we just wanted the packet or multiport wants to send more */ + + *packet = pkt; + if ((sendflags == 0) || ((sendflags & IO_RETRY) && !(pkt & PACKET_MP_DONE))) + return IO_GOT_PACKET; + + if (pkt & PACKET_MP_MORE) + return IO_GOT_PACKET | IO_RETRY; + + /* Multiport is done sending packets and is ready to receive data */ + + if (!poll_until(0x20, 0, 77, gameport, &raw_data)) + return IO_GOT_PACKET | IO_RESET; + + raw_data = gameport_read(gameport); + if (raw_data & 1) + return IO_GOT_PACKET | IO_RESET; + + /* Trigger gameport based on bits in sendcode */ + + gameport_trigger(gameport); + do { + if (!poll_until(0x20, 0x10, 116, gameport, &raw_data)) + return IO_GOT_PACKET | IO_RESET; + + if (!poll_until(0x30, 0, 193, gameport, &raw_data)) + return IO_GOT_PACKET | IO_RESET; + + if (raw_data & 1) + return IO_GOT_PACKET | IO_RESET; + + if (sendcode & 1) + gameport_trigger(gameport); + + sendcode >>= 1; + } while (sendcode); + + return IO_GOT_PACKET | IO_MODE_FAST; +} + +/* + * Disables and restores interrupts for mp_io(), which does the actual I/O. + */ + +static int multiport_io(struct gameport* gameport, int sendflags, int sendcode, u32 *packet) +{ + int status; + unsigned long flags; + + local_irq_save(flags); + status = mp_io(gameport, sendflags, sendcode, packet); + local_irq_restore(flags); + + return status; +} + +/* + * Puts multiport into digital mode. Multiport LED turns green. + * + * Returns true if a valid digital packet was received, false otherwise. + */ + +static int dig_mode_start(struct gameport *gameport, u32 *packet) +{ + int i; + int flags, tries = 0, bads = 0; + + for (i = 0; i < ARRAY_SIZE(init_seq); i++) { /* Send magic sequence */ + if (init_seq[i]) + gameport_trigger(gameport); + udelay(GRIP_INIT_DELAY); + } + + for (i = 0; i < 16; i++) /* Wait for multiport to settle */ + udelay(GRIP_INIT_DELAY); + + while (tries < 64 && bads < 8) { /* Reset multiport and try getting a packet */ + + flags = multiport_io(gameport, IO_RESET, 0x27, packet); + + if (flags & IO_MODE_FAST) + return 1; + + if (flags & IO_RETRY) + tries++; + else + bads++; + } + return 0; +} + +/* + * Packet structure: B0-B15 => gamepad state + * B16-B20 => gamepad device type + * B21-B24 => multiport slot index (1-4) + * + * Known device types: 0x1f (grip pad), 0x0 (no device). Others may exist. + * + * Returns the packet status. + */ + +static int get_and_decode_packet(struct grip_mp *grip, int flags) +{ + struct grip_port *port; + u32 packet; + int joytype = 0; + int slot; + + /* Get a packet and check for validity */ + + flags &= IO_RESET | IO_RETRY; + flags = multiport_io(grip->gameport, flags, 0, &packet); + grip->reads++; + + if (packet & PACKET_MP_DONE) + flags |= IO_DONE; + + if (flags && !(flags & IO_GOT_PACKET)) { + grip->bads++; + return flags; + } + + /* Ignore non-gamepad packets, e.g. multiport hardware version */ + + slot = ((packet >> 21) & 0xf) - 1; + if ((slot < 0) || (slot > 3)) + return flags; + + port = grip->port[slot]; + + /* + * Handle "reset" packets, which occur at startup, and when gamepads + * are removed or plugged in. May contain configuration of a new gamepad. + */ + + joytype = (packet >> 16) & 0x1f; + if (!joytype) { + + if (port->registered) { + printk(KERN_INFO "grip_mp: removing %s, slot %d\n", + grip_name[port->mode], slot); + input_unregister_device(port->dev); + port->registered = 0; + } + dbg("Reset: grip multiport slot %d\n", slot); + port->mode = GRIP_MODE_RESET; + flags |= IO_SLOT_CHANGE; + return flags; + } + + /* Interpret a grip pad packet */ + + if (joytype == 0x1f) { + + int dir = (packet >> 8) & 0xf; /* eight way directional value */ + port->buttons = (~packet) & 0xff; + port->yaxes = ((axis_map[dir] >> 2) & 3) - 1; + port->xaxes = (axis_map[dir] & 3) - 1; + port->dirty = 1; + + if (port->mode == GRIP_MODE_RESET) + flags |= IO_SLOT_CHANGE; + + port->mode = GRIP_MODE_GP; + + if (!port->registered) { + dbg("New Grip pad in multiport slot %d.\n", slot); + if (register_slot(slot, grip)) { + port->mode = GRIP_MODE_RESET; + port->dirty = 0; + } + } + return flags; + } + + /* Handle non-grip device codes. For now, just print diagnostics. */ + + { + static int strange_code = 0; + if (strange_code != joytype) { + printk(KERN_INFO "Possible non-grip pad/joystick detected.\n"); + printk(KERN_INFO "Got joy type 0x%x and packet 0x%x.\n", joytype, packet); + strange_code = joytype; + } + } + return flags; +} + +/* + * Returns true if all multiport slot states appear valid. + */ + +static int slots_valid(struct grip_mp *grip) +{ + int flags, slot, invalid = 0, active = 0; + + flags = get_and_decode_packet(grip, 0); + if (!(flags & IO_GOT_PACKET)) + return 0; + + for (slot = 0; slot < 4; slot++) { + if (grip->port[slot]->mode == GRIP_MODE_RESET) + invalid = 1; + if (grip->port[slot]->mode != GRIP_MODE_NONE) + active = 1; + } + + /* Return true if no active slot but multiport sent all its data */ + if (!active) + return (flags & IO_DONE) ? 1 : 0; + + /* Return false if invalid device code received */ + return invalid ? 0 : 1; +} + +/* + * Returns whether the multiport was placed into digital mode and + * able to communicate its state successfully. + */ + +static int multiport_init(struct grip_mp *grip) +{ + int dig_mode, initialized = 0, tries = 0; + u32 packet; + + dig_mode = dig_mode_start(grip->gameport, &packet); + while (!dig_mode && tries < 4) { + dig_mode = dig_mode_start(grip->gameport, &packet); + tries++; + } + + if (dig_mode) + dbg("multiport_init(): digital mode activated.\n"); + else { + dbg("multiport_init(): unable to activate digital mode.\n"); + return 0; + } + + /* Get packets, store multiport state, and check state's validity */ + for (tries = 0; tries < 4096; tries++) { + if (slots_valid(grip)) { + initialized = 1; + break; + } + } + dbg("multiport_init(): initialized == %d\n", initialized); + return initialized; +} + +/* + * Reports joystick state to the linux input layer. + */ + +static void report_slot(struct grip_mp *grip, int slot) +{ + struct grip_port *port = grip->port[slot]; + int i; + + /* Store button states with linux input driver */ + + for (i = 0; i < 8; i++) + input_report_key(port->dev, grip_btn_gp[i], (port->buttons >> i) & 1); + + /* Store axis states with linux driver */ + + input_report_abs(port->dev, ABS_X, port->xaxes); + input_report_abs(port->dev, ABS_Y, port->yaxes); + + /* Tell the receiver of the events to process them */ + + input_sync(port->dev); + + port->dirty = 0; +} + +/* + * Get the multiport state. + */ + +static void grip_poll(struct gameport *gameport) +{ + struct grip_mp *grip = gameport_get_drvdata(gameport); + int i, npkts, flags; + + for (npkts = 0; npkts < 4; npkts++) { + flags = IO_RETRY; + for (i = 0; i < 32; i++) { + flags = get_and_decode_packet(grip, flags); + if ((flags & IO_GOT_PACKET) || !(flags & IO_RETRY)) + break; + } + if (flags & IO_DONE) + break; + } + + for (i = 0; i < 4; i++) + if (grip->port[i]->dirty) + report_slot(grip, i); +} + +/* + * Called when a joystick device file is opened + */ + +static int grip_open(struct input_dev *dev) +{ + struct grip_mp *grip = input_get_drvdata(dev); + + gameport_start_polling(grip->gameport); + return 0; +} + +/* + * Called when a joystick device file is closed + */ + +static void grip_close(struct input_dev *dev) +{ + struct grip_mp *grip = input_get_drvdata(dev); + + gameport_stop_polling(grip->gameport); +} + +/* + * Tell the linux input layer about a newly plugged-in gamepad. + */ + +static int register_slot(int slot, struct grip_mp *grip) +{ + struct grip_port *port = grip->port[slot]; + struct input_dev *input_dev; + int j, t; + int err; + + port->dev = input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = grip_name[port->mode]; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_GRAVIS; + input_dev->id.product = 0x0100 + port->mode; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &grip->gameport->dev; + + input_set_drvdata(input_dev, grip); + + input_dev->open = grip_open; + input_dev->close = grip_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (j = 0; (t = grip_abs[port->mode][j]) >= 0; j++) + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + + for (j = 0; (t = grip_btn[port->mode][j]) >= 0; j++) + if (t > 0) + set_bit(t, input_dev->keybit); + + err = input_register_device(port->dev); + if (err) { + input_free_device(port->dev); + return err; + } + + port->registered = 1; + + if (port->dirty) /* report initial state, if any */ + report_slot(grip, slot); + + return 0; +} + +static int grip_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct grip_mp *grip; + int err; + + grip = kzalloc_obj(*grip); + if (!grip) + return -ENOMEM; + + grip->gameport = gameport; + + gameport_set_drvdata(gameport, grip); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + gameport_set_poll_handler(gameport, grip_poll); + gameport_set_poll_interval(gameport, 20); + + if (!multiport_init(grip)) { + err = -ENODEV; + goto fail2; + } + + if (!grip->port[0]->mode && !grip->port[1]->mode && !grip->port[2]->mode && !grip->port[3]->mode) { + /* nothing plugged in */ + err = -ENODEV; + goto fail2; + } + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + kfree(grip); + return err; +} + +static void grip_disconnect(struct gameport *gameport) +{ + struct grip_mp *grip = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 4; i++) + if (grip->port[i]->registered) + input_unregister_device(grip->port[i]->dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(grip); +} + +static struct gameport_driver grip_drv = { + .driver = { + .name = "grip_mp", + }, + .description = DRIVER_DESC, + .connect = grip_connect, + .disconnect = grip_disconnect, +}; + +module_gameport_driver(grip_drv); diff --git a/drivers/input/joystick/guillemot.c b/drivers/input/joystick/guillemot.c new file mode 100644 index 000000000..13f0fa4d3 --- /dev/null +++ b/drivers/input/joystick/guillemot.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2001 Vojtech Pavlik + */ + +/* + * Guillemot Digital Interface Protocol driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/gameport.h> +#include <linux/input.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "Guillemot Digital joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define GUILLEMOT_MAX_START 600 /* 600 us */ +#define GUILLEMOT_MAX_STROBE 60 /* 60 us */ +#define GUILLEMOT_MAX_LENGTH 17 /* 17 bytes */ + +static short guillemot_abs_pad[] = + { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER, -1 }; + +static short guillemot_btn_pad[] = + { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_MODE, BTN_SELECT, -1 }; + +static struct { + int x; + int y; +} guillemot_hat_to_axis[16] = {{ 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + +struct guillemot_type { + unsigned char id; + short *abs; + short *btn; + int hat; + char *name; +}; + +struct guillemot { + struct gameport *gameport; + struct input_dev *dev; + int bads; + int reads; + struct guillemot_type *type; + unsigned char length; + char phys[32]; +}; + +static struct guillemot_type guillemot_type[] = { + { 0x00, guillemot_abs_pad, guillemot_btn_pad, 1, "Guillemot Pad" }, + { 0 }}; + +/* + * guillemot_read_packet() reads Guillemot joystick data. + */ + +static int guillemot_read_packet(struct gameport *gameport, u8 *data) +{ + unsigned long flags; + unsigned char u, v; + unsigned int t, s; + int i; + + for (i = 0; i < GUILLEMOT_MAX_LENGTH; i++) + data[i] = 0; + + i = 0; + t = gameport_time(gameport, GUILLEMOT_MAX_START); + s = gameport_time(gameport, GUILLEMOT_MAX_STROBE); + + local_irq_save(flags); + gameport_trigger(gameport); + v = gameport_read(gameport); + + while (t > 0 && i < GUILLEMOT_MAX_LENGTH * 8) { + t--; + u = v; v = gameport_read(gameport); + if (v & ~u & 0x10) { + data[i >> 3] |= ((v >> 5) & 1) << (i & 7); + i++; + t = s; + } + } + + local_irq_restore(flags); + + return i; +} + +/* + * guillemot_poll() reads and analyzes Guillemot joystick data. + */ + +static void guillemot_poll(struct gameport *gameport) +{ + struct guillemot *guillemot = gameport_get_drvdata(gameport); + struct input_dev *dev = guillemot->dev; + u8 data[GUILLEMOT_MAX_LENGTH]; + int i; + + guillemot->reads++; + + if (guillemot_read_packet(guillemot->gameport, data) != GUILLEMOT_MAX_LENGTH * 8 || + data[0] != 0x55 || data[16] != 0xaa) { + guillemot->bads++; + } else { + + for (i = 0; i < 6 && guillemot->type->abs[i] >= 0; i++) + input_report_abs(dev, guillemot->type->abs[i], data[i + 5]); + + if (guillemot->type->hat) { + input_report_abs(dev, ABS_HAT0X, guillemot_hat_to_axis[data[4] >> 4].x); + input_report_abs(dev, ABS_HAT0Y, guillemot_hat_to_axis[data[4] >> 4].y); + } + + for (i = 0; i < 16 && guillemot->type->btn[i] >= 0; i++) + input_report_key(dev, guillemot->type->btn[i], (data[2 + (i >> 3)] >> (i & 7)) & 1); + } + + input_sync(dev); +} + +/* + * guillemot_open() is a callback from the input open routine. + */ + +static int guillemot_open(struct input_dev *dev) +{ + struct guillemot *guillemot = input_get_drvdata(dev); + + gameport_start_polling(guillemot->gameport); + return 0; +} + +/* + * guillemot_close() is a callback from the input close routine. + */ + +static void guillemot_close(struct input_dev *dev) +{ + struct guillemot *guillemot = input_get_drvdata(dev); + + gameport_stop_polling(guillemot->gameport); +} + +/* + * guillemot_connect() probes for Guillemot joysticks. + */ + +static int guillemot_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct guillemot *guillemot; + struct input_dev *input_dev; + u8 data[GUILLEMOT_MAX_LENGTH]; + int i, t; + int err; + + guillemot = kzalloc_obj(*guillemot); + input_dev = input_allocate_device(); + if (!guillemot || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + guillemot->gameport = gameport; + guillemot->dev = input_dev; + + gameport_set_drvdata(gameport, guillemot); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + i = guillemot_read_packet(gameport, data); + + if (i != GUILLEMOT_MAX_LENGTH * 8 || data[0] != 0x55 || data[16] != 0xaa) { + err = -ENODEV; + goto fail2; + } + + for (i = 0; guillemot_type[i].name; i++) + if (guillemot_type[i].id == data[11]) + break; + + if (!guillemot_type[i].name) { + printk(KERN_WARNING "guillemot.c: Unknown joystick on %s. [ %02x%02x:%04x, ver %d.%02d ]\n", + gameport->phys, data[12], data[13], data[11], data[14], data[15]); + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, guillemot_poll); + gameport_set_poll_interval(gameport, 20); + + snprintf(guillemot->phys, sizeof(guillemot->phys), "%s/input0", gameport->phys); + guillemot->type = guillemot_type + i; + + input_dev->name = guillemot_type[i].name; + input_dev->phys = guillemot->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_GUILLEMOT; + input_dev->id.product = guillemot_type[i].id; + input_dev->id.version = (int)data[14] << 8 | data[15]; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, guillemot); + + input_dev->open = guillemot_open; + input_dev->close = guillemot_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; (t = guillemot->type->abs[i]) >= 0; i++) + input_set_abs_params(input_dev, t, 0, 255, 0, 0); + + if (guillemot->type->hat) { + input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0); + } + + for (i = 0; (t = guillemot->type->btn[i]) >= 0; i++) + set_bit(t, input_dev->keybit); + + err = input_register_device(guillemot->dev); + if (err) + goto fail2; + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + input_free_device(input_dev); + kfree(guillemot); + return err; +} + +static void guillemot_disconnect(struct gameport *gameport) +{ + struct guillemot *guillemot = gameport_get_drvdata(gameport); + + printk(KERN_INFO "guillemot.c: Failed %d reads out of %d on %s\n", guillemot->reads, guillemot->bads, guillemot->phys); + input_unregister_device(guillemot->dev); + gameport_close(gameport); + kfree(guillemot); +} + +static struct gameport_driver guillemot_drv = { + .driver = { + .name = "guillemot", + }, + .description = DRIVER_DESC, + .connect = guillemot_connect, + .disconnect = guillemot_disconnect, +}; + +module_gameport_driver(guillemot_drv); 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..8c78cbe55 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-ff.c @@ -0,0 +1,520 @@ +// 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) { + guard(mutex)(&iforce->mem_mutex); + + if (allocate_resource(&iforce->device_memory, mod_chunk, 2, + iforce->device_memory.start, + iforce->device_memory.end, + 2L, NULL, NULL)) + return -ENOSPC; + } + + 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) { + guard(mutex)(&iforce->mem_mutex); + + if (allocate_resource(&iforce->device_memory, mod_chunk, 0x0c, + iforce->device_memory.start, + iforce->device_memory.end, + 2L, NULL, NULL)) + return -ENOSPC; + } + + 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) { + guard(mutex)(&iforce->mem_mutex); + + if (allocate_resource(&(iforce->device_memory), mod_chunk, 0x0e, + iforce->device_memory.start, + iforce->device_memory.end, + 2L, NULL, NULL)) + return -ENOSPC; + } + + 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) { + guard(mutex)(&iforce->mem_mutex); + + if (allocate_resource(&(iforce->device_memory), mod_chunk, 8, + iforce->device_memory.start, + iforce->device_memory.end, + 2L, NULL, NULL)) + return -ENOSPC; + } + + 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..86d09faa6 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-main.c @@ -0,0 +1,400 @@ +// 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/export.h> +#include <linux/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..01fee1405 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-packets.c @@ -0,0 +1,218 @@ +// 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/export.h> +#include <linux/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; + +/* + * Update head and tail of xmit buffer + */ + scoped_guard(spinlock_irqsave, &iforce->xmit_lock) { + 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"); + 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); + } + +/* + * 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 */ + if (len < 7) + break; + + 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 */ + if (len < 7) + break; + + 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 */ + if (len < 2) + break; + + 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 (i < IFORCE_EFFECTS_MAX) { + 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 + sizeof(u16) <= len; j += sizeof(u16)) + 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..e23583488 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-serio.c @@ -0,0 +1,251 @@ +// 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; + + if (test_and_set_bit(IFORCE_XMIT_RUNNING, iforce->xmit_flags)) { + set_bit(IFORCE_XMIT_AGAIN, iforce->xmit_flags); + return; + } + + guard(spinlock_irqsave)(&iforce->xmit_lock); + + do { + if (iforce->xmit.head == iforce->xmit.tail) + break; + + 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); + + } while (test_and_clear_bit(IFORCE_XMIT_AGAIN, iforce->xmit_flags)); + + iforce_clear_xmit_and_wake(iforce); +} + +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_obj(*iforce_serio); + 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..f04370e41 --- /dev/null +++ b/drivers/input/joystick/iforce/iforce-usb.c @@ -0,0 +1,301 @@ +// 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; + + guard(spinlock_irqsave)(&iforce->xmit_lock); + + if (iforce->xmit.head == iforce->xmit.tail) { + iforce_clear_xmit_and_wake(iforce); + 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); + + n=usb_submit_urb(iforce_usb->out, GFP_ATOMIC); + if (n) { + 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 */ +} + +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; + } + + if (!urb->actual_length) + 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_obj(*iforce_usb); + 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; diff --git a/drivers/input/joystick/interact.c b/drivers/input/joystick/interact.c new file mode 100644 index 000000000..507cec36b --- /dev/null +++ b/drivers/input/joystick/interact.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2001 Vojtech Pavlik + * + * Based on the work of: + * Toby Deshane + */ + +/* + * InterAct digital gamepad/joystick driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/gameport.h> +#include <linux/input.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "InterAct digital joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define INTERACT_MAX_START 600 /* 400 us */ +#define INTERACT_MAX_STROBE 60 /* 40 us */ +#define INTERACT_MAX_LENGTH 32 /* 32 bits */ + +#define INTERACT_TYPE_HHFX 0 /* HammerHead/FX */ +#define INTERACT_TYPE_PP8D 1 /* ProPad 8 */ + +struct interact { + struct gameport *gameport; + struct input_dev *dev; + int bads; + int reads; + unsigned char type; + unsigned char length; + char phys[32]; +}; + +static short interact_abs_hhfx[] = + { ABS_RX, ABS_RY, ABS_X, ABS_Y, ABS_HAT0X, ABS_HAT0Y, -1 }; +static short interact_abs_pp8d[] = + { ABS_X, ABS_Y, -1 }; + +static short interact_btn_hhfx[] = + { BTN_TR, BTN_X, BTN_Y, BTN_Z, BTN_A, BTN_B, BTN_C, BTN_TL, BTN_TL2, BTN_TR2, BTN_MODE, BTN_SELECT, -1 }; +static short interact_btn_pp8d[] = + { BTN_C, BTN_TL, BTN_TR, BTN_A, BTN_B, BTN_Y, BTN_Z, BTN_X, -1 }; + +struct interact_type { + int id; + short *abs; + short *btn; + char *name; + unsigned char length; + unsigned char b8; +}; + +static struct interact_type interact_type[] = { + { 0x6202, interact_abs_hhfx, interact_btn_hhfx, "InterAct HammerHead/FX", 32, 4 }, + { 0x53f8, interact_abs_pp8d, interact_btn_pp8d, "InterAct ProPad 8 Digital", 16, 0 }, + { 0 }}; + +/* + * interact_read_packet() reads and InterAct joystick data. + */ + +static int interact_read_packet(struct gameport *gameport, int length, u32 *data) +{ + unsigned long flags; + unsigned char u, v; + unsigned int t, s; + int i; + + i = 0; + data[0] = data[1] = data[2] = 0; + t = gameport_time(gameport, INTERACT_MAX_START); + s = gameport_time(gameport, INTERACT_MAX_STROBE); + + local_irq_save(flags); + gameport_trigger(gameport); + v = gameport_read(gameport); + + while (t > 0 && i < length) { + t--; + u = v; v = gameport_read(gameport); + if (v & ~u & 0x40) { + data[0] = (data[0] << 1) | ((v >> 4) & 1); + data[1] = (data[1] << 1) | ((v >> 5) & 1); + data[2] = (data[2] << 1) | ((v >> 7) & 1); + i++; + t = s; + } + } + + local_irq_restore(flags); + + return i; +} + +/* + * interact_poll() reads and analyzes InterAct joystick data. + */ + +static void interact_poll(struct gameport *gameport) +{ + struct interact *interact = gameport_get_drvdata(gameport); + struct input_dev *dev = interact->dev; + u32 data[3]; + int i; + + interact->reads++; + + if (interact_read_packet(interact->gameport, interact->length, data) < interact->length) { + interact->bads++; + } else { + + for (i = 0; i < 3; i++) + data[i] <<= INTERACT_MAX_LENGTH - interact->length; + + switch (interact->type) { + + case INTERACT_TYPE_HHFX: + + for (i = 0; i < 4; i++) + input_report_abs(dev, interact_abs_hhfx[i], (data[i & 1] >> ((i >> 1) << 3)) & 0xff); + + for (i = 0; i < 2; i++) + input_report_abs(dev, ABS_HAT0Y - i, + ((data[1] >> ((i << 1) + 17)) & 1) - ((data[1] >> ((i << 1) + 16)) & 1)); + + for (i = 0; i < 8; i++) + input_report_key(dev, interact_btn_hhfx[i], (data[0] >> (i + 16)) & 1); + + for (i = 0; i < 4; i++) + input_report_key(dev, interact_btn_hhfx[i + 8], (data[1] >> (i + 20)) & 1); + + break; + + case INTERACT_TYPE_PP8D: + + for (i = 0; i < 2; i++) + input_report_abs(dev, interact_abs_pp8d[i], + ((data[0] >> ((i << 1) + 20)) & 1) - ((data[0] >> ((i << 1) + 21)) & 1)); + + for (i = 0; i < 8; i++) + input_report_key(dev, interact_btn_pp8d[i], (data[1] >> (i + 16)) & 1); + + break; + } + } + + input_sync(dev); +} + +/* + * interact_open() is a callback from the input open routine. + */ + +static int interact_open(struct input_dev *dev) +{ + struct interact *interact = input_get_drvdata(dev); + + gameport_start_polling(interact->gameport); + return 0; +} + +/* + * interact_close() is a callback from the input close routine. + */ + +static void interact_close(struct input_dev *dev) +{ + struct interact *interact = input_get_drvdata(dev); + + gameport_stop_polling(interact->gameport); +} + +/* + * interact_connect() probes for InterAct joysticks. + */ + +static int interact_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct interact *interact; + struct input_dev *input_dev; + __u32 data[3]; + int i, t; + int err; + + interact = kzalloc_obj(*interact); + input_dev = input_allocate_device(); + if (!interact || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + interact->gameport = gameport; + interact->dev = input_dev; + + gameport_set_drvdata(gameport, interact); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + i = interact_read_packet(gameport, INTERACT_MAX_LENGTH * 2, data); + + if (i != 32 || (data[0] >> 24) != 0x0c || (data[1] >> 24) != 0x02) { + err = -ENODEV; + goto fail2; + } + + for (i = 0; interact_type[i].length; i++) + if (interact_type[i].id == (data[2] >> 16)) + break; + + if (!interact_type[i].length) { + printk(KERN_WARNING "interact.c: Unknown joystick on %s. [len %d d0 %08x d1 %08x i2 %08x]\n", + gameport->phys, i, data[0], data[1], data[2]); + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, interact_poll); + gameport_set_poll_interval(gameport, 20); + + snprintf(interact->phys, sizeof(interact->phys), "%s/input0", gameport->phys); + + interact->type = i; + interact->length = interact_type[i].length; + + input_dev->name = interact_type[i].name; + input_dev->phys = interact->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_INTERACT; + input_dev->id.product = interact_type[i].id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, interact); + + input_dev->open = interact_open; + input_dev->close = interact_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; (t = interact_type[interact->type].abs[i]) >= 0; i++) { + if (i < interact_type[interact->type].b8) + input_set_abs_params(input_dev, t, 0, 255, 0, 0); + else + input_set_abs_params(input_dev, t, -1, 1, 0, 0); + } + + for (i = 0; (t = interact_type[interact->type].btn[i]) >= 0; i++) + __set_bit(t, input_dev->keybit); + + err = input_register_device(interact->dev); + if (err) + goto fail2; + + return 0; + +fail2: gameport_close(gameport); +fail1: gameport_set_drvdata(gameport, NULL); + input_free_device(input_dev); + kfree(interact); + return err; +} + +static void interact_disconnect(struct gameport *gameport) +{ + struct interact *interact = gameport_get_drvdata(gameport); + + input_unregister_device(interact->dev); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(interact); +} + +static struct gameport_driver interact_drv = { + .driver = { + .name = "interact", + }, + .description = DRIVER_DESC, + .connect = interact_connect, + .disconnect = interact_disconnect, +}; + +module_gameport_driver(interact_drv); diff --git a/drivers/input/joystick/joydump.c b/drivers/input/joystick/joydump.c new file mode 100644 index 000000000..c9cbdcc7f --- /dev/null +++ b/drivers/input/joystick/joydump.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1996-2001 Vojtech Pavlik + */ + +/* + * This is just a very simple driver that can dump the data + * out of the joystick port into the syslog ... + */ + +#include <linux/module.h> +#include <linux/gameport.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#define DRIVER_DESC "Gameport data dumper module" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define BUF_SIZE 256 + +struct joydump { + unsigned int time; + unsigned char data; +}; + +static int joydump_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct joydump *buf; /* all entries */ + struct joydump *dump, *prev; /* one entry each */ + int axes[4], buttons; + int i, j, t, timeout; + unsigned long flags; + unsigned char u; + + printk(KERN_INFO "joydump: ,------------------ START ----------------.\n"); + printk(KERN_INFO "joydump: | Dumping: %30s |\n", gameport->phys); + printk(KERN_INFO "joydump: | Speed: %28d kHz |\n", gameport->speed); + + if (gameport_open(gameport, drv, GAMEPORT_MODE_RAW)) { + + printk(KERN_INFO "joydump: | Raw mode not available - trying cooked. |\n"); + + if (gameport_open(gameport, drv, GAMEPORT_MODE_COOKED)) { + + printk(KERN_INFO "joydump: | Cooked not available either. Failing. |\n"); + printk(KERN_INFO "joydump: `------------------- END -----------------'\n"); + return -ENODEV; + } + + gameport_cooked_read(gameport, axes, &buttons); + + for (i = 0; i < 4; i++) + printk(KERN_INFO "joydump: | Axis %d: %4d. |\n", i, axes[i]); + printk(KERN_INFO "joydump: | Buttons %02x. |\n", buttons); + printk(KERN_INFO "joydump: `------------------- END -----------------'\n"); + } + + timeout = gameport_time(gameport, 10000); /* 10 ms */ + + buf = kmalloc_objs(struct joydump, BUF_SIZE); + if (!buf) { + printk(KERN_INFO "joydump: no memory for testing\n"); + goto jd_end; + } + dump = buf; + t = 0; + i = 1; + + local_irq_save(flags); + + u = gameport_read(gameport); + + dump->data = u; + dump->time = t; + dump++; + + gameport_trigger(gameport); + + while (i < BUF_SIZE && t < timeout) { + + dump->data = gameport_read(gameport); + + if (dump->data ^ u) { + u = dump->data; + dump->time = t; + i++; + dump++; + } + t++; + } + + local_irq_restore(flags); + +/* + * Dump data. + */ + + t = i; + dump = buf; + prev = dump; + + printk(KERN_INFO "joydump: >------------------ DATA -----------------<\n"); + printk(KERN_INFO "joydump: | index: %3d delta: %3d us data: ", 0, 0); + for (j = 7; j >= 0; j--) + printk("%d", (dump->data >> j) & 1); + printk(" |\n"); + dump++; + + for (i = 1; i < t; i++, dump++, prev++) { + printk(KERN_INFO "joydump: | index: %3d delta: %3d us data: ", + i, dump->time - prev->time); + for (j = 7; j >= 0; j--) + printk("%d", (dump->data >> j) & 1); + printk(" |\n"); + } + kfree(buf); + +jd_end: + printk(KERN_INFO "joydump: `------------------- END -----------------'\n"); + + return 0; +} + +static void joydump_disconnect(struct gameport *gameport) +{ + gameport_close(gameport); +} + +static struct gameport_driver joydump_drv = { + .driver = { + .name = "joydump", + }, + .description = DRIVER_DESC, + .connect = joydump_connect, + .disconnect = joydump_disconnect, +}; + +module_gameport_driver(joydump_drv); diff --git a/drivers/input/joystick/magellan.c b/drivers/input/joystick/magellan.c new file mode 100644 index 000000000..201a049c8 --- /dev/null +++ b/drivers/input/joystick/magellan.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Magellan and Space Mouse 6dof controller driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Magellan and SpaceMouse 6dof controller driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define MAGELLAN_MAX_LENGTH 32 + +static int magellan_buttons[] = { BTN_0, BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7, BTN_8 }; +static int magellan_axes[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; + +/* + * Per-Magellan data. + */ + +struct magellan { + struct input_dev *dev; + int idx; + unsigned char data[MAGELLAN_MAX_LENGTH]; + char phys[32]; +}; + +/* + * magellan_crunch_nibbles() verifies that the bytes sent from the Magellan + * have correct upper nibbles for the lower ones, if not, the packet will + * be thrown away. It also strips these upper halves to simplify further + * processing. + */ + +static int magellan_crunch_nibbles(unsigned char *data, int count) +{ + static const unsigned char nibbles[16] __nonstring = "0AB3D56GH9:K<MN?"; + + do { + if (data[count] == nibbles[data[count] & 0xf]) + data[count] = data[count] & 0xf; + else + return -1; + } while (--count); + + return 0; +} + +static void magellan_process_packet(struct magellan* magellan) +{ + struct input_dev *dev = magellan->dev; + unsigned char *data = magellan->data; + int i, t; + + if (!magellan->idx) return; + + switch (magellan->data[0]) { + + case 'd': /* Axis data */ + if (magellan->idx != 25) return; + if (magellan_crunch_nibbles(data, 24)) return; + for (i = 0; i < 6; i++) + input_report_abs(dev, magellan_axes[i], + (data[(i << 2) + 1] << 12 | data[(i << 2) + 2] << 8 | + data[(i << 2) + 3] << 4 | data[(i << 2) + 4]) - 32768); + break; + + case 'k': /* Button data */ + if (magellan->idx != 4) return; + if (magellan_crunch_nibbles(data, 3)) return; + t = (data[1] << 1) | (data[2] << 5) | data[3]; + for (i = 0; i < 9; i++) input_report_key(dev, magellan_buttons[i], (t >> i) & 1); + break; + } + + input_sync(dev); +} + +static irqreturn_t magellan_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct magellan* magellan = serio_get_drvdata(serio); + + if (data == '\r') { + magellan_process_packet(magellan); + magellan->idx = 0; + } else { + if (magellan->idx < MAGELLAN_MAX_LENGTH) + magellan->data[magellan->idx++] = data; + } + return IRQ_HANDLED; +} + +/* + * magellan_disconnect() is the opposite of magellan_connect() + */ + +static void magellan_disconnect(struct serio *serio) +{ + struct magellan* magellan = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(magellan->dev); + kfree(magellan); +} + +/* + * magellan_connect() is the routine that is called when someone adds a + * new serio device that supports Magellan protocol and registers it as + * an input device. + */ + +static int magellan_connect(struct serio *serio, struct serio_driver *drv) +{ + struct magellan *magellan; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + magellan = kzalloc_obj(*magellan); + input_dev = input_allocate_device(); + if (!magellan || !input_dev) + goto fail1; + + magellan->dev = input_dev; + snprintf(magellan->phys, sizeof(magellan->phys), "%s/input0", serio->phys); + + input_dev->name = "LogiCad3D Magellan / SpaceMouse"; + input_dev->phys = magellan->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_MAGELLAN; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < 9; i++) + set_bit(magellan_buttons[i], input_dev->keybit); + + for (i = 0; i < 6; i++) + input_set_abs_params(input_dev, magellan_axes[i], -360, 360, 0, 0); + + serio_set_drvdata(serio, magellan); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(magellan->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(magellan); + return err; +} + +/* + * The serio driver structure. + */ + +static const struct serio_device_id magellan_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_MAGELLAN, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, magellan_serio_ids); + +static struct serio_driver magellan_drv = { + .driver = { + .name = "magellan", + }, + .description = DRIVER_DESC, + .id_table = magellan_serio_ids, + .interrupt = magellan_interrupt, + .connect = magellan_connect, + .disconnect = magellan_disconnect, +}; + +module_serio_driver(magellan_drv); diff --git a/drivers/input/joystick/maplecontrol.c b/drivers/input/joystick/maplecontrol.c new file mode 100644 index 000000000..6293b6e81 --- /dev/null +++ b/drivers/input/joystick/maplecontrol.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SEGA Dreamcast controller driver + * Based on drivers/usb/iforce.c + * + * Copyright Yaegashi Takeshi, 2001 + * Adrian McMenamin, 2008 - 2009 + */ + +#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> + +MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>"); +MODULE_DESCRIPTION("SEGA Dreamcast controller driver"); +MODULE_LICENSE("GPL"); + +struct dc_pad { + struct input_dev *dev; + struct maple_device *mdev; +}; + +static void dc_pad_callback(struct mapleq *mq) +{ + unsigned short buttons; + struct maple_device *mapledev = mq->dev; + struct dc_pad *pad = maple_get_drvdata(mapledev); + struct input_dev *dev = pad->dev; + unsigned char *res = mq->recvbuf->buf; + + buttons = ~le16_to_cpup((__le16 *)(res + 8)); + + input_report_abs(dev, ABS_HAT0Y, + (buttons & 0x0010 ? -1 : 0) + (buttons & 0x0020 ? 1 : 0)); + input_report_abs(dev, ABS_HAT0X, + (buttons & 0x0040 ? -1 : 0) + (buttons & 0x0080 ? 1 : 0)); + input_report_abs(dev, ABS_HAT1Y, + (buttons & 0x1000 ? -1 : 0) + (buttons & 0x2000 ? 1 : 0)); + input_report_abs(dev, ABS_HAT1X, + (buttons & 0x4000 ? -1 : 0) + (buttons & 0x8000 ? 1 : 0)); + + input_report_key(dev, BTN_C, buttons & 0x0001); + input_report_key(dev, BTN_B, buttons & 0x0002); + input_report_key(dev, BTN_A, buttons & 0x0004); + input_report_key(dev, BTN_START, buttons & 0x0008); + input_report_key(dev, BTN_Z, buttons & 0x0100); + input_report_key(dev, BTN_Y, buttons & 0x0200); + input_report_key(dev, BTN_X, buttons & 0x0400); + input_report_key(dev, BTN_SELECT, buttons & 0x0800); + + input_report_abs(dev, ABS_GAS, res[10]); + input_report_abs(dev, ABS_BRAKE, res[11]); + input_report_abs(dev, ABS_X, res[12]); + input_report_abs(dev, ABS_Y, res[13]); + input_report_abs(dev, ABS_RX, res[14]); + input_report_abs(dev, ABS_RY, res[15]); +} + +static int dc_pad_open(struct input_dev *dev) +{ + struct dc_pad *pad = dev_get_platdata(&dev->dev); + + maple_getcond_callback(pad->mdev, dc_pad_callback, HZ/20, + MAPLE_FUNC_CONTROLLER); + + return 0; +} + +static void dc_pad_close(struct input_dev *dev) +{ + struct dc_pad *pad = dev_get_platdata(&dev->dev); + + maple_getcond_callback(pad->mdev, dc_pad_callback, 0, + MAPLE_FUNC_CONTROLLER); +} + +/* allow the controller to be used */ +static int probe_maple_controller(struct device *dev) +{ + static const short btn_bit[32] = { + BTN_C, BTN_B, BTN_A, BTN_START, -1, -1, -1, -1, + BTN_Z, BTN_Y, BTN_X, BTN_SELECT, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + }; + + static const short abs_bit[32] = { + -1, -1, -1, -1, ABS_HAT0Y, ABS_HAT0Y, ABS_HAT0X, ABS_HAT0X, + -1, -1, -1, -1, ABS_HAT1Y, ABS_HAT1Y, ABS_HAT1X, ABS_HAT1X, + ABS_GAS, ABS_BRAKE, ABS_X, ABS_Y, ABS_RX, ABS_RY, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + }; + + struct maple_device *mdev = to_maple_dev(dev); + struct maple_driver *mdrv = to_maple_driver(dev->driver); + int i, error; + struct dc_pad *pad; + struct input_dev *idev; + unsigned long data = be32_to_cpu(mdev->devinfo.function_data[0]); + + pad = kzalloc_obj(*pad); + idev = input_allocate_device(); + if (!pad || !idev) { + error = -ENOMEM; + goto fail; + } + + pad->dev = idev; + pad->mdev = mdev; + + maple_set_drvdata(mdev, pad); + + idev->open = dc_pad_open; + idev->close = dc_pad_close; + + for (i = 0; i < 32; i++) { + if (data & (1 << i)) { + if (btn_bit[i] >= 0) + __set_bit(btn_bit[i], idev->keybit); + else if (abs_bit[i] >= 0) + __set_bit(abs_bit[i], idev->absbit); + } + } + + if (idev->keybit[BIT_WORD(BTN_JOYSTICK)]) + idev->evbit[0] |= BIT_MASK(EV_KEY); + + if (idev->absbit[0]) + idev->evbit[0] |= BIT_MASK(EV_ABS); + + for (i = ABS_X; i <= ABS_BRAKE; i++) + input_set_abs_params(idev, i, 0, 255, 0, 0); + + for (i = ABS_HAT0X; i <= ABS_HAT3Y; i++) + input_set_abs_params(idev, i, 1, -1, 0, 0); + + idev->dev.platform_data = pad; + idev->dev.parent = &mdev->dev; + idev->name = mdev->product_name; + idev->id.bustype = BUS_HOST; + + error = input_register_device(idev); + if (error) + goto fail; + + mdev->driver = mdrv; + + return 0; + +fail: + input_free_device(idev); + kfree(pad); + maple_set_drvdata(mdev, NULL); + return error; +} + +static int remove_maple_controller(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + struct dc_pad *pad = maple_get_drvdata(mdev); + + mdev->callback = NULL; + input_unregister_device(pad->dev); + maple_set_drvdata(mdev, NULL); + kfree(pad); + + return 0; +} + +static struct maple_driver dc_pad_driver = { + .function = MAPLE_FUNC_CONTROLLER, + .drv = { + .name = "Dreamcast_controller", + .probe = probe_maple_controller, + .remove = remove_maple_controller, + }, +}; + +static int __init dc_pad_init(void) +{ + return maple_driver_register(&dc_pad_driver); +} + +static void __exit dc_pad_exit(void) +{ + maple_driver_unregister(&dc_pad_driver); +} + +module_init(dc_pad_init); +module_exit(dc_pad_exit); diff --git a/drivers/input/joystick/n64joy.c b/drivers/input/joystick/n64joy.c new file mode 100644 index 000000000..43a02bb99 --- /dev/null +++ b/drivers/input/joystick/n64joy.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for the four N64 controllers. + * + * Copyright (c) 2021 Lauri Kasanen + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/limits.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/timer.h> + +MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>"); +MODULE_DESCRIPTION("Driver for N64 controllers"); +MODULE_LICENSE("GPL"); + +#define PIF_RAM 0x1fc007c0 + +#define SI_DRAM_REG 0 +#define SI_READ_REG 1 +#define SI_WRITE_REG 4 +#define SI_STATUS_REG 6 + +#define SI_STATUS_DMA_BUSY BIT(0) +#define SI_STATUS_IO_BUSY BIT(1) + +#define N64_CONTROLLER_ID 0x0500 + +#define MAX_CONTROLLERS 4 + +static const char *n64joy_phys[MAX_CONTROLLERS] = { + "n64joy/port0", + "n64joy/port1", + "n64joy/port2", + "n64joy/port3", +}; + +struct n64joy_priv { + u64 si_buf[8] ____cacheline_aligned; + struct timer_list timer; + struct mutex n64joy_mutex; + struct input_dev *n64joy_dev[MAX_CONTROLLERS]; + u32 __iomem *reg_base; + u8 n64joy_opened; +}; + +struct joydata { + unsigned int: 16; /* unused */ + unsigned int err: 2; + unsigned int: 14; /* unused */ + + union { + u32 data; + + struct { + unsigned int a: 1; + unsigned int b: 1; + unsigned int z: 1; + unsigned int start: 1; + unsigned int up: 1; + unsigned int down: 1; + unsigned int left: 1; + unsigned int right: 1; + unsigned int: 2; /* unused */ + unsigned int l: 1; + unsigned int r: 1; + unsigned int c_up: 1; + unsigned int c_down: 1; + unsigned int c_left: 1; + unsigned int c_right: 1; + signed int x: 8; + signed int y: 8; + }; + }; +}; + +static void n64joy_write_reg(u32 __iomem *reg_base, const u8 reg, const u32 value) +{ + writel(value, reg_base + reg); +} + +static u32 n64joy_read_reg(u32 __iomem *reg_base, const u8 reg) +{ + return readl(reg_base + reg); +} + +static void n64joy_wait_si_dma(u32 __iomem *reg_base) +{ + while (n64joy_read_reg(reg_base, SI_STATUS_REG) & + (SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY)) + cpu_relax(); +} + +static void n64joy_exec_pif(struct n64joy_priv *priv, const u64 in[8]) +{ + unsigned long flags; + + dma_cache_wback_inv((unsigned long) in, 8 * 8); + dma_cache_inv((unsigned long) priv->si_buf, 8 * 8); + + local_irq_save(flags); + + n64joy_wait_si_dma(priv->reg_base); + + barrier(); + n64joy_write_reg(priv->reg_base, SI_DRAM_REG, virt_to_phys(in)); + barrier(); + n64joy_write_reg(priv->reg_base, SI_WRITE_REG, PIF_RAM); + barrier(); + + n64joy_wait_si_dma(priv->reg_base); + + barrier(); + n64joy_write_reg(priv->reg_base, SI_DRAM_REG, virt_to_phys(priv->si_buf)); + barrier(); + n64joy_write_reg(priv->reg_base, SI_READ_REG, PIF_RAM); + barrier(); + + n64joy_wait_si_dma(priv->reg_base); + + local_irq_restore(flags); +} + +static const u64 polldata[] ____cacheline_aligned = { + 0xff010401ffffffff, + 0xff010401ffffffff, + 0xff010401ffffffff, + 0xff010401ffffffff, + 0xfe00000000000000, + 0, + 0, + 1 +}; + +static void n64joy_poll(struct timer_list *t) +{ + const struct joydata *data; + struct n64joy_priv *priv = container_of(t, struct n64joy_priv, timer); + struct input_dev *dev; + u32 i; + + n64joy_exec_pif(priv, polldata); + + data = (struct joydata *) priv->si_buf; + + for (i = 0; i < MAX_CONTROLLERS; i++) { + if (!priv->n64joy_dev[i]) + continue; + + dev = priv->n64joy_dev[i]; + + /* d-pad */ + input_report_key(dev, BTN_DPAD_UP, data[i].up); + input_report_key(dev, BTN_DPAD_DOWN, data[i].down); + input_report_key(dev, BTN_DPAD_LEFT, data[i].left); + input_report_key(dev, BTN_DPAD_RIGHT, data[i].right); + + /* c buttons */ + input_report_key(dev, BTN_FORWARD, data[i].c_up); + input_report_key(dev, BTN_BACK, data[i].c_down); + input_report_key(dev, BTN_LEFT, data[i].c_left); + input_report_key(dev, BTN_RIGHT, data[i].c_right); + + /* matching buttons */ + input_report_key(dev, BTN_START, data[i].start); + input_report_key(dev, BTN_Z, data[i].z); + + /* remaining ones: a, b, l, r */ + input_report_key(dev, BTN_0, data[i].a); + input_report_key(dev, BTN_1, data[i].b); + input_report_key(dev, BTN_2, data[i].l); + input_report_key(dev, BTN_3, data[i].r); + + input_report_abs(dev, ABS_X, data[i].x); + input_report_abs(dev, ABS_Y, data[i].y); + + input_sync(dev); + } + + mod_timer(&priv->timer, jiffies + msecs_to_jiffies(16)); +} + +static int n64joy_open(struct input_dev *dev) +{ + struct n64joy_priv *priv = input_get_drvdata(dev); + + scoped_guard(mutex_intr, &priv->n64joy_mutex) { + if (!priv->n64joy_opened) { + /* + * We could use the vblank irq, but it's not important + * if the poll point slightly changes. + */ + timer_setup(&priv->timer, n64joy_poll, 0); + mod_timer(&priv->timer, jiffies + msecs_to_jiffies(16)); + } + + priv->n64joy_opened++; + return 0; + } + + return -EINTR; +} + +static void n64joy_close(struct input_dev *dev) +{ + struct n64joy_priv *priv = input_get_drvdata(dev); + + guard(mutex)(&priv->n64joy_mutex); + + if (!--priv->n64joy_opened) + timer_delete_sync(&priv->timer); +} + +static const u64 __initconst scandata[] ____cacheline_aligned = { + 0xff010300ffffffff, + 0xff010300ffffffff, + 0xff010300ffffffff, + 0xff010300ffffffff, + 0xfe00000000000000, + 0, + 0, + 1 +}; + +/* + * The target device is embedded and RAM-constrained. We save RAM + * by initializing in __init code that gets dropped late in boot. + * For the same reason there is no module or unloading support. + */ +static int __init n64joy_probe(struct platform_device *pdev) +{ + const struct joydata *data; + struct n64joy_priv *priv; + struct input_dev *dev; + int err = 0; + u32 i, j, found = 0; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + mutex_init(&priv->n64joy_mutex); + + priv->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->reg_base)) { + err = PTR_ERR(priv->reg_base); + goto fail; + } + + /* The controllers are not hotpluggable, so we can scan in init */ + n64joy_exec_pif(priv, scandata); + + data = (struct joydata *) priv->si_buf; + + for (i = 0; i < MAX_CONTROLLERS; i++) { + if (!data[i].err && data[i].data >> 16 == N64_CONTROLLER_ID) { + found++; + + dev = priv->n64joy_dev[i] = input_allocate_device(); + if (!priv->n64joy_dev[i]) { + err = -ENOMEM; + goto fail; + } + + input_set_drvdata(dev, priv); + + dev->name = "N64 controller"; + dev->phys = n64joy_phys[i]; + dev->id.bustype = BUS_HOST; + dev->id.vendor = 0; + dev->id.product = data[i].data >> 16; + dev->id.version = 0; + dev->dev.parent = &pdev->dev; + + dev->open = n64joy_open; + dev->close = n64joy_close; + + /* d-pad */ + input_set_capability(dev, EV_KEY, BTN_DPAD_UP); + input_set_capability(dev, EV_KEY, BTN_DPAD_DOWN); + input_set_capability(dev, EV_KEY, BTN_DPAD_LEFT); + input_set_capability(dev, EV_KEY, BTN_DPAD_RIGHT); + /* c buttons */ + input_set_capability(dev, EV_KEY, BTN_LEFT); + input_set_capability(dev, EV_KEY, BTN_RIGHT); + input_set_capability(dev, EV_KEY, BTN_FORWARD); + input_set_capability(dev, EV_KEY, BTN_BACK); + /* matching buttons */ + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_Z); + /* remaining ones: a, b, l, r */ + input_set_capability(dev, EV_KEY, BTN_0); + input_set_capability(dev, EV_KEY, BTN_1); + input_set_capability(dev, EV_KEY, BTN_2); + input_set_capability(dev, EV_KEY, BTN_3); + + for (j = 0; j < 2; j++) + input_set_abs_params(dev, ABS_X + j, + S8_MIN, S8_MAX, 0, 0); + + err = input_register_device(dev); + if (err) { + input_free_device(dev); + goto fail; + } + } + } + + pr_info("%u controller(s) connected\n", found); + + if (!found) + return -ENODEV; + + return 0; +fail: + for (i = 0; i < MAX_CONTROLLERS; i++) { + if (!priv->n64joy_dev[i]) + continue; + input_unregister_device(priv->n64joy_dev[i]); + } + return err; +} + +static struct platform_driver n64joy_driver = { + .driver = { + .name = "n64joy", + }, +}; + +static int __init n64joy_init(void) +{ + return platform_driver_probe(&n64joy_driver, n64joy_probe); +} + +module_init(n64joy_init); diff --git a/drivers/input/joystick/psxpad-spi.c b/drivers/input/joystick/psxpad-spi.c new file mode 100644 index 000000000..aac77a2cf --- /dev/null +++ b/drivers/input/joystick/psxpad-spi.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PlayStation 1/2 joypads via SPI interface Driver + * + * Copyright (C) 2017 Tomohiro Yoshidomi <sylph23k@gmail.com> + * + * PlayStation 1/2 joypad's plug (not socket) + * 123 456 789 + * (...|...|...) + * + * 1: DAT -> MISO (pullup with 1k owm to 3.3V) + * 2: CMD -> MOSI + * 3: 9V (for motor, if not use N.C.) + * 4: GND + * 5: 3.3V + * 6: Attention -> CS(SS) + * 7: SCK -> SCK + * 8: N.C. + * 9: ACK -> N.C. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/types.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +#define REVERSE_BIT(x) ((((x) & 0x80) >> 7) | (((x) & 0x40) >> 5) | \ + (((x) & 0x20) >> 3) | (((x) & 0x10) >> 1) | (((x) & 0x08) << 1) | \ + (((x) & 0x04) << 3) | (((x) & 0x02) << 5) | (((x) & 0x01) << 7)) + +/* PlayStation 1/2 joypad command and response are LSBFIRST. */ + +/* + * 0x01, 0x42, 0x00, 0x00, 0x00, + * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + */ +static const u8 PSX_CMD_POLL[] = { + 0x80, 0x42, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +#ifdef CONFIG_JOYSTICK_PSXPAD_SPI_FF +/* 0x01, 0x43, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 */ +static const u8 PSX_CMD_ENTER_CFG[] = { + 0x80, 0xC2, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +/* 0x01, 0x43, 0x00, 0x00, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A */ +static const u8 PSX_CMD_EXIT_CFG[] = { + 0x80, 0xC2, 0x00, 0x00, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A +}; +/* 0x01, 0x4D, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF */ +static const u8 PSX_CMD_ENABLE_MOTOR[] = { + 0x80, 0xB2, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF +}; +#endif /* CONFIG_JOYSTICK_PSXPAD_SPI_FF */ + +struct psxpad { + struct spi_device *spi; + struct input_dev *idev; + char phys[0x20]; + bool motor1enable; + bool motor2enable; + u8 motor1level; + u8 motor2level; + u8 sendbuf[0x20] ____cacheline_aligned; + u8 response[sizeof(PSX_CMD_POLL)] ____cacheline_aligned; +}; + +static int psxpad_command(struct psxpad *pad, const u8 sendcmdlen) +{ + struct spi_transfer xfers = { + .tx_buf = pad->sendbuf, + .rx_buf = pad->response, + .len = sendcmdlen, + }; + int err; + + err = spi_sync_transfer(pad->spi, &xfers, 1); + if (err) { + dev_err(&pad->spi->dev, + "%s: failed to SPI xfers mode: %d\n", + __func__, err); + return err; + } + + return 0; +} + +#ifdef CONFIG_JOYSTICK_PSXPAD_SPI_FF +static void psxpad_control_motor(struct psxpad *pad, + bool motor1enable, bool motor2enable) +{ + int err; + + pad->motor1enable = motor1enable; + pad->motor2enable = motor2enable; + + memcpy(pad->sendbuf, PSX_CMD_ENTER_CFG, sizeof(PSX_CMD_ENTER_CFG)); + err = psxpad_command(pad, sizeof(PSX_CMD_ENTER_CFG)); + if (err) { + dev_err(&pad->spi->dev, + "%s: failed to enter config mode: %d\n", + __func__, err); + return; + } + + memcpy(pad->sendbuf, PSX_CMD_ENABLE_MOTOR, + sizeof(PSX_CMD_ENABLE_MOTOR)); + pad->sendbuf[3] = pad->motor1enable ? 0x00 : 0xFF; + pad->sendbuf[4] = pad->motor2enable ? 0x80 : 0xFF; + err = psxpad_command(pad, sizeof(PSX_CMD_ENABLE_MOTOR)); + if (err) { + dev_err(&pad->spi->dev, + "%s: failed to enable motor mode: %d\n", + __func__, err); + return; + } + + memcpy(pad->sendbuf, PSX_CMD_EXIT_CFG, sizeof(PSX_CMD_EXIT_CFG)); + err = psxpad_command(pad, sizeof(PSX_CMD_EXIT_CFG)); + if (err) { + dev_err(&pad->spi->dev, + "%s: failed to exit config mode: %d\n", + __func__, err); + return; + } +} + +static void psxpad_set_motor_level(struct psxpad *pad, + u8 motor1level, u8 motor2level) +{ + pad->motor1level = motor1level ? 0xFF : 0x00; + pad->motor2level = REVERSE_BIT(motor2level); +} + +static int psxpad_spi_play_effect(struct input_dev *idev, + void *data, struct ff_effect *effect) +{ + struct psxpad *pad = input_get_drvdata(idev); + + switch (effect->type) { + case FF_RUMBLE: + psxpad_set_motor_level(pad, + (effect->u.rumble.weak_magnitude >> 8) & 0xFFU, + (effect->u.rumble.strong_magnitude >> 8) & 0xFFU); + break; + } + + return 0; +} + +static int psxpad_spi_init_ff(struct psxpad *pad) +{ + int err; + + input_set_capability(pad->idev, EV_FF, FF_RUMBLE); + + err = input_ff_create_memless(pad->idev, NULL, psxpad_spi_play_effect); + if (err) { + dev_err(&pad->spi->dev, + "input_ff_create_memless() failed: %d\n", err); + return err; + } + + return 0; +} + +#else /* CONFIG_JOYSTICK_PSXPAD_SPI_FF */ + +static void psxpad_control_motor(struct psxpad *pad, + bool motor1enable, bool motor2enable) +{ +} + +static void psxpad_set_motor_level(struct psxpad *pad, + u8 motor1level, u8 motor2level) +{ +} + +static inline int psxpad_spi_init_ff(struct psxpad *pad) +{ + return 0; +} +#endif /* CONFIG_JOYSTICK_PSXPAD_SPI_FF */ + +static int psxpad_spi_poll_open(struct input_dev *input) +{ + struct psxpad *pad = input_get_drvdata(input); + + pm_runtime_get_sync(&pad->spi->dev); + + return 0; +} + +static void psxpad_spi_poll_close(struct input_dev *input) +{ + struct psxpad *pad = input_get_drvdata(input); + + pm_runtime_put_sync(&pad->spi->dev); +} + +static void psxpad_spi_poll(struct input_dev *input) +{ + struct psxpad *pad = input_get_drvdata(input); + u8 b_rsp3, b_rsp4; + int err; + + psxpad_control_motor(pad, true, true); + + memcpy(pad->sendbuf, PSX_CMD_POLL, sizeof(PSX_CMD_POLL)); + pad->sendbuf[3] = pad->motor1enable ? pad->motor1level : 0x00; + pad->sendbuf[4] = pad->motor2enable ? pad->motor2level : 0x00; + err = psxpad_command(pad, sizeof(PSX_CMD_POLL)); + if (err) { + dev_err(&pad->spi->dev, + "%s: poll command failed mode: %d\n", __func__, err); + return; + } + + switch (pad->response[1]) { + case 0xCE: /* 0x73 : analog 1 */ + /* button data is inverted */ + b_rsp3 = ~pad->response[3]; + b_rsp4 = ~pad->response[4]; + + input_report_abs(input, ABS_X, REVERSE_BIT(pad->response[7])); + input_report_abs(input, ABS_Y, REVERSE_BIT(pad->response[8])); + input_report_abs(input, ABS_RX, REVERSE_BIT(pad->response[5])); + input_report_abs(input, ABS_RY, REVERSE_BIT(pad->response[6])); + input_report_key(input, BTN_DPAD_UP, b_rsp3 & BIT(3)); + input_report_key(input, BTN_DPAD_DOWN, b_rsp3 & BIT(1)); + input_report_key(input, BTN_DPAD_LEFT, b_rsp3 & BIT(0)); + input_report_key(input, BTN_DPAD_RIGHT, b_rsp3 & BIT(2)); + input_report_key(input, BTN_X, b_rsp4 & BIT(3)); + input_report_key(input, BTN_A, b_rsp4 & BIT(2)); + input_report_key(input, BTN_B, b_rsp4 & BIT(1)); + input_report_key(input, BTN_Y, b_rsp4 & BIT(0)); + input_report_key(input, BTN_TL, b_rsp4 & BIT(5)); + input_report_key(input, BTN_TR, b_rsp4 & BIT(4)); + input_report_key(input, BTN_TL2, b_rsp4 & BIT(7)); + input_report_key(input, BTN_TR2, b_rsp4 & BIT(6)); + input_report_key(input, BTN_THUMBL, b_rsp3 & BIT(6)); + input_report_key(input, BTN_THUMBR, b_rsp3 & BIT(5)); + input_report_key(input, BTN_SELECT, b_rsp3 & BIT(7)); + input_report_key(input, BTN_START, b_rsp3 & BIT(4)); + break; + + case 0x82: /* 0x41 : digital */ + /* button data is inverted */ + b_rsp3 = ~pad->response[3]; + b_rsp4 = ~pad->response[4]; + + input_report_abs(input, ABS_X, 0x80); + input_report_abs(input, ABS_Y, 0x80); + input_report_abs(input, ABS_RX, 0x80); + input_report_abs(input, ABS_RY, 0x80); + input_report_key(input, BTN_DPAD_UP, b_rsp3 & BIT(3)); + input_report_key(input, BTN_DPAD_DOWN, b_rsp3 & BIT(1)); + input_report_key(input, BTN_DPAD_LEFT, b_rsp3 & BIT(0)); + input_report_key(input, BTN_DPAD_RIGHT, b_rsp3 & BIT(2)); + input_report_key(input, BTN_X, b_rsp4 & BIT(3)); + input_report_key(input, BTN_A, b_rsp4 & BIT(2)); + input_report_key(input, BTN_B, b_rsp4 & BIT(1)); + input_report_key(input, BTN_Y, b_rsp4 & BIT(0)); + input_report_key(input, BTN_TL, b_rsp4 & BIT(5)); + input_report_key(input, BTN_TR, b_rsp4 & BIT(4)); + input_report_key(input, BTN_TL2, b_rsp4 & BIT(7)); + input_report_key(input, BTN_TR2, b_rsp4 & BIT(6)); + input_report_key(input, BTN_THUMBL, false); + input_report_key(input, BTN_THUMBR, false); + input_report_key(input, BTN_SELECT, b_rsp3 & BIT(7)); + input_report_key(input, BTN_START, b_rsp3 & BIT(4)); + break; + } + + input_sync(input); +} + +static int psxpad_spi_probe(struct spi_device *spi) +{ + struct psxpad *pad; + struct input_dev *idev; + int err; + + pad = devm_kzalloc(&spi->dev, sizeof(struct psxpad), GFP_KERNEL); + if (!pad) + return -ENOMEM; + + idev = devm_input_allocate_device(&spi->dev); + if (!idev) { + dev_err(&spi->dev, "failed to allocate input device\n"); + return -ENOMEM; + } + + /* input poll device settings */ + pad->idev = idev; + pad->spi = spi; + + /* input device settings */ + input_set_drvdata(idev, pad); + + idev->name = "PlayStation 1/2 joypad"; + snprintf(pad->phys, sizeof(pad->phys), "%s/input", dev_name(&spi->dev)); + idev->id.bustype = BUS_SPI; + + idev->open = psxpad_spi_poll_open; + idev->close = psxpad_spi_poll_close; + + /* key/value map settings */ + input_set_abs_params(idev, ABS_X, 0, 255, 0, 0); + input_set_abs_params(idev, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(idev, ABS_RX, 0, 255, 0, 0); + input_set_abs_params(idev, ABS_RY, 0, 255, 0, 0); + input_set_capability(idev, EV_KEY, BTN_DPAD_UP); + input_set_capability(idev, EV_KEY, BTN_DPAD_DOWN); + input_set_capability(idev, EV_KEY, BTN_DPAD_LEFT); + input_set_capability(idev, EV_KEY, BTN_DPAD_RIGHT); + input_set_capability(idev, EV_KEY, BTN_A); + input_set_capability(idev, EV_KEY, BTN_B); + input_set_capability(idev, EV_KEY, BTN_X); + input_set_capability(idev, EV_KEY, BTN_Y); + input_set_capability(idev, EV_KEY, BTN_TL); + input_set_capability(idev, EV_KEY, BTN_TR); + input_set_capability(idev, EV_KEY, BTN_TL2); + input_set_capability(idev, EV_KEY, BTN_TR2); + input_set_capability(idev, EV_KEY, BTN_THUMBL); + input_set_capability(idev, EV_KEY, BTN_THUMBR); + input_set_capability(idev, EV_KEY, BTN_SELECT); + input_set_capability(idev, EV_KEY, BTN_START); + + err = psxpad_spi_init_ff(pad); + if (err) + return err; + + /* SPI settings */ + spi->mode = SPI_MODE_3; + spi->bits_per_word = 8; + /* (PlayStation 1/2 joypad might be possible works 250kHz/500kHz) */ + spi->controller->min_speed_hz = 125000; + spi->controller->max_speed_hz = 125000; + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "failed to set up SPI: %d\n", err); + return err; + } + + /* pad settings */ + psxpad_set_motor_level(pad, 0, 0); + + + err = input_setup_polling(idev, psxpad_spi_poll); + if (err) { + dev_err(&spi->dev, "failed to set up polling: %d\n", err); + return err; + } + + /* poll interval is about 60fps */ + input_set_poll_interval(idev, 16); + input_set_min_poll_interval(idev, 8); + input_set_max_poll_interval(idev, 32); + + /* register input poll device */ + err = input_register_device(idev); + if (err) { + dev_err(&spi->dev, + "failed to register input device: %d\n", err); + return err; + } + + spi_set_drvdata(spi, pad); + pm_runtime_enable(&spi->dev); + + return 0; +} + +static int psxpad_spi_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct psxpad *pad = spi_get_drvdata(spi); + + psxpad_set_motor_level(pad, 0, 0); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(psxpad_spi_pm, psxpad_spi_suspend, NULL); + +static const struct spi_device_id psxpad_spi_id[] = { + { "psxpad-spi", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, psxpad_spi_id); + +static struct spi_driver psxpad_spi_driver = { + .driver = { + .name = "psxpad-spi", + .pm = pm_sleep_ptr(&psxpad_spi_pm), + }, + .id_table = psxpad_spi_id, + .probe = psxpad_spi_probe, +}; + +module_spi_driver(psxpad_spi_driver); + +MODULE_AUTHOR("Tomohiro Yoshidomi <sylph23k@gmail.com>"); +MODULE_DESCRIPTION("PlayStation 1/2 joypads via SPI interface Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/joystick/pxrc.c b/drivers/input/joystick/pxrc.c new file mode 100644 index 000000000..52d9eab66 --- /dev/null +++ b/drivers/input/joystick/pxrc.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Phoenix RC Flight Controller Adapter + * + * Copyright (C) 2018 Marcus Folkesson <marcus.folkesson@gmail.com> + */ + +#include <linux/cleanup.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <linux/usb.h> +#include <linux/usb/input.h> + +#define PXRC_VENDOR_ID 0x1781 +#define PXRC_PRODUCT_ID 0x0898 + +struct pxrc { + struct input_dev *input; + struct usb_interface *intf; + struct urb *urb; + struct mutex pm_mutex; + bool is_open; + char phys[64]; +}; + +static void pxrc_usb_irq(struct urb *urb) +{ + struct pxrc *pxrc = urb->context; + u8 *data = urb->transfer_buffer; + int error; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ETIME: + /* this urb is timing out */ + dev_dbg(&pxrc->intf->dev, + "%s - urb timed out - was the device unplugged?\n", + __func__); + return; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -EPIPE: + /* this urb is terminated, clean up */ + dev_dbg(&pxrc->intf->dev, "%s - urb shutting down with status: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(&pxrc->intf->dev, "%s - nonzero urb status received: %d\n", + __func__, urb->status); + goto exit; + } + + if (urb->actual_length == 8) { + input_report_abs(pxrc->input, ABS_X, data[0]); + input_report_abs(pxrc->input, ABS_Y, data[2]); + input_report_abs(pxrc->input, ABS_RX, data[3]); + input_report_abs(pxrc->input, ABS_RY, data[4]); + input_report_abs(pxrc->input, ABS_RUDDER, data[5]); + input_report_abs(pxrc->input, ABS_THROTTLE, data[6]); + input_report_abs(pxrc->input, ABS_MISC, data[7]); + + input_report_key(pxrc->input, BTN_A, data[1]); + } + +exit: + /* Resubmit to fetch new fresh URBs */ + error = usb_submit_urb(urb, GFP_ATOMIC); + if (error && error != -EPERM) + dev_err(&pxrc->intf->dev, + "%s - usb_submit_urb failed with result: %d", + __func__, error); +} + +static int pxrc_open(struct input_dev *input) +{ + struct pxrc *pxrc = input_get_drvdata(input); + int error; + + guard(mutex)(&pxrc->pm_mutex); + error = usb_submit_urb(pxrc->urb, GFP_KERNEL); + if (error) { + dev_err(&pxrc->intf->dev, + "%s - usb_submit_urb failed, error: %d\n", + __func__, error); + return -EIO; + } + + pxrc->is_open = true; + return 0; +} + +static void pxrc_close(struct input_dev *input) +{ + struct pxrc *pxrc = input_get_drvdata(input); + + guard(mutex)(&pxrc->pm_mutex); + usb_kill_urb(pxrc->urb); + pxrc->is_open = false; +} + +static void pxrc_free_urb(void *_pxrc) +{ + struct pxrc *pxrc = _pxrc; + + usb_free_urb(pxrc->urb); +} + +static int pxrc_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct pxrc *pxrc; + struct usb_endpoint_descriptor *epirq; + size_t xfer_size; + void *xfer_buf; + int error; + + /* + * Locate the endpoint information. This device only has an + * interrupt endpoint. + */ + error = usb_find_common_endpoints(intf->cur_altsetting, + NULL, NULL, &epirq, NULL); + if (error) { + dev_err(&intf->dev, "Could not find endpoint\n"); + return error; + } + + pxrc = devm_kzalloc(&intf->dev, sizeof(*pxrc), GFP_KERNEL); + if (!pxrc) + return -ENOMEM; + + mutex_init(&pxrc->pm_mutex); + pxrc->intf = intf; + + usb_set_intfdata(pxrc->intf, pxrc); + + xfer_size = usb_endpoint_maxp(epirq); + xfer_buf = devm_kmalloc(&intf->dev, xfer_size, GFP_KERNEL); + if (!xfer_buf) + return -ENOMEM; + + pxrc->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pxrc->urb) + return -ENOMEM; + + error = devm_add_action_or_reset(&intf->dev, pxrc_free_urb, pxrc); + if (error) + return error; + + usb_fill_int_urb(pxrc->urb, udev, + usb_rcvintpipe(udev, epirq->bEndpointAddress), + xfer_buf, xfer_size, pxrc_usb_irq, pxrc, 1); + + pxrc->input = devm_input_allocate_device(&intf->dev); + if (!pxrc->input) { + dev_err(&intf->dev, "couldn't allocate input device\n"); + return -ENOMEM; + } + + pxrc->input->name = "PXRC Flight Controller Adapter"; + + usb_make_path(udev, pxrc->phys, sizeof(pxrc->phys)); + strlcat(pxrc->phys, "/input0", sizeof(pxrc->phys)); + pxrc->input->phys = pxrc->phys; + + usb_to_input_id(udev, &pxrc->input->id); + + pxrc->input->open = pxrc_open; + pxrc->input->close = pxrc_close; + + input_set_capability(pxrc->input, EV_KEY, BTN_A); + input_set_abs_params(pxrc->input, ABS_X, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_RX, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_RY, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_RUDDER, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_THROTTLE, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_MISC, 0, 255, 0, 0); + + input_set_drvdata(pxrc->input, pxrc); + + error = input_register_device(pxrc->input); + if (error) + return error; + + return 0; +} + +static void pxrc_disconnect(struct usb_interface *intf) +{ + /* All driver resources are devm-managed. */ +} + +static int pxrc_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct pxrc *pxrc = usb_get_intfdata(intf); + + guard(mutex)(&pxrc->pm_mutex); + if (pxrc->is_open) + usb_kill_urb(pxrc->urb); + + return 0; +} + +static int pxrc_resume(struct usb_interface *intf) +{ + struct pxrc *pxrc = usb_get_intfdata(intf); + + guard(mutex)(&pxrc->pm_mutex); + if (pxrc->is_open && usb_submit_urb(pxrc->urb, GFP_KERNEL) < 0) + return -EIO; + + return 0; +} + +static int pxrc_pre_reset(struct usb_interface *intf) +{ + struct pxrc *pxrc = usb_get_intfdata(intf); + + mutex_lock(&pxrc->pm_mutex); + usb_kill_urb(pxrc->urb); + return 0; +} + +static int pxrc_post_reset(struct usb_interface *intf) +{ + struct pxrc *pxrc = usb_get_intfdata(intf); + int retval = 0; + + if (pxrc->is_open && usb_submit_urb(pxrc->urb, GFP_KERNEL) < 0) + retval = -EIO; + + mutex_unlock(&pxrc->pm_mutex); + + return retval; +} + +static int pxrc_reset_resume(struct usb_interface *intf) +{ + return pxrc_resume(intf); +} + +static const struct usb_device_id pxrc_table[] = { + { USB_DEVICE(PXRC_VENDOR_ID, PXRC_PRODUCT_ID) }, + { } +}; +MODULE_DEVICE_TABLE(usb, pxrc_table); + +static struct usb_driver pxrc_driver = { + .name = "pxrc", + .probe = pxrc_probe, + .disconnect = pxrc_disconnect, + .id_table = pxrc_table, + .suspend = pxrc_suspend, + .resume = pxrc_resume, + .pre_reset = pxrc_pre_reset, + .post_reset = pxrc_post_reset, + .reset_resume = pxrc_reset_resume, +}; + +module_usb_driver(pxrc_driver); + +MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>"); +MODULE_DESCRIPTION("PhoenixRC Flight Controller Adapter"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/joystick/qwiic-joystick.c b/drivers/input/joystick/qwiic-joystick.c new file mode 100644 index 000000000..c471afc78 --- /dev/null +++ b/drivers/input/joystick/qwiic-joystick.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Oleh Kravchenko <oleg@kaa.org.ua> + * + * SparkFun Qwiic Joystick + * Product page:https://www.sparkfun.com/products/15168 + * Firmware and hardware sources:https://github.com/sparkfun/Qwiic_Joystick + */ + +#include <linux/bits.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#define DRV_NAME "qwiic-joystick" + +#define QWIIC_JSK_REG_VERS 1 +#define QWIIC_JSK_REG_DATA 3 + +#define QWIIC_JSK_MAX_AXIS GENMASK(9, 0) +#define QWIIC_JSK_FUZZ 2 +#define QWIIC_JSK_FLAT 2 +#define QWIIC_JSK_POLL_INTERVAL 16 +#define QWIIC_JSK_POLL_MIN 8 +#define QWIIC_JSK_POLL_MAX 32 + +struct qwiic_jsk { + char phys[32]; + struct input_dev *dev; + struct i2c_client *client; +}; + +struct qwiic_ver { + u8 major; + u8 minor; +}; + +struct qwiic_data { + __be16 x; + __be16 y; + u8 thumb; +}; + +static void qwiic_poll(struct input_dev *input) +{ + struct qwiic_jsk *priv = input_get_drvdata(input); + struct qwiic_data data; + int err; + + err = i2c_smbus_read_i2c_block_data(priv->client, QWIIC_JSK_REG_DATA, + sizeof(data), (u8 *)&data); + if (err != sizeof(data)) + return; + + input_report_abs(input, ABS_X, be16_to_cpu(data.x) >> 6); + input_report_abs(input, ABS_Y, be16_to_cpu(data.y) >> 6); + input_report_key(input, BTN_THUMBL, !data.thumb); + input_sync(input); +} + +static int qwiic_probe(struct i2c_client *client) +{ + struct qwiic_jsk *priv; + struct qwiic_ver vers; + int err; + + err = i2c_smbus_read_i2c_block_data(client, QWIIC_JSK_REG_VERS, + sizeof(vers), (u8 *)&vers); + if (err < 0) + return err; + if (err != sizeof(vers)) + return -EIO; + + dev_dbg(&client->dev, "SparkFun Qwiic Joystick, FW: %u.%u\n", + vers.major, vers.minor); + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + snprintf(priv->phys, sizeof(priv->phys), + "i2c/%s", dev_name(&client->dev)); + i2c_set_clientdata(client, priv); + + priv->dev = devm_input_allocate_device(&client->dev); + if (!priv->dev) + return -ENOMEM; + + priv->dev->id.bustype = BUS_I2C; + priv->dev->name = "SparkFun Qwiic Joystick"; + priv->dev->phys = priv->phys; + input_set_drvdata(priv->dev, priv); + + input_set_abs_params(priv->dev, ABS_X, 0, QWIIC_JSK_MAX_AXIS, + QWIIC_JSK_FUZZ, QWIIC_JSK_FLAT); + input_set_abs_params(priv->dev, ABS_Y, 0, QWIIC_JSK_MAX_AXIS, + QWIIC_JSK_FUZZ, QWIIC_JSK_FLAT); + input_set_capability(priv->dev, EV_KEY, BTN_THUMBL); + + err = input_setup_polling(priv->dev, qwiic_poll); + if (err) { + dev_err(&client->dev, "failed to set up polling: %d\n", err); + return err; + } + input_set_poll_interval(priv->dev, QWIIC_JSK_POLL_INTERVAL); + input_set_min_poll_interval(priv->dev, QWIIC_JSK_POLL_MIN); + input_set_max_poll_interval(priv->dev, QWIIC_JSK_POLL_MAX); + + err = input_register_device(priv->dev); + if (err) { + dev_err(&client->dev, "failed to register joystick: %d\n", err); + return err; + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id of_qwiic_match[] = { + { .compatible = "sparkfun,qwiic-joystick", }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_qwiic_match); +#endif /* CONFIG_OF */ + +static const struct i2c_device_id qwiic_id_table[] = { + { .name = KBUILD_MODNAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, qwiic_id_table); + +static struct i2c_driver qwiic_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(of_qwiic_match), + }, + .id_table = qwiic_id_table, + .probe = qwiic_probe, +}; +module_i2c_driver(qwiic_driver); + +MODULE_AUTHOR("Oleh Kravchenko <oleg@kaa.org.ua>"); +MODULE_DESCRIPTION("SparkFun Qwiic Joystick driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/joystick/sensehat-joystick.c b/drivers/input/joystick/sensehat-joystick.c new file mode 100644 index 000000000..a84df39d3 --- /dev/null +++ b/drivers/input/joystick/sensehat-joystick.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Raspberry Pi Sense HAT joystick driver + * http://raspberrypi.org + * + * Copyright (C) 2015 Raspberry Pi + * Copyright (C) 2021 Charles Mirabile, Mwesigwa Guma, Joel Savitz + * + * Original Author: Serge Schneider + * Revised for upstream Linux by: Charles Mirabile, Mwesigwa Guma, Joel Savitz + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/property.h> + +#define JOYSTICK_SMB_REG 0xf2 + +struct sensehat_joystick { + struct platform_device *pdev; + struct input_dev *keys_dev; + unsigned long prev_states; + struct regmap *regmap; +}; + +static const unsigned int keymap[] = { + BTN_DPAD_DOWN, BTN_DPAD_RIGHT, BTN_DPAD_UP, BTN_SELECT, BTN_DPAD_LEFT, +}; + +static irqreturn_t sensehat_joystick_report(int irq, void *cookie) +{ + struct sensehat_joystick *sensehat_joystick = cookie; + unsigned long curr_states, changes; + unsigned int keys; + int error; + int i; + + error = regmap_read(sensehat_joystick->regmap, JOYSTICK_SMB_REG, &keys); + if (error < 0) { + dev_err(&sensehat_joystick->pdev->dev, + "Failed to read joystick state: %d", error); + return IRQ_NONE; + } + curr_states = keys; + bitmap_xor(&changes, &curr_states, &sensehat_joystick->prev_states, + ARRAY_SIZE(keymap)); + + for_each_set_bit(i, &changes, ARRAY_SIZE(keymap)) + input_report_key(sensehat_joystick->keys_dev, keymap[i], + curr_states & BIT(i)); + + input_sync(sensehat_joystick->keys_dev); + sensehat_joystick->prev_states = keys; + return IRQ_HANDLED; +} + +static int sensehat_joystick_probe(struct platform_device *pdev) +{ + struct sensehat_joystick *sensehat_joystick; + int error, i, irq; + + sensehat_joystick = devm_kzalloc(&pdev->dev, sizeof(*sensehat_joystick), + GFP_KERNEL); + if (!sensehat_joystick) + return -ENOMEM; + + sensehat_joystick->pdev = pdev; + + sensehat_joystick->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!sensehat_joystick->regmap) { + dev_err(&pdev->dev, "unable to get sensehat regmap"); + return -ENODEV; + } + + sensehat_joystick->keys_dev = devm_input_allocate_device(&pdev->dev); + if (!sensehat_joystick->keys_dev) { + dev_err(&pdev->dev, "Could not allocate input device"); + return -ENOMEM; + } + + sensehat_joystick->keys_dev->name = "Raspberry Pi Sense HAT Joystick"; + sensehat_joystick->keys_dev->phys = "sensehat-joystick/input0"; + sensehat_joystick->keys_dev->id.bustype = BUS_I2C; + + __set_bit(EV_KEY, sensehat_joystick->keys_dev->evbit); + __set_bit(EV_REP, sensehat_joystick->keys_dev->evbit); + for (i = 0; i < ARRAY_SIZE(keymap); i++) + __set_bit(keymap[i], sensehat_joystick->keys_dev->keybit); + + error = input_register_device(sensehat_joystick->keys_dev); + if (error) { + dev_err(&pdev->dev, "Could not register input device"); + return error; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + error = devm_request_threaded_irq(&pdev->dev, irq, + NULL, sensehat_joystick_report, + IRQF_ONESHOT, "keys", + sensehat_joystick); + if (error) { + dev_err(&pdev->dev, "IRQ request failed"); + return error; + } + + return 0; +} + +static const struct of_device_id sensehat_joystick_device_id[] = { + { .compatible = "raspberrypi,sensehat-joystick" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sensehat_joystick_device_id); + +static struct platform_driver sensehat_joystick_driver = { + .probe = sensehat_joystick_probe, + .driver = { + .name = "sensehat-joystick", + .of_match_table = sensehat_joystick_device_id, + }, +}; + +module_platform_driver(sensehat_joystick_driver); + +MODULE_DESCRIPTION("Raspberry Pi Sense HAT joystick driver"); +MODULE_AUTHOR("Charles Mirabile <cmirabil@redhat.com>"); +MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/joystick/sidewinder.c b/drivers/input/joystick/sidewinder.c new file mode 100644 index 000000000..7f37d18c8 --- /dev/null +++ b/drivers/input/joystick/sidewinder.c @@ -0,0 +1,810 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1998-2005 Vojtech Pavlik + */ + +/* + * Microsoft SideWinder joystick family driver for Linux + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/gameport.h> +#include <linux/jiffies.h> +#include <linux/string_choices.h> + +#define DRIVER_DESC "Microsoft SideWinder joystick family driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * These are really magic values. Changing them can make a problem go away, + * as well as break everything. + */ + +#undef SW_DEBUG +#undef SW_DEBUG_DATA + +#define SW_START 600 /* The time we wait for the first bit [600 us] */ +#define SW_STROBE 60 /* Max time per bit [60 us] */ +#define SW_TIMEOUT 6 /* Wait for everything to settle [6 ms] */ +#define SW_KICK 45 /* Wait after A0 fall till kick [45 us] */ +#define SW_END 8 /* Number of bits before end of packet to kick */ +#define SW_FAIL 16 /* Number of packet read errors to fail and reinitialize */ +#define SW_BAD 2 /* Number of packet read errors to switch off 3d Pro optimization */ +#define SW_OK 64 /* Number of packet read successes to switch optimization back on */ +#define SW_LENGTH 512 /* Max number of bits in a packet */ + +#ifdef SW_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +/* + * SideWinder joystick types ... + */ + +#define SW_ID_3DP 0 +#define SW_ID_GP 1 +#define SW_ID_PP 2 +#define SW_ID_FFP 3 +#define SW_ID_FSP 4 +#define SW_ID_FFW 5 + +/* + * Names, buttons, axes ... + */ + +static char *sw_name[] = { "3D Pro", "GamePad", "Precision Pro", "Force Feedback Pro", "FreeStyle Pro", + "Force Feedback Wheel" }; + +static char sw_abs[][7] = { + { ABS_X, ABS_Y, ABS_RZ, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y }, + { ABS_X, ABS_Y }, + { ABS_X, ABS_Y, ABS_RZ, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y }, + { ABS_X, ABS_Y, ABS_RZ, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y }, + { ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y }, + { ABS_RX, ABS_RUDDER, ABS_THROTTLE }}; + +static char sw_bit[][7] = { + { 10, 10, 9, 10, 1, 1 }, + { 1, 1 }, + { 10, 10, 6, 7, 1, 1 }, + { 10, 10, 6, 7, 1, 1 }, + { 10, 10, 6, 1, 1 }, + { 10, 7, 7, 1, 1 }}; + +static short sw_btn[][12] = { + { BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_THUMB2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_MODE }, + { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START, BTN_MODE }, + { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_SELECT }, + { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_SELECT }, + { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START, BTN_MODE, BTN_SELECT }, + { BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_THUMB2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4 }}; + +static struct { + int x; + int y; +} sw_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; + +struct sw { + struct gameport *gameport; + struct input_dev *dev[4]; + char name[64]; + char phys[4][32]; + int length; + int type; + int bits; + int number; + int fail; + int ok; + int reads; + int bads; +}; + +/* + * sw_read_packet() is a function which reads either a data packet, or an + * identification packet from a SideWinder joystick. The protocol is very, + * very, very braindamaged. Microsoft patented it in US patent #5628686. + */ + +static int sw_read_packet(struct gameport *gameport, unsigned char *buf, int length, int id) +{ + unsigned long flags; + int timeout, bitout, sched, i, kick, start, strobe; + unsigned char pending, u, v; + + i = -id; /* Don't care about data, only want ID */ + timeout = id ? gameport_time(gameport, SW_TIMEOUT * 1000) : 0; /* Set up global timeout for ID packet */ + kick = id ? gameport_time(gameport, SW_KICK) : 0; /* Set up kick timeout for ID packet */ + start = gameport_time(gameport, SW_START); + strobe = gameport_time(gameport, SW_STROBE); + bitout = start; + pending = 0; + sched = 0; + + local_irq_save(flags); /* Quiet, please */ + + gameport_trigger(gameport); /* Trigger */ + v = gameport_read(gameport); + + do { + bitout--; + u = v; + v = gameport_read(gameport); + } while (!(~v & u & 0x10) && (bitout > 0)); /* Wait for first falling edge on clock */ + + if (bitout > 0) + bitout = strobe; /* Extend time if not timed out */ + + while ((timeout > 0 || bitout > 0) && (i < length)) { + + timeout--; + bitout--; /* Decrement timers */ + sched--; + + u = v; + v = gameport_read(gameport); + + if ((~u & v & 0x10) && (bitout > 0)) { /* Rising edge on clock - data bit */ + if (i >= 0) /* Want this data */ + buf[i] = v >> 5; /* Store it */ + i++; /* Advance index */ + bitout = strobe; /* Extend timeout for next bit */ + } + + if (kick && (~v & u & 0x01)) { /* Falling edge on axis 0 */ + sched = kick; /* Schedule second trigger */ + kick = 0; /* Don't schedule next time on falling edge */ + pending = 1; /* Mark schedule */ + } + + if (pending && sched < 0 && (i > -SW_END)) { /* Second trigger time */ + gameport_trigger(gameport); /* Trigger */ + bitout = start; /* Long bit timeout */ + pending = 0; /* Unmark schedule */ + timeout = 0; /* Switch from global to bit timeouts */ + } + } + + local_irq_restore(flags); /* Done - relax */ + +#ifdef SW_DEBUG_DATA + { + int j; + printk(KERN_DEBUG "sidewinder.c: Read %d triplets. [", i); + for (j = 0; j < i; j++) printk("%d", buf[j]); + printk("]\n"); + } +#endif + + return i; +} + +/* + * sw_get_bits() and GB() compose bits from the triplet buffer into a __u64. + * Parameter 'pos' is bit number inside packet where to start at, 'num' is number + * of bits to be read, 'shift' is offset in the resulting __u64 to start at, bits + * is number of bits per triplet. + */ + +#define GB(pos,num) sw_get_bits(buf, pos, num, sw->bits) + +static __u64 sw_get_bits(unsigned char *buf, int pos, int num, char bits) +{ + __u64 data = 0; + int tri = pos % bits; /* Start position */ + int i = pos / bits; + int bit = 0; + + while (num--) { + data |= (__u64)((buf[i] >> tri++) & 1) << bit++; /* Transfer bit */ + if (tri == bits) { + i++; /* Next triplet */ + tri = 0; + } + } + + return data; +} + +/* + * sw_init_digital() initializes a SideWinder 3D Pro joystick + * into digital mode. + */ + +static void sw_init_digital(struct gameport *gameport) +{ + static const int seq[] = { 140, 140+725, 140+300, 0 }; + unsigned long flags; + int i, t; + + local_irq_save(flags); + + i = 0; + do { + gameport_trigger(gameport); /* Trigger */ + t = gameport_time(gameport, SW_TIMEOUT * 1000); + while ((gameport_read(gameport) & 1) && t) t--; /* Wait for axis to fall back to 0 */ + udelay(seq[i]); /* Delay magic time */ + } while (seq[++i]); + + gameport_trigger(gameport); /* Last trigger */ + + local_irq_restore(flags); +} + +/* + * sw_parity() computes parity of __u64 + */ + +static int sw_parity(__u64 t) +{ + int x = t ^ (t >> 32); + + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + x ^= x >> 2; + x ^= x >> 1; + return x & 1; +} + +/* + * sw_ccheck() checks synchronization bits and computes checksum of nibbles. + */ + +static int sw_check(__u64 t) +{ + unsigned char sum = 0; + + if ((t & 0x8080808080808080ULL) ^ 0x80) /* Sync */ + return -1; + + while (t) { /* Sum */ + sum += t & 0xf; + t >>= 4; + } + + return sum & 0xf; +} + +/* + * sw_parse() analyzes SideWinder joystick data, and writes the results into + * the axes and buttons arrays. + */ + +static int sw_parse(unsigned char *buf, struct sw *sw) +{ + int hat, i, j; + struct input_dev *dev; + + switch (sw->type) { + + case SW_ID_3DP: + + if (sw_check(GB(0,64)) || (hat = (GB(6,1) << 3) | GB(60,3)) > 8) + return -1; + + dev = sw->dev[0]; + + input_report_abs(dev, ABS_X, (GB( 3,3) << 7) | GB(16,7)); + input_report_abs(dev, ABS_Y, (GB( 0,3) << 7) | GB(24,7)); + input_report_abs(dev, ABS_RZ, (GB(35,2) << 7) | GB(40,7)); + input_report_abs(dev, ABS_THROTTLE, (GB(32,3) << 7) | GB(48,7)); + + input_report_abs(dev, ABS_HAT0X, sw_hat_to_axis[hat].x); + input_report_abs(dev, ABS_HAT0Y, sw_hat_to_axis[hat].y); + + for (j = 0; j < 7; j++) + input_report_key(dev, sw_btn[SW_ID_3DP][j], !GB(j+8,1)); + + input_report_key(dev, BTN_BASE4, !GB(38,1)); + input_report_key(dev, BTN_BASE5, !GB(37,1)); + + input_sync(dev); + + return 0; + + case SW_ID_GP: + + for (i = 0; i < sw->number; i ++) { + + if (sw_parity(GB(i*15,15))) + return -1; + + input_report_abs(sw->dev[i], ABS_X, GB(i*15+3,1) - GB(i*15+2,1)); + input_report_abs(sw->dev[i], ABS_Y, GB(i*15+0,1) - GB(i*15+1,1)); + + for (j = 0; j < 10; j++) + input_report_key(sw->dev[i], sw_btn[SW_ID_GP][j], !GB(i*15+j+4,1)); + + input_sync(sw->dev[i]); + } + + return 0; + + case SW_ID_PP: + case SW_ID_FFP: + + if (!sw_parity(GB(0,48)) || (hat = GB(42,4)) > 8) + return -1; + + dev = sw->dev[0]; + input_report_abs(dev, ABS_X, GB( 9,10)); + input_report_abs(dev, ABS_Y, GB(19,10)); + input_report_abs(dev, ABS_RZ, GB(36, 6)); + input_report_abs(dev, ABS_THROTTLE, GB(29, 7)); + + input_report_abs(dev, ABS_HAT0X, sw_hat_to_axis[hat].x); + input_report_abs(dev, ABS_HAT0Y, sw_hat_to_axis[hat].y); + + for (j = 0; j < 9; j++) + input_report_key(dev, sw_btn[SW_ID_PP][j], !GB(j,1)); + + input_sync(dev); + + return 0; + + case SW_ID_FSP: + + if (!sw_parity(GB(0,43)) || (hat = GB(28,4)) > 8) + return -1; + + dev = sw->dev[0]; + input_report_abs(dev, ABS_X, GB( 0,10)); + input_report_abs(dev, ABS_Y, GB(16,10)); + input_report_abs(dev, ABS_THROTTLE, GB(32, 6)); + + input_report_abs(dev, ABS_HAT0X, sw_hat_to_axis[hat].x); + input_report_abs(dev, ABS_HAT0Y, sw_hat_to_axis[hat].y); + + for (j = 0; j < 6; j++) + input_report_key(dev, sw_btn[SW_ID_FSP][j], !GB(j+10,1)); + + input_report_key(dev, BTN_TR, !GB(26,1)); + input_report_key(dev, BTN_START, !GB(27,1)); + input_report_key(dev, BTN_MODE, !GB(38,1)); + input_report_key(dev, BTN_SELECT, !GB(39,1)); + + input_sync(dev); + + return 0; + + case SW_ID_FFW: + + if (!sw_parity(GB(0,33))) + return -1; + + dev = sw->dev[0]; + input_report_abs(dev, ABS_RX, GB( 0,10)); + input_report_abs(dev, ABS_RUDDER, GB(10, 6)); + input_report_abs(dev, ABS_THROTTLE, GB(16, 6)); + + for (j = 0; j < 8; j++) + input_report_key(dev, sw_btn[SW_ID_FFW][j], !GB(j+22,1)); + + input_sync(dev); + + return 0; + } + + return -1; +} + +/* + * sw_read() reads SideWinder joystick data, and reinitializes + * the joystick in case of persistent problems. This is the function that is + * called from the generic code to poll the joystick. + */ + +static int sw_read(struct sw *sw) +{ + unsigned char buf[SW_LENGTH]; + int i; + + i = sw_read_packet(sw->gameport, buf, sw->length, 0); + + if (sw->type == SW_ID_3DP && sw->length == 66 && i != 66) { /* Broken packet, try to fix */ + + if (i == 64 && !sw_check(sw_get_bits(buf,0,64,1))) { /* Last init failed, 1 bit mode */ + printk(KERN_WARNING "sidewinder.c: Joystick in wrong mode on %s" + " - going to reinitialize.\n", sw->gameport->phys); + sw->fail = SW_FAIL; /* Reinitialize */ + i = 128; /* Bogus value */ + } + + if (i < 66 && GB(0,64) == GB(i*3-66,64)) /* 1 == 3 */ + i = 66; /* Everything is fine */ + + if (i < 66 && GB(0,64) == GB(66,64)) /* 1 == 2 */ + i = 66; /* Everything is fine */ + + if (i < 66 && GB(i*3-132,64) == GB(i*3-66,64)) { /* 2 == 3 */ + memmove(buf, buf + i - 22, 22); /* Move data */ + i = 66; /* Carry on */ + } + } + + if (i == sw->length && !sw_parse(buf, sw)) { /* Parse data */ + + sw->fail = 0; + sw->ok++; + + if (sw->type == SW_ID_3DP && sw->length == 66 /* Many packets OK */ + && sw->ok > SW_OK) { + + printk(KERN_INFO "sidewinder.c: No more trouble on %s" + " - enabling optimization again.\n", sw->gameport->phys); + sw->length = 22; + } + + return 0; + } + + sw->ok = 0; + sw->fail++; + + if (sw->type == SW_ID_3DP && sw->length == 22 && sw->fail > SW_BAD) { /* Consecutive bad packets */ + + printk(KERN_INFO "sidewinder.c: Many bit errors on %s" + " - disabling optimization.\n", sw->gameport->phys); + sw->length = 66; + } + + if (sw->fail < SW_FAIL) + return -1; /* Not enough, don't reinitialize yet */ + + printk(KERN_WARNING "sidewinder.c: Too many bit errors on %s" + " - reinitializing joystick.\n", sw->gameport->phys); + + if (!i && sw->type == SW_ID_3DP) { /* 3D Pro can be in analog mode */ + mdelay(3 * SW_TIMEOUT); + sw_init_digital(sw->gameport); + } + + mdelay(SW_TIMEOUT); + i = sw_read_packet(sw->gameport, buf, SW_LENGTH, 0); /* Read normal data packet */ + mdelay(SW_TIMEOUT); + sw_read_packet(sw->gameport, buf, SW_LENGTH, i); /* Read ID packet, this initializes the stick */ + + sw->fail = SW_FAIL; + + return -1; +} + +static void sw_poll(struct gameport *gameport) +{ + struct sw *sw = gameport_get_drvdata(gameport); + + sw->reads++; + if (sw_read(sw)) + sw->bads++; +} + +static int sw_open(struct input_dev *dev) +{ + struct sw *sw = input_get_drvdata(dev); + + gameport_start_polling(sw->gameport); + return 0; +} + +static void sw_close(struct input_dev *dev) +{ + struct sw *sw = input_get_drvdata(dev); + + gameport_stop_polling(sw->gameport); +} + +/* + * sw_print_packet() prints the contents of a SideWinder packet. + */ + +static void sw_print_packet(char *name, int length, unsigned char *buf, char bits) +{ + int i; + + printk(KERN_INFO "sidewinder.c: %s packet, %d bits. [", name, length); + for (i = (((length + 3) >> 2) - 1); i >= 0; i--) + printk("%x", (int)sw_get_bits(buf, i << 2, 4, bits)); + printk("]\n"); +} + +/* + * sw_3dp_id() translates the 3DP id into a human legible string. + * Unfortunately I don't know how to do this for the other SW types. + */ + +static void sw_3dp_id(unsigned char *buf, char *comment, size_t size) +{ + int i; + char pnp[8], rev[9]; + + for (i = 0; i < 7; i++) /* ASCII PnP ID */ + pnp[i] = sw_get_bits(buf, 24+8*i, 8, 1); + + for (i = 0; i < 8; i++) /* ASCII firmware revision */ + rev[i] = sw_get_bits(buf, 88+8*i, 8, 1); + + pnp[7] = rev[8] = 0; + + snprintf(comment, size, " [PnP %d.%02d id %s rev %s]", + (int) ((sw_get_bits(buf, 8, 6, 1) << 6) | /* Two 6-bit values */ + sw_get_bits(buf, 16, 6, 1)) / 100, + (int) ((sw_get_bits(buf, 8, 6, 1) << 6) | + sw_get_bits(buf, 16, 6, 1)) % 100, + pnp, rev); +} + +/* + * sw_guess_mode() checks the upper two button bits for toggling - + * indication of that the joystick is in 3-bit mode. This is documented + * behavior for 3DP ID packet, and for example the FSP does this in + * normal packets instead. Fun ... + */ + +static int sw_guess_mode(unsigned char *buf, int len) +{ + int i; + unsigned char xor = 0; + + for (i = 1; i < len; i++) + xor |= (buf[i - 1] ^ buf[i]) & 6; + + return !!xor * 2 + 1; +} + +/* + * sw_connect() probes for SideWinder type joysticks. + */ + +static int sw_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + struct sw *sw; + struct input_dev *input_dev; + int i, j, k, l; + int err = 0; + unsigned char *buf = NULL; /* [SW_LENGTH] */ + unsigned char *idbuf = NULL; /* [SW_LENGTH] */ + unsigned char m = 1; + char comment[40]; + + comment[0] = 0; + + sw = kzalloc_obj(*sw); + buf = kmalloc(SW_LENGTH, GFP_KERNEL); + idbuf = kmalloc(SW_LENGTH, GFP_KERNEL); + if (!sw || !buf || !idbuf) { + err = -ENOMEM; + goto fail1; + } + + sw->gameport = gameport; + + gameport_set_drvdata(gameport, sw); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + dbg("Init 0: Opened %s, io %#x, speed %d", + gameport->phys, gameport->io, gameport->speed); + + i = sw_read_packet(gameport, buf, SW_LENGTH, 0); /* Read normal packet */ + msleep(SW_TIMEOUT); + dbg("Init 1: Mode %d. Length %d.", m , i); + + if (!i) { /* No data. 3d Pro analog mode? */ + sw_init_digital(gameport); /* Switch to digital */ + msleep(SW_TIMEOUT); + i = sw_read_packet(gameport, buf, SW_LENGTH, 0); /* Retry reading packet */ + msleep(SW_TIMEOUT); + dbg("Init 1b: Length %d.", i); + if (!i) { /* No data -> FAIL */ + err = -ENODEV; + goto fail2; + } + } + + j = sw_read_packet(gameport, idbuf, SW_LENGTH, i); /* Read ID. This initializes the stick */ + m |= sw_guess_mode(idbuf, j); /* ID packet should carry mode info [3DP] */ + dbg("Init 2: Mode %d. ID Length %d.", m, j); + + if (j <= 0) { /* Read ID failed. Happens in 1-bit mode on PP */ + msleep(SW_TIMEOUT); + i = sw_read_packet(gameport, buf, SW_LENGTH, 0); /* Retry reading packet */ + m |= sw_guess_mode(buf, i); + dbg("Init 2b: Mode %d. Length %d.", m, i); + if (!i) { + err = -ENODEV; + goto fail2; + } + msleep(SW_TIMEOUT); + j = sw_read_packet(gameport, idbuf, SW_LENGTH, i); /* Retry reading ID */ + dbg("Init 2c: ID Length %d.", j); + } + + sw->type = -1; + k = SW_FAIL; /* Try SW_FAIL times */ + l = 0; + + do { + k--; + msleep(SW_TIMEOUT); + i = sw_read_packet(gameport, buf, SW_LENGTH, 0); /* Read data packet */ + dbg("Init 3: Mode %d. Length %d. Last %d. Tries %d.", m, i, l, k); + + if (i > l) { /* Longer? As we can only lose bits, it makes */ + /* no sense to try detection for a packet shorter */ + l = i; /* than the previous one */ + + sw->number = 1; + sw->gameport = gameport; + sw->length = i; + sw->bits = m; + + dbg("Init 3a: Case %d.\n", i * m); + + switch (i * m) { + case 60: + sw->number++; + fallthrough; + case 45: /* Ambiguous packet length */ + if (j <= 40) { /* ID length less or eq 40 -> FSP */ + fallthrough; + case 43: + sw->type = SW_ID_FSP; + break; + } + sw->number++; + fallthrough; + case 30: + sw->number++; + fallthrough; + case 15: + sw->type = SW_ID_GP; + break; + case 33: + case 31: + sw->type = SW_ID_FFW; + break; + case 48: /* Ambiguous */ + if (j == 14) { /* ID length 14*3 -> FFP */ + sw->type = SW_ID_FFP; + sprintf(comment, " [AC %s]", str_off_on(sw_get_bits(idbuf,38,1,3))); + } else + sw->type = SW_ID_PP; + break; + case 66: + sw->bits = 3; + fallthrough; + case 198: + sw->length = 22; + fallthrough; + case 64: + sw->type = SW_ID_3DP; + if (j == 160) + sw_3dp_id(idbuf, comment, sizeof(comment)); + break; + } + } + + } while (k && sw->type == -1); + + if (sw->type == -1) { + printk(KERN_WARNING "sidewinder.c: unknown joystick device detected " + "on %s, contact <vojtech@ucw.cz>\n", gameport->phys); + sw_print_packet("ID", j * 3, idbuf, 3); + sw_print_packet("Data", i * m, buf, m); + err = -ENODEV; + goto fail2; + } + +#ifdef SW_DEBUG + sw_print_packet("ID", j * 3, idbuf, 3); + sw_print_packet("Data", i * m, buf, m); +#endif + + gameport_set_poll_handler(gameport, sw_poll); + gameport_set_poll_interval(gameport, 20); + + k = i; + l = j; + + for (i = 0; i < sw->number; i++) { + int bits, code; + + snprintf(sw->name, sizeof(sw->name), + "Microsoft SideWinder %s", sw_name[sw->type]); + snprintf(sw->phys[i], sizeof(sw->phys[i]), + "%s/input%d", gameport->phys, i); + + sw->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto fail3; + } + + input_dev->name = sw->name; + input_dev->phys = sw->phys[i]; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_MICROSOFT; + input_dev->id.product = sw->type; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &gameport->dev; + + input_set_drvdata(input_dev, sw); + + input_dev->open = sw_open; + input_dev->close = sw_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (j = 0; (bits = sw_bit[sw->type][j]); j++) { + int min, max, fuzz, flat; + + code = sw_abs[sw->type][j]; + min = bits == 1 ? -1 : 0; + max = (1 << bits) - 1; + fuzz = (bits >> 1) >= 2 ? 1 << ((bits >> 1) - 2) : 0; + flat = code == ABS_THROTTLE || bits < 5 ? + 0 : 1 << (bits - 5); + + input_set_abs_params(input_dev, code, + min, max, fuzz, flat); + } + + for (j = 0; (code = sw_btn[sw->type][j]); j++) + __set_bit(code, input_dev->keybit); + + dbg("%s%s [%d-bit id %d data %d]\n", sw->name, comment, m, l, k); + + err = input_register_device(sw->dev[i]); + if (err) + goto fail4; + } + + out: kfree(buf); + kfree(idbuf); + + return err; + + fail4: input_free_device(sw->dev[i]); + fail3: while (--i >= 0) + input_unregister_device(sw->dev[i]); + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(sw); + goto out; +} + +static void sw_disconnect(struct gameport *gameport) +{ + struct sw *sw = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < sw->number; i++) + input_unregister_device(sw->dev[i]); + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(sw); +} + +static struct gameport_driver sw_drv = { + .driver = { + .name = "sidewinder", + .owner = THIS_MODULE, + }, + .description = DRIVER_DESC, + .connect = sw_connect, + .disconnect = sw_disconnect, +}; + +module_gameport_driver(sw_drv); diff --git a/drivers/input/joystick/spaceball.c b/drivers/input/joystick/spaceball.c new file mode 100644 index 000000000..1af299a41 --- /dev/null +++ b/drivers/input/joystick/spaceball.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * David Thompson + * Joseph Krahn + */ + +/* + * SpaceTec SpaceBall 2003/3003/4000 FLX driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/unaligned.h> + +#define DRIVER_DESC "SpaceTec SpaceBall 2003/3003/4000 FLX driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define SPACEBALL_MAX_LENGTH 128 +#define SPACEBALL_MAX_ID 9 + +#define SPACEBALL_1003 1 +#define SPACEBALL_2003B 3 +#define SPACEBALL_2003C 4 +#define SPACEBALL_3003C 7 +#define SPACEBALL_4000FLX 8 +#define SPACEBALL_4000FLX_L 9 + +static int spaceball_axes[] = { ABS_X, ABS_Z, ABS_Y, ABS_RX, ABS_RZ, ABS_RY }; +static char *spaceball_names[] = { + "?", "SpaceTec SpaceBall 1003", "SpaceTec SpaceBall 2003", "SpaceTec SpaceBall 2003B", + "SpaceTec SpaceBall 2003C", "SpaceTec SpaceBall 3003", "SpaceTec SpaceBall SpaceController", + "SpaceTec SpaceBall 3003C", "SpaceTec SpaceBall 4000FLX", "SpaceTec SpaceBall 4000FLX Lefty" }; + +/* + * Per-Ball data. + */ + +struct spaceball { + struct input_dev *dev; + int idx; + int escape; + unsigned char data[SPACEBALL_MAX_LENGTH]; + char phys[32]; +}; + +/* + * spaceball_process_packet() decodes packets the driver receives from the + * SpaceBall. + */ + +static void spaceball_process_packet(struct spaceball* spaceball) +{ + struct input_dev *dev = spaceball->dev; + unsigned char *data = spaceball->data; + int i; + + if (spaceball->idx < 2) return; + + switch (spaceball->data[0]) { + + case 'D': /* Ball data */ + if (spaceball->idx != 15) return; + /* + * Skip first three bytes; read six axes worth of data. + * Axis values are signed 16-bit big-endian. + */ + data += 3; + for (i = 0; i < ARRAY_SIZE(spaceball_axes); i++) { + input_report_abs(dev, spaceball_axes[i], + (__s16)get_unaligned_be16(&data[i * 2])); + } + break; + + case 'K': /* Button data */ + if (spaceball->idx != 3) return; + input_report_key(dev, BTN_1, (data[2] & 0x01) || (data[2] & 0x20)); + input_report_key(dev, BTN_2, data[2] & 0x02); + input_report_key(dev, BTN_3, data[2] & 0x04); + input_report_key(dev, BTN_4, data[2] & 0x08); + input_report_key(dev, BTN_5, data[1] & 0x01); + input_report_key(dev, BTN_6, data[1] & 0x02); + input_report_key(dev, BTN_7, data[1] & 0x04); + input_report_key(dev, BTN_8, data[1] & 0x10); + break; + + case '.': /* Advanced button data */ + if (spaceball->idx != 3) return; + input_report_key(dev, BTN_1, data[2] & 0x01); + input_report_key(dev, BTN_2, data[2] & 0x02); + input_report_key(dev, BTN_3, data[2] & 0x04); + input_report_key(dev, BTN_4, data[2] & 0x08); + input_report_key(dev, BTN_5, data[2] & 0x10); + input_report_key(dev, BTN_6, data[2] & 0x20); + input_report_key(dev, BTN_7, data[2] & 0x80); + input_report_key(dev, BTN_8, data[1] & 0x01); + input_report_key(dev, BTN_9, data[1] & 0x02); + input_report_key(dev, BTN_A, data[1] & 0x04); + input_report_key(dev, BTN_B, data[1] & 0x08); + input_report_key(dev, BTN_C, data[1] & 0x10); + input_report_key(dev, BTN_MODE, data[1] & 0x20); + break; + + case 'E': /* Device error */ + spaceball->data[spaceball->idx - 1] = 0; + printk(KERN_ERR "spaceball: Device error. [%s]\n", spaceball->data + 1); + break; + + case '?': /* Bad command packet */ + spaceball->data[spaceball->idx - 1] = 0; + printk(KERN_ERR "spaceball: Bad command. [%s]\n", spaceball->data + 1); + break; + } + + input_sync(dev); +} + +/* + * Spaceball 4000 FLX packets all start with a one letter packet-type decriptor, + * and end in 0x0d. It uses '^' as an escape for CR, XOFF and XON characters which + * can occur in the axis values. + */ + +static irqreturn_t spaceball_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct spaceball *spaceball = serio_get_drvdata(serio); + + switch (data) { + case 0xd: + spaceball_process_packet(spaceball); + spaceball->idx = 0; + spaceball->escape = 0; + break; + case '^': + if (!spaceball->escape) { + spaceball->escape = 1; + break; + } + spaceball->escape = 0; + fallthrough; + case 'M': + case 'Q': + case 'S': + if (spaceball->escape) { + spaceball->escape = 0; + data &= 0x1f; + } + fallthrough; + default: + if (spaceball->escape) + spaceball->escape = 0; + if (spaceball->idx < SPACEBALL_MAX_LENGTH) + spaceball->data[spaceball->idx++] = data; + break; + } + return IRQ_HANDLED; +} + +/* + * spaceball_disconnect() is the opposite of spaceball_connect() + */ + +static void spaceball_disconnect(struct serio *serio) +{ + struct spaceball* spaceball = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(spaceball->dev); + kfree(spaceball); +} + +/* + * spaceball_connect() is the routine that is called when someone adds a + * new serio device that supports Spaceball protocol and registers it as + * an input device. + */ + +static int spaceball_connect(struct serio *serio, struct serio_driver *drv) +{ + struct spaceball *spaceball; + struct input_dev *input_dev; + int err = -ENOMEM; + int i, id; + + if ((id = serio->id.id) > SPACEBALL_MAX_ID) + return -ENODEV; + + spaceball = kmalloc_obj(*spaceball); + input_dev = input_allocate_device(); + if (!spaceball || !input_dev) + goto fail1; + + spaceball->dev = input_dev; + snprintf(spaceball->phys, sizeof(spaceball->phys), "%s/input0", serio->phys); + + input_dev->name = spaceball_names[id]; + input_dev->phys = spaceball->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_SPACEBALL; + input_dev->id.product = id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + switch (id) { + case SPACEBALL_4000FLX: + case SPACEBALL_4000FLX_L: + input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_9); + input_dev->keybit[BIT_WORD(BTN_A)] |= BIT_MASK(BTN_A) | + BIT_MASK(BTN_B) | BIT_MASK(BTN_C) | + BIT_MASK(BTN_MODE); + fallthrough; + default: + input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_2) | + BIT_MASK(BTN_3) | BIT_MASK(BTN_4) | + BIT_MASK(BTN_5) | BIT_MASK(BTN_6) | + BIT_MASK(BTN_7) | BIT_MASK(BTN_8); + fallthrough; + case SPACEBALL_3003C: + input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_1) | + BIT_MASK(BTN_8); + } + + for (i = 0; i < 3; i++) { + input_set_abs_params(input_dev, ABS_X + i, -8000, 8000, 8, 40); + input_set_abs_params(input_dev, ABS_RX + i, -1600, 1600, 2, 8); + } + + serio_set_drvdata(serio, spaceball); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(spaceball->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(spaceball); + return err; +} + +/* + * The serio driver structure. + */ + +static const struct serio_device_id spaceball_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_SPACEBALL, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, spaceball_serio_ids); + +static struct serio_driver spaceball_drv = { + .driver = { + .name = "spaceball", + }, + .description = DRIVER_DESC, + .id_table = spaceball_serio_ids, + .interrupt = spaceball_interrupt, + .connect = spaceball_connect, + .disconnect = spaceball_disconnect, +}; + +module_serio_driver(spaceball_drv); diff --git a/drivers/input/joystick/spaceorb.c b/drivers/input/joystick/spaceorb.c new file mode 100644 index 000000000..ba5b31899 --- /dev/null +++ b/drivers/input/joystick/spaceorb.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * David Thompson + */ + +/* + * SpaceTec SpaceOrb 360 and Avenger 6dof controller driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "SpaceTec SpaceOrb 360 and Avenger 6dof controller driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define SPACEORB_MAX_LENGTH 64 + +static int spaceorb_buttons[] = { BTN_TL, BTN_TR, BTN_Y, BTN_X, BTN_B, BTN_A }; +static int spaceorb_axes[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; + +/* + * Per-Orb data. + */ + +struct spaceorb { + struct input_dev *dev; + int idx; + unsigned char data[SPACEORB_MAX_LENGTH]; + char phys[32]; +}; + +static unsigned char spaceorb_xor[] = "SpaceWare"; + +static unsigned char *spaceorb_errors[] = { "EEPROM storing 0 failed", "Receive queue overflow", "Transmit queue timeout", + "Bad packet", "Power brown-out", "EEPROM checksum error", "Hardware fault" }; + +/* + * spaceorb_process_packet() decodes packets the driver receives from the + * SpaceOrb. + */ + +static void spaceorb_process_packet(struct spaceorb *spaceorb) +{ + struct input_dev *dev = spaceorb->dev; + unsigned char *data = spaceorb->data; + unsigned char c = 0; + int axes[6]; + int i; + + if (spaceorb->idx < 2) return; + for (i = 0; i < spaceorb->idx; i++) c ^= data[i]; + if (c) return; + + switch (data[0]) { + + case 'R': /* Reset packet */ + spaceorb->data[spaceorb->idx - 1] = 0; + for (i = 1; i < spaceorb->idx && spaceorb->data[i] == ' '; i++); + printk(KERN_INFO "input: %s [%s] is %s\n", + dev->name, spaceorb->data + i, spaceorb->phys); + break; + + case 'D': /* Ball + button data */ + if (spaceorb->idx != 12) return; + for (i = 0; i < 9; i++) spaceorb->data[i+2] ^= spaceorb_xor[i]; + axes[0] = ( data[2] << 3) | (data[ 3] >> 4); + axes[1] = ((data[3] & 0x0f) << 6) | (data[ 4] >> 1); + axes[2] = ((data[4] & 0x01) << 9) | (data[ 5] << 2) | (data[4] >> 5); + axes[3] = ((data[6] & 0x1f) << 5) | (data[ 7] >> 2); + axes[4] = ((data[7] & 0x03) << 8) | (data[ 8] << 1) | (data[7] >> 6); + axes[5] = ((data[9] & 0x3f) << 4) | (data[10] >> 3); + for (i = 0; i < 6; i++) + input_report_abs(dev, spaceorb_axes[i], axes[i] - ((axes[i] & 0x200) ? 1024 : 0)); + for (i = 0; i < 6; i++) + input_report_key(dev, spaceorb_buttons[i], (data[1] >> i) & 1); + break; + + case 'K': /* Button data */ + if (spaceorb->idx != 5) return; + for (i = 0; i < 6; i++) + input_report_key(dev, spaceorb_buttons[i], (data[2] >> i) & 1); + + break; + + case 'E': /* Error packet */ + if (spaceorb->idx != 4) return; + printk(KERN_ERR "spaceorb: Device error. [ "); + for (i = 0; i < 7; i++) if (data[1] & (1 << i)) printk("%s ", spaceorb_errors[i]); + printk("]\n"); + break; + } + + input_sync(dev); +} + +static irqreturn_t spaceorb_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct spaceorb* spaceorb = serio_get_drvdata(serio); + + if (~data & 0x80) { + if (spaceorb->idx) spaceorb_process_packet(spaceorb); + spaceorb->idx = 0; + } + if (spaceorb->idx < SPACEORB_MAX_LENGTH) + spaceorb->data[spaceorb->idx++] = data & 0x7f; + return IRQ_HANDLED; +} + +/* + * spaceorb_disconnect() is the opposite of spaceorb_connect() + */ + +static void spaceorb_disconnect(struct serio *serio) +{ + struct spaceorb* spaceorb = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(spaceorb->dev); + kfree(spaceorb); +} + +/* + * spaceorb_connect() is the routine that is called when someone adds a + * new serio device that supports SpaceOrb/Avenger protocol and registers + * it as an input device. + */ + +static int spaceorb_connect(struct serio *serio, struct serio_driver *drv) +{ + struct spaceorb *spaceorb; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + spaceorb = kzalloc_obj(*spaceorb); + input_dev = input_allocate_device(); + if (!spaceorb || !input_dev) + goto fail1; + + spaceorb->dev = input_dev; + snprintf(spaceorb->phys, sizeof(spaceorb->phys), "%s/input0", serio->phys); + + input_dev->name = "SpaceTec SpaceOrb 360 / Avenger"; + input_dev->phys = spaceorb->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_SPACEORB; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < 6; i++) + set_bit(spaceorb_buttons[i], input_dev->keybit); + + for (i = 0; i < 6; i++) + input_set_abs_params(input_dev, spaceorb_axes[i], -508, 508, 0, 0); + + serio_set_drvdata(serio, spaceorb); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(spaceorb->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(spaceorb); + return err; +} + +/* + * The serio driver structure. + */ + +static const struct serio_device_id spaceorb_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_SPACEORB, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, spaceorb_serio_ids); + +static struct serio_driver spaceorb_drv = { + .driver = { + .name = "spaceorb", + }, + .description = DRIVER_DESC, + .id_table = spaceorb_serio_ids, + .interrupt = spaceorb_interrupt, + .connect = spaceorb_connect, + .disconnect = spaceorb_disconnect, +}; + +module_serio_driver(spaceorb_drv); diff --git a/drivers/input/joystick/stinger.c b/drivers/input/joystick/stinger.c new file mode 100644 index 000000000..011f9b137 --- /dev/null +++ b/drivers/input/joystick/stinger.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2000 Mark Fletcher + */ + +/* + * Gravis Stinger gamepad driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Gravis Stinger gamepad driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define STINGER_MAX_LENGTH 8 + +/* + * Per-Stinger data. + */ + +struct stinger { + struct input_dev *dev; + int idx; + unsigned char data[STINGER_MAX_LENGTH]; + char phys[32]; +}; + +/* + * stinger_process_packet() decodes packets the driver receives from the + * Stinger. It updates the data accordingly. + */ + +static void stinger_process_packet(struct stinger *stinger) +{ + struct input_dev *dev = stinger->dev; + unsigned char *data = stinger->data; + + if (!stinger->idx) return; + + input_report_key(dev, BTN_A, ((data[0] & 0x20) >> 5)); + input_report_key(dev, BTN_B, ((data[0] & 0x10) >> 4)); + input_report_key(dev, BTN_C, ((data[0] & 0x08) >> 3)); + input_report_key(dev, BTN_X, ((data[0] & 0x04) >> 2)); + input_report_key(dev, BTN_Y, ((data[3] & 0x20) >> 5)); + input_report_key(dev, BTN_Z, ((data[3] & 0x10) >> 4)); + input_report_key(dev, BTN_TL, ((data[3] & 0x08) >> 3)); + input_report_key(dev, BTN_TR, ((data[3] & 0x04) >> 2)); + input_report_key(dev, BTN_SELECT, ((data[3] & 0x02) >> 1)); + input_report_key(dev, BTN_START, (data[3] & 0x01)); + + input_report_abs(dev, ABS_X, (data[1] & 0x3F) - ((data[0] & 0x01) << 6)); + input_report_abs(dev, ABS_Y, ((data[0] & 0x02) << 5) - (data[2] & 0x3F)); + + input_sync(dev); + + return; +} + +/* + * stinger_interrupt() is called by the low level driver when characters + * are ready for us. We then buffer them for further processing, or call the + * packet processing routine. + */ + +static irqreturn_t stinger_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct stinger *stinger = serio_get_drvdata(serio); + + /* All Stinger packets are 4 bytes */ + + if (stinger->idx < STINGER_MAX_LENGTH) + stinger->data[stinger->idx++] = data; + + if (stinger->idx == 4) { + stinger_process_packet(stinger); + stinger->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * stinger_disconnect() is the opposite of stinger_connect() + */ + +static void stinger_disconnect(struct serio *serio) +{ + struct stinger *stinger = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(stinger->dev); + kfree(stinger); +} + +/* + * stinger_connect() is the routine that is called when someone adds a + * new serio device that supports Stinger protocol and registers it as + * an input device. + */ + +static int stinger_connect(struct serio *serio, struct serio_driver *drv) +{ + struct stinger *stinger; + struct input_dev *input_dev; + int err = -ENOMEM; + + stinger = kmalloc_obj(*stinger); + input_dev = input_allocate_device(); + if (!stinger || !input_dev) + goto fail1; + + stinger->dev = input_dev; + snprintf(stinger->phys, sizeof(stinger->phys), "%s/serio0", serio->phys); + + input_dev->name = "Gravis Stinger"; + input_dev->phys = stinger->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_STINGER; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_A)] = BIT_MASK(BTN_A) | BIT_MASK(BTN_B) | + BIT_MASK(BTN_C) | BIT_MASK(BTN_X) | BIT_MASK(BTN_Y) | + BIT_MASK(BTN_Z) | BIT_MASK(BTN_TL) | BIT_MASK(BTN_TR) | + BIT_MASK(BTN_START) | BIT_MASK(BTN_SELECT); + input_set_abs_params(input_dev, ABS_X, -64, 64, 0, 4); + input_set_abs_params(input_dev, ABS_Y, -64, 64, 0, 4); + + serio_set_drvdata(serio, stinger); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(stinger->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(stinger); + return err; +} + +/* + * The serio driver structure. + */ + +static const struct serio_device_id stinger_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_STINGER, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, stinger_serio_ids); + +static struct serio_driver stinger_drv = { + .driver = { + .name = "stinger", + }, + .description = DRIVER_DESC, + .id_table = stinger_serio_ids, + .interrupt = stinger_interrupt, + .connect = stinger_connect, + .disconnect = stinger_disconnect, +}; + +module_serio_driver(stinger_drv); diff --git a/drivers/input/joystick/tmdc.c b/drivers/input/joystick/tmdc.c new file mode 100644 index 000000000..d419f41e6 --- /dev/null +++ b/drivers/input/joystick/tmdc.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1998-2001 Vojtech Pavlik + * + * Based on the work of: + * Trystan Larey-Williams + */ + +/* + * ThrustMaster DirectConnect (BSP) joystick family driver for Linux + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/gameport.h> +#include <linux/input.h> +#include <linux/jiffies.h> + +#define DRIVER_DESC "ThrustMaster DirectConnect joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define TMDC_MAX_START 600 /* 600 us */ +#define TMDC_MAX_STROBE 60 /* 60 us */ +#define TMDC_MAX_LENGTH 13 + +#define TMDC_MODE_M3DI 1 +#define TMDC_MODE_3DRP 3 +#define TMDC_MODE_AT 4 +#define TMDC_MODE_FM 8 +#define TMDC_MODE_FGP 163 + +#define TMDC_BYTE_ID 10 +#define TMDC_BYTE_REV 11 +#define TMDC_BYTE_DEF 12 + +#define TMDC_ABS 7 +#define TMDC_ABS_HAT 4 +#define TMDC_BTN 16 + +static const unsigned char tmdc_byte_a[16] = { 0, 1, 3, 4, 6, 7 }; +static const unsigned char tmdc_byte_d[16] = { 2, 5, 8, 9 }; + +static const signed char tmdc_abs[TMDC_ABS] = + { ABS_X, ABS_Y, ABS_RUDDER, ABS_THROTTLE, ABS_RX, ABS_RY, ABS_RZ }; +static const signed char tmdc_abs_hat[TMDC_ABS_HAT] = + { ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y }; +static const signed char tmdc_abs_at[TMDC_ABS] = + { ABS_X, ABS_Y, ABS_RUDDER, -1, ABS_THROTTLE }; +static const signed char tmdc_abs_fm[TMDC_ABS] = + { ABS_RX, ABS_RY, ABS_X, ABS_Y }; + +static const short tmdc_btn_pad[TMDC_BTN] = + { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_START, BTN_SELECT, BTN_TL, BTN_TR }; +static const short tmdc_btn_joy[TMDC_BTN] = + { BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_THUMB2, BTN_PINKIE, + BTN_BASE3, BTN_BASE4, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z }; +static const short tmdc_btn_fm[TMDC_BTN] = + { BTN_TRIGGER, BTN_C, BTN_B, BTN_A, BTN_THUMB, BTN_X, BTN_Y, BTN_Z, BTN_TOP, BTN_TOP2 }; +static const short tmdc_btn_at[TMDC_BTN] = + { BTN_TRIGGER, BTN_THUMB2, BTN_PINKIE, BTN_THUMB, BTN_BASE6, BTN_BASE5, BTN_BASE4, + BTN_BASE3, BTN_BASE2, BTN_BASE }; + +static const struct { + int x; + int y; +} tmdc_hat_to_axis[] = {{ 0, 0}, { 1, 0}, { 0,-1}, {-1, 0}, { 0, 1}}; + +static const struct tmdc_model { + unsigned char id; + const char *name; + char abs; + char hats; + char btnc[4]; + char btno[4]; + const signed char *axes; + const short *buttons; +} tmdc_models[] = { + { 1, "ThrustMaster Millennium 3D Inceptor", 6, 2, { 4, 2 }, { 4, 6 }, tmdc_abs, tmdc_btn_joy }, + { 3, "ThrustMaster Rage 3D Gamepad", 2, 0, { 8, 2 }, { 0, 0 }, tmdc_abs, tmdc_btn_pad }, + { 4, "ThrustMaster Attack Throttle", 5, 2, { 4, 6 }, { 4, 2 }, tmdc_abs_at, tmdc_btn_at }, + { 8, "ThrustMaster FragMaster", 4, 0, { 8, 2 }, { 0, 0 }, tmdc_abs_fm, tmdc_btn_fm }, + { 163, "Thrustmaster Fusion GamePad", 2, 0, { 8, 2 }, { 0, 0 }, tmdc_abs, tmdc_btn_pad }, + { 0, "Unknown %d-axis, %d-button TM device %d", 0, 0, { 0, 0 }, { 0, 0 }, tmdc_abs, tmdc_btn_joy } +}; + + +struct tmdc_port { + struct input_dev *dev; + char name[64]; + char phys[32]; + int mode; + const signed char *abs; + const short *btn; + unsigned char absc; + unsigned char btnc[4]; + unsigned char btno[4]; +}; + +struct tmdc { + struct gameport *gameport; + struct tmdc_port *port[2]; +#if 0 + struct input_dev *dev[2]; + char name[2][64]; + char phys[2][32]; + int mode[2]; + signed char *abs[2]; + short *btn[2]; + unsigned char absc[2]; + unsigned char btnc[2][4]; + unsigned char btno[2][4]; +#endif + int reads; + int bads; + unsigned char exists; +}; + +/* + * tmdc_read_packet() reads a ThrustMaster packet. + */ + +static int tmdc_read_packet(struct gameport *gameport, unsigned char data[2][TMDC_MAX_LENGTH]) +{ + unsigned char u, v, w, x; + unsigned long flags; + int i[2], j[2], t[2], p, k; + + p = gameport_time(gameport, TMDC_MAX_STROBE); + + for (k = 0; k < 2; k++) { + t[k] = gameport_time(gameport, TMDC_MAX_START); + i[k] = j[k] = 0; + } + + local_irq_save(flags); + gameport_trigger(gameport); + + w = gameport_read(gameport) >> 4; + + do { + x = w; + w = gameport_read(gameport) >> 4; + + for (k = 0, v = w, u = x; k < 2; k++, v >>= 2, u >>= 2) { + if (~v & u & 2) { + if (t[k] <= 0 || i[k] >= TMDC_MAX_LENGTH) continue; + t[k] = p; + if (j[k] == 0) { /* Start bit */ + if (~v & 1) t[k] = 0; + data[k][i[k]] = 0; j[k]++; continue; + } + if (j[k] == 9) { /* Stop bit */ + if (v & 1) t[k] = 0; + j[k] = 0; i[k]++; continue; + } + data[k][i[k]] |= (~v & 1) << (j[k]++ - 1); /* Data bit */ + } + t[k]--; + } + } while (t[0] > 0 || t[1] > 0); + + local_irq_restore(flags); + + return (i[0] == TMDC_MAX_LENGTH) | ((i[1] == TMDC_MAX_LENGTH) << 1); +} + +static int tmdc_parse_packet(struct tmdc_port *port, unsigned char *data) +{ + int i, k, l; + + if (data[TMDC_BYTE_ID] != port->mode) + return -1; + + for (i = 0; i < port->absc; i++) { + if (port->abs[i] < 0) + return 0; + + input_report_abs(port->dev, port->abs[i], data[tmdc_byte_a[i]]); + } + + switch (port->mode) { + + case TMDC_MODE_M3DI: + + i = tmdc_byte_d[0]; + input_report_abs(port->dev, ABS_HAT0X, ((data[i] >> 3) & 1) - ((data[i] >> 1) & 1)); + input_report_abs(port->dev, ABS_HAT0Y, ((data[i] >> 2) & 1) - ( data[i] & 1)); + break; + + case TMDC_MODE_AT: + + i = tmdc_byte_a[3]; + input_report_abs(port->dev, ABS_HAT0X, tmdc_hat_to_axis[(data[i] - 141) / 25].x); + input_report_abs(port->dev, ABS_HAT0Y, tmdc_hat_to_axis[(data[i] - 141) / 25].y); + break; + + } + + for (k = l = 0; k < 4; k++) { + for (i = 0; i < port->btnc[k]; i++) + input_report_key(port->dev, port->btn[i + l], + ((data[tmdc_byte_d[k]] >> (i + port->btno[k])) & 1)); + l += port->btnc[k]; + } + + input_sync(port->dev); + + return 0; +} + +/* + * tmdc_poll() reads and analyzes ThrustMaster joystick data. + */ + +static void tmdc_poll(struct gameport *gameport) +{ + unsigned char data[2][TMDC_MAX_LENGTH]; + struct tmdc *tmdc = gameport_get_drvdata(gameport); + unsigned char r, bad = 0; + int i; + + tmdc->reads++; + + if ((r = tmdc_read_packet(tmdc->gameport, data)) != tmdc->exists) + bad = 1; + else { + for (i = 0; i < 2; i++) { + if (r & (1 << i) & tmdc->exists) { + + if (tmdc_parse_packet(tmdc->port[i], data[i])) + bad = 1; + } + } + } + + tmdc->bads += bad; +} + +static int tmdc_open(struct input_dev *dev) +{ + struct tmdc *tmdc = input_get_drvdata(dev); + + gameport_start_polling(tmdc->gameport); + return 0; +} + +static void tmdc_close(struct input_dev *dev) +{ + struct tmdc *tmdc = input_get_drvdata(dev); + + gameport_stop_polling(tmdc->gameport); +} + +static int tmdc_setup_port(struct tmdc *tmdc, int idx, unsigned char *data) +{ + const struct tmdc_model *model; + struct tmdc_port *port; + struct input_dev *input_dev; + int i, j, b = 0; + int err; + + tmdc->port[idx] = port = kzalloc_obj(struct tmdc_port); + input_dev = input_allocate_device(); + if (!port || !input_dev) { + err = -ENOMEM; + goto fail; + } + + port->mode = data[TMDC_BYTE_ID]; + + for (model = tmdc_models; model->id && model->id != port->mode; model++) + /* empty */; + + port->abs = model->axes; + port->btn = model->buttons; + + if (!model->id) { + port->absc = data[TMDC_BYTE_DEF] >> 4; + for (i = 0; i < 4; i++) + port->btnc[i] = i < (data[TMDC_BYTE_DEF] & 0xf) ? 8 : 0; + } else { + port->absc = model->abs; + for (i = 0; i < 4; i++) + port->btnc[i] = model->btnc[i]; + } + + for (i = 0; i < 4; i++) + port->btno[i] = model->btno[i]; + + snprintf(port->name, sizeof(port->name), model->name, + port->absc, (data[TMDC_BYTE_DEF] & 0xf) << 3, port->mode); + snprintf(port->phys, sizeof(port->phys), "%s/input%d", tmdc->gameport->phys, i); + + port->dev = input_dev; + + input_dev->name = port->name; + input_dev->phys = port->phys; + input_dev->id.bustype = BUS_GAMEPORT; + input_dev->id.vendor = GAMEPORT_ID_VENDOR_THRUSTMASTER; + input_dev->id.product = model->id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &tmdc->gameport->dev; + + input_set_drvdata(input_dev, tmdc); + + input_dev->open = tmdc_open; + input_dev->close = tmdc_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + + for (i = 0; i < port->absc && i < TMDC_ABS; i++) + if (port->abs[i] >= 0) + input_set_abs_params(input_dev, port->abs[i], 8, 248, 2, 4); + + for (i = 0; i < model->hats && i < TMDC_ABS_HAT; i++) + input_set_abs_params(input_dev, tmdc_abs_hat[i], -1, 1, 0, 0); + + for (i = 0; i < 4; i++) { + for (j = 0; j < port->btnc[i] && j < TMDC_BTN; j++) + set_bit(port->btn[j + b], input_dev->keybit); + b += port->btnc[i]; + } + + err = input_register_device(port->dev); + if (err) + goto fail; + + return 0; + + fail: input_free_device(input_dev); + kfree(port); + return err; +} + +/* + * tmdc_probe() probes for ThrustMaster type joysticks. + */ + +static int tmdc_connect(struct gameport *gameport, struct gameport_driver *drv) +{ + unsigned char data[2][TMDC_MAX_LENGTH]; + struct tmdc *tmdc; + int i; + int err; + + tmdc = kzalloc_obj(*tmdc); + if (!tmdc) + return -ENOMEM; + + tmdc->gameport = gameport; + + gameport_set_drvdata(gameport, tmdc); + + err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); + if (err) + goto fail1; + + if (!(tmdc->exists = tmdc_read_packet(gameport, data))) { + err = -ENODEV; + goto fail2; + } + + gameport_set_poll_handler(gameport, tmdc_poll); + gameport_set_poll_interval(gameport, 20); + + for (i = 0; i < 2; i++) { + if (tmdc->exists & (1 << i)) { + + err = tmdc_setup_port(tmdc, i, data[i]); + if (err) + goto fail3; + } + } + + return 0; + + fail3: while (--i >= 0) { + if (tmdc->port[i]) { + input_unregister_device(tmdc->port[i]->dev); + kfree(tmdc->port[i]); + } + } + fail2: gameport_close(gameport); + fail1: gameport_set_drvdata(gameport, NULL); + kfree(tmdc); + return err; +} + +static void tmdc_disconnect(struct gameport *gameport) +{ + struct tmdc *tmdc = gameport_get_drvdata(gameport); + int i; + + for (i = 0; i < 2; i++) { + if (tmdc->port[i]) { + input_unregister_device(tmdc->port[i]->dev); + kfree(tmdc->port[i]); + } + } + gameport_close(gameport); + gameport_set_drvdata(gameport, NULL); + kfree(tmdc); +} + +static struct gameport_driver tmdc_drv = { + .driver = { + .name = "tmdc", + .owner = THIS_MODULE, + }, + .description = DRIVER_DESC, + .connect = tmdc_connect, + .disconnect = tmdc_disconnect, +}; + +module_gameport_driver(tmdc_drv); diff --git a/drivers/input/joystick/turbografx.c b/drivers/input/joystick/turbografx.c new file mode 100644 index 000000000..4ee45f272 --- /dev/null +++ b/drivers/input/joystick/turbografx.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1998-2001 Vojtech Pavlik + * + * Based on the work of: + * Steffen Schwenke + */ + +/* + * TurboGraFX parallel port interface driver for Linux. + */ + +#include <linux/kernel.h> +#include <linux/parport.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("TurboGraFX parallel port interface driver"); +MODULE_LICENSE("GPL"); + +#define TGFX_MAX_PORTS 3 +#define TGFX_MAX_DEVICES 7 + +struct tgfx_config { + int args[TGFX_MAX_DEVICES + 1]; + unsigned int nargs; +}; + +static struct tgfx_config tgfx_cfg[TGFX_MAX_PORTS]; + +module_param_array_named(map, tgfx_cfg[0].args, int, &tgfx_cfg[0].nargs, 0); +MODULE_PARM_DESC(map, "Describes first set of devices (<parport#>,<js1>,<js2>,..<js7>"); +module_param_array_named(map2, tgfx_cfg[1].args, int, &tgfx_cfg[1].nargs, 0); +MODULE_PARM_DESC(map2, "Describes second set of devices"); +module_param_array_named(map3, tgfx_cfg[2].args, int, &tgfx_cfg[2].nargs, 0); +MODULE_PARM_DESC(map3, "Describes third set of devices"); + +#define TGFX_REFRESH_TIME HZ/100 /* 10 ms */ + +#define TGFX_TRIGGER 0x08 +#define TGFX_UP 0x10 +#define TGFX_DOWN 0x20 +#define TGFX_LEFT 0x40 +#define TGFX_RIGHT 0x80 + +#define TGFX_THUMB 0x02 +#define TGFX_THUMB2 0x04 +#define TGFX_TOP 0x01 +#define TGFX_TOP2 0x08 + +static int tgfx_buttons[] = { BTN_TRIGGER, BTN_THUMB, BTN_THUMB2, BTN_TOP, BTN_TOP2 }; + +static struct tgfx { + struct pardevice *pd; + struct timer_list timer; + struct input_dev *dev[TGFX_MAX_DEVICES]; + char name[TGFX_MAX_DEVICES][64]; + char phys[TGFX_MAX_DEVICES][32]; + int sticks; + int used; + int parportno; + struct mutex sem; +} *tgfx_base[TGFX_MAX_PORTS]; + +/* + * tgfx_timer() reads and analyzes TurboGraFX joystick data. + */ + +static void tgfx_timer(struct timer_list *t) +{ + struct tgfx *tgfx = timer_container_of(tgfx, t, timer); + struct input_dev *dev; + int data1, data2, i; + + for (i = 0; i < 7; i++) + if (tgfx->sticks & (1 << i)) { + + dev = tgfx->dev[i]; + + parport_write_data(tgfx->pd->port, ~(1 << i)); + data1 = parport_read_status(tgfx->pd->port) ^ 0x7f; + data2 = parport_read_control(tgfx->pd->port) ^ 0x04; /* CAVEAT parport */ + + input_report_abs(dev, ABS_X, !!(data1 & TGFX_RIGHT) - !!(data1 & TGFX_LEFT)); + input_report_abs(dev, ABS_Y, !!(data1 & TGFX_DOWN ) - !!(data1 & TGFX_UP )); + + input_report_key(dev, BTN_TRIGGER, (data1 & TGFX_TRIGGER)); + input_report_key(dev, BTN_THUMB, (data2 & TGFX_THUMB )); + input_report_key(dev, BTN_THUMB2, (data2 & TGFX_THUMB2 )); + input_report_key(dev, BTN_TOP, (data2 & TGFX_TOP )); + input_report_key(dev, BTN_TOP2, (data2 & TGFX_TOP2 )); + + input_sync(dev); + } + + mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME); +} + +static int tgfx_open(struct input_dev *dev) +{ + struct tgfx *tgfx = input_get_drvdata(dev); + + scoped_guard(mutex_intr, &tgfx->sem) { + if (!tgfx->used++) { + parport_claim(tgfx->pd); + parport_write_control(tgfx->pd->port, 0x04); + mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME); + } + + return 0; + } + + return -EINTR; +} + +static void tgfx_close(struct input_dev *dev) +{ + struct tgfx *tgfx = input_get_drvdata(dev); + + guard(mutex)(&tgfx->sem); + + if (!--tgfx->used) { + timer_delete_sync(&tgfx->timer); + parport_write_control(tgfx->pd->port, 0x00); + parport_release(tgfx->pd); + } +} + + + +/* + * tgfx_probe() probes for tg gamepads. + */ + +static void tgfx_attach(struct parport *pp) +{ + struct tgfx *tgfx; + struct input_dev *input_dev; + struct pardevice *pd; + int i, j, port_idx; + int *n_buttons, n_devs; + struct pardev_cb tgfx_parport_cb; + + for (port_idx = 0; port_idx < TGFX_MAX_PORTS; port_idx++) { + if (tgfx_cfg[port_idx].nargs == 0 || + tgfx_cfg[port_idx].args[0] < 0) + continue; + if (tgfx_cfg[port_idx].args[0] == pp->number) + break; + } + + if (port_idx == TGFX_MAX_PORTS) { + pr_debug("Not using parport%d.\n", pp->number); + return; + } + n_buttons = tgfx_cfg[port_idx].args + 1; + n_devs = tgfx_cfg[port_idx].nargs - 1; + + memset(&tgfx_parport_cb, 0, sizeof(tgfx_parport_cb)); + tgfx_parport_cb.flags = PARPORT_FLAG_EXCL; + + pd = parport_register_dev_model(pp, "turbografx", &tgfx_parport_cb, + port_idx); + if (!pd) { + pr_err("parport busy already - lp.o loaded?\n"); + return; + } + + tgfx = kzalloc_obj(*tgfx); + if (!tgfx) { + printk(KERN_ERR "turbografx.c: Not enough memory\n"); + goto err_unreg_pardev; + } + + mutex_init(&tgfx->sem); + tgfx->pd = pd; + tgfx->parportno = pp->number; + timer_setup(&tgfx->timer, tgfx_timer, 0); + + for (i = 0; i < n_devs; i++) { + if (n_buttons[i] < 1) + continue; + + if (n_buttons[i] > ARRAY_SIZE(tgfx_buttons)) { + printk(KERN_ERR "turbografx.c: Invalid number of buttons %d\n", n_buttons[i]); + goto err_unreg_devs; + } + + tgfx->dev[i] = input_dev = input_allocate_device(); + if (!input_dev) { + printk(KERN_ERR "turbografx.c: Not enough memory for input device\n"); + goto err_unreg_devs; + } + + tgfx->sticks |= (1 << i); + snprintf(tgfx->name[i], sizeof(tgfx->name[i]), + "TurboGraFX %d-button Multisystem joystick", n_buttons[i]); + snprintf(tgfx->phys[i], sizeof(tgfx->phys[i]), + "%s/input%d", tgfx->pd->port->name, i); + + input_dev->name = tgfx->name[i]; + input_dev->phys = tgfx->phys[i]; + input_dev->id.bustype = BUS_PARPORT; + input_dev->id.vendor = 0x0003; + input_dev->id.product = n_buttons[i]; + input_dev->id.version = 0x0100; + + input_set_drvdata(input_dev, tgfx); + + input_dev->open = tgfx_open; + input_dev->close = tgfx_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0); + + for (j = 0; j < n_buttons[i]; j++) + set_bit(tgfx_buttons[j], input_dev->keybit); + + if (input_register_device(tgfx->dev[i])) + goto err_free_dev; + } + + if (!tgfx->sticks) { + printk(KERN_ERR "turbografx.c: No valid devices specified\n"); + goto err_free_tgfx; + } + + tgfx_base[port_idx] = tgfx; + return; + + err_free_dev: + input_free_device(tgfx->dev[i]); + err_unreg_devs: + while (--i >= 0) + if (tgfx->dev[i]) + input_unregister_device(tgfx->dev[i]); + err_free_tgfx: + kfree(tgfx); + err_unreg_pardev: + parport_unregister_device(pd); +} + +static void tgfx_detach(struct parport *port) +{ + int i; + struct tgfx *tgfx; + + for (i = 0; i < TGFX_MAX_PORTS; i++) { + if (tgfx_base[i] && tgfx_base[i]->parportno == port->number) + break; + } + + if (i == TGFX_MAX_PORTS) + return; + + tgfx = tgfx_base[i]; + tgfx_base[i] = NULL; + + for (i = 0; i < TGFX_MAX_DEVICES; i++) + if (tgfx->dev[i]) + input_unregister_device(tgfx->dev[i]); + parport_unregister_device(tgfx->pd); + kfree(tgfx); +} + +static struct parport_driver tgfx_parport_driver = { + .name = "turbografx", + .match_port = tgfx_attach, + .detach = tgfx_detach, +}; + +static int __init tgfx_init(void) +{ + int i; + int have_dev = 0; + + for (i = 0; i < TGFX_MAX_PORTS; i++) { + if (tgfx_cfg[i].nargs == 0 || tgfx_cfg[i].args[0] < 0) + continue; + + if (tgfx_cfg[i].nargs < 2) { + printk(KERN_ERR "turbografx.c: at least one joystick must be specified\n"); + return -EINVAL; + } + + have_dev = 1; + } + + if (!have_dev) + return -ENODEV; + + return parport_register_driver(&tgfx_parport_driver); +} + +static void __exit tgfx_exit(void) +{ + parport_unregister_driver(&tgfx_parport_driver); +} + +module_init(tgfx_init); +module_exit(tgfx_exit); diff --git a/drivers/input/joystick/twidjoy.c b/drivers/input/joystick/twidjoy.c new file mode 100644 index 000000000..fb452552c --- /dev/null +++ b/drivers/input/joystick/twidjoy.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2001 Arndt Schoenewald + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2000 Mark Fletcher + * + * Sponsored by Quelltext AG (http://www.quelltext-ag.de), Dortmund, Germany + */ + +/* + * Driver to use Handykey's Twiddler (the first edition, i.e. the one with + * the RS232 interface) as a joystick under Linux + * + * The Twiddler is a one-handed chording keyboard featuring twelve buttons on + * the front, six buttons on the top, and a built-in tilt sensor. The buttons + * on the front, which are grouped as four rows of three buttons, are pressed + * by the four fingers (this implies only one button per row can be held down + * at the same time) and the buttons on the top are for the thumb. The tilt + * sensor delivers X and Y axis data depending on how the Twiddler is held. + * Additional information can be found at http://www.handykey.com. + * + * This driver does not use the Twiddler for its intended purpose, i.e. as + * a chording keyboard, but as a joystick: pressing and releasing a button + * immediately sends a corresponding button event, and tilting it generates + * corresponding ABS_X and ABS_Y events. This turns the Twiddler into a game + * controller with amazing 18 buttons :-) + * + * Note: The Twiddler2 (the successor of the Twiddler that connects directly + * to the PS/2 keyboard and mouse ports) is NOT supported by this driver! + * + * For questions or feedback regarding this driver module please contact: + * Arndt Schoenewald <arndt@quelltext.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Handykey Twiddler keyboard as a joystick driver" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define TWIDJOY_MAX_LENGTH 5 + +static struct twidjoy_button_spec { + int bitshift; + int bitmask; + int buttons[3]; +} +twidjoy_buttons[] = { + { 0, 3, { BTN_A, BTN_B, BTN_C } }, + { 2, 3, { BTN_X, BTN_Y, BTN_Z } }, + { 4, 3, { BTN_TL, BTN_TR, BTN_TR2 } }, + { 6, 3, { BTN_SELECT, BTN_START, BTN_MODE } }, + { 8, 1, { BTN_BASE5 } }, + { 9, 1, { BTN_BASE } }, + { 10, 1, { BTN_BASE3 } }, + { 11, 1, { BTN_BASE4 } }, + { 12, 1, { BTN_BASE2 } }, + { 13, 1, { BTN_BASE6 } }, + { 0, 0, { 0 } } +}; + +/* + * Per-Twiddler data. + */ + +struct twidjoy { + struct input_dev *dev; + int idx; + unsigned char data[TWIDJOY_MAX_LENGTH]; + char phys[32]; +}; + +/* + * twidjoy_process_packet() decodes packets the driver receives from the + * Twiddler. It updates the data accordingly. + */ + +static void twidjoy_process_packet(struct twidjoy *twidjoy) +{ + struct input_dev *dev = twidjoy->dev; + unsigned char *data = twidjoy->data; + struct twidjoy_button_spec *bp; + int button_bits, abs_x, abs_y; + + button_bits = ((data[1] & 0x7f) << 7) | (data[0] & 0x7f); + + for (bp = twidjoy_buttons; bp->bitmask; bp++) { + int value = (button_bits & (bp->bitmask << bp->bitshift)) >> bp->bitshift; + int i; + + for (i = 0; i < bp->bitmask; i++) + input_report_key(dev, bp->buttons[i], i+1 == value); + } + + abs_x = ((data[4] & 0x07) << 5) | ((data[3] & 0x7C) >> 2); + if (data[4] & 0x08) abs_x -= 256; + + abs_y = ((data[3] & 0x01) << 7) | ((data[2] & 0x7F) >> 0); + if (data[3] & 0x02) abs_y -= 256; + + input_report_abs(dev, ABS_X, -abs_x); + input_report_abs(dev, ABS_Y, +abs_y); + + input_sync(dev); +} + +/* + * twidjoy_interrupt() is called by the low level driver when characters + * are ready for us. We then buffer them for further processing, or call the + * packet processing routine. + */ + +static irqreturn_t twidjoy_interrupt(struct serio *serio, unsigned char data, unsigned int flags) +{ + struct twidjoy *twidjoy = serio_get_drvdata(serio); + + /* All Twiddler packets are 5 bytes. The fact that the first byte + * has a MSB of 0 and all other bytes have a MSB of 1 can be used + * to check and regain sync. */ + + if ((data & 0x80) == 0) + twidjoy->idx = 0; /* this byte starts a new packet */ + else if (twidjoy->idx == 0) + return IRQ_HANDLED; /* wrong MSB -- ignore this byte */ + + if (twidjoy->idx < TWIDJOY_MAX_LENGTH) + twidjoy->data[twidjoy->idx++] = data; + + if (twidjoy->idx == TWIDJOY_MAX_LENGTH) { + twidjoy_process_packet(twidjoy); + twidjoy->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * twidjoy_disconnect() is the opposite of twidjoy_connect() + */ + +static void twidjoy_disconnect(struct serio *serio) +{ + struct twidjoy *twidjoy = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(twidjoy->dev); + kfree(twidjoy); +} + +/* + * twidjoy_connect() is the routine that is called when someone adds a + * new serio device. It looks for the Twiddler, and if found, registers + * it as an input device. + */ + +static int twidjoy_connect(struct serio *serio, struct serio_driver *drv) +{ + struct twidjoy_button_spec *bp; + struct twidjoy *twidjoy; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + twidjoy = kzalloc_obj(*twidjoy); + input_dev = input_allocate_device(); + if (!twidjoy || !input_dev) + goto fail1; + + twidjoy->dev = input_dev; + snprintf(twidjoy->phys, sizeof(twidjoy->phys), "%s/input0", serio->phys); + + input_dev->name = "Handykey Twiddler"; + input_dev->phys = twidjoy->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_TWIDJOY; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_set_abs_params(input_dev, ABS_X, -50, 50, 4, 4); + input_set_abs_params(input_dev, ABS_Y, -50, 50, 4, 4); + + for (bp = twidjoy_buttons; bp->bitmask; bp++) + for (i = 0; i < bp->bitmask; i++) + set_bit(bp->buttons[i], input_dev->keybit); + + serio_set_drvdata(serio, twidjoy); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(twidjoy->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(twidjoy); + return err; +} + +/* + * The serio driver structure. + */ + +static const struct serio_device_id twidjoy_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TWIDJOY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, twidjoy_serio_ids); + +static struct serio_driver twidjoy_drv = { + .driver = { + .name = "twidjoy", + }, + .description = DRIVER_DESC, + .id_table = twidjoy_serio_ids, + .interrupt = twidjoy_interrupt, + .connect = twidjoy_connect, + .disconnect = twidjoy_disconnect, +}; + +module_serio_driver(twidjoy_drv); diff --git a/drivers/input/joystick/walkera0701.c b/drivers/input/joystick/walkera0701.c new file mode 100644 index 000000000..15370fb82 --- /dev/null +++ b/drivers/input/joystick/walkera0701.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Parallel port to Walkera WK-0701 TX joystick + * + * Copyright (c) 2008 Peter Popovec + * + * More about driver: <file:Documentation/input/devices/walkera0701.rst> + */ + + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define RESERVE 20000 +#define SYNC_PULSE 1306000 +#define BIN0_PULSE 288000 +#define BIN1_PULSE 438000 + +#define ANALOG_MIN_PULSE 318000 +#define ANALOG_MAX_PULSE 878000 +#define ANALOG_DELTA 80000 + +#define BIN_SAMPLE ((BIN0_PULSE + BIN1_PULSE) / 2) + +#define NO_SYNC 25 + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/parport.h> +#include <linux/input.h> +#include <linux/hrtimer.h> + +MODULE_AUTHOR("Peter Popovec <popovec@fei.tuke.sk>"); +MODULE_DESCRIPTION("Walkera WK-0701 TX as joystick"); +MODULE_LICENSE("GPL"); + +static unsigned int walkera0701_pp_no; +module_param_named(port, walkera0701_pp_no, int, 0); +MODULE_PARM_DESC(port, + "Parallel port adapter for Walkera WK-0701 TX (default is 0)"); + +/* + * For now, only one device is supported, if somebody need more devices, code + * can be expanded, one struct walkera_dev per device must be allocated and + * set up by walkera0701_connect (release of device by walkera0701_disconnect) + */ + +struct walkera_dev { + unsigned char buf[25]; + u64 irq_time, irq_lasttime; + int counter; + int ack; + + struct input_dev *input_dev; + struct hrtimer timer; + + struct parport *parport; + struct pardevice *pardevice; +}; + +static struct walkera_dev w_dev; + +static inline void walkera0701_parse_frame(struct walkera_dev *w) +{ + int i; + int val1, val2, val3, val4, val5, val6, val7, val8; + int magic, magic_bit; + int crc1, crc2; + + for (crc1 = crc2 = i = 0; i < 10; i++) { + crc1 += w->buf[i] & 7; + crc2 += (w->buf[i] & 8) >> 3; + } + if ((w->buf[10] & 7) != (crc1 & 7)) + return; + if (((w->buf[10] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1)) + return; + for (crc1 = crc2 = 0, i = 11; i < 23; i++) { + crc1 += w->buf[i] & 7; + crc2 += (w->buf[i] & 8) >> 3; + } + if ((w->buf[23] & 7) != (crc1 & 7)) + return; + if (((w->buf[23] & 8) >> 3) != (((crc1 >> 3) + crc2) & 1)) + return; + val1 = ((w->buf[0] & 7) * 256 + w->buf[1] * 16 + w->buf[2]) >> 2; + val1 *= ((w->buf[0] >> 2) & 2) - 1; /* sign */ + val2 = (w->buf[2] & 1) << 8 | (w->buf[3] << 4) | w->buf[4]; + val2 *= (w->buf[2] & 2) - 1; /* sign */ + val3 = ((w->buf[5] & 7) * 256 + w->buf[6] * 16 + w->buf[7]) >> 2; + val3 *= ((w->buf[5] >> 2) & 2) - 1; /* sign */ + val4 = (w->buf[7] & 1) << 8 | (w->buf[8] << 4) | w->buf[9]; + val4 *= (w->buf[7] & 2) - 1; /* sign */ + val5 = ((w->buf[11] & 7) * 256 + w->buf[12] * 16 + w->buf[13]) >> 2; + val5 *= ((w->buf[11] >> 2) & 2) - 1; /* sign */ + val6 = (w->buf[13] & 1) << 8 | (w->buf[14] << 4) | w->buf[15]; + val6 *= (w->buf[13] & 2) - 1; /* sign */ + val7 = ((w->buf[16] & 7) * 256 + w->buf[17] * 16 + w->buf[18]) >> 2; + val7 *= ((w->buf[16] >> 2) & 2) - 1; /*sign */ + val8 = (w->buf[18] & 1) << 8 | (w->buf[19] << 4) | w->buf[20]; + val8 *= (w->buf[18] & 2) - 1; /*sign */ + + magic = (w->buf[21] << 4) | w->buf[22]; + magic_bit = (w->buf[24] & 8) >> 3; + pr_debug("%4d %4d %4d %4d %4d %4d %4d %4d (magic %2x %d)\n", + val1, val2, val3, val4, val5, val6, val7, val8, + magic, magic_bit); + + input_report_abs(w->input_dev, ABS_X, val2); + input_report_abs(w->input_dev, ABS_Y, val1); + input_report_abs(w->input_dev, ABS_Z, val6); + input_report_abs(w->input_dev, ABS_THROTTLE, val3); + input_report_abs(w->input_dev, ABS_RUDDER, val4); + input_report_abs(w->input_dev, ABS_MISC, val7); + input_report_key(w->input_dev, BTN_GEAR_DOWN, val5 > 0); +} + +static inline int read_ack(struct pardevice *p) +{ + return parport_read_status(p->port) & 0x40; +} + +/* falling edge, prepare to BIN value calculation */ +static void walkera0701_irq_handler(void *handler_data) +{ + u64 pulse_time; + struct walkera_dev *w = handler_data; + + w->irq_time = ktime_to_ns(ktime_get()); + pulse_time = w->irq_time - w->irq_lasttime; + w->irq_lasttime = w->irq_time; + + /* cancel timer, if in handler or active do resync */ + if (unlikely(0 != hrtimer_try_to_cancel(&w->timer))) { + w->counter = NO_SYNC; + return; + } + + if (w->counter < NO_SYNC) { + if (w->ack) { + pulse_time -= BIN1_PULSE; + w->buf[w->counter] = 8; + } else { + pulse_time -= BIN0_PULSE; + w->buf[w->counter] = 0; + } + if (w->counter == 24) { /* full frame */ + walkera0701_parse_frame(w); + w->counter = NO_SYNC; + if (abs(pulse_time - SYNC_PULSE) < RESERVE) /* new frame sync */ + w->counter = 0; + } else { + if ((pulse_time > (ANALOG_MIN_PULSE - RESERVE) + && (pulse_time < (ANALOG_MAX_PULSE + RESERVE)))) { + pulse_time -= (ANALOG_MIN_PULSE - RESERVE); + pulse_time = (u32) pulse_time / ANALOG_DELTA; /* overtiping is safe, pulsetime < s32.. */ + w->buf[w->counter++] |= (pulse_time & 7); + } else + w->counter = NO_SYNC; + } + } else if (abs(pulse_time - SYNC_PULSE - BIN0_PULSE) < + RESERVE + BIN1_PULSE - BIN0_PULSE) /* frame sync .. */ + w->counter = 0; + + hrtimer_start(&w->timer, BIN_SAMPLE, HRTIMER_MODE_REL); +} + +static enum hrtimer_restart timer_handler(struct hrtimer + *handle) +{ + struct walkera_dev *w; + + w = container_of(handle, struct walkera_dev, timer); + w->ack = read_ack(w->pardevice); + + return HRTIMER_NORESTART; +} + +static int walkera0701_open(struct input_dev *dev) +{ + struct walkera_dev *w = input_get_drvdata(dev); + + if (parport_claim(w->pardevice)) + return -EBUSY; + + parport_enable_irq(w->parport); + return 0; +} + +static void walkera0701_close(struct input_dev *dev) +{ + struct walkera_dev *w = input_get_drvdata(dev); + + parport_disable_irq(w->parport); + hrtimer_cancel(&w->timer); + + parport_release(w->pardevice); +} + +static void walkera0701_attach(struct parport *pp) +{ + struct pardev_cb walkera0701_parport_cb; + struct walkera_dev *w = &w_dev; + + if (pp->number != walkera0701_pp_no) { + pr_debug("Not using parport%d.\n", pp->number); + return; + } + + if (pp->irq == -1) { + pr_err("parport %d does not have interrupt assigned\n", + pp->number); + return; + } + + w->parport = pp; + + memset(&walkera0701_parport_cb, 0, sizeof(walkera0701_parport_cb)); + walkera0701_parport_cb.flags = PARPORT_FLAG_EXCL; + walkera0701_parport_cb.irq_func = walkera0701_irq_handler; + walkera0701_parport_cb.private = w; + + w->pardevice = parport_register_dev_model(pp, "walkera0701", + &walkera0701_parport_cb, 0); + + if (!w->pardevice) { + pr_err("failed to register parport device\n"); + return; + } + + if (parport_negotiate(w->pardevice->port, IEEE1284_MODE_COMPAT)) { + pr_err("failed to negotiate parport mode\n"); + goto err_unregister_device; + } + + hrtimer_setup(&w->timer, timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + w->input_dev = input_allocate_device(); + if (!w->input_dev) { + pr_err("failed to allocate input device\n"); + goto err_unregister_device; + } + + input_set_drvdata(w->input_dev, w); + w->input_dev->name = "Walkera WK-0701 TX"; + w->input_dev->phys = w->parport->name; + w->input_dev->id.bustype = BUS_PARPORT; + + /* TODO what id vendor/product/version ? */ + w->input_dev->id.vendor = 0x0001; + w->input_dev->id.product = 0x0001; + w->input_dev->id.version = 0x0100; + w->input_dev->dev.parent = w->parport->dev; + w->input_dev->open = walkera0701_open; + w->input_dev->close = walkera0701_close; + + w->input_dev->evbit[0] = BIT(EV_ABS) | BIT_MASK(EV_KEY); + w->input_dev->keybit[BIT_WORD(BTN_GEAR_DOWN)] = BIT_MASK(BTN_GEAR_DOWN); + + input_set_abs_params(w->input_dev, ABS_X, -512, 512, 0, 0); + input_set_abs_params(w->input_dev, ABS_Y, -512, 512, 0, 0); + input_set_abs_params(w->input_dev, ABS_Z, -512, 512, 0, 0); + input_set_abs_params(w->input_dev, ABS_THROTTLE, -512, 512, 0, 0); + input_set_abs_params(w->input_dev, ABS_RUDDER, -512, 512, 0, 0); + input_set_abs_params(w->input_dev, ABS_MISC, -512, 512, 0, 0); + + if (input_register_device(w->input_dev)) { + pr_err("failed to register input device\n"); + goto err_free_input_dev; + } + + return; + +err_free_input_dev: + input_free_device(w->input_dev); +err_unregister_device: + parport_unregister_device(w->pardevice); +} + +static void walkera0701_detach(struct parport *port) +{ + struct walkera_dev *w = &w_dev; + + if (!w->pardevice || w->parport->number != port->number) + return; + + input_unregister_device(w->input_dev); + parport_unregister_device(w->pardevice); + w->parport = NULL; +} + +static struct parport_driver walkera0701_parport_driver = { + .name = "walkera0701", + .match_port = walkera0701_attach, + .detach = walkera0701_detach, +}; + +module_parport_driver(walkera0701_parport_driver); diff --git a/drivers/input/joystick/warrior.c b/drivers/input/joystick/warrior.c new file mode 100644 index 000000000..2e1c6df98 --- /dev/null +++ b/drivers/input/joystick/warrior.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Logitech WingMan Warrior joystick driver for Linux + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Logitech WingMan Warrior joystick driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define WARRIOR_MAX_LENGTH 16 +static char warrior_lengths[] = { 0, 4, 12, 3, 4, 4, 0, 0 }; + +/* + * Per-Warrior data. + */ + +struct warrior { + struct input_dev *dev; + int idx, len; + unsigned char data[WARRIOR_MAX_LENGTH]; + char phys[32]; +}; + +/* + * warrior_process_packet() decodes packets the driver receives from the + * Warrior. It updates the data accordingly. + */ + +static void warrior_process_packet(struct warrior *warrior) +{ + struct input_dev *dev = warrior->dev; + unsigned char *data = warrior->data; + + if (!warrior->idx) return; + + switch ((data[0] >> 4) & 7) { + case 1: /* Button data */ + input_report_key(dev, BTN_TRIGGER, data[3] & 1); + input_report_key(dev, BTN_THUMB, (data[3] >> 1) & 1); + input_report_key(dev, BTN_TOP, (data[3] >> 2) & 1); + input_report_key(dev, BTN_TOP2, (data[3] >> 3) & 1); + break; + case 3: /* XY-axis info->data */ + input_report_abs(dev, ABS_X, ((data[0] & 8) << 5) - (data[2] | ((data[0] & 4) << 5))); + input_report_abs(dev, ABS_Y, (data[1] | ((data[0] & 1) << 7)) - ((data[0] & 2) << 7)); + break; + case 5: /* Throttle, spinner, hat info->data */ + input_report_abs(dev, ABS_THROTTLE, (data[1] | ((data[0] & 1) << 7)) - ((data[0] & 2) << 7)); + input_report_abs(dev, ABS_HAT0X, (data[3] & 2 ? 1 : 0) - (data[3] & 1 ? 1 : 0)); + input_report_abs(dev, ABS_HAT0Y, (data[3] & 8 ? 1 : 0) - (data[3] & 4 ? 1 : 0)); + input_report_rel(dev, REL_DIAL, (data[2] | ((data[0] & 4) << 5)) - ((data[0] & 8) << 5)); + break; + } + input_sync(dev); +} + +/* + * warrior_interrupt() is called by the low level driver when characters + * are ready for us. We then buffer them for further processing, or call the + * packet processing routine. + */ + +static irqreturn_t warrior_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct warrior *warrior = serio_get_drvdata(serio); + + if (data & 0x80) { + if (warrior->idx) warrior_process_packet(warrior); + warrior->idx = 0; + warrior->len = warrior_lengths[(data >> 4) & 7]; + } + + if (warrior->idx < warrior->len) + warrior->data[warrior->idx++] = data; + + if (warrior->idx == warrior->len) { + if (warrior->idx) warrior_process_packet(warrior); + warrior->idx = 0; + warrior->len = 0; + } + return IRQ_HANDLED; +} + +/* + * warrior_disconnect() is the opposite of warrior_connect() + */ + +static void warrior_disconnect(struct serio *serio) +{ + struct warrior *warrior = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(warrior->dev); + kfree(warrior); +} + +/* + * warrior_connect() is the routine that is called when someone adds a + * new serio device. It looks for the Warrior, and if found, registers + * it as an input device. + */ + +static int warrior_connect(struct serio *serio, struct serio_driver *drv) +{ + struct warrior *warrior; + struct input_dev *input_dev; + int err = -ENOMEM; + + warrior = kzalloc_obj(*warrior); + input_dev = input_allocate_device(); + if (!warrior || !input_dev) + goto fail1; + + warrior->dev = input_dev; + snprintf(warrior->phys, sizeof(warrior->phys), "%s/input0", serio->phys); + + input_dev->name = "Logitech WingMan Warrior"; + input_dev->phys = warrior->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_WARRIOR; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) | + BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TRIGGER)] = BIT_MASK(BTN_TRIGGER) | + BIT_MASK(BTN_THUMB) | BIT_MASK(BTN_TOP) | BIT_MASK(BTN_TOP2); + input_dev->relbit[0] = BIT_MASK(REL_DIAL); + input_set_abs_params(input_dev, ABS_X, -64, 64, 0, 8); + input_set_abs_params(input_dev, ABS_Y, -64, 64, 0, 8); + input_set_abs_params(input_dev, ABS_THROTTLE, -112, 112, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0); + + serio_set_drvdata(serio, warrior); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(warrior->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(warrior); + return err; +} + +/* + * The serio driver structure. + */ + +static const struct serio_device_id warrior_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_WARRIOR, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, warrior_serio_ids); + +static struct serio_driver warrior_drv = { + .driver = { + .name = "warrior", + }, + .description = DRIVER_DESC, + .id_table = warrior_serio_ids, + .interrupt = warrior_interrupt, + .connect = warrior_connect, + .disconnect = warrior_disconnect, +}; + +module_serio_driver(warrior_drv); diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c new file mode 100644 index 000000000..2da0b7f17 --- /dev/null +++ b/drivers/input/joystick/xpad.c @@ -0,0 +1,2321 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Xbox gamepad driver + * + * Copyright (c) 2002 Marko Friedemann <mfr@bmx-chemnitz.de> + * 2004 Oliver Schwartz <Oliver.Schwartz@gmx.de>, + * Steven Toth <steve@toth.demon.co.uk>, + * Franz Lehner <franz@caos.at>, + * Ivan Hawkes <blackhawk@ivanhawkes.com> + * 2005 Dominic Cerquetti <binary1230@yahoo.com> + * 2006 Adam Buchbinder <adam.buchbinder@gmail.com> + * 2007 Jan Kratochvil <honza@jikos.cz> + * 2010 Christoph Fritz <chf.fritz@googlemail.com> + * + * This driver is based on: + * - information from http://euc.jp/periphs/xbox-controller.ja.html + * - the iForce driver drivers/char/joystick/iforce.c + * - the skeleton-driver drivers/usb/usb-skeleton.c + * - Xbox 360 information http://www.free60.org/wiki/Gamepad + * - Xbox One information https://github.com/quantus/xbox-one-controller-protocol + * + * Thanks to: + * - ITO Takayuki for providing essential xpad information on his website + * - Vojtech Pavlik - iforce driver / input subsystem + * - Greg Kroah-Hartman - usb-skeleton driver + * - Xbox Linux project - extra USB IDs + * - Pekka Pöyry (quantus) - Xbox One controller reverse-engineering + */ + +#include <linux/bits.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/rcupdate.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/module.h> +#include <linux/usb/input.h> +#include <linux/usb/quirks.h> + +#define XPAD_PKT_LEN 64 + +/* + * xbox d-pads should map to buttons, as is required for DDR pads + * but we map them to axes when possible to simplify things + */ +#define MAP_DPAD_TO_BUTTONS BIT(0) +#define MAP_TRIGGERS_TO_BUTTONS BIT(1) +#define MAP_STICKS_TO_NULL BIT(2) +#define MAP_SHARE_BUTTON BIT(3) +#define MAP_PADDLES BIT(4) +#define MAP_PROFILE_BUTTON BIT(5) +#define MAP_SHARE_OFFSET BIT(6) + +#define DANCEPAD_MAP_CONFIG (MAP_DPAD_TO_BUTTONS | \ + MAP_TRIGGERS_TO_BUTTONS | MAP_STICKS_TO_NULL) + +#define XTYPE_XBOX 0 +#define XTYPE_XBOX360 1 +#define XTYPE_XBOX360W 2 +#define XTYPE_XBOXONE 3 +#define XTYPE_UNKNOWN 4 + +/* Send power-off packet to xpad360w after holding the mode button for this many + * seconds + */ +#define XPAD360W_POWEROFF_TIMEOUT 5 + +#define PKT_XB 0 +#define PKT_XBE1 1 +#define PKT_XBE2_FW_OLD 2 +#define PKT_XBE2_FW_5_EARLY 3 +#define PKT_XBE2_FW_5_11 4 + +#define FLAG_DELAY_INIT BIT(0) + +static bool dpad_to_buttons; +module_param(dpad_to_buttons, bool, S_IRUGO); +MODULE_PARM_DESC(dpad_to_buttons, "Map D-PAD to buttons rather than axes for unknown pads"); + +static bool triggers_to_buttons; +module_param(triggers_to_buttons, bool, S_IRUGO); +MODULE_PARM_DESC(triggers_to_buttons, "Map triggers to buttons rather than axes for unknown pads"); + +static bool sticks_to_null; +module_param(sticks_to_null, bool, S_IRUGO); +MODULE_PARM_DESC(sticks_to_null, "Do not map sticks at all for unknown pads"); + +static bool auto_poweroff = true; +module_param(auto_poweroff, bool, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(auto_poweroff, "Power off wireless controllers on suspend"); + +static const struct xpad_device { + u16 idVendor; + u16 idProduct; + char *name; + u8 mapping; + u8 xtype; + u8 flags; +} xpad_device[] = { + /* Please keep this list sorted by vendor and product ID. */ + { 0x0079, 0x18d4, "GPD Win 2 X-Box Controller", 0, XTYPE_XBOX360 }, + { 0x0351, 0x1000, "CRKD LP Blueberry Burst Pro Edition (Xbox)", 0, XTYPE_XBOX360 }, + { 0x0351, 0x2000, "CRKD LP Black Tribal Edition (Xbox) ", 0, XTYPE_XBOX360 }, + { 0x03eb, 0xff01, "Wooting One (Legacy)", 0, XTYPE_XBOX360 }, + { 0x03eb, 0xff02, "Wooting Two (Legacy)", 0, XTYPE_XBOX360 }, + { 0x03f0, 0x038D, "HyperX Clutch", 0, XTYPE_XBOX360 }, /* wired */ + { 0x03f0, 0x048D, "HyperX Clutch", 0, XTYPE_XBOX360 }, /* wireless */ + { 0x03f0, 0x0495, "HyperX Clutch Gladiate", 0, XTYPE_XBOXONE }, + { 0x03f0, 0x07A0, "HyperX Clutch Gladiate RGB", 0, XTYPE_XBOXONE }, + { 0x03f0, 0x08B6, "HyperX Clutch Gladiate", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, /* v2 */ + { 0x03f0, 0x09B4, "HyperX Clutch Tanto", 0, XTYPE_XBOXONE }, + { 0x044f, 0x0f00, "Thrustmaster Wheel", 0, XTYPE_XBOX }, + { 0x044f, 0x0f03, "Thrustmaster Wheel", 0, XTYPE_XBOX }, + { 0x044f, 0x0f07, "Thrustmaster, Inc. Controller", 0, XTYPE_XBOX }, + { 0x044f, 0x0f10, "Thrustmaster Modena GT Wheel", 0, XTYPE_XBOX }, + { 0x044f, 0xb326, "Thrustmaster Gamepad GP XID", 0, XTYPE_XBOX360 }, + { 0x044f, 0xd01e, "ThrustMaster, Inc. ESWAP X 2 ELDEN RING EDITION", 0, XTYPE_XBOXONE }, + { 0x045e, 0x0202, "Microsoft X-Box pad v1 (US)", 0, XTYPE_XBOX }, + { 0x045e, 0x0285, "Microsoft X-Box pad (Japan)", 0, XTYPE_XBOX }, + { 0x045e, 0x0287, "Microsoft Xbox Controller S", 0, XTYPE_XBOX }, + { 0x045e, 0x0288, "Microsoft Xbox Controller S v2", 0, XTYPE_XBOX }, + { 0x045e, 0x0289, "Microsoft X-Box pad v2 (US)", 0, XTYPE_XBOX }, + { 0x045e, 0x028e, "Microsoft X-Box 360 pad", 0, XTYPE_XBOX360 }, + { 0x045e, 0x028f, "Microsoft X-Box 360 pad v2", 0, XTYPE_XBOX360 }, + { 0x045e, 0x0291, "Xbox 360 Wireless Receiver (XBOX)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W }, + { 0x045e, 0x02a9, "Xbox 360 Wireless Receiver (Unofficial)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W }, + { 0x045e, 0x02d1, "Microsoft X-Box One pad", 0, XTYPE_XBOXONE }, + { 0x045e, 0x02dd, "Microsoft X-Box One pad (Firmware 2015)", 0, XTYPE_XBOXONE }, + { 0x045e, 0x02e3, "Microsoft X-Box One Elite pad", MAP_PADDLES, XTYPE_XBOXONE }, + { 0x045e, 0x02ea, "Microsoft X-Box One S pad", 0, XTYPE_XBOXONE }, + { 0x045e, 0x0719, "Xbox 360 Wireless Receiver", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W }, + { 0x045e, 0x0b00, "Microsoft X-Box One Elite 2 pad", MAP_PADDLES, XTYPE_XBOXONE }, + { 0x045e, 0x0b0a, "Microsoft X-Box Adaptive Controller", MAP_PROFILE_BUTTON, XTYPE_XBOXONE }, + { 0x045e, 0x0b12, "Microsoft Xbox Series S|X Controller", MAP_SHARE_BUTTON | MAP_SHARE_OFFSET, XTYPE_XBOXONE }, + { 0x046d, 0xc21d, "Logitech Gamepad F310", 0, XTYPE_XBOX360 }, + { 0x046d, 0xc21e, "Logitech Gamepad F510", 0, XTYPE_XBOX360 }, + { 0x046d, 0xc21f, "Logitech Gamepad F710", 0, XTYPE_XBOX360 }, + { 0x046d, 0xc242, "Logitech Chillstream Controller", 0, XTYPE_XBOX360 }, + { 0x046d, 0xca84, "Logitech Xbox Cordless Controller", 0, XTYPE_XBOX }, + { 0x046d, 0xca88, "Logitech Compact Controller for Xbox", 0, XTYPE_XBOX }, + { 0x046d, 0xca8a, "Logitech Precision Vibration Feedback Wheel", 0, XTYPE_XBOX }, + { 0x046d, 0xcaa3, "Logitech DriveFx Racing Wheel", 0, XTYPE_XBOX360 }, + { 0x0502, 0x1305, "Acer NGR200", 0, XTYPE_XBOX360 }, + { 0x056e, 0x2004, "Elecom JC-U3613M", 0, XTYPE_XBOX360 }, + { 0x05fd, 0x1007, "Mad Catz Controller (unverified)", 0, XTYPE_XBOX }, + { 0x05fd, 0x107a, "InterAct 'PowerPad Pro' X-Box pad (Germany)", 0, XTYPE_XBOX }, + { 0x05fe, 0x3030, "Chic Controller", 0, XTYPE_XBOX }, + { 0x05fe, 0x3031, "Chic Controller", 0, XTYPE_XBOX }, + { 0x062a, 0x0020, "Logic3 Xbox GamePad", 0, XTYPE_XBOX }, + { 0x062a, 0x0033, "Competition Pro Steering Wheel", 0, XTYPE_XBOX }, + { 0x06a3, 0x0200, "Saitek Racing Wheel", 0, XTYPE_XBOX }, + { 0x06a3, 0x0201, "Saitek Adrenalin", 0, XTYPE_XBOX }, + { 0x06a3, 0xf51a, "Saitek P3600", 0, XTYPE_XBOX360 }, + { 0x0738, 0x4503, "Mad Catz Racing Wheel", 0, XTYPE_XBOXONE }, + { 0x0738, 0x4506, "Mad Catz 4506 Wireless Controller", 0, XTYPE_XBOX }, + { 0x0738, 0x4516, "Mad Catz Control Pad", 0, XTYPE_XBOX }, + { 0x0738, 0x4520, "Mad Catz Control Pad Pro", 0, XTYPE_XBOX }, + { 0x0738, 0x4522, "Mad Catz LumiCON", 0, XTYPE_XBOX }, + { 0x0738, 0x4526, "Mad Catz Control Pad Pro", 0, XTYPE_XBOX }, + { 0x0738, 0x4530, "Mad Catz Universal MC2 Racing Wheel and Pedals", 0, XTYPE_XBOX }, + { 0x0738, 0x4536, "Mad Catz MicroCON", 0, XTYPE_XBOX }, + { 0x0738, 0x4540, "Mad Catz Beat Pad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x0738, 0x4556, "Mad Catz Lynx Wireless Controller", 0, XTYPE_XBOX }, + { 0x0738, 0x4586, "Mad Catz MicroCon Wireless Controller", 0, XTYPE_XBOX }, + { 0x0738, 0x4588, "Mad Catz Blaster", 0, XTYPE_XBOX }, + { 0x0738, 0x45ff, "Mad Catz Beat Pad (w/ Handle)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller", 0, XTYPE_XBOX360 }, + { 0x0738, 0x4718, "Mad Catz Street Fighter IV FightStick SE", 0, XTYPE_XBOX360 }, + { 0x0738, 0x4726, "Mad Catz Xbox 360 Controller", 0, XTYPE_XBOX360 }, + { 0x0738, 0x4728, "Mad Catz Street Fighter IV FightPad", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0738, 0x4736, "Mad Catz MicroCon Gamepad", 0, XTYPE_XBOX360 }, + { 0x0738, 0x4738, "Mad Catz Wired Xbox 360 Controller (SFIV)", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0738, 0x4740, "Mad Catz Beat Pad", 0, XTYPE_XBOX360 }, + { 0x0738, 0x4743, "Mad Catz Beat Pad Pro", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x0738, 0x4758, "Mad Catz Arcade Game Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0738, 0x4a01, "Mad Catz FightStick TE 2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, + { 0x0738, 0x6040, "Mad Catz Beat Pad Pro", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x0738, 0x9871, "Mad Catz Portable Drum", 0, XTYPE_XBOX360 }, + { 0x0738, 0xb726, "Mad Catz Xbox controller - MW2", 0, XTYPE_XBOX360 }, + { 0x0738, 0xb738, "Mad Catz MVC2TE Stick 2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0738, 0xbeef, "Mad Catz JOYTECH NEO SE Advanced GamePad", 0, XTYPE_XBOX360 }, + { 0x0738, 0xcb02, "Saitek Cyborg Rumble Pad - PC/Xbox 360", 0, XTYPE_XBOX360 }, + { 0x0738, 0xcb03, "Saitek P3200 Rumble Pad - PC/Xbox 360", 0, XTYPE_XBOX360 }, + { 0x0738, 0xcb29, "Saitek Aviator Stick AV8R02", 0, XTYPE_XBOX360 }, + { 0x0738, 0xf738, "Super SFIV FightStick TE S", 0, XTYPE_XBOX360 }, + { 0x07ff, 0xffff, "Mad Catz GamePad", 0, XTYPE_XBOX360 }, + { 0x0b05, 0x1a38, "ASUS ROG RAIKIRI", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x0b05, 0x1abb, "ASUS ROG RAIKIRI PRO", 0, XTYPE_XBOXONE }, + { 0x0b05, 0x1c91, "ASUS ROG RAIKIRI II", 0, XTYPE_XBOX360 }, + { 0x0b05, 0x1c92, "ASUS ROG RAIKIRI II WIRELESS", 0, XTYPE_XBOX360 }, + { 0x0b05, 0x1c96, "ASUS ROG RAIKIRI II XBOX", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x0b05, 0x1d04, "ASUS ROG RAIKIRI II XBOX WIRELESS", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x0c12, 0x0005, "Intec wireless", 0, XTYPE_XBOX }, + { 0x0c12, 0x8801, "Nyko Xbox Controller", 0, XTYPE_XBOX }, + { 0x0c12, 0x8802, "Zeroplus Xbox Controller", 0, XTYPE_XBOX }, + { 0x0c12, 0x8809, "RedOctane Xbox Dance Pad", DANCEPAD_MAP_CONFIG, XTYPE_XBOX }, + { 0x0c12, 0x880a, "Pelican Eclipse PL-2023", 0, XTYPE_XBOX }, + { 0x0c12, 0x8810, "Zeroplus Xbox Controller", 0, XTYPE_XBOX }, + { 0x0c12, 0x9902, "HAMA VibraX - *FAULTY HARDWARE*", 0, XTYPE_XBOX }, + { 0x0d2f, 0x0002, "Andamiro Pump It Up pad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x0db0, 0x1901, "Micro Star International Xbox360 Controller for Windows", 0, XTYPE_XBOX360 }, + { 0x0e4c, 0x1097, "Radica Gamester Controller", 0, XTYPE_XBOX }, + { 0x0e4c, 0x1103, "Radica Gamester Reflex", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX }, + { 0x0e4c, 0x2390, "Radica Games Jtech Controller", 0, XTYPE_XBOX }, + { 0x0e4c, 0x3510, "Radica Gamester", 0, XTYPE_XBOX }, + { 0x0e6f, 0x0003, "Logic3 Freebird wireless Controller", 0, XTYPE_XBOX }, + { 0x0e6f, 0x0005, "Eclipse wireless Controller", 0, XTYPE_XBOX }, + { 0x0e6f, 0x0006, "Edge wireless Controller", 0, XTYPE_XBOX }, + { 0x0e6f, 0x0008, "After Glow Pro Controller", 0, XTYPE_XBOX }, + { 0x0e6f, 0x0105, "HSM3 Xbox360 dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0e6f, 0x0113, "Afterglow AX.1 Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x011f, "Rock Candy Gamepad Wired Controller", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x0131, "PDP EA Sports Controller", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x0133, "Xbox 360 Wired Controller", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x0139, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x013a, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0146, "Rock Candy Wired Controller for Xbox One", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0147, "PDP Marvel Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x015c, "PDP Xbox One Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, + { 0x0e6f, 0x015d, "PDP Mirror's Edge Official Wired Controller for Xbox One", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0161, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0162, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0163, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0164, "PDP Battlefield One", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0165, "PDP Titanfall 2", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x0246, "Rock Candy Gamepad for Xbox One 2015", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02a0, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02a1, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02a2, "PDP Wired Controller for Xbox One - Crimson Red", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02a4, "PDP Wired Controller for Xbox One - Stealth Series", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02a6, "PDP Wired Controller for Xbox One - Camo Series", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02a7, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02a8, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02ab, "PDP Controller for Xbox One", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02ad, "PDP Wired Controller for Xbox One - Stealth Series", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02b3, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x02b8, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0301, "Logic3 Controller", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x0346, "Rock Candy Gamepad for Xbox One 2016", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0401, "Logic3 Controller", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x0413, "Afterglow AX.1 Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0x0501, "PDP Xbox 360 Controller", 0, XTYPE_XBOX360 }, + { 0x0e6f, 0xf900, "PDP Afterglow AX.1", 0, XTYPE_XBOX360 }, + { 0x0e8f, 0x0201, "SmartJoy Frag Xpad/PS2 adaptor", 0, XTYPE_XBOX }, + { 0x0e8f, 0x3008, "Generic xbox control (dealextreme)", 0, XTYPE_XBOX }, + { 0x0f0d, 0x000a, "Hori Co. DOA4 FightStick", 0, XTYPE_XBOX360 }, + { 0x0f0d, 0x000c, "Hori PadEX Turbo", 0, XTYPE_XBOX360 }, + { 0x0f0d, 0x000d, "Hori Fighting Stick EX2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0f0d, 0x0016, "Hori Real Arcade Pro.EX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0f0d, 0x001b, "Hori Real Arcade Pro VX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0f0d, 0x0063, "Hori Real Arcade Pro Hayabusa (USA) Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, + { 0x0f0d, 0x0067, "HORIPAD ONE", 0, XTYPE_XBOXONE }, + { 0x0f0d, 0x0078, "Hori Real Arcade Pro V Kai Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, + { 0x0f0d, 0x00c5, "Hori Fighting Commander ONE", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, + { 0x0f0d, 0x00dc, "HORIPAD FPS for Nintendo Switch", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0f0d, 0x0151, "Hori Racing Wheel Overdrive for Xbox Series X", 0, XTYPE_XBOXONE }, + { 0x0f0d, 0x0152, "Hori Racing Wheel Overdrive for Xbox Series X", 0, XTYPE_XBOXONE }, + { 0x0f0d, 0x01b2, "HORI Taiko No Tatsujin Drum Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x0f30, 0x010b, "Philips Recoil", 0, XTYPE_XBOX }, + { 0x0f30, 0x0202, "Joytech Advanced Controller", 0, XTYPE_XBOX }, + { 0x0f30, 0x8888, "BigBen XBMiniPad Controller", 0, XTYPE_XBOX }, + { 0x102c, 0xff0c, "Joytech Wireless Advanced Controller", 0, XTYPE_XBOX }, + { 0x1038, 0x1430, "SteelSeries Stratus Duo", 0, XTYPE_XBOX360 }, + { 0x1038, 0x1431, "SteelSeries Stratus Duo", 0, XTYPE_XBOX360 }, + { 0x10f5, 0x7005, "Turtle Beach Recon Controller", 0, XTYPE_XBOXONE }, + { 0x10f5, 0x7008, "Turtle Beach Recon Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x10f5, 0x7073, "Turtle Beach Stealth Ultra Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x11c9, 0x55f0, "Nacon GC-100XF", 0, XTYPE_XBOX360 }, + { 0x11ff, 0x0511, "PXN V900", 0, XTYPE_XBOX360 }, + { 0x1209, 0x2882, "Ardwiino Controller", 0, XTYPE_XBOX360 }, + { 0x12ab, 0x0004, "Honey Bee Xbox360 dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x12ab, 0x0301, "PDP AFTERGLOW AX.1", 0, XTYPE_XBOX360 }, + { 0x12ab, 0x0303, "Mortal Kombat Klassic FightStick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x12ab, 0x8809, "Xbox DDR dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x1430, 0x4748, "RedOctane Guitar Hero X-plorer", 0, XTYPE_XBOX360 }, + { 0x1430, 0x8888, "TX6500+ Dance Pad (first generation)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, + { 0x1430, 0xf801, "RedOctane Controller", 0, XTYPE_XBOX360 }, + { 0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller", 0, XTYPE_XBOX360 }, + { 0x146b, 0x0604, "Bigben Interactive DAIJA Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1532, 0x0a00, "Razer Atrox Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, + { 0x1532, 0x0a03, "Razer Wildcat", 0, XTYPE_XBOXONE }, + { 0x1532, 0x0a29, "Razer Wolverine V2", 0, XTYPE_XBOXONE }, + { 0x1532, 0x0a57, "Razer Wolverine V3 Pro (Wired)", 0, XTYPE_XBOX360 }, + { 0x1532, 0x0a59, "Razer Wolverine V3 Pro (2.4 GHz Dongle)", 0, XTYPE_XBOX360 }, + { 0x15e4, 0x3f00, "Power A Mini Pro Elite", 0, XTYPE_XBOX360 }, + { 0x15e4, 0x3f0a, "Xbox Airflo wired controller", 0, XTYPE_XBOX360 }, + { 0x15e4, 0x3f10, "Batarang Xbox 360 controller", 0, XTYPE_XBOX360 }, + { 0x162e, 0xbeef, "Joytech Neo-Se Take2", 0, XTYPE_XBOX360 }, + { 0x1689, 0xfd00, "Razer Onza Tournament Edition", 0, XTYPE_XBOX360 }, + { 0x1689, 0xfd01, "Razer Onza Classic Edition", 0, XTYPE_XBOX360 }, + { 0x1689, 0xfe00, "Razer Sabertooth", 0, XTYPE_XBOX360 }, + { 0x17ef, 0x6182, "Lenovo Legion Controller for Windows", 0, XTYPE_XBOX360 }, + { 0x1949, 0x041a, "Amazon Game Controller", 0, XTYPE_XBOX360 }, + { 0x1a86, 0xe310, "Legion Go S", 0, XTYPE_XBOX360 }, + { 0x1bad, 0x0002, "Harmonix Rock Band Guitar", 0, XTYPE_XBOX360 }, + { 0x1bad, 0x0003, "Harmonix Rock Band Drumkit", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0x0130, "Ion Drum Rocker", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf016, "Mad Catz Xbox 360 Controller", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf018, "Mad Catz Street Fighter IV SE Fighting Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf019, "Mad Catz Brawlstick for Xbox 360", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf021, "Mad Cats Ghost Recon FS GamePad", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf023, "MLG Pro Circuit Controller (Xbox)", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf025, "Mad Catz Call Of Duty", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf027, "Mad Catz FPS Pro", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf028, "Street Fighter IV FightPad", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf02e, "Mad Catz Fightpad", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf030, "Mad Catz Xbox 360 MC2 MicroCon Racing Wheel", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf036, "Mad Catz MicroCon GamePad Pro", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf038, "Street Fighter IV FightStick TE", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf039, "Mad Catz MvC2 TE", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf03a, "Mad Catz SFxT Fightstick Pro", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf03d, "Street Fighter IV Arcade Stick TE - Chun Li", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf03e, "Mad Catz MLG FightStick TE", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf03f, "Mad Catz FightStick SoulCaliber", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf042, "Mad Catz FightStick TES+", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf080, "Mad Catz FightStick TE2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf501, "HoriPad EX2 Turbo", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf502, "Hori Real Arcade Pro.VX SA", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf503, "Hori Fighting Stick VX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf504, "Hori Real Arcade Pro. EX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf505, "Hori Fighting Stick EX2B", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xf506, "Hori Real Arcade Pro.EX Premium VLX", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf900, "Harmonix Xbox 360 Controller", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf901, "Gamestop Xbox 360 Controller", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf903, "Tron Xbox 360 controller", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf904, "PDP Versus Fighting Pad", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xf906, "MortalKombat FightStick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x1bad, 0xfa01, "MadCatz GamePad", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xfd00, "Razer Onza TE", 0, XTYPE_XBOX360 }, + { 0x1bad, 0xfd01, "Razer Onza", 0, XTYPE_XBOX360 }, + { 0x1ee9, 0x1590, "ZOTAC Gaming Zone", 0, XTYPE_XBOX360 }, + { 0x20bc, 0x5134, "BETOP BTP-KP50B Xinput Dongle", 0, XTYPE_XBOX360 }, + { 0x20bc, 0x514a, "BETOP BTP-KP50C Xinput Dongle", 0, XTYPE_XBOX360 }, + { 0x20d6, 0x2001, "BDA Xbox Series X Wired Controller", 0, XTYPE_XBOXONE }, + { 0x20d6, 0x2009, "PowerA Enhanced Wired Controller for Xbox Series X|S", 0, XTYPE_XBOXONE }, + { 0x20d6, 0x2064, "PowerA Wired Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x20d6, 0x281f, "PowerA Wired Controller For Xbox 360", 0, XTYPE_XBOX360 }, + { 0x20d6, 0x400b, "PowerA FUSION Pro 4 Wired Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x20d6, 0x890b, "PowerA MOGA XP-Ultra Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x2345, 0xe00b, "Machenike G5 Pro Controller", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x5000, "Razer Atrox Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x24c6, 0x5300, "PowerA MINI PROEX Controller", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x5303, "Xbox Airflo wired controller", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x530a, "Xbox 360 Pro EX Controller", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x531a, "PowerA Pro Ex", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x5397, "FUS1ON Tournament Controller", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x541a, "PowerA Xbox One Mini Wired Controller", 0, XTYPE_XBOXONE }, + { 0x24c6, 0x542a, "Xbox ONE spectra", 0, XTYPE_XBOXONE }, + { 0x24c6, 0x543a, "PowerA Xbox One wired controller", 0, XTYPE_XBOXONE }, + { 0x24c6, 0x5500, "Hori XBOX 360 EX 2 with Turbo", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x5501, "Hori Real Arcade Pro VX-SA", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x5502, "Hori Fighting Stick VX Alt", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x24c6, 0x5503, "Hori Fighting Edge", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x24c6, 0x5506, "Hori SOULCALIBUR V Stick", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x550d, "Hori GEM Xbox controller", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x550e, "Hori Real Arcade Pro V Kai 360", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x24c6, 0x5510, "Hori Fighting Commander ONE (Xbox 360/PC Mode)", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x24c6, 0x551a, "PowerA FUSION Pro Controller", 0, XTYPE_XBOXONE }, + { 0x24c6, 0x561a, "PowerA FUSION Controller", 0, XTYPE_XBOXONE }, + { 0x24c6, 0x581a, "ThrustMaster XB1 Classic Controller", 0, XTYPE_XBOXONE }, + { 0x24c6, 0x5b00, "ThrustMaster Ferrari 458 Racing Wheel", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x5b02, "Thrustmaster, Inc. GPX Controller", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x5b03, "Thrustmaster Ferrari 458 Racing Wheel", 0, XTYPE_XBOX360 }, + { 0x24c6, 0x5d04, "Razer Sabertooth", 0, XTYPE_XBOX360 }, + { 0x24c6, 0xfafe, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, + { 0x2563, 0x058d, "OneXPlayer Gamepad", 0, XTYPE_XBOX360 }, + { 0x294b, 0x3303, "Snakebyte GAMEPAD BASE X", 0, XTYPE_XBOXONE }, + { 0x294b, 0x3404, "Snakebyte GAMEPAD RGB X", 0, XTYPE_XBOXONE }, + { 0x2993, 0x2001, "TECNO Pocket Go", 0, XTYPE_XBOX360 }, + { 0x2dc8, 0x2000, "8BitDo Pro 2 Wired Controller fox Xbox", 0, XTYPE_XBOXONE }, + { 0x2dc8, 0x200f, "8BitDo Ultimate 3-mode Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x2dc8, 0x3106, "8BitDo Ultimate Wireless / Pro 2 Wired Controller", 0, XTYPE_XBOX360 }, + { 0x2dc8, 0x3109, "8BitDo Ultimate Wireless Bluetooth", 0, XTYPE_XBOX360 }, + { 0x2dc8, 0x310a, "8BitDo Ultimate 2C Wireless Controller", 0, XTYPE_XBOX360 }, + { 0x2dc8, 0x310b, "8BitDo Ultimate 2 Wireless Controller", 0, XTYPE_XBOX360 }, + { 0x2dc8, 0x6001, "8BitDo SN30 Pro", 0, XTYPE_XBOX360 }, + { 0x2e24, 0x0423, "Hyperkin DuchesS Xbox One pad", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x2e24, 0x0652, "Hyperkin Duke X-Box One pad", 0, XTYPE_XBOXONE }, + { 0x2e24, 0x1688, "Hyperkin X91 X-Box One pad", 0, XTYPE_XBOXONE }, + { 0x2e95, 0x0504, "SCUF Gaming Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x31e3, 0x1100, "Wooting One", 0, XTYPE_XBOX360 }, + { 0x31e3, 0x1200, "Wooting Two", 0, XTYPE_XBOX360 }, + { 0x31e3, 0x1210, "Wooting Lekker", 0, XTYPE_XBOX360 }, + { 0x31e3, 0x1220, "Wooting Two HE", 0, XTYPE_XBOX360 }, + { 0x31e3, 0x1230, "Wooting Two HE (ARM)", 0, XTYPE_XBOX360 }, + { 0x31e3, 0x1300, "Wooting 60HE (AVR)", 0, XTYPE_XBOX360 }, + { 0x31e3, 0x1310, "Wooting 60HE (ARM)", 0, XTYPE_XBOX360 }, + { 0x3285, 0x0603, "Nacon Pro Compact controller for Xbox", 0, XTYPE_XBOXONE }, + { 0x3285, 0x0607, "Nacon GC-100", 0, XTYPE_XBOX360 }, + { 0x3285, 0x0614, "Nacon Pro Compact", 0, XTYPE_XBOXONE }, + { 0x3285, 0x0646, "Nacon Pro Compact", 0, XTYPE_XBOXONE }, + { 0x3285, 0x0662, "Nacon Revolution5 Pro", 0, XTYPE_XBOX360 }, + { 0x3285, 0x0663, "Nacon Evol-X", 0, XTYPE_XBOXONE }, + { 0x3507, 0x000b, "ZENAIM LEVERLESS", 0, XTYPE_XBOX360 }, + { 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 }, + { 0x3537, 0x100f, "GameSir Nova 2 Lite", 0, XTYPE_XBOX360 }, + { 0x3537, 0x1010, "GameSir G7 SE", 0, XTYPE_XBOXONE }, + { 0x3651, 0x1000, "CRKD SG", 0, XTYPE_XBOX360 }, + { 0x366c, 0x0005, "ByoWave Proteus Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE, FLAG_DELAY_INIT }, + { 0x3767, 0x0101, "Fanatec Speedster 3 Forceshock Wheel", 0, XTYPE_XBOX }, + { 0x37d7, 0x2501, "Flydigi Apex 5", 0, XTYPE_XBOX360 }, + { 0x413d, 0x2104, "Black Shark Green Ghost Gamepad", 0, XTYPE_XBOX360 }, + { 0xffff, 0xffff, "Chinese-made Xbox Controller", 0, XTYPE_XBOX }, + { 0x0000, 0x0000, "Generic X-Box pad", 0, XTYPE_UNKNOWN } +}; + +/* buttons shared with xbox and xbox360 */ +static const signed short xpad_common_btn[] = { + BTN_A, BTN_B, BTN_X, BTN_Y, /* "analog" buttons */ + BTN_START, BTN_SELECT, BTN_THUMBL, BTN_THUMBR, /* start/back/sticks */ + -1 /* terminating entry */ +}; + +/* original xbox controllers only */ +static const signed short xpad_btn[] = { + BTN_C, BTN_Z, /* "analog" buttons */ + -1 /* terminating entry */ +}; + +/* used when dpad is mapped to buttons */ +static const signed short xpad_btn_pad[] = { + BTN_DPAD_LEFT, BTN_DPAD_RIGHT, /* d-pad left, right */ + BTN_DPAD_UP, BTN_DPAD_DOWN, /* d-pad up, down */ + -1 /* terminating entry */ +}; + +/* used when triggers are mapped to buttons */ +static const signed short xpad_btn_triggers[] = { + BTN_TL2, BTN_TR2, /* triggers left/right */ + -1 +}; + +static const signed short xpad360_btn[] = { /* buttons for x360 controller */ + BTN_TL, BTN_TR, /* Button LB/RB */ + BTN_MODE, /* The big X button */ + -1 +}; + +static const signed short xpad_abs[] = { + ABS_X, ABS_Y, /* left stick */ + ABS_RX, ABS_RY, /* right stick */ + -1 /* terminating entry */ +}; + +/* used when dpad is mapped to axes */ +static const signed short xpad_abs_pad[] = { + ABS_HAT0X, ABS_HAT0Y, /* d-pad axes */ + -1 /* terminating entry */ +}; + +/* used when triggers are mapped to axes */ +static const signed short xpad_abs_triggers[] = { + ABS_Z, ABS_RZ, /* triggers left/right */ + -1 +}; + +/* used when the controller has extra paddle buttons */ +static const signed short xpad_btn_paddles[] = { + BTN_GRIPR, BTN_GRIPR2, /* paddle upper right, lower right */ + BTN_GRIPL, BTN_GRIPL2, /* paddle upper left, lower left */ + -1 /* terminating entry */ +}; + +/* + * Xbox 360 has a vendor-specific class, so we cannot match it with only + * USB_INTERFACE_INFO (also specifically refused by USB subsystem), so we + * match against vendor id as well. Wired Xbox 360 devices have protocol 1, + * wireless controllers have protocol 129. + */ +#define XPAD_XBOX360_VENDOR_PROTOCOL(vend, pr) \ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \ + .idVendor = (vend), \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ + .bInterfaceSubClass = 93, \ + .bInterfaceProtocol = (pr) +#define XPAD_XBOX360_VENDOR(vend) \ + { XPAD_XBOX360_VENDOR_PROTOCOL((vend), 1) }, \ + { XPAD_XBOX360_VENDOR_PROTOCOL((vend), 129) } + +/* The Xbox One controller uses subclass 71 and protocol 208. */ +#define XPAD_XBOXONE_VENDOR_PROTOCOL(vend, pr) \ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \ + .idVendor = (vend), \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ + .bInterfaceSubClass = 71, \ + .bInterfaceProtocol = (pr) +#define XPAD_XBOXONE_VENDOR(vend) \ + { XPAD_XBOXONE_VENDOR_PROTOCOL((vend), 208) } + +static const struct usb_device_id xpad_table[] = { + /* + * Please keep this list sorted by vendor ID. Note that there are 2 + * macros - XPAD_XBOX360_VENDOR and XPAD_XBOXONE_VENDOR. + */ + { USB_INTERFACE_INFO('X', 'B', 0) }, /* Xbox USB-IF not-approved class */ + XPAD_XBOX360_VENDOR(0x0079), /* GPD Win 2 controller */ + XPAD_XBOX360_VENDOR(0x0351), /* CRKD Controllers */ + XPAD_XBOX360_VENDOR(0x03eb), /* Wooting Keyboards (Legacy) */ + XPAD_XBOX360_VENDOR(0x03f0), /* HP HyperX Xbox 360 controllers */ + XPAD_XBOXONE_VENDOR(0x03f0), /* HP HyperX Xbox One controllers */ + XPAD_XBOX360_VENDOR(0x044f), /* Thrustmaster Xbox 360 controllers */ + XPAD_XBOXONE_VENDOR(0x044f), /* Thrustmaster Xbox One controllers */ + XPAD_XBOX360_VENDOR(0x045e), /* Microsoft Xbox 360 controllers */ + XPAD_XBOXONE_VENDOR(0x045e), /* Microsoft Xbox One controllers */ + XPAD_XBOX360_VENDOR(0x046d), /* Logitech Xbox 360-style controllers */ + XPAD_XBOX360_VENDOR(0x0502), /* Acer Inc. Xbox 360 style controllers */ + XPAD_XBOX360_VENDOR(0x056e), /* Elecom JC-U3613M */ + XPAD_XBOX360_VENDOR(0x06a3), /* Saitek P3600 */ + XPAD_XBOX360_VENDOR(0x0738), /* Mad Catz Xbox 360 controllers */ + { USB_DEVICE(0x0738, 0x4540) }, /* Mad Catz Beat Pad */ + XPAD_XBOXONE_VENDOR(0x0738), /* Mad Catz FightStick TE 2 */ + XPAD_XBOX360_VENDOR(0x07ff), /* Mad Catz Gamepad */ + XPAD_XBOX360_VENDOR(0x0b05), /* ASUS controllers */ + XPAD_XBOXONE_VENDOR(0x0b05), /* ASUS controllers */ + XPAD_XBOX360_VENDOR(0x0c12), /* Zeroplus X-Box 360 controllers */ + XPAD_XBOX360_VENDOR(0x0db0), /* Micro Star International X-Box 360 controllers */ + XPAD_XBOX360_VENDOR(0x0e6f), /* 0x0e6f Xbox 360 controllers */ + XPAD_XBOXONE_VENDOR(0x0e6f), /* 0x0e6f Xbox One controllers */ + XPAD_XBOX360_VENDOR(0x0f0d), /* Hori controllers */ + XPAD_XBOXONE_VENDOR(0x0f0d), /* Hori controllers */ + XPAD_XBOX360_VENDOR(0x1038), /* SteelSeries controllers */ + XPAD_XBOXONE_VENDOR(0x10f5), /* Turtle Beach Controllers */ + XPAD_XBOX360_VENDOR(0x11c9), /* Nacon GC100XF */ + XPAD_XBOX360_VENDOR(0x11ff), /* PXN V900 */ + XPAD_XBOX360_VENDOR(0x1209), /* Ardwiino Controllers */ + XPAD_XBOX360_VENDOR(0x12ab), /* Xbox 360 dance pads */ + XPAD_XBOX360_VENDOR(0x1430), /* RedOctane Xbox 360 controllers */ + XPAD_XBOX360_VENDOR(0x146b), /* Bigben Interactive controllers */ + XPAD_XBOX360_VENDOR(0x1532), /* Razer Sabertooth */ + XPAD_XBOXONE_VENDOR(0x1532), /* Razer Wildcat */ + XPAD_XBOX360_VENDOR(0x15e4), /* Numark Xbox 360 controllers */ + XPAD_XBOX360_VENDOR(0x162e), /* Joytech Xbox 360 controllers */ + XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */ + XPAD_XBOX360_VENDOR(0x17ef), /* Lenovo */ + XPAD_XBOX360_VENDOR(0x1949), /* Amazon controllers */ + XPAD_XBOX360_VENDOR(0x1a86), /* Nanjing Qinheng Microelectronics (WCH) */ + XPAD_XBOX360_VENDOR(0x1bad), /* Harmonix Rock Band guitar and drums */ + XPAD_XBOX360_VENDOR(0x1ee9), /* ZOTAC Technology Limited */ + XPAD_XBOX360_VENDOR(0x20bc), /* BETOP wireless dongles */ + XPAD_XBOX360_VENDOR(0x20d6), /* PowerA controllers */ + XPAD_XBOXONE_VENDOR(0x20d6), /* PowerA controllers */ + XPAD_XBOX360_VENDOR(0x2345), /* Machenike Controllers */ + XPAD_XBOX360_VENDOR(0x24c6), /* PowerA controllers */ + XPAD_XBOXONE_VENDOR(0x24c6), /* PowerA controllers */ + XPAD_XBOX360_VENDOR(0x2563), /* OneXPlayer Gamepad */ + XPAD_XBOX360_VENDOR(0x260d), /* Dareu H101 */ + XPAD_XBOXONE_VENDOR(0x294b), /* Snakebyte */ + XPAD_XBOX360_VENDOR(0x2993), /* TECNO Mobile */ + XPAD_XBOX360_VENDOR(0x2c22), /* Qanba Controllers */ + XPAD_XBOX360_VENDOR(0x2dc8), /* 8BitDo Controllers */ + XPAD_XBOXONE_VENDOR(0x2dc8), /* 8BitDo Controllers */ + XPAD_XBOXONE_VENDOR(0x2e24), /* Hyperkin Controllers */ + XPAD_XBOX360_VENDOR(0x2f24), /* GameSir Controllers */ + XPAD_XBOXONE_VENDOR(0x2e95), /* SCUF Gaming Controller */ + XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */ + XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */ + XPAD_XBOXONE_VENDOR(0x3285), /* Nacon Evol-X */ + XPAD_XBOX360_VENDOR(0x3507), /* ZENAIM Controllers */ + XPAD_XBOX360_VENDOR(0x3537), /* GameSir Controllers */ + XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */ + XPAD_XBOX360_VENDOR(0x3651), /* CRKD Controllers */ + XPAD_XBOXONE_VENDOR(0x366c), /* ByoWave controllers */ + XPAD_XBOX360_VENDOR(0x37d7), /* Flydigi Controllers */ + XPAD_XBOX360_VENDOR(0x3958), /* RedOctane Games Controllers */ + XPAD_XBOX360_VENDOR(0x413d), /* Black Shark Green Ghost Controller */ + { } +}; + +MODULE_DEVICE_TABLE(usb, xpad_table); + +struct xboxone_init_packet { + u16 idVendor; + u16 idProduct; + const u8 *data; + u8 len; +}; + +#define XBOXONE_INIT_PKT(_vid, _pid, _data) \ + { \ + .idVendor = (_vid), \ + .idProduct = (_pid), \ + .data = (_data), \ + .len = ARRAY_SIZE(_data), \ + } + +/* + * starting with xbox one, the game input protocol is used + * magic numbers are taken from + * - https://github.com/xpadneo/gip-dissector/blob/main/src/gip-dissector.lua + * - https://github.com/medusalix/xone/blob/master/bus/protocol.c + */ +#define GIP_CMD_ACK 0x01 +#define GIP_CMD_ANNOUNCE 0x02 +#define GIP_CMD_IDENTIFY 0x04 +#define GIP_CMD_POWER 0x05 +#define GIP_CMD_AUTHENTICATE 0x06 +#define GIP_CMD_VIRTUAL_KEY 0x07 +#define GIP_CMD_RUMBLE 0x09 +#define GIP_CMD_LED 0x0a +#define GIP_CMD_FIRMWARE 0x0c +#define GIP_CMD_INPUT 0x20 + +#define GIP_SEQ0 0x00 + +#define GIP_OPT_ACK 0x10 +#define GIP_OPT_INTERNAL 0x20 + +/* + * length of the command payload encoded with + * https://en.wikipedia.org/wiki/LEB128 + * which is a no-op for N < 128 + */ +#define GIP_PL_LEN(N) (N) + +/* + * payload specific defines + */ +#define GIP_PWR_ON 0x00 +#define GIP_LED_ON 0x01 + +#define GIP_MOTOR_R BIT(0) +#define GIP_MOTOR_L BIT(1) +#define GIP_MOTOR_RT BIT(2) +#define GIP_MOTOR_LT BIT(3) +#define GIP_MOTOR_ALL (GIP_MOTOR_R | GIP_MOTOR_L | GIP_MOTOR_RT | GIP_MOTOR_LT) + +#define GIP_WIRED_INTF_DATA 0 +#define GIP_WIRED_INTF_AUDIO 1 + +/* + * This packet is required for all Xbox One pads with 2015 + * or later firmware installed (or present from the factory). + */ +static const u8 xboxone_power_on[] = { + GIP_CMD_POWER, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(1), GIP_PWR_ON +}; + +/* + * This packet is required for Xbox One S (0x045e:0x02ea) + * and Xbox One Elite Series 2 (0x045e:0x0b00) pads to + * initialize the controller that was previously used in + * Bluetooth mode. + */ +static const u8 xboxone_s_init[] = { + GIP_CMD_POWER, GIP_OPT_INTERNAL, GIP_SEQ0, 0x0f, 0x06 +}; + +/* + * This packet is required to get additional input data + * from Xbox One Elite Series 2 (0x045e:0x0b00) pads. + * We mostly do this right now to get paddle data + */ +static const u8 extra_input_packet_init[] = { + 0x4d, 0x10, 0x01, 0x02, 0x07, 0x00 +}; + +/* + * This packet is required for the Titanfall 2 Xbox One pads + * (0x0e6f:0x0165) to finish initialization and for Hori pads + * (0x0f0d:0x0067) to make the analog sticks work. + */ +static const u8 xboxone_hori_ack_id[] = { + GIP_CMD_ACK, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(9), + 0x00, GIP_CMD_IDENTIFY, GIP_OPT_INTERNAL, 0x3a, 0x00, 0x00, 0x00, 0x80, 0x00 +}; + +/* + * This packet is sent by default on Windows, and is required for some pads to + * start sending input reports, including most (all?) of the PDP. These pads + * include: (0x0e6f:0x02ab), (0x0e6f:0x02a4), (0x0e6f:0x02a6). + */ +static const u8 xboxone_led_on[] = { GIP_CMD_LED, GIP_OPT_INTERNAL, GIP_SEQ0, +GIP_PL_LEN(3), 0x00, GIP_LED_ON, 0x14 }; + +/* + * This packet is required for most (all?) of the PDP pads to start + * sending input reports. These pads include: (0x0e6f:0x02ab), + * (0x0e6f:0x02a4), (0x0e6f:0x02a6). + */ +static const u8 xboxone_auth_done[] = { + GIP_CMD_AUTHENTICATE, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(2), 0x01, 0x00 +}; + +/* + * A specific rumble packet is required for some PowerA pads to start + * sending input reports. One of those pads is (0x24c6:0x543a). + */ +static const u8 xboxone_rumblebegin_init[] = { + GIP_CMD_RUMBLE, 0x00, GIP_SEQ0, GIP_PL_LEN(9), + 0x00, GIP_MOTOR_ALL, 0x00, 0x00, 0x1D, 0x1D, 0xFF, 0x00, 0x00 +}; + +/* + * A rumble packet with zero FF intensity will immediately + * terminate the rumbling required to init PowerA pads. + * This should happen fast enough that the motors don't + * spin up to enough speed to actually vibrate the gamepad. + */ +static const u8 xboxone_rumbleend_init[] = { + GIP_CMD_RUMBLE, 0x00, GIP_SEQ0, GIP_PL_LEN(9), + 0x00, GIP_MOTOR_ALL, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * This specifies the selection of init packets that a gamepad + * will be sent on init *and* the order in which they will be + * sent. The correct sequence number will be added when the + * packet is going to be sent. + */ +static const struct xboxone_init_packet xboxone_init_packets[] = { + XBOXONE_INIT_PKT(0x0e6f, 0x0165, xboxone_hori_ack_id), + XBOXONE_INIT_PKT(0x0f0d, 0x0067, xboxone_hori_ack_id), + XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_power_on), + XBOXONE_INIT_PKT(0x045e, 0x02ea, xboxone_s_init), + XBOXONE_INIT_PKT(0x045e, 0x0b00, xboxone_s_init), + XBOXONE_INIT_PKT(0x045e, 0x0b00, extra_input_packet_init), + XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_led_on), + XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_auth_done), + XBOXONE_INIT_PKT(0x24c6, 0x541a, xboxone_rumblebegin_init), + XBOXONE_INIT_PKT(0x24c6, 0x542a, xboxone_rumblebegin_init), + XBOXONE_INIT_PKT(0x24c6, 0x543a, xboxone_rumblebegin_init), + XBOXONE_INIT_PKT(0x24c6, 0x541a, xboxone_rumbleend_init), + XBOXONE_INIT_PKT(0x24c6, 0x542a, xboxone_rumbleend_init), + XBOXONE_INIT_PKT(0x24c6, 0x543a, xboxone_rumbleend_init), +}; + +struct xpad_output_packet { + u8 data[XPAD_PKT_LEN]; + u8 len; + bool pending; +}; + +#define XPAD_OUT_CMD_IDX 0 +#define XPAD_OUT_FF_IDX 1 +#define XPAD_OUT_LED_IDX (1 + IS_ENABLED(CONFIG_JOYSTICK_XPAD_FF)) +#define XPAD_NUM_OUT_PACKETS (1 + \ + IS_ENABLED(CONFIG_JOYSTICK_XPAD_FF) + \ + IS_ENABLED(CONFIG_JOYSTICK_XPAD_LEDS)) + +struct usb_xpad { + struct input_dev *dev; /* input device interface */ + struct input_dev __rcu *x360w_dev; + struct usb_device *udev; /* usb device */ + struct usb_interface *intf; /* usb interface */ + + bool pad_present; + bool input_created; + + struct urb *irq_in; /* urb for interrupt in report */ + unsigned char *idata; /* input data */ + dma_addr_t idata_dma; + + struct urb *irq_out; /* urb for interrupt out report */ + struct usb_anchor irq_out_anchor; + bool irq_out_active; /* we must not use an active URB */ + u8 odata_serial; /* serial number for xbox one protocol */ + unsigned char *odata; /* output data */ + dma_addr_t odata_dma; + spinlock_t odata_lock; + + struct xpad_output_packet out_packets[XPAD_NUM_OUT_PACKETS]; + int last_out_packet; + int init_seq; + +#if defined(CONFIG_JOYSTICK_XPAD_LEDS) + struct xpad_led *led; +#endif + + char phys[64]; /* physical device path */ + + int mapping; /* map d-pad to buttons or to axes */ + int xtype; /* type of xbox device */ + int packet_type; /* type of the extended packet */ + int pad_nr; /* the order x360 pads were attached */ + const char *name; /* name of the device */ + struct work_struct work; /* init/remove device from callback */ + time64_t mode_btn_down_ts; + bool delay_init; /* init packets should be delayed */ + bool delayed_init_done; +}; + +static int xpad_init_input(struct usb_xpad *xpad); +static void xpad_deinit_input(struct usb_xpad *xpad); +static int xpad_start_input(struct usb_xpad *xpad); +static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num); +static void xpad360w_poweroff_controller(struct usb_xpad *xpad); + +/* + * xpad_process_packet + * + * Completes a request by converting the data into events for the + * input subsystem. + * + * The used report descriptor was taken from ITO Takayuki's website: + * http://euc.jp/periphs/xbox-controller.ja.html + */ +static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data) +{ + struct input_dev *dev = xpad->dev; + + if (!(xpad->mapping & MAP_STICKS_TO_NULL)) { + /* left stick */ + input_report_abs(dev, ABS_X, + (__s16) le16_to_cpup((__le16 *)(data + 12))); + input_report_abs(dev, ABS_Y, + ~(__s16) le16_to_cpup((__le16 *)(data + 14))); + + /* right stick */ + input_report_abs(dev, ABS_RX, + (__s16) le16_to_cpup((__le16 *)(data + 16))); + input_report_abs(dev, ABS_RY, + ~(__s16) le16_to_cpup((__le16 *)(data + 18))); + } + + /* triggers left/right */ + if (xpad->mapping & MAP_TRIGGERS_TO_BUTTONS) { + input_report_key(dev, BTN_TL2, data[10]); + input_report_key(dev, BTN_TR2, data[11]); + } else { + input_report_abs(dev, ABS_Z, data[10]); + input_report_abs(dev, ABS_RZ, data[11]); + } + + /* digital pad */ + if (xpad->mapping & MAP_DPAD_TO_BUTTONS) { + /* dpad as buttons (left, right, up, down) */ + input_report_key(dev, BTN_DPAD_LEFT, data[2] & BIT(2)); + input_report_key(dev, BTN_DPAD_RIGHT, data[2] & BIT(3)); + input_report_key(dev, BTN_DPAD_UP, data[2] & BIT(0)); + input_report_key(dev, BTN_DPAD_DOWN, data[2] & BIT(1)); + } else { + input_report_abs(dev, ABS_HAT0X, + !!(data[2] & 0x08) - !!(data[2] & 0x04)); + input_report_abs(dev, ABS_HAT0Y, + !!(data[2] & 0x02) - !!(data[2] & 0x01)); + } + + /* start/back buttons and stick press left/right */ + input_report_key(dev, BTN_START, data[2] & BIT(4)); + input_report_key(dev, BTN_SELECT, data[2] & BIT(5)); + input_report_key(dev, BTN_THUMBL, data[2] & BIT(6)); + input_report_key(dev, BTN_THUMBR, data[2] & BIT(7)); + + /* "analog" buttons A, B, X, Y */ + input_report_key(dev, BTN_A, data[4]); + input_report_key(dev, BTN_B, data[5]); + input_report_key(dev, BTN_X, data[6]); + input_report_key(dev, BTN_Y, data[7]); + + /* "analog" buttons black, white */ + input_report_key(dev, BTN_C, data[8]); + input_report_key(dev, BTN_Z, data[9]); + + + input_sync(dev); +} + +/* + * xpad360_process_packet + * + * Completes a request by converting the data into events for the + * input subsystem. It is version for xbox 360 controller + * + * The used report descriptor was taken from: + * http://www.free60.org/wiki/Gamepad + */ + +static void xpad360_process_packet(struct usb_xpad *xpad, struct input_dev *dev, + u16 cmd, unsigned char *data) +{ + /* valid pad data */ + if (data[0] != 0x00) + return; + + /* digital pad */ + if (xpad->mapping & MAP_DPAD_TO_BUTTONS) { + /* dpad as buttons (left, right, up, down) */ + input_report_key(dev, BTN_DPAD_LEFT, data[2] & BIT(2)); + input_report_key(dev, BTN_DPAD_RIGHT, data[2] & BIT(3)); + input_report_key(dev, BTN_DPAD_UP, data[2] & BIT(0)); + input_report_key(dev, BTN_DPAD_DOWN, data[2] & BIT(1)); + } + + /* + * This should be a simple else block. However historically + * xbox360w has mapped DPAD to buttons while xbox360 did not. This + * made no sense, but now we can not just switch back and have to + * support both behaviors. + */ + if (!(xpad->mapping & MAP_DPAD_TO_BUTTONS) || + xpad->xtype == XTYPE_XBOX360W) { + input_report_abs(dev, ABS_HAT0X, + !!(data[2] & 0x08) - !!(data[2] & 0x04)); + input_report_abs(dev, ABS_HAT0Y, + !!(data[2] & 0x02) - !!(data[2] & 0x01)); + } + + /* start/back buttons */ + input_report_key(dev, BTN_START, data[2] & BIT(4)); + input_report_key(dev, BTN_SELECT, data[2] & BIT(5)); + + /* stick press left/right */ + input_report_key(dev, BTN_THUMBL, data[2] & BIT(6)); + input_report_key(dev, BTN_THUMBR, data[2] & BIT(7)); + + /* buttons A,B,X,Y,TL,TR and MODE */ + input_report_key(dev, BTN_A, data[3] & BIT(4)); + input_report_key(dev, BTN_B, data[3] & BIT(5)); + input_report_key(dev, BTN_X, data[3] & BIT(6)); + input_report_key(dev, BTN_Y, data[3] & BIT(7)); + input_report_key(dev, BTN_TL, data[3] & BIT(0)); + input_report_key(dev, BTN_TR, data[3] & BIT(1)); + input_report_key(dev, BTN_MODE, data[3] & BIT(2)); + + if (!(xpad->mapping & MAP_STICKS_TO_NULL)) { + /* left stick */ + input_report_abs(dev, ABS_X, + (__s16) le16_to_cpup((__le16 *)(data + 6))); + input_report_abs(dev, ABS_Y, + ~(__s16) le16_to_cpup((__le16 *)(data + 8))); + + /* right stick */ + input_report_abs(dev, ABS_RX, + (__s16) le16_to_cpup((__le16 *)(data + 10))); + input_report_abs(dev, ABS_RY, + ~(__s16) le16_to_cpup((__le16 *)(data + 12))); + } + + /* triggers left/right */ + if (xpad->mapping & MAP_TRIGGERS_TO_BUTTONS) { + input_report_key(dev, BTN_TL2, data[4]); + input_report_key(dev, BTN_TR2, data[5]); + } else { + input_report_abs(dev, ABS_Z, data[4]); + input_report_abs(dev, ABS_RZ, data[5]); + } + + input_sync(dev); + + /* XBOX360W controllers can't be turned off without driver assistance */ + if (xpad->xtype == XTYPE_XBOX360W) { + if (xpad->mode_btn_down_ts > 0 && xpad->pad_present && + ((ktime_get_seconds() - xpad->mode_btn_down_ts) >= + XPAD360W_POWEROFF_TIMEOUT)) { + xpad360w_poweroff_controller(xpad); + xpad->mode_btn_down_ts = 0; + return; + } + + /* mode button down/up */ + if (data[3] & BIT(2)) + xpad->mode_btn_down_ts = ktime_get_seconds(); + else + xpad->mode_btn_down_ts = 0; + } +} + +static void xpad_presence_work(struct work_struct *work) +{ + struct usb_xpad *xpad = container_of(work, struct usb_xpad, work); + int error; + + if (xpad->pad_present) { + error = xpad_init_input(xpad); + if (error) { + /* complain only, not much else we can do here */ + dev_err(&xpad->dev->dev, + "unable to init device: %d\n", error); + } else { + rcu_assign_pointer(xpad->x360w_dev, xpad->dev); + } + } else { + RCU_INIT_POINTER(xpad->x360w_dev, NULL); + synchronize_rcu(); + /* + * Now that we are sure xpad360w_process_packet is not + * using input device we can get rid of it. + */ + xpad_deinit_input(xpad); + } +} + +/* + * xpad360w_process_packet + * + * Completes a request by converting the data into events for the + * input subsystem. It is version for xbox 360 wireless controller. + * + * Byte.Bit + * 00.1 - Status change: The controller or headset has connected/disconnected + * Bits 01.7 and 01.6 are valid + * 01.7 - Controller present + * 01.6 - Headset present + * 01.1 - Pad state (Bytes 4+) valid + * + */ +static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data) +{ + struct input_dev *dev; + bool present; + + /* Presence change */ + if (data[0] & 0x08) { + present = (data[1] & 0x80) != 0; + + if (xpad->pad_present != present) { + xpad->pad_present = present; + schedule_work(&xpad->work); + } + } + + /* Valid pad data */ + if (data[1] != 0x1) + return; + + rcu_read_lock(); + dev = rcu_dereference(xpad->x360w_dev); + if (dev) + xpad360_process_packet(xpad, dev, cmd, &data[4]); + rcu_read_unlock(); +} + +/* + * xpadone_process_packet + * + * Completes a request by converting the data into events for the + * input subsystem. This version is for the Xbox One controller. + * + * The report format was gleaned from + * https://github.com/kylelemons/xbox/blob/master/xbox.go + */ +static void xpadone_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data, u32 len) +{ + struct input_dev *dev = xpad->dev; + bool do_sync = false; + + /* the xbox button has its own special report */ + if (data[0] == GIP_CMD_VIRTUAL_KEY) { + /* + * The Xbox One S controller requires these reports to be + * acked otherwise it continues sending them forever and + * won't report further mode button events. + */ + if (data[1] == (GIP_OPT_ACK | GIP_OPT_INTERNAL)) + xpadone_ack_mode_report(xpad, data[2]); + + input_report_key(dev, BTN_MODE, data[4] & GENMASK(1, 0)); + input_sync(dev); + + do_sync = true; + } else if (data[0] == GIP_CMD_FIRMWARE) { + /* Some packet formats force us to use this separate to poll paddle inputs */ + if (xpad->packet_type == PKT_XBE2_FW_5_11) { + /* Mute paddles if controller is in a custom profile slot + * Checked by looking at the active profile slot to + * verify it's the default slot + */ + if (data[19] != 0) + data[18] = 0; + + /* Elite Series 2 split packet paddle bits */ + input_report_key(dev, BTN_GRIPR, data[18] & BIT(0)); + input_report_key(dev, BTN_GRIPR2, data[18] & BIT(1)); + input_report_key(dev, BTN_GRIPL, data[18] & BIT(2)); + input_report_key(dev, BTN_GRIPL2, data[18] & BIT(3)); + + do_sync = true; + } + } else if (data[0] == GIP_CMD_ANNOUNCE) { + int error; + + if (xpad->delay_init && !xpad->delayed_init_done) { + xpad->delayed_init_done = true; + error = xpad_start_input(xpad); + if (error) + dev_warn(&xpad->dev->dev, + "unable to start delayed input: %d\n", + error); + } + } else if (data[0] == GIP_CMD_INPUT) { /* The main valid packet type for inputs */ + /* menu/view buttons */ + input_report_key(dev, BTN_START, data[4] & BIT(2)); + input_report_key(dev, BTN_SELECT, data[4] & BIT(3)); + if (xpad->mapping & MAP_SHARE_BUTTON) { + u32 offset = (xpad->mapping & MAP_SHARE_OFFSET) ? 26 : 18; + + if (len >= offset) + input_report_key(dev, KEY_RECORD, data[len - offset] & BIT(0)); + } + + /* buttons A,B,X,Y */ + input_report_key(dev, BTN_A, data[4] & BIT(4)); + input_report_key(dev, BTN_B, data[4] & BIT(5)); + input_report_key(dev, BTN_X, data[4] & BIT(6)); + input_report_key(dev, BTN_Y, data[4] & BIT(7)); + + /* digital pad */ + if (xpad->mapping & MAP_DPAD_TO_BUTTONS) { + /* dpad as buttons (left, right, up, down) */ + input_report_key(dev, BTN_DPAD_LEFT, data[5] & BIT(2)); + input_report_key(dev, BTN_DPAD_RIGHT, data[5] & BIT(3)); + input_report_key(dev, BTN_DPAD_UP, data[5] & BIT(0)); + input_report_key(dev, BTN_DPAD_DOWN, data[5] & BIT(1)); + } else { + input_report_abs(dev, ABS_HAT0X, + !!(data[5] & 0x08) - !!(data[5] & 0x04)); + input_report_abs(dev, ABS_HAT0Y, + !!(data[5] & 0x02) - !!(data[5] & 0x01)); + } + + /* TL/TR */ + input_report_key(dev, BTN_TL, data[5] & BIT(4)); + input_report_key(dev, BTN_TR, data[5] & BIT(5)); + + /* stick press left/right */ + input_report_key(dev, BTN_THUMBL, data[5] & BIT(6)); + input_report_key(dev, BTN_THUMBR, data[5] & BIT(7)); + + if (!(xpad->mapping & MAP_STICKS_TO_NULL)) { + /* left stick */ + input_report_abs(dev, ABS_X, + (__s16) le16_to_cpup((__le16 *)(data + 10))); + input_report_abs(dev, ABS_Y, + ~(__s16) le16_to_cpup((__le16 *)(data + 12))); + + /* right stick */ + input_report_abs(dev, ABS_RX, + (__s16) le16_to_cpup((__le16 *)(data + 14))); + input_report_abs(dev, ABS_RY, + ~(__s16) le16_to_cpup((__le16 *)(data + 16))); + } + + /* triggers left/right */ + if (xpad->mapping & MAP_TRIGGERS_TO_BUTTONS) { + input_report_key(dev, BTN_TL2, + (__u16) le16_to_cpup((__le16 *)(data + 6))); + input_report_key(dev, BTN_TR2, + (__u16) le16_to_cpup((__le16 *)(data + 8))); + } else { + input_report_abs(dev, ABS_Z, + (__u16) le16_to_cpup((__le16 *)(data + 6))); + input_report_abs(dev, ABS_RZ, + (__u16) le16_to_cpup((__le16 *)(data + 8))); + } + + /* Profile button has a value of 0-3, so it is reported as an axis */ + if (xpad->mapping & MAP_PROFILE_BUTTON) + input_report_abs(dev, ABS_PROFILE, data[34]); + + /* paddle handling */ + /* based on SDL's SDL_hidapi_xboxone.c */ + if (xpad->mapping & MAP_PADDLES) { + if (xpad->packet_type == PKT_XBE1) { + /* Mute paddles if controller has a custom mapping applied. + * Checked by comparing the current mapping + * config against the factory mapping config + */ + if (memcmp(&data[4], &data[18], 2) != 0) + data[32] = 0; + + /* OG Elite Series Controller paddle bits */ + input_report_key(dev, BTN_GRIPR, data[32] & BIT(1)); + input_report_key(dev, BTN_GRIPR2, data[32] & BIT(3)); + input_report_key(dev, BTN_GRIPL, data[32] & BIT(0)); + input_report_key(dev, BTN_GRIPL2, data[32] & BIT(2)); + } else if (xpad->packet_type == PKT_XBE2_FW_OLD) { + /* Mute paddles if controller has a custom mapping applied. + * Checked by comparing the current mapping + * config against the factory mapping config + */ + if (data[19] != 0) + data[18] = 0; + + /* Elite Series 2 4.x firmware paddle bits */ + input_report_key(dev, BTN_GRIPR, data[18] & BIT(0)); + input_report_key(dev, BTN_GRIPR2, data[18] & BIT(1)); + input_report_key(dev, BTN_GRIPL, data[18] & BIT(2)); + input_report_key(dev, BTN_GRIPL2, data[18] & BIT(3)); + } else if (xpad->packet_type == PKT_XBE2_FW_5_EARLY) { + /* Mute paddles if controller has a custom mapping applied. + * Checked by comparing the current mapping + * config against the factory mapping config + */ + if (data[23] != 0) + data[22] = 0; + + /* Elite Series 2 5.x firmware paddle bits + * (before the packet was split) + */ + input_report_key(dev, BTN_GRIPR, data[22] & BIT(0)); + input_report_key(dev, BTN_GRIPR2, data[22] & BIT(1)); + input_report_key(dev, BTN_GRIPL, data[22] & BIT(2)); + input_report_key(dev, BTN_GRIPL2, data[22] & BIT(3)); + } + } + + do_sync = true; + } + + if (do_sync) + input_sync(dev); +} + +static void xpad_irq_in(struct urb *urb) +{ + struct usb_xpad *xpad = urb->context; + struct device *dev = &xpad->intf->dev; + int retval, status; + + status = urb->status; + + switch (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__, status); + return; + default: + dev_dbg(dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + switch (xpad->xtype) { + case XTYPE_XBOX360: + xpad360_process_packet(xpad, xpad->dev, 0, xpad->idata); + break; + case XTYPE_XBOX360W: + xpad360w_process_packet(xpad, 0, xpad->idata); + break; + case XTYPE_XBOXONE: + xpadone_process_packet(xpad, 0, xpad->idata, urb->actual_length); + break; + default: + xpad_process_packet(xpad, 0, xpad->idata); + } + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(dev, "%s - usb_submit_urb failed with result %d\n", + __func__, retval); +} + +/* Callers must hold xpad->odata_lock spinlock */ +static bool xpad_prepare_next_init_packet(struct usb_xpad *xpad) +{ + const struct xboxone_init_packet *init_packet; + + if (xpad->xtype != XTYPE_XBOXONE) + return false; + + /* + * Some dongles will discard init packets if they're sent before the + * controller connects. In these cases, we need to wait until we get + * an announce packet from them to send the init packet sequence. + */ + if (xpad->delay_init && !xpad->delayed_init_done) + return false; + + /* Perform initialization sequence for Xbox One pads that require it */ + while (xpad->init_seq < ARRAY_SIZE(xboxone_init_packets)) { + init_packet = &xboxone_init_packets[xpad->init_seq++]; + + if (init_packet->idVendor != 0 && + init_packet->idVendor != xpad->dev->id.vendor) + continue; + + if (init_packet->idProduct != 0 && + init_packet->idProduct != xpad->dev->id.product) + continue; + + /* This packet applies to our device, so prepare to send it */ + memcpy(xpad->odata, init_packet->data, init_packet->len); + xpad->irq_out->transfer_buffer_length = init_packet->len; + + /* Update packet with current sequence number */ + xpad->odata[2] = xpad->odata_serial++; + return true; + } + + return false; +} + +/* Callers must hold xpad->odata_lock spinlock */ +static bool xpad_prepare_next_out_packet(struct usb_xpad *xpad) +{ + struct xpad_output_packet *pkt, *packet = NULL; + int i; + + /* We may have init packets to send before we can send user commands */ + if (xpad_prepare_next_init_packet(xpad)) + return true; + + for (i = 0; i < XPAD_NUM_OUT_PACKETS; i++) { + if (++xpad->last_out_packet >= XPAD_NUM_OUT_PACKETS) + xpad->last_out_packet = 0; + + pkt = &xpad->out_packets[xpad->last_out_packet]; + if (pkt->pending) { + dev_dbg(&xpad->intf->dev, + "%s - found pending output packet %d\n", + __func__, xpad->last_out_packet); + packet = pkt; + break; + } + } + + if (packet) { + memcpy(xpad->odata, packet->data, packet->len); + xpad->irq_out->transfer_buffer_length = packet->len; + packet->pending = false; + return true; + } + + return false; +} + +/* Callers must hold xpad->odata_lock spinlock */ +static int xpad_try_sending_next_out_packet(struct usb_xpad *xpad) +{ + int error; + + if (!xpad->irq_out_active && xpad_prepare_next_out_packet(xpad)) { + usb_anchor_urb(xpad->irq_out, &xpad->irq_out_anchor); + error = usb_submit_urb(xpad->irq_out, GFP_ATOMIC); + if (error) { + if (error != -ENODEV) + dev_err(&xpad->intf->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, error); + usb_unanchor_urb(xpad->irq_out); + return error; + } + + xpad->irq_out_active = true; + } + + return 0; +} + +static void xpad_irq_out(struct urb *urb) +{ + struct usb_xpad *xpad = urb->context; + struct device *dev = &xpad->intf->dev; + int status = urb->status; + int error; + + guard(spinlock_irqsave)(&xpad->odata_lock); + + switch (status) { + case 0: + /* success */ + xpad->irq_out_active = xpad_prepare_next_out_packet(xpad); + 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__, status); + xpad->irq_out_active = false; + break; + + default: + dev_dbg(dev, "%s - nonzero urb status received: %d\n", + __func__, status); + break; + } + + if (xpad->irq_out_active) { + usb_anchor_urb(urb, &xpad->irq_out_anchor); + error = usb_submit_urb(urb, GFP_ATOMIC); + if (error) { + dev_err(dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, error); + usb_unanchor_urb(urb); + xpad->irq_out_active = false; + } + } +} + +static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad, + struct usb_endpoint_descriptor *ep_irq_out) +{ + int error; + + if (xpad->xtype == XTYPE_UNKNOWN) + return 0; + + init_usb_anchor(&xpad->irq_out_anchor); + + xpad->odata = usb_alloc_coherent(xpad->udev, XPAD_PKT_LEN, + GFP_KERNEL, &xpad->odata_dma); + if (!xpad->odata) + return -ENOMEM; + + spin_lock_init(&xpad->odata_lock); + + xpad->irq_out = usb_alloc_urb(0, GFP_KERNEL); + if (!xpad->irq_out) { + error = -ENOMEM; + goto err_free_coherent; + } + + usb_fill_int_urb(xpad->irq_out, xpad->udev, + usb_sndintpipe(xpad->udev, ep_irq_out->bEndpointAddress), + xpad->odata, XPAD_PKT_LEN, + xpad_irq_out, xpad, ep_irq_out->bInterval); + xpad->irq_out->transfer_dma = xpad->odata_dma; + xpad->irq_out->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + return 0; + +err_free_coherent: + usb_free_coherent(xpad->udev, XPAD_PKT_LEN, xpad->odata, xpad->odata_dma); + return error; +} + +static void xpad_stop_output(struct usb_xpad *xpad) +{ + if (xpad->xtype != XTYPE_UNKNOWN) { + if (!usb_wait_anchor_empty_timeout(&xpad->irq_out_anchor, + 5000)) { + dev_warn(&xpad->intf->dev, + "timed out waiting for output URB to complete, killing\n"); + usb_kill_anchored_urbs(&xpad->irq_out_anchor); + } + } +} + +static void xpad_deinit_output(struct usb_xpad *xpad) +{ + if (xpad->xtype != XTYPE_UNKNOWN) { + usb_free_urb(xpad->irq_out); + usb_free_coherent(xpad->udev, XPAD_PKT_LEN, + xpad->odata, xpad->odata_dma); + } +} + +static int xpad_inquiry_pad_presence(struct usb_xpad *xpad) +{ + struct xpad_output_packet *packet = + &xpad->out_packets[XPAD_OUT_CMD_IDX]; + + guard(spinlock_irqsave)(&xpad->odata_lock); + + packet->data[0] = 0x08; + packet->data[1] = 0x00; + packet->data[2] = 0x0F; + packet->data[3] = 0xC0; + packet->data[4] = 0x00; + packet->data[5] = 0x00; + packet->data[6] = 0x00; + packet->data[7] = 0x00; + packet->data[8] = 0x00; + packet->data[9] = 0x00; + packet->data[10] = 0x00; + packet->data[11] = 0x00; + packet->len = 12; + packet->pending = true; + + /* Reset the sequence so we send out presence first */ + xpad->last_out_packet = -1; + return xpad_try_sending_next_out_packet(xpad); +} + +static int xpad_start_xbox_one(struct usb_xpad *xpad) +{ + int error; + + if (usb_ifnum_to_if(xpad->udev, GIP_WIRED_INTF_AUDIO)) { + /* + * Explicitly disable the audio interface. This is needed + * for some controllers, such as the PowerA Enhanced Wired + * Controller for Series X|S (0x20d6:0x200e) to report the + * guide button. + */ + error = usb_set_interface(xpad->udev, + GIP_WIRED_INTF_AUDIO, 0); + if (error) + dev_warn(&xpad->dev->dev, + "unable to disable audio interface: %d\n", + error); + } + + guard(spinlock_irqsave)(&xpad->odata_lock); + + /* + * Begin the init sequence by attempting to send a packet. + * We will cycle through the init packet sequence before + * sending any packets from the output ring. + */ + xpad->init_seq = 0; + return xpad_try_sending_next_out_packet(xpad); +} + +static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num) +{ + struct xpad_output_packet *packet = + &xpad->out_packets[XPAD_OUT_CMD_IDX]; + static const u8 mode_report_ack[] = { + GIP_CMD_ACK, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(9), + 0x00, GIP_CMD_VIRTUAL_KEY, GIP_OPT_INTERNAL, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + guard(spinlock_irqsave)(&xpad->odata_lock); + + packet->len = sizeof(mode_report_ack); + memcpy(packet->data, mode_report_ack, packet->len); + packet->data[2] = seq_num; + packet->pending = true; + + /* Reset the sequence so we send out the ack now */ + xpad->last_out_packet = -1; + xpad_try_sending_next_out_packet(xpad); +} + +#ifdef CONFIG_JOYSTICK_XPAD_FF +static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect) +{ + struct usb_xpad *xpad = input_get_drvdata(dev); + struct xpad_output_packet *packet = &xpad->out_packets[XPAD_OUT_FF_IDX]; + __u16 strong; + __u16 weak; + + if (effect->type != FF_RUMBLE) + return 0; + + strong = effect->u.rumble.strong_magnitude; + weak = effect->u.rumble.weak_magnitude; + + guard(spinlock_irqsave)(&xpad->odata_lock); + + switch (xpad->xtype) { + case XTYPE_XBOX: + packet->data[0] = 0x00; + packet->data[1] = 0x06; + packet->data[2] = 0x00; + packet->data[3] = strong / 256; /* left actuator */ + packet->data[4] = 0x00; + packet->data[5] = weak / 256; /* right actuator */ + packet->len = 6; + packet->pending = true; + break; + + case XTYPE_XBOX360: + packet->data[0] = 0x00; + packet->data[1] = 0x08; + packet->data[2] = 0x00; + packet->data[3] = strong / 256; /* left actuator? */ + packet->data[4] = weak / 256; /* right actuator? */ + packet->data[5] = 0x00; + packet->data[6] = 0x00; + packet->data[7] = 0x00; + packet->len = 8; + packet->pending = true; + break; + + case XTYPE_XBOX360W: + packet->data[0] = 0x00; + packet->data[1] = 0x01; + packet->data[2] = 0x0F; + packet->data[3] = 0xC0; + packet->data[4] = 0x00; + packet->data[5] = strong / 256; + packet->data[6] = weak / 256; + packet->data[7] = 0x00; + packet->data[8] = 0x00; + packet->data[9] = 0x00; + packet->data[10] = 0x00; + packet->data[11] = 0x00; + packet->len = 12; + packet->pending = true; + break; + + case XTYPE_XBOXONE: + packet->data[0] = GIP_CMD_RUMBLE; /* activate rumble */ + packet->data[1] = 0x00; + packet->data[2] = xpad->odata_serial++; + packet->data[3] = GIP_PL_LEN(9); + packet->data[4] = 0x00; + packet->data[5] = GIP_MOTOR_ALL; + packet->data[6] = 0x00; /* left trigger */ + packet->data[7] = 0x00; /* right trigger */ + packet->data[8] = strong / 512; /* left actuator */ + packet->data[9] = weak / 512; /* right actuator */ + packet->data[10] = 0xFF; /* on period */ + packet->data[11] = 0x00; /* off period */ + packet->data[12] = 0xFF; /* repeat count */ + packet->len = 13; + packet->pending = true; + break; + + default: + dev_dbg(&xpad->dev->dev, + "%s - rumble command sent to unsupported xpad type: %d\n", + __func__, xpad->xtype); + return -EINVAL; + } + + return xpad_try_sending_next_out_packet(xpad); +} + +static int xpad_init_ff(struct usb_xpad *xpad) +{ + if (xpad->xtype == XTYPE_UNKNOWN) + return 0; + + input_set_capability(xpad->dev, EV_FF, FF_RUMBLE); + + return input_ff_create_memless(xpad->dev, NULL, xpad_play_effect); +} + +#else +static int xpad_init_ff(struct usb_xpad *xpad) { return 0; } +#endif + +#if defined(CONFIG_JOYSTICK_XPAD_LEDS) +#include <linux/leds.h> +#include <linux/idr.h> + +static DEFINE_IDA(xpad_pad_seq); + +struct xpad_led { + char name[16]; + struct led_classdev led_cdev; + struct usb_xpad *xpad; +}; + +/* + * set the LEDs on Xbox 360 / Wireless Controllers + * @param command + * 0: off + * 1: all blink, then previous setting + * 2: 1/top-left blink, then on + * 3: 2/top-right blink, then on + * 4: 3/bottom-left blink, then on + * 5: 4/bottom-right blink, then on + * 6: 1/top-left on + * 7: 2/top-right on + * 8: 3/bottom-left on + * 9: 4/bottom-right on + * 10: rotate + * 11: blink, based on previous setting + * 12: slow blink, based on previous setting + * 13: rotate with two lights + * 14: persistent slow all blink + * 15: blink once, then previous setting + */ +static void xpad_send_led_command(struct usb_xpad *xpad, int command) +{ + struct xpad_output_packet *packet = + &xpad->out_packets[XPAD_OUT_LED_IDX]; + + command %= 16; + + guard(spinlock_irqsave)(&xpad->odata_lock); + + switch (xpad->xtype) { + case XTYPE_XBOX360: + packet->data[0] = 0x01; + packet->data[1] = 0x03; + packet->data[2] = command; + packet->len = 3; + packet->pending = true; + break; + + case XTYPE_XBOX360W: + packet->data[0] = 0x00; + packet->data[1] = 0x00; + packet->data[2] = 0x08; + packet->data[3] = 0x40 + command; + packet->data[4] = 0x00; + packet->data[5] = 0x00; + packet->data[6] = 0x00; + packet->data[7] = 0x00; + packet->data[8] = 0x00; + packet->data[9] = 0x00; + packet->data[10] = 0x00; + packet->data[11] = 0x00; + packet->len = 12; + packet->pending = true; + break; + } + + xpad_try_sending_next_out_packet(xpad); +} + +/* + * Light up the segment corresponding to the pad number on + * Xbox 360 Controllers. + */ +static void xpad_identify_controller(struct usb_xpad *xpad) +{ + led_set_brightness(&xpad->led->led_cdev, (xpad->pad_nr % 4) + 2); +} + +static void xpad_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct xpad_led *xpad_led = container_of(led_cdev, + struct xpad_led, led_cdev); + + xpad_send_led_command(xpad_led->xpad, value); +} + +static int xpad_led_probe(struct usb_xpad *xpad) +{ + struct xpad_led *led; + struct led_classdev *led_cdev; + int error; + + if (xpad->xtype != XTYPE_XBOX360 && xpad->xtype != XTYPE_XBOX360W) + return 0; + + xpad->led = led = kzalloc_obj(*led); + if (!led) + return -ENOMEM; + + xpad->pad_nr = ida_alloc(&xpad_pad_seq, GFP_KERNEL); + if (xpad->pad_nr < 0) { + error = xpad->pad_nr; + goto err_free_mem; + } + + snprintf(led->name, sizeof(led->name), "xpad%d", xpad->pad_nr); + led->xpad = xpad; + + led_cdev = &led->led_cdev; + led_cdev->name = led->name; + led_cdev->brightness_set = xpad_led_set; + led_cdev->flags = LED_CORE_SUSPENDRESUME; + + error = led_classdev_register(&xpad->udev->dev, led_cdev); + if (error) + goto err_free_id; + + xpad_identify_controller(xpad); + + return 0; + +err_free_id: + ida_free(&xpad_pad_seq, xpad->pad_nr); +err_free_mem: + kfree(led); + xpad->led = NULL; + return error; +} + +static void xpad_led_disconnect(struct usb_xpad *xpad) +{ + struct xpad_led *xpad_led = xpad->led; + + if (xpad_led) { + led_classdev_unregister(&xpad_led->led_cdev); + ida_free(&xpad_pad_seq, xpad->pad_nr); + kfree(xpad_led); + } +} +#else +static int xpad_led_probe(struct usb_xpad *xpad) { return 0; } +static void xpad_led_disconnect(struct usb_xpad *xpad) { } +#endif + +static int xpad_start_input(struct usb_xpad *xpad) +{ + int error; + + if (usb_submit_urb(xpad->irq_in, GFP_KERNEL)) + return -EIO; + + if (xpad->xtype == XTYPE_XBOXONE) { + error = xpad_start_xbox_one(xpad); + if (error) { + usb_kill_urb(xpad->irq_in); + return error; + } + } + if (xpad->xtype == XTYPE_XBOX360) { + /* + * Some third-party controllers Xbox 360-style controllers + * require this message to finish initialization. + */ + u8 dummy[20]; + + error = usb_control_msg_recv(xpad->udev, 0, + /* bRequest */ 0x01, + /* bmRequestType */ + USB_TYPE_VENDOR | USB_DIR_IN | + USB_RECIP_INTERFACE, + /* wValue */ 0x100, + /* wIndex */ 0x00, + dummy, sizeof(dummy), + 25, GFP_KERNEL); + if (error) + dev_warn(&xpad->dev->dev, + "unable to receive magic message: %d\n", + error); + } + + return 0; +} + +static void xpad_stop_input(struct usb_xpad *xpad) +{ + usb_kill_urb(xpad->irq_in); +} + +static void xpad360w_poweroff_controller(struct usb_xpad *xpad) +{ + struct xpad_output_packet *packet = + &xpad->out_packets[XPAD_OUT_CMD_IDX]; + + guard(spinlock_irqsave)(&xpad->odata_lock); + + packet->data[0] = 0x00; + packet->data[1] = 0x00; + packet->data[2] = 0x08; + packet->data[3] = 0xC0; + packet->data[4] = 0x00; + packet->data[5] = 0x00; + packet->data[6] = 0x00; + packet->data[7] = 0x00; + packet->data[8] = 0x00; + packet->data[9] = 0x00; + packet->data[10] = 0x00; + packet->data[11] = 0x00; + packet->len = 12; + packet->pending = true; + + /* Reset the sequence so we send out poweroff now */ + xpad->last_out_packet = -1; + xpad_try_sending_next_out_packet(xpad); +} + +static int xpad360w_start_input(struct usb_xpad *xpad) +{ + int error; + + error = usb_submit_urb(xpad->irq_in, GFP_KERNEL); + if (error) + return -EIO; + + /* + * Send presence packet. + * This will force the controller to resend connection packets. + * This is useful in the case we activate the module after the + * adapter has been plugged in, as it won't automatically + * send us info about the controllers. + */ + error = xpad_inquiry_pad_presence(xpad); + if (error) { + usb_kill_urb(xpad->irq_in); + return error; + } + + return 0; +} + +static void xpad360w_stop_input(struct usb_xpad *xpad) +{ + usb_kill_urb(xpad->irq_in); + + /* Make sure we are done with presence work if it was scheduled */ + flush_work(&xpad->work); +} + +static int xpad_open(struct input_dev *dev) +{ + struct usb_xpad *xpad = input_get_drvdata(dev); + + return xpad_start_input(xpad); +} + +static void xpad_close(struct input_dev *dev) +{ + struct usb_xpad *xpad = input_get_drvdata(dev); + + xpad_stop_input(xpad); +} + +static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs) +{ + struct usb_xpad *xpad = input_get_drvdata(input_dev); + + switch (abs) { + case ABS_X: + case ABS_Y: + case ABS_RX: + case ABS_RY: /* the two sticks */ + input_set_abs_params(input_dev, abs, -32768, 32767, 16, 128); + break; + case ABS_Z: + case ABS_RZ: /* the triggers (if mapped to axes) */ + if (xpad->xtype == XTYPE_XBOXONE) + input_set_abs_params(input_dev, abs, 0, 1023, 0, 0); + else + input_set_abs_params(input_dev, abs, 0, 255, 0, 0); + break; + case ABS_HAT0X: + case ABS_HAT0Y: /* the d-pad (only if dpad is mapped to axes */ + input_set_abs_params(input_dev, abs, -1, 1, 0, 0); + break; + case ABS_PROFILE: /* 4 value profile button (such as on XAC) */ + input_set_abs_params(input_dev, abs, 0, 4, 0, 0); + break; + default: + input_set_abs_params(input_dev, abs, 0, 0, 0, 0); + break; + } +} + +static void xpad_deinit_input(struct usb_xpad *xpad) +{ + if (xpad->input_created) { + xpad->input_created = false; + xpad_led_disconnect(xpad); + input_unregister_device(xpad->dev); + } +} + +static int xpad_init_input(struct usb_xpad *xpad) +{ + struct input_dev *input_dev; + int i, error; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + xpad->dev = input_dev; + input_dev->name = xpad->name; + input_dev->phys = xpad->phys; + usb_to_input_id(xpad->udev, &input_dev->id); + + if (xpad->xtype == XTYPE_XBOX360W) { + /* x360w controllers and the receiver have different ids */ + input_dev->id.product = 0x02a1; + } + + input_dev->dev.parent = &xpad->intf->dev; + + input_set_drvdata(input_dev, xpad); + + if (xpad->xtype != XTYPE_XBOX360W) { + input_dev->open = xpad_open; + input_dev->close = xpad_close; + } + + if (!(xpad->mapping & MAP_STICKS_TO_NULL)) { + /* set up axes */ + for (i = 0; xpad_abs[i] >= 0; i++) + xpad_set_up_abs(input_dev, xpad_abs[i]); + } + + /* set up standard buttons */ + for (i = 0; xpad_common_btn[i] >= 0; i++) + input_set_capability(input_dev, EV_KEY, xpad_common_btn[i]); + + /* set up model-specific ones */ + if (xpad->xtype == XTYPE_XBOX360 || xpad->xtype == XTYPE_XBOX360W || + xpad->xtype == XTYPE_XBOXONE) { + for (i = 0; xpad360_btn[i] >= 0; i++) + input_set_capability(input_dev, EV_KEY, xpad360_btn[i]); + if (xpad->mapping & MAP_SHARE_BUTTON) + input_set_capability(input_dev, EV_KEY, KEY_RECORD); + } else { + for (i = 0; xpad_btn[i] >= 0; i++) + input_set_capability(input_dev, EV_KEY, xpad_btn[i]); + } + + if (xpad->mapping & MAP_DPAD_TO_BUTTONS) { + for (i = 0; xpad_btn_pad[i] >= 0; i++) + input_set_capability(input_dev, EV_KEY, + xpad_btn_pad[i]); + } + + /* set up paddles if the controller has them */ + if (xpad->mapping & MAP_PADDLES) { + for (i = 0; xpad_btn_paddles[i] >= 0; i++) + input_set_capability(input_dev, EV_KEY, xpad_btn_paddles[i]); + } + + /* + * This should be a simple else block. However historically + * xbox360w has mapped DPAD to buttons while xbox360 did not. This + * made no sense, but now we can not just switch back and have to + * support both behaviors. + */ + if (!(xpad->mapping & MAP_DPAD_TO_BUTTONS) || + xpad->xtype == XTYPE_XBOX360W) { + for (i = 0; xpad_abs_pad[i] >= 0; i++) + xpad_set_up_abs(input_dev, xpad_abs_pad[i]); + } + + if (xpad->mapping & MAP_TRIGGERS_TO_BUTTONS) { + for (i = 0; xpad_btn_triggers[i] >= 0; i++) + input_set_capability(input_dev, EV_KEY, + xpad_btn_triggers[i]); + } else { + for (i = 0; xpad_abs_triggers[i] >= 0; i++) + xpad_set_up_abs(input_dev, xpad_abs_triggers[i]); + } + + /* setup profile button as an axis with 4 possible values */ + if (xpad->mapping & MAP_PROFILE_BUTTON) + xpad_set_up_abs(input_dev, ABS_PROFILE); + + error = xpad_init_ff(xpad); + if (error) + goto err_free_input; + + error = xpad_led_probe(xpad); + if (error) + goto err_destroy_ff; + + error = input_register_device(xpad->dev); + if (error) + goto err_disconnect_led; + + xpad->input_created = true; + return 0; + +err_disconnect_led: + xpad_led_disconnect(xpad); +err_destroy_ff: + input_ff_destroy(input_dev); +err_free_input: + input_free_device(input_dev); + return error; +} + +static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_xpad *xpad; + struct usb_endpoint_descriptor *ep_irq_in, *ep_irq_out; + int i, error; + + if (intf->cur_altsetting->desc.bNumEndpoints != 2) + return -ENODEV; + + for (i = 0; xpad_device[i].idVendor; i++) { + if ((le16_to_cpu(udev->descriptor.idVendor) == xpad_device[i].idVendor) && + (le16_to_cpu(udev->descriptor.idProduct) == xpad_device[i].idProduct)) + break; + } + + xpad = kzalloc_obj(*xpad); + if (!xpad) + return -ENOMEM; + + usb_make_path(udev, xpad->phys, sizeof(xpad->phys)); + strlcat(xpad->phys, "/input0", sizeof(xpad->phys)); + + xpad->idata = usb_alloc_coherent(udev, XPAD_PKT_LEN, + GFP_KERNEL, &xpad->idata_dma); + if (!xpad->idata) { + error = -ENOMEM; + goto err_free_mem; + } + + xpad->irq_in = usb_alloc_urb(0, GFP_KERNEL); + if (!xpad->irq_in) { + error = -ENOMEM; + goto err_free_idata; + } + + xpad->udev = udev; + xpad->intf = intf; + xpad->mapping = xpad_device[i].mapping; + xpad->xtype = xpad_device[i].xtype; + xpad->name = xpad_device[i].name; + if (xpad_device[i].flags & FLAG_DELAY_INIT) + xpad->delay_init = true; + + xpad->packet_type = PKT_XB; + INIT_WORK(&xpad->work, xpad_presence_work); + + if (xpad->xtype == XTYPE_UNKNOWN) { + if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) { + if (intf->cur_altsetting->desc.bInterfaceProtocol == 129) + xpad->xtype = XTYPE_XBOX360W; + else if (intf->cur_altsetting->desc.bInterfaceProtocol == 208) + xpad->xtype = XTYPE_XBOXONE; + else + xpad->xtype = XTYPE_XBOX360; + } else { + xpad->xtype = XTYPE_XBOX; + } + + if (dpad_to_buttons) + xpad->mapping |= MAP_DPAD_TO_BUTTONS; + if (triggers_to_buttons) + xpad->mapping |= MAP_TRIGGERS_TO_BUTTONS; + if (sticks_to_null) + xpad->mapping |= MAP_STICKS_TO_NULL; + } + + if (xpad->xtype == XTYPE_XBOXONE && + intf->cur_altsetting->desc.bInterfaceNumber != GIP_WIRED_INTF_DATA) { + /* + * The Xbox One controller lists three interfaces all with the + * same interface class, subclass and protocol. Differentiate by + * interface number. + */ + error = -ENODEV; + goto err_free_in_urb; + } + + ep_irq_in = ep_irq_out = NULL; + + for (i = 0; i < 2; i++) { + struct usb_endpoint_descriptor *ep = + &intf->cur_altsetting->endpoint[i].desc; + + if (usb_endpoint_xfer_int(ep)) { + if (usb_endpoint_dir_in(ep)) + ep_irq_in = ep; + else + ep_irq_out = ep; + } + } + + if (!ep_irq_in || !ep_irq_out) { + error = -ENODEV; + goto err_free_in_urb; + } + + error = xpad_init_output(intf, xpad, ep_irq_out); + if (error) + goto err_free_in_urb; + + usb_fill_int_urb(xpad->irq_in, udev, + usb_rcvintpipe(udev, ep_irq_in->bEndpointAddress), + xpad->idata, XPAD_PKT_LEN, xpad_irq_in, + xpad, ep_irq_in->bInterval); + xpad->irq_in->transfer_dma = xpad->idata_dma; + xpad->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + usb_set_intfdata(intf, xpad); + + /* Packet type detection */ + if (le16_to_cpu(udev->descriptor.idVendor) == 0x045e) { /* Microsoft controllers */ + if (le16_to_cpu(udev->descriptor.idProduct) == 0x02e3) { + /* The original elite controller always uses the oldest + * type of extended packet + */ + xpad->packet_type = PKT_XBE1; + } else if (le16_to_cpu(udev->descriptor.idProduct) == 0x0b00) { + /* The elite 2 controller has seen multiple packet + * revisions. These are tied to specific firmware + * versions + */ + if (le16_to_cpu(udev->descriptor.bcdDevice) < 0x0500) { + /* This is the format that the Elite 2 used + * prior to the BLE update + */ + xpad->packet_type = PKT_XBE2_FW_OLD; + } else if (le16_to_cpu(udev->descriptor.bcdDevice) < + 0x050b) { + /* This is the format that the Elite 2 used + * prior to the update that split the packet + */ + xpad->packet_type = PKT_XBE2_FW_5_EARLY; + } else { + /* The split packet format that was introduced + * in firmware v5.11 + */ + xpad->packet_type = PKT_XBE2_FW_5_11; + } + } + } + + if (xpad->xtype == XTYPE_XBOX360W) { + /* + * Submit the int URB immediately rather than waiting for open + * because we get status messages from the device whether + * or not any controllers are attached. In fact, it's + * exactly the message that a controller has arrived that + * we're waiting for. + */ + error = xpad360w_start_input(xpad); + if (error) + goto err_deinit_output; + /* + * Wireless controllers require RESET_RESUME to work properly + * after suspend. Ideally this quirk should be in usb core + * quirk list, but we have too many vendors producing these + * controllers and we'd need to maintain 2 identical lists + * here in this driver and in usb core. + */ + udev->quirks |= USB_QUIRK_RESET_RESUME; + } else { + error = xpad_init_input(xpad); + if (error) + goto err_deinit_output; + } + return 0; + +err_deinit_output: + xpad_deinit_output(xpad); +err_free_in_urb: + usb_free_urb(xpad->irq_in); +err_free_idata: + usb_free_coherent(udev, XPAD_PKT_LEN, xpad->idata, xpad->idata_dma); +err_free_mem: + kfree(xpad); + return error; +} + +static void xpad_disconnect(struct usb_interface *intf) +{ + struct usb_xpad *xpad = usb_get_intfdata(intf); + + if (xpad->xtype == XTYPE_XBOX360W) + xpad360w_stop_input(xpad); + + xpad_deinit_input(xpad); + + /* + * Now that both input device and LED device are gone we can + * stop output URB. + */ + xpad_stop_output(xpad); + + xpad_deinit_output(xpad); + + usb_free_urb(xpad->irq_in); + usb_free_coherent(xpad->udev, XPAD_PKT_LEN, + xpad->idata, xpad->idata_dma); + + kfree(xpad); + + usb_set_intfdata(intf, NULL); +} + +static int xpad_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_xpad *xpad = usb_get_intfdata(intf); + struct input_dev *input = xpad->dev; + + if (xpad->xtype == XTYPE_XBOX360W) { + /* + * Wireless controllers always listen to input so + * they are notified when controller shows up + * or goes away. + */ + xpad360w_stop_input(xpad); + + /* + * The wireless adapter is going off now, so the + * gamepads are going to become disconnected. + * Unless explicitly disabled, power them down + * so they don't just sit there flashing. + */ + if (auto_poweroff && xpad->pad_present) + xpad360w_poweroff_controller(xpad); + } else { + guard(mutex)(&input->mutex); + + if (input_device_enabled(input)) + xpad_stop_input(xpad); + } + + xpad_stop_output(xpad); + + return 0; +} + +static int xpad_resume(struct usb_interface *intf) +{ + struct usb_xpad *xpad = usb_get_intfdata(intf); + struct input_dev *input = xpad->dev; + + xpad->delayed_init_done = false; + if (xpad->xtype == XTYPE_XBOX360W) + return xpad360w_start_input(xpad); + + guard(mutex)(&input->mutex); + + if (input_device_enabled(input)) + return xpad_start_input(xpad); + + if (xpad->xtype == XTYPE_XBOXONE) { + /* + * Even if there are no users, we'll send Xbox One pads + * the startup sequence so they don't sit there and + * blink until somebody opens the input device again. + */ + return xpad_start_xbox_one(xpad); + } + + return 0; +} + +static struct usb_driver xpad_driver = { + .name = "xpad", + .probe = xpad_probe, + .disconnect = xpad_disconnect, + .suspend = xpad_suspend, + .resume = xpad_resume, + .id_table = xpad_table, +}; + +module_usb_driver(xpad_driver); + +MODULE_AUTHOR("Marko Friedemann <mfr@bmx-chemnitz.de>"); +MODULE_DESCRIPTION("Xbox pad driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/joystick/zhenhua.c b/drivers/input/joystick/zhenhua.c new file mode 100644 index 000000000..8ca189663 --- /dev/null +++ b/drivers/input/joystick/zhenhua.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * derived from "twidjoy.c" + * + * Copyright (c) 2008 Martin Kebert + * Copyright (c) 2001 Arndt Schoenewald + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2000 Mark Fletcher + */ + +/* + * Driver to use 4CH RC transmitter using Zhen Hua 5-byte protocol (Walkera Lama, + * EasyCopter etc.) as a joystick under Linux. + * + * RC transmitters using Zhen Hua 5-byte protocol are cheap four channels + * transmitters for control a RC planes or RC helicopters with possibility to + * connect on a serial port. + * Data coming from transmitter is in this order: + * 1. byte = synchronisation byte + * 2. byte = X axis + * 3. byte = Y axis + * 4. byte = RZ axis + * 5. byte = Z axis + * (and this is repeated) + * + * For questions or feedback regarding this driver module please contact: + * Martin Kebert <gkmarty@gmail.com> - but I am not a C-programmer nor kernel + * coder :-( + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/bitrev.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "RC transmitter with 5-byte Zhen Hua protocol joystick driver" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Constants. + */ + +#define ZHENHUA_MAX_LENGTH 5 + +/* + * Zhen Hua data. + */ + +struct zhenhua { + struct input_dev *dev; + int idx; + unsigned char data[ZHENHUA_MAX_LENGTH]; + char phys[32]; +}; + +/* + * zhenhua_process_packet() decodes packets the driver receives from the + * RC transmitter. It updates the data accordingly. + */ + +static void zhenhua_process_packet(struct zhenhua *zhenhua) +{ + struct input_dev *dev = zhenhua->dev; + unsigned char *data = zhenhua->data; + + input_report_abs(dev, ABS_Y, data[1]); + input_report_abs(dev, ABS_X, data[2]); + input_report_abs(dev, ABS_RZ, data[3]); + input_report_abs(dev, ABS_Z, data[4]); + + input_sync(dev); +} + +/* + * zhenhua_interrupt() is called by the low level driver when characters + * are ready for us. We then buffer them for further processing, or call the + * packet processing routine. + */ + +static irqreturn_t zhenhua_interrupt(struct serio *serio, unsigned char data, unsigned int flags) +{ + struct zhenhua *zhenhua = serio_get_drvdata(serio); + + /* All Zhen Hua packets are 5 bytes. The fact that the first byte + * is allways 0xf7 and all others are in range 0x32 - 0xc8 (50-200) + * can be used to check and regain sync. */ + + if (data == 0xef) + zhenhua->idx = 0; /* this byte starts a new packet */ + else if (zhenhua->idx == 0) + return IRQ_HANDLED; /* wrong MSB -- ignore this byte */ + + if (zhenhua->idx < ZHENHUA_MAX_LENGTH) + zhenhua->data[zhenhua->idx++] = bitrev8(data); + + if (zhenhua->idx == ZHENHUA_MAX_LENGTH) { + zhenhua_process_packet(zhenhua); + zhenhua->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * zhenhua_disconnect() is the opposite of zhenhua_connect() + */ + +static void zhenhua_disconnect(struct serio *serio) +{ + struct zhenhua *zhenhua = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(zhenhua->dev); + kfree(zhenhua); +} + +/* + * zhenhua_connect() is the routine that is called when someone adds a + * new serio device. It looks for the Twiddler, and if found, registers + * it as an input device. + */ + +static int zhenhua_connect(struct serio *serio, struct serio_driver *drv) +{ + struct zhenhua *zhenhua; + struct input_dev *input_dev; + int err = -ENOMEM; + + zhenhua = kzalloc_obj(*zhenhua); + input_dev = input_allocate_device(); + if (!zhenhua || !input_dev) + goto fail1; + + zhenhua->dev = input_dev; + snprintf(zhenhua->phys, sizeof(zhenhua->phys), "%s/input0", serio->phys); + + input_dev->name = "Zhen Hua 5-byte device"; + input_dev->phys = zhenhua->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_ZHENHUA; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT(EV_ABS); + input_set_abs_params(input_dev, ABS_X, 50, 200, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 50, 200, 0, 0); + input_set_abs_params(input_dev, ABS_Z, 50, 200, 0, 0); + input_set_abs_params(input_dev, ABS_RZ, 50, 200, 0, 0); + + serio_set_drvdata(serio, zhenhua); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(zhenhua->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(zhenhua); + return err; +} + +/* + * The serio driver structure. + */ + +static const struct serio_device_id zhenhua_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_ZHENHUA, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, zhenhua_serio_ids); + +static struct serio_driver zhenhua_drv = { + .driver = { + .name = "zhenhua", + }, + .description = DRIVER_DESC, + .id_table = zhenhua_serio_ids, + .interrupt = zhenhua_interrupt, + .connect = zhenhua_connect, + .disconnect = zhenhua_disconnect, +}; + +module_serio_driver(zhenhua_drv); |
