diff --git a/hid-xpadneo/src/hid-xpadneo.c b/hid-xpadneo/src/hid-xpadneo.c index ba20eac9..2d7ea65d 100644 --- a/hid-xpadneo/src/hid-xpadneo.c +++ b/hid-xpadneo/src/hid-xpadneo.c @@ -7,12 +7,13 @@ * Copyright (c) 2017 Florian Dollinger */ +#include #include +#include +#include #include -#include /* ff_memless(), ... */ -#include /* MODULE_*, module_*, ... */ -#include /* kzalloc(), kfree(), ... */ -#include /* mdelay(), ... */ +#include +#include #include "hid-ids.h" /* VENDOR_ID... */ #define DEBUG @@ -81,6 +82,9 @@ MODULE_PARM_DESC(quriks, static DEFINE_IDA(xpadneo_device_id_allocator); +#define XPADNEO_RUMBLE_THROTTLE_DELAY (10L * HZ / 1000) +#define XPADNEO_RUMBLE_THROTTLE_JIFFIES (jiffies + XPADNEO_RUMBLE_THROTTLE_DELAY) + enum { FF_RUMBLE_NONE = 0x00, FF_RUMBLE_WEAK = 0x01, @@ -123,6 +127,7 @@ struct xpadneo_devdata { u16 quirks; /* battery information */ + spinlock_t battery_lock; struct power_supply_desc psy_desc; u8 battery_report_id; u8 battery_flags; @@ -133,7 +138,10 @@ struct xpadneo_devdata { s32 last_abs_rz; /* buffer for ff_worker */ - struct work_struct ff_worker; + spinlock_t ff_lock; + struct delayed_work ff_worker; + unsigned long ff_throttle_until; + bool ff_scheduled; struct ff_data ff; struct ff_data ff_shadow; void *output_report_dmabuf; @@ -202,16 +210,19 @@ static const struct usage_map xpadneo_usage_maps[] = { static void xpadneo_ff_worker(struct work_struct *work) { - struct xpadneo_devdata *xdata = container_of(work, struct xpadneo_devdata, ff_worker); + struct xpadneo_devdata *xdata = + container_of(to_delayed_work(work), struct xpadneo_devdata, ff_worker); struct hid_device *hdev = xdata->hdev; struct ff_report *r = xdata->output_report_dmabuf; int ret; + unsigned long flags; memset(r, 0, sizeof(*r)); r->report_id = XPADNEO_XB1S_FF_REPORT; r->ff.enable = FF_RUMBLE_ALL; - /* if pulse is not supported, we do not have to care about explicitly + /* + * if pulse is not supported, we do not have to care about explicitly * stopping the effect, the kernel will do this for us as part of its * ff-memless emulation */ @@ -225,6 +236,11 @@ static void xpadneo_ff_worker(struct work_struct *work) r->ff.loop_count = U8_MAX; } + spin_lock_irqsave(&xdata->ff_lock, flags); + + /* let our scheduler know we've been called */ + xdata->ff_scheduled = false; + if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_TRIGGER_RUMBLE)) { /* do not send these bits if not supported */ r->ff.enable &= ~FF_RUMBLE_TRIGGERS; @@ -249,12 +265,22 @@ static void xpadneo_ff_worker(struct work_struct *work) r->ff.enable &= ~FF_RUMBLE_RIGHT; /* do not send a report if nothing changed */ - if (unlikely(r->ff.enable == FF_RUMBLE_NONE)) + if (unlikely(r->ff.enable == FF_RUMBLE_NONE)) { + spin_unlock_irqrestore(&xdata->ff_lock, flags); return; + } /* shadow our current rumble values for the next cycle */ memcpy(&xdata->ff_shadow, &xdata->ff, sizeof(xdata->ff)); + /* + * throttle next command submission, the firmware doesn't like us to + * send rumble data any faster + */ + xdata->ff_throttle_until = XPADNEO_RUMBLE_THROTTLE_JIFFIES; + + spin_unlock_irqrestore(&xdata->ff_lock, flags); + /* do not send these bits if not supported */ if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK)) { r->ff.enable = 0; @@ -275,6 +301,8 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect * QUARTER = DIRECTION_LEFT, }; + unsigned long flags, ff_run_at, ff_throttle_until; + long delay_work; int fraction_TL, fraction_TR, fraction_MAIN; s32 weak, strong, direction, max_damped, max_unscaled; @@ -355,10 +383,6 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect * break; } - /* calculate the physical magnitudes, scale from 16 bit to 0..100 */ - xdata->ff.magnitude_strong = (u8)((strong * fraction_MAIN) / U16_MAX); - xdata->ff.magnitude_weak = (u8)((weak * fraction_MAIN) / U16_MAX); - /* * we want to keep the rumbling at the triggers at the maximum * of the weak and strong main rumble @@ -374,12 +398,50 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect * else max_damped = max_unscaled; + spin_lock_irqsave(&xdata->ff_lock, flags); + + /* calculate the physical magnitudes, scale from 16 bit to 0..100 */ + xdata->ff.magnitude_strong = (u8)((strong * fraction_MAIN) / U16_MAX); + xdata->ff.magnitude_weak = (u8)((weak * fraction_MAIN) / U16_MAX); + /* calculate the physical magnitudes, scale from 16 bit to 0..100 */ xdata->ff.magnitude_left = (u8)((max_damped * fraction_TL) / U16_MAX); xdata->ff.magnitude_right = (u8)((max_damped * fraction_TR) / U16_MAX); + /* synchronize: is our worker still scheduled? */ + if (xdata->ff_scheduled) { + /* the worker is still guarding rumble programming */ + hid_notice_once(hdev, "throttling rumble reprogramming\n"); + goto unlock_and_return; + } + + /* we want to run now but may be throttled */ + ff_run_at = jiffies; + ff_throttle_until = xdata->ff_throttle_until; + if (time_before(ff_run_at, ff_throttle_until)) { + /* last rumble was recently executed */ + delay_work = (long)ff_throttle_until - (long)ff_run_at; + } else { + /* the firmware is ready */ + delay_work = 0; + } + + /* + * sanitize: If 0 > delay > 1000ms, something is weird: this + * may happen if the delay between two rumble requests is + * several weeks long + */ + delay_work = min((long)HZ, delay_work); + delay_work = max(0L, delay_work); + /* schedule writing a rumble report to the controller */ - schedule_work(&xdata->ff_worker); + if (schedule_delayed_work(&xdata->ff_worker, delay_work)) + xdata->ff_scheduled = true; + else + hid_err(hdev, "lost rumble packet\n"); + +unlock_and_return: + spin_unlock_irqrestore(&xdata->ff_lock, flags); return 0; } @@ -474,7 +536,7 @@ static int xpadneo_init_ff(struct hid_device *hdev) struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); struct input_dev *idev = xdata->idev; - INIT_WORK(&xdata->ff_worker, xpadneo_ff_worker); + INIT_DELAYED_WORK(&xdata->ff_worker, xpadneo_ff_worker); xdata->output_report_dmabuf = devm_kzalloc(&hdev->dev, sizeof(struct ff_report), GFP_KERNEL); if (xdata->output_report_dmabuf == NULL) @@ -486,6 +548,9 @@ static int xpadneo_init_ff(struct hid_device *hdev) if (param_ff_connect_notify) xpadneo_welcome_rumble(hdev); + /* initialize our rumble command throttle */ + xdata->ff_throttle_until = XPADNEO_RUMBLE_THROTTLE_JIFFIES; + input_set_capability(idev, EV_FF, FF_RUMBLE); return input_ff_create_memless(idev, NULL, xpadneo_ff_play); } @@ -501,10 +566,9 @@ static int xpadneo_get_battery_property(struct power_supply *psy, union power_supply_propval *val) { struct xpadneo_devdata *xdata = power_supply_get_drvdata(psy); - - u8 flags = xdata->battery_flags; - u8 level = XPADNEO_BATTERY_CAPACITY_LEVEL(flags); - u8 charging = XPADNEO_BATTERY_CHARGING(flags); + unsigned long flags; + int ret = 0; + u8 battery_flags, level, charging; static int capacity_level_map[] = { [0] = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL, @@ -516,6 +580,12 @@ static int xpadneo_get_battery_property(struct power_supply *psy, if (!xdata->battery) return -EINVAL; + spin_lock_irqsave(&xdata->battery_lock, flags); + + battery_flags = xdata->battery_flags; + level = XPADNEO_BATTERY_CAPACITY_LEVEL(battery_flags); + charging = XPADNEO_BATTERY_CHARGING(battery_flags); + switch (property) { case POWER_SUPPLY_PROP_CAPACITY: val->intval = xdata->battery_capacity; @@ -525,8 +595,8 @@ static int xpadneo_get_battery_property(struct power_supply *psy, if (level >= ARRAY_SIZE(capacity_level_map) || xdata->psy_desc.type == POWER_SUPPLY_TYPE_UNKNOWN) val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; - else if (XPADNEO_BATTERY_MODE(flags) - || XPADNEO_BATTERY_CHARGING(flags)) + else if (XPADNEO_BATTERY_MODE(battery_flags) + || XPADNEO_BATTERY_CHARGING(battery_flags)) val->intval = capacity_level_map[level]; else val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; @@ -537,11 +607,11 @@ static int xpadneo_get_battery_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_ONLINE: - val->intval = XPADNEO_BATTERY_ONLINE(flags); + val->intval = XPADNEO_BATTERY_ONLINE(battery_flags); break; case POWER_SUPPLY_PROP_PRESENT: - val->intval = XPADNEO_BATTERY_PRESENT(flags); + val->intval = XPADNEO_BATTERY_PRESENT(battery_flags); break; case POWER_SUPPLY_PROP_SCOPE: @@ -551,21 +621,24 @@ static int xpadneo_get_battery_property(struct power_supply *psy, case POWER_SUPPLY_PROP_STATUS: if (xdata->psy_desc.type == POWER_SUPPLY_TYPE_UNKNOWN) val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - else if (!charging && XPADNEO_BATTERY_CAPACITY_LEVEL(flags) == 3) + else if (!charging && XPADNEO_BATTERY_CAPACITY_LEVEL(battery_flags) == 3) val->intval = POWER_SUPPLY_STATUS_FULL; else if (charging) val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (XPADNEO_BATTERY_PRESENT(flags)) + else if (XPADNEO_BATTERY_PRESENT(battery_flags)) val->intval = POWER_SUPPLY_STATUS_DISCHARGING; else val->intval = POWER_SUPPLY_STATUS_UNKNOWN; break; default: - return -EINVAL; + ret = -EINVAL; + break; } - return 0; + spin_unlock_irqrestore(&xdata->battery_lock, flags); + + return ret; } static int xpadneo_setup_battery(struct hid_device *hdev, struct hid_field *field) @@ -644,14 +717,14 @@ static int xpadneo_input_mapping(struct hid_device *hdev, struct hid_input *hi, if (usage->hid == HID_DC_BATTERYSTRENGTH) { xpadneo_setup_battery(hdev, field); - return -1; + return MAP_IGNORE; } for (i = 0; i < ARRAY_SIZE(xpadneo_usage_maps); i++) { const struct usage_map *entry = &xpadneo_usage_maps[i]; if (entry->usage == usage->hid) { - if (entry->behaviour == 1) { + if (entry->behaviour == MAP_STATIC) { hid_map_usage_clear(hi, usage, bit, max, entry->ev.event_type, entry->ev.input_code); } @@ -660,7 +733,7 @@ static int xpadneo_input_mapping(struct hid_device *hdev, struct hid_input *hi, } /* let HID handle this */ - return 0; + return MAP_AUTO; } static u8 *xpadneo_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *rsize) @@ -714,6 +787,10 @@ static u8 *xpadneo_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int static void xpadneo_update_battery(struct xpadneo_devdata *xdata, u8 value) { + unsigned long flags; + + spin_lock_irqsave(&xdata->battery_lock, flags); + xdata->battery_flags = value; switch (XPADNEO_BATTERY_MODE(value)) { case 0: @@ -745,6 +822,8 @@ static void xpadneo_update_battery(struct xpadneo_devdata *xdata, u8 value) } } + spin_unlock_irqrestore(&xdata->battery_lock, flags); + power_supply_changed(xdata->battery); } @@ -972,7 +1051,7 @@ static void xpadneo_remove(struct hid_device *hdev) hid_hw_close(hdev); - cancel_work_sync(&xdata->ff_worker); + cancel_delayed_work_sync(&xdata->ff_worker); xpadneo_release_device_id(xdata); hid_hw_stop(hdev);