1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * FM801 gameport driver for Linux |
4 | * |
5 | * Copyright (c) by Takashi Iwai <tiwai@suse.de> |
6 | */ |
7 | |
8 | #include <asm/io.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/errno.h> |
11 | #include <linux/ioport.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/pci.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/gameport.h> |
17 | |
18 | #define PCI_VENDOR_ID_FORTEMEDIA 0x1319 |
19 | #define PCI_DEVICE_ID_FM801_GP 0x0802 |
20 | |
21 | #define HAVE_COOKED |
22 | |
23 | struct fm801_gp { |
24 | struct gameport *gameport; |
25 | struct resource *res_port; |
26 | }; |
27 | |
28 | #ifdef HAVE_COOKED |
29 | static int fm801_gp_cooked_read(struct gameport *gameport, int *axes, int *buttons) |
30 | { |
31 | unsigned short w; |
32 | |
33 | w = inw(port: gameport->io + 2); |
34 | *buttons = (~w >> 14) & 0x03; |
35 | axes[0] = (w == 0xffff) ? -1 : ((w & 0x1fff) << 5); |
36 | w = inw(port: gameport->io + 4); |
37 | axes[1] = (w == 0xffff) ? -1 : ((w & 0x1fff) << 5); |
38 | w = inw(port: gameport->io + 6); |
39 | *buttons |= ((~w >> 14) & 0x03) << 2; |
40 | axes[2] = (w == 0xffff) ? -1 : ((w & 0x1fff) << 5); |
41 | w = inw(port: gameport->io + 8); |
42 | axes[3] = (w == 0xffff) ? -1 : ((w & 0x1fff) << 5); |
43 | outw(value: 0xff, port: gameport->io); /* reset */ |
44 | |
45 | return 0; |
46 | } |
47 | #endif |
48 | |
49 | static int fm801_gp_open(struct gameport *gameport, int mode) |
50 | { |
51 | switch (mode) { |
52 | #ifdef HAVE_COOKED |
53 | case GAMEPORT_MODE_COOKED: |
54 | return 0; |
55 | #endif |
56 | case GAMEPORT_MODE_RAW: |
57 | return 0; |
58 | default: |
59 | return -1; |
60 | } |
61 | |
62 | return 0; |
63 | } |
64 | |
65 | static int fm801_gp_probe(struct pci_dev *pci, const struct pci_device_id *id) |
66 | { |
67 | struct fm801_gp *gp; |
68 | struct gameport *port; |
69 | int error; |
70 | |
71 | gp = kzalloc(size: sizeof(struct fm801_gp), GFP_KERNEL); |
72 | port = gameport_allocate_port(); |
73 | if (!gp || !port) { |
74 | printk(KERN_ERR "fm801-gp: Memory allocation failed\n" ); |
75 | error = -ENOMEM; |
76 | goto err_out_free; |
77 | } |
78 | |
79 | error = pci_enable_device(dev: pci); |
80 | if (error) |
81 | goto err_out_free; |
82 | |
83 | port->open = fm801_gp_open; |
84 | #ifdef HAVE_COOKED |
85 | port->cooked_read = fm801_gp_cooked_read; |
86 | #endif |
87 | gameport_set_name(gameport: port, name: "FM801" ); |
88 | gameport_set_phys(gameport: port, fmt: "pci%s/gameport0" , pci_name(pdev: pci)); |
89 | port->dev.parent = &pci->dev; |
90 | port->io = pci_resource_start(pci, 0); |
91 | |
92 | gp->gameport = port; |
93 | gp->res_port = request_region(port->io, 0x10, "FM801 GP" ); |
94 | if (!gp->res_port) { |
95 | printk(KERN_DEBUG "fm801-gp: unable to grab region 0x%x-0x%x\n" , |
96 | port->io, port->io + 0x0f); |
97 | error = -EBUSY; |
98 | goto err_out_disable_dev; |
99 | } |
100 | |
101 | pci_set_drvdata(pdev: pci, data: gp); |
102 | |
103 | outb(value: 0x60, port: port->io + 0x0d); /* enable joystick 1 and 2 */ |
104 | gameport_register_port(port); |
105 | |
106 | return 0; |
107 | |
108 | err_out_disable_dev: |
109 | pci_disable_device(dev: pci); |
110 | err_out_free: |
111 | gameport_free_port(gameport: port); |
112 | kfree(objp: gp); |
113 | return error; |
114 | } |
115 | |
116 | static void fm801_gp_remove(struct pci_dev *pci) |
117 | { |
118 | struct fm801_gp *gp = pci_get_drvdata(pdev: pci); |
119 | |
120 | gameport_unregister_port(gameport: gp->gameport); |
121 | release_resource(new: gp->res_port); |
122 | kfree(objp: gp); |
123 | |
124 | pci_disable_device(dev: pci); |
125 | } |
126 | |
127 | static const struct pci_device_id fm801_gp_id_table[] = { |
128 | { PCI_VENDOR_ID_FORTEMEDIA, PCI_DEVICE_ID_FM801_GP, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, |
129 | { 0 } |
130 | }; |
131 | MODULE_DEVICE_TABLE(pci, fm801_gp_id_table); |
132 | |
133 | static struct pci_driver fm801_gp_driver = { |
134 | .name = "FM801_gameport" , |
135 | .id_table = fm801_gp_id_table, |
136 | .probe = fm801_gp_probe, |
137 | .remove = fm801_gp_remove, |
138 | }; |
139 | |
140 | module_pci_driver(fm801_gp_driver); |
141 | |
142 | MODULE_DESCRIPTION("FM801 gameport driver" ); |
143 | MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>" ); |
144 | MODULE_LICENSE("GPL" ); |
145 | |