David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 2 | /* |
| 3 | * PlayStation 1/2 joypads via SPI interface Driver |
| 4 | * |
| 5 | * Copyright (C) 2017 Tomohiro Yoshidomi <sylph23k@gmail.com> |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 6 | * |
| 7 | * PlayStation 1/2 joypad's plug (not socket) |
| 8 | * 123 456 789 |
| 9 | * (...|...|...) |
| 10 | * |
| 11 | * 1: DAT -> MISO (pullup with 1k owm to 3.3V) |
| 12 | * 2: CMD -> MOSI |
| 13 | * 3: 9V (for motor, if not use N.C.) |
| 14 | * 4: GND |
| 15 | * 5: 3.3V |
| 16 | * 6: Attention -> CS(SS) |
| 17 | * 7: SCK -> SCK |
| 18 | * 8: N.C. |
| 19 | * 9: ACK -> N.C. |
| 20 | */ |
| 21 | |
| 22 | #include <linux/kernel.h> |
| 23 | #include <linux/device.h> |
| 24 | #include <linux/input.h> |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 25 | #include <linux/module.h> |
| 26 | #include <linux/spi/spi.h> |
| 27 | #include <linux/types.h> |
| 28 | #include <linux/pm.h> |
| 29 | #include <linux/pm_runtime.h> |
| 30 | |
| 31 | #define REVERSE_BIT(x) ((((x) & 0x80) >> 7) | (((x) & 0x40) >> 5) | \ |
| 32 | (((x) & 0x20) >> 3) | (((x) & 0x10) >> 1) | (((x) & 0x08) << 1) | \ |
| 33 | (((x) & 0x04) << 3) | (((x) & 0x02) << 5) | (((x) & 0x01) << 7)) |
| 34 | |
| 35 | /* PlayStation 1/2 joypad command and response are LSBFIRST. */ |
| 36 | |
| 37 | /* |
| 38 | * 0x01, 0x42, 0x00, 0x00, 0x00, |
| 39 | * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 40 | * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| 41 | */ |
| 42 | static const u8 PSX_CMD_POLL[] = { |
| 43 | 0x80, 0x42, 0x00, 0x00, 0x00, |
| 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| 46 | }; |
| 47 | /* 0x01, 0x43, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 */ |
| 48 | static const u8 PSX_CMD_ENTER_CFG[] = { |
| 49 | 0x80, 0xC2, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 |
| 50 | }; |
| 51 | /* 0x01, 0x43, 0x00, 0x00, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A */ |
| 52 | static const u8 PSX_CMD_EXIT_CFG[] = { |
| 53 | 0x80, 0xC2, 0x00, 0x00, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A |
| 54 | }; |
| 55 | /* 0x01, 0x4D, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF */ |
| 56 | static const u8 PSX_CMD_ENABLE_MOTOR[] = { |
| 57 | 0x80, 0xB2, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF |
| 58 | }; |
| 59 | |
| 60 | struct psxpad { |
| 61 | struct spi_device *spi; |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 62 | struct input_dev *idev; |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 63 | char phys[0x20]; |
| 64 | bool motor1enable; |
| 65 | bool motor2enable; |
| 66 | u8 motor1level; |
| 67 | u8 motor2level; |
| 68 | u8 sendbuf[0x20] ____cacheline_aligned; |
| 69 | u8 response[sizeof(PSX_CMD_POLL)] ____cacheline_aligned; |
| 70 | }; |
| 71 | |
| 72 | static int psxpad_command(struct psxpad *pad, const u8 sendcmdlen) |
| 73 | { |
| 74 | struct spi_transfer xfers = { |
| 75 | .tx_buf = pad->sendbuf, |
| 76 | .rx_buf = pad->response, |
| 77 | .len = sendcmdlen, |
| 78 | }; |
| 79 | int err; |
| 80 | |
| 81 | err = spi_sync_transfer(pad->spi, &xfers, 1); |
| 82 | if (err) { |
| 83 | dev_err(&pad->spi->dev, |
| 84 | "%s: failed to SPI xfers mode: %d\n", |
| 85 | __func__, err); |
| 86 | return err; |
| 87 | } |
| 88 | |
| 89 | return 0; |
| 90 | } |
| 91 | |
| 92 | #ifdef CONFIG_JOYSTICK_PSXPAD_SPI_FF |
| 93 | static void psxpad_control_motor(struct psxpad *pad, |
| 94 | bool motor1enable, bool motor2enable) |
| 95 | { |
| 96 | int err; |
| 97 | |
| 98 | pad->motor1enable = motor1enable; |
| 99 | pad->motor2enable = motor2enable; |
| 100 | |
| 101 | memcpy(pad->sendbuf, PSX_CMD_ENTER_CFG, sizeof(PSX_CMD_ENTER_CFG)); |
| 102 | err = psxpad_command(pad, sizeof(PSX_CMD_ENTER_CFG)); |
| 103 | if (err) { |
| 104 | dev_err(&pad->spi->dev, |
| 105 | "%s: failed to enter config mode: %d\n", |
| 106 | __func__, err); |
| 107 | return; |
| 108 | } |
| 109 | |
| 110 | memcpy(pad->sendbuf, PSX_CMD_ENABLE_MOTOR, |
| 111 | sizeof(PSX_CMD_ENABLE_MOTOR)); |
| 112 | pad->sendbuf[3] = pad->motor1enable ? 0x00 : 0xFF; |
| 113 | pad->sendbuf[4] = pad->motor2enable ? 0x80 : 0xFF; |
| 114 | err = psxpad_command(pad, sizeof(PSX_CMD_ENABLE_MOTOR)); |
| 115 | if (err) { |
| 116 | dev_err(&pad->spi->dev, |
| 117 | "%s: failed to enable motor mode: %d\n", |
| 118 | __func__, err); |
| 119 | return; |
| 120 | } |
| 121 | |
| 122 | memcpy(pad->sendbuf, PSX_CMD_EXIT_CFG, sizeof(PSX_CMD_EXIT_CFG)); |
| 123 | err = psxpad_command(pad, sizeof(PSX_CMD_EXIT_CFG)); |
| 124 | if (err) { |
| 125 | dev_err(&pad->spi->dev, |
| 126 | "%s: failed to exit config mode: %d\n", |
| 127 | __func__, err); |
| 128 | return; |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | static void psxpad_set_motor_level(struct psxpad *pad, |
| 133 | u8 motor1level, u8 motor2level) |
| 134 | { |
| 135 | pad->motor1level = motor1level ? 0xFF : 0x00; |
| 136 | pad->motor2level = REVERSE_BIT(motor2level); |
| 137 | } |
| 138 | |
| 139 | static int psxpad_spi_play_effect(struct input_dev *idev, |
| 140 | void *data, struct ff_effect *effect) |
| 141 | { |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 142 | struct psxpad *pad = input_get_drvdata(idev); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 143 | |
| 144 | switch (effect->type) { |
| 145 | case FF_RUMBLE: |
| 146 | psxpad_set_motor_level(pad, |
| 147 | (effect->u.rumble.weak_magnitude >> 8) & 0xFFU, |
| 148 | (effect->u.rumble.strong_magnitude >> 8) & 0xFFU); |
| 149 | break; |
| 150 | } |
| 151 | |
| 152 | return 0; |
| 153 | } |
| 154 | |
| 155 | static int psxpad_spi_init_ff(struct psxpad *pad) |
| 156 | { |
| 157 | int err; |
| 158 | |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 159 | input_set_capability(pad->idev, EV_FF, FF_RUMBLE); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 160 | |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 161 | err = input_ff_create_memless(pad->idev, NULL, psxpad_spi_play_effect); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 162 | if (err) { |
| 163 | dev_err(&pad->spi->dev, |
| 164 | "input_ff_create_memless() failed: %d\n", err); |
| 165 | return err; |
| 166 | } |
| 167 | |
| 168 | return 0; |
| 169 | } |
| 170 | |
| 171 | #else /* CONFIG_JOYSTICK_PSXPAD_SPI_FF */ |
| 172 | |
| 173 | static void psxpad_control_motor(struct psxpad *pad, |
| 174 | bool motor1enable, bool motor2enable) |
| 175 | { |
| 176 | } |
| 177 | |
| 178 | static void psxpad_set_motor_level(struct psxpad *pad, |
| 179 | u8 motor1level, u8 motor2level) |
| 180 | { |
| 181 | } |
| 182 | |
| 183 | static inline int psxpad_spi_init_ff(struct psxpad *pad) |
| 184 | { |
| 185 | return 0; |
| 186 | } |
| 187 | #endif /* CONFIG_JOYSTICK_PSXPAD_SPI_FF */ |
| 188 | |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 189 | static int psxpad_spi_poll_open(struct input_dev *input) |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 190 | { |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 191 | struct psxpad *pad = input_get_drvdata(input); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 192 | |
| 193 | pm_runtime_get_sync(&pad->spi->dev); |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 194 | |
| 195 | return 0; |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 196 | } |
| 197 | |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 198 | static void psxpad_spi_poll_close(struct input_dev *input) |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 199 | { |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 200 | struct psxpad *pad = input_get_drvdata(input); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 201 | |
| 202 | pm_runtime_put_sync(&pad->spi->dev); |
| 203 | } |
| 204 | |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 205 | static void psxpad_spi_poll(struct input_dev *input) |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 206 | { |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 207 | struct psxpad *pad = input_get_drvdata(input); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 208 | u8 b_rsp3, b_rsp4; |
| 209 | int err; |
| 210 | |
| 211 | psxpad_control_motor(pad, true, true); |
| 212 | |
| 213 | memcpy(pad->sendbuf, PSX_CMD_POLL, sizeof(PSX_CMD_POLL)); |
| 214 | pad->sendbuf[3] = pad->motor1enable ? pad->motor1level : 0x00; |
| 215 | pad->sendbuf[4] = pad->motor2enable ? pad->motor2level : 0x00; |
| 216 | err = psxpad_command(pad, sizeof(PSX_CMD_POLL)); |
| 217 | if (err) { |
| 218 | dev_err(&pad->spi->dev, |
| 219 | "%s: poll command failed mode: %d\n", __func__, err); |
| 220 | return; |
| 221 | } |
| 222 | |
| 223 | switch (pad->response[1]) { |
| 224 | case 0xCE: /* 0x73 : analog 1 */ |
| 225 | /* button data is inverted */ |
| 226 | b_rsp3 = ~pad->response[3]; |
| 227 | b_rsp4 = ~pad->response[4]; |
| 228 | |
| 229 | input_report_abs(input, ABS_X, REVERSE_BIT(pad->response[7])); |
| 230 | input_report_abs(input, ABS_Y, REVERSE_BIT(pad->response[8])); |
| 231 | input_report_abs(input, ABS_RX, REVERSE_BIT(pad->response[5])); |
| 232 | input_report_abs(input, ABS_RY, REVERSE_BIT(pad->response[6])); |
| 233 | input_report_key(input, BTN_DPAD_UP, b_rsp3 & BIT(3)); |
| 234 | input_report_key(input, BTN_DPAD_DOWN, b_rsp3 & BIT(1)); |
| 235 | input_report_key(input, BTN_DPAD_LEFT, b_rsp3 & BIT(0)); |
| 236 | input_report_key(input, BTN_DPAD_RIGHT, b_rsp3 & BIT(2)); |
| 237 | input_report_key(input, BTN_X, b_rsp4 & BIT(3)); |
| 238 | input_report_key(input, BTN_A, b_rsp4 & BIT(2)); |
| 239 | input_report_key(input, BTN_B, b_rsp4 & BIT(1)); |
| 240 | input_report_key(input, BTN_Y, b_rsp4 & BIT(0)); |
| 241 | input_report_key(input, BTN_TL, b_rsp4 & BIT(5)); |
| 242 | input_report_key(input, BTN_TR, b_rsp4 & BIT(4)); |
| 243 | input_report_key(input, BTN_TL2, b_rsp4 & BIT(7)); |
| 244 | input_report_key(input, BTN_TR2, b_rsp4 & BIT(6)); |
| 245 | input_report_key(input, BTN_THUMBL, b_rsp3 & BIT(6)); |
| 246 | input_report_key(input, BTN_THUMBR, b_rsp3 & BIT(5)); |
| 247 | input_report_key(input, BTN_SELECT, b_rsp3 & BIT(7)); |
| 248 | input_report_key(input, BTN_START, b_rsp3 & BIT(4)); |
| 249 | break; |
| 250 | |
| 251 | case 0x82: /* 0x41 : digital */ |
| 252 | /* button data is inverted */ |
| 253 | b_rsp3 = ~pad->response[3]; |
| 254 | b_rsp4 = ~pad->response[4]; |
| 255 | |
| 256 | input_report_abs(input, ABS_X, 0x80); |
| 257 | input_report_abs(input, ABS_Y, 0x80); |
| 258 | input_report_abs(input, ABS_RX, 0x80); |
| 259 | input_report_abs(input, ABS_RY, 0x80); |
| 260 | input_report_key(input, BTN_DPAD_UP, b_rsp3 & BIT(3)); |
| 261 | input_report_key(input, BTN_DPAD_DOWN, b_rsp3 & BIT(1)); |
| 262 | input_report_key(input, BTN_DPAD_LEFT, b_rsp3 & BIT(0)); |
| 263 | input_report_key(input, BTN_DPAD_RIGHT, b_rsp3 & BIT(2)); |
| 264 | input_report_key(input, BTN_X, b_rsp4 & BIT(3)); |
| 265 | input_report_key(input, BTN_A, b_rsp4 & BIT(2)); |
| 266 | input_report_key(input, BTN_B, b_rsp4 & BIT(1)); |
| 267 | input_report_key(input, BTN_Y, b_rsp4 & BIT(0)); |
| 268 | input_report_key(input, BTN_TL, b_rsp4 & BIT(5)); |
| 269 | input_report_key(input, BTN_TR, b_rsp4 & BIT(4)); |
| 270 | input_report_key(input, BTN_TL2, b_rsp4 & BIT(7)); |
| 271 | input_report_key(input, BTN_TR2, b_rsp4 & BIT(6)); |
| 272 | input_report_key(input, BTN_THUMBL, false); |
| 273 | input_report_key(input, BTN_THUMBR, false); |
| 274 | input_report_key(input, BTN_SELECT, b_rsp3 & BIT(7)); |
| 275 | input_report_key(input, BTN_START, b_rsp3 & BIT(4)); |
| 276 | break; |
| 277 | } |
| 278 | |
| 279 | input_sync(input); |
| 280 | } |
| 281 | |
| 282 | static int psxpad_spi_probe(struct spi_device *spi) |
| 283 | { |
| 284 | struct psxpad *pad; |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 285 | struct input_dev *idev; |
| 286 | int err; |
| 287 | |
| 288 | pad = devm_kzalloc(&spi->dev, sizeof(struct psxpad), GFP_KERNEL); |
| 289 | if (!pad) |
| 290 | return -ENOMEM; |
| 291 | |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 292 | idev = devm_input_allocate_device(&spi->dev); |
| 293 | if (!idev) { |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 294 | dev_err(&spi->dev, "failed to allocate input device\n"); |
| 295 | return -ENOMEM; |
| 296 | } |
| 297 | |
| 298 | /* input poll device settings */ |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 299 | pad->idev = idev; |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 300 | pad->spi = spi; |
| 301 | |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 302 | /* input device settings */ |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 303 | input_set_drvdata(idev, pad); |
| 304 | |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 305 | idev->name = "PlayStation 1/2 joypad"; |
| 306 | snprintf(pad->phys, sizeof(pad->phys), "%s/input", dev_name(&spi->dev)); |
| 307 | idev->id.bustype = BUS_SPI; |
| 308 | |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 309 | idev->open = psxpad_spi_poll_open; |
| 310 | idev->close = psxpad_spi_poll_close; |
| 311 | |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 312 | /* key/value map settings */ |
| 313 | input_set_abs_params(idev, ABS_X, 0, 255, 0, 0); |
| 314 | input_set_abs_params(idev, ABS_Y, 0, 255, 0, 0); |
| 315 | input_set_abs_params(idev, ABS_RX, 0, 255, 0, 0); |
| 316 | input_set_abs_params(idev, ABS_RY, 0, 255, 0, 0); |
| 317 | input_set_capability(idev, EV_KEY, BTN_DPAD_UP); |
| 318 | input_set_capability(idev, EV_KEY, BTN_DPAD_DOWN); |
| 319 | input_set_capability(idev, EV_KEY, BTN_DPAD_LEFT); |
| 320 | input_set_capability(idev, EV_KEY, BTN_DPAD_RIGHT); |
| 321 | input_set_capability(idev, EV_KEY, BTN_A); |
| 322 | input_set_capability(idev, EV_KEY, BTN_B); |
| 323 | input_set_capability(idev, EV_KEY, BTN_X); |
| 324 | input_set_capability(idev, EV_KEY, BTN_Y); |
| 325 | input_set_capability(idev, EV_KEY, BTN_TL); |
| 326 | input_set_capability(idev, EV_KEY, BTN_TR); |
| 327 | input_set_capability(idev, EV_KEY, BTN_TL2); |
| 328 | input_set_capability(idev, EV_KEY, BTN_TR2); |
| 329 | input_set_capability(idev, EV_KEY, BTN_THUMBL); |
| 330 | input_set_capability(idev, EV_KEY, BTN_THUMBR); |
| 331 | input_set_capability(idev, EV_KEY, BTN_SELECT); |
| 332 | input_set_capability(idev, EV_KEY, BTN_START); |
| 333 | |
| 334 | err = psxpad_spi_init_ff(pad); |
| 335 | if (err) |
| 336 | return err; |
| 337 | |
| 338 | /* SPI settings */ |
| 339 | spi->mode = SPI_MODE_3; |
| 340 | spi->bits_per_word = 8; |
| 341 | /* (PlayStation 1/2 joypad might be possible works 250kHz/500kHz) */ |
| 342 | spi->master->min_speed_hz = 125000; |
| 343 | spi->master->max_speed_hz = 125000; |
| 344 | spi_setup(spi); |
| 345 | |
| 346 | /* pad settings */ |
| 347 | psxpad_set_motor_level(pad, 0, 0); |
| 348 | |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 349 | |
| 350 | err = input_setup_polling(idev, psxpad_spi_poll); |
| 351 | if (err) { |
| 352 | dev_err(&spi->dev, "failed to set up polling: %d\n", err); |
| 353 | return err; |
| 354 | } |
| 355 | |
| 356 | /* poll interval is about 60fps */ |
| 357 | input_set_poll_interval(idev, 16); |
| 358 | input_set_min_poll_interval(idev, 8); |
| 359 | input_set_max_poll_interval(idev, 32); |
| 360 | |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 361 | /* register input poll device */ |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 362 | err = input_register_device(idev); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 363 | if (err) { |
| 364 | dev_err(&spi->dev, |
Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 365 | "failed to register input device: %d\n", err); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 366 | return err; |
| 367 | } |
| 368 | |
| 369 | pm_runtime_enable(&spi->dev); |
| 370 | |
| 371 | return 0; |
| 372 | } |
| 373 | |
| 374 | static int __maybe_unused psxpad_spi_suspend(struct device *dev) |
| 375 | { |
| 376 | struct spi_device *spi = to_spi_device(dev); |
| 377 | struct psxpad *pad = spi_get_drvdata(spi); |
| 378 | |
| 379 | psxpad_set_motor_level(pad, 0, 0); |
| 380 | |
| 381 | return 0; |
| 382 | } |
| 383 | |
| 384 | static SIMPLE_DEV_PM_OPS(psxpad_spi_pm, psxpad_spi_suspend, NULL); |
| 385 | |
| 386 | static const struct spi_device_id psxpad_spi_id[] = { |
| 387 | { "psxpad-spi", 0 }, |
| 388 | { } |
| 389 | }; |
| 390 | MODULE_DEVICE_TABLE(spi, psxpad_spi_id); |
| 391 | |
| 392 | static struct spi_driver psxpad_spi_driver = { |
| 393 | .driver = { |
| 394 | .name = "psxpad-spi", |
| 395 | .pm = &psxpad_spi_pm, |
| 396 | }, |
| 397 | .id_table = psxpad_spi_id, |
| 398 | .probe = psxpad_spi_probe, |
| 399 | }; |
| 400 | |
| 401 | module_spi_driver(psxpad_spi_driver); |
| 402 | |
| 403 | MODULE_AUTHOR("Tomohiro Yoshidomi <sylph23k@gmail.com>"); |
| 404 | MODULE_DESCRIPTION("PlayStation 1/2 joypads via SPI interface Driver"); |
| 405 | MODULE_LICENSE("GPL"); |