/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther * * See Documentation/ABI/testing/sysfs-class-led-trigger-pattern */ #define G_LOG_DOMAIN "fbd-dev-led" #include "fbd-dev-led.h" #include "fbd-dev-led-priv.h" #include "fbd-enums.h" #include "fbd-udev.h" #include #define LED_BRIGHTNESS_ATTR "brightness" #define LED_PATTERN_ATTR "pattern" enum { PROP_0, PROP_DEV, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; /** * FbdDevLed: * * A single color LED driven by Linux sysfs */ typedef struct _FbdDevLedPrivate { GUdevDevice *dev; guint max_brightness; FbdFeedbackLedColor color; } FbdDevLedPrivate; static void initable_iface_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE (FbdDevLed, fbd_dev_led, G_TYPE_OBJECT, G_ADD_PRIVATE (FbdDevLed) G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)) static gboolean fbd_dev_led_probe_default (FbdDevLed *led, GError **error) { FbdDevLedPrivate *priv = fbd_dev_led_get_instance_private (led); const gchar *name, *path; gboolean success = FALSE; name = g_udev_device_get_name (priv->dev); for (int i = 0; i <= FBD_FEEDBACK_LED_COLOR_LAST; i++) { g_autofree char *color = NULL; g_autofree char *enum_name = NULL; gchar *c; enum_name = g_enum_to_string (FBD_TYPE_FEEDBACK_LED_COLOR, i); c = strrchr (enum_name, '_'); color = g_ascii_strdown (c+1, -1); if (g_strstr_len (name, -1, color)) { guint brightness = g_udev_device_get_sysfs_attr_as_int (priv->dev, LED_MAX_BRIGHTNESS_ATTR); if (!brightness) continue; priv->color = i; fbd_dev_led_set_max_brightness (led, brightness); path = g_udev_device_get_sysfs_path (priv->dev); g_debug ("LED at '%s' usable for %s", path, color); success = TRUE; break; } } if (!success) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s not usable as RGB LED", name); } return success; } static gboolean fbd_dev_led_set_color_default (FbdDevLed *led, FbdFeedbackLedColor color, FbdLedRgbColor *rgb) { /* Ignore setting color as we have a single color only */ return TRUE; } static gboolean fbd_dev_led_supports_color_default (FbdDevLed *led, FbdFeedbackLedColor color) { FbdDevLedPrivate *priv; g_return_val_if_fail (FBD_IS_DEV_LED (led), FALSE); priv = fbd_dev_led_get_instance_private (led); return priv->color == color; } static gboolean fbd_dev_led_start_periodic_default (FbdDevLed *led, guint max_brightness_percentage, guint freq) { FbdDevLedPrivate *priv; g_autoptr (GError) err = NULL; g_autofree gchar *str = NULL; gboolean success = FALSE; gdouble max; gdouble t; g_return_val_if_fail (FBD_IS_DEV_LED (led), FALSE); g_return_val_if_fail (max_brightness_percentage <= 100, FALSE); priv = fbd_dev_led_get_instance_private (led); max = priv->max_brightness * (max_brightness_percentage / 100.0); /* ms mHz T/2 */ t = 1000.0 * 1000.0 / freq / 2.0; str = g_strdup_printf ("0 %d %d %d\n", (gint)t, (gint)max, (gint)t); g_debug ("Freq %d mHz, Brightness: %d%%, Blink pattern: %s", freq, max_brightness_percentage, str); success = fbd_udev_set_sysfs_path_attr_as_string (priv->dev, LED_PATTERN_ATTR, str, &err); if (!success) g_warning ("Failed to set led pattern: %s", err->message); return success; } static void fbd_dev_led_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { FbdDevLed *led = FBD_DEV_LED (object); FbdDevLedPrivate *priv = fbd_dev_led_get_instance_private (led); switch (property_id) { case PROP_DEV: priv->dev = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void fbd_dev_led_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { FbdDevLed *led = FBD_DEV_LED (object); FbdDevLedPrivate *priv = fbd_dev_led_get_instance_private (led); switch (property_id) { case PROP_DEV: g_value_set_object (value, priv->dev); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void fbd_dev_led_finalize (GObject *object) { FbdDevLed *self = FBD_DEV_LED (object); FbdDevLedPrivate *priv = fbd_dev_led_get_instance_private (self); g_clear_object (&priv->dev); G_OBJECT_CLASS (fbd_dev_led_parent_class)->finalize (object); } static gboolean initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { FbdDevLedClass *fbd_dev_led_class = FBD_DEV_LED_GET_CLASS (initable); return fbd_dev_led_class->probe (FBD_DEV_LED (initable), error); } static void initable_iface_init (GInitableIface *iface) { iface->init = initable_init; } static void fbd_dev_led_class_init (FbdDevLedClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FbdDevLedClass *fbd_dev_led_class = FBD_DEV_LED_CLASS (klass); object_class->get_property = fbd_dev_led_get_property; object_class->set_property = fbd_dev_led_set_property; object_class->finalize = fbd_dev_led_finalize; fbd_dev_led_class->probe = fbd_dev_led_probe_default; fbd_dev_led_class->start_periodic = fbd_dev_led_start_periodic_default; fbd_dev_led_class->set_color = fbd_dev_led_set_color_default; fbd_dev_led_class->supports_color = fbd_dev_led_supports_color_default; props[PROP_DEV] = g_param_spec_object ("dev", "", "", G_UDEV_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } static void fbd_dev_led_init (FbdDevLed *self) { } FbdDevLed * fbd_dev_led_new (GUdevDevice *dev, GError **err) { return g_initable_new (FBD_TYPE_DEV_LED, NULL, err, "dev", dev, NULL); } gboolean fbd_dev_led_set_brightness (FbdDevLed *led, guint brightness) { FbdDevLedPrivate *priv; g_autoptr (GError) err = NULL; g_return_val_if_fail (FBD_IS_DEV_LED (led), FALSE); priv = fbd_dev_led_get_instance_private (led); if (!fbd_udev_set_sysfs_path_attr_as_int (priv->dev, LED_BRIGHTNESS_ATTR, brightness, &err)) { g_warning ("Failed to setup brightness: %s", err->message); return FALSE; } return TRUE; } gboolean fbd_dev_led_start_periodic (FbdDevLed *led, guint max_brightness_percentage, guint freq) { FbdDevLedClass *fbd_dev_led_class = FBD_DEV_LED_GET_CLASS (led); g_return_val_if_fail (FBD_IS_DEV_LED (led), FALSE); return fbd_dev_led_class->start_periodic (led, max_brightness_percentage, freq); } gboolean fbd_dev_led_set_color (FbdDevLed *led, FbdFeedbackLedColor color, FbdLedRgbColor *rgb) { FbdDevLedClass *fbd_dev_led_class = FBD_DEV_LED_GET_CLASS (led); g_return_val_if_fail (FBD_IS_DEV_LED (led), FALSE); return fbd_dev_led_class->set_color (led, color, rgb); } gboolean fbd_dev_led_supports_color (FbdDevLed *led, FbdFeedbackLedColor color) { FbdDevLedClass *fbd_dev_led_class = FBD_DEV_LED_GET_CLASS (led); g_return_val_if_fail (FBD_IS_DEV_LED (led), FALSE); return fbd_dev_led_class->supports_color (led, color); } guint fbd_dev_led_get_max_brightness (FbdDevLed *led) { FbdDevLedPrivate *priv; g_return_val_if_fail (FBD_IS_DEV_LED (led), 0); priv = fbd_dev_led_get_instance_private (led); return priv->max_brightness; } /* Functions for derived classes */ GUdevDevice * fbd_dev_led_get_device (FbdDevLed *led) { FbdDevLedPrivate *priv; g_return_val_if_fail (FBD_IS_DEV_LED (led), NULL); priv = fbd_dev_led_get_instance_private (led); return priv->dev; } void fbd_dev_led_set_supported_color (FbdDevLed *led, FbdFeedbackLedColor color) { FbdDevLedPrivate *priv; g_return_if_fail (FBD_IS_DEV_LED (led)); priv = fbd_dev_led_get_instance_private (led); priv->color = color; } void fbd_dev_led_set_max_brightness (FbdDevLed *led, guint max_brightness) { FbdDevLedPrivate *priv; g_return_if_fail (FBD_IS_DEV_LED (led)); priv = fbd_dev_led_get_instance_private (led); priv->max_brightness = max_brightness; }