162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 1998-2001 Vojtech Pavlik 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci/* 762306a36Sopenharmony_ci * FP-Gaming Assassin 3D joystick driver for Linux 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <linux/gameport.h> 1462306a36Sopenharmony_ci#include <linux/input.h> 1562306a36Sopenharmony_ci#include <linux/jiffies.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define DRIVER_DESC "FP-Gaming Assassin 3D joystick driver" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 2062306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 2162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define A3D_MAX_START 600 /* 600 us */ 2462306a36Sopenharmony_ci#define A3D_MAX_STROBE 80 /* 80 us */ 2562306a36Sopenharmony_ci#define A3D_MAX_LENGTH 40 /* 40*3 bits */ 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define A3D_MODE_A3D 1 /* Assassin 3D */ 2862306a36Sopenharmony_ci#define A3D_MODE_PAN 2 /* Panther */ 2962306a36Sopenharmony_ci#define A3D_MODE_OEM 3 /* Panther OEM version */ 3062306a36Sopenharmony_ci#define A3D_MODE_PXL 4 /* Panther XL */ 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic char *a3d_names[] = { NULL, "FP-Gaming Assassin 3D", "MadCatz Panther", "OEM Panther", 3362306a36Sopenharmony_ci "MadCatz Panther XL", "MadCatz Panther XL w/ rudder" }; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistruct a3d { 3662306a36Sopenharmony_ci struct gameport *gameport; 3762306a36Sopenharmony_ci struct gameport *adc; 3862306a36Sopenharmony_ci struct input_dev *dev; 3962306a36Sopenharmony_ci int axes[4]; 4062306a36Sopenharmony_ci int buttons; 4162306a36Sopenharmony_ci int mode; 4262306a36Sopenharmony_ci int length; 4362306a36Sopenharmony_ci int reads; 4462306a36Sopenharmony_ci int bads; 4562306a36Sopenharmony_ci char phys[32]; 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/* 4962306a36Sopenharmony_ci * a3d_read_packet() reads an Assassin 3D packet. 5062306a36Sopenharmony_ci */ 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic int a3d_read_packet(struct gameport *gameport, int length, char *data) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci unsigned long flags; 5562306a36Sopenharmony_ci unsigned char u, v; 5662306a36Sopenharmony_ci unsigned int t, s; 5762306a36Sopenharmony_ci int i; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci i = 0; 6062306a36Sopenharmony_ci t = gameport_time(gameport, A3D_MAX_START); 6162306a36Sopenharmony_ci s = gameport_time(gameport, A3D_MAX_STROBE); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci local_irq_save(flags); 6462306a36Sopenharmony_ci gameport_trigger(gameport); 6562306a36Sopenharmony_ci v = gameport_read(gameport); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci while (t > 0 && i < length) { 6862306a36Sopenharmony_ci t--; 6962306a36Sopenharmony_ci u = v; v = gameport_read(gameport); 7062306a36Sopenharmony_ci if (~v & u & 0x10) { 7162306a36Sopenharmony_ci data[i++] = v >> 5; 7262306a36Sopenharmony_ci t = s; 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci local_irq_restore(flags); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci return i; 7962306a36Sopenharmony_ci} 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci/* 8262306a36Sopenharmony_ci * a3d_csum() computes checksum of triplet packet 8362306a36Sopenharmony_ci */ 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic int a3d_csum(char *data, int count) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci int i, csum = 0; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci for (i = 0; i < count - 2; i++) 9062306a36Sopenharmony_ci csum += data[i]; 9162306a36Sopenharmony_ci return (csum & 0x3f) != ((data[count - 2] << 3) | data[count - 1]); 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic void a3d_read(struct a3d *a3d, unsigned char *data) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci struct input_dev *dev = a3d->dev; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci switch (a3d->mode) { 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci case A3D_MODE_A3D: 10162306a36Sopenharmony_ci case A3D_MODE_OEM: 10262306a36Sopenharmony_ci case A3D_MODE_PAN: 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci input_report_rel(dev, REL_X, ((data[5] << 6) | (data[6] << 3) | data[ 7]) - ((data[5] & 4) << 7)); 10562306a36Sopenharmony_ci input_report_rel(dev, REL_Y, ((data[8] << 6) | (data[9] << 3) | data[10]) - ((data[8] & 4) << 7)); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci input_report_key(dev, BTN_RIGHT, data[2] & 1); 10862306a36Sopenharmony_ci input_report_key(dev, BTN_LEFT, data[3] & 2); 10962306a36Sopenharmony_ci input_report_key(dev, BTN_MIDDLE, data[3] & 4); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci input_sync(dev); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci a3d->axes[0] = ((signed char)((data[11] << 6) | (data[12] << 3) | (data[13]))) + 128; 11462306a36Sopenharmony_ci a3d->axes[1] = ((signed char)((data[14] << 6) | (data[15] << 3) | (data[16]))) + 128; 11562306a36Sopenharmony_ci a3d->axes[2] = ((signed char)((data[17] << 6) | (data[18] << 3) | (data[19]))) + 128; 11662306a36Sopenharmony_ci a3d->axes[3] = ((signed char)((data[20] << 6) | (data[21] << 3) | (data[22]))) + 128; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci a3d->buttons = ((data[3] << 3) | data[4]) & 0xf; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci break; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci case A3D_MODE_PXL: 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci input_report_rel(dev, REL_X, ((data[ 9] << 6) | (data[10] << 3) | data[11]) - ((data[ 9] & 4) << 7)); 12562306a36Sopenharmony_ci input_report_rel(dev, REL_Y, ((data[12] << 6) | (data[13] << 3) | data[14]) - ((data[12] & 4) << 7)); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci input_report_key(dev, BTN_RIGHT, data[2] & 1); 12862306a36Sopenharmony_ci input_report_key(dev, BTN_LEFT, data[3] & 2); 12962306a36Sopenharmony_ci input_report_key(dev, BTN_MIDDLE, data[3] & 4); 13062306a36Sopenharmony_ci input_report_key(dev, BTN_SIDE, data[7] & 2); 13162306a36Sopenharmony_ci input_report_key(dev, BTN_EXTRA, data[7] & 4); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci input_report_abs(dev, ABS_X, ((signed char)((data[15] << 6) | (data[16] << 3) | (data[17]))) + 128); 13462306a36Sopenharmony_ci input_report_abs(dev, ABS_Y, ((signed char)((data[18] << 6) | (data[19] << 3) | (data[20]))) + 128); 13562306a36Sopenharmony_ci input_report_abs(dev, ABS_RUDDER, ((signed char)((data[21] << 6) | (data[22] << 3) | (data[23]))) + 128); 13662306a36Sopenharmony_ci input_report_abs(dev, ABS_THROTTLE, ((signed char)((data[24] << 6) | (data[25] << 3) | (data[26]))) + 128); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci input_report_abs(dev, ABS_HAT0X, ( data[5] & 1) - ((data[5] >> 2) & 1)); 13962306a36Sopenharmony_ci input_report_abs(dev, ABS_HAT0Y, ((data[5] >> 1) & 1) - ((data[6] >> 2) & 1)); 14062306a36Sopenharmony_ci input_report_abs(dev, ABS_HAT1X, ((data[4] >> 1) & 1) - ( data[3] & 1)); 14162306a36Sopenharmony_ci input_report_abs(dev, ABS_HAT1Y, ((data[4] >> 2) & 1) - ( data[4] & 1)); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci input_report_key(dev, BTN_TRIGGER, data[8] & 1); 14462306a36Sopenharmony_ci input_report_key(dev, BTN_THUMB, data[8] & 2); 14562306a36Sopenharmony_ci input_report_key(dev, BTN_TOP, data[8] & 4); 14662306a36Sopenharmony_ci input_report_key(dev, BTN_PINKIE, data[7] & 1); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci input_sync(dev); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci break; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci/* 15662306a36Sopenharmony_ci * a3d_poll() reads and analyzes A3D joystick data. 15762306a36Sopenharmony_ci */ 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic void a3d_poll(struct gameport *gameport) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct a3d *a3d = gameport_get_drvdata(gameport); 16262306a36Sopenharmony_ci unsigned char data[A3D_MAX_LENGTH]; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci a3d->reads++; 16562306a36Sopenharmony_ci if (a3d_read_packet(a3d->gameport, a3d->length, data) != a3d->length || 16662306a36Sopenharmony_ci data[0] != a3d->mode || a3d_csum(data, a3d->length)) 16762306a36Sopenharmony_ci a3d->bads++; 16862306a36Sopenharmony_ci else 16962306a36Sopenharmony_ci a3d_read(a3d, data); 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci/* 17362306a36Sopenharmony_ci * a3d_adc_cooked_read() copies the acis and button data to the 17462306a36Sopenharmony_ci * callers arrays. It could do the read itself, but the caller could 17562306a36Sopenharmony_ci * call this more than 50 times a second, which would use too much CPU. 17662306a36Sopenharmony_ci */ 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic int a3d_adc_cooked_read(struct gameport *gameport, int *axes, int *buttons) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci struct a3d *a3d = gameport->port_data; 18162306a36Sopenharmony_ci int i; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci for (i = 0; i < 4; i++) 18462306a36Sopenharmony_ci axes[i] = (a3d->axes[i] < 254) ? a3d->axes[i] : -1; 18562306a36Sopenharmony_ci *buttons = a3d->buttons; 18662306a36Sopenharmony_ci return 0; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci/* 19062306a36Sopenharmony_ci * a3d_adc_open() is the gameport open routine. It refuses to serve 19162306a36Sopenharmony_ci * any but cooked data. 19262306a36Sopenharmony_ci */ 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic int a3d_adc_open(struct gameport *gameport, int mode) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci struct a3d *a3d = gameport->port_data; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci if (mode != GAMEPORT_MODE_COOKED) 19962306a36Sopenharmony_ci return -1; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci gameport_start_polling(a3d->gameport); 20262306a36Sopenharmony_ci return 0; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci/* 20662306a36Sopenharmony_ci * a3d_adc_close() is a callback from the input close routine. 20762306a36Sopenharmony_ci */ 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic void a3d_adc_close(struct gameport *gameport) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci struct a3d *a3d = gameport->port_data; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci gameport_stop_polling(a3d->gameport); 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci/* 21762306a36Sopenharmony_ci * a3d_open() is a callback from the input open routine. 21862306a36Sopenharmony_ci */ 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic int a3d_open(struct input_dev *dev) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci struct a3d *a3d = input_get_drvdata(dev); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci gameport_start_polling(a3d->gameport); 22562306a36Sopenharmony_ci return 0; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci/* 22962306a36Sopenharmony_ci * a3d_close() is a callback from the input close routine. 23062306a36Sopenharmony_ci */ 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic void a3d_close(struct input_dev *dev) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci struct a3d *a3d = input_get_drvdata(dev); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci gameport_stop_polling(a3d->gameport); 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci/* 24062306a36Sopenharmony_ci * a3d_connect() probes for A3D joysticks. 24162306a36Sopenharmony_ci */ 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic int a3d_connect(struct gameport *gameport, struct gameport_driver *drv) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci struct a3d *a3d; 24662306a36Sopenharmony_ci struct input_dev *input_dev; 24762306a36Sopenharmony_ci struct gameport *adc; 24862306a36Sopenharmony_ci unsigned char data[A3D_MAX_LENGTH]; 24962306a36Sopenharmony_ci int i; 25062306a36Sopenharmony_ci int err; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci a3d = kzalloc(sizeof(struct a3d), GFP_KERNEL); 25362306a36Sopenharmony_ci input_dev = input_allocate_device(); 25462306a36Sopenharmony_ci if (!a3d || !input_dev) { 25562306a36Sopenharmony_ci err = -ENOMEM; 25662306a36Sopenharmony_ci goto fail1; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci a3d->dev = input_dev; 26062306a36Sopenharmony_ci a3d->gameport = gameport; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci gameport_set_drvdata(gameport, a3d); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); 26562306a36Sopenharmony_ci if (err) 26662306a36Sopenharmony_ci goto fail1; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci i = a3d_read_packet(gameport, A3D_MAX_LENGTH, data); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci if (!i || a3d_csum(data, i)) { 27162306a36Sopenharmony_ci err = -ENODEV; 27262306a36Sopenharmony_ci goto fail2; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci a3d->mode = data[0]; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (!a3d->mode || a3d->mode > 5) { 27862306a36Sopenharmony_ci printk(KERN_WARNING "a3d.c: Unknown A3D device detected " 27962306a36Sopenharmony_ci "(%s, id=%d), contact <vojtech@ucw.cz>\n", gameport->phys, a3d->mode); 28062306a36Sopenharmony_ci err = -ENODEV; 28162306a36Sopenharmony_ci goto fail2; 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci gameport_set_poll_handler(gameport, a3d_poll); 28562306a36Sopenharmony_ci gameport_set_poll_interval(gameport, 20); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci snprintf(a3d->phys, sizeof(a3d->phys), "%s/input0", gameport->phys); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci input_dev->name = a3d_names[a3d->mode]; 29062306a36Sopenharmony_ci input_dev->phys = a3d->phys; 29162306a36Sopenharmony_ci input_dev->id.bustype = BUS_GAMEPORT; 29262306a36Sopenharmony_ci input_dev->id.vendor = GAMEPORT_ID_VENDOR_MADCATZ; 29362306a36Sopenharmony_ci input_dev->id.product = a3d->mode; 29462306a36Sopenharmony_ci input_dev->id.version = 0x0100; 29562306a36Sopenharmony_ci input_dev->dev.parent = &gameport->dev; 29662306a36Sopenharmony_ci input_dev->open = a3d_open; 29762306a36Sopenharmony_ci input_dev->close = a3d_close; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci input_set_drvdata(input_dev, a3d); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci if (a3d->mode == A3D_MODE_PXL) { 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci int axes[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RUDDER }; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci a3d->length = 33; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci input_dev->evbit[0] |= BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY) | 30862306a36Sopenharmony_ci BIT_MASK(EV_REL); 30962306a36Sopenharmony_ci input_dev->relbit[0] |= BIT_MASK(REL_X) | BIT_MASK(REL_Y); 31062306a36Sopenharmony_ci input_dev->absbit[0] |= BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | 31162306a36Sopenharmony_ci BIT_MASK(ABS_THROTTLE) | BIT_MASK(ABS_RUDDER) | 31262306a36Sopenharmony_ci BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | 31362306a36Sopenharmony_ci BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y); 31462306a36Sopenharmony_ci input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_RIGHT) | 31562306a36Sopenharmony_ci BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE) | 31662306a36Sopenharmony_ci BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA); 31762306a36Sopenharmony_ci input_dev->keybit[BIT_WORD(BTN_JOYSTICK)] |= 31862306a36Sopenharmony_ci BIT_MASK(BTN_TRIGGER) | BIT_MASK(BTN_THUMB) | 31962306a36Sopenharmony_ci BIT_MASK(BTN_TOP) | BIT_MASK(BTN_PINKIE); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci a3d_read(a3d, data); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci for (i = 0; i < 4; i++) { 32462306a36Sopenharmony_ci if (i < 2) 32562306a36Sopenharmony_ci input_set_abs_params(input_dev, axes[i], 32662306a36Sopenharmony_ci 48, input_abs_get_val(input_dev, axes[i]) * 2 - 48, 0, 8); 32762306a36Sopenharmony_ci else 32862306a36Sopenharmony_ci input_set_abs_params(input_dev, axes[i], 2, 253, 0, 0); 32962306a36Sopenharmony_ci input_set_abs_params(input_dev, ABS_HAT0X + i, -1, 1, 0, 0); 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci } else { 33362306a36Sopenharmony_ci a3d->length = 29; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); 33662306a36Sopenharmony_ci input_dev->relbit[0] |= BIT_MASK(REL_X) | BIT_MASK(REL_Y); 33762306a36Sopenharmony_ci input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_RIGHT) | 33862306a36Sopenharmony_ci BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci a3d_read(a3d, data); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci if (!(a3d->adc = adc = gameport_allocate_port())) 34362306a36Sopenharmony_ci printk(KERN_ERR "a3d: Not enough memory for ADC port\n"); 34462306a36Sopenharmony_ci else { 34562306a36Sopenharmony_ci adc->port_data = a3d; 34662306a36Sopenharmony_ci adc->open = a3d_adc_open; 34762306a36Sopenharmony_ci adc->close = a3d_adc_close; 34862306a36Sopenharmony_ci adc->cooked_read = a3d_adc_cooked_read; 34962306a36Sopenharmony_ci adc->fuzz = 1; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci gameport_set_name(adc, a3d_names[a3d->mode]); 35262306a36Sopenharmony_ci gameport_set_phys(adc, "%s/gameport0", gameport->phys); 35362306a36Sopenharmony_ci adc->dev.parent = &gameport->dev; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci gameport_register_port(adc); 35662306a36Sopenharmony_ci } 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci err = input_register_device(a3d->dev); 36062306a36Sopenharmony_ci if (err) 36162306a36Sopenharmony_ci goto fail3; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci return 0; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci fail3: if (a3d->adc) 36662306a36Sopenharmony_ci gameport_unregister_port(a3d->adc); 36762306a36Sopenharmony_ci fail2: gameport_close(gameport); 36862306a36Sopenharmony_ci fail1: gameport_set_drvdata(gameport, NULL); 36962306a36Sopenharmony_ci input_free_device(input_dev); 37062306a36Sopenharmony_ci kfree(a3d); 37162306a36Sopenharmony_ci return err; 37262306a36Sopenharmony_ci} 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_cistatic void a3d_disconnect(struct gameport *gameport) 37562306a36Sopenharmony_ci{ 37662306a36Sopenharmony_ci struct a3d *a3d = gameport_get_drvdata(gameport); 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci input_unregister_device(a3d->dev); 37962306a36Sopenharmony_ci if (a3d->adc) 38062306a36Sopenharmony_ci gameport_unregister_port(a3d->adc); 38162306a36Sopenharmony_ci gameport_close(gameport); 38262306a36Sopenharmony_ci gameport_set_drvdata(gameport, NULL); 38362306a36Sopenharmony_ci kfree(a3d); 38462306a36Sopenharmony_ci} 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_cistatic struct gameport_driver a3d_drv = { 38762306a36Sopenharmony_ci .driver = { 38862306a36Sopenharmony_ci .name = "adc", 38962306a36Sopenharmony_ci .owner = THIS_MODULE, 39062306a36Sopenharmony_ci }, 39162306a36Sopenharmony_ci .description = DRIVER_DESC, 39262306a36Sopenharmony_ci .connect = a3d_connect, 39362306a36Sopenharmony_ci .disconnect = a3d_disconnect, 39462306a36Sopenharmony_ci}; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_cimodule_gameport_driver(a3d_drv); 397