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