diff options
Diffstat (limited to 'drivers/hid/hid-sony.c')
-rw-r--r-- | drivers/hid/hid-sony.c | 434 |
1 files changed, 340 insertions, 94 deletions
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 908de2789219..2259eaa8b988 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -33,6 +33,7 @@ #include <linux/power_supply.h> #include <linux/spinlock.h> #include <linux/list.h> +#include <linux/idr.h> #include <linux/input/mt.h> #include "hid-ids.h" @@ -717,8 +718,39 @@ static enum power_supply_property sony_battery_props[] = { POWER_SUPPLY_PROP_STATUS, }; +struct sixaxis_led { + __u8 time_enabled; /* the total time the led is active (0xff means forever) */ + __u8 duty_length; /* how long a cycle is in deciseconds (0 means "really fast") */ + __u8 enabled; + __u8 duty_off; /* % of duty_length the led is off (0xff means 100%) */ + __u8 duty_on; /* % of duty_length the led is on (0xff mean 100%) */ +} __packed; + +struct sixaxis_rumble { + __u8 padding; + __u8 right_duration; /* Right motor duration (0xff means forever) */ + __u8 right_motor_on; /* Right (small) motor on/off, only supports values of 0 or 1 (off/on) */ + __u8 left_duration; /* Left motor duration (0xff means forever) */ + __u8 left_motor_force; /* left (large) motor, supports force values from 0 to 255 */ +} __packed; + +struct sixaxis_output_report { + __u8 report_id; + struct sixaxis_rumble rumble; + __u8 padding[4]; + __u8 leds_bitmap; /* bitmap of enabled LEDs: LED_1 = 0x02, LED_2 = 0x04, ... */ + struct sixaxis_led led[4]; /* LEDx at (4 - x) */ + struct sixaxis_led _reserved; /* LED5, not actually soldered */ +} __packed; + +union sixaxis_output_report_01 { + struct sixaxis_output_report data; + __u8 buf[36]; +}; + static spinlock_t sony_dev_list_lock; static LIST_HEAD(sony_device_list); +static DEFINE_IDA(sony_device_id_allocator); struct sony_sc { spinlock_t lock; @@ -728,6 +760,7 @@ struct sony_sc { unsigned long quirks; struct work_struct state_worker; struct power_supply battery; + int device_id; #ifdef CONFIG_SONY_FF __u8 left; @@ -740,6 +773,8 @@ struct sony_sc { __u8 battery_charging; __u8 battery_capacity; __u8 led_state[MAX_LEDS]; + __u8 led_delay_on[MAX_LEDS]; + __u8 led_delay_off[MAX_LEDS]; __u8 led_count; }; @@ -1048,6 +1083,52 @@ static int dualshock4_set_operational_bt(struct hid_device *hdev) HID_FEATURE_REPORT, HID_REQ_GET_REPORT); } +static void sixaxis_set_leds_from_id(int id, __u8 values[MAX_LEDS]) +{ + static const __u8 sixaxis_leds[10][4] = { + { 0x01, 0x00, 0x00, 0x00 }, + { 0x00, 0x01, 0x00, 0x00 }, + { 0x00, 0x00, 0x01, 0x00 }, + { 0x00, 0x00, 0x00, 0x01 }, + { 0x01, 0x00, 0x00, 0x01 }, + { 0x00, 0x01, 0x00, 0x01 }, + { 0x00, 0x00, 0x01, 0x01 }, + { 0x01, 0x00, 0x01, 0x01 }, + { 0x00, 0x01, 0x01, 0x01 }, + { 0x01, 0x01, 0x01, 0x01 } + }; + + BUG_ON(MAX_LEDS < ARRAY_SIZE(sixaxis_leds[0])); + + if (id < 0) + return; + + id %= 10; + memcpy(values, sixaxis_leds[id], sizeof(sixaxis_leds[id])); +} + +static void dualshock4_set_leds_from_id(int id, __u8 values[MAX_LEDS]) +{ + /* The first 4 color/index entries match what the PS4 assigns */ + static const __u8 color_code[7][3] = { + /* Blue */ { 0x00, 0x00, 0x01 }, + /* Red */ { 0x01, 0x00, 0x00 }, + /* Green */ { 0x00, 0x01, 0x00 }, + /* Pink */ { 0x02, 0x00, 0x01 }, + /* Orange */ { 0x02, 0x01, 0x00 }, + /* Teal */ { 0x00, 0x01, 0x01 }, + /* White */ { 0x01, 0x01, 0x01 } + }; + + BUG_ON(MAX_LEDS < ARRAY_SIZE(color_code[0])); + + if (id < 0) + return; + + id %= 7; + memcpy(values, color_code[id], sizeof(color_code[id])); +} + static void buzz_set_leds(struct hid_device *hdev, const __u8 *leds) { struct list_head *report_list = @@ -1066,19 +1147,18 @@ static void buzz_set_leds(struct hid_device *hdev, const __u8 *leds) hid_hw_request(hdev, report, HID_REQ_SET_REPORT); } -static void sony_set_leds(struct hid_device *hdev, const __u8 *leds, int count) +static void sony_set_leds(struct sony_sc *sc, const __u8 *leds, int count) { - struct sony_sc *drv_data = hid_get_drvdata(hdev); int n; BUG_ON(count > MAX_LEDS); - if (drv_data->quirks & BUZZ_CONTROLLER && count == 4) { - buzz_set_leds(hdev, leds); + if (sc->quirks & BUZZ_CONTROLLER && count == 4) { + buzz_set_leds(sc->hdev, leds); } else { for (n = 0; n < count; n++) - drv_data->led_state[n] = leds[n]; - schedule_work(&drv_data->state_worker); + sc->led_state[n] = leds[n]; + schedule_work(&sc->state_worker); } } @@ -1090,6 +1170,7 @@ static void sony_led_set_brightness(struct led_classdev *led, struct sony_sc *drv_data; int n; + int force_update; drv_data = hid_get_drvdata(hdev); if (!drv_data) { @@ -1097,12 +1178,29 @@ static void sony_led_set_brightness(struct led_classdev *led, return; } + /* + * The Sixaxis on USB will override any LED settings sent to it + * and keep flashing all of the LEDs until the PS button is pressed. + * Updates, even if redundant, must be always be sent to the + * controller to avoid having to toggle the state of an LED just to + * stop the flashing later on. + */ + force_update = !!(drv_data->quirks & SIXAXIS_CONTROLLER_USB); + for (n = 0; n < drv_data->led_count; n++) { - if (led == drv_data->leds[n]) { - if (value != drv_data->led_state[n]) { - drv_data->led_state[n] = value; - sony_set_leds(hdev, drv_data->led_state, drv_data->led_count); - } + if (led == drv_data->leds[n] && (force_update || + (value != drv_data->led_state[n] || + drv_data->led_delay_on[n] || + drv_data->led_delay_off[n]))) { + + drv_data->led_state[n] = value; + + /* Setting the brightness stops the blinking */ + drv_data->led_delay_on[n] = 0; + drv_data->led_delay_off[n] = 0; + + sony_set_leds(drv_data, drv_data->led_state, + drv_data->led_count); break; } } @@ -1130,63 +1228,112 @@ static enum led_brightness sony_led_get_brightness(struct led_classdev *led) return LED_OFF; } -static void sony_leds_remove(struct hid_device *hdev) +static int sony_led_blink_set(struct led_classdev *led, unsigned long *delay_on, + unsigned long *delay_off) { - struct sony_sc *drv_data; - struct led_classdev *led; + struct device *dev = led->dev->parent; + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct sony_sc *drv_data = hid_get_drvdata(hdev); int n; + __u8 new_on, new_off; - drv_data = hid_get_drvdata(hdev); - BUG_ON(!(drv_data->quirks & SONY_LED_SUPPORT)); + if (!drv_data) { + hid_err(hdev, "No device data\n"); + return -EINVAL; + } + + /* Max delay is 255 deciseconds or 2550 milliseconds */ + if (*delay_on > 2550) + *delay_on = 2550; + if (*delay_off > 2550) + *delay_off = 2550; + + /* Blink at 1 Hz if both values are zero */ + if (!*delay_on && !*delay_off) + *delay_on = *delay_off = 500; + + new_on = *delay_on / 10; + new_off = *delay_off / 10; for (n = 0; n < drv_data->led_count; n++) { - led = drv_data->leds[n]; - drv_data->leds[n] = NULL; + if (led == drv_data->leds[n]) + break; + } + + /* This LED is not registered on this device */ + if (n >= drv_data->led_count) + return -EINVAL; + + /* Don't schedule work if the values didn't change */ + if (new_on != drv_data->led_delay_on[n] || + new_off != drv_data->led_delay_off[n]) { + drv_data->led_delay_on[n] = new_on; + drv_data->led_delay_off[n] = new_off; + schedule_work(&drv_data->state_worker); + } + + return 0; +} + +static void sony_leds_remove(struct sony_sc *sc) +{ + struct led_classdev *led; + int n; + + BUG_ON(!(sc->quirks & SONY_LED_SUPPORT)); + + for (n = 0; n < sc->led_count; n++) { + led = sc->leds[n]; + sc->leds[n] = NULL; if (!led) continue; led_classdev_unregister(led); kfree(led); } - drv_data->led_count = 0; + sc->led_count = 0; } -static int sony_leds_init(struct hid_device *hdev) +static int sony_leds_init(struct sony_sc *sc) { - struct sony_sc *drv_data; + struct hid_device *hdev = sc->hdev; int n, ret = 0; - int max_brightness; - int use_colors; + int use_ds4_names; struct led_classdev *led; size_t name_sz; char *name; size_t name_len; const char *name_fmt; - static const char * const color_str[] = { "red", "green", "blue" }; - static const __u8 initial_values[MAX_LEDS] = { 0x00, 0x00, 0x00, 0x00 }; + static const char * const ds4_name_str[] = { "red", "green", "blue", + "global" }; + __u8 initial_values[MAX_LEDS] = { 0 }; + __u8 max_brightness[MAX_LEDS] = { 1 }; + __u8 use_hw_blink[MAX_LEDS] = { 0 }; - drv_data = hid_get_drvdata(hdev); - BUG_ON(!(drv_data->quirks & SONY_LED_SUPPORT)); + BUG_ON(!(sc->quirks & SONY_LED_SUPPORT)); - if (drv_data->quirks & BUZZ_CONTROLLER) { - drv_data->led_count = 4; - max_brightness = 1; - use_colors = 0; + if (sc->quirks & BUZZ_CONTROLLER) { + sc->led_count = 4; + use_ds4_names = 0; name_len = strlen("::buzz#"); name_fmt = "%s::buzz%d"; /* Validate expected report characteristics. */ if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 7)) return -ENODEV; - } else if (drv_data->quirks & DUALSHOCK4_CONTROLLER) { - drv_data->led_count = 3; - max_brightness = 255; - use_colors = 1; + } else if (sc->quirks & DUALSHOCK4_CONTROLLER) { + dualshock4_set_leds_from_id(sc->device_id, initial_values); + initial_values[3] = 1; + sc->led_count = 4; + memset(max_brightness, 255, 3); + use_hw_blink[3] = 1; + use_ds4_names = 1; name_len = 0; name_fmt = "%s:%s"; } else { - drv_data->led_count = 4; - max_brightness = 1; - use_colors = 0; + sixaxis_set_leds_from_id(sc->device_id, initial_values); + sc->led_count = 4; + memset(use_hw_blink, 1, 4); + use_ds4_names = 0; name_len = strlen("::sony#"); name_fmt = "%s::sony%d"; } @@ -1196,14 +1343,14 @@ static int sony_leds_init(struct hid_device *hdev) * only relevant if the driver is loaded after somebody actively set the * LEDs to on */ - sony_set_leds(hdev, initial_values, drv_data->led_count); + sony_set_leds(sc, initial_values, sc->led_count); name_sz = strlen(dev_name(&hdev->dev)) + name_len + 1; - for (n = 0; n < drv_data->led_count; n++) { + for (n = 0; n < sc->led_count; n++) { - if (use_colors) - name_sz = strlen(dev_name(&hdev->dev)) + strlen(color_str[n]) + 2; + if (use_ds4_names) + name_sz = strlen(dev_name(&hdev->dev)) + strlen(ds4_name_str[n]) + 2; led = kzalloc(sizeof(struct led_classdev) + name_sz, GFP_KERNEL); if (!led) { @@ -1213,30 +1360,35 @@ static int sony_leds_init(struct hid_device *hdev) } name = (void *)(&led[1]); - if (use_colors) - snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), color_str[n]); + if (use_ds4_names) + snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), + ds4_name_str[n]); else snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1); led->name = name; - led->brightness = 0; - led->max_brightness = max_brightness; + led->brightness = initial_values[n]; + led->max_brightness = max_brightness[n]; led->brightness_get = sony_led_get_brightness; led->brightness_set = sony_led_set_brightness; + if (use_hw_blink[n]) + led->blink_set = sony_led_blink_set; + + sc->leds[n] = led; + ret = led_classdev_register(&hdev->dev, led); if (ret) { hid_err(hdev, "Failed to register LED %d\n", n); + sc->leds[n] = NULL; kfree(led); goto error_leds; } - - drv_data->leds[n] = led; } return ret; error_leds: - sony_leds_remove(hdev); + sony_leds_remove(sc); return ret; } @@ -1244,29 +1396,52 @@ error_leds: static void sixaxis_state_worker(struct work_struct *work) { struct sony_sc *sc = container_of(work, struct sony_sc, state_worker); - unsigned char buf[] = { - 0x01, - 0x00, 0xff, 0x00, 0xff, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0x27, 0x10, 0x00, 0x32, - 0xff, 0x27, 0x10, 0x00, 0x32, - 0xff, 0x27, 0x10, 0x00, 0x32, - 0xff, 0x27, 0x10, 0x00, 0x32, - 0x00, 0x00, 0x00, 0x00, 0x00 + int n; + union sixaxis_output_report_01 report = { + .buf = { + 0x01, + 0x00, 0xff, 0x00, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x27, 0x10, 0x00, 0x32, + 0xff, 0x27, 0x10, 0x00, 0x32, + 0xff, 0x27, 0x10, 0x00, 0x32, + 0xff, 0x27, 0x10, 0x00, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00 + } }; #ifdef CONFIG_SONY_FF - buf[3] = sc->right ? 1 : 0; - buf[5] = sc->left; + report.data.rumble.right_motor_on = sc->right ? 1 : 0; + report.data.rumble.left_motor_force = sc->left; #endif - buf[10] |= sc->led_state[0] << 1; - buf[10] |= sc->led_state[1] << 2; - buf[10] |= sc->led_state[2] << 3; - buf[10] |= sc->led_state[3] << 4; + report.data.leds_bitmap |= sc->led_state[0] << 1; + report.data.leds_bitmap |= sc->led_state[1] << 2; + report.data.leds_bitmap |= sc->led_state[2] << 3; + report.data.leds_bitmap |= sc->led_state[3] << 4; + + /* Set flag for all leds off, required for 3rd party INTEC controller */ + if ((report.data.leds_bitmap & 0x1E) == 0) + report.data.leds_bitmap |= 0x20; - hid_hw_raw_request(sc->hdev, 0x01, buf, sizeof(buf), HID_OUTPUT_REPORT, - HID_REQ_SET_REPORT); + /* + * The LEDs in the report are indexed in reverse order to their + * corresponding light on the controller. + * Index 0 = LED 4, index 1 = LED 3, etc... + * + * In the case of both delay values being zero (blinking disabled) the + * default report values should be used or the controller LED will be + * always off. + */ + for (n = 0; n < 4; n++) { + if (sc->led_delay_on[n] || sc->led_delay_off[n]) { + report.data.led[3 - n].duty_off = sc->led_delay_off[n]; + report.data.led[3 - n].duty_on = sc->led_delay_on[n]; + } + } + + hid_hw_raw_request(sc->hdev, report.data.report_id, report.buf, + sizeof(report), HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); } static void dualshock4_state_worker(struct work_struct *work) @@ -1279,7 +1454,7 @@ static void dualshock4_state_worker(struct work_struct *work) if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { buf[0] = 0x05; - buf[1] = 0x03; + buf[1] = 0xFF; offset = 4; } else { buf[0] = 0x11; @@ -1295,9 +1470,18 @@ static void dualshock4_state_worker(struct work_struct *work) offset += 2; #endif - buf[offset++] = sc->led_state[0]; - buf[offset++] = sc->led_state[1]; - buf[offset++] = sc->led_state[2]; + /* LED 3 is the global control */ + if (sc->led_state[3]) { + buf[offset++] = sc->led_state[0]; + buf[offset++] = sc->led_state[1]; + buf[offset++] = sc->led_state[2]; + } else { + offset += 3; + } + + /* If both delay values are zero the DualShock 4 disables blinking. */ + buf[offset++] = sc->led_delay_on[3]; + buf[offset++] = sc->led_delay_off[3]; if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) hid_hw_output_report(hdev, buf, 32); @@ -1323,9 +1507,9 @@ static int sony_play_effect(struct input_dev *dev, void *data, return 0; } -static int sony_init_ff(struct hid_device *hdev) +static int sony_init_ff(struct sony_sc *sc) { - struct hid_input *hidinput = list_entry(hdev->inputs.next, + struct hid_input *hidinput = list_entry(sc->hdev->inputs.next, struct hid_input, list); struct input_dev *input_dev = hidinput->input; @@ -1334,7 +1518,7 @@ static int sony_init_ff(struct hid_device *hdev) } #else -static int sony_init_ff(struct hid_device *hdev) +static int sony_init_ff(struct sony_sc *sc) { return 0; } @@ -1384,8 +1568,6 @@ static int sony_battery_get_property(struct power_supply *psy, static int sony_battery_probe(struct sony_sc *sc) { - static atomic_t power_id_seq = ATOMIC_INIT(0); - unsigned long power_id; struct hid_device *hdev = sc->hdev; int ret; @@ -1395,15 +1577,13 @@ static int sony_battery_probe(struct sony_sc *sc) */ sc->battery_capacity = 100; - power_id = (unsigned long)atomic_inc_return(&power_id_seq); - sc->battery.properties = sony_battery_props; sc->battery.num_properties = ARRAY_SIZE(sony_battery_props); sc->battery.get_property = sony_battery_get_property; sc->battery.type = POWER_SUPPLY_TYPE_BATTERY; sc->battery.use_for_apm = 0; - sc->battery.name = kasprintf(GFP_KERNEL, "sony_controller_battery_%lu", - power_id); + sc->battery.name = kasprintf(GFP_KERNEL, "sony_controller_battery_%pMR", + sc->mac_address); if (!sc->battery.name) return -ENOMEM; @@ -1578,6 +1758,52 @@ static int sony_check_add(struct sony_sc *sc) return sony_check_add_dev_list(sc); } +static int sony_set_device_id(struct sony_sc *sc) +{ + int ret; + + /* + * Only DualShock 4 or Sixaxis controllers get an id. + * All others are set to -1. + */ + if ((sc->quirks & SIXAXIS_CONTROLLER) || + (sc->quirks & DUALSHOCK4_CONTROLLER)) { + ret = ida_simple_get(&sony_device_id_allocator, 0, 0, + GFP_KERNEL); + if (ret < 0) { + sc->device_id = -1; + return ret; + } + sc->device_id = ret; + } else { + sc->device_id = -1; + } + + return 0; +} + +static void sony_release_device_id(struct sony_sc *sc) +{ + if (sc->device_id >= 0) { + ida_simple_remove(&sony_device_id_allocator, sc->device_id); + sc->device_id = -1; + } +} + +static inline void sony_init_work(struct sony_sc *sc, + void (*worker)(struct work_struct *)) +{ + if (!sc->worker_initialized) + INIT_WORK(&sc->state_worker, worker); + + sc->worker_initialized = 1; +} + +static inline void sony_cancel_work_sync(struct sony_sc *sc) +{ + if (sc->worker_initialized) + cancel_work_sync(&sc->state_worker); +} static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -1615,6 +1841,12 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } + ret = sony_set_device_id(sc); + if (ret < 0) { + hid_err(hdev, "failed to allocate the device id\n"); + goto err_stop; + } + if (sc->quirks & SIXAXIS_CONTROLLER_USB) { /* * The Sony Sixaxis does not handle HID Output Reports on the @@ -1629,8 +1861,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID; ret = sixaxis_set_operational_usb(hdev); - sc->worker_initialized = 1; - INIT_WORK(&sc->state_worker, sixaxis_state_worker); + sony_init_work(sc, sixaxis_state_worker); } else if (sc->quirks & SIXAXIS_CONTROLLER_BT) { /* * The Sixaxis wants output reports sent on the ctrl endpoint @@ -1638,8 +1869,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) */ hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; ret = sixaxis_set_operational_bt(hdev); - sc->worker_initialized = 1; - INIT_WORK(&sc->state_worker, sixaxis_state_worker); + sony_init_work(sc, sixaxis_state_worker); } else if (sc->quirks & DUALSHOCK4_CONTROLLER) { if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) { /* @@ -1661,8 +1891,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret < 0) goto err_stop; - sc->worker_initialized = 1; - INIT_WORK(&sc->state_worker, dualshock4_state_worker); + sony_init_work(sc, dualshock4_state_worker); } else { ret = 0; } @@ -1675,7 +1904,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err_stop; if (sc->quirks & SONY_LED_SUPPORT) { - ret = sony_leds_init(hdev); + ret = sony_leds_init(sc); if (ret < 0) goto err_stop; } @@ -1694,7 +1923,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) } if (sc->quirks & SONY_FF_SUPPORT) { - ret = sony_init_ff(hdev); + ret = sony_init_ff(sc); if (ret < 0) goto err_close; } @@ -1704,12 +1933,12 @@ err_close: hid_hw_close(hdev); err_stop: if (sc->quirks & SONY_LED_SUPPORT) - sony_leds_remove(hdev); + sony_leds_remove(sc); if (sc->quirks & SONY_BATTERY_SUPPORT) sony_battery_remove(sc); - if (sc->worker_initialized) - cancel_work_sync(&sc->state_worker); + sony_cancel_work_sync(sc); sony_remove_dev_list(sc); + sony_release_device_id(sc); hid_hw_stop(hdev); return ret; } @@ -1719,18 +1948,19 @@ static void sony_remove(struct hid_device *hdev) struct sony_sc *sc = hid_get_drvdata(hdev); if (sc->quirks & SONY_LED_SUPPORT) - sony_leds_remove(hdev); + sony_leds_remove(sc); if (sc->quirks & SONY_BATTERY_SUPPORT) { hid_hw_close(hdev); sony_battery_remove(sc); } - if (sc->worker_initialized) - cancel_work_sync(&sc->state_worker); + sony_cancel_work_sync(sc); sony_remove_dev_list(sc); + sony_release_device_id(sc); + hid_hw_stop(hdev); } @@ -1775,6 +2005,22 @@ static struct hid_driver sony_driver = { .report_fixup = sony_report_fixup, .raw_event = sony_raw_event }; -module_hid_driver(sony_driver); + +static int __init sony_init(void) +{ + dbg_hid("Sony:%s\n", __func__); + + return hid_register_driver(&sony_driver); +} + +static void __exit sony_exit(void) +{ + dbg_hid("Sony:%s\n", __func__); + + ida_destroy(&sony_device_id_allocator); + hid_unregister_driver(&sony_driver); +} +module_init(sony_init); +module_exit(sony_exit); MODULE_LICENSE("GPL"); |