mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-07 12:25:00 -04:00
Add sign-of-life LED trigger module
Add a new configurable LED trigger that alters the LED blink pattern based on power supply status. By default: Device on or fully charged | LED is solid on Device is charging | LED blinks 700ms on/700ms off Device battery is 15% or lower | LED blinks 3800ms on/200ms off Change-Id: I5d0df2a7cb535679f8837542fb651f73db4924af Signed-off-by: Perry White <awhite6@motorola.com> Reviewed-on: https://gerrit.mot.com/2192503 SME-Granted: SME Approvals Granted SLTApproved: Slta Waiver Tested-by: Jira Key Reviewed-by: Drew Abbott <drewa@motorola.com> Reviewed-by: Konstantin Makariev <kmakariev@motorola.com> Submit-Approved: Jira Key
This commit is contained in:
parent
e08f4d31c1
commit
ca7e446885
4 changed files with 372 additions and 0 deletions
12
drivers/leds/trigger/Android.mk
Normal file
12
drivers/leds/trigger/Android.mk
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
DLKM_DIR := motorola/kernel/modules
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := ledtrig-sol.ko
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
ifeq ($(DLKM_INSTALL_TO_VENDOR_OUT),true)
|
||||
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/lib/modules/
|
||||
else
|
||||
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
|
||||
endif
|
||||
include $(DLKM_DIR)/AndroidKernelModule.mk
|
||||
5
drivers/leds/trigger/Kbuild
Normal file
5
drivers/leds/trigger/Kbuild
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
EXTRA_CFLAGS += -Wall
|
||||
EXTRA_CFLAGS += -I$(TOP)/motorola/kernel/modules/include
|
||||
|
||||
|
||||
obj-m += ledtrig-sol.o
|
||||
10
drivers/leds/trigger/Makefile
Normal file
10
drivers/leds/trigger/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
|
||||
|
||||
all:
|
||||
$(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS)
|
||||
|
||||
modules_install:
|
||||
$(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(M) modules_install
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean
|
||||
345
drivers/leds/trigger/ledtrig-sol.c
Normal file
345
drivers/leds/trigger/ledtrig-sol.c
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
/*
|
||||
* Description: Sign-of-life LED trigger
|
||||
*
|
||||
* Copyright (c) 2021 Lenovo, Inc. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/device/class.h>
|
||||
|
||||
#define CHARGING_DELAY_ON_DEFAULT (700)
|
||||
#define CHARGING_DELAY_OFF_DEFAULT (700)
|
||||
#define LOW_CAP_DELAY_ON_DEFAULT (3800)
|
||||
#define LOW_CAP_DELAY_OFF_DEFAULT (200)
|
||||
|
||||
#define LOW_CAP_THRESHOLD_DEFAULT (15)
|
||||
|
||||
struct sol_trig_data {
|
||||
struct led_classdev *led_cdev;
|
||||
struct notifier_block nb;
|
||||
unsigned long charging_delay_on;
|
||||
unsigned long charging_delay_off;
|
||||
unsigned long low_cap_delay_on;
|
||||
unsigned long low_cap_delay_off;
|
||||
int low_cap_threshold;
|
||||
bool mmi_battery_psy_present;
|
||||
bool battery_psy_present;
|
||||
};
|
||||
|
||||
static ssize_t charging_delay_on_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
|
||||
return sprintf(buf, "%lu\n", sol_data->charging_delay_on);
|
||||
}
|
||||
|
||||
static ssize_t charging_delay_on_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
unsigned long val;
|
||||
ssize_t ret;
|
||||
|
||||
ret = kstrtoul(buf, 10, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sol_data->charging_delay_on = val;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t charging_delay_off_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
|
||||
return sprintf(buf, "%lu\n", sol_data->charging_delay_off);
|
||||
}
|
||||
|
||||
static ssize_t charging_delay_off_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
unsigned long val;
|
||||
ssize_t ret;
|
||||
|
||||
ret = kstrtoul(buf, 10, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sol_data->charging_delay_off = val;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t low_cap_delay_on_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
|
||||
return sprintf(buf, "%lu\n", sol_data->low_cap_delay_on);
|
||||
}
|
||||
|
||||
static ssize_t low_cap_delay_on_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
unsigned long val;
|
||||
ssize_t ret;
|
||||
|
||||
ret = kstrtoul(buf, 10, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sol_data->low_cap_delay_on = val;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t low_cap_delay_off_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
|
||||
return sprintf(buf, "%lu\n", sol_data->low_cap_delay_off);
|
||||
}
|
||||
|
||||
static ssize_t low_cap_delay_off_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
unsigned long val;
|
||||
ssize_t ret;
|
||||
|
||||
ret = kstrtoul(buf, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sol_data->low_cap_delay_off = val;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t low_cap_threshold_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
|
||||
return sprintf(buf, "%d\n", sol_data->low_cap_threshold);
|
||||
}
|
||||
|
||||
static ssize_t low_cap_threshold_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
struct led_classdev *led_cdev = led_trigger_get_led(dev);
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
long val;
|
||||
ssize_t ret;
|
||||
|
||||
ret = kstrtol(buf, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val > 100) /* Max capacity is 100% */
|
||||
return -EINVAL;
|
||||
|
||||
sol_data->low_cap_threshold = val;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(charging_delay_on, 0644, charging_delay_on_show, charging_delay_on_store);
|
||||
static DEVICE_ATTR(charging_delay_off, 0644, charging_delay_off_show, charging_delay_off_store);
|
||||
static DEVICE_ATTR(low_capacity_delay_on, 0644, low_cap_delay_on_show, low_cap_delay_on_store);
|
||||
static DEVICE_ATTR(low_capacity_delay_off, 0644, low_cap_delay_off_show, low_cap_delay_off_store);
|
||||
static DEVICE_ATTR(low_capacity_threshold, 0644, low_cap_threshold_show, low_cap_threshold_store);
|
||||
|
||||
static struct attribute *sol_trig_attrs[] = {
|
||||
&dev_attr_charging_delay_on.attr,
|
||||
&dev_attr_charging_delay_off.attr,
|
||||
&dev_attr_low_capacity_delay_on.attr,
|
||||
&dev_attr_low_capacity_delay_off.attr,
|
||||
&dev_attr_low_capacity_threshold.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(sol_trig);
|
||||
|
||||
static int sol_trig_notifier_fn(struct notifier_block *nb, unsigned long action, void *data)
|
||||
{
|
||||
union power_supply_propval status;
|
||||
union power_supply_propval capacity;
|
||||
struct sol_trig_data *sol_data = container_of(nb, struct sol_trig_data, nb);
|
||||
struct led_classdev *led_cdev = sol_data->led_cdev;
|
||||
struct power_supply *psy = data;
|
||||
|
||||
/* We want to key the SOL LED off "mmi_battery" if possible. If mmi_battery is
|
||||
* unavailable, try the "battery" PSY. If neither of those are available, just
|
||||
* key off any battery PSY available.
|
||||
*/
|
||||
if (sol_data->mmi_battery_psy_present)
|
||||
{
|
||||
if (strcmp(psy->desc->name, "mmi_battery"))
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
else if (sol_data->battery_psy_present)
|
||||
{
|
||||
if (strcmp(psy->desc->name, "battery"))
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
else if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY)
|
||||
{
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status)) {
|
||||
dev_err(led_cdev->dev, "Failed to get PSY status.\n");
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
dev_dbg(led_cdev->dev, "%s: PSY status: %d\n", __func__, status.intval);
|
||||
|
||||
switch (status.intval) {
|
||||
case POWER_SUPPLY_STATUS_UNKNOWN: /* Fall-through */
|
||||
case POWER_SUPPLY_STATUS_FULL:
|
||||
led_set_brightness(led_cdev, LED_FULL);
|
||||
break;
|
||||
case POWER_SUPPLY_STATUS_CHARGING:
|
||||
led_blink_set(led_cdev, &sol_data->charging_delay_on, &sol_data->charging_delay_off);
|
||||
break;
|
||||
case POWER_SUPPLY_STATUS_DISCHARGING: /* Fall-through */
|
||||
case POWER_SUPPLY_STATUS_NOT_CHARGING: /* Fall-through */
|
||||
default:
|
||||
/*
|
||||
* If PSY capacity is equal-to or below a specified threshold, use a special low-capacity blink pattern.
|
||||
* Otherwise, set LED to full brightness by default.
|
||||
*/
|
||||
if (!power_supply_get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &capacity) &&
|
||||
capacity.intval <= sol_data->low_cap_threshold)
|
||||
led_blink_set(led_cdev, &sol_data->low_cap_delay_on, &sol_data->low_cap_delay_off);
|
||||
else
|
||||
led_set_brightness(led_cdev, LED_FULL);
|
||||
break;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int sol_trig_identify_psy(struct device *dev, void *data)
|
||||
{
|
||||
struct sol_trig_data *sol_data = data;
|
||||
struct power_supply *psy = dev_get_drvdata(dev);
|
||||
|
||||
if (!strcmp(psy->desc->name, "mmi_battery"))
|
||||
sol_data->mmi_battery_psy_present = true;
|
||||
|
||||
if (!strcmp(psy->desc->name, "battery"))
|
||||
sol_data->battery_psy_present = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sol_trig_activate(struct led_classdev *led_cdev)
|
||||
{
|
||||
int ret;
|
||||
struct sol_trig_data *sol_data;
|
||||
|
||||
sol_data = kzalloc(sizeof(*sol_data), GFP_KERNEL);
|
||||
if (!sol_data)
|
||||
return -ENOMEM;
|
||||
|
||||
sol_data->nb.notifier_call = sol_trig_notifier_fn;
|
||||
ret = power_supply_reg_notifier(&sol_data->nb);
|
||||
if (ret) {
|
||||
dev_err(led_cdev->dev, "Failed to register notifier: %d\n", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
sol_data->led_cdev = led_cdev;
|
||||
|
||||
sol_data->charging_delay_on = CHARGING_DELAY_ON_DEFAULT;
|
||||
sol_data->charging_delay_off = CHARGING_DELAY_OFF_DEFAULT;
|
||||
sol_data->low_cap_delay_on = LOW_CAP_DELAY_ON_DEFAULT;
|
||||
sol_data->low_cap_delay_off = LOW_CAP_DELAY_OFF_DEFAULT;
|
||||
sol_data->low_cap_threshold = LOW_CAP_THRESHOLD_DEFAULT;
|
||||
sol_data->mmi_battery_psy_present = false;
|
||||
sol_data->battery_psy_present = false;
|
||||
|
||||
class_for_each_device(power_supply_class, NULL, sol_data, sol_trig_identify_psy);
|
||||
|
||||
led_set_trigger_data(led_cdev, sol_data);
|
||||
|
||||
led_set_brightness(led_cdev, LED_FULL); /* Set default LED state */
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
kfree(sol_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sol_trig_deactivate(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct sol_trig_data *sol_data = led_get_trigger_data(led_cdev);
|
||||
|
||||
power_supply_unreg_notifier(&sol_data->nb);
|
||||
|
||||
kfree(sol_data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static struct led_trigger sol_led_trigger = {
|
||||
.name = "sign-of-life",
|
||||
.activate = sol_trig_activate,
|
||||
.deactivate = sol_trig_deactivate,
|
||||
.groups = sol_trig_groups,
|
||||
};
|
||||
|
||||
static int __init sol_trig_init(void)
|
||||
{
|
||||
return led_trigger_register(&sol_led_trigger);
|
||||
}
|
||||
|
||||
static void __exit sol_trig_exit(void)
|
||||
{
|
||||
led_trigger_unregister(&sol_led_trigger);
|
||||
}
|
||||
|
||||
module_init(sol_trig_init);
|
||||
module_exit(sol_trig_exit);
|
||||
|
||||
MODULE_AUTHOR("Andy White <awhite6@motorola.com>");
|
||||
MODULE_DESCRIPTION("Sign-of-life LED trigger");
|
||||
MODULE_LICENSE("GPL");
|
||||
Loading…
Reference in a new issue