fbd-dev-led-qcom: Support QCOM LPG LED single color

QCOM LPG LEDs expose a hw_pattern sysfs entry which accepts a limited
set of patterns but allows to use dedicated hardware for blinking the
LED. This way, battery life can be improved and the LED still blinks,
even if the device is suspended.
This commit is contained in:
Dylan Van Assche
2024-06-09 11:21:20 +02:00
committed by Guido Günther
parent 22e4c33606
commit 10e6d3cba8
6 changed files with 302 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright (C) 2023 Dylan Van Assche <me@dylanvanassche.be>
*
* SPDX-License-Identifier: GPL-3.0+
*/
#pragma once
#include "fbd-dev-led-qcom.h"
#include <glib-object.h>
G_BEGIN_DECLS
gboolean fbd_dev_led_probe_qcom (FbdDevLed *led, GError **error);
G_END_DECLS
+118
View File
@@ -0,0 +1,118 @@
/*
* Copyright (C) 2023 Dylan Van Assche <me@dylanvanassche.be>
*
* SPDX-License-Identifier: GPL-3.0+
*/
#define G_LOG_DOMAIN "fbd-dev-led-qcom"
#include "fbd-dev-led-priv.h"
#include "fbd-dev-led-qcom.h"
#include "fbd-dev-led-qcom-priv.h"
#include "fbd-enums.h"
#include "fbd-udev.h"
#include <gio/gio.h>
#define LED_HW_PATTERN_ATTR "hw_pattern"
#define LED_REPEAT_ATTR "repeat"
#define LED_REPEAT_INFINITY "-1"
#define QCOM_LPG_MAX_PAUSE_MS 511
typedef struct _FbdDevLedQcom {
FbdDevLed parent;
} FbdDevLedQcom;
G_DEFINE_TYPE (FbdDevLedQcom, fbd_dev_led_qcom, FBD_TYPE_DEV_LED)
static gboolean
fbd_dev_led_qcom_probe (FbdDevLed *led, GError **error)
{
GUdevDevice *dev = fbd_dev_led_get_device (led);
const gchar *name;
const gchar * const *index;
name = g_udev_device_get_name (dev);
index = g_udev_device_get_sysfs_attr_as_strv (dev, LED_HW_PATTERN_ATTR);
if (index == NULL) {
g_set_error (error,
G_FILE_ERROR, G_FILE_ERROR_FAILED,
"%s is no QCOM LED with HW support", name);
return FALSE;
}
return FBD_DEV_LED_CLASS (fbd_dev_led_qcom_parent_class)->probe(led, error);
}
static gboolean
fbd_dev_led_qcom_start_periodic (FbdDevLed *led,
guint max_brightness_percentage,
guint freq)
{
GUdevDevice *dev = fbd_dev_led_get_device (led);
gdouble max;
gdouble t;
g_autofree gchar *str = NULL;
g_autoptr (GError) err = NULL;
gboolean success;
g_return_val_if_fail (max_brightness_percentage <= 100.0, FALSE);
max = fbd_dev_led_get_max_brightness (led) * (max_brightness_percentage / 100.0);
g_debug ("Using hw_pattern interface for QCOM LPG LED");
/* QCOM LPG LEDs can only pause up to 511ms */
/* ms mHz T/2 */
t = 1000.0 * 1000.0 / freq / 2.0;
if (t > QCOM_LPG_MAX_PAUSE_MS)
t = QCOM_LPG_MAX_PAUSE_MS;
str = g_strdup_printf ("0 %d 0 0 %d %d %d 0\n", (gint)t, (gint)max, (gint)t, (gint)max);
success = fbd_udev_set_sysfs_path_attr_as_string (dev, LED_REPEAT_ATTR, LED_REPEAT_INFINITY, &err);
if (!success) {
g_warning ("Failed to set LED repeat: %s", err->message);
goto failure;
}
success = fbd_udev_set_sysfs_path_attr_as_string (dev, LED_HW_PATTERN_ATTR, str, &err);
if (!success) {
g_warning ("Failed to set LED hw_pattern: %s", err->message);
goto failure;
}
g_debug ("Freq %d mHz, Brightness: %d%%, Blink pattern: %s, HW: yes", freq, max_brightness_percentage, str);
return TRUE;
failure:
g_warning("Falling back to software pattern");
return FBD_DEV_LED_CLASS (fbd_dev_led_qcom_parent_class)->start_periodic (
led, max_brightness_percentage, freq);
}
static void
fbd_dev_led_qcom_class_init (FbdDevLedQcomClass *klass)
{
FbdDevLedClass *fbd_dev_led_class = FBD_DEV_LED_CLASS (klass);
fbd_dev_led_class->probe = fbd_dev_led_qcom_probe;
fbd_dev_led_class->start_periodic = fbd_dev_led_qcom_start_periodic;
}
static void
fbd_dev_led_qcom_init (FbdDevLedQcom *self)
{
}
FbdDevLed *
fbd_dev_led_qcom_new (GUdevDevice *dev, GError **err)
{
return g_initable_new (FBD_TYPE_DEV_LED_QCOM, NULL, err, "dev", dev, NULL);
}
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Dylan Van Assche <me@dylanvanassche.be>
*
* SPDX-License-Identifier: GPL-3.0+
*/
#pragma once
#include "fbd-dev-led.h"
G_BEGIN_DECLS
#define FBD_TYPE_DEV_LED_QCOM fbd_dev_led_qcom_get_type()
G_DECLARE_FINAL_TYPE (FbdDevLedQcom, fbd_dev_led_qcom, FBD, DEV_LED_QCOM, FbdDevLed)
FbdDevLed *fbd_dev_led_qcom_new (GUdevDevice *dev, GError **err);
G_END_DECLS
+1
View File
@@ -17,6 +17,7 @@ sources = [
'fbd-dev-sound.c', 'fbd-dev-sound.c',
'fbd-dev-led.c', 'fbd-dev-led.c',
'fbd-dev-led-multicolor.c', 'fbd-dev-led-multicolor.c',
'fbd-dev-led-qcom.c',
'fbd-dev-leds.c', 'fbd-dev-leds.c',
'fbd-event.c', 'fbd-event.c',
'fbd-feedback-base.c', 'fbd-feedback-base.c',
@@ -0,0 +1,118 @@
P: /devices/platform/soc@0/c440000.spmi/spmi-0/0-03/c440000.spmi:pmic@3:pwm/leds/white:status
E: FEEDBACKD_TYPE=led
E: ID_FOR_SEAT=leds-platform-c440000_spmi-platform-c440000_spmi_pmic_3_pwm
E: ID_PATH=platform-c440000.spmi-platform-c440000.spmi:pmic@3:pwm
E: ID_PATH_TAG=platform-c440000_spmi-platform-c440000_spmi_pmic_3_pwm
E: OF_COMPATIBLE_N=0
E: OF_FULLNAME=/soc@0/spmi@c440000/pmic@3/pwm/led@5
E: OF_NAME=led
E: SUBSYSTEM=leds
E: TAGS=:seat:
A: brightness=0\n
L: device=../../../c440000.spmi:pmic@3:pwm
A: hw_pattern=
A: max_brightness=511\n
A: pattern=
A: power/control=auto\n
A: power/runtime_active_time=0\n
A: power/runtime_status=unsupported\n
A: power/runtime_suspended_time=0\n
A: repeat=-1\n
A: trigger=none kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlight cpu cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 cpu6 cpu7 activity default-on panic [pattern] mmc0 rfkill-any rfkill-none bluetooth-power hci0-power rfkill0 pmi8998-charger-online qcom-battery-charging-or-full qcom-battery-charging qcom-battery-full qcom-battery-charging-blink-full-solid phy0rx phy0tx phy0assoc phy0radio rfkill1\n
P: /devices/platform/soc@0/c440000.spmi/spmi-0/0-03/c440000.spmi:pmic@3:pwm
E: DRIVER=qcom-spmi-lpg
E: MODALIAS=of:NpwmT(null)Cqcom,pmi8998-lpg
E: OF_COMPATIBLE_0=qcom,pmi8998-lpg
E: OF_COMPATIBLE_N=1
E: OF_FULLNAME=/soc@0/spmi@c440000/pmic@3/pwm
E: OF_NAME=pwm
E: SUBSYSTEM=platform
L: driver=../../../../../../../bus/platform/drivers/qcom-spmi-lpg
A: driver_override=(null)\n
A: modalias=of:NpwmT(null)Cqcom,pmi8998-lpg\n
L: of_node=../../../../../../../firmware/devicetree/base/soc@0/spmi@c440000/pmic@3/pwm
A: power/control=auto\n
A: power/runtime_active_time=0\n
A: power/runtime_status=unsupported\n
A: power/runtime_suspended_time=0\n
P: /devices/platform/soc@0/c440000.spmi/spmi-0/0-03
E: DRIVER=pmic-spmi
E: MODALIAS=of:NpmicT(null)Cqcom,pmi8998Cqcom,spmi-pmic
E: OF_COMPATIBLE_0=qcom,pmi8998
E: OF_COMPATIBLE_1=qcom,spmi-pmic
E: OF_COMPATIBLE_N=2
E: OF_FULLNAME=/soc@0/spmi@c440000/pmic@3
E: OF_NAME=pmic
E: SUBSYSTEM=spmi
L: driver=../../../../../../bus/spmi/drivers/pmic-spmi
L: of_node=../../../../../../firmware/devicetree/base/soc@0/spmi@c440000/pmic@3
A: power/control=auto\n
A: power/runtime_active_time=124723680\n
A: power/runtime_status=active\n
A: power/runtime_suspended_time=0\n
P: /devices/platform/soc@0/c440000.spmi/spmi-0
E: MODALIAS=of:NspmiT(null)Cqcom,spmi-pmic-arb
E: OF_COMPATIBLE_0=qcom,spmi-pmic-arb
E: OF_COMPATIBLE_N=1
E: OF_FULLNAME=/soc@0/spmi@c440000
E: OF_NAME=spmi
E: SUBSYSTEM=spmi
L: of_node=../../../../../firmware/devicetree/base/soc@0/spmi@c440000
A: power/control=auto\n
A: power/runtime_active_time=0\n
A: power/runtime_status=unsupported\n
A: power/runtime_suspended_time=0\n
P: /devices/platform/soc@0/c440000.spmi
E: DRIVER=spmi_pmic_arb
E: MODALIAS=of:NspmiT(null)Cqcom,spmi-pmic-arb
E: OF_COMPATIBLE_0=qcom,spmi-pmic-arb
E: OF_COMPATIBLE_N=1
E: OF_FULLNAME=/soc@0/spmi@c440000
E: OF_NAME=spmi
E: SUBSYSTEM=platform
L: consumer:platform:c440000.spmi:pmic@0:adc@3100=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@0:adc@3100
L: consumer:platform:c440000.spmi:pmic@0:pon@800:pwrkey=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@0:pon@800:pwrkey
L: consumer:platform:c440000.spmi:pmic@0:pon@800:resin=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@0:pon@800:resin
L: consumer:platform:c440000.spmi:pmic@0:rtc@6000=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@0:rtc@6000
L: consumer:platform:c440000.spmi:pmic@0:temp-alarm@2400=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@0:temp-alarm@2400
L: consumer:platform:c440000.spmi:pmic@2:charger@1000=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@2:charger@1000
L: consumer:platform:c440000.spmi:pmic@2:fuel-gauge@4000=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@2:fuel-gauge@4000
L: consumer:platform:c440000.spmi:pmic@3:haptics@c000=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@3:haptics@c000
L: consumer:platform:c440000.spmi:pmic@3:leds@d800=../../../virtual/devlink/platform:c440000.spmi--platform:c440000.spmi:pmic@3:leds@d800
L: driver=../../../../bus/platform/drivers/spmi_pmic_arb
A: driver_override=(null)\n
A: modalias=of:NspmiT(null)Cqcom,spmi-pmic-arb\n
L: of_node=../../../../firmware/devicetree/base/soc@0/spmi@c440000
A: power/control=auto\n
A: power/runtime_active_time=0\n
A: power/runtime_status=unsupported\n
A: power/runtime_suspended_time=0\n
P: /devices/platform/soc@0
E: DRIVER=simple-pm-bus
E: MODALIAS=of:NsocT(null)Csimple-bus
E: OF_COMPATIBLE_0=simple-bus
E: OF_COMPATIBLE_N=1
E: OF_FULLNAME=/soc@0
E: OF_NAME=soc
E: SUBSYSTEM=platform
L: consumer:platform:17c90000.timer=../../virtual/devlink/platform:soc@0--platform:17c90000.timer
L: consumer:platform:894000.i2c=../../virtual/devlink/platform:soc@0--platform:894000.i2c
L: consumer:platform:898000.serial=../../virtual/devlink/platform:soc@0--platform:898000.serial
L: consumer:platform:a600000.usb=../../virtual/devlink/platform:soc@0--platform:a600000.usb
L: consumer:platform:a84000.serial=../../virtual/devlink/platform:soc@0--platform:a84000.serial
L: consumer:platform:a98000.i2c=../../virtual/devlink/platform:soc@0--platform:a98000.i2c
L: driver=../../../bus/platform/drivers/simple-pm-bus
A: driver_override=(null)\n
A: modalias=of:NsocT(null)Csimple-bus\n
L: of_node=../../../firmware/devicetree/base/soc@0
A: power/control=auto\n
A: power/runtime_active_time=0\n
A: power/runtime_status=unsupported\n
A: power/runtime_suspended_time=0\n
+31
View File
@@ -8,6 +8,7 @@
#include "fbd-feedback-led.h" #include "fbd-feedback-led.h"
#include "fbd-dev-led.h" #include "fbd-dev-led.h"
#include "fbd-dev-led-multicolor.h" #include "fbd-dev-led-multicolor.h"
#include "fbd-dev-led-qcom.h"
#include "testlib.h" #include "testlib.h"
@@ -67,6 +68,33 @@ test_fbd_dev_led_multicolor (FbdUmockdevFixture *fixture, gconstpointer unused)
} }
static void
test_fbd_dev_led_qcom_simple (FbdUmockdevFixture *fixture, gconstpointer unused)
{
GUdevClient *client;
g_autolist (GUdevDevice) leds = NULL;
FbdDevLed *led;
g_autoptr (GError) err = NULL;
GUdevDevice *dev;
client = g_udev_client_new ((const char *const []){ "leds", NULL});
leds = g_udev_client_query_by_subsystem (client, "leds");
/* There should be a single device only */
g_assert_cmpint (g_list_length (leds), ==, 1);
dev = G_UDEV_DEVICE (leds->data);
g_assert_cmpstr (g_udev_device_get_property (dev, "FEEDBACKD_TYPE"), ==, "led");
led = fbd_dev_led_qcom_new (dev, &err);
g_assert_no_error (err);
g_assert_cmpint (fbd_dev_led_get_max_brightness (led), ==, 511);
g_assert_true (fbd_dev_led_supports_color (led, FBD_FEEDBACK_LED_COLOR_WHITE));
g_assert_true (fbd_dev_led_start_periodic (led, 50, 50));
g_assert_finalize_object (led);
}
gint gint
main (gint argc, gchar *argv[]) main (gint argc, gchar *argv[])
{ {
@@ -78,6 +106,9 @@ main (gint argc, gchar *argv[])
FBD_UMOCKDEV_TEST_ADD ("/feedbackd/fbd/dev/led/multicolor", FBD_UMOCKDEV_TEST_ADD ("/feedbackd/fbd/dev/led/multicolor",
test_fbd_dev_led_multicolor, test_fbd_dev_led_multicolor,
"led-multicolor"); "led-multicolor");
FBD_UMOCKDEV_TEST_ADD ("/feedbackd/fbd/dev/led/qcom/simple",
test_fbd_dev_led_qcom_simple,
"led-qcom-simple");
return g_test_run(); return g_test_run();
} }