diff --git a/arch/arm/configs/MiSTer_defconfig b/arch/arm/configs/MiSTer_defconfig index dbae0fc3d..940d079b1 100644 --- a/arch/arm/configs/MiSTer_defconfig +++ b/arch/arm/configs/MiSTer_defconfig @@ -2691,6 +2691,7 @@ CONFIG_HID_ZEROPLUS=y # CONFIG_HID_SENSOR_HUB is not set # CONFIG_HID_ALPS is not set # CONFIG_HID_MCP2221 is not set +CONFIG_JOYSTICK_XONE=m # end of Special HID drivers # diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 689071f00..06c308890 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1258,6 +1258,8 @@ config HID_MCP2221 To compile this driver as a module, choose M here: the module will be called hid-mcp2221.ko. +source "drivers/hid/xone/Kconfig" + endmenu endif # HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 46e6dcb6e..6c67f3d04 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -152,3 +152,6 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + +obj-$(CONFIG_JOYSTICK_XONE) += xone/ + diff --git a/drivers/hid/xone/Kconfig b/drivers/hid/xone/Kconfig new file mode 100644 index 000000000..c265e718f --- /dev/null +++ b/drivers/hid/xone/Kconfig @@ -0,0 +1,7 @@ +# +# XOne wireless adapter +# +config JOYSTICK_XONE + tristate "XOne" + depends on INPUT && INPUT_JOYSTICK + diff --git a/drivers/hid/xone/Makefile b/drivers/hid/xone/Makefile new file mode 100644 index 000000000..1d2c428a0 --- /dev/null +++ b/drivers/hid/xone/Makefile @@ -0,0 +1,8 @@ +xone-wired-y := transport/wired.o +xone-dongle-y := transport/dongle.o transport/mt76.o +xone-gip-bus-y := bus/bus.o bus/protocol.o +xone-gip-common-y := driver/common.o +xone-gip-gamepad-y := driver/gamepad.o +xone-gip-headset-y := driver/headset.o +xone-gip-chatpad-y := driver/chatpad.o +obj-$(CONFIG_JOYSTICK_XONE) := xone-wired.o xone-dongle.o xone-gip-bus.o xone-gip-common.o xone-gip-gamepad.o xone-gip-headset.o xone-gip-chatpad.o diff --git a/drivers/hid/xone/bus/bus.c b/drivers/hid/xone/bus/bus.c new file mode 100644 index 000000000..c6bb21b89 --- /dev/null +++ b/drivers/hid/xone/bus/bus.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include +#include +#include + +#include "bus.h" + +#define to_gip_adapter(d) container_of(d, struct gip_adapter, dev) +#define to_gip_client(d) container_of(d, struct gip_client, dev) +#define to_gip_driver(d) container_of(d, struct gip_driver, drv) + +static DEFINE_IDA(gip_adapter_ida); + +static void gip_adapter_release(struct device *dev) +{ + kfree(to_gip_adapter(dev)); +} + +static struct device_type gip_adapter_type = { + .release = gip_adapter_release, +}; + +static void gip_add_client(struct gip_client *client) +{ + int err; + + err = device_add(&client->dev); + if (err) { + dev_err(&client->dev, "%s: add device failed: %d\n", + __func__, err); + return; + } + + dev_dbg(&client->dev, "%s: added\n", __func__); +} + +static void gip_remove_client(struct gip_client *client) +{ + dev_dbg(&client->dev, "%s: removed\n", __func__); + + if (device_is_registered(&client->dev)) + device_del(&client->dev); + + put_device(&client->dev); +} + +static void gip_client_state_changed(struct work_struct *work) +{ + struct gip_client *client = container_of(work, typeof(*client), + state_work); + + switch (atomic_read(&client->state)) { + case GIP_CL_IDENTIFIED: + gip_add_client(client); + break; + case GIP_CL_DISCONNECTED: + gip_remove_client(client); + break; + default: + dev_warn(&client->dev, "%s: invalid state\n", __func__); + break; + } +} + +static int gip_client_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct gip_client *client = to_gip_client(dev); + struct gip_classes *classes = client->classes; + + if (!classes || !classes->count) + return -EINVAL; + + return add_uevent_var(env, "MODALIAS=gip:%s", classes->strings[0]); +} + +static void gip_client_release(struct device *dev) +{ + struct gip_client *client = to_gip_client(dev); + + gip_free_client_info(client); + kfree(client->chunk_buf); + kfree(client); +} + +static struct device_type gip_client_type = { + .uevent = gip_client_uevent, + .release = gip_client_release, +}; + +static int gip_bus_match(struct device *dev, struct device_driver *driver) +{ + struct gip_client *client; + struct gip_driver *drv; + int i; + + if (dev->type != &gip_client_type) + return false; + + client = to_gip_client(dev); + drv = to_gip_driver(driver); + + for (i = 0; i < client->classes->count; i++) + if (!strcmp(client->classes->strings[i], drv->class)) + return true; + + return false; +} + +static int gip_bus_probe(struct device *dev) +{ + struct gip_client *client = to_gip_client(dev); + struct gip_driver *drv = to_gip_driver(dev->driver); + int err; + unsigned long flags; + + if (client->drv) + return 0; + + err = drv->probe(client); + if (!err) { + spin_lock_irqsave(&client->lock, flags); + client->drv = drv; + spin_unlock_irqrestore(&client->lock, flags); + } + + return err; +} + +static void gip_bus_remove(struct device *dev) +{ + struct gip_client *client = to_gip_client(dev); + struct gip_driver *drv = client->drv; + unsigned long flags; + + if (!drv) + return; + + spin_lock_irqsave(&client->lock, flags); + client->drv = NULL; + spin_unlock_irqrestore(&client->lock, flags); + + drv->remove(client); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static int gip_bus_remove_compat(struct device *dev) +{ + gip_bus_remove(dev); + + return 0; +} +#endif + +static struct bus_type gip_bus_type = { + .name = "xone-gip", + .match = gip_bus_match, + .probe = gip_bus_probe, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) + .remove = gip_bus_remove_compat, +#else + .remove = gip_bus_remove, +#endif +}; + +struct gip_adapter *gip_create_adapter(struct device *parent, + struct gip_adapter_ops *ops, + int audio_pkts) +{ + struct gip_adapter *adap; + int err; + + adap = kzalloc(sizeof(*adap), GFP_KERNEL); + if (!adap) + return ERR_PTR(-ENOMEM); + + adap->id = ida_simple_get(&gip_adapter_ida, 0, 0, GFP_KERNEL); + if (adap->id < 0) { + err = adap->id; + goto err_put_device; + } + + adap->state_queue = alloc_ordered_workqueue("gip%d", 0, adap->id); + if (!adap->state_queue) { + err = -ENOMEM; + goto err_remove_ida; + } + + adap->dev.parent = parent; + adap->dev.type = &gip_adapter_type; + adap->dev.bus = &gip_bus_type; + adap->ops = ops; + adap->audio_packet_count = audio_pkts; + dev_set_name(&adap->dev, "gip%d", adap->id); + spin_lock_init(&adap->clients_lock); + spin_lock_init(&adap->send_lock); + + err = device_register(&adap->dev); + if (err) + goto err_destroy_queue; + + dev_dbg(&adap->dev, "%s: registered\n", __func__); + + return adap; + +err_destroy_queue: + destroy_workqueue(adap->state_queue); +err_remove_ida: + ida_simple_remove(&gip_adapter_ida, adap->id); +err_put_device: + put_device(&adap->dev); + + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(gip_create_adapter); + +int gip_power_off_adapter(struct gip_adapter *adap) +{ + struct gip_client *client = adap->clients[0]; + + if (!client) + return 0; + + /* power off main client */ + return gip_set_power_mode(client, GIP_PWR_OFF); +} +EXPORT_SYMBOL_GPL(gip_power_off_adapter); + +void gip_destroy_adapter(struct gip_adapter *adap) +{ + struct gip_client *client; + int i; + + /* ensure all state changes have been processed */ + flush_workqueue(adap->state_queue); + + for (i = GIP_MAX_CLIENTS - 1; i >= 0; i--) { + client = adap->clients[i]; + if (!client) + continue; + + gip_remove_client(client); + adap->clients[i] = NULL; + } + + ida_simple_remove(&gip_adapter_ida, adap->id); + destroy_workqueue(adap->state_queue); + + dev_dbg(&adap->dev, "%s: unregistered\n", __func__); + device_unregister(&adap->dev); +} +EXPORT_SYMBOL_GPL(gip_destroy_adapter); + +static struct gip_client *gip_init_client(struct gip_adapter *adap, u8 id) +{ + struct gip_client *client; + + client = kzalloc(sizeof(*client), GFP_ATOMIC); + if (!client) + return ERR_PTR(-ENOMEM); + + client->dev.parent = &adap->dev; + client->dev.type = &gip_client_type; + client->dev.bus = &gip_bus_type; + client->id = id; + client->adapter = adap; + dev_set_name(&client->dev, "gip%d.%u", adap->id, client->id); + atomic_set(&client->state, GIP_CL_CONNECTED); + spin_lock_init(&client->lock); + INIT_WORK(&client->state_work, gip_client_state_changed); + + device_initialize(&client->dev); + dev_dbg(&client->dev, "%s: initialized\n", __func__); + + return client; +} + +struct gip_client *gip_get_or_init_client(struct gip_adapter *adap, u8 id) +{ + struct gip_client *client; + unsigned long flags; + + spin_lock_irqsave(&adap->clients_lock, flags); + + client = adap->clients[id]; + if (!client) { + client = gip_init_client(adap, id); + if (IS_ERR(client)) + goto err_unlock; + + adap->clients[id] = client; + } + + get_device(&client->dev); + +err_unlock: + spin_unlock_irqrestore(&adap->clients_lock, flags); + + return client; +} + +void gip_put_client(struct gip_client *client) +{ + put_device(&client->dev); +} + +void gip_register_client(struct gip_client *client) +{ + atomic_set(&client->state, GIP_CL_IDENTIFIED); + queue_work(client->adapter->state_queue, &client->state_work); +} + +void gip_unregister_client(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + unsigned long flags; + + spin_lock_irqsave(&adap->clients_lock, flags); + adap->clients[client->id] = NULL; + spin_unlock_irqrestore(&adap->clients_lock, flags); + + atomic_set(&client->state, GIP_CL_DISCONNECTED); + queue_work(adap->state_queue, &client->state_work); +} + +void gip_free_client_info(struct gip_client *client) +{ + int i; + + kfree(client->external_commands); + kfree(client->audio_formats); + kfree(client->capabilities_out); + kfree(client->capabilities_in); + + if (client->classes) + for (i = 0; i < client->classes->count; i++) + kfree(client->classes->strings[i]); + + kfree(client->classes); + kfree(client->interfaces); + kfree(client->hid_descriptor); + + client->external_commands = NULL; + client->audio_formats = NULL; + client->capabilities_out = NULL; + client->capabilities_in = NULL; + client->classes = NULL; + client->interfaces = NULL; + client->hid_descriptor = NULL; +} + +int __gip_register_driver(struct gip_driver *drv, struct module *owner, + const char *mod_name) +{ + drv->drv.name = drv->name; + drv->drv.bus = &gip_bus_type; + drv->drv.owner = owner; + drv->drv.mod_name = mod_name; + + return driver_register(&drv->drv); +} +EXPORT_SYMBOL_GPL(__gip_register_driver); + +void gip_unregister_driver(struct gip_driver *drv) +{ + driver_unregister(&drv->drv); +} +EXPORT_SYMBOL_GPL(gip_unregister_driver); + +static int __init gip_bus_init(void) +{ + return bus_register(&gip_bus_type); +} + +static void __exit gip_bus_exit(void) +{ + bus_unregister(&gip_bus_type); +} + +module_init(gip_bus_init); +module_exit(gip_bus_exit); + +MODULE_AUTHOR("Severin von Wnuck "); +MODULE_DESCRIPTION("xone GIP bus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/bus/bus.h b/drivers/hid/xone/bus/bus.h new file mode 100644 index 000000000..09875f720 --- /dev/null +++ b/drivers/hid/xone/bus/bus.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#pragma once + +#include +#include + +#include "protocol.h" + +#define GIP_MAX_CLIENTS 16 + +#define gip_register_driver(drv) \ + __gip_register_driver(drv, THIS_MODULE, KBUILD_MODNAME) + +#define module_gip_driver(drv) \ + module_driver(drv, gip_register_driver, gip_unregister_driver) + +struct gip_adapter_buffer { + enum gip_adapter_buffer_type { + GIP_BUF_DATA, + GIP_BUF_AUDIO, + } type; + + void *context; + void *data; + int length; +}; + +struct gip_adapter_ops { + int (*get_buffer)(struct gip_adapter *adap, + struct gip_adapter_buffer *buf); + int (*submit_buffer)(struct gip_adapter *adap, + struct gip_adapter_buffer *buf); + int (*enable_audio)(struct gip_adapter *adap); + int (*init_audio_in)(struct gip_adapter *adap); + int (*init_audio_out)(struct gip_adapter *adap, int pkt_len); + int (*disable_audio)(struct gip_adapter *adap); +}; + +struct gip_adapter { + struct device dev; + int id; + + struct gip_adapter_ops *ops; + int audio_packet_count; + + struct gip_client *clients[GIP_MAX_CLIENTS]; + struct workqueue_struct *state_queue; + + /* serializes access to clients array */ + spinlock_t clients_lock; + + /* serializes access to data sequence number */ + spinlock_t send_lock; + + u8 data_sequence; + u8 audio_sequence; +}; + +struct gip_client { + struct device dev; + u8 id; + atomic_t state; + + struct gip_adapter *adapter; + struct gip_driver *drv; + + struct gip_chunk_buffer *chunk_buf; + struct gip_hardware hardware; + + struct gip_info_element *external_commands; + struct gip_info_element *audio_formats; + struct gip_info_element *capabilities_in; + struct gip_info_element *capabilities_out; + struct gip_classes *classes; + struct gip_info_element *interfaces; + struct gip_info_element *hid_descriptor; + + struct gip_audio_config audio_config_in; + struct gip_audio_config audio_config_out; + + /* serializes packet processing */ + spinlock_t lock; + struct work_struct state_work; +}; + +struct gip_driver_ops { + int (*battery)(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level); + int (*guide_button)(struct gip_client *client, bool down); + int (*audio_ready)(struct gip_client *client); + int (*audio_volume)(struct gip_client *client, int in, int out); + int (*hid_report)(struct gip_client *client, void *data, int len); + int (*input)(struct gip_client *client, void *data, int len); + int (*audio_samples)(struct gip_client *client, void *data, int len); +}; + +struct gip_driver { + struct device_driver drv; + const char *name; + const char *class; + + struct gip_driver_ops ops; + + int (*probe)(struct gip_client *client); + void (*remove)(struct gip_client *client); +}; + +struct gip_adapter *gip_create_adapter(struct device *parent, + struct gip_adapter_ops *ops, + int audio_pkts); +int gip_power_off_adapter(struct gip_adapter *adap); +void gip_destroy_adapter(struct gip_adapter *adap); + +struct gip_client *gip_get_or_init_client(struct gip_adapter *adap, u8 id); +void gip_put_client(struct gip_client *client); +void gip_register_client(struct gip_client *client); +void gip_unregister_client(struct gip_client *client); +void gip_free_client_info(struct gip_client *client); + +int __gip_register_driver(struct gip_driver *drv, struct module *owner, + const char *mod_name); +void gip_unregister_driver(struct gip_driver *drv); diff --git a/drivers/hid/xone/bus/protocol.c b/drivers/hid/xone/bus/protocol.c new file mode 100644 index 000000000..4e175d12f --- /dev/null +++ b/drivers/hid/xone/bus/protocol.c @@ -0,0 +1,1377 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include +#include + +#include "bus.h" + +/* vendor/product ID for the chat headset */ +#define GIP_VID_MICROSOFT 0x045e +#define GIP_PID_CHAT_HEADSET 0x0111 + +#define GIP_HDR_CLIENT_ID GENMASK(3, 0) +#define GIP_HDR_LENGTH GENMASK(6, 0) +#define GIP_HDR_EXTENDED BIT(7) + +#define GIP_BATT_LEVEL GENMASK(1, 0) +#define GIP_BATT_TYPE GENMASK(3, 2) +#define GIP_STATUS_CONNECTED BIT(7) + +#define GIP_VKEY_LEFT_WIN 0x5b + +enum gip_command_internal { + GIP_CMD_ACKNOWLEDGE = 0x01, + GIP_CMD_ANNOUNCE = 0x02, + GIP_CMD_STATUS = 0x03, + GIP_CMD_IDENTIFY = 0x04, + GIP_CMD_POWER = 0x05, + GIP_CMD_AUTHENTICATE = 0x06, + GIP_CMD_VIRTUAL_KEY = 0x07, + GIP_CMD_AUDIO_CONTROL = 0x08, + GIP_CMD_LED = 0x0a, + GIP_CMD_HID_REPORT = 0x0b, + GIP_CMD_FIRMWARE = 0x0c, + GIP_CMD_SERIAL_NUMBER = 0x1e, + GIP_CMD_AUDIO_SAMPLES = 0x60, +}; + +enum gip_command_external { + GIP_CMD_RUMBLE = 0x09, + GIP_CMD_INPUT = 0x20, +}; + +enum gip_option { + GIP_OPT_ACKNOWLEDGE = BIT(4), + GIP_OPT_INTERNAL = BIT(5), + GIP_OPT_CHUNK_START = BIT(6), + GIP_OPT_CHUNK = BIT(7), +}; + +enum gip_audio_control { + GIP_AUD_CTRL_VOLUME_CHAT = 0x00, + GIP_AUD_CTRL_FORMAT_CHAT = 0x01, + GIP_AUD_CTRL_FORMAT = 0x02, + GIP_AUD_CTRL_VOLUME = 0x03, +}; + +enum gip_audio_volume_mute { + GIP_AUD_VOLUME_UNMUTED = 0x04, + GIP_AUD_VOLUME_MIC_MUTED = 0x05, +}; + +struct gip_header { + u8 command; + u8 options; + u8 sequence; + u8 length; +} __packed; + +struct gip_chunk_header { + u8 offset_extra; + u8 offset; +} __packed; + +struct gip_pkt_acknowledge { + u8 unknown; + struct gip_header inner; + u8 padding[2]; + __le16 remaining; +} __packed; + +struct gip_pkt_announce { + u8 address[6]; + __le16 unknown; + __le16 vendor_id; + __le16 product_id; + struct gip_version { + __le16 major; + __le16 minor; + __le16 build; + __le16 revision; + } __packed fw_version, hw_version; +} __packed; + +struct gip_pkt_status { + u8 status; + u8 unknown[3]; +} __packed; + +struct gip_pkt_identify { + u8 unknown[16]; + __le16 external_commands_offset; + __le16 unknown_offset; + __le16 audio_formats_offset; + __le16 capabilities_out_offset; + __le16 capabilities_in_offset; + __le16 classes_offset; + __le16 interfaces_offset; + __le16 hid_descriptor_offset; +} __packed; + +struct gip_pkt_power { + u8 mode; +} __packed; + +struct gip_pkt_authenticate { + u8 unknown1; + u8 unknown2; +} __packed; + +struct gip_pkt_virtual_key { + u8 down; + u8 key; +} __packed; + +struct gip_pkt_audio_control { + u8 subcommand; +} __packed; + +struct gip_pkt_audio_volume_chat { + struct gip_pkt_audio_control control; + u8 mute; + u8 gain_out; + u8 out; + u8 in; +} __packed; + +struct gip_pkt_audio_format_chat { + struct gip_pkt_audio_control control; + u8 in_out; +} __packed; + +struct gip_pkt_audio_format { + struct gip_pkt_audio_control control; + u8 in; + u8 out; +} __packed; + +struct gip_pkt_audio_volume { + struct gip_pkt_audio_control control; + u8 mute; + u8 out; + u8 unknown1; + u8 in; + u8 unknown2; + u8 unknown3[2]; +} __packed; + +struct gip_pkt_led { + u8 unknown; + u8 mode; + u8 brightness; +} __packed; + +struct gip_pkt_serial_number { + u8 unknown[2]; + char serial[14]; +} __packed; + +struct gip_pkt_audio_header { + u8 length_extra; + u8 unknown; +} __packed; + +struct gip_command_descriptor { + u8 marker; + u8 unknown1; + u8 command; + u8 length; + u8 unknown2[3]; + u8 options; + u8 unknown3[15]; +} __packed; + +struct gip_chunk { + u16 offset; + void *data; + u8 length; +}; + +static int gip_send_pkt(struct gip_client *client, + struct gip_header *hdr, void *data, int len) +{ + struct gip_adapter *adap = client->adapter; + struct gip_adapter_buffer buf = {}; + int err; + unsigned long flags; + + buf.type = GIP_BUF_DATA; + + spin_lock_irqsave(&adap->send_lock, flags); + + /* sequence number is always greater than zero */ + while (!hdr->sequence) + hdr->sequence = adap->data_sequence++; + + err = adap->ops->get_buffer(adap, &buf); + if (err) { + dev_err(&client->dev, "%s: get buffer failed: %d\n", + __func__, err); + goto err_unlock; + } + + memcpy(buf.data, hdr, sizeof(*hdr)); + if (data) + memcpy(buf.data + sizeof(*hdr), data, len); + + /* set actual length */ + buf.length = sizeof(*hdr) + len; + + /* always fails on adapter removal */ + err = adap->ops->submit_buffer(adap, &buf); + if (err) + dev_dbg(&client->dev, "%s: submit buffer failed: %d\n", + __func__, err); + +err_unlock: + spin_unlock_irqrestore(&adap->send_lock, flags); + + return err; +} + +static int gip_acknowledge_pkt(struct gip_client *client, + struct gip_header *ack, u16 len, u16 remaining) +{ + struct gip_header hdr = {}; + struct gip_pkt_acknowledge pkt = {}; + + hdr.command = GIP_CMD_ACKNOWLEDGE; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.sequence = ack->sequence; + hdr.length = sizeof(pkt); + + pkt.inner.command = ack->command; + pkt.inner.options = client->id | GIP_OPT_INTERNAL; + pkt.inner.sequence = len; + pkt.inner.length = len >> 8; + + /* only required for the start chunk */ + pkt.remaining = cpu_to_le16(remaining); + + return gip_send_pkt(client, &hdr, &pkt, sizeof(pkt)); +} + +static int gip_request_identification(struct gip_client *client) +{ + struct gip_header hdr = {}; + + hdr.command = GIP_CMD_IDENTIFY; + hdr.options = client->id | GIP_OPT_INTERNAL; + + return gip_send_pkt(client, &hdr, NULL, 0); +} + +int gip_set_power_mode(struct gip_client *client, enum gip_power_mode mode) +{ + struct gip_header hdr = {}; + struct gip_pkt_power pkt = {}; + + hdr.command = GIP_CMD_POWER; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.length = sizeof(pkt); + + pkt.mode = mode; + + return gip_send_pkt(client, &hdr, &pkt, sizeof(pkt)); +} +EXPORT_SYMBOL_GPL(gip_set_power_mode); + +int gip_complete_authentication(struct gip_client *client) +{ + struct gip_header hdr = {}; + struct gip_pkt_authenticate pkt = {}; + + hdr.command = GIP_CMD_AUTHENTICATE; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.length = sizeof(pkt); + + pkt.unknown1 = 0x01; + + return gip_send_pkt(client, &hdr, &pkt, sizeof(pkt)); +} +EXPORT_SYMBOL_GPL(gip_complete_authentication); + +static int gip_set_audio_format_chat(struct gip_client *client, + enum gip_audio_format_chat in_out) +{ + struct gip_header hdr = {}; + struct gip_pkt_audio_format_chat pkt = {}; + + hdr.command = GIP_CMD_AUDIO_CONTROL; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.length = sizeof(pkt); + + pkt.control.subcommand = GIP_AUD_CTRL_FORMAT_CHAT; + pkt.in_out = in_out; + + return gip_send_pkt(client, &hdr, &pkt, sizeof(pkt)); +} + +static int gip_set_audio_format(struct gip_client *client, + enum gip_audio_format in, + enum gip_audio_format out) +{ + struct gip_header hdr = {}; + struct gip_pkt_audio_format pkt = {}; + + hdr.command = GIP_CMD_AUDIO_CONTROL; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.length = sizeof(pkt); + + pkt.control.subcommand = GIP_AUD_CTRL_FORMAT; + pkt.in = in; + pkt.out = out; + + return gip_send_pkt(client, &hdr, &pkt, sizeof(pkt)); +} + +int gip_suggest_audio_format(struct gip_client *client, + enum gip_audio_format in, + enum gip_audio_format out) +{ + struct gip_hardware *hw = &client->hardware; + int err; + + /* special handling for the chat headset */ + if (hw->vendor == GIP_VID_MICROSOFT && + hw->product == GIP_PID_CHAT_HEADSET) + err = gip_set_audio_format_chat(client, + GIP_AUD_FORMAT_CHAT_24KHZ); + else + err = gip_set_audio_format(client, in, out); + + if (err) { + dev_err(&client->dev, "%s: set format failed: %d\n", + __func__, err); + return err; + } + + client->audio_config_in.format = in; + client->audio_config_out.format = out; + + return 0; +} +EXPORT_SYMBOL_GPL(gip_suggest_audio_format); + +static int gip_set_audio_volume(struct gip_client *client, u8 in, u8 out) +{ + struct gip_header hdr = {}; + struct gip_pkt_audio_volume pkt = {}; + + hdr.command = GIP_CMD_AUDIO_CONTROL; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.length = sizeof(pkt); + + pkt.control.subcommand = GIP_AUD_CTRL_VOLUME; + pkt.mute = GIP_AUD_VOLUME_UNMUTED; + pkt.out = out; + pkt.in = in; + + return gip_send_pkt(client, &hdr, &pkt, sizeof(pkt)); +} + +int gip_fix_audio_volume(struct gip_client *client) +{ + struct gip_hardware *hw = &client->hardware; + + /* chat headsets have buttons to adjust the hardware volume */ + if (hw->vendor == GIP_VID_MICROSOFT && + hw->product == GIP_PID_CHAT_HEADSET) + return 0; + + /* set hardware volume to maximum */ + return gip_set_audio_volume(client, 100, 100); +} +EXPORT_SYMBOL_GPL(gip_fix_audio_volume); + +int gip_send_rumble(struct gip_client *client, void *pkt, u8 len) +{ + struct gip_header hdr = {}; + + hdr.command = GIP_CMD_RUMBLE; + hdr.options = client->id; + hdr.length = len; + + return gip_send_pkt(client, &hdr, pkt, len); +} +EXPORT_SYMBOL_GPL(gip_send_rumble); + +int gip_set_led_mode(struct gip_client *client, + enum gip_led_mode mode, u8 brightness) +{ + struct gip_header hdr = {}; + struct gip_pkt_led pkt = {}; + + hdr.command = GIP_CMD_LED; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.length = sizeof(pkt); + + pkt.mode = mode; + pkt.brightness = brightness; + + return gip_send_pkt(client, &hdr, &pkt, sizeof(pkt)); +} +EXPORT_SYMBOL_GPL(gip_set_led_mode); + +static void gip_copy_audio_samples(struct gip_client *client, + void *samples, void *buf) +{ + struct gip_audio_config *cfg = &client->audio_config_out; + struct gip_header hdr = {}; + struct gip_pkt_audio_header pkt = {}; + void *src, *dest; + int i; + + /* packet length does not include audio header size */ + hdr.command = GIP_CMD_AUDIO_SAMPLES; + hdr.options = client->id | GIP_OPT_INTERNAL; + hdr.length = cfg->fragment_size; + + if (cfg->fragment_size > GIP_HDR_LENGTH) { + hdr.length |= GIP_HDR_EXTENDED; + pkt.length_extra = GIP_HDR_EXTENDED | (cfg->fragment_size >> 7); + } + + for (i = 0; i < client->adapter->audio_packet_count; i++) { + src = samples + i * cfg->fragment_size; + dest = buf + i * cfg->packet_size; + + /* sequence number is always greater than zero */ + do { + hdr.sequence = client->adapter->audio_sequence++; + } while (!hdr.sequence); + + memcpy(dest, &hdr, sizeof(hdr)); + + if (cfg->fragment_size > GIP_HDR_LENGTH) { + memcpy(dest + sizeof(hdr), &pkt, sizeof(pkt)); + memcpy(dest + sizeof(hdr) + sizeof(pkt), src, + cfg->fragment_size); + } else { + memcpy(dest + sizeof(hdr), src, cfg->fragment_size); + } + } +} + +int gip_send_audio_samples(struct gip_client *client, void *samples) +{ + struct gip_adapter *adap = client->adapter; + struct gip_adapter_buffer buf = {}; + int err; + + buf.type = GIP_BUF_AUDIO; + + /* returns ENOSPC if no buffer is available */ + err = adap->ops->get_buffer(adap, &buf); + if (err) { + dev_err(&client->dev, "%s: get buffer failed: %d\n", + __func__, err); + return err; + } + + gip_copy_audio_samples(client, samples, buf.data); + + /* set actual length */ + buf.length = client->audio_config_out.packet_size * + adap->audio_packet_count; + + /* always fails on adapter removal */ + err = adap->ops->submit_buffer(adap, &buf); + if (err) + dev_dbg(&client->dev, "%s: submit buffer failed: %d\n", + __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_send_audio_samples); + +int gip_enable_audio(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->enable_audio) + return 0; + + err = adap->ops->enable_audio(adap); + if (err) + dev_err(&client->dev, "%s: enable failed: %d\n", + __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_enable_audio); + +int gip_init_audio_in(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->init_audio_in) + return 0; + + err = adap->ops->init_audio_in(adap); + if (err) + dev_err(&client->dev, "%s: init failed: %d\n", __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_init_audio_in); + +int gip_init_audio_out(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->init_audio_out) + return 0; + + err = adap->ops->init_audio_out(adap, + client->audio_config_out.packet_size); + if (err) + dev_err(&client->dev, "%s: init failed: %d\n", __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_init_audio_out); + +void gip_disable_audio(struct gip_client *client) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->disable_audio) + return; + + /* always fails on adapter removal */ + err = adap->ops->disable_audio(adap); + if (err) + dev_dbg(&client->dev, "%s: disable failed: %d\n", + __func__, err); +} +EXPORT_SYMBOL_GPL(gip_disable_audio); + +static int gip_make_audio_config(struct gip_client *client, + struct gip_audio_config *cfg) +{ + switch (cfg->format) { + case GIP_AUD_FORMAT_24KHZ_MONO: + cfg->channels = 1; + cfg->sample_rate = 24000; + break; + case GIP_AUD_FORMAT_48KHZ_STEREO: + cfg->channels = 2; + cfg->sample_rate = 48000; + break; + default: + dev_err(&client->dev, "%s: unknown format: 0x%02x\n", + __func__, cfg->format); + return -ENOTSUPP; + } + + cfg->buffer_size = cfg->sample_rate * cfg->channels * + sizeof(s16) * GIP_AUDIO_INTERVAL / MSEC_PER_SEC; + cfg->fragment_size = cfg->buffer_size / + client->adapter->audio_packet_count; + cfg->packet_size = cfg->fragment_size + sizeof(struct gip_header); + + if (cfg->fragment_size > GIP_HDR_LENGTH) + cfg->packet_size += sizeof(struct gip_pkt_audio_header); + + cfg->valid = true; + + dev_dbg(&client->dev, "%s: rate=%d/%d, buffer=%d\n", __func__, + cfg->sample_rate, cfg->channels, cfg->buffer_size); + + return 0; +} + +static struct gip_info_element *gip_parse_info_element(u8 *data, int len, + __le16 offset, + int item_length) +{ + struct gip_info_element *elem; + u16 off = le16_to_cpu(offset); + u8 count; + int total; + + if (!off) + return ERR_PTR(-ENOTSUPP); + + if (len < off + sizeof(count)) + return ERR_PTR(-EINVAL); + + count = data[off++]; + if (!count) + return ERR_PTR(-ENOTSUPP); + + total = count * item_length; + if (len < off + total) + return ERR_PTR(-EINVAL); + + elem = kzalloc(sizeof(*elem) + total, GFP_ATOMIC); + if (!elem) + return ERR_PTR(-ENOMEM); + + elem->count = count; + memcpy(elem->data, data + off, total); + + return elem; +} + +static int gip_parse_external_commands(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, int len) +{ + struct gip_info_element *cmds; + struct gip_command_descriptor *desc; + int i; + + cmds = gip_parse_info_element(data, len, pkt->external_commands_offset, + sizeof(*desc)); + if (IS_ERR(cmds)) { + if (PTR_ERR(cmds) == -ENOTSUPP) + return 0; + + dev_err(&client->dev, "%s: parse failed: %ld\n", + __func__, PTR_ERR(cmds)); + return PTR_ERR(cmds); + } + + for (i = 0; i < cmds->count; i++) { + desc = (struct gip_command_descriptor *)cmds->data + i; + dev_dbg(&client->dev, + "%s: command=0x%02x, length=0x%02x, options=0x%02x\n", + __func__, desc->command, desc->length, desc->options); + } + + client->external_commands = cmds; + + return 0; +} + +static int gip_parse_audio_formats(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, int len) +{ + struct gip_info_element *fmts; + + fmts = gip_parse_info_element(data, len, + pkt->audio_formats_offset, 2); + if (IS_ERR(fmts)) { + if (PTR_ERR(fmts) == -ENOTSUPP) + return 0; + + dev_err(&client->dev, "%s: parse failed: %ld\n", + __func__, PTR_ERR(fmts)); + return PTR_ERR(fmts); + } + + dev_dbg(&client->dev, "%s: formats=%*phD\n", __func__, + fmts->count * 2, fmts->data); + client->audio_formats = fmts; + + return 0; +} + +static int gip_parse_capabilities(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, int len) +{ + struct gip_info_element *caps; + + caps = gip_parse_info_element(data, len, + pkt->capabilities_out_offset, 1); + if (IS_ERR(caps)) { + dev_err(&client->dev, "%s: parse out failed: %ld\n", + __func__, PTR_ERR(caps)); + return PTR_ERR(caps); + } + + dev_dbg(&client->dev, "%s: out=%*phD\n", __func__, + caps->count, caps->data); + client->capabilities_out = caps; + + caps = gip_parse_info_element(data, len, + pkt->capabilities_in_offset, 1); + if (IS_ERR(caps)) { + dev_err(&client->dev, "%s: parse in failed: %ld\n", + __func__, PTR_ERR(caps)); + return PTR_ERR(caps); + } + + dev_dbg(&client->dev, "%s: in=%*phD\n", __func__, + caps->count, caps->data); + client->capabilities_in = caps; + + return 0; +} + +static int gip_parse_classes(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, int len) +{ + struct gip_classes *classes; + u16 off = le16_to_cpu(pkt->classes_offset); + u8 count; + u16 str_len; + char *str; + + if (len < off + sizeof(count)) + return -EINVAL; + + /* number of individual strings */ + count = data[off++]; + if (!count) + return -EINVAL; + + classes = kzalloc(sizeof(*classes) + sizeof(char *) * count, + GFP_ATOMIC); + if (!classes) + return -ENOMEM; + + client->classes = classes; + + while (classes->count < count) { + if (len < off + sizeof(str_len)) + return -EINVAL; + + str_len = le16_to_cpup((u16 *)(data + off)); + off += sizeof(str_len); + if (!str_len || len < off + str_len) + return -EINVAL; + + /* null-terminated string */ + str = kzalloc(str_len + 1, GFP_ATOMIC); + if (!str) + return -ENOMEM; + + memcpy(str, data + off, str_len); + classes->strings[classes->count] = str; + classes->count++; + off += str_len; + + dev_dbg(&client->dev, "%s: class=%s\n", __func__, str); + } + + return 0; +} + +static int gip_parse_interfaces(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, int len) +{ + struct gip_info_element *intfs; + guid_t *guid; + int i; + + intfs = gip_parse_info_element(data, len, pkt->interfaces_offset, + sizeof(guid_t)); + if (IS_ERR(intfs)) { + dev_err(&client->dev, "%s: parse failed: %ld\n", + __func__, PTR_ERR(intfs)); + return PTR_ERR(intfs); + } + + for (i = 0; i < intfs->count; i++) { + guid = (guid_t *)intfs->data + i; + dev_dbg(&client->dev, "%s: guid=%pUb\n", __func__, guid); + } + + client->interfaces = intfs; + + return 0; +} + +static int gip_parse_hid_descriptor(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, int len) +{ + struct gip_info_element *desc; + + desc = gip_parse_info_element(data, len, + pkt->hid_descriptor_offset, 1); + if (IS_ERR(desc)) { + if (PTR_ERR(desc) == -ENOTSUPP) + return 0; + + dev_err(&client->dev, "%s: parse failed: %ld\n", + __func__, PTR_ERR(desc)); + return PTR_ERR(desc); + } + + dev_dbg(&client->dev, "%s: length=0x%02x\n", __func__, desc->count); + client->hid_descriptor = desc; + + return 0; +} + +static int gip_handle_pkt_announce(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_announce *pkt = data; + struct gip_hardware *hw = &client->hardware; + + if (len != hdr->length || len != sizeof(*pkt)) + return -EINVAL; + + if (atomic_read(&client->state) != GIP_CL_CONNECTED) { + dev_warn(&client->dev, "%s: invalid state\n", __func__); + return 0; + } + + hw->vendor = le16_to_cpu(pkt->vendor_id); + hw->product = le16_to_cpu(pkt->product_id); + hw->version = (le16_to_cpu(pkt->fw_version.major) << 8) | + le16_to_cpu(pkt->fw_version.minor); + + dev_dbg(&client->dev, + "%s: address=%pM, vendor=0x%04x, product=0x%04x\n", + __func__, pkt->address, hw->vendor, hw->product); + dev_dbg(&client->dev, + "%s: firmware=%u.%u.%u.%u, hardware=%u.%u.%u.%u\n", + __func__, + le16_to_cpu(pkt->fw_version.major), + le16_to_cpu(pkt->fw_version.minor), + le16_to_cpu(pkt->fw_version.build), + le16_to_cpu(pkt->fw_version.revision), + le16_to_cpu(pkt->hw_version.major), + le16_to_cpu(pkt->hw_version.minor), + le16_to_cpu(pkt->hw_version.build), + le16_to_cpu(pkt->hw_version.revision)); + + atomic_set(&client->state, GIP_CL_ANNOUNCED); + + return gip_request_identification(client); +} + +static int gip_handle_pkt_status(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_status *pkt = data; + + /* some devices occasionally send larger status packets */ + if (len != hdr->length || len < sizeof(*pkt)) + return -EINVAL; + + if (!(pkt->status & GIP_STATUS_CONNECTED)) { + /* schedule client removal */ + dev_dbg(&client->dev, "%s: disconnected\n", __func__); + gip_unregister_client(client); + return 0; + } + + if (!client->drv || !client->drv->ops.battery) + return 0; + + return client->drv->ops.battery(client, + FIELD_GET(GIP_BATT_TYPE, pkt->status), + FIELD_GET(GIP_BATT_LEVEL, pkt->status)); +} + +static int gip_handle_pkt_identify(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_identify *pkt = data; + int err; + + if (len < sizeof(*pkt)) + return -EINVAL; + + if (atomic_read(&client->state) != GIP_CL_ANNOUNCED) { + dev_warn(&client->dev, "%s: invalid state\n", __func__); + return 0; + } + + /* skip unknown header */ + data += sizeof(pkt->unknown); + len -= sizeof(pkt->unknown); + + err = gip_parse_external_commands(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_audio_formats(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_capabilities(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_classes(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_interfaces(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_hid_descriptor(client, pkt, data, len); + if (err) + goto err_free_info; + + /* schedule client registration */ + gip_register_client(client); + + return 0; + +err_free_info: + gip_free_client_info(client); + + return err; +} + +static int gip_handle_pkt_virtual_key(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_virtual_key *pkt = data; + + if (len != hdr->length || len != sizeof(*pkt)) + return -EINVAL; + + if (pkt->key != GIP_VKEY_LEFT_WIN) + return -EINVAL; + + if (!client->drv || !client->drv->ops.guide_button) + return 0; + + return client->drv->ops.guide_button(client, pkt->down); +} + +static int gip_handle_pkt_audio_format_chat(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_audio_format_chat *pkt = data; + struct gip_audio_config *in = &client->audio_config_in; + struct gip_audio_config *out = &client->audio_config_out; + int err; + + if (len != hdr->length || len != sizeof(*pkt)) + return -EINVAL; + + /* chat headsets apparently default to 24 kHz */ + if (pkt->in_out != GIP_AUD_FORMAT_CHAT_24KHZ || in->valid || out->valid) + return -EPROTO; + + err = gip_make_audio_config(client, in); + if (err) + return err; + + err = gip_make_audio_config(client, out); + if (err) + return err; + + if (!client->drv || !client->drv->ops.audio_ready) + return 0; + + return client->drv->ops.audio_ready(client); +} + +static int gip_handle_pkt_audio_volume_chat(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_audio_volume_chat *pkt = data; + + if (len != hdr->length || len != sizeof(*pkt)) + return -EINVAL; + + if (!client->drv || !client->drv->ops.audio_volume) + return 0; + + return client->drv->ops.audio_volume(client, pkt->in, pkt->out); +} + +static int gip_handle_pkt_audio_format(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_audio_format *pkt = data; + struct gip_audio_config *in = &client->audio_config_in; + struct gip_audio_config *out = &client->audio_config_out; + int err; + + if (len != hdr->length || len != sizeof(*pkt)) + return -EINVAL; + + /* format has already been accepted */ + if (in->valid || out->valid) + return -EPROTO; + + /* client rejected format, accept new format */ + if (pkt->in != in->format || pkt->out != out->format) { + dev_warn(&client->dev, "%s: rejected: 0x%02x/0x%02x\n", + __func__, in->format, out->format); + return gip_suggest_audio_format(client, pkt->in, pkt->out); + } + + err = gip_make_audio_config(client, in); + if (err) + return err; + + err = gip_make_audio_config(client, out); + if (err) + return err; + + if (!client->drv || !client->drv->ops.audio_ready) + return 0; + + return client->drv->ops.audio_ready(client); +} + +static int gip_handle_pkt_audio_volume(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_audio_volume *pkt = data; + + if (len != hdr->length || len != sizeof(*pkt)) + return -EINVAL; + + if (!client->drv || !client->drv->ops.audio_volume) + return 0; + + return client->drv->ops.audio_volume(client, pkt->in, pkt->out); +} + +static int gip_handle_pkt_audio_control(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_audio_control *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + switch (pkt->subcommand) { + case GIP_AUD_CTRL_FORMAT_CHAT: + return gip_handle_pkt_audio_format_chat(client, hdr, data, len); + case GIP_AUD_CTRL_VOLUME_CHAT: + return gip_handle_pkt_audio_volume_chat(client, hdr, data, len); + case GIP_AUD_CTRL_FORMAT: + return gip_handle_pkt_audio_format(client, hdr, data, len); + case GIP_AUD_CTRL_VOLUME: + return gip_handle_pkt_audio_volume(client, hdr, data, len); + } + + dev_err(&client->dev, "%s: unknown subcommand: 0x%02x\n", + __func__, pkt->subcommand); + + return -EPROTO; +} + +static int gip_handle_pkt_hid_report(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + if (len != hdr->length) + return -EINVAL; + + if (!client->drv || !client->drv->ops.hid_report) + return 0; + + return client->drv->ops.hid_report(client, data, len); +} + +static int gip_handle_pkt_input(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + if (len != hdr->length) + return -EINVAL; + + if (!client->drv || !client->drv->ops.input) + return 0; + + return client->drv->ops.input(client, data, len); +} + +static int gip_handle_pkt_audio_samples(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + struct gip_pkt_audio_header *pkt = data; + u16 total = hdr->length & GIP_HDR_LENGTH; + + if (len < sizeof(*pkt)) + return -EINVAL; + + /* wireless clients use extended audio headers */ + if (hdr->length & GIP_HDR_EXTENDED) { + total |= (pkt->length_extra & GENMASK(3, 0)) << 7; + if (total < sizeof(*pkt) + 2 || len < total) + return -EINVAL; + + data += sizeof(*pkt) + 2; + total -= sizeof(*pkt) + 2; + } else { + if (total < sizeof(*pkt) || len < total) + return -EINVAL; + + data += sizeof(*pkt); + total -= sizeof(*pkt); + } + + if (!client->drv || !client->drv->ops.audio_samples) + return 0; + + return client->drv->ops.audio_samples(client, data, total); +} + +static int gip_dispatch_pkt_internal(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + switch (hdr->command) { + case GIP_CMD_ANNOUNCE: + return gip_handle_pkt_announce(client, hdr, data, len); + case GIP_CMD_STATUS: + return gip_handle_pkt_status(client, hdr, data, len); + case GIP_CMD_IDENTIFY: + return gip_handle_pkt_identify(client, hdr, data, len); + case GIP_CMD_VIRTUAL_KEY: + return gip_handle_pkt_virtual_key(client, hdr, data, len); + case GIP_CMD_AUDIO_CONTROL: + return gip_handle_pkt_audio_control(client, hdr, data, len); + case GIP_CMD_HID_REPORT: + return gip_handle_pkt_hid_report(client, hdr, data, len); + case GIP_CMD_AUDIO_SAMPLES: + return gip_handle_pkt_audio_samples(client, hdr, data, len); + } + + return 0; +} + +static int gip_dispatch_pkt(struct gip_client *client, + struct gip_header *hdr, + void *data, int len) +{ + if (hdr->options & GIP_OPT_INTERNAL) + return gip_dispatch_pkt_internal(client, hdr, data, len); + + switch (hdr->command) { + case GIP_CMD_INPUT: + return gip_handle_pkt_input(client, hdr, data, len); + } + + return 0; +} + +static int gip_parse_chunk(struct gip_client *client, + struct gip_chunk *chunk, + void *data, int len) +{ + struct gip_header *hdr = data; + struct gip_chunk_header *chunk_hdr = data + sizeof(*hdr); + + if (len < sizeof(*hdr) + sizeof(*chunk_hdr)) { + dev_err(&client->dev, "%s: invalid length\n", __func__); + return -EINVAL; + } + + if (hdr->length & GIP_HDR_EXTENDED) + chunk->offset = chunk_hdr->offset; + else + chunk->offset = (chunk_hdr->offset << 7) | + (chunk_hdr->offset_extra & GENMASK(6, 0)); + + chunk->data = data + sizeof(*hdr) + sizeof(*chunk_hdr); + chunk->length = hdr->length & GIP_HDR_LENGTH; + + if (len != chunk->length + sizeof(*hdr) + sizeof(*chunk_hdr)) { + dev_err(&client->dev, "%s: length mismatch\n", __func__); + return -EINVAL; + } + + dev_dbg(&client->dev, "%s: offset=0x%04x, length=0x%02x\n", + __func__, chunk->offset, chunk->length); + + return 0; +} + +static int gip_init_chunk_buffer(struct gip_client *client, u16 len) +{ + struct gip_chunk_buffer *buf = client->chunk_buf; + + if (buf) { + dev_err(&client->dev, "%s: already initialized\n", __func__); + kfree(buf); + client->chunk_buf = NULL; + } + + buf = kzalloc(sizeof(*buf) + len, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + dev_dbg(&client->dev, "%s: length=0x%04x\n", __func__, len); + buf->length = len; + client->chunk_buf = buf; + + return 0; +} + +static int gip_copy_chunk_data(struct gip_client *client, + struct gip_chunk chunk) +{ + struct gip_chunk_buffer *buf = client->chunk_buf; + + if (!buf) { + dev_err(&client->dev, "%s: buffer not allocated\n", __func__); + return -EPROTO; + } + + if (buf->full) { + dev_err(&client->dev, "%s: buffer full\n", __func__); + return -ENOMEM; + } + + if (chunk.offset + chunk.length > buf->length) { + dev_err(&client->dev, "%s: buffer too small\n", __func__); + return -EINVAL; + } + + if (chunk.length) { + memcpy(buf->data + chunk.offset, chunk.data, chunk.length); + } else { + /* empty chunk signals the completion of the transfer */ + /* offset *should* be the total length of all chunks */ + /* certain third party devices report an incorrect length */ + if (chunk.offset != buf->length) + dev_warn(&client->dev, "%s: length mismatch\n", + __func__); + + dev_dbg(&client->dev, "%s: buffer complete\n", __func__); + buf->full = true; + } + + return 0; +} + +static int gip_process_chunk(struct gip_client *client, void *data, int len) +{ + struct gip_header *hdr = data; + struct gip_chunk chunk = {}; + int err; + + err = gip_parse_chunk(client, &chunk, data, len); + if (err) + return err; + + if (hdr->options & GIP_OPT_CHUNK_START) { + /* offset is total length of all chunks */ + err = gip_init_chunk_buffer(client, chunk.offset); + if (err) + return err; + + /* acknowledge with remaining length */ + err = gip_acknowledge_pkt(client, hdr, chunk.length, + client->chunk_buf->length - + chunk.length); + if (err) + return err; + + chunk.offset = 0; + } else if (hdr->options & GIP_OPT_ACKNOWLEDGE) { + /* acknowledge with total buffer length */ + err = gip_acknowledge_pkt(client, hdr, + client->chunk_buf->length, 0); + if (err) + return err; + } + + return gip_copy_chunk_data(client, chunk); +} + +static int gip_process_pkt_coherent(struct gip_client *client, + void *data, int len) +{ + struct gip_header *hdr = data; + int err; + + if (hdr->options & GIP_OPT_ACKNOWLEDGE) { + err = gip_acknowledge_pkt(client, hdr, hdr->length, 0); + if (err) + return err; + } + + return gip_dispatch_pkt(client, hdr, + data + sizeof(*hdr), len - sizeof(*hdr)); +} + +static int gip_process_pkt_chunked(struct gip_client *client, + void *data, int len) +{ + struct gip_header *hdr = data; + struct gip_chunk_buffer *buf; + int err; + + err = gip_process_chunk(client, data, len); + if (err) + return err; + + /* all chunks have been received */ + buf = client->chunk_buf; + if (buf->full) { + err = gip_dispatch_pkt(client, hdr, buf->data, buf->length); + + kfree(buf); + client->chunk_buf = NULL; + } + + return err; +} + +int gip_process_buffer(struct gip_adapter *adap, void *data, int len) +{ + struct gip_header *hdr = data; + struct gip_client *client; + u8 id = hdr->options & GIP_HDR_CLIENT_ID; + int err = 0; + unsigned long flags; + + if (len < sizeof(*hdr)) { + dev_err(&adap->dev, "%s: invalid length\n", __func__); + return -EINVAL; + } + + client = gip_get_or_init_client(adap, id); + if (IS_ERR(client)) { + dev_err(&adap->dev, "%s: get/init client failed: %ld\n", + __func__, PTR_ERR(client)); + return PTR_ERR(client); + } + + spin_lock_irqsave(&client->lock, flags); + + if (atomic_read(&client->state) == GIP_CL_DISCONNECTED) + goto err_unlock; + + if (hdr->options & GIP_OPT_CHUNK) + err = gip_process_pkt_chunked(client, data, len); + else + err = gip_process_pkt_coherent(client, data, len); + + if (err) { + dev_err(&adap->dev, "%s: process packet failed: %d\n", + __func__, err); + print_hex_dump_debug("xone-gip packet: ", DUMP_PREFIX_NONE, + 16, 1, data, len, false); + } + +err_unlock: + spin_unlock_irqrestore(&client->lock, flags); + gip_put_client(client); + + return err; +} +EXPORT_SYMBOL_GPL(gip_process_buffer); diff --git a/drivers/hid/xone/bus/protocol.h b/drivers/hid/xone/bus/protocol.h new file mode 100644 index 000000000..7b86e41da --- /dev/null +++ b/drivers/hid/xone/bus/protocol.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#pragma once + +#include + +/* time between audio packets in ms */ +#define GIP_AUDIO_INTERVAL 8 + +enum gip_client_state { + GIP_CL_CONNECTED, + GIP_CL_ANNOUNCED, + GIP_CL_IDENTIFIED, + GIP_CL_DISCONNECTED, +}; + +enum gip_battery_type { + GIP_BATT_TYPE_NONE = 0x00, + GIP_BATT_TYPE_STANDARD = 0x01, + GIP_BATT_TYPE_KIT = 0x02, + GIP_BATT_TYPE_UNKNOWN = 0x03, +}; + +enum gip_battery_level { + GIP_BATT_LEVEL_LOW = 0x00, + GIP_BATT_LEVEL_NORMAL = 0x01, + GIP_BATT_LEVEL_HIGH = 0x02, + GIP_BATT_LEVEL_FULL = 0x03, +}; + +enum gip_power_mode { + GIP_PWR_ON = 0x00, + GIP_PWR_SLEEP = 0x01, + GIP_PWR_OFF = 0x04, + GIP_PWR_RESET = 0x07, +}; + +enum gip_audio_format { + GIP_AUD_FORMAT_24KHZ_MONO = 0x09, + GIP_AUD_FORMAT_48KHZ_STEREO = 0x10, +}; + +enum gip_audio_format_chat { + GIP_AUD_FORMAT_CHAT_24KHZ = 0x04, + GIP_AUD_FORMAT_CHAT_16KHZ = 0x05, +}; + +enum gip_led_mode { + GIP_LED_OFF = 0x00, + GIP_LED_ON = 0x01, + GIP_LED_BLINK_FAST = 0x02, + GIP_LED_BLINK_NORMAL = 0x03, + GIP_LED_BLINK_SLOW = 0x04, + GIP_LED_FADE_SLOW = 0x08, + GIP_LED_FADE_FAST = 0x09, +}; + +struct gip_chunk_buffer { + bool full; + u16 length; + u8 data[]; +}; + +struct gip_hardware { + u16 vendor; + u16 product; + u16 version; +}; + +struct gip_info_element { + u8 count; + u8 data[]; +}; + +struct gip_audio_config { + enum gip_audio_format format; + + int channels; + int sample_rate; + + int buffer_size; + int fragment_size; + int packet_size; + + bool valid; +}; + +struct gip_classes { + u8 count; + const char *strings[]; +}; + +struct gip_client; +struct gip_adapter; + +int gip_set_power_mode(struct gip_client *client, enum gip_power_mode mode); +int gip_complete_authentication(struct gip_client *client); +int gip_suggest_audio_format(struct gip_client *client, + enum gip_audio_format in, + enum gip_audio_format out); +int gip_fix_audio_volume(struct gip_client *client); +int gip_send_rumble(struct gip_client *client, void *pkt, u8 len); +int gip_set_led_mode(struct gip_client *client, + enum gip_led_mode mode, u8 brightness); +int gip_send_audio_samples(struct gip_client *client, void *samples); + +int gip_enable_audio(struct gip_client *client); +int gip_init_audio_in(struct gip_client *client); +int gip_init_audio_out(struct gip_client *client); +void gip_disable_audio(struct gip_client *client); + +int gip_process_buffer(struct gip_adapter *adap, void *data, int len); diff --git a/drivers/hid/xone/driver/chatpad.c b/drivers/hid/xone/driver/chatpad.c new file mode 100644 index 000000000..ad8394563 --- /dev/null +++ b/drivers/hid/xone/driver/chatpad.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include + +#include "common.h" + +#define GIP_CP_NAME "Microsoft X-Box One chatpad" + +struct gip_chatpad { + struct gip_client *client; + struct gip_input input; + + struct hid_device *hid_dev; +}; + +static int gip_chatpad_hid_start(struct hid_device *dev) +{ + return 0; +} + +static void gip_chatpad_hid_stop(struct hid_device *dev) +{ +} + +static int gip_chatpad_hid_open(struct hid_device *dev) +{ + return 0; +} + +static void gip_chatpad_hid_close(struct hid_device *dev) +{ +} + +static int gip_chatpad_hid_parse(struct hid_device *dev) +{ + struct gip_chatpad *chatpad = dev->driver_data; + struct gip_client *client = chatpad->client; + struct gip_info_element *desc_info = client->hid_descriptor; + struct hid_descriptor *desc = (struct hid_descriptor *)desc_info->data; + + if (desc->bLength < sizeof(*desc) || desc->bNumDescriptors != 1) { + dev_err(&client->dev, "%s: invalid descriptor\n", __func__); + return -EINVAL; + } + + dev->version = le16_to_cpu(desc->bcdHID); + dev->country = desc->bCountryCode; + + return hid_parse_report(dev, desc_info->data + sizeof(*desc), + desc_info->count - sizeof(*desc)); +} + +static int gip_chatpad_hid_raw_request(struct hid_device *dev, + unsigned char report_num, __u8 *buf, + size_t len, unsigned char report_type, + int request_type) +{ + return 0; +} + +static struct hid_ll_driver gip_chatpad_hid_driver = { + .start = gip_chatpad_hid_start, + .stop = gip_chatpad_hid_stop, + .open = gip_chatpad_hid_open, + .close = gip_chatpad_hid_close, + .parse = gip_chatpad_hid_parse, + .raw_request = gip_chatpad_hid_raw_request, +}; + +static int gip_chatpad_init_input(struct gip_chatpad *chatpad) +{ + int err; + + input_set_capability(chatpad->input.dev, EV_KEY, BTN_MODE); + + err = input_register_device(chatpad->input.dev); + if (err) + dev_err(&chatpad->client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} + +static int gip_chatpad_init_hid(struct gip_chatpad *chatpad) +{ + struct gip_client *client = chatpad->client; + struct hid_device *dev; + int err; + + dev = hid_allocate_device(); + if (IS_ERR(dev)) { + dev_err(&client->dev, "%s: allocate failed: %ld\n", + __func__, PTR_ERR(dev)); + return PTR_ERR(dev); + } + + dev->bus = BUS_USB; + dev->vendor = client->hardware.vendor; + dev->product = client->hardware.product; + dev->version = client->hardware.version; + dev->dev.parent = &client->dev; + dev->ll_driver = &gip_chatpad_hid_driver; + + strscpy(dev->name, GIP_CP_NAME, sizeof(dev->name)); + snprintf(dev->phys, sizeof(dev->phys), "%s/input1", + dev_name(&client->dev)); + + dev->driver_data = chatpad; + + err = hid_add_device(dev); + if (err) { + dev_err(&client->dev, "%s: add failed: %d\n", __func__, err); + hid_destroy_device(dev); + return err; + } + + chatpad->hid_dev = dev; + + return 0; +} + +static int gip_chatpad_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); + + input_report_key(chatpad->input.dev, BTN_MODE, down); + input_sync(chatpad->input.dev); + + return 0; +} + +static int gip_chatpad_op_hid_report(struct gip_client *client, + void *data, int len) +{ + struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); + + return hid_input_report(chatpad->hid_dev, HID_INPUT_REPORT, + data, len, true); +} + +static int gip_chatpad_probe(struct gip_client *client) +{ + struct gip_chatpad *chatpad; + struct gip_info_element *hid_desc = client->hid_descriptor; + int err; + + if (!hid_desc || hid_desc->count < sizeof(struct hid_descriptor)) + return -ENODEV; + + chatpad = devm_kzalloc(&client->dev, sizeof(*chatpad), GFP_KERNEL); + if (!chatpad) + return -ENOMEM; + + chatpad->client = client; + + err = gip_init_input(&chatpad->input, client, GIP_CP_NAME); + if (err) + return err; + + err = gip_chatpad_init_input(chatpad); + if (err) + return err; + + err = gip_chatpad_init_hid(chatpad); + if (err) + return err; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + dev_set_drvdata(&client->dev, chatpad); + + return 0; +} + +static void gip_chatpad_remove(struct gip_client *client) +{ + struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); + + hid_destroy_device(chatpad->hid_dev); + dev_set_drvdata(&client->dev, NULL); +} + +static struct gip_driver gip_chatpad_driver = { + .name = "xone-gip-chatpad", + .class = "Windows.Xbox.Input.Chatpad", + .ops = { + .guide_button = gip_chatpad_op_guide_button, + .hid_report = gip_chatpad_op_hid_report, + }, + .probe = gip_chatpad_probe, + .remove = gip_chatpad_remove, +}; +module_gip_driver(gip_chatpad_driver); + +MODULE_ALIAS("gip:Windows.Xbox.Input.Chatpad"); +MODULE_AUTHOR("Severin von Wnuck "); +MODULE_DESCRIPTION("xone GIP chatpad driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/common.c b/drivers/hid/xone/driver/common.c new file mode 100644 index 000000000..142d30c9c --- /dev/null +++ b/drivers/hid/xone/driver/common.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include + +#include "common.h" + +#define GIP_LED_BRIGHTNESS_DEFAULT 20 +#define GIP_LED_BRIGHTNESS_MAX 50 + +static enum power_supply_property gip_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static int gip_get_battery_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct gip_battery *batt = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = batt->status; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = batt->capacity; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = batt->name; + break; + default: + return -EINVAL; + } + + return 0; +} + +int gip_init_battery(struct gip_battery *batt, struct gip_client *client, + const char *name) +{ + struct power_supply_config cfg = {}; + + batt->name = name; + batt->status = POWER_SUPPLY_STATUS_UNKNOWN; + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + + batt->desc.name = dev_name(&client->dev); + batt->desc.type = POWER_SUPPLY_TYPE_BATTERY; + batt->desc.properties = gip_battery_props; + batt->desc.num_properties = ARRAY_SIZE(gip_battery_props); + batt->desc.get_property = gip_get_battery_prop; + + cfg.drv_data = batt; + + batt->supply = devm_power_supply_register(&client->dev, &batt->desc, + &cfg); + if (IS_ERR(batt->supply)) { + dev_err(&client->dev, "%s: register failed: %ld\n", + __func__, PTR_ERR(batt->supply)); + return PTR_ERR(batt->supply); + } + + power_supply_powers(batt->supply, &client->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(gip_init_battery); + +void gip_report_battery(struct gip_battery *batt, + enum gip_battery_type type, + enum gip_battery_level level) +{ + if (type == GIP_BATT_TYPE_NONE) + batt->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + batt->status = POWER_SUPPLY_STATUS_DISCHARGING; + + if (type == GIP_BATT_TYPE_NONE) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else if (level == GIP_BATT_LEVEL_LOW) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (level == GIP_BATT_LEVEL_NORMAL) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (level == GIP_BATT_LEVEL_HIGH) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else if (level == GIP_BATT_LEVEL_FULL) + batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + if (batt->supply) + power_supply_changed(batt->supply); +} +EXPORT_SYMBOL_GPL(gip_report_battery); + +static void gip_led_brightness_set(struct led_classdev *dev, + enum led_brightness brightness) +{ + struct gip_led *led = container_of(dev, typeof(*led), dev); + int err; + + if (dev->flags & LED_UNREGISTERING) + return; + + dev_dbg(&led->client->dev, "%s: brightness=%d\n", __func__, brightness); + + err = gip_set_led_mode(led->client, led->mode, brightness); + if (err) + dev_err(&led->client->dev, "%s: set LED mode failed: %d\n", + __func__, err); +} + +static ssize_t gip_led_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct gip_led *led = container_of(cdev, typeof(*led), dev); + + return sprintf(buf, "%u\n", led->mode); +} + +static ssize_t gip_led_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct gip_led *led = container_of(cdev, typeof(*led), dev); + u8 mode; + int err; + + err = kstrtou8(buf, 10, &mode); + if (err) + return err; + + dev_dbg(&led->client->dev, "%s: mode=%u\n", __func__, mode); + led->mode = mode; + + err = gip_set_led_mode(led->client, mode, cdev->brightness); + if (err) { + dev_err(&led->client->dev, "%s: set LED mode failed: %d\n", + __func__, err); + return err; + } + + return count; +} + +static struct device_attribute gip_led_attr_mode = + __ATTR(mode, 0644, gip_led_mode_show, gip_led_mode_store); + +static struct attribute *gip_led_attrs[] = { + &gip_led_attr_mode.attr, + NULL, +}; +ATTRIBUTE_GROUPS(gip_led); + +int gip_init_led(struct gip_led *led, struct gip_client *client) +{ + int err; + + /* set default brightness */ + err = gip_set_led_mode(client, GIP_LED_ON, GIP_LED_BRIGHTNESS_DEFAULT); + if (err) { + dev_err(&client->dev, "%s: set brightness failed: %d\n", + __func__, err); + return err; + } + + led->dev.name = devm_kasprintf(&client->dev, GFP_KERNEL, + "%s:white:status", + dev_name(&client->dev)); + if (!led->dev.name) + return -ENOMEM; + + led->dev.brightness = GIP_LED_BRIGHTNESS_DEFAULT; + led->dev.max_brightness = GIP_LED_BRIGHTNESS_MAX; + led->dev.brightness_set = gip_led_brightness_set; + led->dev.groups = gip_led_groups; + + led->client = client; + led->mode = GIP_LED_ON; + + err = devm_led_classdev_register(&client->dev, &led->dev); + if (err) + dev_err(&client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} +EXPORT_SYMBOL_GPL(gip_init_led); + +int gip_init_input(struct gip_input *input, struct gip_client *client, + const char *name) +{ + input->dev = devm_input_allocate_device(&client->dev); + if (!input->dev) + return -ENOMEM; + + input->dev->phys = devm_kasprintf(&client->dev, GFP_KERNEL, + "%s/input0", dev_name(&client->dev)); + if (!input->dev->phys) + return -ENOMEM; + + input->dev->name = name; + input->dev->id.bustype = BUS_VIRTUAL; + input->dev->id.vendor = client->hardware.vendor; + input->dev->id.product = client->hardware.product; + input->dev->id.version = client->hardware.version; + input->dev->dev.parent = &client->dev; + + return 0; +} +EXPORT_SYMBOL_GPL(gip_init_input); + +MODULE_AUTHOR("Severin von Wnuck "); +MODULE_DESCRIPTION("xone GIP common driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/common.h b/drivers/hid/xone/driver/common.h new file mode 100644 index 000000000..4a4626f12 --- /dev/null +++ b/drivers/hid/xone/driver/common.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#pragma once + +#include +#include +#include + +#include "../bus/bus.h" + +struct gip_battery { + struct power_supply *supply; + struct power_supply_desc desc; + + const char *name; + int status; + int capacity; +}; + +struct gip_led { + struct led_classdev dev; + + struct gip_client *client; + enum gip_led_mode mode; +}; + +struct gip_input { + struct input_dev *dev; +}; + +int gip_init_battery(struct gip_battery *batt, struct gip_client *client, + const char *name); +void gip_report_battery(struct gip_battery *batt, + enum gip_battery_type type, + enum gip_battery_level level); + +int gip_init_led(struct gip_led *led, struct gip_client *client); + +int gip_init_input(struct gip_input *input, struct gip_client *client, + const char *name); diff --git a/drivers/hid/xone/driver/gamepad.c b/drivers/hid/xone/driver/gamepad.c new file mode 100644 index 000000000..7847ece67 --- /dev/null +++ b/drivers/hid/xone/driver/gamepad.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include +#include +#include + +#include "common.h" + +#define GIP_GP_NAME "Microsoft X-Box One pad" + +/* vendor/product ID for the elite controller series 2 */ +#define GIP_GP_VID_MICROSOFT 0x045e +#define GIP_GP_PID_ELITE2 0x0b00 + +#define GIP_GP_RUMBLE_DELAY msecs_to_jiffies(10) +#define GIP_GP_RUMBLE_MAX 100 + +static const guid_t gip_gamepad_guid_middle_button = + GUID_INIT(0xecddd2fe, 0xd387, 0x4294, + 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d); + +enum gip_gamepad_button { + GIP_GP_BTN_MENU = BIT(2), + GIP_GP_BTN_VIEW = BIT(3), + GIP_GP_BTN_A = BIT(4), + GIP_GP_BTN_B = BIT(5), + GIP_GP_BTN_X = BIT(6), + GIP_GP_BTN_Y = BIT(7), + GIP_GP_BTN_DPAD_U = BIT(8), + GIP_GP_BTN_DPAD_D = BIT(9), + GIP_GP_BTN_DPAD_L = BIT(10), + GIP_GP_BTN_DPAD_R = BIT(11), + GIP_GP_BTN_BUMPER_L = BIT(12), + GIP_GP_BTN_BUMPER_R = BIT(13), + GIP_GP_BTN_STICK_L = BIT(14), + GIP_GP_BTN_STICK_R = BIT(15), +}; + +enum gip_gamepad_motor { + GIP_GP_MOTOR_R = BIT(0), + GIP_GP_MOTOR_L = BIT(1), + GIP_GP_MOTOR_RT = BIT(2), + GIP_GP_MOTOR_LT = BIT(3), +}; + +struct gip_gamepad_pkt_input { + __le16 buttons; + __le16 trigger_left; + __le16 trigger_right; + __le16 stick_left_x; + __le16 stick_left_y; + __le16 stick_right_x; + __le16 stick_right_y; +} __packed; + +struct gip_gamepad_pkt_series_xs { + u8 unknown[4]; + u8 share_button; +} __packed; + +struct gip_gamepad_pkt_rumble { + u8 unknown; + u8 motors; + u8 left_trigger; + u8 right_trigger; + u8 left; + u8 right; + u8 duration; + u8 delay; + u8 repeat; +} __packed; + +struct gip_gamepad { + struct gip_client *client; + struct gip_battery battery; + struct gip_led led; + struct gip_input input; + + bool series_xs; + + struct gip_gamepad_rumble { + /* serializes access to rumble packet */ + spinlock_t lock; + bool queued; + unsigned long last; + struct timer_list timer; + struct gip_gamepad_pkt_rumble pkt; + } rumble; +}; + +static void gip_gamepad_send_rumble(struct timer_list *timer) +{ + struct gip_gamepad_rumble *rumble = from_timer(rumble, timer, timer); + struct gip_gamepad *gamepad = container_of(rumble, typeof(*gamepad), + rumble); + unsigned long flags; + + spin_lock_irqsave(&rumble->lock, flags); + + gip_send_rumble(gamepad->client, &rumble->pkt, sizeof(rumble->pkt)); + rumble->last = jiffies; + + spin_unlock_irqrestore(&rumble->lock, flags); +} + +static int gip_gamepad_queue_rumble(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct gip_gamepad_rumble *rumble = input_get_drvdata(dev); + u32 mag_left = effect->u.rumble.strong_magnitude; + u32 mag_right = effect->u.rumble.weak_magnitude; + unsigned long flags; + + if (effect->type != FF_RUMBLE) + return 0; + + spin_lock_irqsave(&rumble->lock, flags); + + rumble->pkt.motors = GIP_GP_MOTOR_R | GIP_GP_MOTOR_L; + rumble->pkt.left = (mag_left * GIP_GP_RUMBLE_MAX + S16_MAX) / U16_MAX; + rumble->pkt.right = (mag_right * GIP_GP_RUMBLE_MAX + S16_MAX) / U16_MAX; + rumble->pkt.duration = 0xff; + rumble->pkt.repeat = 0xeb; + + /* delay rumble to work around firmware bug */ + if (!timer_pending(&rumble->timer)) + mod_timer(&rumble->timer, rumble->last + GIP_GP_RUMBLE_DELAY); + + spin_unlock_irqrestore(&rumble->lock, flags); + + return 0; +} + +static bool gip_gamepad_is_series_xs(struct gip_client *client) +{ + struct gip_hardware *hw = &client->hardware; + guid_t *guid; + int i; + + /* the elite controller also has a middle button */ + if (hw->vendor == GIP_GP_VID_MICROSOFT && + hw->product == GIP_GP_PID_ELITE2) + return false; + + for (i = 0; i < client->interfaces->count; i++) { + guid = (guid_t *)client->interfaces->data + i; + if (guid_equal(guid, &gip_gamepad_guid_middle_button)) + return true; + } + + return false; +} + +static int gip_gamepad_init_input(struct gip_gamepad *gamepad) +{ + struct input_dev *dev = gamepad->input.dev; + int err; + + gamepad->series_xs = gip_gamepad_is_series_xs(gamepad->client); + if (gamepad->series_xs) + input_set_capability(dev, EV_KEY, KEY_RECORD); + + input_set_capability(dev, EV_KEY, BTN_MODE); + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_SELECT); + input_set_capability(dev, EV_KEY, BTN_A); + input_set_capability(dev, EV_KEY, BTN_B); + input_set_capability(dev, EV_KEY, BTN_X); + input_set_capability(dev, EV_KEY, BTN_Y); + input_set_capability(dev, EV_KEY, BTN_TL); + input_set_capability(dev, EV_KEY, BTN_TR); + input_set_capability(dev, EV_KEY, BTN_THUMBL); + input_set_capability(dev, EV_KEY, BTN_THUMBR); + input_set_capability(dev, EV_FF, FF_RUMBLE); + input_set_abs_params(dev, ABS_X, -32768, 32767, 16, 128); + input_set_abs_params(dev, ABS_RX, -32768, 32767, 16, 128); + input_set_abs_params(dev, ABS_Y, -32768, 32767, 16, 128); + input_set_abs_params(dev, ABS_RY, -32768, 32767, 16, 128); + input_set_abs_params(dev, ABS_Z, 0, 1023, 0, 0); + input_set_abs_params(dev, ABS_RZ, 0, 1023, 0, 0); + input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); + input_set_drvdata(dev, &gamepad->rumble); + + err = input_ff_create_memless(dev, NULL, gip_gamepad_queue_rumble); + if (err) { + dev_err(&gamepad->client->dev, "%s: create FF failed: %d\n", + __func__, err); + return err; + } + + err = input_register_device(dev); + if (err) { + dev_err(&gamepad->client->dev, "%s: register failed: %d\n", + __func__, err); + return err; + } + + spin_lock_init(&gamepad->rumble.lock); + timer_setup(&gamepad->rumble.timer, gip_gamepad_send_rumble, 0); + + return 0; +} + +static int gip_gamepad_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + + gip_report_battery(&gamepad->battery, type, level); + + return 0; +} + +static int gip_gamepad_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + + input_report_key(gamepad->input.dev, BTN_MODE, down); + input_sync(gamepad->input.dev); + + return 0; +} + +static int gip_gamepad_op_input(struct gip_client *client, void *data, int len) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + struct gip_gamepad_pkt_input *pkt = data; + struct gip_gamepad_pkt_series_xs *pkt_xs = data + sizeof(*pkt); + struct input_dev *dev = gamepad->input.dev; + u16 buttons = le16_to_cpu(pkt->buttons); + + if (len < sizeof(*pkt)) + return -EINVAL; + + if (gamepad->series_xs) { + if (len < sizeof(*pkt) + sizeof(*pkt_xs)) + return -EINVAL; + + input_report_key(dev, KEY_RECORD, !!pkt_xs->share_button); + } + + input_report_key(dev, BTN_START, buttons & GIP_GP_BTN_MENU); + input_report_key(dev, BTN_SELECT, buttons & GIP_GP_BTN_VIEW); + input_report_key(dev, BTN_A, buttons & GIP_GP_BTN_A); + input_report_key(dev, BTN_B, buttons & GIP_GP_BTN_B); + input_report_key(dev, BTN_X, buttons & GIP_GP_BTN_X); + input_report_key(dev, BTN_Y, buttons & GIP_GP_BTN_Y); + input_report_key(dev, BTN_TL, buttons & GIP_GP_BTN_BUMPER_L); + input_report_key(dev, BTN_TR, buttons & GIP_GP_BTN_BUMPER_R); + input_report_key(dev, BTN_THUMBL, buttons & GIP_GP_BTN_STICK_L); + input_report_key(dev, BTN_THUMBR, buttons & GIP_GP_BTN_STICK_R); + input_report_abs(dev, ABS_X, (s16)le16_to_cpu(pkt->stick_left_x)); + input_report_abs(dev, ABS_RX, (s16)le16_to_cpu(pkt->stick_right_x)); + input_report_abs(dev, ABS_Y, ~(s16)le16_to_cpu(pkt->stick_left_y)); + input_report_abs(dev, ABS_RY, ~(s16)le16_to_cpu(pkt->stick_right_y)); + input_report_abs(dev, ABS_Z, le16_to_cpu(pkt->trigger_left)); + input_report_abs(dev, ABS_RZ, le16_to_cpu(pkt->trigger_right)); + input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_GP_BTN_DPAD_R) - + !!(buttons & GIP_GP_BTN_DPAD_L)); + input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_GP_BTN_DPAD_D) - + !!(buttons & GIP_GP_BTN_DPAD_U)); + input_sync(dev); + + return 0; +} + +static int gip_gamepad_probe(struct gip_client *client) +{ + struct gip_gamepad *gamepad; + int err; + + gamepad = devm_kzalloc(&client->dev, sizeof(*gamepad), GFP_KERNEL); + if (!gamepad) + return -ENOMEM; + + gamepad->client = client; + + err = gip_init_input(&gamepad->input, client, GIP_GP_NAME); + if (err) + return err; + + err = gip_gamepad_init_input(gamepad); + if (err) + return err; + + err = gip_init_battery(&gamepad->battery, client, GIP_GP_NAME); + if (err) + return err; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + err = gip_init_led(&gamepad->led, client); + if (err) + return err; + + err = gip_complete_authentication(client); + if (err) + return err; + + dev_set_drvdata(&client->dev, gamepad); + + return 0; +} + +static void gip_gamepad_remove(struct gip_client *client) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + + del_timer_sync(&gamepad->rumble.timer); + dev_set_drvdata(&client->dev, NULL); +} + +static struct gip_driver gip_gamepad_driver = { + .name = "xone-gip-gamepad", + .class = "Windows.Xbox.Input.Gamepad", + .ops = { + .battery = gip_gamepad_op_battery, + .guide_button = gip_gamepad_op_guide_button, + .input = gip_gamepad_op_input, + }, + .probe = gip_gamepad_probe, + .remove = gip_gamepad_remove, +}; +module_gip_driver(gip_gamepad_driver); + +MODULE_ALIAS("gip:Windows.Xbox.Input.Gamepad"); +MODULE_AUTHOR("Severin von Wnuck "); +MODULE_DESCRIPTION("xone GIP gamepad driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/headset.c b/drivers/hid/xone/driver/headset.c new file mode 100644 index 000000000..434aa54da --- /dev/null +++ b/drivers/hid/xone/driver/headset.c @@ -0,0 +1,498 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include +#include +#include +#include + +#include "../bus/bus.h" + +#define GIP_HS_NAME "Microsoft X-Box One headset" + +#define GIP_HS_CONFIG_DELAY msecs_to_jiffies(1000) +#define GIP_HS_POWER_ON_DELAY msecs_to_jiffies(1000) + +static const struct snd_pcm_hardware gip_headset_pcm_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .periods_min = 2, + .periods_max = 1024, +}; + +struct gip_headset { + struct gip_client *client; + + struct delayed_work config_work; + struct delayed_work power_on_work; + struct work_struct register_work; + bool registered; + + struct hrtimer timer; + void *buffer; + + struct gip_headset_stream { + struct snd_pcm_substream *substream; + snd_pcm_uframes_t pointer; + snd_pcm_uframes_t period; + } playback, capture; + + struct snd_card *card; + struct snd_pcm *pcm; +}; + +static int gip_headset_pcm_open(struct snd_pcm_substream *sub) +{ + struct gip_headset *headset = snd_pcm_substream_chip(sub); + struct gip_audio_config *cfg; + struct snd_pcm_hardware hw = gip_headset_pcm_hw; + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + cfg = &headset->client->audio_config_out; + else + cfg = &headset->client->audio_config_in; + + hw.rate_min = cfg->sample_rate; + hw.rate_max = cfg->sample_rate; + hw.channels_min = cfg->channels; + hw.channels_max = cfg->channels; + hw.buffer_bytes_max = cfg->buffer_size * 8; + hw.period_bytes_min = cfg->buffer_size; + hw.period_bytes_max = cfg->buffer_size * 8; + + sub->runtime->hw = hw; + + return 0; +} + +static int gip_headset_pcm_close(struct snd_pcm_substream *sub) +{ + return 0; +} + +static int gip_headset_pcm_hw_params(struct snd_pcm_substream *sub, + struct snd_pcm_hw_params *params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(sub, + params_buffer_bytes(params)); +} + +static int gip_headset_pcm_hw_free(struct snd_pcm_substream *sub) +{ + return snd_pcm_lib_free_vmalloc_buffer(sub); +} + +static int gip_headset_pcm_prepare(struct snd_pcm_substream *sub) +{ + return 0; +} + +static int gip_headset_pcm_trigger(struct snd_pcm_substream *sub, int cmd) +{ + struct gip_headset *headset = snd_pcm_substream_chip(sub); + struct gip_headset_stream *stream; + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + stream = &headset->playback; + else + stream = &headset->capture; + + stream->pointer = 0; + stream->period = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + stream->substream = sub; + break; + case SNDRV_PCM_TRIGGER_STOP: + stream->substream = NULL; + break; + default: + return -EINVAL; + } + + if (!stream->substream && sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + memset(headset->buffer, 0, + headset->client->audio_config_out.buffer_size); + + return 0; +} + +static snd_pcm_uframes_t gip_headset_pcm_pointer(struct snd_pcm_substream *sub) +{ + struct gip_headset *headset = snd_pcm_substream_chip(sub); + struct gip_headset_stream *stream; + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + stream = &headset->playback; + else + stream = &headset->capture; + + return bytes_to_frames(sub->runtime, stream->pointer); +} + +static const struct snd_pcm_ops gip_headset_pcm_ops = { + .open = gip_headset_pcm_open, + .close = gip_headset_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = gip_headset_pcm_hw_params, + .hw_free = gip_headset_pcm_hw_free, + .prepare = gip_headset_pcm_prepare, + .trigger = gip_headset_pcm_trigger, + .pointer = gip_headset_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static bool gip_headset_copy_playback(struct gip_headset_stream *stream, + unsigned char *data, int len) +{ + struct snd_pcm_runtime *runtime = stream->substream->runtime; + size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); + size_t remaining = buf_size - stream->pointer; + + if (len <= remaining) { + memcpy(data, runtime->dma_area + stream->pointer, len); + } else { + memcpy(data, runtime->dma_area + stream->pointer, remaining); + memcpy(data + remaining, runtime->dma_area, len - remaining); + } + + stream->pointer += len; + if (stream->pointer >= buf_size) + stream->pointer -= buf_size; + + stream->period += len; + if (stream->period >= runtime->period_size) { + stream->period -= runtime->period_size; + return true; + } + + return false; +} + +static bool gip_headset_copy_capture(struct gip_headset_stream *stream, + unsigned char *data, int len) +{ + struct snd_pcm_runtime *runtime = stream->substream->runtime; + size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); + size_t remaining = buf_size - stream->pointer; + + if (len <= remaining) { + memcpy(runtime->dma_area + stream->pointer, data, len); + } else { + memcpy(runtime->dma_area + stream->pointer, data, remaining); + memcpy(runtime->dma_area, data + remaining, len - remaining); + } + + stream->pointer += len; + if (stream->pointer >= buf_size) + stream->pointer -= buf_size; + + stream->period += len; + if (stream->period >= runtime->period_size) { + stream->period -= runtime->period_size; + return true; + } + + return false; +} + +static enum hrtimer_restart gip_headset_send_samples(struct hrtimer *timer) +{ + struct gip_headset *headset = container_of(timer, typeof(*headset), + timer); + struct gip_headset_stream *stream = &headset->playback; + struct gip_audio_config *cfg = &headset->client->audio_config_out; + struct snd_pcm_substream *sub = stream->substream; + bool elapsed = false; + int err; + unsigned long flags; + + if (sub) { + snd_pcm_stream_lock_irqsave(sub, flags); + + if (sub->runtime && snd_pcm_running(sub)) + elapsed = gip_headset_copy_playback(stream, + headset->buffer, + cfg->buffer_size); + + snd_pcm_stream_unlock_irqrestore(sub, flags); + + if (elapsed) + snd_pcm_period_elapsed(sub); + } + + /* retry if driver runs out of buffers */ + err = gip_send_audio_samples(headset->client, headset->buffer); + if (err && err != -ENOSPC) + return HRTIMER_NORESTART; + + hrtimer_forward_now(timer, ms_to_ktime(GIP_AUDIO_INTERVAL)); + + return HRTIMER_RESTART; +} + +static int gip_headset_init_card(struct gip_headset *headset) +{ + struct snd_card *card; + int err; + + err = snd_card_new(&headset->client->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &card); + if (err) + return err; + + strscpy(card->driver, "GIP Headset", sizeof(card->driver)); + strscpy(card->shortname, GIP_HS_NAME, sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), "%s at %s", + GIP_HS_NAME, dev_name(&headset->client->dev)); + + headset->card = card; + + return 0; +} + +static int gip_headset_init_pcm(struct gip_headset *headset) +{ + struct gip_client *client = headset->client; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(headset->card, "GIP Headset", 0, 1, 1, &pcm); + if (err) + return err; + + strscpy(pcm->name, "GIP Headset", sizeof(pcm->name)); + pcm->private_data = headset; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &gip_headset_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &gip_headset_pcm_ops); + + headset->buffer = devm_kzalloc(&client->dev, + client->audio_config_out.buffer_size, + GFP_KERNEL); + if (!headset->buffer) + return -ENOMEM; + + headset->pcm = pcm; + + return snd_card_register(headset->card); +} + +static int gip_headset_start_audio(struct gip_headset *headset) +{ + struct gip_client *client = headset->client; + int err; + + /* set headset volume to maximum */ + err = gip_fix_audio_volume(client); + if (err) + return err; + + err = gip_init_audio_out(client); + if (err) + return err; + + hrtimer_start(&headset->timer, 0, HRTIMER_MODE_REL); + + return 0; +} + +static void gip_headset_config(struct work_struct *work) +{ + struct gip_headset *headset = container_of(to_delayed_work(work), + typeof(*headset), + config_work); + struct gip_client *client = headset->client; + struct gip_info_element *fmts = client->audio_formats; + int err; + + dev_dbg(&client->dev, "%s: format=0x%02x/0x%02x\n", __func__, + fmts->data[0], fmts->data[1]); + + /* suggest initial audio format */ + err = gip_suggest_audio_format(client, fmts->data[0], fmts->data[1]); + if (err) + dev_err(&client->dev, "%s: suggest format failed: %d\n", + __func__, err); +} + +static void gip_headset_power_on(struct work_struct *work) +{ + struct gip_headset *headset = container_of(to_delayed_work(work), + typeof(*headset), + power_on_work); + struct gip_client *client = headset->client; + int err; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + dev_err(&client->dev, "%s: set power mode failed: %d\n", + __func__, err); +} + +static void gip_headset_register(struct work_struct *work) +{ + struct gip_headset *headset = container_of(work, typeof(*headset), + register_work); + struct device *dev = &headset->client->dev; + int err; + + err = gip_headset_init_card(headset); + if (err) { + dev_err(dev, "%s: init card failed: %d\n", __func__, err); + return; + } + + err = gip_headset_init_pcm(headset); + if (err) { + dev_err(dev, "%s: init PCM failed: %d\n", __func__, err); + goto err_free_card; + } + + err = gip_headset_start_audio(headset); + if (err) { + dev_err(dev, "%s: start audio failed: %d\n", __func__, err); + goto err_free_card; + } + + return; + +err_free_card: + snd_card_free(headset->card); + headset->card = NULL; +} + +static int gip_headset_op_audio_ready(struct gip_client *client) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + schedule_delayed_work(&headset->power_on_work, GIP_HS_POWER_ON_DELAY); + + return 0; +} + +static int gip_headset_op_audio_volume(struct gip_client *client, + int in, int out) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + /* headset reported initial volume, start audio I/O */ + if (!headset->registered) { + schedule_work(&headset->register_work); + headset->registered = true; + } + + /* ignore hardware volume, let software handle volume changes */ + return 0; +} + +static int gip_headset_op_audio_samples(struct gip_client *client, + void *data, int len) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + struct gip_headset_stream *stream = &headset->capture; + struct snd_pcm_substream *sub = stream->substream; + bool elapsed = false; + unsigned long flags; + + if (!sub) + return 0; + + snd_pcm_stream_lock_irqsave(sub, flags); + + if (sub->runtime && snd_pcm_running(sub)) + elapsed = gip_headset_copy_capture(stream, data, len); + + snd_pcm_stream_unlock_irqrestore(sub, flags); + + if (elapsed) + snd_pcm_period_elapsed(sub); + + return 0; +} + +static int gip_headset_probe(struct gip_client *client) +{ + struct gip_headset *headset; + struct gip_info_element *fmts = client->audio_formats; + int err; + + if (!fmts || !fmts->count) + return -ENODEV; + + headset = devm_kzalloc(&client->dev, sizeof(*headset), GFP_KERNEL); + if (!headset) + return -ENOMEM; + + headset->client = client; + + INIT_DELAYED_WORK(&headset->config_work, gip_headset_config); + INIT_DELAYED_WORK(&headset->power_on_work, gip_headset_power_on); + INIT_WORK(&headset->register_work, gip_headset_register); + + hrtimer_init(&headset->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + headset->timer.function = gip_headset_send_samples; + + err = gip_enable_audio(client); + if (err) + return err; + + err = gip_init_audio_in(client); + if (err) { + gip_disable_audio(client); + return err; + } + + dev_set_drvdata(&client->dev, headset); + + /* delay to prevent response from being dropped */ + schedule_delayed_work(&headset->config_work, GIP_HS_CONFIG_DELAY); + + return 0; +} + +static void gip_headset_remove(struct gip_client *client) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + cancel_delayed_work_sync(&headset->config_work); + cancel_delayed_work_sync(&headset->power_on_work); + cancel_work_sync(&headset->register_work); + hrtimer_cancel(&headset->timer); + gip_disable_audio(client); + + if (headset->card) { + snd_card_disconnect(headset->card); + snd_card_free_when_closed(headset->card); + } + + dev_set_drvdata(&client->dev, NULL); +} + +static struct gip_driver gip_headset_driver = { + .name = "xone-gip-headset", + .class = "Windows.Xbox.Input.Headset", + .ops = { + .audio_ready = gip_headset_op_audio_ready, + .audio_volume = gip_headset_op_audio_volume, + .audio_samples = gip_headset_op_audio_samples, + }, + .probe = gip_headset_probe, + .remove = gip_headset_remove, +}; +module_gip_driver(gip_headset_driver); + +MODULE_ALIAS("gip:Windows.Xbox.Input.Headset"); +MODULE_AUTHOR("Severin von Wnuck "); +MODULE_DESCRIPTION("xone GIP headset driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/transport/dongle.c b/drivers/hid/xone/transport/dongle.c new file mode 100644 index 000000000..62e269ed6 --- /dev/null +++ b/drivers/hid/xone/transport/dongle.c @@ -0,0 +1,986 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mt76.h" +#include "../bus/bus.h" + +#define XONE_DONGLE_NUM_IN_URBS 12 +#define XONE_DONGLE_NUM_OUT_URBS 12 + +#define XONE_DONGLE_LEN_CMD_PKT 0x0654 +#define XONE_DONGLE_LEN_WLAN_PKT 0x8400 + +#define XONE_DONGLE_MAX_CLIENTS 16 + +#define XONE_DONGLE_PWR_OFF_TIMEOUT msecs_to_jiffies(5000) + +enum xone_dongle_queue { + XONE_DONGLE_QUEUE_DATA = 0x00, + XONE_DONGLE_QUEUE_AUDIO = 0x02, +}; + +struct xone_dongle_skb_cb { + struct xone_dongle *dongle; + struct urb *urb; +}; + +struct xone_dongle_client { + struct xone_dongle *dongle; + u8 wcid; + u8 address[ETH_ALEN]; + + struct gip_adapter *adapter; +}; + +struct xone_dongle_event { + enum xone_dongle_event_type { + XONE_DONGLE_EVT_ADD_CLIENT, + XONE_DONGLE_EVT_REMOVE_CLIENT, + XONE_DONGLE_EVT_PAIR_CLIENT, + XONE_DONGLE_EVT_TOGGLE_PAIRING, + } type; + + struct xone_dongle *dongle; + u8 address[ETH_ALEN]; + u8 wcid; + + struct work_struct work; +}; + +struct xone_dongle { + struct xone_mt76 mt; + + struct usb_anchor urbs_in_idle; + struct usb_anchor urbs_in_busy; + struct usb_anchor urbs_out_idle; + struct usb_anchor urbs_out_busy; + + /* serializes pairing changes */ + struct mutex pairing_lock; + bool pairing; + + /* serializes access to clients array */ + spinlock_t clients_lock; + struct xone_dongle_client *clients[XONE_DONGLE_MAX_CLIENTS]; + atomic_t client_count; + wait_queue_head_t disconnect_wait; + + struct workqueue_struct *event_wq; +}; + +static void xone_dongle_prep_packet(struct xone_dongle_client *client, + struct sk_buff *skb, + enum xone_dongle_queue queue) +{ + struct ieee80211_qos_hdr hdr = {}; + struct mt76_txwi txwi = {}; + u8 data[] = { + 0x00, 0x00, queue, client->wcid - 1, 0x00, 0x00, 0x00, 0x00, + }; + + /* frame is sent from AP (DS) */ + /* duration is the time required to transmit (in μs) */ + hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_QOS_DATA | + IEEE80211_FCTL_FROMDS); + hdr.duration_id = cpu_to_le16(144); + memcpy(hdr.addr1, client->address, ETH_ALEN); + memcpy(hdr.addr2, client->dongle->mt.address, ETH_ALEN); + memcpy(hdr.addr3, client->dongle->mt.address, ETH_ALEN); + + /* wait for acknowledgment */ + txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, + IEEE80211_HT_MPDU_DENSITY_4)); + txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); + txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; + txwi.len_ctl = cpu_to_le16(sizeof(hdr) + skb->len); + + memset(skb_push(skb, 2), 0, 2); + memcpy(skb_push(skb, sizeof(hdr)), &hdr, sizeof(hdr)); + memcpy(skb_push(skb, sizeof(txwi)), &txwi, sizeof(txwi)); + memcpy(skb_push(skb, sizeof(data)), data, sizeof(data)); + + xone_mt76_prep_command(skb, 0); +} + +static int xone_dongle_get_buffer(struct gip_adapter *adap, + struct gip_adapter_buffer *buf) +{ + struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); + struct xone_dongle_skb_cb *cb; + struct urb *urb; + struct sk_buff *skb; + + urb = usb_get_from_anchor(&client->dongle->urbs_out_idle); + if (!urb) + return -ENOSPC; + + skb = xone_mt76_alloc_message(XONE_DONGLE_LEN_CMD_PKT, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + /* command header + WCID data + TXWI + QoS header + padding */ + /* see xone_dongle_prep_packet and xone_mt76_prep_message */ + skb_reserve(skb, MT_CMD_HDR_LEN + 8 + sizeof(struct mt76_txwi) + + sizeof(struct ieee80211_qos_hdr) + 2 + MT_CMD_HDR_LEN); + + cb = (struct xone_dongle_skb_cb *)skb->cb; + cb->dongle = client->dongle; + cb->urb = urb; + + buf->context = skb; + buf->data = skb->data; + buf->length = skb->len; + + return 0; +} + +static int xone_dongle_submit_buffer(struct gip_adapter *adap, + struct gip_adapter_buffer *buf) +{ + struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); + struct xone_dongle_skb_cb *cb; + struct sk_buff *skb = buf->context; + int err; + + skb_put(skb, buf->length); + + if (buf->type == GIP_BUF_DATA) + xone_dongle_prep_packet(client, skb, XONE_DONGLE_QUEUE_DATA); + else if (buf->type == GIP_BUF_AUDIO) + xone_dongle_prep_packet(client, skb, XONE_DONGLE_QUEUE_AUDIO); + else + return -EINVAL; + + cb = (struct xone_dongle_skb_cb *)skb->cb; + cb->urb->context = skb; + cb->urb->transfer_buffer = skb->data; + cb->urb->transfer_buffer_length = skb->len; + usb_anchor_urb(cb->urb, &client->dongle->urbs_out_busy); + + err = usb_submit_urb(cb->urb, GFP_ATOMIC); + if (err) { + usb_unanchor_urb(cb->urb); + usb_anchor_urb(cb->urb, &client->dongle->urbs_out_idle); + dev_kfree_skb_any(skb); + } + + usb_free_urb(cb->urb); + + return err; +} + +static struct gip_adapter_ops xone_dongle_adapter_ops = { + .get_buffer = xone_dongle_get_buffer, + .submit_buffer = xone_dongle_submit_buffer, +}; + +static int xone_dongle_toggle_pairing(struct xone_dongle *dongle, bool enable) +{ + int err = 0; + + mutex_lock(&dongle->pairing_lock); + + /* pairing is already enabled */ + if (dongle->pairing && enable) + goto err_unlock; + + err = xone_mt76_set_pairing(&dongle->mt, enable); + if (err) + goto err_unlock; + + err = xone_mt76_set_led_mode(&dongle->mt, enable ? XONE_MT_LED_BLINK : + XONE_MT_LED_OFF); + if (err) + goto err_unlock; + + dev_dbg(dongle->mt.dev, "%s: enabled=%d\n", __func__, enable); + dongle->pairing = enable; + +err_unlock: + mutex_unlock(&dongle->pairing_lock); + + return err; +} + +static ssize_t xone_dongle_pairing_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct xone_dongle *dongle = usb_get_intfdata(intf); + + return sprintf(buf, "%d\n", dongle->pairing); +} + +static ssize_t xone_dongle_pairing_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct xone_dongle *dongle = usb_get_intfdata(intf); + bool enable; + int err; + + err = kstrtobool(buf, &enable); + if (err) + return err; + + err = xone_dongle_toggle_pairing(dongle, enable); + if (err) + return err; + + return count; +} + +static struct device_attribute xone_dongle_attr_pairing = + __ATTR(pairing, 0644, + xone_dongle_pairing_show, + xone_dongle_pairing_store); + +static struct attribute *xone_dongle_attrs[] = { + &xone_dongle_attr_pairing.attr, + NULL, +}; +ATTRIBUTE_GROUPS(xone_dongle); + +static struct xone_dongle_client * +xone_dongle_create_client(struct xone_dongle *dongle, u8 *addr) +{ + struct xone_dongle_client *client; + int i, err; + + /* find free WCID */ + for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) + if (!dongle->clients[i]) + break; + + if (i == XONE_DONGLE_MAX_CLIENTS) + return ERR_PTR(-ENOSPC); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + client->dongle = dongle; + client->wcid = i + 1; + memcpy(client->address, addr, ETH_ALEN); + + client->adapter = gip_create_adapter(dongle->mt.dev, + &xone_dongle_adapter_ops, 1); + if (IS_ERR(client->adapter)) { + err = PTR_ERR(client->adapter); + kfree(client); + return ERR_PTR(err); + } + + dev_set_drvdata(&client->adapter->dev, client); + + return client; +} + +static int xone_dongle_add_client(struct xone_dongle *dongle, u8 *addr) +{ + struct xone_dongle_client *client; + int err; + unsigned long flags; + + client = xone_dongle_create_client(dongle, addr); + if (IS_ERR(client)) + return PTR_ERR(client); + + err = xone_mt76_associate_client(&dongle->mt, client->wcid, addr); + if (err) + goto err_free_client; + + err = xone_mt76_set_led_mode(&dongle->mt, XONE_MT_LED_ON); + if (err) + goto err_free_client; + + dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", + __func__, client->wcid, addr); + + spin_lock_irqsave(&dongle->clients_lock, flags); + dongle->clients[client->wcid - 1] = client; + spin_unlock_irqrestore(&dongle->clients_lock, flags); + + atomic_inc(&dongle->client_count); + + return 0; + +err_free_client: + gip_destroy_adapter(client->adapter); + kfree(client); + + return err; +} + +static int xone_dongle_remove_client(struct xone_dongle *dongle, u8 wcid) +{ + struct xone_dongle_client *client; + int err; + unsigned long flags; + + client = dongle->clients[wcid - 1]; + if (!client) + return 0; + + dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", + __func__, wcid, client->address); + + spin_lock_irqsave(&dongle->clients_lock, flags); + dongle->clients[wcid - 1] = NULL; + spin_unlock_irqrestore(&dongle->clients_lock, flags); + + gip_destroy_adapter(client->adapter); + kfree(client); + + err = xone_mt76_remove_client(&dongle->mt, wcid); + if (err) + dev_err(dongle->mt.dev, "%s: remove failed: %d\n", + __func__, err); + + /* turn off LED if all clients have disconnected */ + if (atomic_read(&dongle->client_count) == 1) + err = xone_mt76_set_led_mode(&dongle->mt, XONE_MT_LED_OFF); + + atomic_dec(&dongle->client_count); + wake_up(&dongle->disconnect_wait); + + return err; +} + +static int xone_dongle_pair_client(struct xone_dongle *dongle, u8 *addr) +{ + int err; + + dev_dbg(dongle->mt.dev, "%s: address=%pM\n", __func__, addr); + + err = xone_mt76_pair_client(&dongle->mt, addr); + if (err) + return err; + + return xone_dongle_toggle_pairing(dongle, false); +} + +static void xone_dongle_handle_event(struct work_struct *work) +{ + struct xone_dongle_event *evt = container_of(work, typeof(*evt), work); + int err; + + switch (evt->type) { + case XONE_DONGLE_EVT_ADD_CLIENT: + err = xone_dongle_add_client(evt->dongle, evt->address); + break; + case XONE_DONGLE_EVT_REMOVE_CLIENT: + err = xone_dongle_remove_client(evt->dongle, evt->wcid); + break; + case XONE_DONGLE_EVT_PAIR_CLIENT: + err = xone_dongle_pair_client(evt->dongle, evt->address); + break; + case XONE_DONGLE_EVT_TOGGLE_PAIRING: + err = xone_dongle_toggle_pairing(evt->dongle, true); + break; + } + + if (err) + dev_err(evt->dongle->mt.dev, "%s: handle event failed: %d\n", + __func__, err); + + kfree(evt); +} + +static struct xone_dongle_event * +xone_dongle_alloc_event(struct xone_dongle *dongle, + enum xone_dongle_event_type type) +{ + struct xone_dongle_event *evt; + + evt = kzalloc(sizeof(*evt), GFP_ATOMIC); + if (!evt) + return NULL; + + evt->type = type; + evt->dongle = dongle; + INIT_WORK(&evt->work, xone_dongle_handle_event); + + return evt; +} + +static int xone_dongle_handle_qos_data(struct xone_dongle *dongle, + struct sk_buff *skb, u8 wcid) +{ + struct xone_dongle_client *client; + int err = 0; + unsigned long flags; + + if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) + return 0; + + spin_lock_irqsave(&dongle->clients_lock, flags); + + client = dongle->clients[wcid - 1]; + if (client) + err = gip_process_buffer(client->adapter, skb->data, skb->len); + + spin_unlock_irqrestore(&dongle->clients_lock, flags); + + return err; +} + +static int xone_dongle_handle_association(struct xone_dongle *dongle, u8 *addr) +{ + struct xone_dongle_event *evt; + + evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_ADD_CLIENT); + if (!evt) + return -ENOMEM; + + memcpy(evt->address, addr, ETH_ALEN); + + queue_work(dongle->event_wq, &evt->work); + + return 0; +} + +static int xone_dongle_handle_disassociation(struct xone_dongle *dongle, + u8 wcid) +{ + struct xone_dongle_event *evt; + + if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) + return 0; + + evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_REMOVE_CLIENT); + if (!evt) + return -ENOMEM; + + evt->wcid = wcid; + + queue_work(dongle->event_wq, &evt->work); + + return 0; +} + +static int xone_dongle_handle_reserved(struct xone_dongle *dongle, + struct sk_buff *skb, u8 *addr) +{ + struct xone_dongle_event *evt; + + if (skb->len < 2) + return -EINVAL; + + if (skb->data[1] != 0x01) + return 0; + + evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_PAIR_CLIENT); + if (!evt) + return -ENOMEM; + + memcpy(evt->address, addr, ETH_ALEN); + + queue_work(dongle->event_wq, &evt->work); + + return 0; +} + +static int xone_dongle_handle_button(struct xone_dongle *dongle) +{ + struct xone_dongle_event *evt; + + evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_TOGGLE_PAIRING); + if (!evt) + return -ENOMEM; + + queue_work(dongle->event_wq, &evt->work); + + return 0; +} + +static int xone_dongle_handle_loss(struct xone_dongle *dongle, + struct sk_buff *skb) +{ + u8 wcid; + + if (skb->len < sizeof(wcid)) + return -EINVAL; + + wcid = skb->data[0]; + if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) + return 0; + + dev_dbg(dongle->mt.dev, "%s: wcid=%d\n", __func__, wcid); + + return xone_dongle_handle_disassociation(dongle, wcid); +} + +static int xone_dongle_process_frame(struct xone_dongle *dongle, + struct sk_buff *skb, + unsigned int hdr_len, u8 wcid) +{ + struct ieee80211_hdr_3addr *hdr = + (struct ieee80211_hdr_3addr *)skb->data; + u16 type; + + /* ignore invalid frames */ + if (skb->len < hdr_len || hdr_len < sizeof(*hdr)) + return 0; + + skb_pull(skb, hdr_len); + type = le16_to_cpu(hdr->frame_control); + + switch (type & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) { + case IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA: + return xone_dongle_handle_qos_data(dongle, skb, wcid); + case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ASSOC_REQ: + return xone_dongle_handle_association(dongle, hdr->addr2); + case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_DISASSOC: + return xone_dongle_handle_disassociation(dongle, wcid); + case IEEE80211_FTYPE_MGMT | XONE_MT_WLAN_RESERVED: + return xone_dongle_handle_reserved(dongle, skb, hdr->addr2); + } + + return 0; +} + +static int xone_dongle_process_wlan(struct xone_dongle *dongle, + struct sk_buff *skb) +{ + struct mt76_rxwi *rxwi = (struct mt76_rxwi *)skb->data; + unsigned int hdr_len; + u32 ctl; + + if (skb->len < sizeof(*rxwi)) + return -EINVAL; + + skb_pull(skb, sizeof(*rxwi)); + hdr_len = ieee80211_get_hdrlen_from_skb(skb); + + /* 2 bytes of padding after 802.11 header */ + if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_L2PAD)) { + if (skb->len < hdr_len + 2) + return -EINVAL; + + memmove(skb->data + 2, skb->data, hdr_len); + skb_pull(skb, 2); + } + + ctl = le32_to_cpu(rxwi->ctl); + skb_trim(skb, FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl)); + + return xone_dongle_process_frame(dongle, skb, hdr_len, + FIELD_GET(MT_RXWI_CTL_WCID, ctl)); +} + +static int xone_dongle_process_message(struct xone_dongle *dongle, + struct sk_buff *skb) +{ + enum mt76_dma_msg_port port; + u32 info; + + /* command header + trailer */ + if (skb->len < MT_CMD_HDR_LEN * 2) + return -EINVAL; + + info = get_unaligned_le32(skb->data); + port = FIELD_GET(MT_RX_FCE_INFO_D_PORT, info); + + /* ignore command reponses */ + if (FIELD_GET(MT_RX_FCE_INFO_CMD_SEQ, info) == 0x01) + return 0; + + /* remove header + trailer */ + skb_pull(skb, MT_CMD_HDR_LEN); + skb_trim(skb, skb->len - MT_CMD_HDR_LEN); + + if (port == MT_WLAN_PORT) + return xone_dongle_process_wlan(dongle, skb); + + if (port != MT_CPU_RX_PORT) + return 0; + + switch (FIELD_GET(MT_RX_FCE_INFO_EVT_TYPE, info)) { + case XONE_MT_EVT_BUTTON: + return xone_dongle_handle_button(dongle); + case XONE_MT_EVT_PACKET_RX: + return xone_dongle_process_wlan(dongle, skb); + case XONE_MT_EVT_CLIENT_LOST: + return xone_dongle_handle_loss(dongle, skb); + } + + return 0; +} + +static int xone_dongle_process_buffer(struct xone_dongle *dongle, + void *data, int len) +{ + struct sk_buff *skb; + int err; + + if (!len) + return 0; + + skb = dev_alloc_skb(len); + if (!skb) + return -ENOMEM; + + skb_put_data(skb, data, len); + + err = xone_dongle_process_message(dongle, skb); + if (err) { + dev_err(dongle->mt.dev, "%s: process failed: %d\n", + __func__, err); + print_hex_dump_debug("xone-dongle packet: ", DUMP_PREFIX_NONE, + 16, 1, data, len, false); + } + + dev_kfree_skb(skb); + + return err; +} + +static void xone_dongle_complete_in(struct urb *urb) +{ + struct xone_dongle *dongle = urb->context; + int err; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + usb_anchor_urb(urb, &dongle->urbs_in_idle); + return; + default: + goto resubmit; + } + + err = xone_dongle_process_buffer(dongle, urb->transfer_buffer, + urb->actual_length); + if (err) + dev_err(dongle->mt.dev, "%s: process failed: %d\n", + __func__, err); + +resubmit: + /* can fail during USB device removal */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + dev_dbg(dongle->mt.dev, "%s: submit failed: %d\n", + __func__, err); + usb_anchor_urb(urb, &dongle->urbs_in_idle); + } else { + usb_anchor_urb(urb, &dongle->urbs_in_busy); + } +} + +static void xone_dongle_complete_out(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct xone_dongle_skb_cb *cb = (struct xone_dongle_skb_cb *)skb->cb; + + usb_anchor_urb(urb, &cb->dongle->urbs_out_idle); + dev_consume_skb_any(skb); +} + +static int xone_dongle_init_urbs_in(struct xone_dongle *dongle, + int ep, int buf_len) +{ + struct xone_mt76 *mt = &dongle->mt; + struct urb *urb; + void *buf; + int i, err; + + for (i = 0; i < XONE_DONGLE_NUM_IN_URBS; i++) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + usb_anchor_urb(urb, &dongle->urbs_in_busy); + usb_free_urb(urb); + + buf = usb_alloc_coherent(mt->udev, buf_len, + GFP_KERNEL, &urb->transfer_dma); + if (!buf) + return -ENOMEM; + + usb_fill_bulk_urb(urb, mt->udev, + usb_rcvbulkpipe(mt->udev, ep), buf, buf_len, + xone_dongle_complete_in, dongle); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err) + return err; + } + + return 0; +} + +static int xone_dongle_init_urbs_out(struct xone_dongle *dongle) +{ + struct xone_mt76 *mt = &dongle->mt; + struct urb *urb; + int i; + + for (i = 0; i < XONE_DONGLE_NUM_OUT_URBS; i++) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + usb_fill_bulk_urb(urb, mt->udev, + usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), + NULL, 0, xone_dongle_complete_out, NULL); + usb_anchor_urb(urb, &dongle->urbs_out_idle); + usb_free_urb(urb); + } + + return 0; +} + +static int xone_dongle_init(struct xone_dongle *dongle) +{ + struct xone_mt76 *mt = &dongle->mt; + int err; + + init_usb_anchor(&dongle->urbs_out_idle); + init_usb_anchor(&dongle->urbs_out_busy); + init_usb_anchor(&dongle->urbs_in_idle); + init_usb_anchor(&dongle->urbs_in_busy); + + err = xone_dongle_init_urbs_out(dongle); + if (err) + return err; + + err = xone_dongle_init_urbs_in(dongle, XONE_MT_EP_IN_CMD, + XONE_DONGLE_LEN_CMD_PKT); + if (err) + return err; + + err = xone_dongle_init_urbs_in(dongle, XONE_MT_EP_IN_WLAN, + XONE_DONGLE_LEN_WLAN_PKT); + if (err) + return err; + + err = xone_mt76_load_firmware(mt, "xow_dongle.bin"); + if (err) { + dev_err(mt->dev, "%s: load firmware failed: %d\n", + __func__, err); + return err; + } + + err = xone_mt76_init_radio(mt); + if (err) + dev_err(mt->dev, "%s: init radio failed: %d\n", __func__, err); + + return err; +} + +static int xone_dongle_power_off_clients(struct xone_dongle *dongle) +{ + struct xone_dongle_client *client; + int i; + int err = 0; + unsigned long flags; + + spin_lock_irqsave(&dongle->clients_lock, flags); + + for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) { + client = dongle->clients[i]; + if (!client) + continue; + + err = gip_power_off_adapter(client->adapter); + if (err) + break; + } + + spin_unlock_irqrestore(&dongle->clients_lock, flags); + + if (err) + return err; + + /* can time out if new client connects */ + if (!wait_event_timeout(dongle->disconnect_wait, + !atomic_read(&dongle->client_count), + XONE_DONGLE_PWR_OFF_TIMEOUT)) + return -ETIMEDOUT; + + return 0; +} + +static void xone_dongle_destroy(struct xone_dongle *dongle) +{ + struct xone_dongle_client *client; + struct urb *urb; + int i; + + usb_kill_anchored_urbs(&dongle->urbs_in_busy); + destroy_workqueue(dongle->event_wq); + + for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) { + client = dongle->clients[i]; + if (!client) + continue; + + gip_destroy_adapter(client->adapter); + kfree(client); + dongle->clients[i] = NULL; + } + + usb_kill_anchored_urbs(&dongle->urbs_out_busy); + + while ((urb = usb_get_from_anchor(&dongle->urbs_out_idle))) + usb_free_urb(urb); + + while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + } + + mutex_destroy(&dongle->pairing_lock); +} + +static int xone_dongle_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct xone_dongle *dongle; + int err; + + dongle = devm_kzalloc(&intf->dev, sizeof(*dongle), GFP_KERNEL); + if (!dongle) + return -ENOMEM; + + dongle->mt.dev = &intf->dev; + dongle->mt.udev = interface_to_usbdev(intf); + + usb_reset_device(dongle->mt.udev); + + /* enable USB remote wakeup feature */ + device_wakeup_enable(&dongle->mt.udev->dev); + + dongle->event_wq = alloc_ordered_workqueue("xone_dongle", 0); + if (!dongle->event_wq) + return -ENOMEM; + + mutex_init(&dongle->pairing_lock); + spin_lock_init(&dongle->clients_lock); + init_waitqueue_head(&dongle->disconnect_wait); + + err = xone_dongle_init(dongle); + if (err) { + xone_dongle_destroy(dongle); + return err; + } + + usb_set_intfdata(intf, dongle); + + err = device_add_groups(&intf->dev, xone_dongle_groups); + if (err) + xone_dongle_destroy(dongle); + + return err; +} + +static void xone_dongle_disconnect(struct usb_interface *intf) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + int err; + + device_remove_groups(&intf->dev, xone_dongle_groups); + + /* can fail during USB device removal */ + err = xone_dongle_power_off_clients(dongle); + if (err) + dev_dbg(dongle->mt.dev, "%s: power off failed: %d\n", + __func__, err); + + xone_dongle_destroy(dongle); + usb_set_intfdata(intf, NULL); +} + +static int xone_dongle_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + int err; + + err = xone_dongle_power_off_clients(dongle); + if (err) + dev_err(dongle->mt.dev, "%s: power off failed: %d\n", + __func__, err); + + usb_kill_anchored_urbs(&dongle->urbs_in_busy); + usb_kill_anchored_urbs(&dongle->urbs_out_busy); + flush_workqueue(dongle->event_wq); + + return xone_mt76_suspend_radio(&dongle->mt); +} + +static int xone_dongle_resume(struct usb_interface *intf) +{ + struct xone_dongle *dongle = usb_get_intfdata(intf); + struct urb *urb; + int err; + + while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { + usb_anchor_urb(urb, &dongle->urbs_in_busy); + usb_free_urb(urb); + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err) + return err; + } + + return xone_mt76_resume_radio(&dongle->mt); +} + +static void xone_dongle_shutdown(struct device *dev) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct xone_dongle *dongle = usb_get_intfdata(intf); + int err; + + err = xone_dongle_power_off_clients(dongle); + if (err) + dev_err(dongle->mt.dev, "%s: power off failed: %d\n", + __func__, err); +} + +static const struct usb_device_id xone_dongle_id_table[] = { + { USB_DEVICE(0x045e, 0x02e6) }, /* old dongle */ + { USB_DEVICE(0x045e, 0x02fe) }, /* new dongle */ + { USB_DEVICE(0x045e, 0x02f9) }, /* built-in dongle (ASUS, Lenovo) */ + { USB_DEVICE(0x045e, 0x091e) }, /* built-in dongle (Surface Book 2) */ + { }, +}; + +static struct usb_driver xone_dongle_driver = { + .name = "xone-dongle", + .probe = xone_dongle_probe, + .disconnect = xone_dongle_disconnect, + .suspend = xone_dongle_suspend, + .resume = xone_dongle_resume, + .id_table = xone_dongle_id_table, + .drvwrap.driver.shutdown = xone_dongle_shutdown, + .soft_unbind = 1, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(xone_dongle_driver); + +MODULE_DEVICE_TABLE(usb, xone_dongle_id_table); +MODULE_AUTHOR("Severin von Wnuck "); +MODULE_DESCRIPTION("xone dongle driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/transport/mt76.c b/drivers/hid/xone/transport/mt76.c new file mode 100644 index 000000000..bc3d4e97b --- /dev/null +++ b/drivers/hid/xone/transport/mt76.c @@ -0,0 +1,1142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include +#include +#include +#include +#include + +#include "mt76.h" + +/* bulk transfer timeout in ms */ +#define XONE_MT_USB_TIMEOUT 1000 + +#define XONE_MT_POLL_RETRIES 50 + +#define XONE_MT_RF_PATCH 0x0130 +#define XONE_MT_FW_LOAD_IVB 0x12 +#define XONE_MT_FW_ILM_OFFSET 0x080000 +#define XONE_MT_FW_DLM_OFFSET 0x110800 +#define XONE_MT_FW_CHUNK_SIZE 0x3800 + +/* wireless channel bands */ +#define XONE_MT_CH_2G_LOW 0x01 +#define XONE_MT_CH_2G_MID 0x02 +#define XONE_MT_CH_2G_HIGH 0x03 +#define XONE_MT_CH_5G_LOW 0x01 +#define XONE_MT_CH_5G_HIGH 0x02 + +/* commands specific to the dongle's firmware */ +enum xone_mt76_ms_command { + XONE_MT_SET_MAC_ADDRESS = 0x00, + XONE_MT_ADD_CLIENT = 0x01, + XONE_MT_REMOVE_CLIENT = 0x02, + XONE_MT_SET_IDLE_TIME = 0x05, + XONE_MT_SET_CHAN_CANDIDATES = 0x07, +}; + +enum xone_mt76_wow_feature { + XONE_MT_WOW_ENABLE = 0x01, + XONE_MT_WOW_TRAFFIC = 0x03, +}; + +enum xone_mt76_wow_traffic { + XONE_MT_WOW_TO_FIRMWARE = 0x00, + XONE_MT_WOW_TO_HOST = 0x01, +}; + +struct xone_mt76_msg_load_cr { + u8 mode; + u8 temperature; + u8 channel; + u8 padding; +} __packed; + +struct xone_mt76_msg_switch_channel { + u8 channel; + u8 padding1[3]; + __le16 tx_rx_setting; + u8 padding2[10]; + u8 bandwidth; + u8 tx_power; + u8 scan; + u8 unknown; +} __packed; + +static u32 xone_mt76_read_register(struct xone_mt76 *mt, u32 addr) +{ + u8 req = MT_VEND_MULTI_READ; + int ret; + + if (addr & MT_VEND_TYPE_CFG) { + req = MT_VEND_READ_CFG; + addr &= ~MT_VEND_TYPE_CFG; + } + + ret = usb_control_msg(mt->udev, usb_rcvctrlpipe(mt->udev, 0), req, + USB_DIR_IN | USB_TYPE_VENDOR, addr >> 16, addr, + &mt->control_data, sizeof(mt->control_data), + XONE_MT_USB_TIMEOUT); + if (ret != sizeof(mt->control_data)) + ret = -EREMOTEIO; + + if (ret < 0) { + dev_err(mt->dev, "%s: control message failed: %d\n", + __func__, ret); + return 0; + } + + return le32_to_cpu(mt->control_data); +} + +static void xone_mt76_write_register(struct xone_mt76 *mt, u32 addr, u32 val) +{ + u8 req = MT_VEND_MULTI_WRITE; + int ret; + + if (addr & MT_VEND_TYPE_CFG) { + req = MT_VEND_WRITE_CFG; + addr &= ~MT_VEND_TYPE_CFG; + } + + mt->control_data = cpu_to_le32(val); + + ret = usb_control_msg(mt->udev, usb_sndctrlpipe(mt->udev, 0), req, + USB_DIR_OUT | USB_TYPE_VENDOR, addr >> 16, addr, + &mt->control_data, sizeof(mt->control_data), + XONE_MT_USB_TIMEOUT); + if (ret != sizeof(mt->control_data)) + ret = -EREMOTEIO; + + if (ret < 0) + dev_err(mt->dev, "%s: control message failed: %d\n", + __func__, ret); +} + +static int xone_mt76_load_ivb(struct xone_mt76 *mt) +{ + /* load interrupt vector block */ + return usb_control_msg(mt->udev, usb_sndctrlpipe(mt->udev, 0), + MT_VEND_DEV_MODE, USB_DIR_OUT | USB_TYPE_VENDOR, + XONE_MT_FW_LOAD_IVB, 0, NULL, 0, + XONE_MT_USB_TIMEOUT); +} + +static bool xone_mt76_poll(struct xone_mt76 *mt, u32 offset, u32 mask, u32 val) +{ + int i; + u32 reg; + + for (i = 0; i < XONE_MT_POLL_RETRIES; i++) { + reg = xone_mt76_read_register(mt, offset); + if ((reg & mask) == val) + return true; + + usleep_range(10000, 20000); + } + + return false; +} + +static int xone_mt76_read_efuse(struct xone_mt76 *mt, u16 addr, + void *data, int len) +{ + u32 ctrl, offset, val; + int i, remaining; + + ctrl = xone_mt76_read_register(mt, MT_EFUSE_CTRL); + ctrl &= ~(MT_EFUSE_CTRL_AIN | MT_EFUSE_CTRL_MODE); + ctrl |= MT_EFUSE_CTRL_KICK; + ctrl |= FIELD_PREP(MT_EFUSE_CTRL_AIN, addr & ~0x0f); + ctrl |= FIELD_PREP(MT_EFUSE_CTRL_MODE, MT_EE_READ); + xone_mt76_write_register(mt, MT_EFUSE_CTRL, ctrl); + + if (!xone_mt76_poll(mt, MT_EFUSE_CTRL, MT_EFUSE_CTRL_KICK, 0)) + return -ETIMEDOUT; + + for (i = 0; i < len; i += sizeof(u32)) { + /* block data offset (multiple of 32 bits) */ + offset = (addr & GENMASK(3, 2)) + i; + val = xone_mt76_read_register(mt, MT_EFUSE_DATA_BASE + offset); + remaining = min_t(int, len - i, sizeof(u32)); + + memcpy(data + i, &val, remaining); + } + + return 0; +} + +struct sk_buff *xone_mt76_alloc_message(int len, gfp_t gfp) +{ + struct sk_buff *skb; + + /* up to 4 bytes of padding */ + skb = alloc_skb(MT_CMD_HDR_LEN + len + sizeof(u32) + MT_CMD_HDR_LEN, + gfp); + if (!skb) + return NULL; + + skb_reserve(skb, MT_CMD_HDR_LEN); + + return skb; +} + +void xone_mt76_prep_message(struct sk_buff *skb, u32 info) +{ + int len, pad; + + /* padding and trailer */ + len = round_up(skb->len, sizeof(u32)); + pad = len - skb->len + MT_CMD_HDR_LEN; + + put_unaligned_le32(info | FIELD_PREP(MT_MCU_MSG_LEN, len), + skb_push(skb, MT_CMD_HDR_LEN)); + memset(skb_put(skb, pad), 0, pad); +} + +void xone_mt76_prep_command(struct sk_buff *skb, enum mt76_mcu_cmd cmd) +{ + xone_mt76_prep_message(skb, MT_MCU_MSG_TYPE_CMD | + FIELD_PREP(MT_MCU_MSG_PORT, MT_CPU_TX_PORT) | + FIELD_PREP(MT_MCU_MSG_CMD_TYPE, cmd)); +} + +static int xone_mt76_send_command(struct xone_mt76 *mt, struct sk_buff *skb, + enum mt76_mcu_cmd cmd) +{ + int err; + + xone_mt76_prep_command(skb, cmd); + + err = usb_bulk_msg(mt->udev, usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), + skb->data, skb->len, NULL, XONE_MT_USB_TIMEOUT); + consume_skb(skb); + + return err; +} + +static int xone_mt76_send_wlan(struct xone_mt76 *mt, struct sk_buff *skb) +{ + struct mt76_txwi txwi = {}; + int err; + + /* wait for acknowledgment */ + /* ignore wireless client identifier (WCID) */ + txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, + IEEE80211_HT_MPDU_DENSITY_4)); + txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); + txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; + txwi.wcid = 0xff; + txwi.len_ctl = cpu_to_le16(skb->len); + + memcpy(skb_push(skb, sizeof(txwi)), &txwi, sizeof(txwi)); + + /* enhanced distributed channel access (EDCA) */ + /* wireless information valid (WIV) */ + xone_mt76_prep_message(skb, + FIELD_PREP(MT_TXD_INFO_DPORT, MT_WLAN_PORT) | + FIELD_PREP(MT_TXD_INFO_QSEL, MT_QSEL_EDCA) | + MT_TXD_INFO_WIV | + MT_TXD_INFO_80211); + + err = usb_bulk_msg(mt->udev, usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), + skb->data, skb->len, NULL, XONE_MT_USB_TIMEOUT); + consume_skb(skb); + + return err; +} + +static int xone_mt76_select_function(struct xone_mt76 *mt, + enum mt76_mcu_function func, u32 val) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) * 2, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(func, skb_put(skb, sizeof(u32))); + put_unaligned_le32(val, skb_put(skb, sizeof(u32))); + + return xone_mt76_send_command(mt, skb, MT_CMD_FUN_SET_OP); +} + +static int xone_mt76_load_cr(struct xone_mt76 *mt, enum mt76_mcu_cr_mode mode) +{ + struct sk_buff *skb; + struct xone_mt76_msg_load_cr msg = {}; + + skb = xone_mt76_alloc_message(sizeof(msg), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + msg.mode = mode; + skb_put_data(skb, &msg, sizeof(msg)); + + return xone_mt76_send_command(mt, skb, MT_CMD_LOAD_CR); +} + +static int xone_mt76_send_ms_command(struct xone_mt76 *mt, + enum xone_mt76_ms_command cmd, + void *data, int len) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) + len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(cmd, skb_put(skb, sizeof(u32))); + skb_put_data(skb, data, len); + + /* send command to Microsoft's proprietary firmware */ + return xone_mt76_send_command(mt, skb, MT_CMD_INIT_GAIN_OP); +} + +static int xone_mt76_write_burst(struct xone_mt76 *mt, u32 idx, + void *data, int len) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(idx) + len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* register offset in memory */ + put_unaligned_le32(idx + MT_MCU_MEMMAP_WLAN, skb_put(skb, sizeof(idx))); + skb_put_data(skb, data, len); + + return xone_mt76_send_command(mt, skb, MT_CMD_BURST_WRITE); +} + +int xone_mt76_set_led_mode(struct xone_mt76 *mt, enum xone_mt76_led_mode mode) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(mode, skb_put(skb, sizeof(u32))); + + return xone_mt76_send_command(mt, skb, MT_CMD_LED_MODE_OP); +} + +static int xone_mt76_set_power_mode(struct xone_mt76 *mt, + enum mt76_mcu_power_mode mode) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(mode, skb_put(skb, sizeof(u32))); + + return xone_mt76_send_command(mt, skb, MT_CMD_POWER_SAVING_OP); +} + +static int xone_mt76_set_wow_enable(struct xone_mt76 *mt, bool enable) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) + sizeof(u8) * 2, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(XONE_MT_WOW_ENABLE, skb_put(skb, sizeof(u32))); + skb_put_u8(skb, enable); + skb_put_u8(skb, mt->channel->index); + + return xone_mt76_send_command(mt, skb, MT_CMD_WOW_FEATURE); +} + +static int xone_mt76_set_wow_traffic(struct xone_mt76 *mt, + enum xone_mt76_wow_traffic traffic) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) + sizeof(u8), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(XONE_MT_WOW_TRAFFIC, skb_put(skb, sizeof(u32))); + skb_put_u8(skb, traffic); + + return xone_mt76_send_command(mt, skb, MT_CMD_WOW_FEATURE); +} + +static int xone_mt76_switch_channel(struct xone_mt76 *mt, + struct xone_mt76_channel *chan) +{ + struct sk_buff *skb; + struct xone_mt76_msg_switch_channel msg = {}; + + skb = xone_mt76_alloc_message(sizeof(msg), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* select TX and RX stream 1 */ + /* enable or disable scanning (unknown purpose) */ + msg.channel = chan->index; + msg.tx_rx_setting = cpu_to_le16(0x0101); + msg.bandwidth = chan->bandwidth; + msg.tx_power = chan->power; + msg.scan = chan->scan; + skb_put_data(skb, &msg, sizeof(msg)); + + return xone_mt76_send_command(mt, skb, MT_CMD_SWITCH_CHANNEL_OP); +} + +static int xone_mt76_calibrate(struct xone_mt76 *mt, + enum mt76_mcu_calibration calib, u32 val) +{ + struct sk_buff *skb; + + skb = xone_mt76_alloc_message(sizeof(u32) * 2, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(calib, skb_put(skb, sizeof(u32))); + put_unaligned_le32(val, skb_put(skb, sizeof(u32))); + + return xone_mt76_send_command(mt, skb, MT_CMD_CALIBRATION_OP); +} + +static int xone_mt76_send_firmware_part(struct xone_mt76 *mt, u32 offset, + const u8 *data, u32 len) +{ + struct sk_buff *skb; + u32 pos, chunk_len, complete; + int err; + + for (pos = 0; pos < len; pos += XONE_MT_FW_CHUNK_SIZE) { + chunk_len = min_t(u32, len - pos, XONE_MT_FW_CHUNK_SIZE); + + skb = xone_mt76_alloc_message(chunk_len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + skb_put_data(skb, data + pos, chunk_len); + chunk_len = roundup(chunk_len, sizeof(u32)); + + xone_mt76_write_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, + offset + pos); + xone_mt76_write_register(mt, MT_FCE_DMA_LEN | MT_VEND_TYPE_CFG, + chunk_len << 16); + + err = xone_mt76_send_command(mt, skb, 0); + if (err) + return err; + + complete = 0xc0000000 | (chunk_len << 16); + if (!xone_mt76_poll(mt, MT_FCE_DMA_LEN | MT_VEND_TYPE_CFG, + 0xffffffff, complete)) + return -ETIMEDOUT; + } + + return 0; +} + +static int xone_mt76_send_firmware(struct xone_mt76 *mt, + const struct firmware *fw) +{ + const struct mt76_fw_header *hdr; + u32 ilm_len, dlm_len; + int err; + + if (fw->size < sizeof(*hdr)) + return -EINVAL; + + hdr = (const struct mt76_fw_header *)fw->data; + ilm_len = le32_to_cpu(hdr->ilm_len); + dlm_len = le32_to_cpu(hdr->dlm_len); + + if (fw->size != sizeof(*hdr) + ilm_len + dlm_len) + return -EINVAL; + + dev_dbg(mt->dev, "%s: build=%.16s\n", __func__, hdr->build_time); + + /* configure DMA, enable FCE and packet DMA */ + xone_mt76_write_register(mt, MT_USB_U3DMA_CFG | MT_VEND_TYPE_CFG, + MT_USB_DMA_CFG_TX_BULK_EN | + MT_USB_DMA_CFG_RX_BULK_EN); + xone_mt76_write_register(mt, MT_FCE_PSE_CTRL, 0x01); + xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_BASE_PTR, 0x00400230); + xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_MAX_COUNT, 0x01); + xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_CPU_DESC_IDX, 0x01); + xone_mt76_write_register(mt, MT_FCE_PDMA_GLOBAL_CONF, 0x44); + xone_mt76_write_register(mt, MT_FCE_SKIP_FS, 0x03); + + /* send instruction local memory */ + err = xone_mt76_send_firmware_part(mt, XONE_MT_FW_ILM_OFFSET, + fw->data + sizeof(*hdr), ilm_len); + if (err) + return err; + + /* send data local memory */ + return xone_mt76_send_firmware_part(mt, XONE_MT_FW_DLM_OFFSET, + fw->data + sizeof(*hdr) + ilm_len, + dlm_len); +} + +static int xone_mt76_reset_firmware(struct xone_mt76 *mt) +{ + u32 val; + int err; + + /* apply power-on RF patch */ + val = xone_mt76_read_register(mt, XONE_MT_RF_PATCH | MT_VEND_TYPE_CFG); + xone_mt76_write_register(mt, XONE_MT_RF_PATCH | MT_VEND_TYPE_CFG, + val & ~BIT(19)); + + err = xone_mt76_load_ivb(mt); + if (err) + return err; + + /* wait for reset */ + if (!xone_mt76_poll(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, + 0x80000000, 0x80000000)) + return -ETIMEDOUT; + + return 0; +} + +int xone_mt76_load_firmware(struct xone_mt76 *mt, const char *name) +{ + const struct firmware *fw; + int err; + + if (xone_mt76_read_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG)) { + dev_dbg(mt->dev, "%s: resetting firmware...\n", __func__); + return xone_mt76_reset_firmware(mt); + } + + err = request_firmware(&fw, name, mt->dev); + if (err) { + if (err == -ENOENT) + dev_err(mt->dev, "%s: firmware not found\n", __func__); + + return err; + } + + err = xone_mt76_send_firmware(mt, fw); + if (err) + goto err_free_firmware; + + xone_mt76_write_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, 0); + + err = xone_mt76_load_ivb(mt); + if (err) + goto err_free_firmware; + + if (!xone_mt76_poll(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, 0x01, 0x01)) + err = -ETIMEDOUT; + +err_free_firmware: + release_firmware(fw); + + return err; +} + +static const struct xone_mt76_channel +xone_mt76_channels[XONE_MT_NUM_CHANNELS] = { + { 0x01, XONE_MT_CH_2G_LOW, MT_PHY_BW_20, 0, true }, + { 0x06, XONE_MT_CH_2G_MID, MT_PHY_BW_20, 0, true }, + { 0x0b, XONE_MT_CH_2G_HIGH, MT_PHY_BW_20, 0, true }, + { 0x24, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, true }, + { 0x28, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, false }, + { 0x2c, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, true }, + { 0x30, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, false }, + { 0x95, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, true }, + { 0x99, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, false }, + { 0x9d, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, true }, + { 0xa1, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false }, + { 0xa5, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false }, +}; + +static int xone_mt76_set_channel_candidates(struct xone_mt76 *mt) +{ + struct sk_buff *skb; + u8 best_chan = mt->channel->index; + u8 chan; + int i, err; + + skb = alloc_skb(sizeof(u32) * 2 + sizeof(u32) * XONE_MT_NUM_CHANNELS, + GFP_KERNEL); + if (!skb) + return -ENOMEM; + + put_unaligned_le32(1, skb_put(skb, sizeof(u32))); + put_unaligned_le32(best_chan, skb_put(skb, sizeof(u32))); + put_unaligned_le32(XONE_MT_NUM_CHANNELS - 1, skb_put(skb, sizeof(u32))); + + for (i = 0; i < XONE_MT_NUM_CHANNELS; i++) { + chan = mt->channels[i].index; + if (chan != best_chan) + put_unaligned_le32(chan, skb_put(skb, sizeof(u32))); + } + + err = xone_mt76_send_ms_command(mt, XONE_MT_SET_CHAN_CANDIDATES, + skb->data, skb->len); + consume_skb(skb); + + return err; +} + +static int xone_mt76_get_channel_power(struct xone_mt76 *mt, + struct xone_mt76_channel *chan) +{ + u16 addr; + u8 idx, target, offset; + u8 entry[8]; + int err; + + if (chan->bandwidth == MT_PHY_BW_20) { + addr = MT_EE_TX_POWER_0_START_2G; + idx = 4; + } else { + /* each group has its own power table */ + addr = MT_EE_TX_POWER_0_START_5G + + chan->group * MT_TX_POWER_GROUP_SIZE_5G; + idx = 5; + } + + err = xone_mt76_read_efuse(mt, addr, entry, sizeof(entry)); + if (err) { + dev_err(mt->dev, "%s: read EFUSE failed: %d\n", __func__, err); + return err; + } + + target = entry[idx]; + offset = entry[idx + chan->band]; + + /* increase or decrease power by offset (in 0.5 dB steps) */ + if (offset & BIT(7)) + chan->power = (offset & BIT(6)) ? + target + (offset & GENMASK(5, 0)) : + target - (offset & GENMASK(5, 0)); + else + chan->power = target; + + return 0; +} + +static int xone_mt76_evaluate_channels(struct xone_mt76 *mt) +{ + struct xone_mt76_channel *chan; + int i, err; + + memcpy(mt->channels, xone_mt76_channels, sizeof(xone_mt76_channels)); + + for (i = 0; i < XONE_MT_NUM_CHANNELS; i++) { + chan = &mt->channels[i]; + + /* original driver increases power for channels 0x24 to 0x30 */ + err = xone_mt76_get_channel_power(mt, chan); + if (err) + return err; + + err = xone_mt76_switch_channel(mt, chan); + if (err) + return err; + + dev_dbg(mt->dev, "%s: channel=%u, power=%u\n", __func__, + chan->index, chan->power); + } + + /* the last channel might not be the best one */ + mt->channel = chan; + + return 0; +} + +static int xone_mt76_init_channels(struct xone_mt76 *mt) +{ + int err; + + /* enable promiscuous mode */ + xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x014f13); + + err = xone_mt76_evaluate_channels(mt); + if (err) + return err; + + /* disable promiscuous mode */ + xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x017f17); + + dev_dbg(mt->dev, "%s: channel=%u\n", __func__, mt->channel->index); + + mt->channel->scan = true; + + err = xone_mt76_switch_channel(mt, mt->channel); + if (err) + return err; + + err = xone_mt76_set_power_mode(mt, MT_RADIO_OFF); + if (err) + return err; + + msleep(50); + + err = xone_mt76_set_power_mode(mt, MT_RADIO_ON); + if (err) + return err; + + mt->channel->scan = false; + + err = xone_mt76_switch_channel(mt, mt->channel); + if (err) + return err; + + return xone_mt76_set_channel_candidates(mt); +} + +static int xone_mt76_set_idle_time(struct xone_mt76 *mt) +{ + __le32 time = cpu_to_le32(64); + + /* prevent wireless clients from disconnecting when idle */ + return xone_mt76_send_ms_command(mt, XONE_MT_SET_IDLE_TIME, + &time, sizeof(time)); +} + +static int xone_mt76_init_address(struct xone_mt76 *mt) +{ + int err; + + err = xone_mt76_read_efuse(mt, MT_EE_MAC_ADDR, + mt->address, sizeof(mt->address)); + if (err) + return err; + + dev_dbg(mt->dev, "%s: address=%pM\n", __func__, mt->address); + + /* some addresses start with 6c:5d:3a */ + /* clients only connect to 62:45:bx:xx:xx:xx */ + if (mt->address[0] != 0x62) { + mt->address[0] = 0x62; + mt->address[1] = 0x45; + mt->address[2] = 0xbd; + } + + err = xone_mt76_write_burst(mt, MT_MAC_ADDR_DW0, + mt->address, sizeof(mt->address)); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_MAC_BSSID_DW0, + mt->address, sizeof(mt->address)); + if (err) + return err; + + return xone_mt76_send_ms_command(mt, XONE_MT_SET_MAC_ADDRESS, + mt->address, sizeof(mt->address)); +} + +static int xone_mt76_calibrate_crystal(struct xone_mt76 *mt) +{ + u8 trim[4]; + u16 val; + s8 offset; + u32 ctrl; + int err; + + err = xone_mt76_read_efuse(mt, MT_EE_XTAL_TRIM_2, trim, sizeof(trim)); + if (err) + return err; + + val = (trim[3] << 8) | trim[2]; + offset = val & GENMASK(6, 0); + if ((val & 0xff) == 0xff) + offset = 0; + else if (val & BIT(7)) + offset = -offset; + + val >>= 8; + if (!val || val == 0xff) { + err = xone_mt76_read_efuse(mt, MT_EE_XTAL_TRIM_1, trim, + sizeof(trim)); + if (err) + return err; + + val = (trim[3] << 8) | trim[2]; + val &= 0xff; + if (!val || val == 0xff) + val = 0x14; + } + + val = (val & GENMASK(6, 0)) + offset; + ctrl = xone_mt76_read_register(mt, MT_XO_CTRL5 | MT_VEND_TYPE_CFG); + xone_mt76_write_register(mt, MT_XO_CTRL5 | MT_VEND_TYPE_CFG, + (ctrl & ~MT_XO_CTRL5_C2_VAL) | (val << 8)); + xone_mt76_write_register(mt, MT_XO_CTRL6 | MT_VEND_TYPE_CFG, + MT_XO_CTRL6_C2_CTRL); + xone_mt76_write_register(mt, MT_CMB_CTRL, 0x0091a7ff); + + return 0; +} + +static int xone_mt76_calibrate_radio(struct xone_mt76 *mt) +{ + int err; + + /* configure automatic gain control (AGC) */ + xone_mt76_write_register(mt, MT_BBP(AGC, 8), 0x18365efa); + xone_mt76_write_register(mt, MT_BBP(AGC, 9), 0x18365efa); + + /* reset required for reliable WLAN associations */ + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); + xone_mt76_write_register(mt, MT_RF_BYPASS_0, 0); + xone_mt76_write_register(mt, MT_RF_SETTING_0, 0); + + err = xone_mt76_calibrate(mt, MT_MCU_CAL_TEMP_SENSOR, 0); + if (err) + return err; + + err = xone_mt76_calibrate(mt, MT_MCU_CAL_RXDCOC, 1); + if (err) + return err; + + err = xone_mt76_calibrate(mt, MT_MCU_CAL_RC, 0); + if (err) + return err; + + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_RX | + MT_MAC_SYS_CTRL_ENABLE_TX); + + return 0; +} + +static void xone_mt76_init_registers(struct xone_mt76 *mt) +{ + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_RESET_BBP | + MT_MAC_SYS_CTRL_RESET_CSR); + xone_mt76_write_register(mt, MT_USB_DMA_CFG, 0); + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); + xone_mt76_write_register(mt, MT_PWR_PIN_CFG, 0); + xone_mt76_write_register(mt, MT_LDO_CTRL_1, 0x6b006464); + xone_mt76_write_register(mt, MT_WPDMA_GLO_CFG, 0x70); + xone_mt76_write_register(mt, MT_WMM_AIFSN, 0x2273); + xone_mt76_write_register(mt, MT_WMM_CWMIN, 0x2344); + xone_mt76_write_register(mt, MT_WMM_CWMAX, 0x34aa); + xone_mt76_write_register(mt, MT_FCE_DMA_ADDR, 0x041200); + xone_mt76_write_register(mt, MT_TSO_CTRL, 0); + xone_mt76_write_register(mt, MT_PBF_SYS_CTRL, 0x080c00); + xone_mt76_write_register(mt, MT_PBF_TX_MAX_PCNT, 0x1fbf1f1f); + xone_mt76_write_register(mt, MT_FCE_PSE_CTRL, 0x01); + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_RX | + MT_MAC_SYS_CTRL_ENABLE_TX); + xone_mt76_write_register(mt, MT_AUTO_RSP_CFG, 0x13); + xone_mt76_write_register(mt, MT_MAX_LEN_CFG, 0x3e3fff); + xone_mt76_write_register(mt, MT_AMPDU_MAX_LEN_20M1S, 0xfffc9855); + xone_mt76_write_register(mt, MT_AMPDU_MAX_LEN_20M2S, 0xff); + xone_mt76_write_register(mt, MT_BKOFF_SLOT_CFG, 0x0109); + xone_mt76_write_register(mt, MT_PWR_PIN_CFG, 0); + xone_mt76_write_register(mt, MT_EDCA_CFG_AC(0), 0x064320); + xone_mt76_write_register(mt, MT_EDCA_CFG_AC(1), 0x0a4700); + xone_mt76_write_register(mt, MT_EDCA_CFG_AC(2), 0x043238); + xone_mt76_write_register(mt, MT_EDCA_CFG_AC(3), 0x03212f); + xone_mt76_write_register(mt, MT_TX_PIN_CFG, 0x150f0f); + xone_mt76_write_register(mt, MT_TX_SW_CFG0, 0x101001); + xone_mt76_write_register(mt, MT_TX_SW_CFG1, 0x010000); + xone_mt76_write_register(mt, MT_TXOP_CTRL_CFG, 0x10583f); + xone_mt76_write_register(mt, MT_TX_TIMEOUT_CFG, 0x0a0f90); + xone_mt76_write_register(mt, MT_TX_RETRY_CFG, 0x47d01f0f); + xone_mt76_write_register(mt, MT_CCK_PROT_CFG, 0x03f40003); + xone_mt76_write_register(mt, MT_OFDM_PROT_CFG, 0x03f40003); + xone_mt76_write_register(mt, MT_MM20_PROT_CFG, 0x01742004); + xone_mt76_write_register(mt, MT_GF20_PROT_CFG, 0x01742004); + xone_mt76_write_register(mt, MT_GF40_PROT_CFG, 0x03f42084); + xone_mt76_write_register(mt, MT_EXP_ACK_TIME, 0x2c00dc); + xone_mt76_write_register(mt, MT_TX_ALC_CFG_2, 0x22160a00); + xone_mt76_write_register(mt, MT_TX_ALC_CFG_3, 0x22160a76); + xone_mt76_write_register(mt, MT_TX_ALC_CFG_0, 0x3f3f1818); + xone_mt76_write_register(mt, MT_TX_ALC_CFG_4, 0x0606); + xone_mt76_write_register(mt, MT_PIFS_TX_CFG, 0x060fff); + xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x017f17); + xone_mt76_write_register(mt, MT_LEGACY_BASIC_RATE, 0x017f); + xone_mt76_write_register(mt, MT_HT_BASIC_RATE, 0x8003); + xone_mt76_write_register(mt, MT_PN_PAD_MODE, 0x02); + xone_mt76_write_register(mt, MT_TXOP_HLDR_ET, 0x02); + xone_mt76_write_register(mt, MT_TX_PROT_CFG6, 0xe3f42004); + xone_mt76_write_register(mt, MT_TX_PROT_CFG7, 0xe3f42084); + xone_mt76_write_register(mt, MT_TX_PROT_CFG8, 0xe3f42104); + xone_mt76_write_register(mt, MT_DACCLK_EN_DLY_CFG, 0); + xone_mt76_write_register(mt, MT_RF_PA_MODE_ADJ0, 0xee000000); + xone_mt76_write_register(mt, MT_RF_PA_MODE_ADJ1, 0xee000000); + xone_mt76_write_register(mt, MT_TX0_RF_GAIN_CORR, 0x0f3c3c3c); + xone_mt76_write_register(mt, MT_TX1_RF_GAIN_CORR, 0x0f3c3c3c); + xone_mt76_write_register(mt, MT_PBF_CFG, 0x1efebcf5); + xone_mt76_write_register(mt, MT_PAUSE_ENABLE_CONTROL1, 0x0a); + xone_mt76_write_register(mt, MT_RF_BYPASS_0, 0x7f000000); + xone_mt76_write_register(mt, MT_RF_SETTING_0, 0x1a800000); + xone_mt76_write_register(mt, MT_XIFS_TIME_CFG, 0x33a40e0a); + xone_mt76_write_register(mt, MT_FCE_L2_STUFF, 0x03ff0223); + xone_mt76_write_register(mt, MT_TX_RTS_CFG, 0); + xone_mt76_write_register(mt, MT_BEACON_TIME_CFG, 0x0640); + xone_mt76_write_register(mt, MT_EXT_CCA_CFG, 0xf0e4); + xone_mt76_write_register(mt, MT_CH_TIME_CFG, 0x015f); +} + +static u16 xone_mt76_get_chip_id(struct xone_mt76 *mt) +{ + u8 id[4]; + + if (xone_mt76_read_efuse(mt, MT_EE_CHIP_ID, &id, sizeof(id))) + return 0; + + return (id[1] << 8) | id[2]; +} + +int xone_mt76_init_radio(struct xone_mt76 *mt) +{ + int err; + + dev_dbg(mt->dev, "%s: id=0x%04x\n", __func__, + xone_mt76_get_chip_id(mt)); + + err = xone_mt76_select_function(mt, MT_Q_SELECT, 1); + if (err) + return err; + + err = xone_mt76_set_power_mode(mt, MT_RADIO_ON); + if (err) + return err; + + err = xone_mt76_load_cr(mt, MT_RF_BBP_CR); + if (err) + return err; + + xone_mt76_init_registers(mt); + + err = xone_mt76_calibrate_crystal(mt); + if (err) + return err; + + err = xone_mt76_init_address(mt); + if (err) + return err; + + err = xone_mt76_set_idle_time(mt); + if (err) + return err; + + err = xone_mt76_calibrate_radio(mt); + if (err) + return err; + + err = xone_mt76_init_channels(mt); + if (err) + return err; + + /* mandatory delay after channel change */ + msleep(1000); + + return xone_mt76_set_pairing(mt, false); +} + +int xone_mt76_suspend_radio(struct xone_mt76 *mt) +{ + int err; + + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); + + /* enable wake-on-wireless */ + err = xone_mt76_set_wow_enable(mt, true); + if (err) + return err; + + return xone_mt76_set_wow_traffic(mt, XONE_MT_WOW_TO_HOST); +} + +int xone_mt76_resume_radio(struct xone_mt76 *mt) +{ + int err; + + err = xone_mt76_set_wow_traffic(mt, XONE_MT_WOW_TO_FIRMWARE); + if (err) + return err; + + /* disable wake-on-wireless */ + err = xone_mt76_set_wow_enable(mt, false); + if (err) + return err; + + err = xone_mt76_switch_channel(mt, mt->channel); + if (err) + return err; + + err = xone_mt76_set_pairing(mt, false); + if (err) + return err; + + xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_RX | + MT_MAC_SYS_CTRL_ENABLE_TX); + + return 0; +} + +static int xone_mt76_write_beacon(struct xone_mt76 *mt, bool pair) +{ + struct sk_buff *skb; + struct mt76_txwi txwi = {}; + struct ieee80211_mgmt mgmt = {}; + u8 data[] = { + /* information element with Microsoft's OUI (00:50:f2) */ + /* probably includes the selected channel pair */ + 0x00, 0x00, 0xdd, 0x10, 0x00, 0x50, 0xf2, 0x11, + 0x01, 0x10, pair, 0xa5, 0x30, 0x99, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + int mgmt_len = sizeof(struct ieee80211_hdr_3addr) + + sizeof(mgmt.u.beacon); + int err; + + skb = alloc_skb(sizeof(txwi) + mgmt_len + sizeof(data), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* generate beacon timestamp */ + /* use hardware sequence control */ + txwi.flags = cpu_to_le16(MT_TXWI_FLAGS_TS); + txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); + txwi.ack_ctl = MT_TXWI_ACK_CTL_NSEQ; + txwi.len_ctl = cpu_to_le16(mgmt_len + sizeof(data)); + + mgmt.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_BEACON); + eth_broadcast_addr(mgmt.da); + memcpy(mgmt.sa, mt->address, ETH_ALEN); + memcpy(mgmt.bssid, mt->address, ETH_ALEN); + + /* default beacon interval (100 ms) */ + /* original capability info */ + mgmt.u.beacon.beacon_int = cpu_to_le16(100); + mgmt.u.beacon.capab_info = cpu_to_le16(0xc631); + + skb_put_data(skb, &txwi, sizeof(txwi)); + skb_put_data(skb, &mgmt, mgmt_len); + skb_put_data(skb, data, sizeof(data)); + + err = xone_mt76_write_burst(mt, MT_BEACON_BASE, skb->data, skb->len); + consume_skb(skb); + + return err; +} + +int xone_mt76_set_pairing(struct xone_mt76 *mt, bool enable) +{ + int err; + + err = xone_mt76_write_beacon(mt, enable); + if (err) + return err; + + /* enable timing synchronization function (TSF) timer */ + /* enable target beacon transmission time (TBTT) timer */ + /* set TSF timer to AP mode */ + /* activate beacon transmission */ + xone_mt76_write_register(mt, MT_BEACON_TIME_CFG, + MT_BEACON_TIME_CFG_BEACON_TX | + MT_BEACON_TIME_CFG_TBTT_EN | + MT_BEACON_TIME_CFG_SYNC_MODE | + MT_BEACON_TIME_CFG_TIMER_EN | + FIELD_PREP(MT_BEACON_TIME_CFG_INTVAL, 0x0640)); + + return 0; +} + +int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr) +{ + struct sk_buff *skb; + struct ieee80211_hdr_3addr hdr = {}; + u8 data[] = { + 0x70, 0x02, 0x00, 0x45, 0x55, 0x01, 0x0f, 0x8f, + 0xff, 0x87, 0x1f, + }; + + skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + sizeof(hdr) + + sizeof(data), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + XONE_MT_WLAN_RESERVED); + memcpy(hdr.addr1, addr, ETH_ALEN); + memcpy(hdr.addr2, mt->address, ETH_ALEN); + memcpy(hdr.addr3, mt->address, ETH_ALEN); + + skb_reserve(skb, sizeof(struct mt76_txwi)); + skb_put_data(skb, &hdr, sizeof(hdr)); + skb_put_data(skb, data, sizeof(data)); + + return xone_mt76_send_wlan(mt, skb); +} + +int xone_mt76_associate_client(struct xone_mt76 *mt, u8 wcid, u8 *addr) +{ + struct sk_buff *skb; + struct ieee80211_mgmt mgmt = {}; + u8 data[] = { wcid - 1, 0x00, 0x00, 0x00, 0x40, 0x1f, 0x00, 0x00 }; + int mgmt_len = sizeof(struct ieee80211_hdr_3addr) + + sizeof(mgmt.u.assoc_resp); + int err; + + skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + mgmt_len, + GFP_KERNEL); + if (!skb) + return -ENOMEM; + + mgmt.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ASSOC_RESP); + memcpy(mgmt.da, addr, ETH_ALEN); + memcpy(mgmt.sa, mt->address, ETH_ALEN); + memcpy(mgmt.bssid, mt->address, ETH_ALEN); + + /* original status code and association ID */ + mgmt.u.assoc_resp.status_code = cpu_to_le16(0x0110); + mgmt.u.assoc_resp.aid = cpu_to_le16(0x0f00); + + skb_reserve(skb, sizeof(struct mt76_txwi)); + skb_put_data(skb, &mgmt, mgmt_len); + memset(skb_put(skb, 8), 0, 8); + + err = xone_mt76_write_burst(mt, MT_WCID_ADDR(wcid), addr, ETH_ALEN); + if (err) + goto err_free_skb; + + err = xone_mt76_send_ms_command(mt, XONE_MT_ADD_CLIENT, + data, sizeof(data)); + if (err) + goto err_free_skb; + + return xone_mt76_send_wlan(mt, skb); + +err_free_skb: + kfree_skb(skb); + + return err; +} + +int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid) +{ + u8 data[] = { wcid - 1, 0x00, 0x00, 0x00 }; + u8 addr[ETH_ALEN] = {}; + int err; + + err = xone_mt76_send_ms_command(mt, XONE_MT_REMOVE_CLIENT, + data, sizeof(data)); + if (err) + return err; + + return xone_mt76_write_burst(mt, MT_WCID_ADDR(wcid), addr, ETH_ALEN); +} diff --git a/drivers/hid/xone/transport/mt76.h b/drivers/hid/xone/transport/mt76.h new file mode 100644 index 000000000..71474adf1 --- /dev/null +++ b/drivers/hid/xone/transport/mt76.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#pragma once + +#include "mt76_defs.h" + +#define XONE_MT_EP_IN_CMD 0x05 +#define XONE_MT_EP_IN_WLAN 0x04 +#define XONE_MT_EP_OUT 0x04 + +#define XONE_MT_NUM_CHANNELS 12 + +/* 802.11 frame subtype: reserved */ +#define XONE_MT_WLAN_RESERVED 0x70 + +enum xone_mt76_led_mode { + XONE_MT_LED_BLINK = 0x00, + XONE_MT_LED_ON = 0x01, + XONE_MT_LED_OFF = 0x02, +}; + +enum xone_mt76_event { + XONE_MT_EVT_BUTTON = 0x04, + XONE_MT_EVT_CHANNELS = 0x0a, + XONE_MT_EVT_PACKET_RX = 0x0c, + XONE_MT_EVT_COREDUMP = 0x0d, + XONE_MT_EVT_CLIENT_LOST = 0x0e, +}; + +struct xone_mt76_channel { + u8 index; + u8 band; + enum mt76_phy_bandwidth bandwidth; + enum mt76_cal_channel_group group; + bool scan; + u8 power; +}; + +struct xone_mt76 { + struct device *dev; + struct usb_device *udev; + + __le32 control_data; + u8 address[ETH_ALEN]; + + struct xone_mt76_channel channels[XONE_MT_NUM_CHANNELS]; + struct xone_mt76_channel *channel; +}; + +struct sk_buff *xone_mt76_alloc_message(int len, gfp_t gfp); +void xone_mt76_prep_command(struct sk_buff *skb, enum mt76_mcu_cmd cmd); + +int xone_mt76_set_led_mode(struct xone_mt76 *mt, enum xone_mt76_led_mode mode); +int xone_mt76_load_firmware(struct xone_mt76 *mt, const char *name); +int xone_mt76_init_radio(struct xone_mt76 *mt); +int xone_mt76_suspend_radio(struct xone_mt76 *mt); +int xone_mt76_resume_radio(struct xone_mt76 *mt); +int xone_mt76_set_pairing(struct xone_mt76 *mt, bool enable); + +int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr); +int xone_mt76_associate_client(struct xone_mt76 *mt, u8 wcid, u8 *addr); +int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid); diff --git a/drivers/hid/xone/transport/mt76_defs.h b/drivers/hid/xone/transport/mt76_defs.h new file mode 100644 index 000000000..d190ceef8 --- /dev/null +++ b/drivers/hid/xone/transport/mt76_defs.h @@ -0,0 +1,1052 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Based on code from the open source mt76 driver with minor modifications. + * + * Copyright (C) 2021 Severin von Wnuck + * + * Special thanks to the authors of the mt76 driver: + * + * Copyright (C) Felix Fietkau + * Copyright (C) Lorenzo Bianconi + * Copyright (C) Stanislaw Gruszka + */ + +#pragma once + +#include + +#define MT_ASIC_VERSION 0x0000 + +#define MT_CMB_CTRL 0x0020 +#define MT_CMB_CTRL_XTAL_RDY BIT(22) +#define MT_CMB_CTRL_PLL_LD BIT(23) + +#define MT_EFUSE_CTRL 0x0024 +#define MT_EFUSE_CTRL_AOUT GENMASK(5, 0) +#define MT_EFUSE_CTRL_MODE GENMASK(7, 6) +#define MT_EFUSE_CTRL_LDO_OFF_TIME GENMASK(13, 8) +#define MT_EFUSE_CTRL_LDO_ON_TIME GENMASK(15, 14) +#define MT_EFUSE_CTRL_AIN GENMASK(25, 16) +#define MT_EFUSE_CTRL_KICK BIT(30) +#define MT_EFUSE_CTRL_SEL BIT(31) + +#define MT_EFUSE_DATA_BASE 0x0028 +#define MT_EFUSE_DATA(n) (MT_EFUSE_DATA_BASE + ((n) << 2)) + +#define MT_COEXCFG0 0x0040 +#define MT_COEXCFG0_COEX_EN BIT(0) + +#define MT_WLAN_FUN_CTRL 0x0080 +#define MT_WLAN_FUN_CTRL_WLAN_EN BIT(0) +#define MT_WLAN_FUN_CTRL_WLAN_CLK_EN BIT(1) +#define MT_WLAN_FUN_CTRL_WLAN_RESET_RF BIT(2) + +#define MT_COEXCFG3 0x004c + +#define MT_LDO_CTRL_0 0x006c +#define MT_LDO_CTRL_1 0x0070 + +#define MT_WLAN_FUN_CTRL_CSR_F20M_CKEN BIT(3) + +#define MT_WLAN_FUN_CTRL_PCIE_CLK_REQ BIT(4) +#define MT_WLAN_FUN_CTRL_FRC_WL_ANT_SEL BIT(5) +#define MT_WLAN_FUN_CTRL_INV_ANT_SEL BIT(6) +#define MT_WLAN_FUN_CTRL_WAKE_HOST BIT(7) + +#define MT_WLAN_FUN_CTRL_THERM_RST BIT(8) +#define MT_WLAN_FUN_CTRL_THERM_CKEN BIT(9) + +#define MT_XO_CTRL0 0x0100 +#define MT_XO_CTRL1 0x0104 +#define MT_XO_CTRL2 0x0108 +#define MT_XO_CTRL3 0x010c +#define MT_XO_CTRL4 0x0110 + +#define MT_XO_CTRL5 0x0114 +#define MT_XO_CTRL5_C2_VAL GENMASK(14, 8) + +#define MT_XO_CTRL6 0x0118 +#define MT_XO_CTRL6_C2_CTRL GENMASK(14, 8) + +#define MT_XO_CTRL7 0x011c + +#define MT_IOCFG_6 0x0124 + +#define MT_USB_U3DMA_CFG 0x9018 +#define MT_USB_DMA_CFG_RX_BULK_AGG_TOUT GENMASK(7, 0) +#define MT_USB_DMA_CFG_RX_BULK_AGG_LMT GENMASK(15, 8) +#define MT_USB_DMA_CFG_UDMA_TX_WL_DROP BIT(16) +#define MT_USB_DMA_CFG_WAKE_UP_EN BIT(17) +#define MT_USB_DMA_CFG_RX_DROP_OR_PAD BIT(18) +#define MT_USB_DMA_CFG_TX_CLR BIT(19) +#define MT_USB_DMA_CFG_TXOP_HALT BIT(20) +#define MT_USB_DMA_CFG_RX_BULK_AGG_EN BIT(21) +#define MT_USB_DMA_CFG_RX_BULK_EN BIT(22) +#define MT_USB_DMA_CFG_TX_BULK_EN BIT(23) +#define MT_USB_DMA_CFG_EP_OUT_VALID GENMASK(29, 24) +#define MT_USB_DMA_CFG_RX_BUSY BIT(30) +#define MT_USB_DMA_CFG_TX_BUSY BIT(31) + +#define MT_WLAN_MTC_CTRL 0x010148 +#define MT_WLAN_MTC_CTRL_MTCMOS_PWR_UP BIT(0) +#define MT_WLAN_MTC_CTRL_PWR_ACK BIT(12) +#define MT_WLAN_MTC_CTRL_PWR_ACK_S BIT(13) +#define MT_WLAN_MTC_CTRL_BBP_MEM_PD GENMASK(19, 16) +#define MT_WLAN_MTC_CTRL_PBF_MEM_PD BIT(20) +#define MT_WLAN_MTC_CTRL_FCE_MEM_PD BIT(21) +#define MT_WLAN_MTC_CTRL_TSO_MEM_PD BIT(22) +#define MT_WLAN_MTC_CTRL_BBP_MEM_RB BIT(24) +#define MT_WLAN_MTC_CTRL_PBF_MEM_RB BIT(25) +#define MT_WLAN_MTC_CTRL_FCE_MEM_RB BIT(26) +#define MT_WLAN_MTC_CTRL_TSO_MEM_RB BIT(27) +#define MT_WLAN_MTC_CTRL_STATE_UP BIT(28) + +#define MT_INT_SOURCE_CSR 0x0200 +#define MT_INT_MASK_CSR 0x0204 + +#define MT_INT_RX_DONE(n) BIT(n) +#define MT_INT_RX_DONE_ALL GENMASK(1, 0) +#define MT_INT_TX_DONE_ALL GENMASK(13, 4) +#define MT_INT_TX_DONE(n) BIT((n) + 4) +#define MT_INT_RX_COHERENT BIT(16) +#define MT_INT_TX_COHERENT BIT(17) +#define MT_INT_ANY_COHERENT BIT(18) +#define MT_INT_MCU_CMD BIT(19) +#define MT_INT_TBTT BIT(20) +#define MT_INT_PRE_TBTT BIT(21) +#define MT_INT_TX_STAT BIT(22) +#define MT_INT_AUTO_WAKEUP BIT(23) +#define MT_INT_GPTIMER BIT(24) +#define MT_INT_RXDELAYINT BIT(26) +#define MT_INT_TXDELAYINT BIT(27) + +#define MT_WPDMA_GLO_CFG 0x0208 +#define MT_WPDMA_GLO_CFG_TX_DMA_EN BIT(0) +#define MT_WPDMA_GLO_CFG_TX_DMA_BUSY BIT(1) +#define MT_WPDMA_GLO_CFG_RX_DMA_EN BIT(2) +#define MT_WPDMA_GLO_CFG_RX_DMA_BUSY BIT(3) +#define MT_WPDMA_GLO_CFG_DMA_BURST_SIZE GENMASK(5, 4) +#define MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE BIT(6) +#define MT_WPDMA_GLO_CFG_BIG_ENDIAN BIT(7) +#define MT_WPDMA_GLO_CFG_HDR_SEG_LEN GENMASK(15, 8) +#define MT_WPDMA_GLO_CFG_CLK_GATE_DIS BIT(30) +#define MT_WPDMA_GLO_CFG_RX_2B_OFFSET BIT(31) + +#define MT_WPDMA_RST_IDX 0x020c + +#define MT_WPDMA_DELAY_INT_CFG 0x0210 + +#define MT_WMM_AIFSN 0x0214 +#define MT_WMM_AIFSN_MASK GENMASK(3, 0) +#define MT_WMM_AIFSN_SHIFT(n) ((n) * 4) + +#define MT_WMM_CWMIN 0x0218 +#define MT_WMM_CWMIN_MASK GENMASK(3, 0) +#define MT_WMM_CWMIN_SHIFT(n) ((n) * 4) + +#define MT_WMM_CWMAX 0x021c +#define MT_WMM_CWMAX_MASK GENMASK(3, 0) +#define MT_WMM_CWMAX_SHIFT(n) ((n) * 4) + +#define MT_WMM_TXOP_BASE 0x0220 +#define MT_WMM_TXOP(n) (MT_WMM_TXOP_BASE + (((n) / 2) << 2)) +#define MT_WMM_TXOP_SHIFT(n) (((n) & 1) * 16) +#define MT_WMM_TXOP_MASK GENMASK(15, 0) + +#define MT_FCE_DMA_ADDR 0x0230 +#define MT_FCE_DMA_LEN 0x0234 +#define MT_USB_DMA_CFG 0x0238 + +#define MT_TSO_CTRL 0x0250 +#define MT_HEADER_TRANS_CTRL_REG 0x0260 + +#define MT_US_CYC_CFG 0x02a4 +#define MT_US_CYC_CNT GENMASK(7, 0) + +#define MT_TX_RING_BASE 0x0300 +#define MT_RX_RING_BASE 0x03c0 + +#define MT_PBF_SYS_CTRL 0x0400 +#define MT_PBF_SYS_CTRL_MCU_RESET BIT(0) +#define MT_PBF_SYS_CTRL_DMA_RESET BIT(1) +#define MT_PBF_SYS_CTRL_MAC_RESET BIT(2) +#define MT_PBF_SYS_CTRL_PBF_RESET BIT(3) +#define MT_PBF_SYS_CTRL_ASY_RESET BIT(4) + +#define MT_PBF_CFG 0x0404 +#define MT_PBF_CFG_TX0Q_EN BIT(0) +#define MT_PBF_CFG_TX1Q_EN BIT(1) +#define MT_PBF_CFG_TX2Q_EN BIT(2) +#define MT_PBF_CFG_TX3Q_EN BIT(3) +#define MT_PBF_CFG_RX0Q_EN BIT(4) +#define MT_PBF_CFG_RX_DROP_EN BIT(8) + +#define MT_PBF_TX_MAX_PCNT 0x0408 +#define MT_PBF_RX_MAX_PCNT 0x040c + +#define MT_BCN_OFFSET_BASE 0x041c +#define MT_BCN_OFFSET(n) (MT_BCN_OFFSET_BASE + ((n) << 2)) + +#define MT_RXQ_STA 0x0430 +#define MT_TXQ_STA 0x0434 +#define MT_RF_CSR_CFG 0x0500 +#define MT_RF_CSR_CFG_DATA GENMASK(7, 0) +#define MT_RF_CSR_CFG_REG_ID GENMASK(14, 8) +#define MT_RF_CSR_CFG_REG_BANK GENMASK(17, 15) +#define MT_RF_CSR_CFG_WR BIT(30) +#define MT_RF_CSR_CFG_KICK BIT(31) + +#define MT_RF_BYPASS_0 0x0504 +#define MT_RF_BYPASS_1 0x0508 +#define MT_RF_SETTING_0 0x050c + +#define MT_RF_MISC 0x0518 +#define MT_RF_DATA_WRITE 0x0524 + +#define MT_RF_CTRL 0x0528 +#define MT_RF_CTRL_ADDR GENMASK(11, 0) +#define MT_RF_CTRL_WRITE BIT(12) +#define MT_RF_CTRL_BUSY BIT(13) +#define MT_RF_CTRL_IDX BIT(16) + +#define MT_RF_DATA_READ 0x052c + +#define MT_COM_REG0 0x0730 +#define MT_COM_REG1 0x0734 +#define MT_COM_REG2 0x0738 +#define MT_COM_REG3 0x073c + +#define MT_LED_CTRL 0x0770 +#define MT_LED_CTRL_REPLAY(n) BIT(0 + (8 * (n))) +#define MT_LED_CTRL_POLARITY(n) BIT(1 + (8 * (n))) +#define MT_LED_CTRL_TX_BLINK_MODE(n) BIT(2 + (8 * (n))) +#define MT_LED_CTRL_KICK(n) BIT(7 + (8 * (n))) + +#define MT_LED_TX_BLINK_0 0x0774 +#define MT_LED_TX_BLINK_1 0x0778 + +#define MT_LED_S0_BASE 0x077c +#define MT_LED_S0(n) (MT_LED_S0_BASE + 8 * (n)) +#define MT_LED_S1_BASE 0x0780 +#define MT_LED_S1(n) (MT_LED_S1_BASE + 8 * (n)) +#define MT_LED_STATUS_OFF GENMASK(31, 24) +#define MT_LED_STATUS_ON GENMASK(23, 16) +#define MT_LED_STATUS_DURATION GENMASK(15, 8) + +#define MT_FCE_PSE_CTRL 0x0800 +#define MT_FCE_PARAMETERS 0x0804 +#define MT_FCE_CSO 0x0808 + +#define MT_FCE_L2_STUFF 0x080c +#define MT_FCE_L2_STUFF_HT_L2_EN BIT(0) +#define MT_FCE_L2_STUFF_QOS_L2_EN BIT(1) +#define MT_FCE_L2_STUFF_RX_STUFF_EN BIT(2) +#define MT_FCE_L2_STUFF_TX_STUFF_EN BIT(3) +#define MT_FCE_L2_STUFF_WR_MPDU_LEN_EN BIT(4) +#define MT_FCE_L2_STUFF_MVINV_BSWAP BIT(5) +#define MT_FCE_L2_STUFF_TS_CMD_QSEL_EN GENMASK(15, 8) +#define MT_FCE_L2_STUFF_TS_LEN_EN GENMASK(23, 16) +#define MT_FCE_L2_STUFF_OTHER_PORT GENMASK(25, 24) + +#define MT_FCE_WLAN_FLOW_CONTROL1 0x0824 + +#define MT_TX_CPU_FROM_FCE_BASE_PTR 0x09a0 +#define MT_TX_CPU_FROM_FCE_MAX_COUNT 0x09a4 +#define MT_TX_CPU_FROM_FCE_CPU_DESC_IDX 0x09a8 +#define MT_FCE_PDMA_GLOBAL_CONF 0x09c4 +#define MT_FCE_SKIP_FS 0x0a6c + +#define MT_PAUSE_ENABLE_CONTROL1 0x0a38 + +#define MT_MAC_CSR0 0x1000 + +#define MT_MAC_SYS_CTRL 0x1004 +#define MT_MAC_SYS_CTRL_RESET_CSR BIT(0) +#define MT_MAC_SYS_CTRL_RESET_BBP BIT(1) +#define MT_MAC_SYS_CTRL_ENABLE_TX BIT(2) +#define MT_MAC_SYS_CTRL_ENABLE_RX BIT(3) + +#define MT_MAC_ADDR_DW0 0x1008 +#define MT_MAC_ADDR_DW1 0x100c +#define MT_MAC_ADDR_DW1_U2ME_MASK GENMASK(23, 16) + +#define MT_MAC_BSSID_DW0 0x1010 +#define MT_MAC_BSSID_DW1 0x1014 +#define MT_MAC_BSSID_DW1_ADDR GENMASK(15, 0) +#define MT_MAC_BSSID_DW1_MBSS_MODE GENMASK(17, 16) +#define MT_MAC_BSSID_DW1_MBEACON_N GENMASK(20, 18) +#define MT_MAC_BSSID_DW1_MBSS_LOCAL_BIT BIT(21) +#define MT_MAC_BSSID_DW1_MBSS_MODE_B2 BIT(22) +#define MT_MAC_BSSID_DW1_MBEACON_N_B3 BIT(23) +#define MT_MAC_BSSID_DW1_MBSS_IDX_BYTE GENMASK(26, 24) + +#define MT_MAX_LEN_CFG 0x1018 +#define MT_MAX_LEN_CFG_AMPDU GENMASK(13, 12) + +#define MT_LED_CFG 0x102c + +#define MT_AMPDU_MAX_LEN_20M1S 0x1030 +#define MT_AMPDU_MAX_LEN_20M2S 0x1034 +#define MT_AMPDU_MAX_LEN_40M1S 0x1038 +#define MT_AMPDU_MAX_LEN_40M2S 0x103c +#define MT_AMPDU_MAX_LEN 0x1040 + +#define MT_WCID_DROP_BASE 0x106c +#define MT_WCID_DROP(n) (MT_WCID_DROP_BASE + ((n) >> 5) * 4) +#define MT_WCID_DROP_MASK(n) BIT((n) % 32) + +#define MT_BCN_BYPASS_MASK 0x108c + +#define MT_MAC_APC_BSSID_BASE 0x1090 +#define MT_MAC_APC_BSSID_L(n) (MT_MAC_APC_BSSID_BASE + ((n) * 8)) +#define MT_MAC_APC_BSSID_H(n) (MT_MAC_APC_BSSID_BASE + ((n) * 8 + 4)) +#define MT_MAC_APC_BSSID_H_ADDR GENMASK(15, 0) +#define MT_MAC_APC_BSSID0_H_EN BIT(16) + +#define MT_XIFS_TIME_CFG 0x1100 +#define MT_XIFS_TIME_CFG_CCK_SIFS GENMASK(7, 0) +#define MT_XIFS_TIME_CFG_OFDM_SIFS GENMASK(15, 8) +#define MT_XIFS_TIME_CFG_OFDM_XIFS GENMASK(19, 16) +#define MT_XIFS_TIME_CFG_EIFS GENMASK(28, 20) +#define MT_XIFS_TIME_CFG_BB_RXEND_EN BIT(29) + +#define MT_BKOFF_SLOT_CFG 0x1104 +#define MT_BKOFF_SLOT_CFG_SLOTTIME GENMASK(7, 0) +#define MT_BKOFF_SLOT_CFG_CC_DELAY GENMASK(11, 8) + +#define MT_CH_TIME_CFG 0x110c +#define MT_CH_TIME_CFG_TIMER_EN BIT(0) +#define MT_CH_TIME_CFG_TX_AS_BUSY BIT(1) +#define MT_CH_TIME_CFG_RX_AS_BUSY BIT(2) +#define MT_CH_TIME_CFG_NAV_AS_BUSY BIT(3) +#define MT_CH_TIME_CFG_EIFS_AS_BUSY BIT(4) +#define MT_CH_TIME_CFG_MDRDY_CNT_EN BIT(5) +#define MT_CH_CCA_RC_EN BIT(6) +#define MT_CH_TIME_CFG_CH_TIMER_CLR GENMASK(9, 8) +#define MT_CH_TIME_CFG_MDRDY_CLR GENMASK(11, 10) + +#define MT_PBF_LIFE_TIMER 0x1110 + +#define MT_BEACON_TIME_CFG 0x1114 +#define MT_BEACON_TIME_CFG_INTVAL GENMASK(15, 0) +#define MT_BEACON_TIME_CFG_TIMER_EN BIT(16) +#define MT_BEACON_TIME_CFG_SYNC_MODE GENMASK(18, 17) +#define MT_BEACON_TIME_CFG_TBTT_EN BIT(19) +#define MT_BEACON_TIME_CFG_BEACON_TX BIT(20) +#define MT_BEACON_TIME_CFG_TSF_COMP GENMASK(31, 24) + +#define MT_TBTT_SYNC_CFG 0x1118 +#define MT_TSF_TIMER_DW0 0x111c +#define MT_TSF_TIMER_DW1 0x1120 +#define MT_TBTT_TIMER 0x1124 +#define MT_TBTT_TIMER_VAL GENMASK(16, 0) + +#define MT_INT_TIMER_CFG 0x1128 +#define MT_INT_TIMER_CFG_PRE_TBTT GENMASK(15, 0) +#define MT_INT_TIMER_CFG_GP_TIMER GENMASK(31, 16) + +#define MT_INT_TIMER_EN 0x112c +#define MT_INT_TIMER_EN_PRE_TBTT_EN BIT(0) +#define MT_INT_TIMER_EN_GP_TIMER_EN BIT(1) + +#define MT_CH_IDLE 0x1130 +#define MT_CH_BUSY 0x1134 +#define MT_EXT_CH_BUSY 0x1138 +#define MT_ED_CCA_TIMER 0x1140 + +#define MT_MAC_STATUS 0x1200 +#define MT_MAC_STATUS_TX BIT(0) +#define MT_MAC_STATUS_RX BIT(1) + +#define MT_PWR_PIN_CFG 0x1204 +#define MT_AUX_CLK_CFG 0x120c + +#define MT_BB_PA_MODE_CFG0 0x1214 +#define MT_BB_PA_MODE_CFG1 0x1218 +#define MT_RF_PA_MODE_CFG0 0x121c +#define MT_RF_PA_MODE_CFG1 0x1220 + +#define MT_RF_PA_MODE_ADJ0 0x1228 +#define MT_RF_PA_MODE_ADJ1 0x122c + +#define MT_DACCLK_EN_DLY_CFG 0x1264 + +#define MT_EDCA_CFG_BASE 0x1300 +#define MT_EDCA_CFG_AC(n) (MT_EDCA_CFG_BASE + ((n) << 2)) +#define MT_EDCA_CFG_TXOP GENMASK(7, 0) +#define MT_EDCA_CFG_AIFSN GENMASK(11, 8) +#define MT_EDCA_CFG_CWMIN GENMASK(15, 12) +#define MT_EDCA_CFG_CWMAX GENMASK(19, 16) + +#define MT_TX_PWR_CFG_0 0x1314 +#define MT_TX_PWR_CFG_1 0x1318 +#define MT_TX_PWR_CFG_2 0x131c +#define MT_TX_PWR_CFG_3 0x1320 +#define MT_TX_PWR_CFG_4 0x1324 +#define MT_TX_PIN_CFG 0x1328 +#define MT_TX_PIN_CFG_TXANT GENMASK(3, 0) +#define MT_TX_PIN_CFG_RXANT GENMASK(11, 8) +#define MT_TX_PIN_RFTR_EN BIT(16) +#define MT_TX_PIN_TRSW_EN BIT(18) + +#define MT_TX_BAND_CFG 0x132c +#define MT_TX_BAND_CFG_UPPER_40M BIT(0) +#define MT_TX_BAND_CFG_5G BIT(1) +#define MT_TX_BAND_CFG_2G BIT(2) + +#define MT_HT_FBK_TO_LEGACY 0x1384 +#define MT_TX_MPDU_ADJ_INT 0x1388 + +#define MT_TX_PWR_CFG_7 0x13d4 +#define MT_TX_PWR_CFG_8 0x13d8 +#define MT_TX_PWR_CFG_9 0x13dc + +#define MT_TX_SW_CFG0 0x1330 +#define MT_TX_SW_CFG1 0x1334 +#define MT_TX_SW_CFG2 0x1338 + +#define MT_TXOP_CTRL_CFG 0x1340 +#define MT_TXOP_TRUN_EN GENMASK(5, 0) +#define MT_TXOP_EXT_CCA_DLY GENMASK(15, 8) +#define MT_TXOP_ED_CCA_EN BIT(20) + +#define MT_TX_RTS_CFG 0x1344 +#define MT_TX_RTS_CFG_RETRY_LIMIT GENMASK(7, 0) +#define MT_TX_RTS_CFG_THRESH GENMASK(23, 8) +#define MT_TX_RTS_FALLBACK BIT(24) + +#define MT_TX_TIMEOUT_CFG 0x1348 +#define MT_TX_TIMEOUT_CFG_ACKTO GENMASK(15, 8) + +#define MT_TX_RETRY_CFG 0x134c +#define MT_TX_LINK_CFG 0x1350 +#define MT_TX_CFACK_EN BIT(12) +#define MT_VHT_HT_FBK_CFG0 0x1354 +#define MT_VHT_HT_FBK_CFG1 0x1358 +#define MT_LG_FBK_CFG0 0x135c +#define MT_LG_FBK_CFG1 0x1360 + +#define MT_PROT_CFG_RATE GENMASK(15, 0) +#define MT_PROT_CFG_CTRL GENMASK(17, 16) +#define MT_PROT_CFG_NAV GENMASK(19, 18) +#define MT_PROT_CFG_TXOP_ALLOW GENMASK(25, 20) +#define MT_PROT_CFG_RTS_THRESH BIT(26) + +#define MT_CCK_PROT_CFG 0x1364 +#define MT_OFDM_PROT_CFG 0x1368 +#define MT_MM20_PROT_CFG 0x136c +#define MT_MM40_PROT_CFG 0x1370 +#define MT_GF20_PROT_CFG 0x1374 +#define MT_GF40_PROT_CFG 0x1378 + +#define MT_PROT_RATE GENMASK(15, 0) +#define MT_PROT_CTRL_RTS_CTS BIT(16) +#define MT_PROT_CTRL_CTS2SELF BIT(17) +#define MT_PROT_NAV_SHORT BIT(18) +#define MT_PROT_NAV_LONG BIT(19) +#define MT_PROT_TXOP_ALLOW_CCK BIT(20) +#define MT_PROT_TXOP_ALLOW_OFDM BIT(21) +#define MT_PROT_TXOP_ALLOW_MM20 BIT(22) +#define MT_PROT_TXOP_ALLOW_MM40 BIT(23) +#define MT_PROT_TXOP_ALLOW_GF20 BIT(24) +#define MT_PROT_TXOP_ALLOW_GF40 BIT(25) +#define MT_PROT_RTS_THR_EN BIT(26) +#define MT_PROT_RATE_CCK_11 0x0003 +#define MT_PROT_RATE_OFDM_6 0x2000 +#define MT_PROT_RATE_OFDM_24 0x2004 +#define MT_PROT_RATE_DUP_OFDM_24 0x2084 +#define MT_PROT_RATE_SGI_OFDM_24 0x2104 +#define MT_PROT_TXOP_ALLOW_ALL GENMASK(25, 20) +#define MT_PROT_TXOP_ALLOW_BW20 (MT_PROT_TXOP_ALLOW_ALL & \ + ~MT_PROT_TXOP_ALLOW_MM40 & \ + ~MT_PROT_TXOP_ALLOW_GF40) + +#define MT_EXP_ACK_TIME 0x1380 + +#define MT_TX_PWR_CFG_0_EXT 0x1390 +#define MT_TX_PWR_CFG_1_EXT 0x1394 + +#define MT_TX_FBK_LIMIT 0x1398 +#define MT_TX_FBK_LIMIT_MPDU_FBK GENMASK(7, 0) +#define MT_TX_FBK_LIMIT_AMPDU_FBK GENMASK(15, 8) +#define MT_TX_FBK_LIMIT_MPDU_UP_CLEAR BIT(16) +#define MT_TX_FBK_LIMIT_AMPDU_UP_CLEAR BIT(17) +#define MT_TX_FBK_LIMIT_RATE_LUT BIT(18) + +#define MT_TX0_RF_GAIN_CORR 0x13a0 +#define MT_TX1_RF_GAIN_CORR 0x13a4 +#define MT_TX0_RF_GAIN_ATTEN 0x13a8 + +#define MT_TX_ALC_CFG_0 0x13b0 +#define MT_TX_ALC_CFG_0_CH_INIT_0 GENMASK(5, 0) +#define MT_TX_ALC_CFG_0_CH_INIT_1 GENMASK(13, 8) +#define MT_TX_ALC_CFG_0_LIMIT_0 GENMASK(21, 16) +#define MT_TX_ALC_CFG_0_LIMIT_1 GENMASK(29, 24) + +#define MT_TX_ALC_CFG_1 0x13b4 +#define MT_TX_ALC_CFG_1_TEMP_COMP GENMASK(5, 0) + +#define MT_TX_ALC_CFG_2 0x13a8 +#define MT_TX_ALC_CFG_2_TEMP_COMP GENMASK(5, 0) + +#define MT_TX_ALC_CFG_3 0x13ac +#define MT_TX_ALC_CFG_4 0x13c0 +#define MT_TX_ALC_CFG_4_LOWGAIN_CH_EN BIT(31) + +#define MT_TX_ALC_VGA3 0x13c8 + +#define MT_TX_PROT_CFG6 0x13e0 +#define MT_TX_PROT_CFG7 0x13e4 +#define MT_TX_PROT_CFG8 0x13e8 + +#define MT_PIFS_TX_CFG 0x13ec + +#define MT_RX_FILTR_CFG 0x1400 + +#define MT_RX_FILTR_CFG_CRC_ERR BIT(0) +#define MT_RX_FILTR_CFG_PHY_ERR BIT(1) +#define MT_RX_FILTR_CFG_PROMISC BIT(2) +#define MT_RX_FILTR_CFG_OTHER_BSS BIT(3) +#define MT_RX_FILTR_CFG_VER_ERR BIT(4) +#define MT_RX_FILTR_CFG_MCAST BIT(5) +#define MT_RX_FILTR_CFG_BCAST BIT(6) +#define MT_RX_FILTR_CFG_DUP BIT(7) +#define MT_RX_FILTR_CFG_CFACK BIT(8) +#define MT_RX_FILTR_CFG_CFEND BIT(9) +#define MT_RX_FILTR_CFG_ACK BIT(10) +#define MT_RX_FILTR_CFG_CTS BIT(11) +#define MT_RX_FILTR_CFG_RTS BIT(12) +#define MT_RX_FILTR_CFG_PSPOLL BIT(13) +#define MT_RX_FILTR_CFG_BA BIT(14) +#define MT_RX_FILTR_CFG_BAR BIT(15) +#define MT_RX_FILTR_CFG_CTRL_RSV BIT(16) + +#define MT_AUTO_RSP_CFG 0x1404 +#define MT_AUTO_RSP_EN BIT(0) +#define MT_AUTO_RSP_PREAMB_SHORT BIT(4) +#define MT_LEGACY_BASIC_RATE 0x1408 +#define MT_HT_BASIC_RATE 0x140c + +#define MT_HT_CTRL_CFG 0x1410 +#define MT_RX_PARSER_CFG 0x1418 +#define MT_RX_PARSER_RX_SET_NAV_ALL BIT(0) + +#define MT_EXT_CCA_CFG 0x141c +#define MT_EXT_CCA_CFG_CCA0 GENMASK(1, 0) +#define MT_EXT_CCA_CFG_CCA1 GENMASK(3, 2) +#define MT_EXT_CCA_CFG_CCA2 GENMASK(5, 4) +#define MT_EXT_CCA_CFG_CCA3 GENMASK(7, 6) +#define MT_EXT_CCA_CFG_CCA_MASK GENMASK(11, 8) +#define MT_EXT_CCA_CFG_ED_CCA_MASK GENMASK(15, 12) + +#define MT_TX_SW_CFG3 0x1478 + +#define MT_PN_PAD_MODE 0x150c + +#define MT_TXOP_HLDR_ET 0x1608 +#define MT_TXOP_HLDR_TX40M_BLK_EN BIT(1) + +#define MT_PROT_AUTO_TX_CFG 0x1648 +#define MT_PROT_AUTO_TX_CFG_PROT_PADJ GENMASK(11, 8) +#define MT_PROT_AUTO_TX_CFG_AUTO_PADJ GENMASK(27, 24) + +#define MT_RX_STAT_0 0x1700 +#define MT_RX_STAT_0_CRC_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_0_PHY_ERRORS GENMASK(31, 16) + +#define MT_RX_STAT_1 0x1704 +#define MT_RX_STAT_1_CCA_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_1_PLCP_ERRORS GENMASK(31, 16) + +#define MT_RX_STAT_2 0x1708 +#define MT_RX_STAT_2_DUP_ERRORS GENMASK(15, 0) +#define MT_RX_STAT_2_OVERFLOW_ERRORS GENMASK(31, 16) + +#define MT_TX_STA_0 0x170c +#define MT_TX_STA_1 0x1710 +#define MT_TX_STA_2 0x1714 + +#define MT_TX_STAT_FIFO 0x1718 +#define MT_TX_STAT_FIFO_VALID BIT(0) +#define MT_TX_STAT_FIFO_SUCCESS BIT(5) +#define MT_TX_STAT_FIFO_AGGR BIT(6) +#define MT_TX_STAT_FIFO_ACKREQ BIT(7) +#define MT_TX_STAT_FIFO_WCID GENMASK(15, 8) +#define MT_TX_STAT_FIFO_RATE GENMASK(31, 16) + +#define MT_TX_AGG_STAT 0x171c + +#define MT_TX_AGG_CNT_BASE0 0x1720 +#define MT_MPDU_DENSITY_CNT 0x1740 +#define MT_TX_AGG_CNT_BASE1 0x174c + +#define MT_TX_STAT_FIFO_EXT 0x1798 +#define MT_TX_STAT_FIFO_EXT_RETRY GENMASK(7, 0) +#define MT_TX_STAT_FIFO_EXT_PKTID GENMASK(15, 8) + +#define MT_WCID_TX_RATE_BASE 0x1c00 +#define MT_WCID_TX_RATE(i) (MT_WCID_TX_RATE_BASE + ((i) << 3)) + +#define MT_BBP_CORE_BASE 0x2000 +#define MT_BBP_IBI_BASE 0x2100 +#define MT_BBP_AGC_BASE 0x2300 +#define MT_BBP_TXC_BASE 0x2400 +#define MT_BBP_RXC_BASE 0x2500 +#define MT_BBP_TXO_BASE 0x2600 +#define MT_BBP_TXBE_BASE 0x2700 +#define MT_BBP_RXFE_BASE 0x2800 +#define MT_BBP_RXO_BASE 0x2900 +#define MT_BBP_DFS_BASE 0x2a00 +#define MT_BBP_TR_BASE 0x2b00 +#define MT_BBP_CAL_BASE 0x2c00 +#define MT_BBP_DSC_BASE 0x2e00 +#define MT_BBP_PFMU_BASE 0x2f00 + +#define MT_BBP(type, n) (MT_BBP_##type##_BASE + ((n) << 2)) + +#define MT_BBP_CORE_R1_BW GENMASK(4, 3) + +#define MT_BBP_AGC_R0_CTRL_CHAN GENMASK(9, 8) +#define MT_BBP_AGC_R0_BW GENMASK(14, 12) + +/* AGC, R4/R5 */ +#define MT_BBP_AGC_LNA_HIGH_GAIN GENMASK(21, 16) +#define MT_BBP_AGC_LNA_MID_GAIN GENMASK(13, 8) +#define MT_BBP_AGC_LNA_LOW_GAIN GENMASK(5, 0) + +/* AGC, R6/R7 */ +#define MT_BBP_AGC_LNA_ULOW_GAIN GENMASK(5, 0) + +/* AGC, R8/R9 */ +#define MT_BBP_AGC_LNA_GAIN_MODE GENMASK(7, 6) +#define MT_BBP_AGC_GAIN GENMASK(14, 8) + +#define MT_BBP_AGC20_RSSI0 GENMASK(7, 0) +#define MT_BBP_AGC20_RSSI1 GENMASK(15, 8) + +#define MT_BBP_TXBE_R0_CTRL_CHAN GENMASK(1, 0) + +#define MT_WCID_ADDR_BASE 0x1800 +#define MT_WCID_ADDR(n) (MT_WCID_ADDR_BASE + (n) * 8) + +#define MT_SRAM_BASE 0x4000 + +#define MT_WCID_KEY_BASE 0x8000 +#define MT_WCID_KEY(n) (MT_WCID_KEY_BASE + (n) * 32) + +#define MT_WCID_IV_BASE 0xa000 +#define MT_WCID_IV(n) (MT_WCID_IV_BASE + (n) * 8) + +#define MT_WCID_ATTR_BASE 0xa800 +#define MT_WCID_ATTR(n) (MT_WCID_ATTR_BASE + (n) * 4) + +#define MT_WCID_ATTR_PAIRWISE BIT(0) +#define MT_WCID_ATTR_PKEY_MODE GENMASK(3, 1) +#define MT_WCID_ATTR_BSS_IDX GENMASK(6, 4) +#define MT_WCID_ATTR_RXWI_UDF GENMASK(9, 7) +#define MT_WCID_ATTR_PKEY_MODE_EXT BIT(10) +#define MT_WCID_ATTR_BSS_IDX_EXT BIT(11) +#define MT_WCID_ATTR_WAPI_MCBC BIT(15) +#define MT_WCID_ATTR_WAPI_KEYID GENMASK(31, 24) + +#define MT_SKEY_BASE_0 0xac00 +#define MT_SKEY_BASE_1 0xb400 +#define MT_SKEY_0(bss, idx) (MT_SKEY_BASE_0 + (4 * (bss) + (idx)) * 32) +#define MT_SKEY_1(bss, idx) (MT_SKEY_BASE_1 + (4 * ((bss) & 7) + (idx)) * 32) + +#define MT_SKEY_MODE_BASE_0 0xb000 +#define MT_SKEY_MODE_BASE_1 0xb3f0 +#define MT_SKEY_MODE_0(bss) (MT_SKEY_MODE_BASE_0 + (((bss) / 2) << 2)) +#define MT_SKEY_MODE_1(bss) (MT_SKEY_MODE_BASE_1 + ((((bss) & 7) / 2) << 2)) +#define MT_SKEY_MODE_MASK GENMASK(3, 0) +#define MT_SKEY_MODE_SHIFT(bss, idx) (4 * ((idx) + 4 * ((bss) & 1))) + +#define MT_BEACON_BASE 0xc000 + +#define MT_TEMP_SENSOR 0x01d000 +#define MT_TEMP_SENSOR_VAL GENMASK(6, 0) + +#define MT_MCU_RESET_CTL 0x070c +#define MT_MCU_INT_LEVEL 0x0718 +#define MT_MCU_COM_REG0 0x0730 +#define MT_MCU_COM_REG1 0x0734 +#define MT_MCU_COM_REG2 0x0738 +#define MT_MCU_COM_REG3 0x073c + +#define MT_MCU_MEMMAP_WLAN 0x410000 + +#define MT_TXD_INFO_LEN GENMASK(15, 0) +#define MT_TXD_INFO_NEXT_VLD BIT(16) +#define MT_TXD_INFO_TX_BURST BIT(17) +#define MT_TXD_INFO_80211 BIT(19) +#define MT_TXD_INFO_TSO BIT(20) +#define MT_TXD_INFO_CSO BIT(21) +#define MT_TXD_INFO_WIV BIT(24) +#define MT_TXD_INFO_QSEL GENMASK(26, 25) +#define MT_TXD_INFO_DPORT GENMASK(29, 27) +#define MT_TXD_INFO_TYPE GENMASK(31, 30) + +#define MT_RX_FCE_INFO_LEN GENMASK(13, 0) +#define MT_RX_FCE_INFO_SELF_GEN BIT(15) +#define MT_RX_FCE_INFO_CMD_SEQ GENMASK(19, 16) +#define MT_RX_FCE_INFO_EVT_TYPE GENMASK(23, 20) +#define MT_RX_FCE_INFO_PCIE_INTR BIT(24) +#define MT_RX_FCE_INFO_QSEL GENMASK(26, 25) +#define MT_RX_FCE_INFO_D_PORT GENMASK(29, 27) +#define MT_RX_FCE_INFO_TYPE GENMASK(31, 30) + +#define MT_MCU_MSG_LEN GENMASK(15, 0) +#define MT_MCU_MSG_CMD_SEQ GENMASK(19, 16) +#define MT_MCU_MSG_CMD_TYPE GENMASK(26, 20) +#define MT_MCU_MSG_PORT GENMASK(29, 27) +#define MT_MCU_MSG_TYPE GENMASK(31, 30) +#define MT_MCU_MSG_TYPE_CMD BIT(30) + +#define MT_FCE_DMA_ADDR 0x0230 +#define MT_FCE_DMA_LEN 0x0234 + +#define MT_TX_CPU_FROM_FCE_CPU_DESC_IDX 0x09a8 + +#define MT_PKTID_RATE GENMASK(4, 0) +#define MT_PKTID_AC GENMASK(6, 5) + +#define MT_RXINFO_BA BIT(0) +#define MT_RXINFO_DATA BIT(1) +#define MT_RXINFO_NULL BIT(2) +#define MT_RXINFO_FRAG BIT(3) +#define MT_RXINFO_UNICAST BIT(4) +#define MT_RXINFO_MULTICAST BIT(5) +#define MT_RXINFO_BROADCAST BIT(6) +#define MT_RXINFO_MYBSS BIT(7) +#define MT_RXINFO_CRCERR BIT(8) +#define MT_RXINFO_ICVERR BIT(9) +#define MT_RXINFO_MICERR BIT(10) +#define MT_RXINFO_AMSDU BIT(11) +#define MT_RXINFO_HTC BIT(12) +#define MT_RXINFO_RSSI BIT(13) +#define MT_RXINFO_L2PAD BIT(14) +#define MT_RXINFO_AMPDU BIT(15) +#define MT_RXINFO_DECRYPT BIT(16) +#define MT_RXINFO_BSSIDX3 BIT(17) +#define MT_RXINFO_WAPI_KEY BIT(18) +#define MT_RXINFO_PN_LEN GENMASK(21, 19) +#define MT_RXINFO_SW_FTYPE0 BIT(22) +#define MT_RXINFO_SW_FTYPE1 BIT(23) +#define MT_RXINFO_PROBE_RESP BIT(24) +#define MT_RXINFO_BEACON BIT(25) +#define MT_RXINFO_DISASSOC BIT(26) +#define MT_RXINFO_DEAUTH BIT(27) +#define MT_RXINFO_ACTION BIT(28) +#define MT_RXINFO_TCP_SUM_ERR BIT(30) +#define MT_RXINFO_IP_SUM_ERR BIT(31) + +#define MT_RXWI_CTL_WCID GENMASK(7, 0) +#define MT_RXWI_CTL_KEY_IDX GENMASK(9, 8) +#define MT_RXWI_CTL_BSS_IDX GENMASK(12, 10) +#define MT_RXWI_CTL_UDF GENMASK(15, 13) +#define MT_RXWI_CTL_MPDU_LEN GENMASK(29, 16) +#define MT_RXWI_CTL_EOF BIT(31) + +#define MT_RXWI_TID GENMASK(3, 0) +#define MT_RXWI_SN GENMASK(15, 4) + +#define MT_RXWI_RATE_INDEX GENMASK(5, 0) +#define MT_RXWI_RATE_LDPC BIT(6) +#define MT_RXWI_RATE_BW GENMASK(8, 7) +#define MT_RXWI_RATE_SGI BIT(9) +#define MT_RXWI_RATE_STBC BIT(10) +#define MT_RXWI_RATE_LDPC_EXSYM BIT(11) +#define MT_RXWI_RATE_PHY GENMASK(15, 13) + +#define MT_RATE_INDEX_VHT_IDX GENMASK(3, 0) +#define MT_RATE_INDEX_VHT_NSS GENMASK(5, 4) + +#define MT_TX_PWR_ADJ GENMASK(3, 0) + +#define MT_TXWI_FLAGS_FRAG BIT(0) +#define MT_TXWI_FLAGS_MMPS BIT(1) +#define MT_TXWI_FLAGS_CFACK BIT(2) +#define MT_TXWI_FLAGS_TS BIT(3) +#define MT_TXWI_FLAGS_AMPDU BIT(4) +#define MT_TXWI_FLAGS_MPDU_DENSITY GENMASK(7, 5) +#define MT_TXWI_FLAGS_TXOP GENMASK(9, 8) +#define MT_TXWI_FLAGS_NDPS BIT(10) +#define MT_TXWI_FLAGS_RTSBWSIG BIT(11) +#define MT_TXWI_FLAGS_NDP_BW GENMASK(13, 12) +#define MT_TXWI_FLAGS_SOUND BIT(14) +#define MT_TXWI_FLAGS_TX_RATE_LUT BIT(15) + +#define MT_TXWI_ACK_CTL_REQ BIT(0) +#define MT_TXWI_ACK_CTL_NSEQ BIT(1) +#define MT_TXWI_ACK_CTL_BA_WINDOW GENMASK(7, 2) + +#define MT_EE_ANTENNA_DUAL BIT(15) + +#define MT_EE_NIC_CONF_0_RX_PATH GENMASK(3, 0) +#define MT_EE_NIC_CONF_0_TX_PATH GENMASK(7, 4) +#define MT_EE_NIC_CONF_0_PA_TYPE GENMASK(9, 8) +#define MT_EE_NIC_CONF_0_PA_INT_2G BIT(8) +#define MT_EE_NIC_CONF_0_PA_INT_5G BIT(9) +#define MT_EE_NIC_CONF_0_PA_IO_CURRENT BIT(10) +#define MT_EE_NIC_CONF_0_BOARD_TYPE GENMASK(13, 12) + +#define MT_EE_NIC_CONF_1_HW_RF_CTRL BIT(0) +#define MT_EE_NIC_CONF_1_TEMP_TX_ALC BIT(1) +#define MT_EE_NIC_CONF_1_LNA_EXT_2G BIT(2) +#define MT_EE_NIC_CONF_1_LNA_EXT_5G BIT(3) +#define MT_EE_NIC_CONF_1_TX_ALC_EN BIT(13) + +#define MT_EE_NIC_CONF_2_ANT_OPT BIT(3) +#define MT_EE_NIC_CONF_2_ANT_DIV BIT(4) +#define MT_EE_NIC_CONF_2_XTAL_OPTION GENMASK(10, 9) + +#define MT_VEND_TYPE_CFG BIT(30) + +#define MT_CMD_HDR_LEN 4 + +enum mt76_vendor_req { + MT_VEND_DEV_MODE = 0x01, + MT_VEND_WRITE = 0x02, + MT_VEND_POWER_ON = 0x04, + MT_VEND_MULTI_WRITE = 0x06, + MT_VEND_MULTI_READ = 0x07, + MT_VEND_READ_EEPROM = 0x09, + MT_VEND_WRITE_FCE = 0x42, + MT_VEND_WRITE_CFG = 0x46, + MT_VEND_READ_CFG = 0x47, + MT_VEND_READ_EXT = 0x63, + MT_VEND_WRITE_EXT = 0x66, + MT_VEND_FEATURE_SET = 0x91, +}; + +enum mt76_dma_msg_port { + MT_WLAN_PORT, + MT_CPU_RX_PORT, + MT_CPU_TX_PORT, + MT_HOST_PORT, + MT_VIRTUAL_CPU_RX_PORT, + MT_VIRTUAL_CPU_TX_PORT, + MT_DISCARD, +}; + +enum mt76_mcu_cmd { + MT_CMD_FUN_SET_OP = 1, + MT_CMD_LOAD_CR = 2, + MT_CMD_INIT_GAIN_OP = 3, + MT_CMD_DYNC_VGA_OP = 6, + MT_CMD_TDLS_CH_SW = 7, + MT_CMD_BURST_WRITE = 8, + MT_CMD_READ_MODIFY_WRITE = 9, + MT_CMD_RANDOM_READ = 10, + MT_CMD_BURST_READ = 11, + MT_CMD_RANDOM_WRITE = 12, + MT_CMD_LED_MODE_OP = 16, + MT_CMD_POWER_SAVING_OP = 20, + MT_CMD_WOW_CONFIG = 21, + MT_CMD_WOW_QUERY = 22, + MT_CMD_WOW_FEATURE = 24, + MT_CMD_CARRIER_DETECT_OP = 28, + MT_CMD_RADOR_DETECT_OP = 29, + MT_CMD_SWITCH_CHANNEL_OP = 30, + MT_CMD_CALIBRATION_OP = 31, + MT_CMD_BEACON_OP = 32, + MT_CMD_ANTENNA_OP = 33, +}; + +enum mt76_mcu_function { + MT_Q_SELECT = 1, + MT_BW_SETTING = 2, + MT_USB2_SW_DISCONNECT = 2, + MT_USB3_SW_DISCONNECT = 3, + MT_LOG_FW_DEBUG_MSG = 4, + MT_GET_FW_VERSION = 5, +}; + +enum mt76_mcu_cr_mode { + MT_RF_CR, + MT_BBP_CR, + MT_RF_BBP_CR, + MT_HL_TEMP_CR_UPDATE, +}; + +enum mt76_mcu_power_mode { + MT_RADIO_OFF = 0x30, + MT_RADIO_ON = 0x31, + MT_RADIO_OFF_AUTO_WAKEUP = 0x32, + MT_RADIO_OFF_ADVANCE = 0x33, + MT_RADIO_ON_ADVANCE = 0x34, +}; + +enum mt76_mcu_calibration { + MT_MCU_CAL_R = 1, + MT_MCU_CAL_TEMP_SENSOR, + MT_MCU_CAL_RXDCOC, + MT_MCU_CAL_RC, + MT_MCU_CAL_SX_LOGEN, + MT_MCU_CAL_LC, + MT_MCU_CAL_TX_LOFT, + MT_MCU_CAL_TXIQ, + MT_MCU_CAL_TSSI, + MT_MCU_CAL_TSSI_COMP, + MT_MCU_CAL_DPD, + MT_MCU_CAL_RXIQC_FI, + MT_MCU_CAL_RXIQC_FD, + MT_MCU_CAL_PWRON, + MT_MCU_CAL_TX_SHAPING, +}; + +enum mt76_eeprom_mode { + MT_EE_READ, + MT_EE_PHYSICAL_READ, +}; + +enum mt76_eeprom_field { + MT_EE_CHIP_ID = 0x0000, + MT_EE_VERSION = 0x0002, + MT_EE_MAC_ADDR = 0x0004, + MT_EE_PCI_ID = 0x000a, + MT_EE_ANTENNA = 0x0022, + MT_EE_CFG1_INIT = 0x0024, + MT_EE_NIC_CONF_0 = 0x0034, + MT_EE_NIC_CONF_1 = 0x0036, + MT_EE_COUNTRY_REGION_5GHZ = 0x0038, + MT_EE_COUNTRY_REGION_2GHZ = 0x0039, + MT_EE_FREQ_OFFSET = 0x003a, + MT_EE_NIC_CONF_2 = 0x0042, + + MT_EE_XTAL_TRIM_1 = 0x003a, + MT_EE_XTAL_TRIM_2 = 0x009e, + + MT_EE_LNA_GAIN = 0x0044, + MT_EE_RSSI_OFFSET_2G_0 = 0x0046, + MT_EE_RSSI_OFFSET_2G_1 = 0x0048, + MT_EE_LNA_GAIN_5GHZ_1 = 0x0049, + MT_EE_RSSI_OFFSET_5G_0 = 0x004a, + MT_EE_RSSI_OFFSET_5G_1 = 0x004c, + MT_EE_LNA_GAIN_5GHZ_2 = 0x004d, + + MT_EE_TX_POWER_DELTA_BW40 = 0x0050, + MT_EE_TX_POWER_DELTA_BW80 = 0x0052, + + MT_EE_TX_POWER_EXT_PA_5G = 0x0054, + + MT_EE_TX_POWER_0_START_2G = 0x0056, + MT_EE_TX_POWER_1_START_2G = 0x005c, + +#define MT_TX_POWER_GROUP_SIZE_5G 5 +#define MT_TX_POWER_GROUPS_5G 6 + MT_EE_TX_POWER_0_START_5G = 0x0062, + MT_EE_TSSI_SLOPE_2G = 0x006e, + + MT_EE_TX_POWER_0_GRP3_TX_POWER_DELTA = 0x0074, + MT_EE_TX_POWER_0_GRP4_TSSI_SLOPE = 0x0076, + + MT_EE_TX_POWER_1_START_5G = 0x0080, + + MT_EE_TX_POWER_CCK = 0x00a0, + MT_EE_TX_POWER_OFDM_2G_6M = 0x00a2, + MT_EE_TX_POWER_OFDM_2G_24M = 0x00a4, + MT_EE_TX_POWER_OFDM_5G_6M = 0x00b2, + MT_EE_TX_POWER_OFDM_5G_24M = 0x00b4, + MT_EE_TX_POWER_HT_MCS0 = 0x00a6, + MT_EE_TX_POWER_HT_MCS4 = 0x00a8, + MT_EE_TX_POWER_HT_MCS8 = 0x00aa, + MT_EE_TX_POWER_HT_MCS12 = 0x00ac, + MT_EE_TX_POWER_VHT_MCS0 = 0x00ba, + MT_EE_TX_POWER_VHT_MCS4 = 0x00bc, + MT_EE_TX_POWER_VHT_MCS8 = 0x00be, + + MT_EE_2G_TARGET_POWER = 0x00d0, + MT_EE_TEMP_OFFSET = 0x00d1, + MT_EE_5G_TARGET_POWER = 0x00d2, + MT_EE_TSSI_BOUND1 = 0x00d4, + MT_EE_TSSI_BOUND2 = 0x00d6, + MT_EE_TSSI_BOUND3 = 0x00d8, + MT_EE_TSSI_BOUND4 = 0x00da, + MT_EE_FREQ_OFFSET_COMPENSATION = 0x00db, + MT_EE_TSSI_BOUND5 = 0x00dc, + MT_EE_TX_POWER_BYRATE_BASE = 0x00de, + + MT_EE_TSSI_SLOPE_5G = 0x00f0, + MT_EE_RF_TEMP_COMP_SLOPE_5G = 0x00f2, + MT_EE_RF_TEMP_COMP_SLOPE_2G = 0x00f4, + + MT_EE_RF_2G_TSSI_OFF_TXPOWER = 0x00f6, + MT_EE_RF_2G_RX_HIGH_GAIN = 0x00f8, + MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN = 0x00fa, + MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN = 0x00fc, + MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN = 0x00fe, + + MT_EE_BT_RCAL_RESULT = 0x0138, + MT_EE_BT_VCDL_CALIBRATION = 0x013c, + MT_EE_BT_PMUCFG = 0x013e, + + MT_EE_USAGE_MAP_START = 0x01e0, + MT_EE_USAGE_MAP_END = 0x01fc, +}; + +enum mt76_phy_type { + MT_PHY_TYPE_CCK, + MT_PHY_TYPE_OFDM, + MT_PHY_TYPE_HT, + MT_PHY_TYPE_HT_GF, + MT_PHY_TYPE_VHT, + MT_PHY_TYPE_HE_SU = 8, + MT_PHY_TYPE_HE_EXT_SU, + MT_PHY_TYPE_HE_TB, + MT_PHY_TYPE_HE_MU, +}; + +enum mt76_phy_bandwidth { + MT_PHY_BW_20, + MT_PHY_BW_40, + MT_PHY_BW_80, +}; + +enum mt76_cal_channel_group { + MT_CH_5G_JAPAN, + MT_CH_5G_UNII_1, + MT_CH_5G_UNII_2, + MT_CH_5G_UNII_2E_1, + MT_CH_5G_UNII_2E_2, + MT_CH_5G_UNII_3, +}; + +enum mt76_qsel { + MT_QSEL_MGMT, + MT_QSEL_HCCA, + MT_QSEL_EDCA, + MT_QSEL_EDCA_2, +}; + +struct mt76_fw_header { + __le32 ilm_len; + __le32 dlm_len; + __le16 build_ver; + __le16 fw_ver; + u8 pad[4]; + char build_time[16]; +} __packed; + +struct mt76_rxwi { + __le32 rxinfo; + __le32 ctl; + __le16 tid_sn; + __le16 rate; + u8 rssi[4]; + __le32 bbp_rxinfo[4]; +} __packed; + +struct mt76_txwi { + __le16 flags; + __le16 rate; + u8 ack_ctl; + u8 wcid; + __le16 len_ctl; + __le32 iv; + __le32 eiv; + u8 aid; + u8 txstream; + u8 ctl2; + u8 pktid; +} __packed; diff --git a/drivers/hid/xone/transport/wired.c b/drivers/hid/xone/transport/wired.c new file mode 100644 index 000000000..5b176b7c0 --- /dev/null +++ b/drivers/hid/xone/transport/wired.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Severin von Wnuck + */ + +#include +#include +#include + +#include "../bus/bus.h" + +#define XONE_WIRED_INTF_DATA 0 +#define XONE_WIRED_INTF_AUDIO 1 + +#define XONE_WIRED_NUM_DATA_URBS 8 +#define XONE_WIRED_NUM_AUDIO_URBS 12 +#define XONE_WIRED_NUM_AUDIO_PKTS 8 + +#define XONE_WIRED_LEN_DATA_PKT 64 + +#define XONE_WIRED_VENDOR(vendor) \ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | \ + USB_DEVICE_ID_MATCH_INT_INFO | \ + USB_DEVICE_ID_MATCH_INT_NUMBER, \ + .idVendor = vendor, \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ + .bInterfaceSubClass = 0x47, \ + .bInterfaceProtocol = 0xd0, \ + .bInterfaceNumber = XONE_WIRED_INTF_DATA, + +struct xone_wired { + struct usb_device *udev; + + struct xone_wired_port { + struct usb_interface *intf; + + struct usb_endpoint_descriptor *ep_in; + struct usb_endpoint_descriptor *ep_out; + + struct urb *urb_in; + struct usb_anchor urbs_out_idle; + struct usb_anchor urbs_out_busy; + + int buffer_length_out; + } data_port, audio_port; + + struct gip_adapter *adapter; +}; + +static void xone_wired_complete_data_in(struct urb *urb) +{ + struct xone_wired *wired = urb->context; + struct device *dev = &wired->data_port.intf->dev; + int err; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + return; + default: + goto resubmit; + } + + if (!urb->actual_length) + goto resubmit; + + err = gip_process_buffer(wired->adapter, urb->transfer_buffer, + urb->actual_length); + if (err) + dev_err(dev, "%s: process failed: %d\n", __func__, err); + +resubmit: + /* can fail during USB device removal */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dev_dbg(dev, "%s: submit failed: %d\n", __func__, err); +} + +static void xone_wired_complete_audio_in(struct urb *urb) +{ + struct xone_wired *wired = urb->context; + struct device *dev = &wired->audio_port.intf->dev; + struct usb_iso_packet_descriptor *desc; + int i, err; + + if (urb->status) + return; + + for (i = 0; i < urb->number_of_packets; i++) { + desc = &urb->iso_frame_desc[i]; + + /* device reset after system sleep can cause xHCI errors */ + if (desc->status == -EPROTO) { + dev_warn_once(dev, "%s: protocol error\n", __func__); + break; + } + + if (!desc->actual_length) + continue; + + err = gip_process_buffer(wired->adapter, + urb->transfer_buffer + desc->offset, + desc->actual_length); + if (err) + dev_err(dev, "%s: process failed: %d\n", __func__, err); + } + + /* can fail during USB device removal */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dev_dbg(dev, "%s: submit failed: %d\n", __func__, err); +} + +static void xone_wired_complete_out(struct urb *urb) +{ + struct xone_wired_port *port = urb->context; + + usb_anchor_urb(urb, &port->urbs_out_idle); +} + +static int xone_wired_init_data_in(struct xone_wired *wired) +{ + struct xone_wired_port *port = &wired->data_port; + struct urb *urb; + void *buf; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + port->urb_in = urb; + + buf = usb_alloc_coherent(wired->udev, XONE_WIRED_LEN_DATA_PKT, + GFP_KERNEL, &urb->transfer_dma); + if (!buf) + return -ENOMEM; + + usb_fill_int_urb(urb, wired->udev, + usb_rcvintpipe(wired->udev, + port->ep_in->bEndpointAddress), + buf, XONE_WIRED_LEN_DATA_PKT, + xone_wired_complete_data_in, wired, + port->ep_in->bInterval); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + return usb_submit_urb(urb, GFP_KERNEL); +} + +static int xone_wired_init_data_out(struct xone_wired *wired) +{ + struct xone_wired_port *port = &wired->data_port; + struct urb *urb; + void *buf; + int i; + + port->buffer_length_out = XONE_WIRED_LEN_DATA_PKT; + + for (i = 0; i < XONE_WIRED_NUM_DATA_URBS; i++) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + usb_anchor_urb(urb, &port->urbs_out_idle); + usb_free_urb(urb); + + buf = usb_alloc_coherent(wired->udev, XONE_WIRED_LEN_DATA_PKT, + GFP_KERNEL, &urb->transfer_dma); + if (!buf) + return -ENOMEM; + + usb_fill_int_urb(urb, wired->udev, + usb_sndintpipe(wired->udev, + port->ep_out->bEndpointAddress), + buf, XONE_WIRED_LEN_DATA_PKT, + xone_wired_complete_out, port, + port->ep_out->bInterval); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + + return 0; +} + +static void xone_wired_free_urbs(struct xone_wired_port *port) +{ + struct urb *urb = port->urb_in; + + if (urb) { + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + port->urb_in = NULL; + } + + while ((urb = usb_get_from_anchor(&port->urbs_out_idle))) { + usb_free_coherent(urb->dev, port->buffer_length_out, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + } +} + +static int xone_wired_get_buffer(struct gip_adapter *adap, + struct gip_adapter_buffer *buf) +{ + struct xone_wired *wired = dev_get_drvdata(&adap->dev); + struct xone_wired_port *port; + struct urb *urb; + + if (buf->type == GIP_BUF_DATA) + port = &wired->data_port; + else if (buf->type == GIP_BUF_AUDIO) + port = &wired->audio_port; + else + return -EINVAL; + + urb = usb_get_from_anchor(&port->urbs_out_idle); + if (!urb) + return -ENOSPC; + + buf->context = urb; + buf->data = urb->transfer_buffer; + buf->length = port->buffer_length_out; + + return 0; +} + +static int xone_wired_submit_buffer(struct gip_adapter *adap, + struct gip_adapter_buffer *buf) +{ + struct xone_wired *wired = dev_get_drvdata(&adap->dev); + struct xone_wired_port *port; + struct urb *urb = buf->context; + int err; + + if (buf->type == GIP_BUF_DATA) + port = &wired->data_port; + else if (buf->type == GIP_BUF_AUDIO) + port = &wired->audio_port; + else + return -EINVAL; + + urb->transfer_buffer_length = buf->length; + usb_anchor_urb(urb, &port->urbs_out_busy); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + usb_unanchor_urb(urb); + usb_anchor_urb(urb, &port->urbs_out_idle); + } + + usb_free_urb(urb); + + return err; +} + +static int xone_wired_enable_audio(struct gip_adapter *adap) +{ + struct xone_wired *wired = dev_get_drvdata(&adap->dev); + struct xone_wired_port *port = &wired->audio_port; + + if (port->intf->cur_altsetting->desc.bAlternateSetting == 1) + return -EALREADY; + + return usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 1); +} + +static int xone_wired_init_audio_in(struct gip_adapter *adap) +{ + struct xone_wired *wired = dev_get_drvdata(&adap->dev); + struct xone_wired_port *port = &wired->audio_port; + struct urb *urb; + void *buf; + int len, i; + + urb = usb_alloc_urb(XONE_WIRED_NUM_AUDIO_PKTS, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + port->urb_in = urb; + + len = usb_endpoint_maxp(port->ep_in); + buf = usb_alloc_coherent(wired->udev, len * XONE_WIRED_NUM_AUDIO_PKTS, + GFP_KERNEL, &urb->transfer_dma); + if (!buf) + return -ENOMEM; + + urb->dev = wired->udev; + urb->pipe = usb_rcvisocpipe(wired->udev, port->ep_in->bEndpointAddress); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = buf; + urb->transfer_buffer_length = len * XONE_WIRED_NUM_AUDIO_PKTS; + urb->number_of_packets = XONE_WIRED_NUM_AUDIO_PKTS; + urb->interval = port->ep_in->bInterval; + urb->context = wired; + urb->complete = xone_wired_complete_audio_in; + + for (i = 0; i < XONE_WIRED_NUM_AUDIO_PKTS; i++) { + urb->iso_frame_desc[i].offset = i * len; + urb->iso_frame_desc[i].length = len; + } + + return usb_submit_urb(urb, GFP_KERNEL); +} + +static int xone_wired_init_audio_out(struct gip_adapter *adap, int pkt_len) +{ + struct xone_wired *wired = dev_get_drvdata(&adap->dev); + struct xone_wired_port *port = &wired->audio_port; + struct urb *urb; + void *buf; + int i, j; + + port->buffer_length_out = pkt_len * XONE_WIRED_NUM_AUDIO_PKTS; + + for (i = 0; i < XONE_WIRED_NUM_AUDIO_URBS; i++) { + urb = usb_alloc_urb(XONE_WIRED_NUM_AUDIO_PKTS, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + usb_anchor_urb(urb, &port->urbs_out_idle); + usb_free_urb(urb); + + buf = usb_alloc_coherent(wired->udev, port->buffer_length_out, + GFP_KERNEL, &urb->transfer_dma); + if (!buf) + return -ENOMEM; + + urb->dev = wired->udev; + urb->pipe = usb_sndisocpipe(wired->udev, + port->ep_out->bEndpointAddress); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = buf; + urb->transfer_buffer_length = port->buffer_length_out; + urb->number_of_packets = XONE_WIRED_NUM_AUDIO_PKTS; + urb->interval = port->ep_out->bInterval; + urb->context = port; + urb->complete = xone_wired_complete_out; + + for (j = 0; j < XONE_WIRED_NUM_AUDIO_PKTS; j++) { + urb->iso_frame_desc[j].offset = j * pkt_len; + urb->iso_frame_desc[j].length = pkt_len; + } + } + + return 0; +} + +static int xone_wired_disable_audio(struct gip_adapter *adap) +{ + struct xone_wired *wired = dev_get_drvdata(&adap->dev); + struct xone_wired_port *port = &wired->audio_port; + + if (!port->intf->cur_altsetting->desc.bAlternateSetting) + return -EALREADY; + + usb_kill_urb(port->urb_in); + usb_kill_anchored_urbs(&port->urbs_out_busy); + xone_wired_free_urbs(port); + + return usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 0); +} + +static struct gip_adapter_ops xone_wired_adapter_ops = { + .get_buffer = xone_wired_get_buffer, + .submit_buffer = xone_wired_submit_buffer, + .enable_audio = xone_wired_enable_audio, + .init_audio_in = xone_wired_init_audio_in, + .init_audio_out = xone_wired_init_audio_out, + .disable_audio = xone_wired_disable_audio, +}; + +static struct usb_driver xone_wired_driver; + +static int xone_wired_find_isoc_endpoints(struct usb_host_interface *alt, + struct usb_endpoint_descriptor **in, + struct usb_endpoint_descriptor **out) +{ + struct usb_endpoint_descriptor *ep; + int i; + + for (i = 0; i < alt->desc.bNumEndpoints; i++) { + ep = &alt->endpoint[i].desc; + if (usb_endpoint_is_isoc_in(ep)) + *in = ep; + else if (usb_endpoint_is_isoc_out(ep)) + *out = ep; + + if (*in && *out) + return 0; + } + + return -ENXIO; +} + +static int xone_wired_init_data_port(struct xone_wired *wired, + struct usb_interface *intf) +{ + struct xone_wired_port *port = &wired->data_port; + int err; + + err = usb_find_common_endpoints(intf->cur_altsetting, NULL, NULL, + &port->ep_in, &port->ep_out); + if (err) + return err; + + port->intf = intf; + init_usb_anchor(&port->urbs_out_idle); + init_usb_anchor(&port->urbs_out_busy); + + return 0; +} + +static int xone_wired_init_audio_port(struct xone_wired *wired) +{ + struct xone_wired_port *port = &wired->audio_port; + struct usb_interface *intf; + struct usb_host_interface *alt; + int err; + + intf = usb_ifnum_to_if(wired->udev, XONE_WIRED_INTF_AUDIO); + if (!intf) + return -ENODEV; + + alt = usb_altnum_to_altsetting(intf, 1); + if (!alt) + return -ENODEV; + + err = usb_driver_claim_interface(&xone_wired_driver, intf, NULL); + if (err) + return err; + + /* disable the audio interface */ + /* mandatory for certain third party devices */ + err = usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 0); + if (err) + return err; + + err = xone_wired_find_isoc_endpoints(alt, &port->ep_in, &port->ep_out); + if (err) + return err; + + port->intf = intf; + init_usb_anchor(&port->urbs_out_idle); + init_usb_anchor(&port->urbs_out_busy); + + return 0; +} + +static int xone_wired_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct xone_wired *wired; + int err; + + wired = devm_kzalloc(&intf->dev, sizeof(*wired), GFP_KERNEL); + if (!wired) + return -ENOMEM; + + wired->udev = interface_to_usbdev(intf); + + /* newer devices require a reset after system sleep */ + usb_reset_device(wired->udev); + + /* enable USB remote wakeup feature */ + device_wakeup_enable(&wired->udev->dev); + + err = xone_wired_init_data_port(wired, intf); + if (err) + return err; + + err = xone_wired_init_audio_port(wired); + if (err) + return err; + + wired->adapter = gip_create_adapter(&intf->dev, &xone_wired_adapter_ops, + XONE_WIRED_NUM_AUDIO_PKTS); + if (IS_ERR(wired->adapter)) + return PTR_ERR(wired->adapter); + + dev_set_drvdata(&wired->adapter->dev, wired); + + err = xone_wired_init_data_out(wired); + if (err) + goto err_free_urbs; + + err = xone_wired_init_data_in(wired); + if (err) + goto err_free_urbs; + + usb_set_intfdata(intf, wired); + + return 0; + +err_free_urbs: + xone_wired_free_urbs(&wired->data_port); + gip_destroy_adapter(wired->adapter); + + return err; +} + +static void xone_wired_disconnect(struct usb_interface *intf) +{ + struct xone_wired *wired = usb_get_intfdata(intf); + + /* ignore audio interface unbind */ + if (!wired) + return; + + usb_kill_urb(wired->data_port.urb_in); + usb_kill_urb(wired->audio_port.urb_in); + + /* also disables the audio interface */ + gip_destroy_adapter(wired->adapter); + + usb_kill_anchored_urbs(&wired->data_port.urbs_out_busy); + xone_wired_free_urbs(&wired->data_port); + + usb_set_intfdata(intf, NULL); +} + +static const struct usb_device_id xone_wired_id_table[] = { + { XONE_WIRED_VENDOR(0x045e) }, /* Microsoft */ + { XONE_WIRED_VENDOR(0x0738) }, /* Mad Catz */ + { XONE_WIRED_VENDOR(0x0e6f) }, /* PDP */ + { XONE_WIRED_VENDOR(0x0f0d) }, /* Hori */ + { XONE_WIRED_VENDOR(0x1532) }, /* Razer */ + { XONE_WIRED_VENDOR(0x24c6) }, /* PowerA */ + { XONE_WIRED_VENDOR(0x20d6) }, /* BDA */ + { XONE_WIRED_VENDOR(0x044f) }, /* Thrustmaster */ + { XONE_WIRED_VENDOR(0x10f5) }, /* Turtle Beach */ + { XONE_WIRED_VENDOR(0x2e24) }, /* Hyperkin */ + { XONE_WIRED_VENDOR(0x3285) }, /* Nacon */ + { XONE_WIRED_VENDOR(0x2dc8) }, /* 8BitDo */ + { XONE_WIRED_VENDOR(0x2e95) }, /* SCUF */ + { }, +}; + +static struct usb_driver xone_wired_driver = { + .name = "xone-wired", + .probe = xone_wired_probe, + .disconnect = xone_wired_disconnect, + .id_table = xone_wired_id_table, +}; + +module_usb_driver(xone_wired_driver); + +MODULE_DEVICE_TABLE(usb, xone_wired_id_table); +MODULE_AUTHOR("Severin von Wnuck "); +MODULE_DESCRIPTION("xone wired driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL");