Creates an LED trigger that changes LED behavior depending on the state of the controller battery. The trigger function runs on a 500 millisecond timer and only updates the LEDs if the controller power state has changed or a new device has been added to the trigger. The trigger sets the LEDs to solid if the controller is in wireless mode and the battery is above 20% power or if the controller is plugged in and the battery has completed charging. If the controller is not charging and the battery drops to or below 20% it blinks the LEDs in 500ms intervals. If the controller is plugged in and charging it blinks the LEDs in 1 second intervals. The order of subsystem initialization had to be changed in sony_probe() so that the trigger is created before the LEDs are initialized. By default the controller LEDs are set to the trigger local to that controller. Signed-off-by: Frank Praznik <frank.praznik@xxxxxxxxx> --- drivers/hid/hid-sony.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 162 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 914a6cc..d7889ac 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -730,6 +730,17 @@ struct sony_sc { struct work_struct state_worker; struct power_supply battery; +#ifdef CONFIG_LEDS_TRIGGERS + spinlock_t trigger_lock; + struct led_trigger battery_trigger; + struct timer_list battery_trigger_timer; + atomic_t trigger_device_added; + __u8 trigger_initialized; + __u8 trigger_timer_initialized; + __u8 trigger_capacity; + __u8 trigger_charging; +#endif + #ifdef CONFIG_SONY_FF __u8 left; __u8 right; @@ -1329,14 +1340,19 @@ static int sony_leds_init(struct sony_sc *sc) if (!(sc->quirks & BUZZ_CONTROLLER)) led->blink_set = sony_blink_set; +#ifdef CONFIG_LEDS_TRIGGERS + led->default_trigger = sc->battery_trigger.name; +#endif + + sc->leds[n] = led; + ret = led_classdev_register(&hdev->dev, led); if (ret) { hid_err(hdev, "Failed to register LED %d\n", n); kfree(led); + sc->leds[n] = NULL; goto error_leds; } - - sc->leds[n] = led; } return ret; @@ -1552,6 +1568,137 @@ static void sony_battery_remove(struct sony_sc *sc) sc->battery.name = NULL; } +#ifdef CONFIG_LEDS_TRIGGERS +static void sony_battery_trigger_callback(unsigned long data) +{ + struct sony_sc *drv_data = (struct sony_sc *)data; + struct led_classdev *led; + unsigned long flags; + unsigned long delay_on, delay_off; + int dev_added, ret; + __u8 charging, capacity; + + /* Check if new LEDs were added since the last time */ + dev_added = atomic_cmpxchg(&drv_data->trigger_device_added, 1, 0); + + /* Get the battery info */ + spin_lock_irqsave(&drv_data->lock, flags); + charging = drv_data->battery_charging; + capacity = drv_data->battery_capacity; + spin_unlock_irqrestore(&drv_data->lock, flags); + + /* Don't set the LEDs if nothing has changed */ + if (!dev_added && drv_data->trigger_capacity == capacity && + drv_data->trigger_charging == charging) + goto reset_timer; + + if (charging) { + /* Charging: blink at 1 sec intervals */ + delay_on = delay_off = 1000; + led_trigger_blink(&drv_data->battery_trigger, &delay_on, + &delay_off); + } else if (capacity <= 20) { + /* Low battery: blink at 500ms intervals */ + delay_on = delay_off = 500; + led_trigger_blink(&drv_data->battery_trigger, &delay_on, + &delay_off); + } else { + /* + * Normal: set the brightness to stop blinking + * + * This just walks the list of LEDs on the trigger and sets the + * brightness to the existing value. This leaves the brightness + * the same but the blinking is stopped. + */ + read_lock(&drv_data->battery_trigger.leddev_list_lock); + list_for_each_entry(led, + &drv_data->battery_trigger.led_cdevs, trig_list) { + led_set_brightness(led, led->brightness); + } + read_unlock(&drv_data->battery_trigger.leddev_list_lock); + } + + /* Cache the power state for next time */ + drv_data->trigger_charging = charging; + drv_data->trigger_capacity = capacity; + +reset_timer: + ret = mod_timer(&drv_data->battery_trigger_timer, + jiffies + msecs_to_jiffies(500)); + + if (ret < 0) + hid_err(drv_data->hdev, + "Failed to set battery trigger timer\n"); +} + +static void sony_battery_trigger_activate(struct led_classdev *led) +{ + struct sony_sc *sc; + + sc = container_of(led->trigger, struct sony_sc, battery_trigger); + + /* + * Set the device_added flag to tell the timer function that it + * should send an update even if the power state hasn't changed. + */ + atomic_set(&sc->trigger_device_added, 1); +} + +static int sony_battery_trigger_init(struct sony_sc *sc) +{ + int ret; + + sc->battery_trigger.name = kasprintf(GFP_KERNEL, + "%s-blink-low-or-charging", sc->battery.name); + if (!sc->battery.name) + return -ENOMEM; + + sc->battery_trigger.activate = sony_battery_trigger_activate; + + ret = led_trigger_register(&sc->battery_trigger); + if (ret < 0) + goto trigger_failure; + + setup_timer(&sc->battery_trigger_timer, + sony_battery_trigger_callback, (unsigned long)sc); + + ret = mod_timer(&sc->battery_trigger_timer, + jiffies + msecs_to_jiffies(500)); + if (ret < 0) + goto timer_failure; + + sc->trigger_initialized = 1; + + return 0; + +timer_failure: + led_trigger_unregister(&sc->battery_trigger); +trigger_failure: + kfree(sc->battery_trigger.name); + return ret; +} + +static void sony_battery_trigger_remove(struct sony_sc *sc) +{ + if (sc->trigger_initialized) { + del_timer_sync(&sc->battery_trigger_timer); + led_trigger_unregister(&sc->battery_trigger); + kfree(sc->battery_trigger.name); + } +} +#else +static int sony_battery_trigger_init(struct sony_sc *sc) +{ + /* Nothing to do */ + return 0; +} + +static void sony_battery_trigger_remove(struct sony_sc *sc) +{ + /* Nothing to do */ +} +#endif + static int sony_register_touchpad(struct sony_sc *sc, int touch_count, int w, int h) { @@ -1786,14 +1933,12 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret < 0) goto err_stop; - if (sc->quirks & SONY_LED_SUPPORT) { - ret = sony_leds_init(sc); + if (sc->quirks & SONY_BATTERY_SUPPORT) { + ret = sony_battery_probe(sc); if (ret < 0) goto err_stop; - } - if (sc->quirks & SONY_BATTERY_SUPPORT) { - ret = sony_battery_probe(sc); + ret = sony_battery_trigger_init(sc); if (ret < 0) goto err_stop; @@ -1805,6 +1950,12 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) } } + if (sc->quirks & SONY_LED_SUPPORT) { + ret = sony_leds_init(sc); + if (ret < 0) + goto err_stop; + } + if (sc->quirks & SONY_FF_SUPPORT) { ret = sony_init_ff(sc); if (ret < 0) @@ -1817,8 +1968,10 @@ err_close: err_stop: if (sc->quirks & SONY_LED_SUPPORT) sony_leds_remove(sc); - if (sc->quirks & SONY_BATTERY_SUPPORT) + if (sc->quirks & SONY_BATTERY_SUPPORT) { + sony_battery_trigger_remove(sc); sony_battery_remove(sc); + } sony_cancel_work_sync(sc); sony_remove_dev_list(sc); hid_hw_stop(hdev); @@ -1834,6 +1987,7 @@ static void sony_remove(struct hid_device *hdev) if (sc->quirks & SONY_BATTERY_SUPPORT) { hid_hw_close(hdev); + sony_battery_trigger_remove(sc); sony_battery_remove(sc); } -- 1.8.5.3 -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html