mirror of
https://github.com/MiSTer-devel/Linux-Kernel_MiSTer.git
synced 2026-04-19 03:04:24 +00:00
Add xone (XBox wireless adapter) driver.
This commit is contained in:
@@ -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
|
||||
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
|
||||
7
drivers/hid/xone/Kconfig
Normal file
7
drivers/hid/xone/Kconfig
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# XOne wireless adapter
|
||||
#
|
||||
config JOYSTICK_XONE
|
||||
tristate "XOne"
|
||||
depends on INPUT && INPUT_JOYSTICK
|
||||
|
||||
8
drivers/hid/xone/Makefile
Normal file
8
drivers/hid/xone/Makefile
Normal file
@@ -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
|
||||
389
drivers/hid/xone/bus/bus.c
Normal file
389
drivers/hid/xone/bus/bus.c
Normal file
@@ -0,0 +1,389 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#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 <severinvonw@outlook.de>");
|
||||
MODULE_DESCRIPTION("xone GIP bus driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
127
drivers/hid/xone/bus/bus.h
Normal file
127
drivers/hid/xone/bus/bus.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#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);
|
||||
1377
drivers/hid/xone/bus/protocol.c
Normal file
1377
drivers/hid/xone/bus/protocol.c
Normal file
File diff suppressed because it is too large
Load Diff
115
drivers/hid/xone/bus/protocol.h
Normal file
115
drivers/hid/xone/bus/protocol.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/* 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);
|
||||
205
drivers/hid/xone/driver/chatpad.c
Normal file
205
drivers/hid/xone/driver/chatpad.c
Normal file
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/hid.h>
|
||||
|
||||
#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 <severinvonw@outlook.de>");
|
||||
MODULE_DESCRIPTION("xone GIP chatpad driver");
|
||||
MODULE_VERSION("#VERSION#");
|
||||
MODULE_LICENSE("GPL");
|
||||
225
drivers/hid/xone/driver/common.c
Normal file
225
drivers/hid/xone/driver/common.c
Normal file
@@ -0,0 +1,225 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#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 <severinvonw@outlook.de>");
|
||||
MODULE_DESCRIPTION("xone GIP common driver");
|
||||
MODULE_VERSION("#VERSION#");
|
||||
MODULE_LICENSE("GPL");
|
||||
43
drivers/hid/xone/driver/common.h
Normal file
43
drivers/hid/xone/driver/common.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#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);
|
||||
338
drivers/hid/xone/driver/gamepad.c
Normal file
338
drivers/hid/xone/driver/gamepad.c
Normal file
@@ -0,0 +1,338 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/uuid.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#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 <severinvonw@outlook.de>");
|
||||
MODULE_DESCRIPTION("xone GIP gamepad driver");
|
||||
MODULE_VERSION("#VERSION#");
|
||||
MODULE_LICENSE("GPL");
|
||||
498
drivers/hid/xone/driver/headset.c
Normal file
498
drivers/hid/xone/driver/headset.c
Normal file
@@ -0,0 +1,498 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
|
||||
#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 <severinvonw@outlook.de>");
|
||||
MODULE_DESCRIPTION("xone GIP headset driver");
|
||||
MODULE_VERSION("#VERSION#");
|
||||
MODULE_LICENSE("GPL");
|
||||
986
drivers/hid/xone/transport/dongle.c
Normal file
986
drivers/hid/xone/transport/dongle.c
Normal file
@@ -0,0 +1,986 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/ieee80211.h>
|
||||
#include <net/cfg80211.h>
|
||||
|
||||
#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 <severinvonw@outlook.de>");
|
||||
MODULE_DESCRIPTION("xone dongle driver");
|
||||
MODULE_VERSION("#VERSION#");
|
||||
MODULE_LICENSE("GPL");
|
||||
1142
drivers/hid/xone/transport/mt76.c
Normal file
1142
drivers/hid/xone/transport/mt76.c
Normal file
File diff suppressed because it is too large
Load Diff
65
drivers/hid/xone/transport/mt76.h
Normal file
65
drivers/hid/xone/transport/mt76.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#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);
|
||||
1052
drivers/hid/xone/transport/mt76_defs.h
Normal file
1052
drivers/hid/xone/transport/mt76_defs.h
Normal file
File diff suppressed because it is too large
Load Diff
553
drivers/hid/xone/transport/wired.c
Normal file
553
drivers/hid/xone/transport/wired.c
Normal file
@@ -0,0 +1,553 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
#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 <severinvonw@outlook.de>");
|
||||
MODULE_DESCRIPTION("xone wired driver");
|
||||
MODULE_VERSION("#VERSION#");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user