162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Driver for the Gravis Grip Multiport, a gamepad "hub" that
462306a36Sopenharmony_ci *  connects up to four 9-pin digital gamepads/joysticks.
562306a36Sopenharmony_ci *  Driver tested on SMP and UP kernel versions 2.4.18-4 and 2.4.18-5.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *  Thanks to Chris Gassib for helpful advice.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci *  Copyright (c)      2002 Brian Bonnlander, Bill Soudan
1062306a36Sopenharmony_ci *  Copyright (c) 1998-2000 Vojtech Pavlik
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/gameport.h>
1762306a36Sopenharmony_ci#include <linux/input.h>
1862306a36Sopenharmony_ci#include <linux/delay.h>
1962306a36Sopenharmony_ci#include <linux/proc_fs.h>
2062306a36Sopenharmony_ci#include <linux/jiffies.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define DRIVER_DESC	"Gravis Grip Multiport driver"
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ciMODULE_AUTHOR("Brian Bonnlander");
2562306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
2662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#ifdef GRIP_DEBUG
2962306a36Sopenharmony_ci#define dbg(format, arg...) printk(KERN_ERR __FILE__ ": " format "\n" , ## arg)
3062306a36Sopenharmony_ci#else
3162306a36Sopenharmony_ci#define dbg(format, arg...) do {} while (0)
3262306a36Sopenharmony_ci#endif
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define GRIP_MAX_PORTS	4
3562306a36Sopenharmony_ci/*
3662306a36Sopenharmony_ci * Grip multiport state
3762306a36Sopenharmony_ci */
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistruct grip_port {
4062306a36Sopenharmony_ci	struct input_dev *dev;
4162306a36Sopenharmony_ci	int mode;
4262306a36Sopenharmony_ci	int registered;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	/* individual gamepad states */
4562306a36Sopenharmony_ci	int buttons;
4662306a36Sopenharmony_ci	int xaxes;
4762306a36Sopenharmony_ci	int yaxes;
4862306a36Sopenharmony_ci	int dirty;     /* has the state been updated? */
4962306a36Sopenharmony_ci};
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistruct grip_mp {
5262306a36Sopenharmony_ci	struct gameport *gameport;
5362306a36Sopenharmony_ci	struct grip_port *port[GRIP_MAX_PORTS];
5462306a36Sopenharmony_ci	int reads;
5562306a36Sopenharmony_ci	int bads;
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci/*
5962306a36Sopenharmony_ci * Multiport packet interpretation
6062306a36Sopenharmony_ci */
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci#define PACKET_FULL          0x80000000       /* packet is full                        */
6362306a36Sopenharmony_ci#define PACKET_IO_FAST       0x40000000       /* 3 bits per gameport read              */
6462306a36Sopenharmony_ci#define PACKET_IO_SLOW       0x20000000       /* 1 bit per gameport read               */
6562306a36Sopenharmony_ci#define PACKET_MP_MORE       0x04000000       /* multiport wants to send more          */
6662306a36Sopenharmony_ci#define PACKET_MP_DONE       0x02000000       /* multiport done sending                */
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/*
6962306a36Sopenharmony_ci * Packet status code interpretation
7062306a36Sopenharmony_ci */
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci#define IO_GOT_PACKET        0x0100           /* Got a packet                           */
7362306a36Sopenharmony_ci#define IO_MODE_FAST         0x0200           /* Used 3 data bits per gameport read     */
7462306a36Sopenharmony_ci#define IO_SLOT_CHANGE       0x0800           /* Multiport physical slot status changed */
7562306a36Sopenharmony_ci#define IO_DONE              0x1000           /* Multiport is done sending packets      */
7662306a36Sopenharmony_ci#define IO_RETRY             0x4000           /* Try again later to get packet          */
7762306a36Sopenharmony_ci#define IO_RESET             0x8000           /* Force multiport to resend all packets  */
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci/*
8062306a36Sopenharmony_ci * Gamepad configuration data.  Other 9-pin digital joystick devices
8162306a36Sopenharmony_ci * may work with the multiport, so this may not be an exhaustive list!
8262306a36Sopenharmony_ci * Commodore 64 joystick remains untested.
8362306a36Sopenharmony_ci */
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci#define GRIP_INIT_DELAY         2000          /*  2 ms */
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci#define GRIP_MODE_NONE		0
8862306a36Sopenharmony_ci#define GRIP_MODE_RESET         1
8962306a36Sopenharmony_ci#define GRIP_MODE_GP		2
9062306a36Sopenharmony_ci#define GRIP_MODE_C64		3
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic const int grip_btn_gp[]  = { BTN_TR, BTN_TL, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, -1 };
9362306a36Sopenharmony_cistatic const int grip_btn_c64[] = { BTN_JOYSTICK, -1 };
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic const int grip_abs_gp[]  = { ABS_X, ABS_Y, -1 };
9662306a36Sopenharmony_cistatic const int grip_abs_c64[] = { ABS_X, ABS_Y, -1 };
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic const int *grip_abs[] = { NULL, NULL, grip_abs_gp, grip_abs_c64 };
9962306a36Sopenharmony_cistatic const int *grip_btn[] = { NULL, NULL, grip_btn_gp, grip_btn_c64 };
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic const char *grip_name[] = { NULL, NULL, "Gravis Grip Pad", "Commodore 64 Joystick" };
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic const int init_seq[] = {
10462306a36Sopenharmony_ci	1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1,
10562306a36Sopenharmony_ci	1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
10662306a36Sopenharmony_ci	1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1,
10762306a36Sopenharmony_ci	0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1 };
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci/* Maps multiport directional values to X,Y axis values (each axis encoded in 3 bits) */
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic const int axis_map[] = { 5, 9, 1, 5, 6, 10, 2, 6, 4, 8, 0, 4, 5, 9, 1, 5 };
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic int register_slot(int i, struct grip_mp *grip);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/*
11662306a36Sopenharmony_ci * Returns whether an odd or even number of bits are on in pkt.
11762306a36Sopenharmony_ci */
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic int bit_parity(u32 pkt)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	int x = pkt ^ (pkt >> 16);
12262306a36Sopenharmony_ci	x ^= x >> 8;
12362306a36Sopenharmony_ci	x ^= x >> 4;
12462306a36Sopenharmony_ci	x ^= x >> 2;
12562306a36Sopenharmony_ci	x ^= x >> 1;
12662306a36Sopenharmony_ci	return x & 1;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci/*
13062306a36Sopenharmony_ci * Poll gameport; return true if all bits set in 'onbits' are on and
13162306a36Sopenharmony_ci * all bits set in 'offbits' are off.
13262306a36Sopenharmony_ci */
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic inline int poll_until(u8 onbits, u8 offbits, int u_sec, struct gameport* gp, u8 *data)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	int i, nloops;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	nloops = gameport_time(gp, u_sec);
13962306a36Sopenharmony_ci	for (i = 0; i < nloops; i++) {
14062306a36Sopenharmony_ci		*data = gameport_read(gp);
14162306a36Sopenharmony_ci		if ((*data & onbits) == onbits &&
14262306a36Sopenharmony_ci		    (~(*data) & offbits) == offbits)
14362306a36Sopenharmony_ci			return 1;
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci	dbg("gameport timed out after %d microseconds.\n", u_sec);
14662306a36Sopenharmony_ci	return 0;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci/*
15062306a36Sopenharmony_ci * Gets a 28-bit packet from the multiport.
15162306a36Sopenharmony_ci *
15262306a36Sopenharmony_ci * After getting a packet successfully, commands encoded by sendcode may
15362306a36Sopenharmony_ci * be sent to the multiport.
15462306a36Sopenharmony_ci *
15562306a36Sopenharmony_ci * The multiport clock value is reflected in gameport bit B4.
15662306a36Sopenharmony_ci *
15762306a36Sopenharmony_ci * Returns a packet status code indicating whether packet is valid, the transfer
15862306a36Sopenharmony_ci * mode, and any error conditions.
15962306a36Sopenharmony_ci *
16062306a36Sopenharmony_ci * sendflags:      current I/O status
16162306a36Sopenharmony_ci * sendcode:   data to send to the multiport if sendflags is nonzero
16262306a36Sopenharmony_ci */
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic int mp_io(struct gameport* gameport, int sendflags, int sendcode, u32 *packet)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	u8  raw_data;            /* raw data from gameport */
16762306a36Sopenharmony_ci	u8  data_mask;           /* packet data bits from raw_data */
16862306a36Sopenharmony_ci	u32 pkt;                 /* packet temporary storage */
16962306a36Sopenharmony_ci	int bits_per_read;       /* num packet bits per gameport read */
17062306a36Sopenharmony_ci	int portvals = 0;        /* used for port value sanity check */
17162306a36Sopenharmony_ci	int i;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/* Gameport bits B0, B4, B5 should first be off, then B4 should come on. */
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	*packet = 0;
17662306a36Sopenharmony_ci	raw_data = gameport_read(gameport);
17762306a36Sopenharmony_ci	if (raw_data & 1)
17862306a36Sopenharmony_ci		return IO_RETRY;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	for (i = 0; i < 64; i++) {
18162306a36Sopenharmony_ci		raw_data = gameport_read(gameport);
18262306a36Sopenharmony_ci		portvals |= 1 << ((raw_data >> 4) & 3); /* Demux B4, B5 */
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	if (portvals == 1) {                            /* B4, B5 off */
18662306a36Sopenharmony_ci		raw_data = gameport_read(gameport);
18762306a36Sopenharmony_ci		portvals = raw_data & 0xf0;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci		if (raw_data & 0x31)
19062306a36Sopenharmony_ci			return IO_RESET;
19162306a36Sopenharmony_ci		gameport_trigger(gameport);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci		if (!poll_until(0x10, 0, 308, gameport, &raw_data))
19462306a36Sopenharmony_ci			return IO_RESET;
19562306a36Sopenharmony_ci	} else
19662306a36Sopenharmony_ci		return IO_RETRY;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	/* Determine packet transfer mode and prepare for packet construction. */
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	if (raw_data & 0x20) {                 /* 3 data bits/read */
20162306a36Sopenharmony_ci		portvals |= raw_data >> 4;     /* Compare B4-B7 before & after trigger */
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci		if (portvals != 0xb)
20462306a36Sopenharmony_ci			return 0;
20562306a36Sopenharmony_ci		data_mask = 7;
20662306a36Sopenharmony_ci		bits_per_read = 3;
20762306a36Sopenharmony_ci		pkt = (PACKET_FULL | PACKET_IO_FAST) >> 28;
20862306a36Sopenharmony_ci	} else {                                 /* 1 data bit/read */
20962306a36Sopenharmony_ci		data_mask = 1;
21062306a36Sopenharmony_ci		bits_per_read = 1;
21162306a36Sopenharmony_ci		pkt = (PACKET_FULL | PACKET_IO_SLOW) >> 28;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	/* Construct a packet.  Final data bits must be zero. */
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	while (1) {
21762306a36Sopenharmony_ci		if (!poll_until(0, 0x10, 77, gameport, &raw_data))
21862306a36Sopenharmony_ci			return IO_RESET;
21962306a36Sopenharmony_ci		raw_data = (raw_data >> 5) & data_mask;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci		if (pkt & PACKET_FULL)
22262306a36Sopenharmony_ci			break;
22362306a36Sopenharmony_ci		pkt = (pkt << bits_per_read) | raw_data;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci		if (!poll_until(0x10, 0, 77, gameport, &raw_data))
22662306a36Sopenharmony_ci			return IO_RESET;
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	if (raw_data)
23062306a36Sopenharmony_ci		return IO_RESET;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	/* If 3 bits/read used, drop from 30 bits to 28. */
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (bits_per_read == 3) {
23562306a36Sopenharmony_ci		pkt = (pkt & 0xffff0000) | ((pkt << 1) & 0xffff);
23662306a36Sopenharmony_ci		pkt = (pkt >> 2) | 0xf0000000;
23762306a36Sopenharmony_ci	}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	if (bit_parity(pkt) == 1)
24062306a36Sopenharmony_ci		return IO_RESET;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	/* Acknowledge packet receipt */
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	if (!poll_until(0x30, 0, 77, gameport, &raw_data))
24562306a36Sopenharmony_ci		return IO_RESET;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	raw_data = gameport_read(gameport);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	if (raw_data & 1)
25062306a36Sopenharmony_ci		return IO_RESET;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	gameport_trigger(gameport);
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	if (!poll_until(0, 0x20, 77, gameport, &raw_data))
25562306a36Sopenharmony_ci		return IO_RESET;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci        /* Return if we just wanted the packet or multiport wants to send more */
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	*packet = pkt;
26062306a36Sopenharmony_ci	if ((sendflags == 0) || ((sendflags & IO_RETRY) && !(pkt & PACKET_MP_DONE)))
26162306a36Sopenharmony_ci		return IO_GOT_PACKET;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	if (pkt & PACKET_MP_MORE)
26462306a36Sopenharmony_ci		return IO_GOT_PACKET | IO_RETRY;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	/* Multiport is done sending packets and is ready to receive data */
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (!poll_until(0x20, 0, 77, gameport, &raw_data))
26962306a36Sopenharmony_ci		return IO_GOT_PACKET | IO_RESET;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	raw_data = gameport_read(gameport);
27262306a36Sopenharmony_ci	if (raw_data & 1)
27362306a36Sopenharmony_ci		return IO_GOT_PACKET | IO_RESET;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	/* Trigger gameport based on bits in sendcode */
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	gameport_trigger(gameport);
27862306a36Sopenharmony_ci	do {
27962306a36Sopenharmony_ci		if (!poll_until(0x20, 0x10, 116, gameport, &raw_data))
28062306a36Sopenharmony_ci			return IO_GOT_PACKET | IO_RESET;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci		if (!poll_until(0x30, 0, 193, gameport, &raw_data))
28362306a36Sopenharmony_ci			return IO_GOT_PACKET | IO_RESET;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci		if (raw_data & 1)
28662306a36Sopenharmony_ci			return IO_GOT_PACKET | IO_RESET;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci		if (sendcode & 1)
28962306a36Sopenharmony_ci			gameport_trigger(gameport);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci		sendcode >>= 1;
29262306a36Sopenharmony_ci	} while (sendcode);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	return IO_GOT_PACKET | IO_MODE_FAST;
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci/*
29862306a36Sopenharmony_ci * Disables and restores interrupts for mp_io(), which does the actual I/O.
29962306a36Sopenharmony_ci */
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_cistatic int multiport_io(struct gameport* gameport, int sendflags, int sendcode, u32 *packet)
30262306a36Sopenharmony_ci{
30362306a36Sopenharmony_ci	int status;
30462306a36Sopenharmony_ci	unsigned long flags;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	local_irq_save(flags);
30762306a36Sopenharmony_ci	status = mp_io(gameport, sendflags, sendcode, packet);
30862306a36Sopenharmony_ci	local_irq_restore(flags);
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	return status;
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci/*
31462306a36Sopenharmony_ci * Puts multiport into digital mode.  Multiport LED turns green.
31562306a36Sopenharmony_ci *
31662306a36Sopenharmony_ci * Returns true if a valid digital packet was received, false otherwise.
31762306a36Sopenharmony_ci */
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_cistatic int dig_mode_start(struct gameport *gameport, u32 *packet)
32062306a36Sopenharmony_ci{
32162306a36Sopenharmony_ci	int i;
32262306a36Sopenharmony_ci	int flags, tries = 0, bads = 0;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {     /* Send magic sequence */
32562306a36Sopenharmony_ci		if (init_seq[i])
32662306a36Sopenharmony_ci			gameport_trigger(gameport);
32762306a36Sopenharmony_ci		udelay(GRIP_INIT_DELAY);
32862306a36Sopenharmony_ci	}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	for (i = 0; i < 16; i++)            /* Wait for multiport to settle */
33162306a36Sopenharmony_ci		udelay(GRIP_INIT_DELAY);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	while (tries < 64 && bads < 8) {    /* Reset multiport and try getting a packet */
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci		flags = multiport_io(gameport, IO_RESET, 0x27, packet);
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci		if (flags & IO_MODE_FAST)
33862306a36Sopenharmony_ci			return 1;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci		if (flags & IO_RETRY)
34162306a36Sopenharmony_ci			tries++;
34262306a36Sopenharmony_ci		else
34362306a36Sopenharmony_ci			bads++;
34462306a36Sopenharmony_ci	}
34562306a36Sopenharmony_ci	return 0;
34662306a36Sopenharmony_ci}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci/*
34962306a36Sopenharmony_ci * Packet structure: B0-B15   => gamepad state
35062306a36Sopenharmony_ci *                   B16-B20  => gamepad device type
35162306a36Sopenharmony_ci *                   B21-B24  => multiport slot index (1-4)
35262306a36Sopenharmony_ci *
35362306a36Sopenharmony_ci * Known device types: 0x1f (grip pad), 0x0 (no device).  Others may exist.
35462306a36Sopenharmony_ci *
35562306a36Sopenharmony_ci * Returns the packet status.
35662306a36Sopenharmony_ci */
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_cistatic int get_and_decode_packet(struct grip_mp *grip, int flags)
35962306a36Sopenharmony_ci{
36062306a36Sopenharmony_ci	struct grip_port *port;
36162306a36Sopenharmony_ci	u32 packet;
36262306a36Sopenharmony_ci	int joytype = 0;
36362306a36Sopenharmony_ci	int slot;
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	/* Get a packet and check for validity */
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	flags &= IO_RESET | IO_RETRY;
36862306a36Sopenharmony_ci	flags = multiport_io(grip->gameport, flags, 0, &packet);
36962306a36Sopenharmony_ci	grip->reads++;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	if (packet & PACKET_MP_DONE)
37262306a36Sopenharmony_ci		flags |= IO_DONE;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	if (flags && !(flags & IO_GOT_PACKET)) {
37562306a36Sopenharmony_ci		grip->bads++;
37662306a36Sopenharmony_ci		return flags;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	/* Ignore non-gamepad packets, e.g. multiport hardware version */
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	slot = ((packet >> 21) & 0xf) - 1;
38262306a36Sopenharmony_ci	if ((slot < 0) || (slot > 3))
38362306a36Sopenharmony_ci		return flags;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	port = grip->port[slot];
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	/*
38862306a36Sopenharmony_ci	 * Handle "reset" packets, which occur at startup, and when gamepads
38962306a36Sopenharmony_ci	 * are removed or plugged in.  May contain configuration of a new gamepad.
39062306a36Sopenharmony_ci	 */
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	joytype = (packet >> 16) & 0x1f;
39362306a36Sopenharmony_ci	if (!joytype) {
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci		if (port->registered) {
39662306a36Sopenharmony_ci			printk(KERN_INFO "grip_mp: removing %s, slot %d\n",
39762306a36Sopenharmony_ci			       grip_name[port->mode], slot);
39862306a36Sopenharmony_ci			input_unregister_device(port->dev);
39962306a36Sopenharmony_ci			port->registered = 0;
40062306a36Sopenharmony_ci		}
40162306a36Sopenharmony_ci		dbg("Reset: grip multiport slot %d\n", slot);
40262306a36Sopenharmony_ci		port->mode = GRIP_MODE_RESET;
40362306a36Sopenharmony_ci		flags |= IO_SLOT_CHANGE;
40462306a36Sopenharmony_ci		return flags;
40562306a36Sopenharmony_ci	}
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	/* Interpret a grip pad packet */
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (joytype == 0x1f) {
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci		int dir = (packet >> 8) & 0xf;          /* eight way directional value */
41262306a36Sopenharmony_ci		port->buttons = (~packet) & 0xff;
41362306a36Sopenharmony_ci		port->yaxes = ((axis_map[dir] >> 2) & 3) - 1;
41462306a36Sopenharmony_ci		port->xaxes = (axis_map[dir] & 3) - 1;
41562306a36Sopenharmony_ci		port->dirty = 1;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci		if (port->mode == GRIP_MODE_RESET)
41862306a36Sopenharmony_ci			flags |= IO_SLOT_CHANGE;
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci		port->mode = GRIP_MODE_GP;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci		if (!port->registered) {
42362306a36Sopenharmony_ci			dbg("New Grip pad in multiport slot %d.\n", slot);
42462306a36Sopenharmony_ci			if (register_slot(slot, grip)) {
42562306a36Sopenharmony_ci				port->mode = GRIP_MODE_RESET;
42662306a36Sopenharmony_ci				port->dirty = 0;
42762306a36Sopenharmony_ci			}
42862306a36Sopenharmony_ci		}
42962306a36Sopenharmony_ci		return flags;
43062306a36Sopenharmony_ci	}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	/* Handle non-grip device codes.  For now, just print diagnostics. */
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	{
43562306a36Sopenharmony_ci		static int strange_code = 0;
43662306a36Sopenharmony_ci		if (strange_code != joytype) {
43762306a36Sopenharmony_ci			printk(KERN_INFO "Possible non-grip pad/joystick detected.\n");
43862306a36Sopenharmony_ci			printk(KERN_INFO "Got joy type 0x%x and packet 0x%x.\n", joytype, packet);
43962306a36Sopenharmony_ci			strange_code = joytype;
44062306a36Sopenharmony_ci		}
44162306a36Sopenharmony_ci	}
44262306a36Sopenharmony_ci	return flags;
44362306a36Sopenharmony_ci}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci/*
44662306a36Sopenharmony_ci * Returns true if all multiport slot states appear valid.
44762306a36Sopenharmony_ci */
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_cistatic int slots_valid(struct grip_mp *grip)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	int flags, slot, invalid = 0, active = 0;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	flags = get_and_decode_packet(grip, 0);
45462306a36Sopenharmony_ci	if (!(flags & IO_GOT_PACKET))
45562306a36Sopenharmony_ci		return 0;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	for (slot = 0; slot < 4; slot++) {
45862306a36Sopenharmony_ci		if (grip->port[slot]->mode == GRIP_MODE_RESET)
45962306a36Sopenharmony_ci			invalid = 1;
46062306a36Sopenharmony_ci		if (grip->port[slot]->mode != GRIP_MODE_NONE)
46162306a36Sopenharmony_ci			active = 1;
46262306a36Sopenharmony_ci	}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	/* Return true if no active slot but multiport sent all its data */
46562306a36Sopenharmony_ci	if (!active)
46662306a36Sopenharmony_ci		return (flags & IO_DONE) ? 1 : 0;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	/* Return false if invalid device code received */
46962306a36Sopenharmony_ci	return invalid ? 0 : 1;
47062306a36Sopenharmony_ci}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci/*
47362306a36Sopenharmony_ci * Returns whether the multiport was placed into digital mode and
47462306a36Sopenharmony_ci * able to communicate its state successfully.
47562306a36Sopenharmony_ci */
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_cistatic int multiport_init(struct grip_mp *grip)
47862306a36Sopenharmony_ci{
47962306a36Sopenharmony_ci	int dig_mode, initialized = 0, tries = 0;
48062306a36Sopenharmony_ci	u32 packet;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	dig_mode = dig_mode_start(grip->gameport, &packet);
48362306a36Sopenharmony_ci	while (!dig_mode && tries < 4) {
48462306a36Sopenharmony_ci		dig_mode = dig_mode_start(grip->gameport, &packet);
48562306a36Sopenharmony_ci		tries++;
48662306a36Sopenharmony_ci	}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	if (dig_mode)
48962306a36Sopenharmony_ci		dbg("multiport_init(): digital mode activated.\n");
49062306a36Sopenharmony_ci	else {
49162306a36Sopenharmony_ci		dbg("multiport_init(): unable to activate digital mode.\n");
49262306a36Sopenharmony_ci		return 0;
49362306a36Sopenharmony_ci	}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	/* Get packets, store multiport state, and check state's validity */
49662306a36Sopenharmony_ci	for (tries = 0; tries < 4096; tries++) {
49762306a36Sopenharmony_ci		if (slots_valid(grip)) {
49862306a36Sopenharmony_ci			initialized = 1;
49962306a36Sopenharmony_ci			break;
50062306a36Sopenharmony_ci		}
50162306a36Sopenharmony_ci	}
50262306a36Sopenharmony_ci	dbg("multiport_init(): initialized == %d\n", initialized);
50362306a36Sopenharmony_ci	return initialized;
50462306a36Sopenharmony_ci}
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci/*
50762306a36Sopenharmony_ci * Reports joystick state to the linux input layer.
50862306a36Sopenharmony_ci */
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_cistatic void report_slot(struct grip_mp *grip, int slot)
51162306a36Sopenharmony_ci{
51262306a36Sopenharmony_ci	struct grip_port *port = grip->port[slot];
51362306a36Sopenharmony_ci	int i;
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	/* Store button states with linux input driver */
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	for (i = 0; i < 8; i++)
51862306a36Sopenharmony_ci		input_report_key(port->dev, grip_btn_gp[i], (port->buttons >> i) & 1);
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	/* Store axis states with linux driver */
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	input_report_abs(port->dev, ABS_X, port->xaxes);
52362306a36Sopenharmony_ci	input_report_abs(port->dev, ABS_Y, port->yaxes);
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	/* Tell the receiver of the events to process them */
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	input_sync(port->dev);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	port->dirty = 0;
53062306a36Sopenharmony_ci}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci/*
53362306a36Sopenharmony_ci * Get the multiport state.
53462306a36Sopenharmony_ci */
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_cistatic void grip_poll(struct gameport *gameport)
53762306a36Sopenharmony_ci{
53862306a36Sopenharmony_ci	struct grip_mp *grip = gameport_get_drvdata(gameport);
53962306a36Sopenharmony_ci	int i, npkts, flags;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	for (npkts = 0; npkts < 4; npkts++) {
54262306a36Sopenharmony_ci		flags = IO_RETRY;
54362306a36Sopenharmony_ci		for (i = 0; i < 32; i++) {
54462306a36Sopenharmony_ci			flags = get_and_decode_packet(grip, flags);
54562306a36Sopenharmony_ci			if ((flags & IO_GOT_PACKET) || !(flags & IO_RETRY))
54662306a36Sopenharmony_ci				break;
54762306a36Sopenharmony_ci		}
54862306a36Sopenharmony_ci		if (flags & IO_DONE)
54962306a36Sopenharmony_ci			break;
55062306a36Sopenharmony_ci	}
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	for (i = 0; i < 4; i++)
55362306a36Sopenharmony_ci		if (grip->port[i]->dirty)
55462306a36Sopenharmony_ci			report_slot(grip, i);
55562306a36Sopenharmony_ci}
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci/*
55862306a36Sopenharmony_ci * Called when a joystick device file is opened
55962306a36Sopenharmony_ci */
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_cistatic int grip_open(struct input_dev *dev)
56262306a36Sopenharmony_ci{
56362306a36Sopenharmony_ci	struct grip_mp *grip = input_get_drvdata(dev);
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_ci	gameport_start_polling(grip->gameport);
56662306a36Sopenharmony_ci	return 0;
56762306a36Sopenharmony_ci}
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_ci/*
57062306a36Sopenharmony_ci * Called when a joystick device file is closed
57162306a36Sopenharmony_ci */
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_cistatic void grip_close(struct input_dev *dev)
57462306a36Sopenharmony_ci{
57562306a36Sopenharmony_ci	struct grip_mp *grip = input_get_drvdata(dev);
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	gameport_stop_polling(grip->gameport);
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci/*
58162306a36Sopenharmony_ci * Tell the linux input layer about a newly plugged-in gamepad.
58262306a36Sopenharmony_ci */
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_cistatic int register_slot(int slot, struct grip_mp *grip)
58562306a36Sopenharmony_ci{
58662306a36Sopenharmony_ci	struct grip_port *port = grip->port[slot];
58762306a36Sopenharmony_ci	struct input_dev *input_dev;
58862306a36Sopenharmony_ci	int j, t;
58962306a36Sopenharmony_ci	int err;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	port->dev = input_dev = input_allocate_device();
59262306a36Sopenharmony_ci	if (!input_dev)
59362306a36Sopenharmony_ci		return -ENOMEM;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	input_dev->name = grip_name[port->mode];
59662306a36Sopenharmony_ci	input_dev->id.bustype = BUS_GAMEPORT;
59762306a36Sopenharmony_ci	input_dev->id.vendor = GAMEPORT_ID_VENDOR_GRAVIS;
59862306a36Sopenharmony_ci	input_dev->id.product = 0x0100 + port->mode;
59962306a36Sopenharmony_ci	input_dev->id.version = 0x0100;
60062306a36Sopenharmony_ci	input_dev->dev.parent = &grip->gameport->dev;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	input_set_drvdata(input_dev, grip);
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	input_dev->open = grip_open;
60562306a36Sopenharmony_ci	input_dev->close = grip_close;
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	for (j = 0; (t = grip_abs[port->mode][j]) >= 0; j++)
61062306a36Sopenharmony_ci		input_set_abs_params(input_dev, t, -1, 1, 0, 0);
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	for (j = 0; (t = grip_btn[port->mode][j]) >= 0; j++)
61362306a36Sopenharmony_ci		if (t > 0)
61462306a36Sopenharmony_ci			set_bit(t, input_dev->keybit);
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	err = input_register_device(port->dev);
61762306a36Sopenharmony_ci	if (err) {
61862306a36Sopenharmony_ci		input_free_device(port->dev);
61962306a36Sopenharmony_ci		return err;
62062306a36Sopenharmony_ci	}
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	port->registered = 1;
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	if (port->dirty)	            /* report initial state, if any */
62562306a36Sopenharmony_ci		report_slot(grip, slot);
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	return 0;
62862306a36Sopenharmony_ci}
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_cistatic int grip_connect(struct gameport *gameport, struct gameport_driver *drv)
63162306a36Sopenharmony_ci{
63262306a36Sopenharmony_ci	struct grip_mp *grip;
63362306a36Sopenharmony_ci	int err;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	if (!(grip = kzalloc(sizeof(struct grip_mp), GFP_KERNEL)))
63662306a36Sopenharmony_ci		return -ENOMEM;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	grip->gameport = gameport;
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci	gameport_set_drvdata(gameport, grip);
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW);
64362306a36Sopenharmony_ci	if (err)
64462306a36Sopenharmony_ci		goto fail1;
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	gameport_set_poll_handler(gameport, grip_poll);
64762306a36Sopenharmony_ci	gameport_set_poll_interval(gameport, 20);
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci	if (!multiport_init(grip)) {
65062306a36Sopenharmony_ci		err = -ENODEV;
65162306a36Sopenharmony_ci		goto fail2;
65262306a36Sopenharmony_ci	}
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	if (!grip->port[0]->mode && !grip->port[1]->mode && !grip->port[2]->mode && !grip->port[3]->mode) {
65562306a36Sopenharmony_ci		/* nothing plugged in */
65662306a36Sopenharmony_ci		err = -ENODEV;
65762306a36Sopenharmony_ci		goto fail2;
65862306a36Sopenharmony_ci	}
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	return 0;
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_cifail2:	gameport_close(gameport);
66362306a36Sopenharmony_cifail1:	gameport_set_drvdata(gameport, NULL);
66462306a36Sopenharmony_ci	kfree(grip);
66562306a36Sopenharmony_ci	return err;
66662306a36Sopenharmony_ci}
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_cistatic void grip_disconnect(struct gameport *gameport)
66962306a36Sopenharmony_ci{
67062306a36Sopenharmony_ci	struct grip_mp *grip = gameport_get_drvdata(gameport);
67162306a36Sopenharmony_ci	int i;
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	for (i = 0; i < 4; i++)
67462306a36Sopenharmony_ci		if (grip->port[i]->registered)
67562306a36Sopenharmony_ci			input_unregister_device(grip->port[i]->dev);
67662306a36Sopenharmony_ci	gameport_close(gameport);
67762306a36Sopenharmony_ci	gameport_set_drvdata(gameport, NULL);
67862306a36Sopenharmony_ci	kfree(grip);
67962306a36Sopenharmony_ci}
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_cistatic struct gameport_driver grip_drv = {
68262306a36Sopenharmony_ci	.driver		= {
68362306a36Sopenharmony_ci		.name	= "grip_mp",
68462306a36Sopenharmony_ci	},
68562306a36Sopenharmony_ci	.description	= DRIVER_DESC,
68662306a36Sopenharmony_ci	.connect	= grip_connect,
68762306a36Sopenharmony_ci	.disconnect	= grip_disconnect,
68862306a36Sopenharmony_ci};
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_cimodule_gameport_driver(grip_drv);
691