Skip to content

Commit

Permalink
[WIP] 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

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 5, 2020
1 parent ad31ad3 commit 894f292
Showing 1 changed file with 83 additions and 80 deletions.
163 changes: 83 additions & 80 deletions hid-xpadneo/src/hid-xpadneo.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,18 @@ enum {
};

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

#define XPADNEO_ONES_FF_REPORT 3

struct ff_report {
u8 report_id;
struct ff_data ff;
Expand Down Expand Up @@ -182,31 +184,53 @@ 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;
};


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) {
/*
* Force Feedback Worker
*
* This function is called by the kernel worker thread.
*/
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_ONES_FF_REPORT;

/* 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
*/
/* the user can disable some rumble motors */
r->ff.enable = FF_ENABLE_ALL;
if (param_disable_ff & PARAM_DISABLE_FF_TRIGGER)
r->ff.enable &= ~(FF_ENABLE_LEFT_TRIGGER | FF_ENABLE_RIGHT_TRIGGER);
if (param_disable_ff & PARAM_DISABLE_FF_MAIN)
r->ff.enable &= ~(FF_ENABLE_LEFT | FF_ENABLE_RIGHT);

/* 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_trigger = xdata->ff.magnitude_left_trigger;
r->ff.magnitude_right_trigger = xdata->ff.magnitude_right_trigger;

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

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 @@ -224,17 +248,9 @@ 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;
u8 trigger_rumble_damping_nonzero;

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

int fraction_TL, fraction_TR;
u32 weak, strong, direction, max_damped, max_unscaled;

struct hid_device *hdev = input_get_drvdata(dev);

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

if (effect->type != FF_RUMBLE)
return 0;
Expand All @@ -260,59 +276,42 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data,
hid_dbg_lvl(DBG_LVL_FEW, hdev, "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_left = (u8)((strong * 100) / U16_MAX);
xdata->ff.magnitude_right = (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 */
trigger_rumble_damping_nonzero
= param_trigger_rumble_damping == 0 ? 1 : param_trigger_rumble_damping;
max_damped = max_unscaled / trigger_rumble_damping_nonzero;
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);
if (param_trigger_rumble_damping > 0) {
max_damped = max_unscaled / param_trigger_rumble_damping;
}
else {
max_damped = max_unscaled;
}

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_trigger = (u8)((max_damped * fraction_TL) / U16_MAX);
xdata->ff.magnitude_right_trigger = (u8)((max_damped * fraction_TR) / U16_MAX);

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

Expand All @@ -328,7 +327,9 @@ static void xpadneo_welcome_rumble(struct hid_device *hdev)
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.pulse_sustain_10ms = 5;
ff_pck.ff.pulse_release_10ms = 5;
ff_pck.ff.loop_count = 3;

ff_pck.ff.enable = FF_ENABLE_RIGHT;
hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck));
Expand All @@ -350,21 +351,22 @@ static void xpadneo_welcome_rumble(struct hid_device *hdev)
*/
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);
}


Expand Down Expand Up @@ -1232,6 +1234,7 @@ static void xpadneo_remove_device(struct hid_device *hdev)
{
struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);

cancel_work_sync(&xdata->ff_worker);
hid_hw_close(hdev);

/* Cleaning up here */
Expand Down

0 comments on commit 894f292

Please sign in to comment.