static int ffpl_handle_state_change(struct klgd_plugin_private *priv, struct klgd_command_stream *s, struct ffpl_effect *eff,
const unsigned long now);
-static bool ffpl_has_gain(const struct ff_effect *eff);
static bool ffpl_needs_replacing(const struct ff_effect *ac_eff, const struct ff_effect *la_eff);
void ffpl_lvl_dir_to_x_y(const s32 level, const u16 direction, s32 *x, s32 *y)
return 0;
}
+static int ffpl_set_gain(struct klgd_plugin_private *priv, struct klgd_command_stream *s)
+{
+ union ffpl_control_data data;
+
+ data.gain = priv->gain;
+ return priv->control(priv->dev, s, FFPL_SET_GAIN, data);
+}
+
static void ffpl_calculate_trip_times(struct ffpl_effect *eff, const unsigned long now)
{
const struct ff_effect *ueff = &eff->latest;
return 0;
}
-/*FIXME: Rewrite this! */
static void ffpl_set_gain_rq(struct input_dev *dev, u16 gain)
{
struct klgd_plugin *self = dev->ff->private;
klgd_lock_plugins(self->plugins_lock);
- printk(KERN_DEBUG "KLGDFF: Gain set, %u\n", gain);
+ priv->gain = gain;
+ priv->change_gain = true;
+ if (priv->has_native_gain)
+ goto out;
+
for (idx = 0; idx < priv->effect_count; idx++) {
struct ffpl_effect *eff = &priv->effects[idx];
- if (ffpl_has_gain(&eff->active))
- eff->change = FFPL_TO_UPDATE;
+ if (eff->state != FFPL_STARTED)
+ continue;
+
+ eff->change = FFPL_TO_UPDATE;
+ eff->trigger = FFPL_TRIG_NOW;
}
- /* priv->gain = gain;*/
+out:
klgd_unlock_plugins_sched(self->plugins_lock);
}
}
}
-static struct klgd_command_stream * ffpl_get_commands(struct klgd_plugin *self, const unsigned long now)
+static int ffpl_get_commands(struct klgd_plugin *self, struct klgd_command_stream **s, const unsigned long now)
{
struct klgd_plugin_private *priv = self->private;
- struct klgd_command_stream *s;
size_t idx;
int ret;
- s = klgd_alloc_stream();
+ *s = klgd_alloc_stream();
if (!s)
- return NULL; /* TODO: Error handling */
+ return -EAGAIN;
- ret = ffpl_handle_combinable_effects(priv, s, now);
- if (ret)
+ if (priv->change_gain) {
+ ret = ffpl_set_gain(priv, *s);
+ if (ret)
+ goto out;
+ priv->change_gain = false;
+ }
+
+ ret = ffpl_handle_combinable_effects(priv, *s, now);
+ if (ret) {
printk(KERN_WARNING "KLGDFF: Cannot process combinable effects, ret %d\n", ret);
+ goto out;
+ }
/* Handle combined effect here */
- ret = ffpl_handle_state_change(priv, s, &priv->combined_effect, now);
- if (ret)
+ ret = ffpl_handle_state_change(priv, *s, &priv->combined_effect, now);
+ if (ret) {
printk(KERN_WARNING "KLGDFF: Cannot get command stream for combined effect\n");
+ goto out;
+ }
for (idx = 0; idx < priv->effect_count; idx++) {
struct ffpl_effect *eff = &priv->effects[idx];
printk(KERN_NOTICE "KLGDFF: Processing effect %lu\n", idx);
- ret = ffpl_handle_state_change(priv, s, eff, now);
+ ret = ffpl_handle_state_change(priv, *s, eff, now);
/* TODO: Do something useful with the return code */
- if (ret)
+ if (ret) {
printk(KERN_WARNING "KLGDFF: Cannot get command stream for effect %lu\n", idx);
+ goto out;
+ }
ffpl_advance_trigger(eff, now);
}
-
- return s;
+out:
+ return ret;
}
static bool ffpl_get_update_time(struct klgd_plugin *self, const unsigned long now, unsigned long *t)
size_t idx;
unsigned long events = 0;
+ /* Handle device-wide changes first */
+ if (priv->change_gain) {
+ *t = now;
+ return true;
+ }
+
for (idx = 0; idx < priv->effect_count; idx++) {
unsigned long current_t;
struct ffpl_effect *eff = &priv->effects[idx];
return ret;
}
-static bool ffpl_has_gain(const struct ff_effect *eff)
-{
- switch (eff->type) {
- case FF_CONSTANT:
- case FF_PERIODIC:
- case FF_RAMP:
- case FF_RUMBLE:
- return true;
- default:
- return false;
- }
-}
-
static int ffpl_init(struct klgd_plugin *self)
{
struct klgd_plugin_private *priv = self->private;
priv->effect_count = effect_count;
priv->dev = dev;
priv->control = control;
+ priv->gain = 0xFFFF;
self->private = priv;
*plugin = self;
priv->has_owr_to_srt = true;
printk("KLGDFF: Using REPLACE STARTED\n");
}
+ if (FFPL_HAS_NATIVE_GAIN & flags) {
+ priv->has_native_gain = true;
+ printk(KERN_NOTICE "KLGDFF: Using HAS_NATIVE_GAIN\n");
+ }
set_bit(FF_GAIN, dev->ffbit);
for (idx = 0; idx <= (FF_WAVEFORM_MAX - FF_EFFECT_MIN); idx++) {
#define FFPL_ERASE_WHEN_STOPPED BIT(3) /* Erases effect from device when it is stopped - this implies HAS_SRT_TO_EMP */
#define FFPL_REPLACE_UPLOADED BIT(4) /* Device can accept a new effect to UPLOADED state without the need to explicitly stop and erase the previously uploaded effect beforehand */
#define FFPL_REPLACE_STARTED BIT(5) /* Device can accept a new effect to STARTED state without the need to explicitly stop and erase the previously uploaded effect beforehand */
+#define FFPL_HAS_NATIVE_GAIN BIT(15) /* Device can adjust the gain by itself */
enum ffpl_control_command {
/* Force feedback state transitions */
FFPL_EMP_TO_SRT, /* Upload and start effect */
FFPL_SRT_TO_EMP, /* Stop and erase started effect */
FFPL_OWR_TO_UPL, /* Overwrite an effect with a new one and set its state to UPLOADED */
- FFPL_OWR_TO_SRT /* Overwrite an effect with a new one and set its state to STARTED */
+ FFPL_OWR_TO_SRT, /* Overwrite an effect with a new one and set its state to STARTED */
+
+ FFPL_SET_GAIN /* Set gain */
};
struct ffpl_effects {
u16 gain;
};
-
void ffpl_lvl_dir_to_x_y(const s32 level, const u16 direction, s32 *x, s32 *y);
int ffpl_init_plugin(struct klgd_plugin **plugin, struct input_dev *dev, const size_t effect_count,
const unsigned long supported_effects,
enum ffpl_state {
FFPL_EMPTY, /* There is no effect in the slot */
FFPL_UPLOADED, /* Effect in the slot is uploaded to device */
- FFPL_STARTED, /* Effect in the slot is started on device */
+ FFPL_STARTED /* Effect in the slot is started on device */
};
/* What to do at the next timing trip point */
size_t effect_count;
struct input_dev *dev;
int (*control)(struct input_dev *dev, struct klgd_command_stream *s, const enum ffpl_control_command cmd, const union ffpl_control_data data);
+ u16 gain;
/* Optional device capabilities */
bool has_emp_to_srt;
bool has_srt_to_emp;
bool erase_when_stopped;
bool has_owr_to_upl;
bool has_owr_to_srt;
- u32 padding:26;
+ bool has_native_gain;
+ u32 padding_caps:25;
+ /* Device-wide state changes */
+ bool change_gain;
+ u32 padding_dw:31;
};
static struct input_dev *dev;
static struct klgd_main klgd;
static struct klgd_plugin *ff_plugin;
+static u16 gain;
static int klgdff_erase(struct klgd_command_stream *s, const struct ff_effect *effect)
{
return klgd_append_cmd(s, c);
}
+static int klgdff_set_gain(struct klgd_command_stream *s, const u16 _gain)
+{
+ char *text = kasprintf(GFP_KERNEL, "Setting gain to: %u", _gain);
+ size_t len = strlen(text);
+ struct klgd_command *c = klgd_alloc_cmd(len + 1);
+
+ if (!c)
+ return -ENOMEM;
+
+ gain = _gain;
+
+ memcpy(c->bytes, text, len);
+ kfree(text);
+ return klgd_append_cmd(s, c);
+}
+
static int klgdff_start(struct klgd_command_stream *s, const struct ff_effect *effect, const int repeat)
{
char *text;
{
s32 x;
s32 y;
+ s16 level = effect->u.constant.level * gain / 0xFFFF;
- ffpl_lvl_dir_to_x_y(effect->u.constant.level, effect->direction, &x, &y);
- text = kasprintf(GFP_KERNEL, "Playing FF_CONSTANT, level: %d, dir: %u, X: %d, Y: %d", effect->u.constant.level, effect->direction, x, y);
+ ffpl_lvl_dir_to_x_y(level, effect->direction, &x, &y);
+ text = kasprintf(GFP_KERNEL, "Playing FF_CONSTANT, level: %d, dir: %u, X: %d, Y: %d", level, effect->direction, x, y);
break;
}
default:
{
s32 x;
s32 y;
+ s16 level = effect->u.constant.level * gain / 0xFFFF;
- ffpl_lvl_dir_to_x_y(effect->u.constant.level, effect->direction, &x, &y);
- text = kasprintf(GFP_KERNEL, "Updating FF_CONSTANT, level: %d, dir: %u, X: %d, Y: %d", effect->u.constant.level, effect->direction, x, y);
+ ffpl_lvl_dir_to_x_y(level, effect->direction, &x, &y);
+ text = kasprintf(GFP_KERNEL, "Updating FF_CONSTANT, level: %d, dir: %u, X: %d, Y: %d", level, effect->direction, x, y);
break;
}
default:
{
s32 x;
s32 y;
+ s16 level = effect->u.constant.level * gain / 0xFFFF;
- ffpl_lvl_dir_to_x_y(effect->u.constant.level, effect->direction, &x, &y);
- text = kasprintf(GFP_KERNEL, "Uploading and starting FF_CONSTANT, level: %d, dir: %u, X: %d, Y: %d", effect->u.constant.level, effect->direction, x, y);
+ ffpl_lvl_dir_to_x_y(level, effect->direction, &x, &y);
+ text = kasprintf(GFP_KERNEL, "Uploading and starting FF_CONSTANT, level: %d, dir: %u, X: %d, Y: %d", level, effect->direction, x, y);
break;
}
default:
case FFPL_OWR_TO_UPL:
return klgdff_owr_upload(s, data.effects.cur, data.effects.old);
break;
+ case FFPL_SET_GAIN:
+ return klgdff_set_gain(s, data.gain);
default:
printk(KERN_NOTICE "KLGDFF-TD - Unhandled command\n");
break;
if (!klgdff_obj)
return -ENOMEM;
+
ret = klgd_init(&klgd, NULL, klgdff_callback, 1);
if (ret) {
printk(KERN_ERR "KLGDFF-TD: Cannot initialize KLGD\n");
dev->name = kasprintf(GFP_KERNEL, "KLGD-FF TestModule");
dev->uniq = kasprintf(GFP_KERNEL, "KLGD-FF TestModule-X");
dev->dev.parent = NULL;
+ gain = 0xFFFF;
input_set_capability(dev, EV_ABS, ABS_X);
input_set_capability(dev, EV_ABS, ABS_Y);