Skip to content

Commit

Permalink
hid-xpadneo: Use work queue for rumble effects
Browse files Browse the repository at this point in the history
This makes the rumble playback asynchronous from the playback callback.

This reworks much of the code-design and replaces the previous patch
for sending rumble reports only when there's a rumble command to do.
With this change, we now get the following kernel message immediately
on disconnects:

    xpadneo 0005:045E:02FD.0054: failed to send FF report

It also renames the motor usage from left and right to weak and strong
because no one knows if the strong motor is left or right, it also
prevents confusion with the trigger motors which are clearly left or
right.

The general design is a valuable effort on improving code quality, as
it simplifies some complex functions.

Maybe-related: atar-axis#171
Maybe-related: atar-axis#122
Maybe-affects: atar-axis#180
Signed-off-by: Kai Krakow <[email protected]>
  • Loading branch information
kakra committed May 7, 2020
1 parent 9c03e76 commit 636d2fa
Showing 1 changed file with 92 additions and 92 deletions.
184 changes: 92 additions & 92 deletions hid-xpadneo/src/hid-xpadneo.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,24 @@ static DEFINE_IDA(xpadneo_device_id_allocator);
*/

enum {
FF_ENABLE_NONE = 0x00,
FF_ENABLE_RIGHT = 0x01,
FF_ENABLE_LEFT = 0x02,
FF_ENABLE_RIGHT_TRIGGER = 0x04,
FF_ENABLE_LEFT_TRIGGER = 0x08,
FF_ENABLE_ALL = 0x0F
FF_RUMBLE_NONE = 0x00,
FF_RUMBLE_WEAK = 0x01,
FF_RUMBLE_STRONG = 0x02,
FF_RUMBLE_MAIN = FF_RUMBLE_WEAK | FF_RUMBLE_STRONG,
FF_RUMBLE_RIGHT = 0x04,
FF_RUMBLE_LEFT = 0x08,
FF_RUMBLE_TRIGGERS = FF_RUMBLE_LEFT | FF_RUMBLE_RIGHT,
FF_RUMBLE_ALL = 0x0F
};

struct ff_data {
u8 enable_actuators;
u8 magnitude_left_trigger;
u8 magnitude_right_trigger;
u8 enable;
u8 magnitude_left;
u8 magnitude_right;
u8 duration;
u8 start_delay;
u8 magnitude_strong;
u8 magnitude_weak;
u8 pulse_sustain_10ms;
u8 pulse_release_10ms;
u8 loop_count;
} __packed;

Expand Down Expand Up @@ -192,32 +194,50 @@ struct xpadneo_devdata {
/* axis states */
s32 last_abs_z;
s32 last_abs_rz;

/* buffer for ff_worker */
struct work_struct ff_worker;
struct ff_data ff;
void *output_report_dmabuf;
};

static void create_ff_pck(struct ff_report *pck, u8 id, u8 en_act,
u8 mag_lt, u8 mag_rt, u8 mag_l, u8 mag_r,
u8 start_delay)
static void xpadneo_ff_worker(struct work_struct *work)
{
struct xpadneo_devdata *xdata =
container_of(work, struct xpadneo_devdata, ff_worker);
struct hid_device *hdev = xdata->hdev;
struct ff_report *r = xdata->output_report_dmabuf;
int ret;

pck->report_id = id;
memset(r, 0, sizeof(*r));

pck->ff.enable_actuators = en_act;
pck->ff.magnitude_left_trigger = mag_lt;
pck->ff.magnitude_right_trigger = mag_rt;
pck->ff.magnitude_left = mag_l;
pck->ff.magnitude_right = mag_r;
pck->ff.duration = 0xFF;
pck->ff.start_delay = start_delay;
pck->ff.loop_count = 0xFF;
r->report_id = XPADNEO_XB1S_FF_REPORT;

/* the user can disable some rumble motors */
r->ff.enable = FF_RUMBLE_ALL;
if (param_disable_ff & PARAM_DISABLE_FF_TRIGGER)
r->ff.enable &= ~FF_RUMBLE_TRIGGERS;
if (param_disable_ff & PARAM_DISABLE_FF_MAIN)
r->ff.enable &= ~FF_RUMBLE_MAIN;

/*
* It is up to the Input-Subsystem to start and stop effects as needed.
* All WE need to do is to play the effect at least 32767 ms long.
* Take a look here:
* https://stackoverflow.com/questions/48034091/
* We therefore simply play the effect as long as possible, which is
* 2, 55s * 255 = 650, 25s ~ = 10min
* ff-memless has a time resolution of 50ms but we pulse the motors
* as long as possible
*/
r->ff.pulse_sustain_10ms = U8_MAX;
r->ff.loop_count = U8_MAX;

/* trigger motors */
r->ff.magnitude_left = xdata->ff.magnitude_left;
r->ff.magnitude_right = xdata->ff.magnitude_right;

/* main motors */
r->ff.magnitude_strong = xdata->ff.magnitude_strong;
r->ff.magnitude_weak = xdata->ff.magnitude_weak;

ret = hid_hw_output_report(hdev, (__u8 *) r, sizeof(*r));
if (ret < 0)
hid_warn(hdev, "failed to send FF report: %d\n", ret);
}

/*
Expand All @@ -234,18 +254,11 @@ static void create_ff_pck(struct ff_report *pck, u8 id, u8 en_act,
static int xpadneo_ff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct ff_report ff_pck;
u32 weak, strong, direction, max, max_damped, max_unscaled;
u8 mag_main_right, mag_main_left, mag_trigger_right, mag_trigger_left;
u8 ff_active;

const int fractions_percent[] = {
100, 96, 85, 69, 50, 31, 15, 4, 0, 4, 15, 31, 50, 69, 85, 96,
100
};
const int proportions_idx_max = 16;
u8 index_left, index_right;
int fraction_TL, fraction_TR;

enum {
DIRECTION_DOWN = 0x0000,
Expand All @@ -254,10 +267,11 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data,
DIRECTION_RIGHT = 0xC000,
};

struct hid_device *hdev = input_get_drvdata(dev);
int fraction_TL, fraction_TR;
u32 weak, strong, direction, max_damped, max_unscaled;

if (param_disable_ff == PARAM_DISABLE_FF_ALL)
return 0;
struct hid_device *hdev = input_get_drvdata(dev);
struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);

if (effect->type != FF_RUMBLE)
return 0;
Expand All @@ -271,32 +285,30 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data,
"playing effect: strong: %#04x, weak: %#04x, direction: %#04x\n",
strong, weak, direction);

/* calculate the physical magnitudes */
mag_main_right = (u8)((weak * 100) / 0xFFFF); /* scale from 16 bit to 0..100 */
mag_main_left = (u8)((strong * 100) / 0xFFFF); /* scale from 16 bit to 0..100 */
/* calculate the physical magnitudes, scale from 16 bit to 0..100 */
xdata->ff.magnitude_strong = (u8)((strong * 100) / U16_MAX);
xdata->ff.magnitude_weak = (u8)((weak * 100) / U16_MAX);

/*
* we want to keep the rumbling at the triggers below the maximum
* of the weak and strong main rumble
*/
max_unscaled = weak > strong ? weak : strong;

/*
* get the proportions from a precalculated cosine table
* calculation goes like:
* cosine(a) * 100 = {100, 96, 85, 69, 50, 31, 15, 4, 0, 4, 15, 31, 50, 69, 85, 96, 100}
* fractions_percent(a) = round(50 + (cosine * 50))
*/
fraction_TL = 0;
fraction_TR = 0;
fraction_TL = fraction_TR = 0;
if (direction >= DIRECTION_LEFT && direction <= DIRECTION_RIGHT) {
index_left = (direction - DIRECTION_LEFT) >> 11;
index_right = proportions_idx_max - index_left;
u8 index_left = (direction - DIRECTION_LEFT) >> 11;
u8 index_right = proportions_idx_max - index_left;
fraction_TL = fractions_percent[index_left];
fraction_TR = fractions_percent[index_right];
}

/*
* we want to keep the rumbling at the triggers below the maximum
* of the weak and strong main rumble
*/
max = mag_main_right > mag_main_left ? mag_main_right : mag_main_left;
max_unscaled = weak > strong ? weak : strong;

/*
* the user can change the damping at runtime, hence check the
* range
Expand All @@ -306,30 +318,12 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data,
else
max_damped = max_unscaled;

mag_trigger_left = (u8)((max_damped * fraction_TL) / 0xFFFF);
mag_trigger_right = (u8)((max_damped * fraction_TR) / 0xFFFF);

ff_active = FF_ENABLE_ALL;
if (param_disable_ff & PARAM_DISABLE_FF_TRIGGER)
ff_active &=
~(FF_ENABLE_LEFT_TRIGGER | FF_ENABLE_RIGHT_TRIGGER);
if (param_disable_ff & PARAM_DISABLE_FF_MAIN)
ff_active &= ~(FF_ENABLE_LEFT | FF_ENABLE_RIGHT);

create_ff_pck(&ff_pck, 0x03,
ff_active,
mag_trigger_left, mag_trigger_right,
mag_main_left, mag_main_right, 0);

hid_dbg_lvl(DBG_LVL_FEW, hdev,
"active: %#04x, max: %#04x, prop_left: %#04x, prop_right: %#04x, left trigger: %#04x, right: %#04x\n",
ff_active,
max, fraction_TL, fraction_TR,
ff_pck.ff.magnitude_left_trigger,
ff_pck.ff.magnitude_right_trigger);

hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck));
/* 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);

/* schedule writing a rumble report to the controller */
schedule_work(&xdata->ff_worker);
return 0;
}

Expand All @@ -340,44 +334,48 @@ static void xpadneo_welcome_rumble(struct hid_device *hdev)
memset(&ff_pck.ff, 0, sizeof(ff_pck.ff));

ff_pck.report_id = XPADNEO_XB1S_FF_REPORT;
ff_pck.ff.magnitude_right = 40;
ff_pck.ff.magnitude_left = 20;
ff_pck.ff.magnitude_right_trigger = 10;
ff_pck.ff.magnitude_left_trigger = 10;
ff_pck.ff.duration = 33;

ff_pck.ff.enable_actuators = FF_ENABLE_RIGHT;
ff_pck.ff.magnitude_weak = 40;
ff_pck.ff.magnitude_strong = 20;
ff_pck.ff.magnitude_right = 10;
ff_pck.ff.magnitude_left = 10;
ff_pck.ff.pulse_sustain_10ms = 5;
ff_pck.ff.pulse_release_10ms = 5;
ff_pck.ff.loop_count = 3;

ff_pck.ff.enable = FF_RUMBLE_WEAK;
hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck));
mdelay(330);

ff_pck.ff.enable_actuators = FF_ENABLE_LEFT;
ff_pck.ff.enable = FF_RUMBLE_STRONG;
hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck));
mdelay(330);

ff_pck.ff.enable_actuators = FF_ENABLE_RIGHT_TRIGGER | FF_ENABLE_LEFT_TRIGGER;
ff_pck.ff.enable = FF_RUMBLE_TRIGGERS;
hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck));
mdelay(330);
}

/* Device (Gamepad) Initialization */
static int xpadneo_initDevice(struct hid_device *hdev)
{
int error;

struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);
struct input_dev *idev = xdata->idev;

INIT_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)
return -ENOMEM;

/* 'HELLO' FROM THE OTHER SIDE */
if (!param_disable_ff)
xpadneo_welcome_rumble(hdev);

/* Init Input System for Force Feedback (FF) */
input_set_capability(idev, EV_FF, FF_RUMBLE);
error = input_ff_create_memless(idev, NULL, xpadneo_ff_play);
if (error)
return error;

return 0;
return input_ff_create_memless(idev, NULL, xpadneo_ff_play);
}

/* Callback function which return the available properties to userspace */
Expand Down Expand Up @@ -1287,6 +1285,8 @@ static void xpadneo_remove_device(struct hid_device *hdev)

hid_hw_close(hdev);

cancel_work_sync(&xdata->ff_worker);

/* Cleaning up here */
ida_simple_remove(&xpadneo_device_id_allocator, xdata->id);
hid_hw_stop(hdev);
Expand Down

0 comments on commit 636d2fa

Please sign in to comment.