1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ledtrig-gio.c - LED Trigger Based on GPIO events |
4 | * |
5 | * Copyright 2009 Felipe Balbi <me@felipebalbi.com> |
6 | * Copyright 2023 Linus Walleij <linus.walleij@linaro.org> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/init.h> |
12 | #include <linux/gpio/consumer.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/leds.h> |
15 | #include <linux/slab.h> |
16 | #include "../leds.h" |
17 | |
18 | struct gpio_trig_data { |
19 | struct led_classdev *led; |
20 | unsigned desired_brightness; /* desired brightness when led is on */ |
21 | struct gpio_desc *gpiod; /* gpio that triggers the led */ |
22 | }; |
23 | |
24 | static irqreturn_t gpio_trig_irq(int irq, void *_led) |
25 | { |
26 | struct led_classdev *led = _led; |
27 | struct gpio_trig_data *gpio_data = led_get_trigger_data(led_cdev: led); |
28 | int tmp; |
29 | |
30 | tmp = gpiod_get_value_cansleep(desc: gpio_data->gpiod); |
31 | if (tmp) { |
32 | if (gpio_data->desired_brightness) |
33 | led_set_brightness_nosleep(led_cdev: gpio_data->led, |
34 | value: gpio_data->desired_brightness); |
35 | else |
36 | led_set_brightness_nosleep(led_cdev: gpio_data->led, value: LED_FULL); |
37 | } else { |
38 | led_set_brightness_nosleep(led_cdev: gpio_data->led, value: LED_OFF); |
39 | } |
40 | |
41 | return IRQ_HANDLED; |
42 | } |
43 | |
44 | static ssize_t desired_brightness_show(struct device *dev, |
45 | struct device_attribute *attr, char *buf) |
46 | { |
47 | struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); |
48 | |
49 | return sysfs_emit(buf, fmt: "%u\n" , gpio_data->desired_brightness); |
50 | } |
51 | |
52 | static ssize_t desired_brightness_store(struct device *dev, |
53 | struct device_attribute *attr, const char *buf, size_t n) |
54 | { |
55 | struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); |
56 | u8 desired_brightness; |
57 | int ret; |
58 | |
59 | ret = kstrtou8(s: buf, base: 10, res: &desired_brightness); |
60 | if (ret) |
61 | return ret; |
62 | |
63 | gpio_data->desired_brightness = desired_brightness; |
64 | |
65 | return n; |
66 | } |
67 | static DEVICE_ATTR_RW(desired_brightness); |
68 | |
69 | static struct attribute *gpio_trig_attrs[] = { |
70 | &dev_attr_desired_brightness.attr, |
71 | NULL |
72 | }; |
73 | ATTRIBUTE_GROUPS(gpio_trig); |
74 | |
75 | static int gpio_trig_activate(struct led_classdev *led) |
76 | { |
77 | struct gpio_trig_data *gpio_data; |
78 | struct device *dev = led->dev; |
79 | int ret; |
80 | |
81 | gpio_data = kzalloc(size: sizeof(*gpio_data), GFP_KERNEL); |
82 | if (!gpio_data) |
83 | return -ENOMEM; |
84 | |
85 | /* |
86 | * The generic property "trigger-sources" is followed, |
87 | * and we hope that this is a GPIO. |
88 | */ |
89 | gpio_data->gpiod = gpiod_get_optional(dev, con_id: "trigger-sources" , flags: GPIOD_IN); |
90 | if (IS_ERR(ptr: gpio_data->gpiod)) { |
91 | ret = PTR_ERR(ptr: gpio_data->gpiod); |
92 | kfree(objp: gpio_data); |
93 | return ret; |
94 | } |
95 | if (!gpio_data->gpiod) { |
96 | dev_err(dev, "no valid GPIO for the trigger\n" ); |
97 | kfree(objp: gpio_data); |
98 | return -EINVAL; |
99 | } |
100 | |
101 | gpiod_set_consumer_name(desc: gpio_data->gpiod, name: "led-trigger" ); |
102 | |
103 | gpio_data->led = led; |
104 | led_set_trigger_data(led_cdev: led, trigger_data: gpio_data); |
105 | |
106 | ret = request_threaded_irq(irq: gpiod_to_irq(desc: gpio_data->gpiod), NULL, thread_fn: gpio_trig_irq, |
107 | IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING |
108 | | IRQF_TRIGGER_FALLING, name: "ledtrig-gpio" , dev: led); |
109 | if (ret) { |
110 | dev_err(dev, "request_irq failed with error %d\n" , ret); |
111 | gpiod_put(desc: gpio_data->gpiod); |
112 | kfree(objp: gpio_data); |
113 | return ret; |
114 | } |
115 | |
116 | /* Finally update the LED to initial status */ |
117 | gpio_trig_irq(irq: 0, led: led); |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | static void gpio_trig_deactivate(struct led_classdev *led) |
123 | { |
124 | struct gpio_trig_data *gpio_data = led_get_trigger_data(led_cdev: led); |
125 | |
126 | free_irq(gpiod_to_irq(desc: gpio_data->gpiod), led); |
127 | gpiod_put(desc: gpio_data->gpiod); |
128 | kfree(objp: gpio_data); |
129 | } |
130 | |
131 | static struct led_trigger gpio_led_trigger = { |
132 | .name = "gpio" , |
133 | .activate = gpio_trig_activate, |
134 | .deactivate = gpio_trig_deactivate, |
135 | .groups = gpio_trig_groups, |
136 | }; |
137 | module_led_trigger(gpio_led_trigger); |
138 | |
139 | MODULE_AUTHOR("Felipe Balbi <me@felipebalbi.com>" ); |
140 | MODULE_DESCRIPTION("GPIO LED trigger" ); |
141 | MODULE_LICENSE("GPL v2" ); |
142 | |