18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Copyright (c) 1998-2001 Vojtech Pavlik
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *   Based on the work of:
68c2ecf20Sopenharmony_ci *	Trystan Larey-Williams
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci/*
108c2ecf20Sopenharmony_ci * ThrustMaster DirectConnect (BSP) joystick family driver for Linux
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci/*
148c2ecf20Sopenharmony_ci */
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <linux/delay.h>
178c2ecf20Sopenharmony_ci#include <linux/kernel.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <linux/module.h>
208c2ecf20Sopenharmony_ci#include <linux/gameport.h>
218c2ecf20Sopenharmony_ci#include <linux/input.h>
228c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define DRIVER_DESC	"ThrustMaster DirectConnect joystick driver"
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
278c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
288c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define TMDC_MAX_START		600	/* 600 us */
318c2ecf20Sopenharmony_ci#define TMDC_MAX_STROBE		60	/* 60 us */
328c2ecf20Sopenharmony_ci#define TMDC_MAX_LENGTH		13
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#define TMDC_MODE_M3DI		1
358c2ecf20Sopenharmony_ci#define TMDC_MODE_3DRP		3
368c2ecf20Sopenharmony_ci#define TMDC_MODE_AT		4
378c2ecf20Sopenharmony_ci#define TMDC_MODE_FM		8
388c2ecf20Sopenharmony_ci#define TMDC_MODE_FGP		163
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define TMDC_BYTE_ID		10
418c2ecf20Sopenharmony_ci#define TMDC_BYTE_REV		11
428c2ecf20Sopenharmony_ci#define TMDC_BYTE_DEF		12
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#define TMDC_ABS		7
458c2ecf20Sopenharmony_ci#define TMDC_ABS_HAT		4
468c2ecf20Sopenharmony_ci#define TMDC_BTN		16
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic const unsigned char tmdc_byte_a[16] = { 0, 1, 3, 4, 6, 7 };
498c2ecf20Sopenharmony_cistatic const unsigned char tmdc_byte_d[16] = { 2, 5, 8, 9 };
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic const signed char tmdc_abs[TMDC_ABS] =
528c2ecf20Sopenharmony_ci	{ ABS_X, ABS_Y, ABS_RUDDER, ABS_THROTTLE, ABS_RX, ABS_RY, ABS_RZ };
538c2ecf20Sopenharmony_cistatic const signed char tmdc_abs_hat[TMDC_ABS_HAT] =
548c2ecf20Sopenharmony_ci	{ ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y };
558c2ecf20Sopenharmony_cistatic const signed char tmdc_abs_at[TMDC_ABS] =
568c2ecf20Sopenharmony_ci	{ ABS_X, ABS_Y, ABS_RUDDER, -1, ABS_THROTTLE };
578c2ecf20Sopenharmony_cistatic const signed char tmdc_abs_fm[TMDC_ABS] =
588c2ecf20Sopenharmony_ci	{ ABS_RX, ABS_RY, ABS_X, ABS_Y };
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic const short tmdc_btn_pad[TMDC_BTN] =
618c2ecf20Sopenharmony_ci	{ BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_START, BTN_SELECT, BTN_TL, BTN_TR };
628c2ecf20Sopenharmony_cistatic const short tmdc_btn_joy[TMDC_BTN] =
638c2ecf20Sopenharmony_ci	{ BTN_TRIGGER, BTN_THUMB, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_THUMB2, BTN_PINKIE,
648c2ecf20Sopenharmony_ci	  BTN_BASE3, BTN_BASE4, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z };
658c2ecf20Sopenharmony_cistatic const short tmdc_btn_fm[TMDC_BTN] =
668c2ecf20Sopenharmony_ci        { BTN_TRIGGER, BTN_C, BTN_B, BTN_A, BTN_THUMB, BTN_X, BTN_Y, BTN_Z, BTN_TOP, BTN_TOP2 };
678c2ecf20Sopenharmony_cistatic const short tmdc_btn_at[TMDC_BTN] =
688c2ecf20Sopenharmony_ci        { BTN_TRIGGER, BTN_THUMB2, BTN_PINKIE, BTN_THUMB, BTN_BASE6, BTN_BASE5, BTN_BASE4,
698c2ecf20Sopenharmony_ci          BTN_BASE3, BTN_BASE2, BTN_BASE };
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistatic const struct {
728c2ecf20Sopenharmony_ci        int x;
738c2ecf20Sopenharmony_ci        int y;
748c2ecf20Sopenharmony_ci} tmdc_hat_to_axis[] = {{ 0, 0}, { 1, 0}, { 0,-1}, {-1, 0}, { 0, 1}};
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic const struct tmdc_model {
778c2ecf20Sopenharmony_ci	unsigned char id;
788c2ecf20Sopenharmony_ci	const char *name;
798c2ecf20Sopenharmony_ci	char abs;
808c2ecf20Sopenharmony_ci	char hats;
818c2ecf20Sopenharmony_ci	char btnc[4];
828c2ecf20Sopenharmony_ci	char btno[4];
838c2ecf20Sopenharmony_ci	const signed char *axes;
848c2ecf20Sopenharmony_ci	const short *buttons;
858c2ecf20Sopenharmony_ci} tmdc_models[] = {
868c2ecf20Sopenharmony_ci	{   1, "ThrustMaster Millenium 3D Inceptor",	  6, 2, { 4, 2 }, { 4, 6 }, tmdc_abs, tmdc_btn_joy },
878c2ecf20Sopenharmony_ci	{   3, "ThrustMaster Rage 3D Gamepad",		  2, 0, { 8, 2 }, { 0, 0 }, tmdc_abs, tmdc_btn_pad },
888c2ecf20Sopenharmony_ci	{   4, "ThrustMaster Attack Throttle",		  5, 2, { 4, 6 }, { 4, 2 }, tmdc_abs_at, tmdc_btn_at },
898c2ecf20Sopenharmony_ci	{   8, "ThrustMaster FragMaster",		  4, 0, { 8, 2 }, { 0, 0 }, tmdc_abs_fm, tmdc_btn_fm },
908c2ecf20Sopenharmony_ci	{ 163, "Thrustmaster Fusion GamePad",		  2, 0, { 8, 2 }, { 0, 0 }, tmdc_abs, tmdc_btn_pad },
918c2ecf20Sopenharmony_ci	{   0, "Unknown %d-axis, %d-button TM device %d", 0, 0, { 0, 0 }, { 0, 0 }, tmdc_abs, tmdc_btn_joy }
928c2ecf20Sopenharmony_ci};
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistruct tmdc_port {
968c2ecf20Sopenharmony_ci	struct input_dev *dev;
978c2ecf20Sopenharmony_ci	char name[64];
988c2ecf20Sopenharmony_ci	char phys[32];
998c2ecf20Sopenharmony_ci	int mode;
1008c2ecf20Sopenharmony_ci	const signed char *abs;
1018c2ecf20Sopenharmony_ci	const short *btn;
1028c2ecf20Sopenharmony_ci	unsigned char absc;
1038c2ecf20Sopenharmony_ci	unsigned char btnc[4];
1048c2ecf20Sopenharmony_ci	unsigned char btno[4];
1058c2ecf20Sopenharmony_ci};
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_cistruct tmdc {
1088c2ecf20Sopenharmony_ci	struct gameport *gameport;
1098c2ecf20Sopenharmony_ci	struct tmdc_port *port[2];
1108c2ecf20Sopenharmony_ci#if 0
1118c2ecf20Sopenharmony_ci	struct input_dev *dev[2];
1128c2ecf20Sopenharmony_ci	char name[2][64];
1138c2ecf20Sopenharmony_ci	char phys[2][32];
1148c2ecf20Sopenharmony_ci	int mode[2];
1158c2ecf20Sopenharmony_ci	signed char *abs[2];
1168c2ecf20Sopenharmony_ci	short *btn[2];
1178c2ecf20Sopenharmony_ci	unsigned char absc[2];
1188c2ecf20Sopenharmony_ci	unsigned char btnc[2][4];
1198c2ecf20Sopenharmony_ci	unsigned char btno[2][4];
1208c2ecf20Sopenharmony_ci#endif
1218c2ecf20Sopenharmony_ci	int reads;
1228c2ecf20Sopenharmony_ci	int bads;
1238c2ecf20Sopenharmony_ci	unsigned char exists;
1248c2ecf20Sopenharmony_ci};
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci/*
1278c2ecf20Sopenharmony_ci * tmdc_read_packet() reads a ThrustMaster packet.
1288c2ecf20Sopenharmony_ci */
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic int tmdc_read_packet(struct gameport *gameport, unsigned char data[2][TMDC_MAX_LENGTH])
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	unsigned char u, v, w, x;
1338c2ecf20Sopenharmony_ci	unsigned long flags;
1348c2ecf20Sopenharmony_ci	int i[2], j[2], t[2], p, k;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	p = gameport_time(gameport, TMDC_MAX_STROBE);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	for (k = 0; k < 2; k++) {
1398c2ecf20Sopenharmony_ci		t[k] = gameport_time(gameport, TMDC_MAX_START);
1408c2ecf20Sopenharmony_ci		i[k] = j[k] = 0;
1418c2ecf20Sopenharmony_ci	}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	local_irq_save(flags);
1448c2ecf20Sopenharmony_ci	gameport_trigger(gameport);
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	w = gameport_read(gameport) >> 4;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	do {
1498c2ecf20Sopenharmony_ci		x = w;
1508c2ecf20Sopenharmony_ci		w = gameport_read(gameport) >> 4;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci		for (k = 0, v = w, u = x; k < 2; k++, v >>= 2, u >>= 2) {
1538c2ecf20Sopenharmony_ci			if (~v & u & 2) {
1548c2ecf20Sopenharmony_ci				if (t[k] <= 0 || i[k] >= TMDC_MAX_LENGTH) continue;
1558c2ecf20Sopenharmony_ci				t[k] = p;
1568c2ecf20Sopenharmony_ci				if (j[k] == 0) {				 /* Start bit */
1578c2ecf20Sopenharmony_ci					if (~v & 1) t[k] = 0;
1588c2ecf20Sopenharmony_ci					data[k][i[k]] = 0; j[k]++; continue;
1598c2ecf20Sopenharmony_ci				}
1608c2ecf20Sopenharmony_ci				if (j[k] == 9) {				/* Stop bit */
1618c2ecf20Sopenharmony_ci					if (v & 1) t[k] = 0;
1628c2ecf20Sopenharmony_ci					j[k] = 0; i[k]++; continue;
1638c2ecf20Sopenharmony_ci				}
1648c2ecf20Sopenharmony_ci				data[k][i[k]] |= (~v & 1) << (j[k]++ - 1);	/* Data bit */
1658c2ecf20Sopenharmony_ci			}
1668c2ecf20Sopenharmony_ci			t[k]--;
1678c2ecf20Sopenharmony_ci		}
1688c2ecf20Sopenharmony_ci	} while (t[0] > 0 || t[1] > 0);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	local_irq_restore(flags);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	return (i[0] == TMDC_MAX_LENGTH) | ((i[1] == TMDC_MAX_LENGTH) << 1);
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cistatic int tmdc_parse_packet(struct tmdc_port *port, unsigned char *data)
1768c2ecf20Sopenharmony_ci{
1778c2ecf20Sopenharmony_ci	int i, k, l;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	if (data[TMDC_BYTE_ID] != port->mode)
1808c2ecf20Sopenharmony_ci		return -1;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	for (i = 0; i < port->absc; i++) {
1838c2ecf20Sopenharmony_ci		if (port->abs[i] < 0)
1848c2ecf20Sopenharmony_ci			return 0;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci		input_report_abs(port->dev, port->abs[i], data[tmdc_byte_a[i]]);
1878c2ecf20Sopenharmony_ci	}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	switch (port->mode) {
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci		case TMDC_MODE_M3DI:
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci			i = tmdc_byte_d[0];
1948c2ecf20Sopenharmony_ci			input_report_abs(port->dev, ABS_HAT0X, ((data[i] >> 3) & 1) - ((data[i] >> 1) & 1));
1958c2ecf20Sopenharmony_ci			input_report_abs(port->dev, ABS_HAT0Y, ((data[i] >> 2) & 1) - ( data[i]       & 1));
1968c2ecf20Sopenharmony_ci			break;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		case TMDC_MODE_AT:
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci			i = tmdc_byte_a[3];
2018c2ecf20Sopenharmony_ci			input_report_abs(port->dev, ABS_HAT0X, tmdc_hat_to_axis[(data[i] - 141) / 25].x);
2028c2ecf20Sopenharmony_ci			input_report_abs(port->dev, ABS_HAT0Y, tmdc_hat_to_axis[(data[i] - 141) / 25].y);
2038c2ecf20Sopenharmony_ci			break;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	for (k = l = 0; k < 4; k++) {
2088c2ecf20Sopenharmony_ci		for (i = 0; i < port->btnc[k]; i++)
2098c2ecf20Sopenharmony_ci			input_report_key(port->dev, port->btn[i + l],
2108c2ecf20Sopenharmony_ci				((data[tmdc_byte_d[k]] >> (i + port->btno[k])) & 1));
2118c2ecf20Sopenharmony_ci		l += port->btnc[k];
2128c2ecf20Sopenharmony_ci	}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	input_sync(port->dev);
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	return 0;
2178c2ecf20Sopenharmony_ci}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci/*
2208c2ecf20Sopenharmony_ci * tmdc_poll() reads and analyzes ThrustMaster joystick data.
2218c2ecf20Sopenharmony_ci */
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_cistatic void tmdc_poll(struct gameport *gameport)
2248c2ecf20Sopenharmony_ci{
2258c2ecf20Sopenharmony_ci	unsigned char data[2][TMDC_MAX_LENGTH];
2268c2ecf20Sopenharmony_ci	struct tmdc *tmdc = gameport_get_drvdata(gameport);
2278c2ecf20Sopenharmony_ci	unsigned char r, bad = 0;
2288c2ecf20Sopenharmony_ci	int i;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	tmdc->reads++;
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	if ((r = tmdc_read_packet(tmdc->gameport, data)) != tmdc->exists)
2338c2ecf20Sopenharmony_ci		bad = 1;
2348c2ecf20Sopenharmony_ci	else {
2358c2ecf20Sopenharmony_ci		for (i = 0; i < 2; i++) {
2368c2ecf20Sopenharmony_ci			if (r & (1 << i) & tmdc->exists) {
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci				if (tmdc_parse_packet(tmdc->port[i], data[i]))
2398c2ecf20Sopenharmony_ci					bad = 1;
2408c2ecf20Sopenharmony_ci			}
2418c2ecf20Sopenharmony_ci		}
2428c2ecf20Sopenharmony_ci	}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	tmdc->bads += bad;
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_cistatic int tmdc_open(struct input_dev *dev)
2488c2ecf20Sopenharmony_ci{
2498c2ecf20Sopenharmony_ci	struct tmdc *tmdc = input_get_drvdata(dev);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	gameport_start_polling(tmdc->gameport);
2528c2ecf20Sopenharmony_ci	return 0;
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic void tmdc_close(struct input_dev *dev)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	struct tmdc *tmdc = input_get_drvdata(dev);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	gameport_stop_polling(tmdc->gameport);
2608c2ecf20Sopenharmony_ci}
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_cistatic int tmdc_setup_port(struct tmdc *tmdc, int idx, unsigned char *data)
2638c2ecf20Sopenharmony_ci{
2648c2ecf20Sopenharmony_ci	const struct tmdc_model *model;
2658c2ecf20Sopenharmony_ci	struct tmdc_port *port;
2668c2ecf20Sopenharmony_ci	struct input_dev *input_dev;
2678c2ecf20Sopenharmony_ci	int i, j, b = 0;
2688c2ecf20Sopenharmony_ci	int err;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	tmdc->port[idx] = port = kzalloc(sizeof (struct tmdc_port), GFP_KERNEL);
2718c2ecf20Sopenharmony_ci	input_dev = input_allocate_device();
2728c2ecf20Sopenharmony_ci	if (!port || !input_dev) {
2738c2ecf20Sopenharmony_ci		err = -ENOMEM;
2748c2ecf20Sopenharmony_ci		goto fail;
2758c2ecf20Sopenharmony_ci	}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	port->mode = data[TMDC_BYTE_ID];
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	for (model = tmdc_models; model->id && model->id != port->mode; model++)
2808c2ecf20Sopenharmony_ci		/* empty */;
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	port->abs = model->axes;
2838c2ecf20Sopenharmony_ci	port->btn = model->buttons;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	if (!model->id) {
2868c2ecf20Sopenharmony_ci		port->absc = data[TMDC_BYTE_DEF] >> 4;
2878c2ecf20Sopenharmony_ci		for (i = 0; i < 4; i++)
2888c2ecf20Sopenharmony_ci			port->btnc[i] = i < (data[TMDC_BYTE_DEF] & 0xf) ? 8 : 0;
2898c2ecf20Sopenharmony_ci	} else {
2908c2ecf20Sopenharmony_ci		port->absc = model->abs;
2918c2ecf20Sopenharmony_ci		for (i = 0; i < 4; i++)
2928c2ecf20Sopenharmony_ci			port->btnc[i] = model->btnc[i];
2938c2ecf20Sopenharmony_ci	}
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	for (i = 0; i < 4; i++)
2968c2ecf20Sopenharmony_ci		port->btno[i] = model->btno[i];
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	snprintf(port->name, sizeof(port->name), model->name,
2998c2ecf20Sopenharmony_ci		 port->absc, (data[TMDC_BYTE_DEF] & 0xf) << 3, port->mode);
3008c2ecf20Sopenharmony_ci	snprintf(port->phys, sizeof(port->phys), "%s/input%d", tmdc->gameport->phys, i);
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	port->dev = input_dev;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	input_dev->name = port->name;
3058c2ecf20Sopenharmony_ci	input_dev->phys = port->phys;
3068c2ecf20Sopenharmony_ci	input_dev->id.bustype = BUS_GAMEPORT;
3078c2ecf20Sopenharmony_ci	input_dev->id.vendor = GAMEPORT_ID_VENDOR_THRUSTMASTER;
3088c2ecf20Sopenharmony_ci	input_dev->id.product = model->id;
3098c2ecf20Sopenharmony_ci	input_dev->id.version = 0x0100;
3108c2ecf20Sopenharmony_ci	input_dev->dev.parent = &tmdc->gameport->dev;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	input_set_drvdata(input_dev, tmdc);
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	input_dev->open = tmdc_open;
3158c2ecf20Sopenharmony_ci	input_dev->close = tmdc_close;
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	for (i = 0; i < port->absc && i < TMDC_ABS; i++)
3208c2ecf20Sopenharmony_ci		if (port->abs[i] >= 0)
3218c2ecf20Sopenharmony_ci			input_set_abs_params(input_dev, port->abs[i], 8, 248, 2, 4);
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	for (i = 0; i < model->hats && i < TMDC_ABS_HAT; i++)
3248c2ecf20Sopenharmony_ci		input_set_abs_params(input_dev, tmdc_abs_hat[i], -1, 1, 0, 0);
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	for (i = 0; i < 4; i++) {
3278c2ecf20Sopenharmony_ci		for (j = 0; j < port->btnc[i] && j < TMDC_BTN; j++)
3288c2ecf20Sopenharmony_ci			set_bit(port->btn[j + b], input_dev->keybit);
3298c2ecf20Sopenharmony_ci		b += port->btnc[i];
3308c2ecf20Sopenharmony_ci	}
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	err = input_register_device(port->dev);
3338c2ecf20Sopenharmony_ci	if (err)
3348c2ecf20Sopenharmony_ci		goto fail;
3358c2ecf20Sopenharmony_ci
3368c2ecf20Sopenharmony_ci	return 0;
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci fail:	input_free_device(input_dev);
3398c2ecf20Sopenharmony_ci	kfree(port);
3408c2ecf20Sopenharmony_ci	return err;
3418c2ecf20Sopenharmony_ci}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci/*
3448c2ecf20Sopenharmony_ci * tmdc_probe() probes for ThrustMaster type joysticks.
3458c2ecf20Sopenharmony_ci */
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_cistatic int tmdc_connect(struct gameport *gameport, struct gameport_driver *drv)
3488c2ecf20Sopenharmony_ci{
3498c2ecf20Sopenharmony_ci	unsigned char data[2][TMDC_MAX_LENGTH];
3508c2ecf20Sopenharmony_ci	struct tmdc *tmdc;
3518c2ecf20Sopenharmony_ci	int i;
3528c2ecf20Sopenharmony_ci	int err;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	if (!(tmdc = kzalloc(sizeof(struct tmdc), GFP_KERNEL)))
3558c2ecf20Sopenharmony_ci		return -ENOMEM;
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	tmdc->gameport = gameport;
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	gameport_set_drvdata(gameport, tmdc);
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW);
3628c2ecf20Sopenharmony_ci	if (err)
3638c2ecf20Sopenharmony_ci		goto fail1;
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	if (!(tmdc->exists = tmdc_read_packet(gameport, data))) {
3668c2ecf20Sopenharmony_ci		err = -ENODEV;
3678c2ecf20Sopenharmony_ci		goto fail2;
3688c2ecf20Sopenharmony_ci	}
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	gameport_set_poll_handler(gameport, tmdc_poll);
3718c2ecf20Sopenharmony_ci	gameport_set_poll_interval(gameport, 20);
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	for (i = 0; i < 2; i++) {
3748c2ecf20Sopenharmony_ci		if (tmdc->exists & (1 << i)) {
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci			err = tmdc_setup_port(tmdc, i, data[i]);
3778c2ecf20Sopenharmony_ci			if (err)
3788c2ecf20Sopenharmony_ci				goto fail3;
3798c2ecf20Sopenharmony_ci		}
3808c2ecf20Sopenharmony_ci	}
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	return 0;
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci fail3: while (--i >= 0) {
3858c2ecf20Sopenharmony_ci		if (tmdc->port[i]) {
3868c2ecf20Sopenharmony_ci			input_unregister_device(tmdc->port[i]->dev);
3878c2ecf20Sopenharmony_ci			kfree(tmdc->port[i]);
3888c2ecf20Sopenharmony_ci		}
3898c2ecf20Sopenharmony_ci	}
3908c2ecf20Sopenharmony_ci fail2:	gameport_close(gameport);
3918c2ecf20Sopenharmony_ci fail1:	gameport_set_drvdata(gameport, NULL);
3928c2ecf20Sopenharmony_ci	kfree(tmdc);
3938c2ecf20Sopenharmony_ci	return err;
3948c2ecf20Sopenharmony_ci}
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_cistatic void tmdc_disconnect(struct gameport *gameport)
3978c2ecf20Sopenharmony_ci{
3988c2ecf20Sopenharmony_ci	struct tmdc *tmdc = gameport_get_drvdata(gameport);
3998c2ecf20Sopenharmony_ci	int i;
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	for (i = 0; i < 2; i++) {
4028c2ecf20Sopenharmony_ci		if (tmdc->port[i]) {
4038c2ecf20Sopenharmony_ci			input_unregister_device(tmdc->port[i]->dev);
4048c2ecf20Sopenharmony_ci			kfree(tmdc->port[i]);
4058c2ecf20Sopenharmony_ci		}
4068c2ecf20Sopenharmony_ci	}
4078c2ecf20Sopenharmony_ci	gameport_close(gameport);
4088c2ecf20Sopenharmony_ci	gameport_set_drvdata(gameport, NULL);
4098c2ecf20Sopenharmony_ci	kfree(tmdc);
4108c2ecf20Sopenharmony_ci}
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_cistatic struct gameport_driver tmdc_drv = {
4138c2ecf20Sopenharmony_ci	.driver		= {
4148c2ecf20Sopenharmony_ci		.name	= "tmdc",
4158c2ecf20Sopenharmony_ci		.owner	= THIS_MODULE,
4168c2ecf20Sopenharmony_ci	},
4178c2ecf20Sopenharmony_ci	.description	= DRIVER_DESC,
4188c2ecf20Sopenharmony_ci	.connect	= tmdc_connect,
4198c2ecf20Sopenharmony_ci	.disconnect	= tmdc_disconnect,
4208c2ecf20Sopenharmony_ci};
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_cimodule_gameport_driver(tmdc_drv);
423