Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064// SPDX-License-Identifier: GPL-2.0-or-later/* * HID driver for MSI Claw Handheld PC gamepads. * * Provides configuration support for the MSI Claw series of handheld PC * gamepads. Multiple iterations of the device firmware has led to some * quirks for how certain attributes are handled. The original firmware * did not support remapping of the M1 (right) and M2 (left) rear paddles. * Additionally, the MCU RAM address for writing configuration data has * changed twice. Checks are done during probe to enumerate these variances. * * Copyright (c) 2026 Zhouwang Huang <honjow311@gmail.com> * Copyright (c) 2026 Denis Benato <denis.benato@linux.dev> * Copyright (c) 2026 Valve Corporation */
#include <linux/array_size.h>#include <linux/cleanup.h>#include <linux/completion.h>#include <linux/container_of.h>#include <linux/delay.h>#include <linux/device.h>#include <linux/hid.h>#include <linux/kobject.h>#include <linux/led-class-multicolor.h>#include <linux/leds.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/pm.h>#include <linux/spinlock.h>#include <linux/sysfs.h>#include <linux/types.h>#include <linux/unaligned.h>#include <linux/usb.h>#include <linux/workqueue.h>
#include "hid-ids.h"
#define CLAW_OUTPUT_REPORT_ID 0x0f#define CLAW_INPUT_REPORT_ID 0x10
#define CLAW_PACKET_SIZE 64
#define CLAW_DINPUT_CFG_INTF_IN 0x82#define CLAW_XINPUT_CFG_INTF_IN 0x83
#define CLAW_KEYS_MAX 5
#define CLAW_RGB_ZONES 9#define CLAW_RGB_MAX_FRAMES 8#define CLAW_RGB_FRAME_OFFSET 0x24
enum claw_command_index { CLAW_COMMAND_TYPE_NONE = 0x00, CLAW_COMMAND_TYPE_READ_PROFILE = 0x04, CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05, CLAW_COMMAND_TYPE_ACK = 0x06, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA = 0x21, CLAW_COMMAND_TYPE_SYNC_TO_ROM = 0x22, CLAW_COMMAND_TYPE_SWITCH_MODE = 0x24, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE = 0x26, CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK = 0x27, CLAW_COMMAND_TYPE_RESET_DEVICE = 0x28,};
enum claw_gamepad_mode_index { CLAW_GAMEPAD_MODE_XINPUT = 0x01, CLAW_GAMEPAD_MODE_DINPUT = 0x02, CLAW_GAMEPAD_MODE_DESKTOP = 0x04,};
static const char * const claw_gamepad_mode_text[] = { [CLAW_GAMEPAD_MODE_XINPUT] = "xinput", [CLAW_GAMEPAD_MODE_DINPUT] = "dinput", [CLAW_GAMEPAD_MODE_DESKTOP] = "desktop",};
enum claw_profile_ack_pending { CLAW_NO_PENDING, CLAW_M1_PENDING, CLAW_M2_PENDING, CLAW_RGB_PENDING, CLAW_RUMBLE_LEFT_PENDING, CLAW_RUMBLE_RIGHT_PENDING,};
enum claw_key_index { CLAW_KEY_M1, CLAW_KEY_M2,};
enum claw_mkeys_function_index { CLAW_MKEY_FUNCTION_MACRO, CLAW_MKEY_FUNCTION_DISABLED, CLAW_MKEY_FUNCTION_COMBO,};
enum claw_mode_field { CLAW_FIELD_GAMEPAD_MODE, CLAW_FIELD_MKEYS_FUNCTION,};
static const char * const claw_mkeys_function_text[] = { [CLAW_MKEY_FUNCTION_MACRO] = "macro", [CLAW_MKEY_FUNCTION_DISABLED] = "disabled", [CLAW_MKEY_FUNCTION_COMBO] = "combination",};
static const struct { u8 code; const char *name;} claw_button_mapping_key_map[] = { /* Gamepad buttons */ { 0x01, "ABS_HAT0Y_UP" }, { 0x02, "ABS_HAT0Y_DOWN" }, { 0x03, "ABS_HAT0X_LEFT" }, { 0x04, "ABS_HAT0X_RIGHT" }, { 0x05, "BTN_TL" }, { 0x06, "BTN_TR" }, { 0x07, "BTN_THUMBL" }, { 0x08, "BTN_THUMBR" }, { 0x09, "BTN_SOUTH" }, { 0x0a, "BTN_EAST" }, { 0x0b, "BTN_NORTH" }, { 0x0c, "BTN_WEST" }, { 0x0d, "BTN_MODE" }, { 0x0e, "BTN_SELECT" }, { 0x0f, "BTN_START" }, { 0x13, "BTN_TL2"}, { 0x14, "BTN_TR2"}, { 0x15, "ABS_Y_UP"}, { 0x16, "ABS_Y_DOWN"}, { 0x17, "ABS_X_LEFT"}, { 0x18, "ABS_X_RIGHT"}, { 0x19, "ABS_RY_UP"}, { 0x1a, "ABS_RY_DOWN"}, { 0x1b, "ABS_RX_LEFT"}, { 0x1c, "ABS_RX_RIGHT"}, /* Keyboard keys */ { 0x32, "KEY_ESC" }, { 0x33, "KEY_F1" }, { 0x34, "KEY_F2" }, { 0x35, "KEY_F3" }, { 0x36, "KEY_F4" }, { 0x37, "KEY_F5" }, { 0x38, "KEY_F6" }, { 0x39, "KEY_F7" }, { 0x3a, "KEY_F8" }, { 0x3b, "KEY_F9" }, { 0x3c, "KEY_F10" }, { 0x3d, "KEY_F11" }, { 0x3e, "KEY_F12" }, { 0x3f, "KEY_GRAVE" }, { 0x40, "KEY_1" }, { 0x41, "KEY_2" }, { 0x42, "KEY_3" }, { 0x43, "KEY_4" }, { 0x44, "KEY_5" }, { 0x45, "KEY_6" }, { 0x46, "KEY_7" }, { 0x47, "KEY_8" }, { 0x48, "KEY_9" }, { 0x49, "KEY_0" }, { 0x4a, "KEY_MINUS" }, { 0x4b, "KEY_EQUAL" }, { 0x4c, "KEY_BACKSPACE" }, { 0x4d, "KEY_TAB" }, { 0x4e, "KEY_Q" }, { 0x4f, "KEY_W" }, { 0x50, "KEY_E" }, { 0x51, "KEY_R" }, { 0x52, "KEY_T" }, { 0x53, "KEY_Y" }, { 0x54, "KEY_U" }, { 0x55, "KEY_I" }, { 0x56, "KEY_O" }, { 0x57, "KEY_P" }, { 0x58, "KEY_LEFTBRACE" }, { 0x59, "KEY_RIGHTBRACE" }, { 0x5a, "KEY_BACKSLASH" }, { 0x5b, "KEY_CAPSLOCK" }, { 0x5c, "KEY_A" }, { 0x5d, "KEY_S" }, { 0x5e, "KEY_D" }, { 0x5f, "KEY_F" }, { 0x60, "KEY_G" }, { 0x61, "KEY_H" }, { 0x62, "KEY_J" }, { 0x63, "KEY_K" }, { 0x64, "KEY_L" }, { 0x65, "KEY_SEMICOLON" }, { 0x66, "KEY_APOSTROPHE" }, { 0x67, "KEY_ENTER" }, { 0x68, "KEY_LEFTSHIFT" }, { 0x69, "KEY_Z" }, { 0x6a, "KEY_X" }, { 0x6b, "KEY_C" }, { 0x6c, "KEY_V" }, { 0x6d, "KEY_B" }, { 0x6e, "KEY_N" }, { 0x6f, "KEY_M" }, { 0x70, "KEY_COMMA" }, { 0x71, "KEY_DOT" }, { 0x72, "KEY_SLASH" }, { 0x73, "KEY_RIGHTSHIFT" }, { 0x74, "KEY_LEFTCTRL" }, { 0x75, "KEY_LEFTMETA" }, { 0x76, "KEY_LEFTALT" }, { 0x77, "KEY_SPACE" }, { 0x78, "KEY_RIGHTALT" }, { 0x79, "KEY_RIGHTCTRL" }, { 0x7a, "KEY_INSERT" }, { 0x7b, "KEY_HOME" }, { 0x7c, "KEY_PAGEUP" }, { 0x7d, "KEY_DELETE" }, { 0x7e, "KEY_END" }, { 0x7f, "KEY_PAGEDOWN" }, { 0x8a, "KEY_KPENTER" }, { 0x8b, "KEY_KP0" }, { 0x8c, "KEY_KP1" }, { 0x8d, "KEY_KP2" }, { 0x8e, "KEY_KP3" }, { 0x8f, "KEY_KP4" }, { 0x90, "KEY_KP5" }, { 0x91, "KEY_KP6" }, { 0x92, "KEY_KP7" }, { 0x93, "KEY_KP8" }, { 0x94, "KEY_KP9" }, { 0x95, "MD_PLAY" }, { 0x96, "MD_STOP" }, { 0x97, "MD_NEXT" }, { 0x98, "MD_PREV" }, { 0x99, "MD_VOL_UP" }, { 0x9a, "MD_VOL_DOWN" }, { 0x9b, "MD_VOL_MUTE" }, { 0x9c, "KEY_F23" }, /* Mouse events */ { 0xc8, "BTN_LEFT" }, { 0xc9, "BTN_MIDDLE" }, { 0xca, "BTN_RIGHT" }, { 0xcb, "BTN_SIDE" }, { 0xcc, "BTN_EXTRA" }, { 0xcd, "REL_WHEEL_UP" }, { 0xce, "REL_WHEEL_DOWN" }, { 0xff, "DISABLED" },};
enum claw_rgb_effect_index { CLAW_RGB_EFFECT_MONOCOLOR, CLAW_RGB_EFFECT_BREATHE, CLAW_RGB_EFFECT_CHROMA, CLAW_RGB_EFFECT_RAINBOW, CLAW_RGB_EFFECT_FROSTFIRE,};
static const char * const claw_rgb_effect_text[] = { [CLAW_RGB_EFFECT_MONOCOLOR] = "monocolor", [CLAW_RGB_EFFECT_BREATHE] = "breathe", [CLAW_RGB_EFFECT_CHROMA] = "chroma", [CLAW_RGB_EFFECT_RAINBOW] = "rainbow", [CLAW_RGB_EFFECT_FROSTFIRE] = "frostfire",};
static const u16 button_mapping_addr_old[] = { 0x007a, /* M1 */ 0x011f, /* M2 */};
static const u16 button_mapping_addr_new[] = { 0x00bb, /* M1 */ 0x0164, /* M2 */};
static const u16 rgb_addr_old = 0x01fa;static const u16 rgb_addr_new = 0x024a;
static const u16 rumble_addr[] = { 0x0022, /* left */ 0x0023, /* right */};
struct claw_command_report { u8 report_id; u8 padding[2]; u8 header_tail; u8 cmd; u8 data[59];} __packed;
struct claw_profile_report { u8 profile; __be16 read_addr;} __packed;
struct claw_mkey_report { struct claw_profile_report; u8 padding_0; u8 padding_1; u8 padding_2; u8 codes[5];} __packed;
struct rgb_zone { u8 red; u8 green; u8 blue;};
struct rgb_frame { struct rgb_zone zone[CLAW_RGB_ZONES];};
struct claw_rgb_report { struct claw_profile_report; u8 frame_bytes; u8 padding; u8 frame_count; u8 state; /* Always 0x09 */ u8 speed; u8 brightness; struct rgb_frame zone_data;} __packed;
struct claw_rumble_report { struct claw_profile_report; u8 padding; u8 intensity;} __packed;
struct claw_drvdata { /* MCU General Variables */ enum claw_profile_ack_pending profile_pending; struct completion orphan_ack_complete; struct completion send_cmd_complete; struct delayed_work cfg_resume; struct delayed_work cfg_setup; spinlock_t registration_lock; /* Lock for registration read/write */ struct mutex profile_mutex; /* mutex for profile_pending calls */ spinlock_t profile_lock; /* Lock for profile_pending read/write */ struct hid_device *hdev; bool orphan_ack_pending; struct mutex cfg_mutex; /* mutex for synchronous data */ struct mutex rom_mutex; /* mutex for SYNC_TO_ROM calls */ spinlock_t cmd_lock; /* Lock for cmd data read/write */ u8 waiting_cmd; int cmd_status; u16 bcd_device; u8 ep;
/* Gamepad Variables */ enum claw_mkeys_function_index mkeys_function; enum claw_gamepad_mode_index gamepad_mode; u8 m1_codes[CLAW_KEYS_MAX]; u8 m2_codes[CLAW_KEYS_MAX]; u8 rumble_intensity_right; u8 rumble_intensity_left; const u16 *bmap_addr; spinlock_t rumble_lock; /* lock for rumble_intensity read/write */ spinlock_t mode_lock; /* Lock for mode data read/write */ bool rumble_support; bool gp_registered; bool bmap_support;
/* RGB Variables */ struct rgb_frame rgb_frames[CLAW_RGB_MAX_FRAMES]; enum claw_rgb_effect_index rgb_effect; struct led_classdev_mc led_mc; struct delayed_work rgb_queue; spinlock_t frame_lock; /* lock for rgb_frames read/write */ bool rgb_registered; u8 rgb_frame_count; bool rgb_enabled; u8 rgb_speed; u16 rgb_addr;};
static int get_endpoint_address(struct hid_device *hdev){ struct usb_host_endpoint *ep; struct usb_interface *intf;
intf = to_usb_interface(hdev->dev.parent); ep = intf->cur_altsetting->endpoint; if (ep) return ep->desc.bEndpointAddress;
return -ENODEV;}
static int claw_gamepad_mode_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep){ if (cmd_rep->data[0] >= ARRAY_SIZE(claw_gamepad_mode_text) || !claw_gamepad_mode_text[cmd_rep->data[0]] || cmd_rep->data[1] >= ARRAY_SIZE(claw_mkeys_function_text)) return -EINVAL;
scoped_guard(spinlock_irqsave, &drvdata->mode_lock) { drvdata->gamepad_mode = cmd_rep->data[0]; drvdata->mkeys_function = cmd_rep->data[1]; }
return 0;}
static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep){ enum claw_profile_ack_pending profile; struct claw_rumble_report *rumble; struct claw_mkey_report *mkeys; struct claw_rgb_report *frame; u16 rgb_addr, read_addr; u8 *codes, key, f_idx; u16 frame_calc; int i;
scoped_guard(spinlock_irqsave, &drvdata->profile_lock) profile = drvdata->profile_pending;
switch (profile) { case CLAW_M1_PENDING: case CLAW_M2_PENDING: key = (profile == CLAW_M1_PENDING) ? CLAW_KEY_M1 : CLAW_KEY_M2; mkeys = (struct claw_mkey_report *)cmd_rep->data; if (be16_to_cpu(mkeys->read_addr) != drvdata->bmap_addr[key]) return -EAGAIN; codes = (profile == CLAW_M1_PENDING) ? drvdata->m1_codes : drvdata->m2_codes; for (i = 0; i < CLAW_KEYS_MAX; i++) codes[i] = (mkeys->codes[i]); break; case CLAW_RGB_PENDING: frame = (struct claw_rgb_report *)cmd_rep->data; rgb_addr = drvdata->rgb_addr; read_addr = be16_to_cpu(frame->read_addr);
if (read_addr < drvdata->rgb_addr) return -EAGAIN;
frame_calc = (read_addr - rgb_addr) / CLAW_RGB_FRAME_OFFSET; if (frame_calc >= CLAW_RGB_MAX_FRAMES) { dev_err(&drvdata->hdev->dev, "Got unsupported frame index: %x\n", frame_calc); return -EAGAIN; } f_idx = frame_calc;
scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data, sizeof(struct rgb_frame));
/* Only use frame 0 for remaining variable assignment */ if (f_idx != 0) break;
drvdata->rgb_speed = frame->speed; drvdata->led_mc.led_cdev.brightness = frame->brightness; drvdata->led_mc.subled_info[0].intensity = frame->zone_data.zone[0].red; drvdata->led_mc.subled_info[1].intensity = frame->zone_data.zone[0].green; drvdata->led_mc.subled_info[2].intensity = frame->zone_data.zone[0].blue; }
break; case CLAW_RUMBLE_LEFT_PENDING: rumble = (struct claw_rumble_report *)cmd_rep->data; if (be16_to_cpu(rumble->read_addr) != rumble_addr[0]) return -EAGAIN; scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) drvdata->rumble_intensity_left = rumble->intensity; break; case CLAW_RUMBLE_RIGHT_PENDING: rumble = (struct claw_rumble_report *)cmd_rep->data; if (be16_to_cpu(rumble->read_addr) != rumble_addr[1]) return -EAGAIN; scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) drvdata->rumble_intensity_right = rumble->intensity; break; default: dev_dbg(&drvdata->hdev->dev, "Got profile event without changes pending from command: %x\n", cmd_rep->cmd); return -EINVAL; } scoped_guard(spinlock_irqsave, &drvdata->profile_lock) drvdata->profile_pending = CLAW_NO_PENDING;
return 0;}
static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report, u8 *data, int size){ struct claw_command_report *cmd_rep; int ret = 0;
if (size != CLAW_PACKET_SIZE) return 0;
cmd_rep = (struct claw_command_report *)data;
if (cmd_rep->report_id != CLAW_INPUT_REPORT_ID || cmd_rep->header_tail != 0x3c) return 0;
dev_dbg(&drvdata->hdev->dev, "Rx data as raw input report: [%*ph]\n", CLAW_PACKET_SIZE, data);
guard(spinlock_irqsave)(&drvdata->cmd_lock); switch (cmd_rep->cmd) { case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK: ret = claw_gamepad_mode_event(drvdata, cmd_rep); if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE) { drvdata->cmd_status = ret; complete(&drvdata->send_cmd_complete); }
break; case CLAW_COMMAND_TYPE_READ_PROFILE_ACK: ret = claw_profile_event(drvdata, cmd_rep); /* Stale address received, ignore and keep waiting */ if (ret == -EAGAIN) return 0; if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_PROFILE) { drvdata->cmd_status = ret; complete(&drvdata->send_cmd_complete); }
break; case CLAW_COMMAND_TYPE_ACK: if (drvdata->orphan_ack_pending) { drvdata->orphan_ack_pending = false; complete(&drvdata->orphan_ack_complete); break; }
if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_NONE) { dev_warn(&drvdata->hdev->dev, "Got unexpected ACK from MCU, ignoring\n"); break; }
drvdata->cmd_status = 0; complete(&drvdata->send_cmd_complete);
dev_dbg(&drvdata->hdev->dev, "Waiting CMD: %x\n", drvdata->waiting_cmd);
break; default: dev_dbg(&drvdata->hdev->dev, "Unknown command: %x\n", cmd_rep->cmd); return 0; }
return ret;}
static int msi_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size){ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
if (!drvdata || (drvdata->ep != CLAW_XINPUT_CFG_INTF_IN && drvdata->ep != CLAW_DINPUT_CFG_INTF_IN)) return 0;
return claw_raw_event(drvdata, report, data, size);}
/* Caller must hold drvdata->cfg_mutex. */static int __claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data, size_t len, unsigned int timeout){ unsigned char *dmabuf __free(kfree) = NULL; u8 header[] = { CLAW_OUTPUT_REPORT_ID, 0, 0, 0x3c, index }; struct claw_drvdata *drvdata = hid_get_drvdata(hdev); size_t header_size = ARRAY_SIZE(header); bool orphaned; int ret;
lockdep_assert_held(&drvdata->cfg_mutex);
/* If expecting an orphan ack, hold next event until MCU has time to clear it */ scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) orphaned = drvdata->orphan_ack_pending;
if (orphaned) { wait_for_completion_timeout(&drvdata->orphan_ack_complete, msecs_to_jiffies(25)); scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) drvdata->orphan_ack_pending = false; }
if (header_size + len > CLAW_PACKET_SIZE) return -EINVAL;
/* We can't use a devm_alloc reusable buffer without side effects during suspend */ dmabuf = kzalloc(CLAW_PACKET_SIZE, GFP_KERNEL); if (!dmabuf) return -ENOMEM;
memcpy(dmabuf, header, header_size); if (data && len) memcpy(dmabuf + header_size, data, len);
reinit_completion(&drvdata->send_cmd_complete);
scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) { if (timeout) { drvdata->waiting_cmd = index; drvdata->cmd_status = -ETIMEDOUT; } else { reinit_completion(&drvdata->orphan_ack_complete); drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; drvdata->orphan_ack_pending = true; } }
dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", CLAW_PACKET_SIZE, dmabuf);
ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE); if (ret < 0) goto err;
ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO; if (ret) goto err;
if (timeout) { ret = wait_for_completion_interruptible_timeout(&drvdata->send_cmd_complete, msecs_to_jiffies(timeout));
dev_dbg(&hdev->dev, "Remaining timeout: %u\n", ret); ret = ret > 0 ? drvdata->cmd_status : ret ?: -EBUSY; if (ret) goto err; }
scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE;
return ret;
err: scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) { drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; drvdata->orphan_ack_pending = false; } return ret;}
static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data, size_t len, unsigned int timeout){ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
guard(mutex)(&drvdata->cfg_mutex); return __claw_hw_output_report(hdev, index, data, len, timeout);}
static int claw_switch_mode(struct hid_device *hdev, enum claw_mode_field field, u8 val){ struct claw_drvdata *drvdata = hid_get_drvdata(hdev); u8 data[2];
guard(mutex)(&drvdata->cfg_mutex);
scoped_guard(spinlock_irqsave, &drvdata->mode_lock) { switch (field) { case CLAW_FIELD_GAMEPAD_MODE: data[0] = val; data[1] = drvdata->mkeys_function; break; case CLAW_FIELD_MKEYS_FUNCTION: data[0] = drvdata->gamepad_mode; data[1] = val; break; } }
return __claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0);}
static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); int i, ret = -EINVAL;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) { if (claw_gamepad_mode_text[i] && sysfs_streq(buf, claw_gamepad_mode_text[i])) { ret = i; break; } } if (ret < 0) return ret;
ret = claw_switch_mode(hdev, CLAW_FIELD_GAMEPAD_MODE, ret); if (ret) return ret;
return count;}
static ssize_t gamepad_mode_show(struct device *dev, struct device_attribute *attr, char *buf){ struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); int ret, i;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25); if (ret) return ret;
scoped_guard(spinlock_irqsave, &drvdata->mode_lock) i = drvdata->gamepad_mode;
if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0') return sysfs_emit(buf, "unsupported\n");
return sysfs_emit(buf, "%s\n", claw_gamepad_mode_text[i]);}static DEVICE_ATTR_RW(gamepad_mode);
static ssize_t gamepad_mode_index_show(struct device *dev, struct device_attribute *attr, char *buf){ ssize_t count = 0; int i;
for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) { if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0') continue; count += sysfs_emit_at(buf, count, "%s ", claw_gamepad_mode_text[i]); }
if (count) buf[count - 1] = '\n';
return count;}static DEVICE_ATTR_RO(gamepad_mode_index);
static ssize_t mkeys_function_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); int i, ret = -EINVAL;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) { if (claw_mkeys_function_text[i] && sysfs_streq(buf, claw_mkeys_function_text[i])) { ret = i; break; } } if (ret < 0) return ret;
ret = claw_switch_mode(hdev, CLAW_FIELD_MKEYS_FUNCTION, ret); if (ret) return ret;
return count;}
static ssize_t mkeys_function_show(struct device *dev, struct device_attribute *attr, char *buf){ struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); int ret, i;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25); if (ret) return ret;
scoped_guard(spinlock_irqsave, &drvdata->mode_lock) i = drvdata->mkeys_function;
if (i >= ARRAY_SIZE(claw_mkeys_function_text)) return sysfs_emit(buf, "unsupported\n");
return sysfs_emit(buf, "%s\n", claw_mkeys_function_text[i]);}static DEVICE_ATTR_RW(mkeys_function);
static ssize_t mkeys_function_index_show(struct device *dev, struct device_attribute *attr, char *buf){ int i, count = 0;
for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) count += sysfs_emit_at(buf, count, "%s ", claw_mkeys_function_text[i]);
if (count) buf[count - 1] = '\n';
return count;}static DEVICE_ATTR_RO(mkeys_function_index);
static ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); bool val; int ret;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
ret = kstrtobool(buf, &val); if (ret) return ret;
if (!val) return -EINVAL;
ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_RESET_DEVICE, NULL, 0, 0); if (ret) return ret;
return count;}static DEVICE_ATTR_WO(reset);
static int mkey_mapping_name_to_code(const char *name){ int i;
for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) { if (!strcmp(name, claw_button_mapping_key_map[i].name)) return claw_button_mapping_key_map[i].code; }
return -EINVAL;}
static const char *mkey_mapping_code_to_name(u8 code){ int i;
if (code == 0xff) return NULL;
for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) { if (claw_button_mapping_key_map[i].code == code) return claw_button_mapping_key_map[i].name; }
return NULL;}
static int claw_mkey_store(struct device *dev, const char *buf, u8 mkey){ struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); struct claw_mkey_report report = { {0x01, cpu_to_be16(drvdata->bmap_addr[mkey])}, 0x07, 0x04, 0x00, {0xff, 0xff, 0xff, 0xff, 0xff} }; char **raw_keys __free(argv_free) = NULL; int ret, key_count, i;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
raw_keys = argv_split(GFP_KERNEL, buf, &key_count); if (!raw_keys) return -ENOMEM;
if (key_count > CLAW_KEYS_MAX) return -EINVAL;
if (key_count == 0) goto set_buttons;
for (i = 0; i < key_count; i++) { ret = mkey_mapping_name_to_code(raw_keys[i]); if (ret < 0) return ret;
report.codes[i] = ret; }
set_buttons: scoped_guard(mutex, &drvdata->rom_mutex) { ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, (u8 *)&report, sizeof(report), 25); if (ret) return ret; /* MCU will not send ACK until the USB transaction completes. ACK is sent * immediately after and will hit the stale state machine, before the next * command re-arms the state machine. Timeout 0 ensures no deadlock waiting * for ACK that ill never come. */ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); }
return ret;}
static int claw_mkey_show(struct device *dev, char *buf, enum claw_key_index m_key){ struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); struct claw_mkey_report report = { {0x01, cpu_to_be16(drvdata->bmap_addr[m_key])}, 0x07 }; int i, ret, count = 0; const char *name; u8 *codes;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
codes = (m_key == CLAW_KEY_M1) ? drvdata->m1_codes : drvdata->m2_codes;
guard(mutex)(&drvdata->profile_mutex); scoped_guard(spinlock_irqsave, &drvdata->profile_lock) drvdata->profile_pending = (m_key == CLAW_KEY_M1) ? CLAW_M1_PENDING : CLAW_M2_PENDING;
ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, (u8 *)&report, sizeof(report), 25); if (ret) return ret;
for (i = 0; i < CLAW_KEYS_MAX; i++) { name = mkey_mapping_code_to_name(codes[i]); if (name) count += sysfs_emit_at(buf, count, "%s ", name); }
if (!count) return sysfs_emit(buf, "(not set)\n");
buf[count - 1] = '\n';
return count;}
static ssize_t button_m1_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ int ret;
ret = claw_mkey_store(dev, buf, CLAW_KEY_M1); if (ret) return ret;
return count;}
static ssize_t button_m1_show(struct device *dev, struct device_attribute *attr, char *buf){ return claw_mkey_show(dev, buf, CLAW_KEY_M1);}static DEVICE_ATTR_RW(button_m1);
static ssize_t button_m2_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ int ret;
ret = claw_mkey_store(dev, buf, CLAW_KEY_M2); if (ret) return ret;
return count;}
static ssize_t button_m2_show(struct device *dev, struct device_attribute *attr, char *buf){ return claw_mkey_show(dev, buf, CLAW_KEY_M2);}static DEVICE_ATTR_RW(button_m2);
static ssize_t button_mapping_options_show(struct device *dev, struct device_attribute *attr, char *buf){ int i, count = 0;
for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) count += sysfs_emit_at(buf, count, "%s ", claw_button_mapping_key_map[i].name);
if (count) buf[count - 1] = '\n';
return count;}static DEVICE_ATTR_RO(button_mapping_options);
static ssize_t rumble_intensity_left_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[0])}, 0x01 }; struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); u8 val; int ret;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
ret = kstrtou8(buf, 10, &val); if (ret) return ret;
if (val > 100) return -EINVAL;
report.intensity = val;
guard(mutex)(&drvdata->rom_mutex); ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, (u8 *)&report, sizeof(report), 25); if (ret) return ret;
/* MCU will not send ACK until the USB transaction completes. ACK is sent * immediately after and will hit the stale state machine, before the next * command re-arms the state machine. Timeout 0 ensures no deadlock waiting * for ACK that ill never come. */ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); if (ret) return ret;
return count;}
static ssize_t rumble_intensity_left_show(struct device *dev, struct device_attribute *attr, char *buf){ struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[0])}, 0x01 }; struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); int ret; u8 val;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
guard(mutex)(&drvdata->profile_mutex); scoped_guard(spinlock_irqsave, &drvdata->profile_lock) drvdata->profile_pending = CLAW_RUMBLE_LEFT_PENDING; ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, (u8 *)&report, sizeof(report), 25); if (ret) return ret;
scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) val = drvdata->rumble_intensity_left;
return sysfs_emit(buf, "%u\n", val);}static DEVICE_ATTR_RW(rumble_intensity_left);
static ssize_t rumble_intensity_right_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[1])}, 0x01 }; struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); u8 val; int ret;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
ret = kstrtou8(buf, 10, &val); if (ret) return ret;
if (val > 100) return -EINVAL;
report.intensity = val;
guard(mutex)(&drvdata->rom_mutex); ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, (u8 *)&report, sizeof(report), 25); if (ret) return ret;
/* MCU will not send ACK until the USB transaction completes. ACK is sent * immediately after and will hit the stale state machine, before the next * command re-arms the state machine. Timeout 0 ensures no deadlock waiting * for ACK that ill never come. */ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); if (ret) return ret;
return count;}
static ssize_t rumble_intensity_right_show(struct device *dev, struct device_attribute *attr, char *buf){ struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[1])}, 0x01 }; struct hid_device *hdev = to_hid_device(dev); struct claw_drvdata *drvdata = hid_get_drvdata(hdev); int ret; u8 val;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered)) return -ENODEV; }
guard(mutex)(&drvdata->profile_mutex); scoped_guard(spinlock_irqsave, &drvdata->profile_lock) drvdata->profile_pending = CLAW_RUMBLE_RIGHT_PENDING; ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, (u8 *)&report, sizeof(report), 25); if (ret) return ret;
scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) val = drvdata->rumble_intensity_right;
return sysfs_emit(buf, "%u\n", val);}static DEVICE_ATTR_RW(rumble_intensity_right);
static ssize_t rumble_intensity_range_show(struct device *dev, struct device_attribute *attr, char *buf){ return sysfs_emit(buf, "0-100\n");}static DEVICE_ATTR_RO(rumble_intensity_range);
static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n){ struct hid_device *hdev = to_hid_device(kobj_to_dev(kobj)); struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
if (!drvdata) { dev_warn(&hdev->dev, "Failed to get drvdata from kobj. Gamepad attributes are not available.\n"); return 0; }
/* Always show attrs available on all firmware */ if (attr == &dev_attr_gamepad_mode.attr || attr == &dev_attr_gamepad_mode_index.attr || attr == &dev_attr_mkeys_function.attr || attr == &dev_attr_mkeys_function_index.attr || attr == &dev_attr_reset.attr) return attr->mode;
/* Hide rumble attrs if not supported */ if (attr == &dev_attr_rumble_intensity_left.attr || attr == &dev_attr_rumble_intensity_right.attr || attr == &dev_attr_rumble_intensity_range.attr) return drvdata->rumble_support ? attr->mode : 0;
/* Hide button mapping attrs if it isn't supported */ return drvdata->bmap_support ? attr->mode : 0;}
static struct attribute *claw_gamepad_attrs[] = { &dev_attr_button_m1.attr, &dev_attr_button_m2.attr, &dev_attr_button_mapping_options.attr, &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, &dev_attr_mkeys_function.attr, &dev_attr_mkeys_function_index.attr, &dev_attr_reset.attr, &dev_attr_rumble_intensity_left.attr, &dev_attr_rumble_intensity_right.attr, &dev_attr_rumble_intensity_range.attr, NULL,};
static const struct attribute_group claw_gamepad_attr_group = { .attrs = claw_gamepad_attrs, .is_visible = claw_gamepad_attr_is_visible,};
/* Read RGB config from device */static int claw_read_rgb_config(struct hid_device *hdev){ u8 data[4] = { 0x01, 0x00, 0x00, CLAW_RGB_FRAME_OFFSET }; struct claw_drvdata *drvdata = hid_get_drvdata(hdev); u16 read_addr = drvdata->rgb_addr; size_t len = ARRAY_SIZE(data); int ret, i;
if (!drvdata->rgb_addr) return -ENODEV;
/* Loop through all 8 pages of RGB data */ guard(mutex)(&drvdata->profile_mutex); for (i = 0; i < CLAW_RGB_MAX_FRAMES; i++) { scoped_guard(spinlock_irqsave, &drvdata->profile_lock) drvdata->profile_pending = CLAW_RGB_PENDING; data[1] = (read_addr >> 8) & 0xff; data[2] = read_addr & 0x00ff; ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, len, 25); if (ret) return ret;
read_addr += CLAW_RGB_FRAME_OFFSET; }
return 0;}
/* Send RGB configuration to device */static int claw_write_rgb_state(struct claw_drvdata *drvdata){ struct claw_rgb_report report = { {0x01, 0}, CLAW_RGB_FRAME_OFFSET, 0x00, drvdata->rgb_frame_count, 0x09, drvdata->rgb_speed, drvdata->led_mc.led_cdev.brightness }; u16 write_addr = drvdata->rgb_addr; int f, ret;
if (!drvdata->rgb_addr) return -ENODEV;
if (!drvdata->rgb_frame_count) return -EINVAL;
guard(mutex)(&drvdata->rom_mutex); /* Loop through (up to) 8 pages of RGB data */ for (f = 0; f < drvdata->rgb_frame_count; f++) { scoped_guard(spinlock_irqsave, &drvdata->frame_lock) report.zone_data = drvdata->rgb_frames[f];
/* Set the MCU address to write the frame data to */ report.read_addr = cpu_to_be16(write_addr);
/* Serialize the rgb_report and write it to MCU */ ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, (u8 *)&report, sizeof(report), 25); if (ret) return ret;
/* Increment the write addr by the offset for the next frame */ write_addr += CLAW_RGB_FRAME_OFFSET; }
/* MCU will not send ACK until the USB transaction completes. ACK is sent * immediately after and will hit the stale state machine, before the next * command re-arms the state machine. Timeout 0 ensures no deadlock waiting * for ACK that ill never come. */ ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0);
return ret;}
/* Fill all zones with the same color */static void claw_frame_fill_solid(struct rgb_frame *frame, struct rgb_zone zone){ int z;
for (z = 0; z < CLAW_RGB_ZONES; z++) frame->zone[z] = zone;}
/* Apply solid effect (1 frame, no color) */static int claw_apply_disabled(struct claw_drvdata *drvdata){ struct rgb_zone off = { 0x00, 0x00, 0x00};
scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { drvdata->rgb_frame_count = 1; claw_frame_fill_solid(&drvdata->rgb_frames[0], off); }
return claw_write_rgb_state(drvdata);}
/* Apply solid effect (1 frame, all zones same color) */static int claw_apply_monocolor(struct claw_drvdata *drvdata){ struct mc_subled *subleds = drvdata->led_mc.subled_info; struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity, subleds[2].intensity };
scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { drvdata->rgb_frame_count = 1; claw_frame_fill_solid(&drvdata->rgb_frames[0], zone); }
return claw_write_rgb_state(drvdata);}
/* Apply breathe effect (2 frames: color -> off) */static int claw_apply_breathe(struct claw_drvdata *drvdata){ struct mc_subled *subleds = drvdata->led_mc.subled_info; struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity, subleds[2].intensity }; static const struct rgb_zone off = { 0, 0, 0 };
scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { drvdata->rgb_frame_count = 2; claw_frame_fill_solid(&drvdata->rgb_frames[0], zone); claw_frame_fill_solid(&drvdata->rgb_frames[1], off); }
return claw_write_rgb_state(drvdata);}
/* Apply chroma effect (6 frames: rainbow cycle, all zones sync) */static int claw_apply_chroma(struct claw_drvdata *drvdata){ static const struct rgb_zone colors[] = { {255, 0, 0}, /* red */ {255, 255, 0}, /* yellow */ { 0, 255, 0}, /* green */ { 0, 255, 255}, /* cyan */ { 0, 0, 255}, /* blue */ {255, 0, 255}, /* magenta */ }; u8 frame_count = ARRAY_SIZE(colors); int f;
scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { drvdata->rgb_frame_count = frame_count;
for (f = 0; f < frame_count; f++) claw_frame_fill_solid(&drvdata->rgb_frames[f], colors[f]); }
return claw_write_rgb_state(drvdata);}
/* Apply rainbow effect (4 frames: rotating colors around joysticks) */static int claw_apply_rainbow(struct claw_drvdata *drvdata){ static const struct rgb_zone colors[] = { {255, 0, 0}, /* red */ { 0, 255, 0}, /* green */ { 0, 255, 255}, /* cyan */ { 0, 0, 255}, /* blue */ }; u8 frame_count = ARRAY_SIZE(colors); int f, z;
scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { drvdata->rgb_frame_count = frame_count;
for (f = 0; f < frame_count; f++) { for (z = 0; z < 4; z++) { drvdata->rgb_frames[f].zone[z] = colors[(z + f) % 4]; drvdata->rgb_frames[f].zone[z + 4] = colors[(z + f) % 4]; } drvdata->rgb_frames[f].zone[8] = colors[f]; } }
return claw_write_rgb_state(drvdata);}
/* * Apply frostfire effect (4 frames: fire vs ice rotating) * Right joystick: fire red -> dark -> ice blue -> dark (clockwise) * Left joystick: ice blue -> dark -> fire red -> dark (counter-clockwise) * ABXY: fire red -> dark -> ice blue -> dark */static int claw_apply_frostfire(struct claw_drvdata *drvdata){ static const struct rgb_zone colors[] = { {255, 0, 0}, /* fire red */ { 0, 0, 0}, /* dark */ { 0, 0, 255}, /* ice blue */ { 0, 0, 0}, /* dark */ }; u8 frame_count = ARRAY_SIZE(colors); int f, z;
scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { drvdata->rgb_frame_count = frame_count;
for (f = 0; f < frame_count; f++) { for (z = 0; z < 4; z++) { drvdata->rgb_frames[f].zone[z] = colors[(z + f) % 4]; drvdata->rgb_frames[f].zone[z + 4] = colors[(z - f + 6) % 4]; } drvdata->rgb_frames[f].zone[8] = colors[f]; } }
return claw_write_rgb_state(drvdata);}
/* Apply current state to device */static int claw_apply_rgb_state(struct claw_drvdata *drvdata){ if (!drvdata->rgb_enabled) return claw_apply_disabled(drvdata);
switch (drvdata->rgb_effect) { case CLAW_RGB_EFFECT_MONOCOLOR: return claw_apply_monocolor(drvdata); case CLAW_RGB_EFFECT_BREATHE: return claw_apply_breathe(drvdata); case CLAW_RGB_EFFECT_CHROMA: return claw_apply_chroma(drvdata); case CLAW_RGB_EFFECT_RAINBOW: return claw_apply_rainbow(drvdata); case CLAW_RGB_EFFECT_FROSTFIRE: return claw_apply_frostfire(drvdata); default: dev_err(&drvdata->hdev->dev, "No supported rgb_effect selected\n"); return -EINVAL; }}
static void claw_rgb_queue_fn(struct work_struct *work){ struct delayed_work *dwork = container_of(work, struct delayed_work, work); struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, rgb_queue); int ret;
if (!drvdata) return;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->rgb_registered)) return; }
ret = claw_apply_rgb_state(drvdata); if (ret) dev_err(&drvdata->hdev->dev, "Failed to apply RGB state: %d\n", ret);}
static ssize_t effect_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); int ret;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->rgb_registered)) return -ENODEV; }
ret = sysfs_match_string(claw_rgb_effect_text, buf); if (ret < 0) return ret;
scoped_guard(spinlock_irqsave, &drvdata->profile_lock) drvdata->rgb_effect = ret;
mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
return count;}
static ssize_t effect_show(struct device *dev, struct device_attribute *attr, char *buf){ struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->rgb_registered)) return -ENODEV; }
if (drvdata->rgb_effect >= ARRAY_SIZE(claw_rgb_effect_text)) return -EINVAL;
return sysfs_emit(buf, "%s\n", claw_rgb_effect_text[drvdata->rgb_effect]);}
static DEVICE_ATTR_RW(effect);
static ssize_t effect_index_show(struct device *dev, struct device_attribute *attr, char *buf){ int i, count = 0;
for (i = 0; i < ARRAY_SIZE(claw_rgb_effect_text); i++) count += sysfs_emit_at(buf, count, "%s ", claw_rgb_effect_text[i]);
if (count) buf[count - 1] = '\n';
return count;}static DEVICE_ATTR_RO(effect_index);
static ssize_t enabled_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); bool val; int ret;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->rgb_registered)) return -ENODEV; }
ret = kstrtobool(buf, &val); if (ret) return ret;
scoped_guard(spinlock_irqsave, &drvdata->profile_lock) drvdata->rgb_enabled = val;
mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
return count;}
static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, char *buf){ struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->rgb_registered)) return -ENODEV; }
return sysfs_emit(buf, "%s\n", drvdata->rgb_enabled ? "true" : "false");}static DEVICE_ATTR_RW(enabled);
static ssize_t enabled_index_show(struct device *dev, struct device_attribute *attr, char *buf){ return sysfs_emit(buf, "true false\n");}static DEVICE_ATTR_RO(enabled_index);
static ssize_t speed_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); unsigned int val, speed; int ret;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->rgb_registered)) return -ENODEV; }
ret = kstrtouint(buf, 10, &val); if (ret) return ret;
if (val > 20) return -EINVAL;
/* 0 is fastest, invert value for intuitive userspace speed */ speed = 20 - val;
scoped_guard(spinlock_irqsave, &drvdata->profile_lock) drvdata->rgb_speed = speed;
mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
return count;}
static ssize_t speed_show(struct device *dev, struct device_attribute *attr, char *buf){ struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); u8 speed = 20 - drvdata->rgb_speed;
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->rgb_registered)) return -ENODEV; }
return sysfs_emit(buf, "%u\n", speed);}static DEVICE_ATTR_RW(speed);
static ssize_t speed_range_show(struct device *dev, struct device_attribute *attr, char *buf){ return sysfs_emit(buf, "0-20\n");}static DEVICE_ATTR_RO(speed_range);
static void claw_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness _brightness){ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->rgb_registered)) return; }
mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));}
static struct attribute *claw_rgb_attrs[] = { &dev_attr_effect.attr, &dev_attr_effect_index.attr, &dev_attr_enabled.attr, &dev_attr_enabled_index.attr, &dev_attr_speed.attr, &dev_attr_speed_range.attr, NULL,};
static const struct attribute_group claw_rgb_attr_group = { .attrs = claw_rgb_attrs,};
static struct mc_subled claw_rgb_subled_info[] = { { .color_index = LED_COLOR_ID_RED, .channel = 0x1, }, { .color_index = LED_COLOR_ID_GREEN, .channel = 0x2, }, { .color_index = LED_COLOR_ID_BLUE, .channel = 0x3, },};
static void cfg_setup_fn(struct work_struct *work){ struct delayed_work *dwork = container_of(work, struct delayed_work, work); struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_setup); bool gamepad_ready = false, rgb_ready = false, gp_registered, rgb_registered; int ret;
ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25); if (ret) { dev_err(&drvdata->hdev->dev, "Failed to read gamepad mode: %d\n", ret); goto prep_rgb; } gamepad_ready = true;
prep_rgb: ret = claw_read_rgb_config(drvdata->hdev); if (ret) { dev_err(&drvdata->hdev->dev, "Failed to read RGB config: %d\n", ret); goto try_gamepad; } rgb_ready = true;
/* Add sysfs attributes after we get the device state */try_gamepad: scoped_guard(spinlock_irqsave, &drvdata->registration_lock) /* Pairs with smp_store_release from below */ gp_registered = smp_load_acquire(&drvdata->gp_registered);
if (!gp_registered && gamepad_ready) { ret = device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group); if (ret) { dev_err(&drvdata->hdev->dev, "Failed to create gamepad attrs: %d\n", ret); goto try_rgb; }
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_load_acquire in attribute show/store functions */ smp_store_release(&drvdata->gp_registered, true); gp_registered = true; } }
try_rgb: /* Add and enable RGB interface once we have the device state */ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) /* Pairs with smp_store_release from below */ rgb_registered = smp_load_acquire(&drvdata->rgb_registered);
if (!rgb_registered && rgb_ready) { ret = led_classdev_multicolor_register(&drvdata->hdev->dev, &drvdata->led_mc); if (ret) { dev_err(&drvdata->hdev->dev, "Failed to create led device: %d\n", ret); goto update_kobjects; }
ret = device_add_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group); if (ret) { dev_err(&drvdata->hdev->dev, "Failed to create RGB attrs: %d\n", ret); led_classdev_multicolor_unregister(&drvdata->led_mc); goto update_kobjects; }
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_load_acquire in attribute show/store functions */ smp_store_release(&drvdata->rgb_registered, true); rgb_registered = true; } }
update_kobjects: if (gp_registered) kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE); if (rgb_registered) kobject_uevent(&drvdata->led_mc.led_cdev.dev->kobj, KOBJ_CHANGE);}
static void cfg_resume_fn(struct work_struct *work){ struct delayed_work *dwork = container_of(work, struct delayed_work, work); struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_resume);
guard(spinlock_irqsave)(&drvdata->registration_lock); /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ if (!smp_load_acquire(&drvdata->gp_registered) || /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ !smp_load_acquire(&drvdata->rgb_registered)) schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));}
static void claw_features_supported(struct claw_drvdata *drvdata){ u8 major = (drvdata->bcd_device >> 8) & 0xff; u8 minor = drvdata->bcd_device & 0xff;
if (major == 0x01) { drvdata->bmap_support = true; if (minor >= 0x66) { drvdata->bmap_addr = button_mapping_addr_new; drvdata->rumble_support = true; drvdata->rgb_addr = rgb_addr_new; } else { drvdata->bmap_addr = button_mapping_addr_old; drvdata->rgb_addr = rgb_addr_old; } return; }
if ((major == 0x02 && minor >= 0x17) || major >= 0x03) { drvdata->bmap_support = true; drvdata->bmap_addr = button_mapping_addr_new; drvdata->rumble_support = true; drvdata->rgb_addr = rgb_addr_new; return; }
drvdata->rgb_addr = rgb_addr_old;}
static int claw_probe(struct hid_device *hdev, u8 ep){ struct usb_interface *intf = to_usb_interface(hdev->dev.parent); struct usb_device *udev = interface_to_usbdev(intf); struct claw_drvdata *drvdata; int ret;
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); if (!drvdata) return -ENOMEM;
drvdata->gamepad_mode = CLAW_GAMEPAD_MODE_XINPUT; drvdata->rgb_enabled = true; drvdata->hdev = hdev; drvdata->ep = ep;
/* Determine feature level from firmware version */ drvdata->bcd_device = le16_to_cpu(udev->descriptor.bcdDevice); claw_features_supported(drvdata);
if (!drvdata->bmap_support) dev_dbg(&hdev->dev, "M-Key mapping is not supported. Update firmware to enable.\n");
/* Device is hardwired and name is guaranteed to be unique */ drvdata->led_mc.led_cdev.name = "msi_claw:rgb:joystick_rings"; drvdata->led_mc.led_cdev.brightness = 0x50; drvdata->led_mc.led_cdev.max_brightness = 0x64; drvdata->led_mc.led_cdev.color = LED_COLOR_ID_RGB; drvdata->led_mc.led_cdev.brightness_set = claw_led_brightness_set; drvdata->led_mc.num_colors = 3; drvdata->led_mc.subled_info = devm_kmemdup(&hdev->dev, claw_rgb_subled_info, sizeof(claw_rgb_subled_info), GFP_KERNEL); if (!drvdata->led_mc.subled_info) return -ENOMEM;
mutex_init(&drvdata->cfg_mutex); mutex_init(&drvdata->profile_mutex); mutex_init(&drvdata->rom_mutex); spin_lock_init(&drvdata->registration_lock); spin_lock_init(&drvdata->cmd_lock); spin_lock_init(&drvdata->mode_lock); spin_lock_init(&drvdata->profile_lock); spin_lock_init(&drvdata->frame_lock); spin_lock_init(&drvdata->rumble_lock); init_completion(&drvdata->orphan_ack_complete); init_completion(&drvdata->send_cmd_complete); INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn); INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn); INIT_DELAYED_WORK(&drvdata->rgb_queue, &claw_rgb_queue_fn);
/* For control interface: open the HID transport for sending commands. */ ret = hid_hw_open(hdev); if (ret) return ret;
hid_set_drvdata(hdev, drvdata); schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
return 0;}
static int msi_probe(struct hid_device *hdev, const struct hid_device_id *id){ int ret; u8 ep;
if (!hid_is_usb(hdev)) { ret = -ENODEV; goto err_probe; }
ret = hid_parse(hdev); if (ret) goto err_probe;
/* Set quirk to create separate input devices per HID application */ hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) goto err_probe;
/* For non-control interfaces (keyboard/mouse), allow userspace to grab the devices. */ ret = get_endpoint_address(hdev); if (ret < 0) goto err_stop_hw;
ep = ret; if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) { ret = claw_probe(hdev, ep); if (ret) goto err_stop_hw; }
return 0;
err_stop_hw: hid_hw_stop(hdev);err_probe: return dev_err_probe(&hdev->dev, ret, "Failed to init device\n");}
static void claw_remove(struct hid_device *hdev){ struct claw_drvdata *drvdata = hid_get_drvdata(hdev); bool gp_registered; bool rgb_registered;
if (!drvdata) return;
cancel_delayed_work_sync(&drvdata->cfg_resume); cancel_delayed_work_sync(&drvdata->cfg_setup);
scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ gp_registered = smp_load_acquire(&drvdata->gp_registered); /* Pairs with smp_load_acquire in attribute show/store functions */ smp_store_release(&drvdata->gp_registered, false); /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ rgb_registered = smp_load_acquire(&drvdata->rgb_registered); /* Pairs with smp_load_acquire in attribute show/store functions */ smp_store_release(&drvdata->rgb_registered, false); }
if (gp_registered) device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
if (rgb_registered) { device_remove_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group); led_classdev_multicolor_unregister(&drvdata->led_mc); } cancel_delayed_work_sync(&drvdata->rgb_queue);
hid_hw_close(hdev);}
static void msi_remove(struct hid_device *hdev){ int ret; u8 ep;
/* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ ret = get_endpoint_address(hdev); if (ret <= 0) goto hw_stop;
ep = ret; if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) claw_remove(hdev);
hw_stop: hid_hw_stop(hdev);}
static int claw_resume(struct hid_device *hdev){ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
if (!drvdata) return -ENODEV;
/* MCU can take up to 500ms to be ready after resume */ schedule_delayed_work(&drvdata->cfg_resume, msecs_to_jiffies(500)); return 0;}
static int msi_resume(struct hid_device *hdev){ int ret; u8 ep;
/* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ ret = get_endpoint_address(hdev); if (ret <= 0) return 0;
ep = ret; if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) return claw_resume(hdev);
return 0;}
static int claw_suspend(struct hid_device *hdev){ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
if (!drvdata) return -ENODEV;
cancel_delayed_work_sync(&drvdata->cfg_resume); cancel_delayed_work_sync(&drvdata->cfg_setup); cancel_delayed_work_sync(&drvdata->rgb_queue);
return 0;}
static int msi_suspend(struct hid_device *hdev, pm_message_t msg){ int ret; u8 ep;
/* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ ret = get_endpoint_address(hdev); if (ret <= 0) return 0;
ep = ret; if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) return claw_suspend(hdev);
return 0;}
static const struct hid_device_id msi_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_XINPUT) }, { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DINPUT) }, { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DESKTOP) }, { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_BIOS) }, { }};MODULE_DEVICE_TABLE(hid, msi_devices);
static struct hid_driver msi_driver = { .name = "hid-msi", .id_table = msi_devices, .raw_event = msi_raw_event, .probe = msi_probe, .remove = msi_remove, .resume = pm_ptr(msi_resume), .suspend = pm_ptr(msi_suspend),};module_hid_driver(msi_driver);
MODULE_LICENSE("GPL");MODULE_AUTHOR("Denis Benato <denis.benato@linux.dev>");MODULE_AUTHOR("Zhouwang Huang <honjow311@gmail.com>");MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");MODULE_DESCRIPTION("HID driver for MSI Claw Handheld PC gamepads");