diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 5e6a0cef2..41e7adae1 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -6,21 +6,24 @@ * Speed Force Wireless (WiiWheel) * * Copyright (c) 2010 Simon Wood + * Copyright (c) 2019 Bernat Arlandis */ -/* - */ - - +#include #include #include #include +#include +#include +#include #include "usbhid/usbhid.h" #include "hid-lg.h" #include "hid-lg4ff.h" #include "hid-ids.h" +#define LG4FF_VERSION "0.3.3" + #define LG4FF_MMODE_IS_MULTIMODE 0 #define LG4FF_MMODE_SWITCHED 1 #define LG4FF_MMODE_NOT_MULTIMODE 2 @@ -58,13 +61,73 @@ #define LG4FF_FFEX_REV_MAJ 0x21 #define LG4FF_FFEX_REV_MIN 0x00 -static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range); -static void lg4ff_set_range_g25(struct hid_device *hid, u16 range); +#define DEBUG(...) pr_debug("lg4ff: " __VA_ARGS__) +#define time_diff(a,b) ({ \ + typecheck(unsigned long, a); \ + typecheck(unsigned long, b); \ + ((a) - (long)(b)); }) +#define CLAMP_VALUE_U16(x) ((unsigned short)((x) > 0xffff ? 0xffff : (x))) +#define CLAMP_VALUE_S16(x) ((unsigned short)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x)))) +#define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits)) +#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs(x) * 2, bits) +#define TRANSLATE_FORCE(x) ((CLAMP_VALUE_S16(x) + 0x8000) >> 8) +#define STOP_EFFECT(state) ((state)->flags = 0) +#define JIFFIES2MS(jiffies) ((jiffies) * 1000 / HZ) +#undef fixp_sin16 +#define fixp_sin16(v) (((v % 360) > 180)? -(fixp_sin32((v % 360) - 180) >> 16) : fixp_sin32(v) >> 16) + +#define DEFAULT_TIMER_PERIOD 2 +#define LG4FF_MAX_EFFECTS 16 + +#define FF_EFFECT_STARTED 0 +#define FF_EFFECT_ALLSET 1 +#define FF_EFFECT_PLAYING 2 +#define FF_EFFECT_UPDATING 3 + +struct lg4ff_effect_state { + struct ff_effect effect; + struct ff_envelope *envelope; + unsigned long start_at; + unsigned long play_at; + unsigned long stop_at; + unsigned long flags; + unsigned long time_playing; + unsigned long updated_at; + unsigned int phase; + unsigned int phase_adj; + unsigned int count; + unsigned int cmd; + unsigned int cmd_start_time; + unsigned int cmd_start_count; + int direction_gain; + int slope; +}; + +struct lg4ff_effect_parameters { + int level; + int d1; + int d2; + int k1; + int k2; + unsigned int clip; +}; + +struct lg4ff_slot { + int id; + struct lg4ff_effect_parameters parameters; + u8 current_cmd[7]; + int cmd_op; + int is_updated; + int effect_type; +}; struct lg4ff_wheel_data { const u32 product_id; u16 combine; u16 range; + u16 autocenter; + u16 master_gain; + u16 gain; const u16 min_range; const u16 max_range; #ifdef CONFIG_LEDS_CLASS @@ -81,13 +144,34 @@ struct lg4ff_wheel_data { struct lg4ff_device_entry { spinlock_t report_lock; /* Protect output HID report */ + spinlock_t timer_lock; struct hid_report *report; struct lg4ff_wheel_data wdata; + struct hid_device *hid; + struct timer_list timer; + struct hrtimer hrtimer; + struct lg4ff_slot slots[4]; + struct lg4ff_effect_state states[LG4FF_MAX_EFFECTS]; + unsigned peak_ffb_level; + int effects_used; +#ifdef CONFIG_LEDS_CLASS + int has_leds; +#endif }; static const signed short lg4ff_wheel_effects[] = { FF_CONSTANT, + FF_SPRING, + FF_DAMPER, FF_AUTOCENTER, + FF_PERIODIC, + FF_SINE, + FF_SQUARE, + FF_TRIANGLE, + FF_SAW_UP, + FF_SAW_DOWN, + FF_RAMP, + FF_FRICTION, -1 }; @@ -128,6 +212,12 @@ struct lg4ff_alternate_mode { const char *name; }; +static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range); +static void lg4ff_set_range_g25(struct hid_device *hid, u16 range); +#ifdef CONFIG_LEDS_CLASS +static void lg4ff_set_leds(struct hid_device *hid, u8 leds); +#endif + static const struct lg4ff_wheel lg4ff_devices[] = { {USB_DEVICE_ID_LOGITECH_WINGMAN_FG, no_wheel_effects, 40, 180, NULL}, {USB_DEVICE_ID_LOGITECH_WINGMAN_FFG, lg4ff_wheel_effects, 40, 180, NULL}, @@ -273,6 +363,659 @@ static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = { {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} }; +static int timer_msecs = DEFAULT_TIMER_PERIOD; +module_param(timer_msecs, int, 0660); +MODULE_PARM_DESC(timer_msecs, "Timer resolution in msecs."); + +static int fixed_loop = 0; +module_param(fixed_loop, int, 0); +MODULE_PARM_DESC(fixed_loop, "Put the device into fixed loop mode."); + +static int timer_mode = 2; +module_param(timer_mode, int, 0660); +MODULE_PARM_DESC(timer_mode, "Timer mode: 0) fixed, 1) static, 2) dynamic (default)."); + +static int profile = 0; +module_param(profile, int, 0660); +MODULE_PARM_DESC(profile, "Enable profile debug messages."); + +#ifdef CONFIG_LEDS_CLASS +static int ffb_leds = 0; +module_param(ffb_leds, int, 0); +MODULE_PARM_DESC(ffb_leds, "Use leds to display FFB levels for calibration."); +#endif + +static int spring_level = 30; +module_param(spring_level, int, 0); +MODULE_PARM_DESC(spring_level, "Level of spring force (0-100)."); + +static int damper_level = 30; +module_param(damper_level, int, 0); +MODULE_PARM_DESC(damper_level, "Level of damper force (0-100)."); + +static int friction_level = 30; +module_param(friction_level, int, 0); +MODULE_PARM_DESC(friction_level, "Level of friction force (0-100)."); + +static struct lg4ff_device_entry *lg4ff_get_device_entry(struct hid_device *hid) +{ + struct lg_drv_data *drv_data; + struct lg4ff_device_entry *entry; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return NULL; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return NULL; + } + + return entry; +} + +void lg4ff_send_cmd(struct lg4ff_device_entry *entry, u8 *cmd) +{ + unsigned long flags; + s32 *value = entry->report->field[0]->value; + + spin_lock_irqsave(&entry->report_lock, flags); + value[0] = cmd[0]; + value[1] = cmd[1]; + value[2] = cmd[2]; + value[3] = cmd[3]; + value[4] = cmd[4]; + value[5] = cmd[5]; + value[6] = cmd[6]; + hid_hw_request(entry->hid, entry->report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); + if (unlikely(profile)) + DEBUG("send_cmd: %02X %02X %02X %02X %02X %02X %02X", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]); +} + +void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_parameters *parameters) +{ + u8 original_cmd[7]; + int d1; + int d2; + int s1; + int s2; + + memcpy(original_cmd, slot->current_cmd, sizeof(original_cmd)); + + if ((original_cmd[0] & 0xf) == 1) { + original_cmd[0] = (original_cmd[0] & 0xf0) + 0xc; + } + + if (slot->effect_type == FF_CONSTANT) { + if (slot->cmd_op == 0) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } else { + if (parameters->clip == 0) { + slot->cmd_op = 3; + } else if (slot->cmd_op == 3) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } + + slot->current_cmd[0] = (0x10 << slot->id) + slot->cmd_op; + + if (slot->cmd_op == 3) { + slot->current_cmd[1] = 0; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + } else { + switch (slot->effect_type) { + case FF_CONSTANT: + slot->current_cmd[1] = 0x00; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + slot->current_cmd[2 + slot->id] = TRANSLATE_FORCE(parameters->level); + break; + case FF_SPRING: + d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11); + d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0b; + slot->current_cmd[2] = d1 >> 3; + slot->current_cmd[3] = d2 >> 3; + slot->current_cmd[4] = (SCALE_COEFF(parameters->k2, 4) << 4) + SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[5] = ((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case FF_DAMPER: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0c; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[3] = s1; + slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); + slot->current_cmd[5] = s2; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case FF_FRICTION: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0e; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8); + slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8); + slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8); + slot->current_cmd[5] = (s2 << 4) + s1; + slot->current_cmd[6] = 0; + break; + } + } + + if (memcmp(original_cmd, slot->current_cmd, sizeof(original_cmd))) { + slot->is_updated = 1; + } +} + +static __always_inline int lg4ff_calculate_constant(struct lg4ff_effect_state *state) +{ + int level_sign; + int level = state->effect.u.constant.level; + int d, t; + + if (state->time_playing < state->envelope->attack_length) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * state->envelope->attack_level; + level = level_sign * state->envelope->attack_level + d * state->time_playing / state->envelope->attack_length; + } else if (state->effect.replay.length) { + t = state->time_playing - state->effect.replay.length + state->envelope->fade_length; + if (t > 0) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * state->envelope->fade_level; + level = level - d * t / state->envelope->fade_length; + } + } + + return state->direction_gain * level / 0x7fff; +} + +static __always_inline int lg4ff_calculate_ramp(struct lg4ff_effect_state *state) +{ + struct ff_ramp_effect *ramp = &state->effect.u.ramp; + int level_sign; + int level = INT_MAX; + int d, t; + + if (state->time_playing < state->envelope->attack_length) { + level = ramp->start_level; + level_sign = level < 0 ? -1 : 1; + t = state->envelope->attack_length - state->time_playing; + d = level - level_sign * state->envelope->attack_level; + level = level_sign * state->envelope->attack_level + d * t / state->envelope->attack_length; + } else if (state->effect.replay.length && state->time_playing >= state->effect.replay.length - state->envelope->fade_length) { + level = ramp->end_level; + level_sign = level < 0 ? -1 : 1; + t = state->time_playing - state->effect.replay.length + state->envelope->fade_length; + d = level_sign * state->envelope->fade_level - level; + level = level - d * t / state->envelope->fade_length; + } else { + t = state->time_playing - state->envelope->attack_length; + level = ramp->start_level + ((t * state->slope) >> 16); + } + + return state->direction_gain * level / 0x7fff; +} + +static __always_inline int lg4ff_calculate_periodic(struct lg4ff_effect_state *state) +{ + struct ff_periodic_effect *periodic = &state->effect.u.periodic; + int magnitude = periodic->magnitude; + int magnitude_sign = magnitude < 0 ? -1 : 1; + int level = periodic->offset; + int d, t; + + if (state->time_playing < state->envelope->attack_length) { + d = magnitude - magnitude_sign * state->envelope->attack_level; + magnitude = magnitude_sign * state->envelope->attack_level + d * state->time_playing / state->envelope->attack_length; + } else if (state->effect.replay.length) { + t = state->time_playing - state->effect.replay.length + state->envelope->fade_length; + if (t > 0) { + d = magnitude - magnitude_sign * state->envelope->fade_level; + magnitude = magnitude - d * t / state->envelope->fade_length; + } + } + + switch (periodic->waveform) { + case FF_SINE: + level += fixp_sin16(state->phase) * magnitude / 0x7fff; + break; + case FF_SQUARE: + level += (state->phase < 180 ? 1 : -1) * magnitude; + break; + case FF_TRIANGLE: + level += abs(state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude; + break; + case FF_SAW_UP: + level += state->phase * magnitude * 2 / 360 - magnitude; + break; + case FF_SAW_DOWN: + level += magnitude - state->phase * magnitude * 2 / 360; + break; + } + + return state->direction_gain * level / 0x7fff; +} + +static __always_inline void lg4ff_calculate_spring(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + struct ff_condition_effect *condition = &state->effect.u.condition[0]; + int d1; + int d2; + + d1 = condition->center - condition->deadband / 2; + d2 = condition->center + condition->deadband / 2; + if (d1 < parameters->d1) { + parameters->d1 = d1; + } + if (d2 > parameters->d2) { + parameters->d2 = d2; + } + parameters->k1 += condition->left_coeff; + parameters->k2 += condition->right_coeff; + parameters->clip = max(parameters->clip, (unsigned)max(condition->left_saturation, condition->right_saturation)); +} + +static __always_inline void lg4ff_calculate_resistance(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + struct ff_condition_effect *condition = &state->effect.u.condition[0]; + + parameters->k1 += condition->left_coeff; + parameters->k2 += condition->right_coeff; + parameters->clip = max(parameters->clip, (unsigned)max(condition->left_saturation, condition->right_saturation)); +} + +static __always_inline struct ff_envelope *lg4ff_effect_envelope(struct ff_effect *effect) +{ + switch (effect->type) { + case FF_CONSTANT: + return &effect->u.constant.envelope; + case FF_RAMP: + return &effect->u.ramp.envelope; + case FF_PERIODIC: + return &effect->u.periodic.envelope; + } + + return NULL; +} + +static __always_inline void lg4ff_update_state(struct lg4ff_effect_state *state, const unsigned long now) +{ + struct ff_effect *effect = &state->effect; + unsigned long phase_time; + + if (!__test_and_set_bit(FF_EFFECT_ALLSET, &state->flags)) { + state->play_at = state->start_at + effect->replay.delay; + if (!test_bit(FF_EFFECT_UPDATING, &state->flags)) { + state->updated_at = state->play_at; + } + state->direction_gain = fixp_sin16(effect->direction * 360 / 0x10000); + if (effect->type == FF_PERIODIC) { + state->phase_adj = effect->u.periodic.phase * 360 / effect->u.periodic.period; + } + if (effect->replay.length) { + state->stop_at = state->play_at + effect->replay.length; + } + } + + if (__test_and_clear_bit(FF_EFFECT_UPDATING, &state->flags)) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + state->play_at = state->start_at + effect->replay.delay; + state->direction_gain = fixp_sin16(effect->direction * 360 / 0x10000); + if (effect->replay.length) { + state->stop_at = state->play_at + effect->replay.length; + } + if (effect->type == FF_PERIODIC) { + state->phase_adj = state->phase; + } + } + + state->envelope = lg4ff_effect_envelope(effect); + + state->slope = 0; + if (effect->type == FF_RAMP && effect->replay.length) { + state->slope = ((effect->u.ramp.end_level - effect->u.ramp.start_level) << 16) / (effect->replay.length - state->envelope->attack_length - state->envelope->fade_length); + } + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags) && time_after_eq(now, + state->play_at) && (effect->replay.length == 0 || + time_before(now, state->stop_at))) { + __set_bit(FF_EFFECT_PLAYING, &state->flags); + } + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) { + state->time_playing = time_diff(now, state->play_at); + if (effect->type == FF_PERIODIC) { + phase_time = time_diff(now, state->updated_at); + state->phase = (phase_time % effect->u.periodic.period) * 360 / effect->u.periodic.period; + state->phase += state->phase_adj % 360; + } + } +} + +static __always_inline int lg4ff_timer(struct lg4ff_device_entry *entry) +{ + struct usbhid_device *usbhid = entry->hid->driver_data; + struct lg4ff_slot *slot; + struct lg4ff_effect_state *state; + struct lg4ff_effect_parameters parameters[4]; + unsigned long jiffies_now = jiffies; + unsigned long now = JIFFIES2MS(jiffies_now); + unsigned long flags; + unsigned gain; + int current_period; + int count; + int effect_id; + int i; + int ffb_level; +#ifdef CONFIG_LEDS_CLASS + static int leds_timer = 0; + static int leds_level = 0; + u8 led_states; +#endif + + if (timer_mode > 0 && usbhid->outhead != usbhid->outtail) { + current_period = timer_msecs; + if (timer_mode == 1) { + timer_msecs *= 2; + hid_info(entry->hid, "Commands stacking up, increasing timer period to %d ms.", timer_msecs); + } else { + DEBUG("Commands stacking up, delaying timer."); + } + return current_period; + } + + memset(parameters, 0, sizeof(parameters)); + + gain = (unsigned)entry->wdata.master_gain * entry->wdata.gain / 0xffff; + + spin_lock_irqsave(&entry->timer_lock, flags); + + count = entry->effects_used; + + for (effect_id = 0; effect_id < LG4FF_MAX_EFFECTS; effect_id++) { + + if (!count) { + break; + } + + state = &entry->states[effect_id]; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) { + continue; + } + + count--; + + if (test_bit(FF_EFFECT_ALLSET, &state->flags)) { + if (state->effect.replay.length && time_after_eq(now, state->stop_at)) { + STOP_EFFECT(state); + if (!--state->count) { + entry->effects_used--; + continue; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = state->stop_at; + } + } + + lg4ff_update_state(state, now); + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags)) { + continue; + } + + switch (state->effect.type) { + case FF_CONSTANT: + parameters[0].level += lg4ff_calculate_constant(state); + break; + case FF_RAMP: + parameters[0].level += lg4ff_calculate_ramp(state); + break; + case FF_PERIODIC: + parameters[0].level += lg4ff_calculate_periodic(state); + break; + case FF_SPRING: + lg4ff_calculate_spring(state, ¶meters[1]); + break; + case FF_DAMPER: + lg4ff_calculate_resistance(state, ¶meters[2]); + break; + case FF_FRICTION: + lg4ff_calculate_resistance(state, ¶meters[3]); + break; + } + } + + spin_unlock_irqrestore(&entry->timer_lock, flags); + + parameters[0].level = (long)parameters[0].level * gain / 0xffff; + parameters[1].clip = parameters[1].clip * spring_level / 100; + parameters[2].clip = parameters[2].clip * damper_level / 100; + parameters[3].clip = parameters[3].clip * friction_level / 100; + + ffb_level = abs(parameters[0].level); + for (i = 1; i < 4; i++) { + parameters[i].k1 = parameters[i].k1 * gain / 0xffff; + parameters[i].k2 = parameters[i].k2 * gain / 0xffff; + parameters[i].clip = parameters[i].clip * gain / 0xffff; + ffb_level += parameters[i].clip * 0x7fff / 0xffff; + } + if (ffb_level > entry->peak_ffb_level) { + entry->peak_ffb_level = ffb_level; + } + + for (i = 0; i < 4; i++) { + slot = &entry->slots[i]; + lg4ff_update_slot(slot, ¶meters[i]); + if (slot->is_updated) { + lg4ff_send_cmd(entry, slot->current_cmd); + slot->is_updated = 0; + } + } + +#ifdef CONFIG_LEDS_CLASS + if (ffb_leds || leds_level > 0) { + if (ffb_level > leds_level) { + leds_level = ffb_level; + } + if (!ffb_leds || entry->effects_used == 0) { + leds_timer = 0; + leds_level = 0; + } + if (leds_timer == 0) { + leds_timer = 480 / timer_msecs; + if (leds_level < 2458) { // < 7.5% + led_states = 0; + } else if (leds_level < 8192) { // < 25% + led_states = 1; + } else if (leds_level < 16384) { // < 50% + led_states = 3; + } else if (leds_level < 24576) { // < 75% + led_states = 7; + } else if (leds_level < 29491) { // < 90% + led_states = 15; + } else if (leds_level <= 32768) { // <= 100% + led_states = 31; + } else if (leds_level < 36045) { // < 110% + led_states = 30; + } else if (leds_level < 40960) { // < 125% + led_states = 28; + } else if (leds_level < 49152) { // < 150% + led_states = 24; + } else { + led_states = 16; + } + lg4ff_set_leds(entry->hid, led_states); + leds_level = 0; + } + leds_timer--; + } +#endif + + return 0; +} + +static enum hrtimer_restart lg4ff_timer_hires(struct hrtimer *t) +{ + struct lg4ff_device_entry *entry = container_of(t, struct lg4ff_device_entry, hrtimer); + int delay_timer; + int overruns; + + delay_timer = lg4ff_timer(entry); + + if (delay_timer) { + hrtimer_forward_now(&entry->hrtimer, ms_to_ktime(delay_timer)); + return HRTIMER_RESTART; + } + + if (entry->effects_used) { + overruns = hrtimer_forward_now(&entry->hrtimer, ms_to_ktime(timer_msecs)); + overruns--; + if (unlikely(profile && overruns > 0)) + DEBUG("Overruns: %d", overruns); + return HRTIMER_RESTART; + } else { + if (unlikely(profile)) + DEBUG("Stop timer."); + return HRTIMER_NORESTART; + } +} + +static void lg4ff_init_slots(struct lg4ff_device_entry *entry) +{ + struct lg4ff_effect_parameters parameters; + u8 cmd[8] = {0}; + int i; + + // Set/unset fixed loop mode + cmd[0] = 0x0d; + cmd[1] = fixed_loop ? 1 : 0; + lg4ff_send_cmd(entry, cmd); + + memset(&entry->states, 0, sizeof(entry->states)); + memset(&entry->slots, 0, sizeof(entry->slots)); + memset(¶meters, 0, sizeof(parameters)); + + entry->slots[0].effect_type = FF_CONSTANT; + entry->slots[1].effect_type = FF_SPRING; + entry->slots[2].effect_type = FF_DAMPER; + entry->slots[3].effect_type = FF_FRICTION; + + for (i = 0; i < 4; i++) { + entry->slots[i].id = i; + lg4ff_update_slot(&entry->slots[i], ¶meters); + lg4ff_send_cmd(entry, entry->slots[i].current_cmd); + entry->slots[i].is_updated = 0; + } +} + +static void lg4ff_stop_effects(struct lg4ff_device_entry *entry) +{ + u8 cmd[7] = {0}; + + cmd[0] = 0xf3; + lg4ff_send_cmd(entry, cmd); +} + +static int lg4ff_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg4ff_device_entry *entry; + struct lg4ff_effect_state *state; + unsigned long now = JIFFIES2MS(jiffies); + unsigned long flags; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + if (effect->type == FF_PERIODIC && effect->u.periodic.period == 0) { + return -EINVAL; + } + + state = &entry->states[effect->id]; + + if (test_bit(FF_EFFECT_STARTED, &state->flags) && effect->type != state->effect.type) { + return -EINVAL; + } + + spin_lock_irqsave(&entry->timer_lock, flags); + + state->effect = *effect; + + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + __set_bit(FF_EFFECT_UPDATING, &state->flags); + state->updated_at = now; + } + + spin_unlock_irqrestore(&entry->timer_lock, flags); + + return 0; +} + +static int lg4ff_play_effect(struct input_dev *dev, int effect_id, int value) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg4ff_device_entry *entry; + struct lg4ff_effect_state *state; + unsigned long now = JIFFIES2MS(jiffies); + unsigned long flags; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + state = &entry->states[effect_id]; + + spin_lock_irqsave(&entry->timer_lock, flags); + + if (value > 0) { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + } else { + entry->effects_used++; + if (!hrtimer_active(&entry->hrtimer)) { + hrtimer_start(&entry->hrtimer, ms_to_ktime(timer_msecs), HRTIMER_MODE_REL); + if (unlikely(profile)) + DEBUG("Start timer."); + } + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = now; + state->count = value; + } else { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + entry->effects_used--; + } + } + + spin_unlock_irqrestore(&entry->timer_lock, flags); + + return 0; +} + /* Recalculates X axis value accordingly to currently selected range */ static s32 lg4ff_adjust_dfp_x_axis(s32 value, u16 range) { @@ -333,7 +1076,7 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, return 0; /* adjust HID report present combined pedals data */ - if (entry->wdata.combine) { + if (entry->wdata.combine == 1) { switch (entry->wdata.product_id) { case USB_DEVICE_ID_LOGITECH_WHEEL: rd[5] = rd[3]; @@ -371,6 +1114,25 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, return 1; } + if (entry->wdata.combine == 2) { + switch (entry->wdata.product_id) { + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + offset = 5; + break; + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + offset = 6; + break; + default: + return 0; + } + + /* Compute a combined axis when wheel does not supply it */ + rd[offset] = (0xFF + rd[offset] - rd[offset+2]) >> 1; + rd[offset+2] = 0x7F; + return 1; + } + return 0; } @@ -403,103 +1165,32 @@ static void lg4ff_init_wheel_data(struct lg4ff_wheel_data * const wdata, const s } } -static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) -{ - struct hid_device *hid = input_get_drvdata(dev); - struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; - unsigned long flags; - s32 *value; - int x; - - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return -EINVAL; - } - - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return -EINVAL; - } - value = entry->report->field[0]->value; - -#define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0) - - switch (effect->type) { - case FF_CONSTANT: - x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ - CLAMP(x); - - spin_lock_irqsave(&entry->report_lock, flags); - if (x == 0x80) { - /* De-activate force in slot-1*/ - value[0] = 0x13; - value[1] = 0x00; - value[2] = 0x00; - value[3] = 0x00; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; - - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); - return 0; - } - - value[0] = 0x11; /* Slot 1 */ - value[1] = 0x08; - value[2] = x; - value[3] = 0x80; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; - - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); - break; - } - return 0; -} - /* Sends default autocentering command compatible with * all wheels except Formula Force EX */ static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) { struct hid_device *hid = input_get_drvdata(dev); - s32 *value; + u8 cmd[7]; u32 expand_a, expand_b; struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; - unsigned long flags; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { return; } - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return; - } - value = entry->report->field[0]->value; + entry->wdata.autocenter = magnitude; /* De-activate Auto-Center */ - spin_lock_irqsave(&entry->report_lock, flags); if (magnitude == 0) { - value[0] = 0xf5; - value[1] = 0x00; - value[2] = 0x00; - value[3] = 0x00; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; - - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); + cmd[0] = 0xf5; + cmd[1] = 0x00; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); return; } @@ -521,27 +1212,24 @@ static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) break; } - value[0] = 0xfe; - value[1] = 0x0d; - value[2] = expand_a / 0xaaaa; - value[3] = expand_a / 0xaaaa; - value[4] = expand_b / 0xaaaa; - value[5] = 0x00; - value[6] = 0x00; - - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); + cmd[0] = 0xfe; + cmd[1] = 0x0d; + cmd[2] = expand_a / 0xaaaa; + cmd[3] = expand_a / 0xaaaa; + cmd[4] = expand_b / 0xaaaa; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); /* Activate Auto-Center */ - value[0] = 0x14; - value[1] = 0x00; - value[2] = 0x00; - value[3] = 0x00; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; - - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); + cmd[0] = 0x14; + cmd[1] = 0x00; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); } /* Sends autocentering command compatible with Formula Force EX */ @@ -549,35 +1237,25 @@ static void lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) { struct hid_device *hid = input_get_drvdata(dev); struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; - unsigned long flags; - s32 *value; + u8 cmd[7]; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return; + } + + entry->wdata.autocenter = magnitude; + magnitude = magnitude * 90 / 65535; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return; - } - - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return; - } - value = entry->report->field[0]->value; - - spin_lock_irqsave(&entry->report_lock, flags); - value[0] = 0xfe; - value[1] = 0x03; - value[2] = magnitude >> 14; - value[3] = magnitude >> 14; - value[4] = magnitude; - value[5] = 0x00; - value[6] = 0x00; - - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); + cmd[0] = 0xfe; + cmd[1] = 0x03; + cmd[2] = magnitude >> 14; + cmd[3] = magnitude >> 14; + cmd[4] = magnitude; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); } /* Sends command to set range compatible with G25/G27/Driving Force GT */ @@ -585,34 +1263,21 @@ static void lg4ff_set_range_g25(struct hid_device *hid, u16 range) { struct lg4ff_device_entry *entry; struct lg_drv_data *drv_data; - unsigned long flags; - s32 *value; + u8 cmd[7]; drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return; - } - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return; - } - value = entry->report->field[0]->value; + dbg_hid("G25/G27/DFGT: setting range to %u\n", range); - spin_lock_irqsave(&entry->report_lock, flags); - value[0] = 0xf8; - value[1] = 0x81; - value[2] = range & 0x00ff; - value[3] = (range & 0xff00) >> 8; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; - - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); + cmd[0] = 0xf8; + cmd[1] = 0x81; + cmd[2] = range & 0x00ff; + cmd[3] = (range & 0xff00) >> 8; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); } /* Sends commands to set range compatible with Driving Force Pro wheel */ @@ -620,55 +1285,43 @@ static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range) { struct lg4ff_device_entry *entry; struct lg_drv_data *drv_data; - unsigned long flags; int start_left, start_right, full_range; - s32 *value; + u8 cmd[7]; drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return; - } - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return; - } - value = entry->report->field[0]->value; + dbg_hid("Driving Force Pro: setting range to %u\n", range); /* Prepare "coarse" limit command */ - spin_lock_irqsave(&entry->report_lock, flags); - value[0] = 0xf8; - value[1] = 0x00; /* Set later */ - value[2] = 0x00; - value[3] = 0x00; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; + cmd[0] = 0xf8; + cmd[1] = 0x00; /* Set later */ + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; if (range > 200) { - value[1] = 0x03; + cmd[1] = 0x03; full_range = 900; } else { - value[1] = 0x02; + cmd[1] = 0x02; full_range = 200; } - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); + lg4ff_send_cmd(entry, cmd); /* Prepare "fine" limit command */ - value[0] = 0x81; - value[1] = 0x0b; - value[2] = 0x00; - value[3] = 0x00; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; + cmd[0] = 0x81; + cmd[1] = 0x0b; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; if (range == 200 || range == 900) { /* Do not apply any fine limit */ - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); + lg4ff_send_cmd(entry, cmd); return; } @@ -676,14 +1329,25 @@ static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range) start_left = (((full_range - range + 1) * 2047) / full_range); start_right = 0xfff - start_left; - value[2] = start_left >> 4; - value[3] = start_right >> 4; - value[4] = 0xff; - value[5] = (start_right & 0xe) << 4 | (start_left & 0xe); - value[6] = 0xff; + cmd[2] = start_left >> 4; + cmd[3] = start_right >> 4; + cmd[4] = 0xff; + cmd[5] = (start_right & 0xe) << 4 | (start_left & 0xe); + cmd[6] = 0xff; + lg4ff_send_cmd(entry, cmd); +} - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); +static void lg4ff_set_gain(struct input_dev *dev, u16 gain) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg4ff_device_entry *entry; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return; + } + + entry->wdata.gain = gain; } static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id) @@ -763,34 +1427,22 @@ static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(cons static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s) { struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; - unsigned long flags; - s32 *value; + u8 cmd[7]; u8 i; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { return -EINVAL; } - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return -EINVAL; - } - value = entry->report->field[0]->value; - - spin_lock_irqsave(&entry->report_lock, flags); for (i = 0; i < s->cmd_count; i++) { u8 j; for (j = 0; j < 7; j++) - value[j] = s->cmd[j + (7*i)]; + cmd[j] = s->cmd[j + (7*i)]; - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); + lg4ff_send_cmd(entry, cmd); } - spin_unlock_irqrestore(&entry->report_lock, flags); hid_hw_wait(hid); return 0; } @@ -799,20 +1451,12 @@ static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attr { struct hid_device *hid = to_hid_device(dev); struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; ssize_t count = 0; int i; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return 0; - } - - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return 0; + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; } if (!entry->wdata.real_name) { @@ -848,21 +1492,13 @@ static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_att { struct hid_device *hid = to_hid_device(dev); struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; const struct lg4ff_compat_mode_switch *s; u16 target_product_id = 0; int i, ret; char *lbuf; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return -EINVAL; - } - - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { return -EINVAL; } @@ -935,19 +1571,11 @@ static ssize_t lg4ff_combine_show(struct device *dev, struct device_attribute *a { struct hid_device *hid = to_hid_device(dev); struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; size_t count; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return 0; - } - - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return 0; + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; } count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.combine); @@ -959,23 +1587,15 @@ static ssize_t lg4ff_combine_store(struct device *dev, struct device_attribute * { struct hid_device *hid = to_hid_device(dev); struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; u16 combine = simple_strtoul(buf, NULL, 10); - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { return -EINVAL; } - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return -EINVAL; - } - - if (combine > 1) - combine = 1; + if (combine > 2) + combine = 2; entry->wdata.combine = combine; return count; @@ -988,19 +1608,11 @@ static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *att { struct hid_device *hid = to_hid_device(dev); struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; size_t count; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return 0; - } - - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return 0; + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; } count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.range); @@ -1014,18 +1626,10 @@ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *at { struct hid_device *hid = to_hid_device(dev); struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; u16 range = simple_strtoul(buf, NULL, 10); - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return -EINVAL; - } - - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { return -EINVAL; } @@ -1047,19 +1651,11 @@ static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *a { struct hid_device *hid = to_hid_device(dev); struct lg4ff_device_entry *entry; - struct lg_drv_data *drv_data; size_t count; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); - return 0; - } - - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return 0; + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; } if (!entry->wdata.real_tag || !entry->wdata.real_name) { @@ -1078,37 +1674,235 @@ static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute * } static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store); +/* Export the currently set gain of the wheel */ +static ssize_t lg4ff_gain_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.master_gain); + return count; +} + +/* Set gain to user specified value, call appropriate function + * according to the type of the wheel */ +static ssize_t lg4ff_gain_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + u16 gain = simple_strtoul(buf, NULL, 10); + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + if (gain > 0xffff) { + gain = 0xffff; + } + + entry->wdata.master_gain = gain; + + return count; +} +static DEVICE_ATTR(gain, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_gain_show, lg4ff_gain_store); + +/* Export the currently set autocenter of the wheel */ +static ssize_t lg4ff_autocenter_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.autocenter); + return count; +} + +/* Set autocenter to user specified value, call appropriate function + * according to the type of the wheel */ +static ssize_t lg4ff_autocenter_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct input_dev *inputdev = hidinput->input; + u16 autocenter = simple_strtoul(buf, NULL, 10); + + if (autocenter > 0xffff) { + autocenter = 0xffff; + } + + inputdev->ff->set_autocenter(inputdev, autocenter); + + return count; +} +static DEVICE_ATTR(autocenter, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_autocenter_show, lg4ff_autocenter_store); + +static ssize_t lg4ff_spring_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t count; + + count = scnprintf(buf, PAGE_SIZE, "%u\n", spring_level); + + return count; +} + +static ssize_t lg4ff_spring_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned value = simple_strtoul(buf, NULL, 10); + + if (value > 100) { + value = 100; + } + + spring_level = value; + + return count; +} +static DEVICE_ATTR(spring_level, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_spring_level_show, lg4ff_spring_level_store); + +static ssize_t lg4ff_damper_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t count; + + count = scnprintf(buf, PAGE_SIZE, "%u\n", damper_level); + + return count; +} + +static ssize_t lg4ff_damper_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned value = simple_strtoul(buf, NULL, 10); + + if (value > 100) { + value = 100; + } + + damper_level = value; + + return count; +} +static DEVICE_ATTR(damper_level, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_damper_level_show, lg4ff_damper_level_store); + +static ssize_t lg4ff_friction_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t count; + + count = scnprintf(buf, PAGE_SIZE, "%u\n", friction_level); + + return count; +} + +static ssize_t lg4ff_friction_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned value = simple_strtoul(buf, NULL, 10); + + if (value > 100) { + value = 100; + } + + friction_level = value; + + return count; +} +static DEVICE_ATTR(friction_level, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_friction_level_show, lg4ff_friction_level_store); + +static ssize_t lg4ff_peak_ffb_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + size_t count; + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->peak_ffb_level); + + return count; +} + +static ssize_t lg4ff_peak_ffb_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + unsigned long value = simple_strtoul(buf, NULL, 10); + + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; + } + + entry->peak_ffb_level = value; + + return count; +} +static DEVICE_ATTR(peak_ffb_level, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_peak_ffb_level_show, lg4ff_peak_ffb_level_store); + #ifdef CONFIG_LEDS_CLASS + +static ssize_t lg4ff_ffb_leds_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t count; + + count = scnprintf(buf, PAGE_SIZE, "%d\n", ffb_leds); + + return count; +} + +static ssize_t lg4ff_ffb_leds_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long value = simple_strtoul(buf, NULL, 10); + + ffb_leds = value; + + return count; +} +static DEVICE_ATTR(ffb_leds, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_ffb_leds_show, lg4ff_ffb_leds_store); + static void lg4ff_set_leds(struct hid_device *hid, u8 leds) { - struct lg_drv_data *drv_data; struct lg4ff_device_entry *entry; - unsigned long flags; - s32 *value; + u8 cmd[7]; - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Private driver data not found!\n"); + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { return; } - entry = drv_data->device_props; - if (!entry) { - hid_err(hid, "Device properties not found!\n"); - return; - } - value = entry->report->field[0]->value; - - spin_lock_irqsave(&entry->report_lock, flags); - value[0] = 0xf8; - value[1] = 0x12; - value[2] = leds; - value[3] = 0x00; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; - hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT); - spin_unlock_irqrestore(&entry->report_lock, flags); + cmd[0] = 0xf8; + cmd[1] = 0x12; + cmd[2] = leds; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + lg4ff_send_cmd(entry, cmd); } static void lg4ff_led_set_brightness(struct led_classdev *led_cdev, @@ -1116,19 +1910,11 @@ static void lg4ff_led_set_brightness(struct led_classdev *led_cdev, { struct device *dev = led_cdev->dev->parent; struct hid_device *hid = to_hid_device(dev); - struct lg_drv_data *drv_data = hid_get_drvdata(hid); struct lg4ff_device_entry *entry; int i, state = 0; - if (!drv_data) { - hid_err(hid, "Device data not found."); - return; - } - - entry = drv_data->device_props; - - if (!entry) { - hid_err(hid, "Device properties not found."); + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { return; } @@ -1138,10 +1924,14 @@ static void lg4ff_led_set_brightness(struct led_classdev *led_cdev, state = (entry->wdata.led_state >> i) & 1; if (value == LED_OFF && state) { entry->wdata.led_state &= ~(1 << i); - lg4ff_set_leds(hid, entry->wdata.led_state); + if (!ffb_leds) { + lg4ff_set_leds(hid, entry->wdata.led_state); + } } else if (value != LED_OFF && !state) { entry->wdata.led_state |= 1 << i; - lg4ff_set_leds(hid, entry->wdata.led_state); + if (!ffb_leds) { + lg4ff_set_leds(hid, entry->wdata.led_state); + } } break; } @@ -1151,20 +1941,12 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde { struct device *dev = led_cdev->dev->parent; struct hid_device *hid = to_hid_device(dev); - struct lg_drv_data *drv_data = hid_get_drvdata(hid); struct lg4ff_device_entry *entry; int i, value = 0; - if (!drv_data) { - hid_err(hid, "Device data not found."); - return LED_OFF; - } - - entry = drv_data->device_props; - - if (!entry) { - hid_err(hid, "Device properties not found."); - return LED_OFF; + entry = lg4ff_get_device_entry(hid); + if (entry == NULL) { + return -EINVAL; } for (i = 0; i < 5; i++) @@ -1175,6 +1957,62 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde return value ? LED_FULL : LED_OFF; } + +static void lg4ff_init_leds(struct hid_device *hid, struct lg4ff_device_entry *entry, int i) +{ + int error, j; + + /* register led subsystem - G27/G29 only */ + entry->wdata.led_state = 0; + for (j = 0; j < 5; j++) + entry->wdata.led[j] = NULL; + + { + struct led_classdev *led; + size_t name_sz; + char *name; + + lg4ff_set_leds(hid, 0); + + name_sz = strlen(dev_name(&hid->dev)) + 8; + + for (j = 0; j < 5; j++) { + led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); + if (!led) { + hid_err(hid, "can't allocate memory for LED %d\n", j); + goto err_leds; + } + + name = (void *)(&led[1]); + snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = lg4ff_led_get_brightness; + led->brightness_set = lg4ff_led_set_brightness; + + entry->wdata.led[j] = led; + error = led_classdev_register(&hid->dev, led); + + if (error) { + hid_err(hid, "failed to register LED %d. Aborting.\n", j); +err_leds: + /* Deregister LEDs (if any) */ + for (j = 0; j < 5; j++) { + led = entry->wdata.led[j]; + entry->wdata.led[j] = NULL; + if (!led) + continue; + led_classdev_unregister(led); + kfree(led); + } + goto out; /* Let the driver continue without LEDs */ + } + } + } +out: + return; +} #endif static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice) @@ -1250,11 +2088,14 @@ static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_produc return LG4FF_MMODE_IS_MULTIMODE; } +static void lg4ff_destroy(struct ff_device *ff) +{ +} int lg4ff_init(struct hid_device *hid) { - struct hid_input *hidinput; - struct input_dev *dev; + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct input_dev *dev = hidinput->input; struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct hid_report *report = list_entry(report_list->next, struct hid_report, list); const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor); @@ -1265,13 +2106,7 @@ int lg4ff_init(struct hid_device *hid) int error, i, j; int mmode_ret, mmode_idx = -1; u16 real_product_id; - - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_entry(hid->inputs.next, struct hid_input, list); - dev = hidinput->input; + struct ff_device *ff; /* Check that the report looks ok */ if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) @@ -1285,7 +2120,9 @@ int lg4ff_init(struct hid_device *hid) entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; + spin_lock_init(&entry->report_lock); + entry->hid = hid; entry->report = report; drv_data->device_props = entry; @@ -1296,9 +2133,10 @@ int lg4ff_init(struct hid_device *hid) /* Wheel has been told to switch to native mode. There is no point in going on * with the initialization as the wheel will do a USB reset when it switches mode */ - if (mmode_ret == LG4FF_MMODE_SWITCHED) - return 0; - else if (mmode_ret < 0) { + if (mmode_ret == LG4FF_MMODE_SWITCHED) { + error = 0; + goto err_init; + } else if (mmode_ret < 0) { hid_err(hid, "Unable to switch device mode during initialization, errno %d\n", mmode_ret); error = mmode_ret; goto err_init; @@ -1337,11 +2175,19 @@ int lg4ff_init(struct hid_device *hid) for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); - error = input_ff_create_memless(dev, NULL, lg4ff_play); + error = input_ff_create(dev, LG4FF_MAX_EFFECTS); + + //__clear_bit(FF_RUMBLE, dev->ffbit); if (error) goto err_init; + ff = dev->ff; + ff->upload = lg4ff_upload_effect; + ff->playback = lg4ff_play_effect; + ff->set_gain = lg4ff_set_gain; + ff->destroy = lg4ff_destroy; + /* Initialize device properties */ if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { BUG_ON(mmode_idx == -1); @@ -1349,6 +2195,8 @@ int lg4ff_init(struct hid_device *hid) } lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id); + set_bit(FF_GAIN, dev->ffbit); + /* Check if autocentering is available and * set the centering force to zero by default */ if (test_bit(FF_AUTOCENTER, dev->ffbit)) { @@ -1362,6 +2210,16 @@ int lg4ff_init(struct hid_device *hid) dev->ff->set_autocenter(dev, 0); } +#ifdef CONFIG_LEDS_CLASS + if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL || + lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G29_WHEEL) { + entry->has_leds = 1; + lg4ff_init_leds(hid, entry, i); + } else { + ffb_leds = 0; + } +#endif + /* Create sysfs interface */ error = device_create_file(&hid->dev, &dev_attr_combine_pedals); if (error) @@ -1377,6 +2235,44 @@ int lg4ff_init(struct hid_device *hid) if (error) hid_warn(hid, "Unable to create sysfs interface for \"alternate_modes\", errno %d\n", error); } + + if (dev->ffbit) { + error = device_create_file(&hid->dev, &dev_attr_gain); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"gain\", errno %d\n", error); + if (test_bit(FF_AUTOCENTER, dev->ffbit)) { + error = device_create_file(&hid->dev, &dev_attr_autocenter); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"autocenter\", errno %d\n", error); + } + error = device_create_file(&hid->dev, &dev_attr_peak_ffb_level); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"peak_ffb_level\", errno %d\n", error); + if (test_bit(FF_SPRING, dev->ffbit)) { + error = device_create_file(&hid->dev, &dev_attr_spring_level); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"spring_level\", errno %d\n", error); + } + if (test_bit(FF_DAMPER, dev->ffbit)) { + error = device_create_file(&hid->dev, &dev_attr_damper_level); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"damper_level\", errno %d\n", error); + } + if (test_bit(FF_FRICTION, dev->ffbit)) { + error = device_create_file(&hid->dev, &dev_attr_friction_level); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"friction_level\", errno %d\n", error); + } + } + +#ifdef CONFIG_LEDS_CLASS + if (entry->has_leds) { + error = device_create_file(&hid->dev, &dev_attr_ffb_leds); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"ffb_leds\", errno %d\n", error); + } +#endif + dbg_hid("sysfs interface created\n"); /* Set the maximum range to start with */ @@ -1384,59 +2280,21 @@ int lg4ff_init(struct hid_device *hid) if (entry->wdata.set_range) entry->wdata.set_range(hid, entry->wdata.range); -#ifdef CONFIG_LEDS_CLASS - /* register led subsystem - G27/G29 only */ - entry->wdata.led_state = 0; - for (j = 0; j < 5; j++) - entry->wdata.led[j] = NULL; + lg4ff_init_slots(entry); - if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL || - lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G29_WHEEL) { - struct led_classdev *led; - size_t name_sz; - char *name; + entry->effects_used = 0; + entry->wdata.master_gain = 0xffff; + entry->wdata.gain = 0xffff; - lg4ff_set_leds(hid, 0); + spin_lock_init(&entry->timer_lock); - name_sz = strlen(dev_name(&hid->dev)) + 8; + hrtimer_init(&entry->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + entry->hrtimer.function = lg4ff_timer_hires; - for (j = 0; j < 5; j++) { - led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); - if (!led) { - hid_err(hid, "can't allocate memory for LED %d\n", j); - goto err_leds; - } + hid_info(hid, "Force feedback support for Logitech Gaming Wheels (%s)\n", LG4FF_VERSION); - name = (void *)(&led[1]); - snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1); - led->name = name; - led->brightness = 0; - led->max_brightness = 1; - led->brightness_get = lg4ff_led_get_brightness; - led->brightness_set = lg4ff_led_set_brightness; + hid_info(hid, "Hires timer: period = %d ms", timer_msecs); - entry->wdata.led[j] = led; - error = led_classdev_register(&hid->dev, led); - - if (error) { - hid_err(hid, "failed to register LED %d. Aborting.\n", j); -err_leds: - /* Deregister LEDs (if any) */ - for (j = 0; j < 5; j++) { - led = entry->wdata.led[j]; - entry->wdata.led[j] = NULL; - if (!led) - continue; - led_classdev_unregister(led); - kfree(led); - } - goto out; /* Let the driver continue without LEDs */ - } - } - } -out: -#endif - hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n"); return 0; err_init: @@ -1447,6 +2305,8 @@ err_init: int lg4ff_deinit(struct hid_device *hid) { + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct input_dev *dev = hidinput->input; struct lg4ff_device_entry *entry; struct lg_drv_data *drv_data; @@ -1459,6 +2319,8 @@ int lg4ff_deinit(struct hid_device *hid) if (!entry) goto out; /* Nothing more to do */ + hrtimer_cancel(&entry->hrtimer); + /* Multimode devices will have at least the "MODE_NATIVE" bit set */ if (entry->wdata.alternate_modes) { device_remove_file(&hid->dev, &dev_attr_real_id); @@ -1467,11 +2329,33 @@ int lg4ff_deinit(struct hid_device *hid) device_remove_file(&hid->dev, &dev_attr_combine_pedals); device_remove_file(&hid->dev, &dev_attr_range); + + if (dev->ffbit) { + device_remove_file(&hid->dev, &dev_attr_gain); + if (test_bit(FF_AUTOCENTER, dev->ffbit)) { + device_remove_file(&hid->dev, &dev_attr_autocenter); + } + device_remove_file(&hid->dev, &dev_attr_peak_ffb_level); + if (test_bit(FF_SPRING, dev->ffbit)) { + device_remove_file(&hid->dev, &dev_attr_spring_level); + } + if (test_bit(FF_DAMPER, dev->ffbit)) { + device_remove_file(&hid->dev, &dev_attr_damper_level); + } + if (test_bit(FF_FRICTION, dev->ffbit)) { + device_remove_file(&hid->dev, &dev_attr_friction_level); + } + } + + lg4ff_stop_effects(entry); + #ifdef CONFIG_LEDS_CLASS - { + if (entry->has_leds) { int j; struct led_classdev *led; + device_remove_file(&hid->dev, &dev_attr_ffb_leds); + /* Deregister LEDs (if any) */ for (j = 0; j < 5; j++) { @@ -1484,6 +2368,7 @@ int lg4ff_deinit(struct hid_device *hid) } } #endif + drv_data->device_props = NULL; kfree(entry);