1d97d5c0cSVladimir Kondratyev /*- 2d97d5c0cSVladimir Kondratyev * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3d97d5c0cSVladimir Kondratyev * 4d97d5c0cSVladimir Kondratyev * Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org> 5*0b453151SVal Packett * Copyright (c) 2020 Val Packett <val@packett.cool> 6d97d5c0cSVladimir Kondratyev * 7d97d5c0cSVladimir Kondratyev * Redistribution and use in source and binary forms, with or without 8d97d5c0cSVladimir Kondratyev * modification, are permitted provided that the following conditions 9d97d5c0cSVladimir Kondratyev * are met: 10d97d5c0cSVladimir Kondratyev * 1. Redistributions of source code must retain the above copyright 11d97d5c0cSVladimir Kondratyev * notice, this list of conditions and the following disclaimer. 12d97d5c0cSVladimir Kondratyev * 2. Redistributions in binary form must reproduce the above copyright 13d97d5c0cSVladimir Kondratyev * notice, this list of conditions and the following disclaimer in the 14d97d5c0cSVladimir Kondratyev * documentation and/or other materials provided with the distribution. 15d97d5c0cSVladimir Kondratyev * 16d97d5c0cSVladimir Kondratyev * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17d97d5c0cSVladimir Kondratyev * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18d97d5c0cSVladimir Kondratyev * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19d97d5c0cSVladimir Kondratyev * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20d97d5c0cSVladimir Kondratyev * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21d97d5c0cSVladimir Kondratyev * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22d97d5c0cSVladimir Kondratyev * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23d97d5c0cSVladimir Kondratyev * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24d97d5c0cSVladimir Kondratyev * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25d97d5c0cSVladimir Kondratyev * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26d97d5c0cSVladimir Kondratyev * SUCH DAMAGE. 27d97d5c0cSVladimir Kondratyev */ 28d97d5c0cSVladimir Kondratyev 29d97d5c0cSVladimir Kondratyev #include <sys/cdefs.h> 30d97d5c0cSVladimir Kondratyev __FBSDID("$FreeBSD$"); 31d97d5c0cSVladimir Kondratyev 32d97d5c0cSVladimir Kondratyev /* 33d97d5c0cSVladimir Kondratyev * Generic HID game controller (joystick/gamepad) driver, 34d97d5c0cSVladimir Kondratyev */ 35d97d5c0cSVladimir Kondratyev 36d97d5c0cSVladimir Kondratyev #include <sys/param.h> 37d97d5c0cSVladimir Kondratyev #include <sys/bus.h> 38d97d5c0cSVladimir Kondratyev #include <sys/kernel.h> 39d97d5c0cSVladimir Kondratyev #include <sys/module.h> 40d97d5c0cSVladimir Kondratyev #include <sys/sysctl.h> 41d97d5c0cSVladimir Kondratyev 42d97d5c0cSVladimir Kondratyev #include <dev/evdev/input.h> 43d97d5c0cSVladimir Kondratyev #include <dev/evdev/evdev.h> 44d97d5c0cSVladimir Kondratyev 45d97d5c0cSVladimir Kondratyev #include <dev/hid/hgame.h> 46d97d5c0cSVladimir Kondratyev #include <dev/hid/hid.h> 47d97d5c0cSVladimir Kondratyev #include <dev/hid/hidbus.h> 48d97d5c0cSVladimir Kondratyev #include <dev/hid/hidquirk.h> 49d97d5c0cSVladimir Kondratyev #include <dev/hid/hidmap.h> 50d97d5c0cSVladimir Kondratyev 51d97d5c0cSVladimir Kondratyev #define HGAME_MAP_BRG(number_from, number_to, code) \ 52d97d5c0cSVladimir Kondratyev { HIDMAP_KEY_RANGE(HUP_BUTTON, number_from, number_to, code) } 53d97d5c0cSVladimir Kondratyev #define HGAME_MAP_ABS(usage, code) \ 54d97d5c0cSVladimir Kondratyev { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) } 5551b22161SGreg V #define HGAME_MAP_GCB(usage, callback) \ 5651b22161SGreg V { HIDMAP_ANY_CB(HUP_GENERIC_DESKTOP, HUG_##usage, callback) } 57d97d5c0cSVladimir Kondratyev #define HGAME_MAP_CRG(usage_from, usage_to, callback) \ 58d97d5c0cSVladimir Kondratyev { HIDMAP_ANY_CB_RANGE(HUP_GENERIC_DESKTOP, \ 59d97d5c0cSVladimir Kondratyev HUG_##usage_from, HUG_##usage_to, callback) } 60d97d5c0cSVladimir Kondratyev #define HGAME_FINALCB(cb) \ 61d97d5c0cSVladimir Kondratyev { HIDMAP_FINAL_CB(&cb) } 62d97d5c0cSVladimir Kondratyev 63d97d5c0cSVladimir Kondratyev static const struct hidmap_item hgame_map[] = { 64d97d5c0cSVladimir Kondratyev HGAME_MAP_BRG(1, 16, BTN_TRIGGER), 65d97d5c0cSVladimir Kondratyev HGAME_MAP_ABS(X, ABS_X), 66d97d5c0cSVladimir Kondratyev HGAME_MAP_ABS(Y, ABS_Y), 67d97d5c0cSVladimir Kondratyev HGAME_MAP_ABS(Z, ABS_Z), 68d97d5c0cSVladimir Kondratyev HGAME_MAP_ABS(RX, ABS_RX), 69d97d5c0cSVladimir Kondratyev HGAME_MAP_ABS(RY, ABS_RY), 70d97d5c0cSVladimir Kondratyev HGAME_MAP_ABS(RZ, ABS_RZ), 7151b22161SGreg V HGAME_MAP_GCB(HAT_SWITCH, hgame_hat_switch_cb), 72d97d5c0cSVladimir Kondratyev HGAME_MAP_CRG(D_PAD_UP, D_PAD_LEFT, hgame_dpad_cb), 73d97d5c0cSVladimir Kondratyev HGAME_MAP_BRG(17, 57, BTN_TRIGGER_HAPPY), 74d97d5c0cSVladimir Kondratyev HGAME_FINALCB( hgame_final_cb), 75d97d5c0cSVladimir Kondratyev }; 76d97d5c0cSVladimir Kondratyev 77d97d5c0cSVladimir Kondratyev static const struct hid_device_id hgame_devs[] = { 78d97d5c0cSVladimir Kondratyev { HID_TLC(HUP_GENERIC_DESKTOP, HUG_JOYSTICK), 79d97d5c0cSVladimir Kondratyev HID_DRIVER_INFO(HUG_JOYSTICK) }, 80d97d5c0cSVladimir Kondratyev { HID_TLC(HUP_GENERIC_DESKTOP, HUG_GAME_PAD), 81d97d5c0cSVladimir Kondratyev HID_DRIVER_INFO(HUG_GAME_PAD) }, 82d97d5c0cSVladimir Kondratyev }; 83d97d5c0cSVladimir Kondratyev 8451b22161SGreg V int 8551b22161SGreg V hgame_hat_switch_cb(HIDMAP_CB_ARGS) 8651b22161SGreg V { 8751b22161SGreg V static const struct { int32_t x; int32_t y; } hat_switch_map[] = { 8851b22161SGreg V {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, 8951b22161SGreg V {-1, -1},{0, 0} 9051b22161SGreg V }; 9151b22161SGreg V struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); 9251b22161SGreg V u_int idx; 9351b22161SGreg V 9451b22161SGreg V switch (HIDMAP_CB_GET_STATE()) { 9551b22161SGreg V case HIDMAP_CB_IS_ATTACHING: 9651b22161SGreg V evdev_support_event(evdev, EV_ABS); 9751b22161SGreg V evdev_support_abs(evdev, ABS_HAT0X, -1, 1, 0, 0, 0); 9851b22161SGreg V evdev_support_abs(evdev, ABS_HAT0Y, -1, 1, 0, 0, 0); 9951b22161SGreg V break; 10051b22161SGreg V 10151b22161SGreg V case HIDMAP_CB_IS_RUNNING: 10251b22161SGreg V idx = MIN(nitems(hat_switch_map) - 1, (u_int)ctx.data); 10351b22161SGreg V evdev_push_abs(evdev, ABS_HAT0X, hat_switch_map[idx].x); 10451b22161SGreg V evdev_push_abs(evdev, ABS_HAT0Y, hat_switch_map[idx].y); 10551b22161SGreg V break; 10651b22161SGreg V 10751b22161SGreg V default: 10851b22161SGreg V break; 10951b22161SGreg V } 11051b22161SGreg V 11151b22161SGreg V return (0); 11251b22161SGreg V } 11351b22161SGreg V 114d97d5c0cSVladimir Kondratyev /* 115d97d5c0cSVladimir Kondratyev * Emulate the hat switch report via the D-pad usages 116d97d5c0cSVladimir Kondratyev * found on XInput/XBox style devices 117d97d5c0cSVladimir Kondratyev */ 118d97d5c0cSVladimir Kondratyev int 119d97d5c0cSVladimir Kondratyev hgame_dpad_cb(HIDMAP_CB_ARGS) 120d97d5c0cSVladimir Kondratyev { 121d97d5c0cSVladimir Kondratyev struct hgame_softc *sc = HIDMAP_CB_GET_SOFTC(); 122d97d5c0cSVladimir Kondratyev struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); 123d97d5c0cSVladimir Kondratyev int32_t data; 124d97d5c0cSVladimir Kondratyev 125d97d5c0cSVladimir Kondratyev switch (HIDMAP_CB_GET_STATE()) { 126d97d5c0cSVladimir Kondratyev case HIDMAP_CB_IS_ATTACHING: 127d97d5c0cSVladimir Kondratyev HIDMAP_CB_UDATA64 = HID_GET_USAGE(ctx.hi->usage); 128d97d5c0cSVladimir Kondratyev evdev_support_event(evdev, EV_ABS); 129d97d5c0cSVladimir Kondratyev evdev_support_abs(evdev, ABS_HAT0X, -1, 1, 0, 0, 0); 130d97d5c0cSVladimir Kondratyev evdev_support_abs(evdev, ABS_HAT0Y, -1, 1, 0, 0, 0); 131d97d5c0cSVladimir Kondratyev break; 132d97d5c0cSVladimir Kondratyev 133d97d5c0cSVladimir Kondratyev case HIDMAP_CB_IS_RUNNING: 134d97d5c0cSVladimir Kondratyev data = ctx.data; 135d97d5c0cSVladimir Kondratyev switch (HIDMAP_CB_UDATA64) { 136d97d5c0cSVladimir Kondratyev case HUG_D_PAD_UP: 137d97d5c0cSVladimir Kondratyev if (sc->dpad_down) 138d97d5c0cSVladimir Kondratyev return (ENOMSG); 139d97d5c0cSVladimir Kondratyev evdev_push_abs(evdev, ABS_HAT0Y, (data == 0) ? 0 : -1); 140d97d5c0cSVladimir Kondratyev sc->dpad_up = (data != 0); 141d97d5c0cSVladimir Kondratyev break; 142d97d5c0cSVladimir Kondratyev case HUG_D_PAD_DOWN: 143d97d5c0cSVladimir Kondratyev if (sc->dpad_up) 144d97d5c0cSVladimir Kondratyev return (ENOMSG); 145d97d5c0cSVladimir Kondratyev evdev_push_abs(evdev, ABS_HAT0Y, (data == 0) ? 0 : 1); 146d97d5c0cSVladimir Kondratyev sc->dpad_down = (data != 0); 147d97d5c0cSVladimir Kondratyev break; 148d97d5c0cSVladimir Kondratyev case HUG_D_PAD_RIGHT: 149d97d5c0cSVladimir Kondratyev if (sc->dpad_left) 150d97d5c0cSVladimir Kondratyev return (ENOMSG); 151d97d5c0cSVladimir Kondratyev evdev_push_abs(evdev, ABS_HAT0X, (data == 0) ? 0 : 1); 152d97d5c0cSVladimir Kondratyev sc->dpad_right = (data != 0); 153d97d5c0cSVladimir Kondratyev break; 154d97d5c0cSVladimir Kondratyev case HUG_D_PAD_LEFT: 155d97d5c0cSVladimir Kondratyev if (sc->dpad_right) 156d97d5c0cSVladimir Kondratyev return (ENOMSG); 157d97d5c0cSVladimir Kondratyev evdev_push_abs(evdev, ABS_HAT0X, (data == 0) ? 0 : -1); 158d97d5c0cSVladimir Kondratyev sc->dpad_left = (data != 0); 159d97d5c0cSVladimir Kondratyev break; 160d97d5c0cSVladimir Kondratyev } 16116079c72SRyan Libby break; 16216079c72SRyan Libby 16316079c72SRyan Libby default: 16416079c72SRyan Libby break; 165d97d5c0cSVladimir Kondratyev } 166d97d5c0cSVladimir Kondratyev 167d97d5c0cSVladimir Kondratyev return (0); 168d97d5c0cSVladimir Kondratyev } 169d97d5c0cSVladimir Kondratyev 170d97d5c0cSVladimir Kondratyev int 171d97d5c0cSVladimir Kondratyev hgame_final_cb(HIDMAP_CB_ARGS) 172d97d5c0cSVladimir Kondratyev { 173d97d5c0cSVladimir Kondratyev struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); 174d97d5c0cSVladimir Kondratyev 175d97d5c0cSVladimir Kondratyev if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) 176d97d5c0cSVladimir Kondratyev evdev_support_prop(evdev, INPUT_PROP_DIRECT); 177d97d5c0cSVladimir Kondratyev 178d97d5c0cSVladimir Kondratyev /* Do not execute callback at interrupt handler and detach */ 179d97d5c0cSVladimir Kondratyev return (ENOSYS); 180d97d5c0cSVladimir Kondratyev } 181d97d5c0cSVladimir Kondratyev 182d97d5c0cSVladimir Kondratyev static int 183d97d5c0cSVladimir Kondratyev hgame_probe(device_t dev) 184d97d5c0cSVladimir Kondratyev { 185d97d5c0cSVladimir Kondratyev const struct hid_device_info *hw = hid_get_device_info(dev); 186d97d5c0cSVladimir Kondratyev struct hgame_softc *sc = device_get_softc(dev); 187d97d5c0cSVladimir Kondratyev int error; 188d97d5c0cSVladimir Kondratyev 189d97d5c0cSVladimir Kondratyev if (hid_test_quirk(hw, HQ_IS_XBOX360GP)) 190d97d5c0cSVladimir Kondratyev return(ENXIO); 191d97d5c0cSVladimir Kondratyev 192d97d5c0cSVladimir Kondratyev error = HIDMAP_PROBE(&sc->hm, dev, hgame_devs, hgame_map, NULL); 193d97d5c0cSVladimir Kondratyev if (error > 0) 194d97d5c0cSVladimir Kondratyev return (error); 195d97d5c0cSVladimir Kondratyev 196d97d5c0cSVladimir Kondratyev hidbus_set_desc(dev, hidbus_get_driver_info(dev) == HUG_GAME_PAD ? 197d97d5c0cSVladimir Kondratyev "Gamepad" : "Joystick"); 198d97d5c0cSVladimir Kondratyev 199d97d5c0cSVladimir Kondratyev return (BUS_PROBE_GENERIC); 200d97d5c0cSVladimir Kondratyev } 201d97d5c0cSVladimir Kondratyev 202d97d5c0cSVladimir Kondratyev 203d97d5c0cSVladimir Kondratyev 204d97d5c0cSVladimir Kondratyev static int 205d97d5c0cSVladimir Kondratyev hgame_attach(device_t dev) 206d97d5c0cSVladimir Kondratyev { 207d97d5c0cSVladimir Kondratyev struct hgame_softc *sc = device_get_softc(dev); 208d97d5c0cSVladimir Kondratyev 209d97d5c0cSVladimir Kondratyev return (hidmap_attach(&sc->hm)); 210d97d5c0cSVladimir Kondratyev } 211d97d5c0cSVladimir Kondratyev 212d97d5c0cSVladimir Kondratyev static int 213d97d5c0cSVladimir Kondratyev hgame_detach(device_t dev) 214d97d5c0cSVladimir Kondratyev { 215d97d5c0cSVladimir Kondratyev struct hgame_softc *sc = device_get_softc(dev); 216d97d5c0cSVladimir Kondratyev 217d97d5c0cSVladimir Kondratyev return (hidmap_detach(&sc->hm)); 218d97d5c0cSVladimir Kondratyev } 219d97d5c0cSVladimir Kondratyev 220d97d5c0cSVladimir Kondratyev static device_method_t hgame_methods[] = { 221d97d5c0cSVladimir Kondratyev DEVMETHOD(device_probe, hgame_probe), 222d97d5c0cSVladimir Kondratyev DEVMETHOD(device_attach, hgame_attach), 223d97d5c0cSVladimir Kondratyev DEVMETHOD(device_detach, hgame_detach), 224d97d5c0cSVladimir Kondratyev 225d97d5c0cSVladimir Kondratyev DEVMETHOD_END 226d97d5c0cSVladimir Kondratyev }; 227d97d5c0cSVladimir Kondratyev 228d97d5c0cSVladimir Kondratyev DEFINE_CLASS_0(hgame, hgame_driver, hgame_methods, sizeof(struct hgame_softc)); 2297eeede15SJohn Baldwin DRIVER_MODULE(hgame, hidbus, hgame_driver, NULL, NULL); 230d97d5c0cSVladimir Kondratyev MODULE_DEPEND(hgame, hid, 1, 1, 1); 231d97d5c0cSVladimir Kondratyev MODULE_DEPEND(hgame, hidbus, 1, 1, 1); 232d97d5c0cSVladimir Kondratyev MODULE_DEPEND(hgame, hidmap, 1, 1, 1); 233d97d5c0cSVladimir Kondratyev MODULE_DEPEND(hgame, evdev, 1, 1, 1); 234d97d5c0cSVladimir Kondratyev MODULE_VERSION(hgame, 1); 235d97d5c0cSVladimir Kondratyev HID_PNP_INFO(hgame_devs); 236