-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathsysfs-led-main.c
1057 lines (870 loc) · 27.7 KB
/
sysfs-led-main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/** @file sysfs-led-main.c
*
* mce-plugin-libhybris - Libhybris plugin for Mode Control Entity
* <p>
* Copyright (c) 2013 - 2017 Jolla Ltd.
* Copyright (c) 2024 Jollyboys Ltd.
* <p>
* @author Simo Piiroinen <[email protected]>
*
* mce-plugin-libhybris is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License.
*
* mce-plugin-libhybris is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with mce-plugin-libhybris; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "sysfs-led-main.h"
#include "sysfs-led-util.h"
#include "sysfs-led-vanilla.h"
#include "sysfs-led-hammerhead.h"
#include "sysfs-led-bacon.h"
#include "sysfs-led-f5121.h"
#include "sysfs-led-htcvision.h"
#include "sysfs-led-binary.h"
#include "sysfs-led-redgreen.h"
#include "sysfs-led-white.h"
#include "sysfs-led-mind2-v1.h"
#include "sysfs-led-mind2-v2.h"
#include "plugin-logging.h"
#include "plugin-config.h"
#include "plugin-quirks.h"
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <glib.h>
/* ========================================================================= *
* CONSTANTS
* ========================================================================= */
/** Questimate of the duration of the kernel delayed work */
#define SYSFS_LED_KERNEL_DELAY 10 // [ms]
/** Minimum delay between breathing steps */
#define SYSFS_LED_STEP_DELAY 50 // [ms]
/** Maximum number of breathing steps; rise and fall time combined */
#define SYSFS_LED_MAX_STEPS 256
/** Minimum number of breathing steps on rise/fall time */
#define SYSFS_LED_MIN_STEPS 5
/* ========================================================================= *
* PROTOTYPES
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* LED_CONTROL
*
* Frontend for controlling (assumed to be RGB) led via sysfs
* ------------------------------------------------------------------------- */
static void led_control_enable (led_control_t *self, bool enable);
static void led_control_blink (led_control_t *self, int on_ms, int off_ms);
static void led_control_value (led_control_t *self, int r, int g, int b);
static void led_control_init (led_control_t *self);
static bool led_control_can_breathe (const led_control_t *self);
static led_ramp_t led_control_breath_type (const led_control_t *self);
static bool led_control_probe (led_control_t *self);
void led_control_close (led_control_t *self);
/* ------------------------------------------------------------------------- *
* LED_STATE
*
* Managing logical state of indicator led
* ------------------------------------------------------------------------- */
/** Led state
*/
typedef struct
{
int r,g,b; // color
int on,off; // blink timing
int level; // brightness [0 ... 255]
bool breathe; // breathe instead of blinking
} led_state_t;
/** Different styles of led patterns
*/
typedef enum
{
STYLE_OFF, // led is off
STYLE_STATIC, // led has constant color
STYLE_BLINK, // led is blinking with on/off periods
STYLE_BREATH, // led is breathing with rise/fall times
} led_style_t;
static bool led_state_has_equal_timing (const led_state_t *self, const led_state_t *that);
static bool led_state_is_equal (const led_state_t *self, const led_state_t *that);
static bool led_state_has_color (const led_state_t *self);
static void led_state_sanitize (led_state_t *self);
static led_style_t led_state_get_style (const led_state_t *self);
/* ------------------------------------------------------------------------- *
* SYSFS_LED
*
* Top level sysfs led functionality used by mce hybris plugin.
* ------------------------------------------------------------------------- */
static void sysfs_led_close_files (void);
static bool sysfs_led_probe_files (void);
static void sysfs_led_set_rgb_blink (int on, int off);
static void sysfs_led_set_rgb_value (int r, int g, int b);
static void sysfs_led_generate_ramp_sine (int ms_on, int ms_off, bool half);
static void sysfs_led_generate_ramp_hard_step (int ms_on, int ms_off);
static void sysfs_led_generate_ramp_triangle (int ms_on, int ms_off);
static void sysfs_led_generate_ramp_sawtooth (int ms_on, int ms_off);
static void sysfs_led_generate_ramp_dummy (void);
static void sysfs_led_generate_ramp (int ms_on, int ms_off);
static gboolean sysfs_led_static_cb (gpointer aptr);
static gboolean sysfs_led_step_cb (gpointer aptr);
static gboolean sysfs_led_stop_cb (gpointer aptr);
static void sysfs_led_start (void);
static void sysfs_led_wait_kernel (void);
bool sysfs_led_init (void);
void sysfs_led_quit (void);
bool sysfs_led_set_pattern (int r, int g, int b, int ms_on, int ms_off);
bool sysfs_led_can_breathe (void);
void sysfs_led_set_breathing (bool enable);
void sysfs_led_set_brightness (int level);
/* ========================================================================= *
* LED_CONTROL
* ========================================================================= */
/** Set RGB LED enabled/disable
*
* @param self control object
* @param enable true for enable, or false for disable
*/
static void
led_control_enable(led_control_t *self, bool enable)
{
if( self->enable )
{
self->enable(self->data, enable);
}
}
/** Set RGB LED blinking period
*
* If both on and off are greater than zero, then the PWM generator
* is used to full intensity blinking. Otherwise it is used for
* adjusting the LED brightness.
*
* @param self control object
* @param on milliseconds on
* @param off milliseconds off
*/
static void
led_control_blink(led_control_t *self, int on_ms, int off_ms)
{
if( self->blink )
{
led_control_enable(self, false);
self->blink(self->data, on_ms, off_ms);
}
}
/** Set RGB LED color
*
* @param self control object
* @param r red intensity (0 ... 255)
* @param g green intensity (0 ... 255)
* @param b blue intensity (0 ... 255)
*/
static void
led_control_value(led_control_t *self, int r, int g, int b)
{
if( self->value )
{
led_control_enable(self, false);
self->value(self->data, r, g, b);
led_control_enable(self, true);
}
}
/** Reset RGB led control object
*
* Initialize control object to closed but valid state.
*
* @param self uninitialized control object
*/
static void
led_control_init(led_control_t *self)
{
self->name = 0;
self->data = 0;
self->enable = 0;
self->blink = 0;
self->value = 0;
self->close = 0;
/* Assume paths from config are not to be used */
self->use_config = false;
/* Assume that it is exceptional if sw breathing can't be supported */
self->can_breathe = true;
/* And half sine curve should be used for breathing */
self->breath_type = LED_RAMP_HALF_SINE;
}
/** Query if backend can support sw breathing
*
* @return true if breathing can be enabled, false otherwise
*/
static bool
led_control_can_breathe(const led_control_t *self)
{
return self->can_breathe;
}
/** Query type of sw breathing backend supports
*
* @return LED_RAMP_DISABLED, LED_RAMP_HALF_SINE, or ...
*/
static led_ramp_t
led_control_breath_type(const led_control_t *self)
{
return self->can_breathe ? self->breath_type : LED_RAMP_DISABLED;
}
/** Probe sysfs for RGB LED controls
*
* @param self control object
*
* @return true if required control files were available, false otherwise
*/
static bool
led_control_probe(led_control_t *self)
{
typedef bool (*led_control_probe_fn)(led_control_t *);
/* The probing should be done in order that minimizes
* chances of false positives.
*/
static const struct
{
const char *name;
led_control_probe_fn func;
} lut[] =
{
/* The hammerhead backend requires presense of
* unique 'on_off_ms' and 'rgb_start' files. */
{ "hammerhead", led_control_hammerhead_probe },
/* The htc vision backend requires presense of
* unique 'amber' control directory. */
{ "htcvision", led_control_htcvision_probe },
/* The bacon backend */
{ "bacon", led_control_bacon_probe },
/* The f5121 requires 'brightness', 'max_brightness' and 'blink'
* control files to be present for red, green and blue channels. */
{ "f5121", led_control_f5121_probe },
/* The vanilla backend requires only 'brightness'
* control file, but still needs three directories
* to be present for red, green and blue channels. */
{ "vanilla", led_control_vanilla_probe },
/* The redgreen uses subset of "standard" rgb led
* control paths, so to avoid false positive matches
* it must be probed after rgb led controls. */
{ "redgreen", led_control_redgreen_probe },
/* Single control channel with actually working
* brightness control and max_brightness. */
{ "white", led_control_white_probe },
/* The binary backend needs just one directory
* that has 'brightness' control file. */
{ "binary", led_control_binary_probe },
/* Arrangement of two rgb leds
* with 0/1 red, green, blue color selection
* and 0-N brightness control
* plus master power toggle governing both leds. */
{ "mind2v1", led_control_mind2v1_probe },
/* Arrangement of two rgb leds
* with 0-N red, green, blue color selection
* plus master power toggle governing both leds. */
{ "mind2v2", led_control_mind2v2_probe },
};
bool ack = false;
gchar *name = plugin_config_get_string(MCE_CONF_LED_CONFIG_HYBRIS_GROUP,
MCE_CONF_LED_CONFIG_HYBRIS_BACKEND,
0);
for( size_t i = 0; i < G_N_ELEMENTS(lut); ++i )
{
led_control_close(self);
if( name ) {
if( strcmp(lut[i].name, name) ) {
continue;
}
self->use_config = true;
}
if( name && strcmp(lut[i].name, name) )
{
continue;
}
mce_log(LL_DEBUG, "probing sysfs led backend: %s", lut[i].name);
if( !lut[i].func(self) )
{
continue;
}
self->can_breathe = QUIRK(QUIRK_BREATHING_ENABLED, self->can_breathe);
self->breath_type = QUIRK(QUIRK_BREATHING_TYPE, self->breath_type);
ack = true;
break;
}
g_free(name);
return ack;
}
/** Set RGB LED enabled/disable
*
* @param self control object
* @param enable true for enable, or false for disable
*/
void
led_control_close(led_control_t *self)
{
if( self->close )
{
self->close(self->data);
}
led_control_init(self);
}
/* ========================================================================= *
* LED_STATE
* ========================================================================= */
/** Test for led request blink/breathing timing equality
*/
static bool
led_state_has_equal_timing(const led_state_t *self, const led_state_t *that)
{
return (self->on == that->on &&
self->off == that->off);
}
/** Test for led request equality
*/
static bool
led_state_is_equal(const led_state_t *self, const led_state_t *that)
{
return (self->r == that->r &&
self->g == that->g &&
self->b == that->b &&
self->on == that->on &&
self->off == that->off &&
self->level == that->level &&
self->breathe == that->breathe);
}
/** Test for active led request
*/
static bool
led_state_has_color(const led_state_t *self)
{
return self->r > 0 || self->g > 0 || self->b > 0;
}
/** Normalize/sanity check requested values
*/
static void
led_state_sanitize(led_state_t *self)
{
int min_period = SYSFS_LED_STEP_DELAY * SYSFS_LED_MIN_STEPS;
if( !led_state_has_color(self) ) {
/* blinking/breathing black and black makes no sense */
self->on = 0;
self->off = 0;
self->breathe = false;
}
else if( self->on <= 0 || self->off <= 0) {
/* both on and off periods must be > 0 for blinking/breathing */
self->on = 0;
self->off = 0;
self->breathe = false;
}
else if( self->on < min_period || self->off < min_period ) {
/* Whether a pattern should breathe or not is decided at mce side.
* But, since there are limitations on how often the led intensity
* can be changed, we must check that the rise/fall times are long
* enough to allow a reasonable amount of adjustments to be made. */
self->breathe = false;
}
}
/** Evaluate request style
*/
static led_style_t
led_state_get_style(const led_state_t *self)
{
if( !led_state_has_color(self) ) {
return STYLE_OFF;
}
if( self->on <= 0 || self->off <= 0 ) {
return STYLE_STATIC;
}
if( self->breathe ) {
return STYLE_BREATH;
}
return STYLE_BLINK;
}
/* ========================================================================= *
* SYSFS_LED
* ========================================================================= */
/** Currently used intensity curve for sw breathing */
static struct {
size_t step;
size_t steps;
int delay;
uint8_t value[SYSFS_LED_MAX_STEPS];
} sysfs_led_breathe =
{
.step = 0,
.steps = 0,
.delay = 0,
};
/** Currently active RGB led state; initialize to invalid color */
static led_state_t sysfs_led_curr =
{
/* force 1st change to take effect by initializing to invalid color */
.r = -1,
.g = -1,
.b = -1,
};
/** Target RGB led state; initialize to off */
static led_state_t sysfs_led_next =
{
/* color is "black" i.e. led is off */
.r = 0,
.g = 0,
.b = 0,
/* not blinking or breathing */
.on = 0,
.off = 0,
.breathe = false,
/* full brightness */
.level = 255,
};
static led_control_t led_control;
/** Close all LED sysfs files
*/
static void
sysfs_led_close_files(void)
{
led_control_close(&led_control);
}
/** Open sysfs control files for RGB leds
*
* @return true if required control files were available, false otherwise
*/
static bool
sysfs_led_probe_files(void)
{
led_control_init(&led_control);
bool probed = led_control_probe(&led_control);
/* Note: As there are devices that do not have indicator
* led, a ailures to find a suitable backend must
* be assumed to be ok and not logged in the default
* verbosity level.
*/
mce_log(LL_NOTICE, "led sysfs backend: %s",
probed ? led_control.name : "N/A");
return probed;
}
/** Change blinking attributes of RGB led
*/
static void
sysfs_led_set_rgb_blink(int on, int off)
{
mce_log(LOG_DEBUG, "on_ms = %d, off_ms = %d", on, off);
led_control_blink(&led_control, on, off);
}
/** Change intensity attributes of RGB led
*/
static void
sysfs_led_set_rgb_value(int r, int g, int b)
{
mce_log(LOG_DEBUG, "rgb = %d %d %d", r, g, b);
led_control_value(&led_control, r, g, b);
}
/** Generate half sine intensity curve for use from breathing timer
*/
static void
sysfs_led_generate_ramp_sine(int ms_on, int ms_off, bool half)
{
int t = ms_on + ms_off;
int s = (t + SYSFS_LED_MAX_STEPS - 1) / SYSFS_LED_MAX_STEPS;
if( s < SYSFS_LED_STEP_DELAY ) {
s = SYSFS_LED_STEP_DELAY;
}
int n = (t + s - 1) / s;
int steps_on = (n * ms_on + t / 2) / t;
int steps_off = n - steps_on;
/* Calculate a non-zero value for each step on the ramp.
*/
int k = 0;
if( half ) {
const float m_pi_2 = (float)M_PI_2;
for( int i = 0; i < steps_on; ++i ) {
float a = i * m_pi_2 / steps_on;
sysfs_led_breathe.value[k++] = (uint8_t)roundf(led_util_ftrans(sinf(a), 0, 1, 1, 255));
}
for( int i = 0; i < steps_off; ++i ) {
float a = m_pi_2 + i * m_pi_2 / steps_off;
sysfs_led_breathe.value[k++] = (uint8_t)roundf(led_util_ftrans(sinf(a), 0, 1, 1, 255));
}
}
else {
const float m_pi = (float)M_PI;
for( int i = 0; i < steps_on; ++i ) {
float a = (0.5f * m_pi) + (i * m_pi / steps_on);
sysfs_led_breathe.value[k++] = (uint8_t)roundf(led_util_ftrans(sinf(a), -1, +1, 1, 255));
}
for( int i = 0; i < steps_off; ++i ) {
float a = (1.5f * m_pi) + (i * m_pi / steps_off);
sysfs_led_breathe.value[k++] = (uint8_t)roundf(led_util_ftrans(sinf(a), -1, +1, 1, 255));
}
}
sysfs_led_breathe.delay = s;
sysfs_led_breathe.steps = k;
mce_log(LL_DEBUG, "delay=%d, steps_on=%d, steps_off=%d",
sysfs_led_breathe.delay, steps_on, steps_off);
}
/** Generate hard step intensity curve for use from breathing timer
*/
static void
sysfs_led_generate_ramp_hard_step(int ms_on, int ms_off)
{
/* Calculate ramp duration - round up given on/off lengths
* to avoid totally bizarre values that could cause excessive
* number of timer wakeups.
*/
ms_on = led_util_roundup(ms_on, 100);
ms_off = led_util_roundup(ms_off, 100);
int ms_tot = ms_on + ms_off;
/* Ideally we would want to wake up only to flip the led
* on/off. But to keep using the existing ramp timer logic,
* we wake up in pace of the greatest common divisor for on
* and off periods.
*/
int ms_step = led_util_gcd(ms_on, ms_off);
if( ms_step < SYSFS_LED_STEP_DELAY ) {
ms_step = SYSFS_LED_STEP_DELAY;
}
/* Calculate number of steps we need and make sure it does
* not exceed the defined maximum value.
*/
int steps_tot = (ms_tot + ms_step - 1) / ms_step;
if( steps_tot > SYSFS_LED_MAX_STEPS ) {
steps_tot = SYSFS_LED_MAX_STEPS;
ms_step = (ms_tot + steps_tot - 1) / steps_tot;
if( ms_step < SYSFS_LED_STEP_DELAY ) {
ms_step = SYSFS_LED_STEP_DELAY;
}
}
int steps_on = (ms_on + ms_step - 1 ) / ms_step;
int steps_off = steps_tot - steps_on;
/* Set intensity value for each step on the ramp.
*/
int i = 0;
while( i < steps_on ) sysfs_led_breathe.value[i++] = 255;
while( i < steps_tot ) sysfs_led_breathe.value[i++] = 0;
sysfs_led_breathe.delay = ms_step;
sysfs_led_breathe.steps = steps_tot;
mce_log(LL_DEBUG, "delay=%d, steps_on=%d, steps_off=%d",
sysfs_led_breathe.delay, steps_on, steps_off);
}
/** Generate triangle wave for use from breathing timer
*/
static void
sysfs_led_generate_ramp_triangle(int ms_on, int ms_off)
{
/* Round up to multiples of minimum step size */
int ms_step = SYSFS_LED_STEP_DELAY;
ms_on = led_util_roundup(ms_on, ms_step);
ms_off = led_util_roundup(ms_off, ms_step);
int ms_tot = ms_on + ms_off;
/* Calculate number of steps */
int steps_tot = (ms_tot + ms_step - 1) / ms_step;
if( steps_tot > SYSFS_LED_MAX_STEPS ) {
steps_tot = SYSFS_LED_MAX_STEPS;
ms_step = (ms_tot + steps_tot - 1) / steps_tot;
if( ms_step < SYSFS_LED_STEP_DELAY )
ms_step = SYSFS_LED_STEP_DELAY;
}
/* Divide time between raising and lowering edges.
* Put rounding excess to raising edge to facilitate
* zero ms_off time used for sawtooth case.
*/
int steps_off = (ms_off + ms_step - 1 ) / ms_step;
int steps_on = steps_tot - steps_off;
/* Set a non-zero intensity value for each step on the ramp.
*/
int i = 0;
for( ; i < steps_on; ++i )
sysfs_led_breathe.value[i] = led_util_trans(i, 0, steps_on, 1, 255);;
for( ; i < steps_tot; ++i )
sysfs_led_breathe.value[i] = led_util_trans(steps_tot - i, 0, steps_off, 1, 255);
sysfs_led_breathe.delay = ms_step;
sysfs_led_breathe.steps = steps_tot;
mce_log(LL_DEBUG, "delay=%d, steps_on=%d, steps_off=%d",
sysfs_led_breathe.delay, steps_on, steps_off);
}
/** Generate sawtooth wave for use from breathing timer
*/
static void
sysfs_led_generate_ramp_sawtooth(int ms_on, int ms_off)
{
sysfs_led_generate_ramp_triangle(ms_on + ms_off, 0);
}
/** Invalidate sw breathing intensity curve
*/
static void
sysfs_led_generate_ramp_dummy(void)
{
sysfs_led_breathe.delay = 0;
sysfs_led_breathe.steps = 0;
}
/** Generate intensity curve for use from breathing timer
*/
static void
sysfs_led_generate_ramp(int ms_on, int ms_off)
{
switch( led_control_breath_type(&led_control) ) {
case LED_RAMP_SINE:
sysfs_led_generate_ramp_sine(ms_on, ms_off, false);
break;
case LED_RAMP_SAWTOOTH:
sysfs_led_generate_ramp_sawtooth(ms_on, ms_off);
break;
case LED_RAMP_TRIANGLE:
sysfs_led_generate_ramp_triangle(ms_on, ms_off);
break;
case LED_RAMP_HARD_STEP:
sysfs_led_generate_ramp_hard_step(ms_on, ms_off);
break;
case LED_RAMP_HALF_SINE:
sysfs_led_generate_ramp_sine(ms_on, ms_off, true);
break;
default:
sysfs_led_generate_ramp_dummy();
break;
}
}
/** Timer id for stopping led */
static guint sysfs_led_stop_id = 0;
/** Timer id for breathing/setting led */
static guint sysfs_led_step_id = 0;
/** Timer callback for setting led
*/
static gboolean
sysfs_led_static_cb(gpointer aptr)
{
(void) aptr;
if( !sysfs_led_step_id ) {
goto cleanup;
}
sysfs_led_step_id = 0;
// get configured color
int r = sysfs_led_curr.r;
int g = sysfs_led_curr.g;
int b = sysfs_led_curr.b;
// adjust by brightness level
int l = sysfs_led_curr.level;
r = led_util_scale_value(r, l);
g = led_util_scale_value(g, l);
b = led_util_scale_value(b, l);
// set led blinking and color
sysfs_led_set_rgb_blink(sysfs_led_curr.on, sysfs_led_curr.off);
sysfs_led_set_rgb_value(r, g, b);
cleanup:
return FALSE;
}
/** Timer callback for taking a led breathing step
*/
static gboolean
sysfs_led_step_cb(gpointer aptr)
{
(void)aptr;
if( !sysfs_led_step_id ) {
goto cleanup;
}
if( sysfs_led_breathe.step >= sysfs_led_breathe.steps ) {
sysfs_led_breathe.step = 0;
}
// get configured color
int r = sysfs_led_curr.r;
int g = sysfs_led_curr.g;
int b = sysfs_led_curr.b;
// adjust by brightness level
int l = sysfs_led_curr.level;
r = led_util_scale_value(r, l);
g = led_util_scale_value(g, l);
b = led_util_scale_value(b, l);
// adjust by curve position
size_t i = sysfs_led_breathe.step++;
int v = sysfs_led_breathe.value[i];
r = led_util_scale_value(r, v);
g = led_util_scale_value(g, v);
b = led_util_scale_value(b, v);
// set led color
sysfs_led_set_rgb_value(r, g, b);
cleanup:
return sysfs_led_step_id != 0;
}
static bool sysfs_led_reset_blinking = true;
/** Timer callback from stopping/restarting led
*/
static gboolean
sysfs_led_stop_cb(gpointer aptr)
{
(void) aptr;
if( !sysfs_led_stop_id ) {
goto cleanup;
}
sysfs_led_stop_id = 0;
if( sysfs_led_reset_blinking ) {
// blinking off - must be followed by rgb set to have an effect
sysfs_led_set_rgb_blink(0, 0);
}
if( !led_state_has_color(&sysfs_led_curr) ) {
// set rgb to black before returning
sysfs_led_reset_blinking = true;
}
else {
if( sysfs_led_breathe.delay > 0 ) {
// start breathing timer
sysfs_led_step_id = g_timeout_add(sysfs_led_breathe.delay,
sysfs_led_step_cb, 0);
}
else {
// set rgb to target after timer delay
sysfs_led_step_id = g_timeout_add(SYSFS_LED_KERNEL_DELAY,
sysfs_led_static_cb, 0);
}
}
if( sysfs_led_reset_blinking ) {
// set rgb to black
sysfs_led_set_rgb_value(0, 0, 0);
sysfs_led_reset_blinking = false;
}
cleanup:
return FALSE;
}
static guint sysfs_led_start_id = 0;
static gboolean sysfs_led_start_cb(gpointer aptr)
{
(void)aptr;
sysfs_led_start_id = 0;
led_state_sanitize(&sysfs_led_next);
if( led_state_is_equal(&sysfs_led_curr, &sysfs_led_next) ) {
goto cleanup;
}
/* Assumption: Before changing the led state, we need to wait a bit
* for kernel side to finish with last change we made and then possibly
* reset the blinking status and wait a bit more */
bool restart = true;
led_style_t old_style = led_state_get_style(&sysfs_led_curr);
led_style_t new_style = led_state_get_style(&sysfs_led_next);
/* Exception: When we are already breathing and continue to
* breathe, the blinking is not in use and the breathing timer
* is keeping the updates far enough from each other */
if( old_style == STYLE_BREATH && new_style == STYLE_BREATH &&
led_state_has_equal_timing(&sysfs_led_curr, &sysfs_led_next) ) {
restart = false;
}
/* If only the als-based brightness level changes, we need to
* adjust the breathing amplitude without affecting the phase.
* Otherwise assume that the pattern has been changed and the
* breathing step counter needs to be reset. */
sysfs_led_curr.level = sysfs_led_next.level;
if( !led_state_is_equal(&sysfs_led_curr, &sysfs_led_next) ) {
sysfs_led_breathe.step = 0;
}
sysfs_led_curr = sysfs_led_next;
if( restart ) {
// stop existing breathing timer
if( sysfs_led_step_id ) {
g_source_remove(sysfs_led_step_id), sysfs_led_step_id = 0;
}
// re-evaluate breathing constants
sysfs_led_breathe.delay = 0;
if( new_style == STYLE_BREATH ) {
sysfs_led_generate_ramp(sysfs_led_next.on, sysfs_led_next.off);
}
if( old_style == STYLE_BLINK || new_style == STYLE_BLINK )
sysfs_led_reset_blinking = true;
/* Schedule led off after kernel settle timeout; once that
* is done, new led color/blink/breathing will be started */
if( !sysfs_led_stop_id ) {
sysfs_led_stop_id = g_timeout_add(SYSFS_LED_KERNEL_DELAY,
sysfs_led_stop_cb, 0);
}
}
cleanup:
return G_SOURCE_REMOVE;
}
/** Start static/blinking/breathing led
*/
static void
sysfs_led_start(void)
{
if( !sysfs_led_start_id ) {
sysfs_led_start_id = g_idle_add(sysfs_led_start_cb, NULL);
}
}
/** Nanosleep helper
*/
static void
sysfs_led_wait_kernel(void)
{
struct timespec ts = { 0, SYSFS_LED_KERNEL_DELAY * 1000000l };
TEMP_FAILURE_RETRY(nanosleep(&ts, &ts));
}
bool
sysfs_led_init(void)
{
bool ack = false;
if( !sysfs_led_probe_files() ) {
goto cleanup;
}
/* adjust current state to: color=black */
sysfs_led_start();
ack = true;
cleanup:
return ack;
}
void
sysfs_led_quit(void)
{
// cancel timers
if( sysfs_led_start_id ) {
g_source_remove(sysfs_led_start_id), sysfs_led_start_id = 0;
}
if( sysfs_led_step_id ) {