1 /*
2 * Copyright (C) 2020 Purism SPC
3 * SPDX-License-Identifier: GPL-3.0+
4 * Author: Guido Günther <agx@sigxcpu.org>
5 *
6 * See Documentation/ABI/testing/sysfs-class-led-trigger-pattern
7 */
8
9 #define G_LOG_DOMAIN "fbd-dev-leds"
10
11 #include "fbd.h"
12 #include "fbd-enums.h"
13 #include "fbd-dev-leds.h"
14 #include "fbd-feedback-led.h"
15 #include "fbd-udev.h"
16
17 #include <gio/gio.h>
18
19 /**
20 * SECTION:fbd-dev-led
21 * @short_description: LED device interface
22 * @Title: FbdDevLeds
23 *
24 * #FbdDevLeds is used to interface with LEDS via sysfs
25 * It currently only supports one pattern per led at a time.
26 */
27
28 #define LED_BRIGHTNESS_ATTR "brightness"
29 #define LED_PATTERN_ATTR "pattern"
30 #define LED_SUBSYSTEM "leds"
31
32 typedef struct _FbdDevLed {
33 GUdevDevice *dev;
34 guint max_brightness;
35 /*
36 * We just use the colors from the feedback until we
37 * do rgb mixing, etc
38 */
39 FbdFeedbackLedColor color;
40 } FbdDevLed;
41
42 typedef struct _FbdDevLeds {
43 GObject parent;
44
45 GUdevClient *client;
46 GSList *leds;
47 } FbdDevLeds;
48
49 static void initable_iface_init (GInitableIface *iface);
50
51 G_DEFINE_TYPE_WITH_CODE (FbdDevLeds, fbd_dev_leds, G_TYPE_OBJECT,
52 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
53
54
55 static void
fbd_dev_led_free(FbdDevLed * led)56 fbd_dev_led_free (FbdDevLed *led)
57 {
58 g_object_unref (led->dev);
59 g_free (led);
60 }
61
62 static gboolean
fbd_dev_led_set_brightness(FbdDevLed * led,guint brightness)63 fbd_dev_led_set_brightness (FbdDevLed *led, guint brightness)
64 {
65 g_autoptr (GError) err = NULL;
66
67 if (!fbd_udev_set_sysfs_path_attr_as_int (led->dev, LED_BRIGHTNESS_ATTR, brightness, &err)) {
68 g_warning ("Failed to setup brightness: %s", err->message);
69 return FALSE;
70 }
71
72 return TRUE;
73 }
74
75 static FbdDevLed *
find_led_by_color(FbdDevLeds * self,FbdFeedbackLedColor color)76 find_led_by_color (FbdDevLeds *self, FbdFeedbackLedColor color)
77 {
78 g_return_val_if_fail (self->leds, NULL);
79
80 for (GSList *l = self->leds; l != NULL; l = l->next) {
81 FbdDevLed *led = l->data;
82 if (led->color == color)
83 return led;
84 }
85
86 /* If we did not match a color pick the first */
87 return self->leds->data;
88 }
89
90 static gboolean
initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)91 initable_init (GInitable *initable,
92 GCancellable *cancellable,
93 GError **error)
94 {
95 const gchar * const subsystems[] = { LED_SUBSYSTEM, NULL };
96 FbdDevLeds *self = FBD_DEV_LEDS (initable);
97 g_autolist (GUdevDevice) leds = NULL;
98 gboolean found = FALSE;
99
100 self->client = g_udev_client_new (subsystems);
101
102 leds = g_udev_client_query_by_subsystem (self->client, LED_SUBSYSTEM);
103
104 for (GList *l = leds; l != NULL; l = l->next) {
105 GUdevDevice *dev = G_UDEV_DEVICE (l->data);
106 const gchar *name, *path;
107 FbdDevLed *led = NULL;
108
109 if (g_strcmp0 (g_udev_device_get_property (dev, FEEDBACKD_UDEV_ATTR),
110 FEEDBACKD_UDEV_VAL_LED)) {
111 continue;
112 }
113 name = g_udev_device_get_name (dev);
114
115 /* We don't know anything about diffusors that can combine different
116 color LEDSs so go with fixed colors until the kernel gives us
117 enough information */
118 for (int i = 0; i <= FBD_FEEDBACK_LED_COLOR_LAST; i++) {
119 g_autofree char *color = NULL;
120 g_autofree char *enum_name = NULL;
121 gchar *c;
122
123 enum_name = g_enum_to_string (FBD_TYPE_FEEDBACK_LED_COLOR, i);
124 c = strrchr (enum_name, '_');
125 color = g_ascii_strdown (c+1, -1);
126 if (g_strstr_len (name, -1, color)) {
127 g_autoptr (GError) err = NULL;
128 guint brightness = g_udev_device_get_sysfs_attr_as_int (dev, "max_brightness");
129
130 if (!brightness)
131 continue;
132
133 led = g_malloc0 (sizeof(FbdDevLed));
134 led->dev = g_object_ref (dev);
135 led->color = i;
136 led->max_brightness = brightness;
137 path = g_udev_device_get_sysfs_path (dev);
138 g_debug ("LED at '%s' usable", path);
139 self->leds = g_slist_append (self->leds, led);
140 found = TRUE;
141 break;
142 }
143 }
144 }
145
146 /* TODO: listen for new leds via udev events */
147
148 if (!found) {
149 g_set_error (error,
150 G_FILE_ERROR, G_FILE_ERROR_FAILED,
151 "No usable LEDs found");
152 }
153
154 return found;
155 }
156
157 static void
initable_iface_init(GInitableIface * iface)158 initable_iface_init (GInitableIface *iface)
159 {
160 iface->init = initable_init;
161 }
162
163 static void
fbd_dev_leds_dispose(GObject * object)164 fbd_dev_leds_dispose (GObject *object)
165 {
166 FbdDevLeds *self = FBD_DEV_LEDS (object);
167
168 g_clear_object (&self->client);
169 g_slist_free_full (self->leds, (GDestroyNotify)fbd_dev_led_free);
170 self->leds = NULL;
171
172 G_OBJECT_CLASS (fbd_dev_leds_parent_class)->dispose (object);
173 }
174
175 static void
fbd_dev_leds_class_init(FbdDevLedsClass * klass)176 fbd_dev_leds_class_init (FbdDevLedsClass *klass)
177 {
178 GObjectClass *object_class = G_OBJECT_CLASS (klass);
179
180 object_class->dispose = fbd_dev_leds_dispose;
181 }
182
183 static void
fbd_dev_leds_init(FbdDevLeds * self)184 fbd_dev_leds_init (FbdDevLeds *self)
185 {
186 }
187
188 FbdDevLeds *
fbd_dev_leds_new(GError ** error)189 fbd_dev_leds_new (GError **error)
190 {
191 return FBD_DEV_LEDS (g_initable_new (FBD_TYPE_DEV_LEDS,
192 NULL,
193 error,
194 NULL));
195 }
196
197 /**
198 * fbd_dev_leds_start_periodic:
199 * @self: The #FbdDevLeds
200 * @color: The color to use for the LED pattern
201 * @max_brightness: The max brightness (in percent) to use for the pattern
202 * @freq: The pattern's frequency in mHz
203 *
204 * Start periodic feedback.
205 */
206 gboolean
fbd_dev_leds_start_periodic(FbdDevLeds * self,FbdFeedbackLedColor color,guint max_brightness,guint freq)207 fbd_dev_leds_start_periodic (FbdDevLeds *self, FbdFeedbackLedColor color,
208 guint max_brightness, guint freq)
209 {
210 FbdDevLed *led;
211 gdouble max;
212 gdouble t;
213 g_autofree gchar *str = NULL;
214
215 g_autoptr (GError) err = NULL;
216 gboolean success;
217
218 g_return_val_if_fail (FBD_IS_DEV_LEDS (self), FALSE);
219 led = find_led_by_color (self, color);
220 g_return_val_if_fail (led, FALSE);
221
222 max = led->max_brightness * (max_brightness / 100.0);
223 /* ms mHz T/2 */
224 t = 1000.0 * 1000.0 / freq / 2.0;
225 str = g_strdup_printf ("0 %d %d %d\n", (gint)t, (gint)max, (gint)t);
226 g_debug ("Freq %d mHz, Brightness: %d%%, Blink pattern: %s", freq, max_brightness, str);
227 success = fbd_udev_set_sysfs_path_attr_as_string (led->dev, LED_PATTERN_ATTR, str, &err);
228 if (!success)
229 g_warning ("Failed to set led pattern: %s", err->message);
230
231 return success;
232 }
233
234 gboolean
fbd_dev_leds_stop(FbdDevLeds * self,FbdFeedbackLedColor color)235 fbd_dev_leds_stop (FbdDevLeds *self, FbdFeedbackLedColor color)
236 {
237 FbdDevLed *led;
238
239 g_return_val_if_fail (FBD_IS_DEV_LEDS (self), FALSE);
240
241 led = find_led_by_color (self, color);
242 g_return_val_if_fail (led, FALSE);
243
244 return fbd_dev_led_set_brightness (led, 0);
245 }
246