#include "klgd_ff_plugin_p.h"
#include <linux/slab.h>
+#include <linux/fixp-arith.h>
+#define DIR_TO_DEGREES(dir) (360 - ((((dir > 0xc000) ? (u32)dir + 0x4000 - 0xffff : (u32)dir + 0x4000) * 360) / 0xffff))
+
+static int ffpl_handle_state_change(struct klgd_plugin_private *priv, struct klgd_command_stream *s, struct ffpl_effect *eff,
+ const bool ignore_combined);
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);
+static u16 ffpl_degrees_from_x_y(const int x, const int y)
+{
+ u16 degrees;
+ /* Precalculated tan() values expanded by FRAC_N */
+ static const unsigned int precalc_tan[] = {0, 4, 8, 13, 17, 22, 26, 31, 35, 40, /* 0 - 9 deg */
+ 45, 49, 54, 59, 63, 68, 73, 78, 83, 88, /* 10 - 19 deg */
+ 93, 98, 108, 113, 119, 124, 130, 136, 141, /* 20 - 29 deg */
+ 147, 153, 159, 166, 172, 179, 185, 192, 200, 207, /* 30 - 39 deg */
+ 214, 222, 230, 238, 247, 255, 265, 274, 284, 294, /* 40 - 49 deg */
+ 305, 316, 327, 339, 352, 365, 379, 394, 409, 426, /* 50 - 59 deg */
+ 443, 461, 481, 502, 524, 548, 574, 603, 633, 666, /* 60 - 69 deg */
+ 703, 743, 787, 837, 892, 955, 1026, 1108, 1204, 1317, /* 70 - 79 deg */
+ 1451, 1616, 1821, 2084, 2435, 2926, 3660, 4884, 7330, 14666}; /* 80 - 89 deg */
+
+ for (degrees = 0; degrees < ARRAY_SIZE(precalc_tan); degrees++) {
+ unsigned int tan = (abs(y) << FRAC_N) / abs(x);
+ if (tan <= precalc_tan[degrees])
+ return degrees;
+ }
+
+ return 90;
+}
+
+static void ffpl_x_y_to_lvl_dir(const int x, const int y, s16 *level, u16 *direction)
+{
+ const u16 angle = (x == 0) ? 90 : ffpl_degrees_from_x_y(x, y);
+
+ printk(KERN_NOTICE "KLGDFF: Angle = %u\n", angle);
+ *level = int_sqrt(x * x + y * y);
+ /* 1st quadrant */
+ if (x >= 0 && y >= 0)
+ *direction = ((270 - angle) * 0xffff) / 360;
+ /* 2nd quadrant */
+ else if (x < 0 && y >= 0)
+ *direction = ((90 + angle) * 0xffff) / 360;
+ /* 3rd quadrant */
+ else if (x < 0 && y < 0)
+ *direction = ((90 - angle) * 0xffff) / 360;
+ /* 4th quadrant */
+ else if (x > 0 && y < 0)
+ *direction = ((270 + angle) * 0xffff) / 360;
+ else
+ *direction = 0;
+}
+
+static bool ffpl_is_combinable(const struct ff_effect *eff)
+{
+ /* TODO: Proper decision of what is a combinable effect */
+ return eff->type == FF_CONSTANT;
+}
+
+
+bool ffpl_constant_force_to_x_y(const struct ff_effect *eff, int *x, int *y)
+{
+ int degrees;
+
+ if (eff->type != FF_CONSTANT)
+ return false;
+
+ degrees = DIR_TO_DEGREES(eff->direction);
+ printk(KERN_NOTICE "KLGDFF: DIR_TO_DEGREES > Dir: %u, Deg: %u\n", eff->direction, degrees);
+ *x += (eff->u.constant.level * fixp_cos(degrees)) >> FRAC_N;
+ *y += (eff->u.constant.level * fixp_sin(degrees)) >> FRAC_N;
+
+ return true;
+}
+
+static void ffpl_recalc_combined(struct klgd_plugin_private *priv)
+{
+ size_t idx;
+ struct ff_effect *cb_latest = &priv->combined_effect.latest;
+ int x = 0;
+ int y = 0;
+
+ for (idx = 0; idx < priv->effect_count; idx++) {
+ const struct ffpl_effect *eff = &priv->effects[idx];
+
+ switch (eff->change) {
+ case FFPL_DONT_TOUCH:
+ if (eff->state != FFPL_STARTED)
+ break;
+ case FFPL_TO_START:
+ case FFPL_TO_UPDATE:
+ ffpl_constant_force_to_x_y(&eff->latest, &x, &y);
+ break;
+ default:
+ break;
+ }
+ }
+
+ ffpl_x_y_to_lvl_dir(x, y, &cb_latest->u.constant.level, &cb_latest->direction);
+ cb_latest->type = FF_CONSTANT;
+ printk(KERN_NOTICE "KLGDFF: Resulting combined CF effect > x: %d, y: %d, level: %d, direction: %d\n", x, y, cb_latest->u.constant.level,
+ cb_latest->direction);
+}
+
+static int ffpl_handle_combinable_effects(struct klgd_plugin_private *priv, struct klgd_command_stream *s)
+{
+ size_t idx;
+ bool needs_update = false;
+ size_t active_effects = 0;
+
+ for (idx = 0; idx < priv->effect_count; idx++) {
+ struct ffpl_effect *eff = &priv->effects[idx];
+
+ if (!ffpl_is_combinable(&eff->latest))
+ continue;
+
+ switch (eff->change) {
+ case FFPL_DONT_TOUCH:
+ if (eff->state == FFPL_STARTED)
+ active_effects++;
+ printk(KERN_NOTICE "KLGDFF: Unchanged combinable effect, total active effects %lu\n", active_effects);
+ break;
+ case FFPL_TO_START:
+ eff->state = FFPL_STARTED;
+ eff->active = eff->latest;
+ case FFPL_TO_UPDATE:
+ active_effects++;
+ needs_update = true;
+ printk(KERN_NOTICE "KLGDFF: Altered combinable effect, total active effects %lu\n", active_effects);
+ break;
+ default:
+ needs_update = true;
+ eff->state = FFPL_EMPTY;
+ printk(KERN_NOTICE "KLGDFF: Stopped combinable effect, total active effects %lu\n", active_effects);
+ break;
+ }
+ }
+
+ /* Combined effect needs recalculation */
+ if (needs_update) {
+ if (active_effects) {
+ printk(KERN_NOTICE "KLGDFF: Combined effect needs an update, total effects active: %lu\n", active_effects);
+ ffpl_recalc_combined(priv);
+ if (priv->combined_effect.state == FFPL_STARTED)
+ priv->combined_effect.change = FFPL_TO_UPDATE;
+ else
+ priv->combined_effect.change = FFPL_TO_START;
+
+ return 0;
+ }
+ /* No combinable effects are active, remove the effect from device */
+ printk(KERN_NOTICE "KLGDFF: No combinable effects are active, erase the combined effect from device\n");
+ priv->combined_effect.change = FFPL_TO_ERASE;
+ }
+
+ return 0;
+}
+
static int ffpl_erase_effect(struct klgd_plugin_private *priv, struct klgd_command_stream *s, struct ffpl_effect *eff)
{
if (eff->uploaded_to_device) {
eff->latest = *effect;
if (eff->state != FFPL_EMPTY) {
+ printk(KERN_NOTICE "KLGDFF: Updating effect in slot %d\n", effect->id);
if (ffpl_needs_replacing(&eff->active, &eff->latest)) {
eff->replace = true;
eff->change = FFPL_TO_UPLOAD;
struct klgd_plugin_private *priv = self->private;
struct klgd_command_stream *s;
size_t idx;
+ int ret;
s = klgd_alloc_stream();
if (!s)
return NULL; /* TODO: Error handling */
+ ret = ffpl_handle_combinable_effects(priv, s);
+ if (ret)
+ printk(KERN_WARNING "KLGDFF: Cannot process combinable effects, ret %d\n", ret);
+
for (idx = 0; idx < priv->effect_count; idx++) {
struct ffpl_effect *eff = &priv->effects[idx];
- int ret = 0;
-
- printk(KERN_NOTICE "KLGDFF - Processing effect %lu\n", idx);
- /* Latest effect is of different type than currently active effect,
- * remove it from the device and upload the latest one */
- if (eff->replace) {
- switch (eff->change) {
- case FFPL_TO_ERASE:
- printk("KLGDFF - Rpl chg - TO_ERASE\n");
- switch (eff->state) {
- case FFPL_STARTED:
- ret = ffpl_stop_effect(priv, s, eff);
- if (ret)
- break;
- case FFPL_UPLOADED:
- ret = ffpl_erase_effect(priv, s, eff);
- /* TODO: Handle error */
- default:
- /* Nothing to do - the effect that is replacing the old effect is about to be erased anyway
- * State of the effect to be replaced should also never be EMPTY */
- break;
- }
- break;
- case FFPL_TO_UPLOAD:
- case FFPL_TO_STOP: /* There is no difference between stopping or uploading an effect when we are replacing it */
- printk("KLGDFF - Rpl chg - TO_UPLOAD/TO_STOP\n");
- switch (eff->state) {
- case FFPL_STARTED:
- /* Overwrite the currently active effect and set it to UPLOADED state */
- if (priv->has_owr_to_upl) {
- ret = ffpl_replace_effect(priv, s, eff, FFPL_OWR_TO_UPL);
- if (ret)
- break;
- continue;
- }
- ret = ffpl_stop_effect(priv, s, eff);
- if (ret)
- break;
- case FFPL_UPLOADED:
- ret = ffpl_erase_effect(priv, s, eff);
- if (ret)
- break;
- case FFPL_EMPTY: /* State cannot actually be FFPL_EMPTY becuase only uploaded or started effects have to be replaced like this */
- ret = ffpl_upload_effect(priv, s, eff);
- break;
- }
- break;
- case FFPL_TO_START:
- case FFPL_TO_UPDATE: /* There is no difference between staring or updating an effect when we are replacing it */
- printk("KLGDFF - Rpl chg - TO_START/TO_UPDATE\n");
- switch (eff->state) {
- case FFPL_STARTED:
- if (priv->has_owr_to_srt) {
- ret = ffpl_replace_effect(priv, s, eff, FFPL_OWR_TO_SRT);
- if (ret)
- break;
- continue;
- }
- ret = ffpl_stop_effect(priv, s, eff);
- if (ret)
- break;
- case FFPL_UPLOADED:
- ret = ffpl_erase_effect(priv, s, eff);
- if (ret)
- break;
- case FFPL_EMPTY: /* State cannot actually be FFPL_EMPTY - same as above applies */
- ret = ffpl_upload_effect(priv, s, eff);
- if (ret)
- break;
- ret = ffpl_start_effect(priv, s, eff);
- break;
- }
- break;
- case FFPL_DONT_TOUCH:
- printk(KERN_WARNING "Got FFPL_DONT_TOUCH change for effect that should be replaced - this should not happen!\n");
- break;
- default:
- printk(KERN_WARNING "Unhandled state change while replacing effect\n");
- break;
- }
- if (ret)
- printk(KERN_WARNING "Error %d while replacing effect %lu\n", ret, idx);
- else {
- eff->replace = false;
- eff->change = FFPL_DONT_TOUCH;
- continue;
- }
- }
+ printk(KERN_NOTICE "KLGDFF: Processing effect %lu\n", idx);
+ ret = ffpl_handle_state_change(priv, s, eff, true);
+ /* TODO: Do something useful with the return code */
+ if (ret)
+ printk(KERN_WARNING "KLGDFF: Cannot get command stream effect %lu\n", idx);
+ }
+
+ /* Handle combined effect here */
+ ret = ffpl_handle_state_change(priv, s, &priv->combined_effect, false);
+ if (ret)
+ printk(KERN_WARNING "KLGDFF: Cannot get command stream for combined effect\n");
+
+ return s;
+}
+
+static bool ffpl_get_update_time(struct klgd_plugin *self, const unsigned long now, unsigned long *t)
+{
+ struct klgd_plugin_private *priv = self->private;
+ size_t idx, events = 0;
+
+ for (idx = 0; idx < priv->effect_count; idx++) {
+ struct ffpl_effect *eff = &priv->effects[idx];
+
+ /* Tell KLGD to attend to us as soon as possible if an effect has to change state */
+ if (eff->change == FFPL_DONT_TOUCH)
+ continue;
+ *t = now;
+ events++;
+ }
+
+ return events ? true : false;
+}
+
+static int ffpl_handle_state_change(struct klgd_plugin_private *priv, struct klgd_command_stream *s, struct ffpl_effect *eff,
+ const bool ignore_combined)
+{
+ int ret;
+
+ /* Latest effect is of different type than currently active effect,
+ * remove it from the device and upload the latest one */
+ if (eff->replace) {
switch (eff->change) {
- case FFPL_TO_UPLOAD:
- printk(KERN_INFO "KLGDFF - Chg TO_UPLOAD\n");
+ case FFPL_TO_ERASE:
+ printk(KERN_NOTICE "KLGDFF: Rpl chg - TO_ERASE\n");
switch (eff->state) {
- case FFPL_EMPTY:
- ret = ffpl_upload_effect(priv, s, eff);
- break;
case FFPL_STARTED:
ret = ffpl_stop_effect(priv, s, eff);
- break;
+ if (ret)
+ break;
+ case FFPL_UPLOADED:
+ ret = ffpl_erase_effect(priv, s, eff);
+ if (ret)
+ break;
default:
+ /* Nothing to do - the effect that is replacing the old effect is about to be erased anyway
+ * State of the effect to be replaced should also never be EMPTY */
+ ret = 0;
break;
}
break;
- case FFPL_TO_START:
- printk(KERN_INFO "KLGDFF - Chg TO_START\n");
+ case FFPL_TO_UPLOAD:
+ case FFPL_TO_STOP: /* There is no difference between stopping or uploading an effect when we are replacing it */
+ printk("KLGDFF: Rpl chg - TO_UPLOAD/TO_STOP\n");
switch (eff->state) {
- case FFPL_EMPTY:
- if (priv->has_emp_to_srt) {
- ret = ffpl_start_effect(priv, s, eff);
+ case FFPL_STARTED:
+ /* Overwrite the currently active effect and set it to UPLOADED state */
+ if (priv->has_owr_to_upl) {
+ ret = ffpl_replace_effect(priv, s, eff, FFPL_OWR_TO_UPL);
break;
}
- ret = ffpl_upload_effect(priv, s, eff);
+ ret = ffpl_stop_effect(priv, s, eff);
if (ret)
break;
case FFPL_UPLOADED:
- ret = ffpl_start_effect(priv, s, eff);
+ ret = ffpl_erase_effect(priv, s, eff);
+ if (ret)
+ break;
+ case FFPL_EMPTY: /* State cannot actually be FFPL_EMPTY becuase only uploaded or started effects have to be replaced like this */
+ /* Combinable effects are taken care of elsewhere and should not be uploaded individually */
+ if (!ffpl_is_combinable(&eff->latest))
+ ret = ffpl_upload_effect(priv, s, eff);
+ else
+ ret = 0;
break;
default:
- break;
+ printk(KERN_WARNING "KLGDFF: Unhandled effect state\n");
+ ret = -EINVAL;
}
break;
- case FFPL_TO_STOP:
- printk(KERN_INFO "KLGDFF - Chg TO_STOP\n");
+ case FFPL_TO_START:
+ case FFPL_TO_UPDATE: /* There is no difference between staring or updating an effect when we are replacing it */
+ printk("KLGDFF: Rpl chg - TO_START/TO_UPDATE\n");
switch (eff->state) {
case FFPL_STARTED:
+ if (priv->has_owr_to_srt) {
+ ret = ffpl_replace_effect(priv, s, eff, FFPL_OWR_TO_SRT);
+ break;
+ }
ret = ffpl_stop_effect(priv, s, eff);
- break;
- case FFPL_EMPTY:
- ret = ffpl_upload_effect(priv, s, eff);
- break;
- default:
- break;
- }
- break;
- case FFPL_TO_ERASE:
- printk(KERN_INFO "KLGDFF - Chg TO_ERASE\n");
- switch (eff->state) {
+ if (ret)
+ break;
case FFPL_UPLOADED:
ret = ffpl_erase_effect(priv, s, eff);
- break;
- case FFPL_STARTED:
- ret = ffpl_stop_effect(priv, s, eff);
if (ret)
break;
- ret = ffpl_erase_effect(priv, s, eff);
+ case FFPL_EMPTY: /* State cannot actually be FFPL_EMPTY - same as above applies */
+ /* Combinable effects are taken care of elsewhere and should not be uploaded and started individually */
+ if (ffpl_is_combinable(&eff->latest)) {
+ ret = 0;
+ break;
+ }
+ ret = ffpl_upload_effect(priv, s, eff);
+ if (ret)
+ break;
+ ret = ffpl_start_effect(priv, s, eff);
break;
default:
+ printk(KERN_WARNING "KLGDFF: Unhandled effect state\n");
+ ret = -EINVAL;
break;
}
break;
- case FFPL_TO_UPDATE:
- printk(KERN_INFO "KLGDFF - Chg TO_UPDATE\n");
- ret = ffpl_update_effect(priv, s, eff);
- break;
case FFPL_DONT_TOUCH:
- printk(KERN_INFO "KLGDFF - Chg - NO CHANGE\n");
- continue;
+ printk(KERN_WARNING "KLGDFF: Got FFPL_DONT_TOUCH change for effect that should be replaced - this should not happen!\n");
+ ret = -EINVAL;
+ break;
default:
- pr_debug("Unhandled state\n");
+ printk(KERN_WARNING "KLGDFF: Unhandled state change while replacing effect\n");
+ ret = -EINVAL;
+ break;
}
-
- /* TODO: Handle errors */
if (ret)
- printk(KERN_WARNING "Error %d while processing effect %lu\n", ret, idx);
- else
- eff->change = FFPL_DONT_TOUCH;
- }
-
- return s;
-}
+ printk(KERN_WARNING "KLGDFF: Error %d while replacing effect\n", ret);
-static bool ffpl_get_update_time(struct klgd_plugin *self, const unsigned long now, unsigned long *t)
-{
- struct klgd_plugin_private *priv = self->private;
- size_t idx, events = 0;
+ eff->replace = false;
+ eff->change = FFPL_DONT_TOUCH;
+ return ret;
+ }
- for (idx = 0; idx < priv->effect_count; idx++) {
- struct ffpl_effect *eff = &priv->effects[idx];
+ /* Combinable effects have already been handled, do not try to handle then again individually */
+ if (ffpl_is_combinable(&eff->latest) && ignore_combined) {
+ printk(KERN_NOTICE "KLGDFF: Effect is combinable\n");
+ ret = 0;
+ goto out;
+ }
- /* Tell KLGD to attend to us as soon as possible if an effect has to change state */
- if (eff->change == FFPL_DONT_TOUCH)
- continue;
- *t = now;
- events++;
+ switch (eff->change) {
+ case FFPL_TO_UPLOAD:
+ printk(KERN_INFO "KLGDFF: Chg TO_UPLOAD\n");
+ switch (eff->state) {
+ case FFPL_EMPTY:
+ ret = ffpl_upload_effect(priv, s, eff);
+ break;
+ case FFPL_STARTED:
+ ret = ffpl_stop_effect(priv, s, eff);
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ break;
+ case FFPL_TO_START:
+ printk(KERN_INFO "KLGDFF: Chg TO_START\n");
+ switch (eff->state) {
+ case FFPL_EMPTY:
+ if (priv->has_emp_to_srt) {
+ ret = ffpl_start_effect(priv, s, eff);
+ break;
+ }
+ ret = ffpl_upload_effect(priv, s, eff);
+ if (ret)
+ return ret;
+ case FFPL_UPLOADED:
+ ret = ffpl_start_effect(priv, s, eff);
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ break;
+ case FFPL_TO_STOP:
+ printk(KERN_INFO "KLGDFF: Chg TO_STOP\n");
+ switch (eff->state) {
+ case FFPL_STARTED:
+ ret = ffpl_stop_effect(priv, s, eff);
+ break;
+ case FFPL_EMPTY:
+ ret = ffpl_upload_effect(priv, s, eff);
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ break;
+ case FFPL_TO_ERASE:
+ printk(KERN_INFO "KLGDFF: Chg TO_ERASE\n");
+ switch (eff->state) {
+ case FFPL_UPLOADED:
+ ret = ffpl_erase_effect(priv, s, eff);
+ break;
+ case FFPL_STARTED:
+ ret = ffpl_stop_effect(priv, s, eff);
+ if (ret)
+ break;
+ ret = ffpl_erase_effect(priv, s, eff);
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ break;
+ case FFPL_TO_UPDATE:
+ printk(KERN_INFO "KLGDFF: Chg TO_UPDATE\n");
+ ret = ffpl_update_effect(priv, s, eff);
+ break;
+ case FFPL_DONT_TOUCH:
+ printk(KERN_INFO "KLGDFF: Chg - NO CHANGE\n");
+ return 0;
+ default:
+ return -EINVAL;
+ pr_debug("KLGDFF: Unhandled effect state change\n");
}
- return events ? true : false;
+out:
+ eff->change = FFPL_DONT_TOUCH;
+
+ return ret;
}
static bool ffpl_has_gain(const struct ff_effect *eff)
input_set_capability(dev, EV_FF, idx + FF_EFFECT_MIN);
}
}
+
return 0;
err_out2:
static bool ffpl_needs_replacing(const struct ff_effect *ac_eff, const struct ff_effect *la_eff)
{
if (ac_eff->type != la_eff->type) {
- printk(KERN_NOTICE "KLGDFF - Effects are of different type - replacing (%d x %d)\n", ac_eff->type, la_eff->type);
+ printk(KERN_NOTICE "KLGDFF: Effects are of different type - replacing (%d x %d)\n", ac_eff->type, la_eff->type);
return true;
}
if (ac_eff->type == FF_PERIODIC) {
if (ac_eff->u.periodic.waveform != la_eff->u.periodic.waveform) {
- printk(KERN_NOTICE "KLGDFF - Effects have different waveforms - replacing\n");
+ printk(KERN_NOTICE "KLGDFF: Effects have different waveforms - replacing\n");
return true;
}
}
- printk(KERN_NOTICE "KLGDFF - Effect does not have to be replaced, updating\n");
+ printk(KERN_NOTICE "KLGDFF: Effect does not have to be replaced, updating\n");
return false;
}