Files
libratbag/src/driver-asus.c
haytham d93a8bc474 asus: add support for the Omni receiver and new data layouts
Mice behind an ASUS Omni receiver all enumerate with the same usb id
(0b05:1ace), so the regular bus:vid:pid DeviceMatch cannot tell models
apart. Identify the model during probe instead:

- asus-rog-omni-receiver.device is a minimal bootstrap file that only
  declares Quirks=OMNI_RECEIVER, so the asus driver gets probed on the
  receiver's hidraw nodes
- the probe queries the serial number signature (03 12 12 02) and
  matches its prefix against the OmniSignature field of the .device
  files, then re-resolves the device data for the identified usb id
- an unknown signature fails the probe with -ENODEV, which also drops
  the receiver's keyboard and media control nodes

The Strix Impact III family answers with data two bytes earlier than the
original protocol (code plus params echo instead of code plus pad), so
two quirks cover the new layouts:

- LED_V2: per-zone LED responses with data at offset 4, brightness in
  0-100
- SETTINGS_V2: settings and DPI responses with data at offset 4,
  rate/debounce/snapping as single bytes

Also write DPI values with two bytes so presets above 12850 dpi work.

Verified on a ROG Strix Impact III Wireless: identification on hidraw4,
all 5 profiles with 8 buttons, 1 LED and DPI presets 400/800/1600/3200,
report rate 1000 Hz, debounce 12 ms, LED brightness 127 (50%). Writes
round-trip and survive a ratbagd restart.
2026-08-18 20:11:11 +03:00

718 lines
21 KiB
C

/*
* Copyright (C) 2021 Kyoken, kyoken@kyoken.ninja
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include <libevdev/libevdev.h>
#include <linux/input.h>
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "libratbag-private.h"
#include "libratbag-hidraw.h"
#include "libratbag-data.h"
#include "asus.h"
/* ButtonMapping configuration property defaults */
static int ASUS_CONFIG_BUTTON_MAPPING[] = {
0xf0, /* left */
0xf1, /* right (button 3 in xev) */
0xf2, /* middle (button 2 in xev) */
0xe4, /* backward */
0xe5, /* forward */
0xe6, /* DPI */
0xe8, /* wheel up */
0xe9, /* wheel down */
};
/* LedModes configuration property defaults */
static unsigned int ASUS_LED_MODE[] = {
RATBAG_LED_ON,
RATBAG_LED_BREATHING,
RATBAG_LED_CYCLE,
RATBAG_LED_ON, /* rainbow wave */
RATBAG_LED_ON, /* reactive - react to clicks */
RATBAG_LED_ON, /* custom - depends on mouse type */
RATBAG_LED_ON, /* battery - battery indicator */
};
struct asus_data {
uint8_t is_ready;
int button_mapping[ASUS_MAX_NUM_BUTTON * ASUS_MAX_NUM_BUTTON_GROUP];
int button_indices[ASUS_MAX_NUM_BUTTON * ASUS_MAX_NUM_BUTTON_GROUP];
int led_modes[ASUS_MAX_NUM_LED_MODES];
};
static int
asus_driver_load_profile(struct ratbag_device *device, struct ratbag_profile *profile, int dpi_preset)
{
int rc;
const struct _asus_binding *asus_binding;
const struct _asus_led *asus_led;
const struct asus_button *asus_button;
struct ratbag_button *button;
struct ratbag_led *led;
struct ratbag_resolution *resolution;
union asus_binding_data binding_data;
union asus_binding_data binding_data_secondary;
union asus_led_data led_data;
union asus_resolution_data resolution_data;
union asus_resolution_data xy_resolution_data;
unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions;
unsigned int led_count = ratbag_device_get_num_leds(device);
uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data);
struct asus_data *drv_data = ratbag_get_drv_data(device);
/* get buttons */
log_debug(device->ratbag, "Loading buttons data\n");
rc = asus_get_binding_data(device, &binding_data, 0);
if (rc)
return rc;
if (quirks & ASUS_QUIRK_BUTTONS_SECONDARY) {
rc = asus_get_binding_data(device, &binding_data_secondary, 1);
if (rc)
return rc;
}
ratbag_profile_for_each_button(profile, button) {
int asus_index = drv_data->button_indices[button->index];
if (asus_index == -1) {
log_debug(device->ratbag, "No mapping for button %d\n", button->index);
continue;
}
if (asus_index < ASUS_MAX_NUM_BUTTON) {
asus_binding = &binding_data.data.binding[asus_index];
} else {
asus_binding = &binding_data_secondary.data.binding[asus_index % ASUS_MAX_NUM_BUTTON];
}
/* disabled */
if (asus_binding->action == ASUS_BUTTON_CODE_DISABLED) {
button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE;
continue;
}
/* got action */
switch (asus_binding->type) {
case ASUS_BUTTON_ACTION_TYPE_KEY:
button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY;
rc = asus_get_linux_key_code(asus_binding->action);
if (rc > 0) {
button->action.action.key = (unsigned int)rc;
} else {
log_debug(device->ratbag, "Unknown button code %02x\n", asus_binding->action);
}
break;
case ASUS_BUTTON_ACTION_TYPE_BUTTON:
asus_button = asus_find_button_by_code(asus_binding->action);
if (asus_button != NULL) { /* found button to bind to */
button->action.type = asus_button->type;
if (asus_button->type == RATBAG_BUTTON_ACTION_TYPE_BUTTON)
button->action.action.button = asus_button->button;
else if (asus_button->type == RATBAG_BUTTON_ACTION_TYPE_SPECIAL)
button->action.action.special = asus_button->special;
} else {
log_debug(device->ratbag, "Unknown action code %02x\n", asus_binding->action);
}
break;
default:
break;
}
}
/* get DPIs */
log_debug(device->ratbag, "Loading resolutions data\n");
rc = asus_get_resolution_data(device, &resolution_data, false);
if (rc)
return rc;
if (quirks & ASUS_QUIRK_SEPARATE_XY_DPI) {
rc = asus_get_resolution_data(device, &xy_resolution_data, true);
if (rc)
return rc;
}
switch (dpi_count) {
case 2: /* 2 DPI presets */
profile->hz = resolution_data.data2.rate;
profile->angle_snapping = resolution_data.data2.snapping;
profile->debounce = resolution_data.data2.response;
ratbag_profile_for_each_resolution(profile, resolution) {
if (quirks & ASUS_QUIRK_SEPARATE_XY_DPI) {
ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION);
ratbag_resolution_set_resolution(
resolution,
xy_resolution_data.data_xy.dpi[resolution->index].x,
xy_resolution_data.data_xy.dpi[resolution->index].y);
} else {
ratbag_resolution_set_resolution(
resolution,
resolution_data.data2.dpi[resolution->index],
resolution_data.data2.dpi[resolution->index]);
}
if (dpi_preset != -1 && (unsigned int)dpi_preset == resolution->index)
resolution->is_active = true;
}
break;
case 4: /* 4 DPI presets */
profile->hz = resolution_data.data4.rate;
profile->angle_snapping = resolution_data.data4.snapping;
profile->debounce = resolution_data.data4.response;
ratbag_profile_for_each_resolution(profile, resolution) {
if (quirks & ASUS_QUIRK_SEPARATE_XY_DPI) {
ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION);
ratbag_resolution_set_resolution(
resolution,
xy_resolution_data.data_xy.dpi[resolution->index].x,
xy_resolution_data.data_xy.dpi[resolution->index].y);
} else {
ratbag_resolution_set_resolution(
resolution,
resolution_data.data4.dpi[resolution->index],
resolution_data.data4.dpi[resolution->index]);
}
if (dpi_preset != -1 && (unsigned int)dpi_preset == resolution->index)
resolution->is_active = true;
}
break;
default:
break;
}
/* get LEDs */
if (!(quirks & (ASUS_QUIRK_SEPARATE_LEDS | ASUS_QUIRK_LED_V2)) && led_count) {
log_debug(device->ratbag, "Loading LEDs data\n");
rc = asus_get_led_data(device, &led_data, 0);
if (rc)
return rc;
}
ratbag_profile_for_each_led(profile, led) {
if (quirks & (ASUS_QUIRK_SEPARATE_LEDS | ASUS_QUIRK_LED_V2)) {
log_debug(device->ratbag, "Loading LED %d data\n", led->index);
rc = asus_get_led_data(device, &led_data, led->index);
if (rc)
return rc;
}
if (quirks & ASUS_QUIRK_LED_V2) {
/* new layout: a single zone per response with data at
* offset 4, brightness is 0-100 percent */
led->mode = drv_data->led_modes[led_data.raw[4]];
led->brightness = led_data.raw[5] * 255 / 100;
led->color.red = led_data.raw[6];
led->color.green = led_data.raw[7];
led->color.blue = led_data.raw[8];
continue;
}
if (quirks & ASUS_QUIRK_SEPARATE_LEDS)
asus_led = &led_data.data.led[0];
else
asus_led = &led_data.data.led[led->index];
led->mode = drv_data->led_modes[asus_led->mode];
if (quirks & ASUS_QUIRK_RAW_BRIGHTNESS) {
led->brightness = asus_led->brightness;
} else {
/* convert brightness from 0-4 to 0-256 */
led->brightness = asus_led->brightness * 64;
}
led->color.red = asus_led->r;
led->color.green = asus_led->g;
led->color.blue = asus_led->b;
}
return 0;
}
static int
asus_driver_save_profile(struct ratbag_device *device, struct ratbag_profile *profile)
{
int rc = 0;
struct ratbag_button *button;
struct ratbag_led *led;
struct ratbag_resolution *resolution;
struct asus_data *drv_data = ratbag_get_drv_data(device);
uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data);
/* set buttons */
ratbag_profile_for_each_button(profile, button) {
if (!button->dirty)
continue;
int asus_index = drv_data->button_indices[button->index];
if (asus_index == -1) {
log_debug(device->ratbag, "No mapping for button %d\n", button->index);
continue;
}
const struct asus_button *asus_button;
uint8_t asus_code_src;
uint8_t asus_code_dst;
bool is_joystick;
rc = drv_data->button_mapping[asus_index];
if (rc == -1) {
log_debug(device->ratbag, "No mapping for button %d\n", button->index);
continue;
}
asus_code_src = (uint8_t)rc;
log_debug(device->ratbag, "Button %d (%02x) changed\n",
button->index, asus_code_src);
switch (button->action.type) {
case RATBAG_BUTTON_ACTION_TYPE_NONE:
rc = asus_set_button_action(
device, asus_code_src, ASUS_BUTTON_CODE_DISABLED,
ASUS_BUTTON_ACTION_TYPE_BUTTON);
break;
case RATBAG_BUTTON_ACTION_TYPE_KEY:
/* Linux code to ASUS code */
rc = asus_find_key_code(button->action.action.key);
if (rc != -1) {
asus_code_dst = (uint8_t)rc;
rc = asus_set_button_action(
device, asus_code_src, asus_code_dst,
ASUS_BUTTON_ACTION_TYPE_KEY);
}
break;
case RATBAG_BUTTON_ACTION_TYPE_BUTTON:
case RATBAG_BUTTON_ACTION_TYPE_SPECIAL:
/* ratbag action to ASUS code */
is_joystick = asus_code_is_joystick(asus_code_src);
if (is_joystick) {
asus_button = asus_find_button_by_action(button->action, true);
if (asus_button != NULL) { /* found button to bind to */
rc = asus_set_button_action(
device, asus_code_src, asus_button->asus_code,
ASUS_BUTTON_ACTION_TYPE_BUTTON);
break;
}
}
asus_button = asus_find_button_by_action(button->action, false);
if (asus_button != NULL) /* found button to bind to */
rc = asus_set_button_action(
device, asus_code_src, asus_button->asus_code,
ASUS_BUTTON_ACTION_TYPE_BUTTON);
break;
default:
break;
}
if (rc)
return rc;
}
/* set extra settings */
if (profile->rate_dirty) {
log_debug(device->ratbag, "Polling rate changed to %d Hz\n", profile->hz);
rc = asus_set_polling_rate(device, profile->hz);
if (rc)
return rc;
}
if (profile->angle_snapping_dirty) {
log_debug(device->ratbag, "Angle snapping changed to %d\n", profile->angle_snapping);
rc = asus_set_angle_snapping(device, profile->angle_snapping);
if (rc)
return rc;
}
if (profile->debounce_dirty) {
log_debug(device->ratbag, "Debounce time changed to %d\n", profile->debounce);
rc = asus_set_button_response(device, profile->debounce);
if (rc)
return rc;
}
/* set DPIs */
ratbag_profile_for_each_resolution(profile, resolution) {
if (!resolution->dirty)
continue;
log_debug(
device->ratbag, "Resolution %d changed to %d\n",
resolution->index, resolution->dpi_x);
rc = asus_set_dpi(device, resolution->index, resolution->dpi_x);
if (rc)
return rc;
}
/* set LEDs */
ratbag_profile_for_each_led(profile, led) {
if (!led->dirty)
continue;
log_debug(device->ratbag, "LED %d changed\n", led->index);
uint8_t led_mode = 0;
for (unsigned int i = 0; i < ASUS_MAX_NUM_LED_MODES; i++) {
if (drv_data->led_modes[i] == (int) led->mode) {
led_mode = i;
break;
}
}
uint8_t led_brightness;
if (quirks & ASUS_QUIRK_RAW_BRIGHTNESS) {
led_brightness = led->brightness;
} else if (quirks & ASUS_QUIRK_LED_V2) {
/* convert brightness from 0-255 to 0-100 percent */
led_brightness = (uint8_t)round((double)led->brightness * 100.0 / 255.0);
} else {
/* convert brightness from 0-256 to 0-4 */
led_brightness = (uint8_t)round((double)led->brightness / 64.0);
}
rc = asus_set_led(device, led->index, led_mode, led_brightness, led->color);
if (rc)
return rc;
}
return 0;
}
static int
asus_driver_load_profiles(struct ratbag_device *device)
{
int rc;
struct asus_profile_data profile_data;
struct ratbag_profile *profile;
unsigned int current_profile_id = 0;
/* get current profile id */
rc = asus_get_profile_data(device, &profile_data);
if (rc)
return rc;
if (device->num_profiles > 1) {
current_profile_id = profile_data.profile_id;
log_debug(device->ratbag, "Initial profile is %d\n", current_profile_id);
}
log_debug(
device->ratbag, "Primary version %02X.%02X.%02X\n",
profile_data.version_primary_major,
profile_data.version_primary_minor,
profile_data.version_primary_build);
log_debug(
device->ratbag, "Secondary version %02X.%02X.%02X\n",
profile_data.version_secondary_major,
profile_data.version_secondary_minor,
profile_data.version_secondary_build);
/* read ratbag profiles */
ratbag_device_for_each_profile(device, profile) {
if (profile->index == current_profile_id) { /* profile is already selected */
profile->is_active = true;
} else { /* switch profile */
profile->is_active = false;
log_debug(device->ratbag, "Switching to profile %d\n", profile->index);
rc = asus_set_profile(device, profile->index);
if (rc)
return rc;
}
rc = asus_driver_load_profile(device, profile, profile_data.dpi_preset);
if (rc)
return rc;
}
/* back to initial profile */
if (device->num_profiles > 1) {
log_debug(device->ratbag, "Switching back to initial profile %d\n", current_profile_id);
rc = asus_set_profile(device, current_profile_id);
if (rc)
return rc;
}
return 0;
}
static int
asus_driver_save_profiles(struct ratbag_device *device)
{
int rc;
struct asus_profile_data profile_data;
struct ratbag_profile *profile;
unsigned int current_profile_id = 0;
/* get current profile id */
if (device->num_profiles > 1) {
rc = asus_get_profile_data(device, &profile_data);
if (rc)
return rc;
current_profile_id = profile_data.profile_id;
log_debug(device->ratbag, "Initial profile is %d\n", current_profile_id);
}
ratbag_device_for_each_profile(device, profile) {
if (!profile->dirty)
continue;
log_debug(device->ratbag, "Profile %d changed\n", profile->index);
/* switch profile */
if (profile->index != current_profile_id) {
log_debug(device->ratbag, "Switching to profile %d\n", profile->index);
rc = asus_set_profile(device, profile->index);
if (rc)
return rc;
}
rc = asus_driver_save_profile(device, profile);
if (rc)
return rc;
/* save profile */
log_debug(device->ratbag, "Saving profile\n");
rc = asus_save_profile(device);
if (rc)
return rc;
}
/* back to initial profile */
if (device->num_profiles > 1) {
log_debug(device->ratbag, "Switching back to initial profile %d\n", current_profile_id);
rc = asus_set_profile(device, current_profile_id);
if (rc)
return rc;
}
return 0;
}
static int
asus_driver_probe(struct ratbag_device *device)
{
int rc;
unsigned int profile_count, dpi_count, button_count, led_count;
const struct asus_button *asus_button;
struct asus_data *drv_data;
struct asus_profile_data profile_data;
struct ratbag_profile *profile;
struct ratbag_button *button;
struct ratbag_resolution *resolution;
struct ratbag_led *led;
uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data);
rc = ratbag_open_hidraw(device);
if (rc)
return rc;
/* The Omni receiver is a shared USB device for multiple mice, so the
* actual model has to be identified and resolved to its own .device
* data before anything else. Wrong hidraw nodes (keyboard, media
* control, etc.) fail the identification and are rejected here. */
if (quirks & ASUS_QUIRK_OMNI_RECEIVER) {
struct ratbag_device_data *data;
struct input_id id = device->ids;
rc = asus_omni_identify(device, &id);
if (rc) {
log_debug(device->ratbag,
"Omni device is not a supported mouse\n");
ratbag_close_hidraw(device);
return -ENODEV;
}
data = ratbag_device_data_new_for_id(device->ratbag, &id);
if (!data) {
log_error(device->ratbag,
"No device data for Omni mouse %04x:%04x\n",
id.vendor, id.product);
ratbag_close_hidraw(device);
return -ENODEV;
}
ratbag_device_data_unref(device->data);
device->data = data;
device->devicetype = ratbag_device_data_get_device_type(device->data);
free(device->name);
device->name = strdup_safe(ratbag_device_data_get_name(device->data));
log_info(device->ratbag,
"ASUS Omni receiver identified device: %s\n",
device->name);
quirks = ratbag_device_data_asus_get_quirks(device->data);
}
rc = asus_get_profile_data(device, &profile_data);
/* create device state data */
drv_data = zalloc(sizeof(*drv_data));
ratbag_set_drv_data(device, drv_data);
drv_data->is_ready = 1;
/* get device properties from configuration file */
profile_count = ratbag_device_data_asus_get_profile_count(device->data);
dpi_count = ratbag_device_data_asus_get_dpi_count(device->data);
button_count = ratbag_device_data_asus_get_button_count(device->data);
led_count = ratbag_device_data_asus_get_led_count(device->data);
const int *bm = ratbag_device_data_asus_get_button_mapping(device->data);
const int *led_modes = ratbag_device_data_asus_get_led_modes(device->data);
/* merge ButtonMapping configuration property with defaults */
for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON * ASUS_MAX_NUM_BUTTON_GROUP; i++) {
if (bm[i] != -1) {
drv_data->button_mapping[i] = bm[i];
} else {
if (i < ARRAY_LENGTH(ASUS_CONFIG_BUTTON_MAPPING)) {
drv_data->button_mapping[i] = ASUS_CONFIG_BUTTON_MAPPING[i];
} else {
drv_data->button_mapping[i] = -1;
}
}
drv_data->button_indices[i] = -1;
}
/* merge LedModes configuration property with defaults */
for (unsigned int i = 0; i < ASUS_MAX_NUM_LED_MODES; i++)
drv_data->led_modes[i] = (led_modes[i] != -1) ? led_modes[i] : (int) ASUS_LED_MODE[i];
/* setup a lookup table for all defined buttons */
unsigned int button_index = 0;
ARRAY_FOR_EACH(ASUS_BUTTON_MAPPING, asus_button) {
/* search for this button in the ButtonMapping by it's ASUS code */
for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON * ASUS_MAX_NUM_BUTTON_GROUP; i++) {
if (drv_data->button_mapping[i] == (int)asus_button->asus_code) {
/* add button to indices array */
drv_data->button_indices[button_index] = (int)i;
log_debug(device->ratbag, "Button %d is mapped to 0x%02x at position %d group %d\n",
button_index, (uint8_t)drv_data->button_mapping[i],
i % ASUS_MAX_NUM_BUTTON, i / ASUS_MAX_NUM_BUTTON);
button_index++;
break;
}
}
}
/* init profiles */
ratbag_device_init_profiles(
device,
max(profile_count, 1),
max(dpi_count, 2),
max(button_count, 8),
max(led_count, 0));
/* setup profiles */
ratbag_device_for_each_profile(device, profile) {
if (profile->index == 0) {
profile->is_active = true;
}
asus_setup_profile(device, profile);
ratbag_profile_for_each_button(profile, button)
asus_setup_button(device, button);
ratbag_profile_for_each_resolution(profile, resolution)
asus_setup_resolution(device, resolution);
ratbag_profile_for_each_led(profile, led)
asus_setup_led(device, led);
}
/* load profiles */
rc = asus_driver_load_profiles(device);
if (rc == ASUS_STATUS_ERROR) { /* mouse in invalid state */
drv_data->is_ready = 0;
} else if (rc) { /* other errors */
log_error(
device->ratbag, "Can't talk to the mouse: '%s' (%d)\n",
strerror(-rc), rc);
free(drv_data);
ratbag_set_drv_data(device, NULL);
return -ENODEV;
}
return 0;
}
static void
asus_driver_remove(struct ratbag_device *device)
{
ratbag_close_hidraw(device);
free(ratbag_get_drv_data(device));
}
static int
asus_driver_commit(struct ratbag_device *device)
{
int rc;
struct asus_data *drv_data;
/* check last device state */
drv_data = ratbag_get_drv_data(device);
if (!drv_data->is_ready) { /* device was not ready */
log_error(device->ratbag, "Device was not ready, trying to reload\n");
rc = asus_driver_load_profiles(device);
if (rc) {
log_error(device->ratbag, "Device reloading failed (%d)\n", rc);
if (rc != ASUS_STATUS_ERROR)
return rc;
} else {
drv_data->is_ready = 1;
log_error(device->ratbag, "Device was successfully reloaded\n");
}
return RATBAG_ERROR_DEVICE; /* fail in any case because we tried to rollback instead of commit */
}
/* save profiles */
rc = asus_driver_save_profiles(device);
if (rc) {
log_error(device->ratbag, "Commit failed (%d)\n", rc);
if (rc != ASUS_STATUS_ERROR)
return rc;
return RATBAG_ERROR_DEVICE;
}
return 0;
}
struct ratbag_driver asus_driver = {
.name = "ASUS",
.id = "asus",
.probe = asus_driver_probe,
.remove = asus_driver_remove,
.commit = asus_driver_commit,
.set_active_profile = asus_set_profile,
};