1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Backlight emulation LED trigger |
4 | * |
5 | * Copyright 2008 (C) Rodolfo Giometti <giometti@linux.it> |
6 | * Copyright 2008 (C) Eurotech S.p.A. <info@eurotech.it> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/init.h> |
13 | #include <linux/fb.h> |
14 | #include <linux/leds.h> |
15 | #include "../leds.h" |
16 | |
17 | #define BLANK 1 |
18 | #define UNBLANK 0 |
19 | |
20 | struct bl_trig_notifier { |
21 | struct led_classdev *led; |
22 | int brightness; |
23 | int old_status; |
24 | struct notifier_block notifier; |
25 | unsigned invert; |
26 | }; |
27 | |
28 | static int fb_notifier_callback(struct notifier_block *p, |
29 | unsigned long event, void *data) |
30 | { |
31 | struct bl_trig_notifier *n = container_of(p, |
32 | struct bl_trig_notifier, notifier); |
33 | struct led_classdev *led = n->led; |
34 | struct fb_event *fb_event = data; |
35 | int *blank; |
36 | int new_status; |
37 | |
38 | /* If we aren't interested in this event, skip it immediately ... */ |
39 | if (event != FB_EVENT_BLANK) |
40 | return 0; |
41 | |
42 | blank = fb_event->data; |
43 | new_status = *blank ? BLANK : UNBLANK; |
44 | |
45 | if (new_status == n->old_status) |
46 | return 0; |
47 | |
48 | if ((n->old_status == UNBLANK) ^ n->invert) { |
49 | n->brightness = led->brightness; |
50 | led_set_brightness_nosleep(led_cdev: led, value: LED_OFF); |
51 | } else { |
52 | led_set_brightness_nosleep(led_cdev: led, value: n->brightness); |
53 | } |
54 | |
55 | n->old_status = new_status; |
56 | |
57 | return 0; |
58 | } |
59 | |
60 | static ssize_t bl_trig_invert_show(struct device *dev, |
61 | struct device_attribute *attr, char *buf) |
62 | { |
63 | struct bl_trig_notifier *n = led_trigger_get_drvdata(dev); |
64 | |
65 | return sprintf(buf, fmt: "%u\n" , n->invert); |
66 | } |
67 | |
68 | static ssize_t bl_trig_invert_store(struct device *dev, |
69 | struct device_attribute *attr, const char *buf, size_t num) |
70 | { |
71 | struct led_classdev *led = led_trigger_get_led(dev); |
72 | struct bl_trig_notifier *n = led_trigger_get_drvdata(dev); |
73 | unsigned long invert; |
74 | int ret; |
75 | |
76 | ret = kstrtoul(s: buf, base: 10, res: &invert); |
77 | if (ret < 0) |
78 | return ret; |
79 | |
80 | if (invert > 1) |
81 | return -EINVAL; |
82 | |
83 | n->invert = invert; |
84 | |
85 | /* After inverting, we need to update the LED. */ |
86 | if ((n->old_status == BLANK) ^ n->invert) |
87 | led_set_brightness_nosleep(led_cdev: led, value: LED_OFF); |
88 | else |
89 | led_set_brightness_nosleep(led_cdev: led, value: n->brightness); |
90 | |
91 | return num; |
92 | } |
93 | static DEVICE_ATTR(inverted, 0644, bl_trig_invert_show, bl_trig_invert_store); |
94 | |
95 | static struct attribute *bl_trig_attrs[] = { |
96 | &dev_attr_inverted.attr, |
97 | NULL, |
98 | }; |
99 | ATTRIBUTE_GROUPS(bl_trig); |
100 | |
101 | static int bl_trig_activate(struct led_classdev *led) |
102 | { |
103 | int ret; |
104 | |
105 | struct bl_trig_notifier *n; |
106 | |
107 | n = kzalloc(size: sizeof(struct bl_trig_notifier), GFP_KERNEL); |
108 | if (!n) |
109 | return -ENOMEM; |
110 | led_set_trigger_data(led_cdev: led, trigger_data: n); |
111 | |
112 | n->led = led; |
113 | n->brightness = led->brightness; |
114 | n->old_status = UNBLANK; |
115 | n->notifier.notifier_call = fb_notifier_callback; |
116 | |
117 | ret = fb_register_client(nb: &n->notifier); |
118 | if (ret) |
119 | dev_err(led->dev, "unable to register backlight trigger\n" ); |
120 | |
121 | return 0; |
122 | } |
123 | |
124 | static void bl_trig_deactivate(struct led_classdev *led) |
125 | { |
126 | struct bl_trig_notifier *n = led_get_trigger_data(led_cdev: led); |
127 | |
128 | fb_unregister_client(nb: &n->notifier); |
129 | kfree(objp: n); |
130 | } |
131 | |
132 | static struct led_trigger bl_led_trigger = { |
133 | .name = "backlight" , |
134 | .activate = bl_trig_activate, |
135 | .deactivate = bl_trig_deactivate, |
136 | .groups = bl_trig_groups, |
137 | }; |
138 | module_led_trigger(bl_led_trigger); |
139 | |
140 | MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>" ); |
141 | MODULE_DESCRIPTION("Backlight emulation LED trigger" ); |
142 | MODULE_LICENSE("GPL v2" ); |
143 | |