1 // SPDX-License-Identifier: GPL-2.0
5 * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
8 #include <linux/device.h>
9 #include <linux/leds.h>
10 #include <linux/module.h>
12 #include <linux/slab.h>
13 #include <linux/usb.h>
14 #include <linux/usb/of.h>
16 struct usbport_trig_data
{
17 struct led_classdev
*led_cdev
;
18 struct list_head ports
;
19 struct notifier_block nb
;
20 int count
; /* Amount of connected matching devices */
23 struct usbport_trig_port
{
24 struct usbport_trig_data
*data
;
25 struct usb_device
*hub
;
29 struct device_attribute attr
;
30 struct list_head list
;
33 /***************************************
35 ***************************************/
38 * usbport_trig_usb_dev_observed - Check if dev is connected to observed port
40 static bool usbport_trig_usb_dev_observed(struct usbport_trig_data
*usbport_data
,
41 struct usb_device
*usb_dev
)
43 struct usbport_trig_port
*port
;
48 list_for_each_entry(port
, &usbport_data
->ports
, list
) {
49 if (usb_dev
->parent
== port
->hub
&&
50 usb_dev
->portnum
== port
->portnum
)
51 return port
->observed
;
57 static int usbport_trig_usb_dev_check(struct usb_device
*usb_dev
, void *data
)
59 struct usbport_trig_data
*usbport_data
= data
;
61 if (usbport_trig_usb_dev_observed(usbport_data
, usb_dev
))
62 usbport_data
->count
++;
68 * usbport_trig_update_count - Recalculate amount of connected matching devices
70 static void usbport_trig_update_count(struct usbport_trig_data
*usbport_data
)
72 struct led_classdev
*led_cdev
= usbport_data
->led_cdev
;
74 usbport_data
->count
= 0;
75 usb_for_each_dev(usbport_data
, usbport_trig_usb_dev_check
);
76 led_set_brightness(led_cdev
, usbport_data
->count
? LED_FULL
: LED_OFF
);
79 /***************************************
81 ***************************************/
83 static ssize_t
usbport_trig_port_show(struct device
*dev
,
84 struct device_attribute
*attr
, char *buf
)
86 struct usbport_trig_port
*port
= container_of(attr
,
87 struct usbport_trig_port
,
90 return sprintf(buf
, "%d\n", port
->observed
) + 1;
93 static ssize_t
usbport_trig_port_store(struct device
*dev
,
94 struct device_attribute
*attr
,
95 const char *buf
, size_t size
)
97 struct usbport_trig_port
*port
= container_of(attr
,
98 struct usbport_trig_port
,
101 if (!strcmp(buf
, "0") || !strcmp(buf
, "0\n"))
103 else if (!strcmp(buf
, "1") || !strcmp(buf
, "1\n"))
108 usbport_trig_update_count(port
->data
);
113 static struct attribute
*ports_attrs
[] = {
116 static const struct attribute_group ports_group
= {
118 .attrs
= ports_attrs
,
121 /***************************************
122 * Adding & removing ports
123 ***************************************/
126 * usbport_trig_port_observed - Check if port should be observed
128 static bool usbport_trig_port_observed(struct usbport_trig_data
*usbport_data
,
129 struct usb_device
*usb_dev
, int port1
)
131 struct device
*dev
= usbport_data
->led_cdev
->dev
;
132 struct device_node
*led_np
= dev
->of_node
;
133 struct of_phandle_args args
;
134 struct device_node
*port_np
;
141 * Get node of port being added
143 * FIXME: This is really the device node of the connected device
145 port_np
= usb_of_get_device_node(usb_dev
, port1
);
149 of_node_put(port_np
);
151 /* Amount of trigger sources for this LED */
152 count
= of_count_phandle_with_args(led_np
, "trigger-sources",
153 "#trigger-source-cells");
155 dev_warn(dev
, "Failed to get trigger sources for %pOF\n",
160 /* Check list of sources for this specific port */
161 for (i
= 0; i
< count
; i
++) {
164 err
= of_parse_phandle_with_args(led_np
, "trigger-sources",
165 "#trigger-source-cells", i
,
168 dev_err(dev
, "Failed to get trigger source phandle at index %d: %d\n",
173 of_node_put(args
.np
);
175 if (args
.np
== port_np
)
182 static int usbport_trig_add_port(struct usbport_trig_data
*usbport_data
,
183 struct usb_device
*usb_dev
,
184 const char *hub_name
, int portnum
)
186 struct led_classdev
*led_cdev
= usbport_data
->led_cdev
;
187 struct usbport_trig_port
*port
;
191 port
= kzalloc(sizeof(*port
), GFP_KERNEL
);
197 port
->data
= usbport_data
;
199 port
->portnum
= portnum
;
200 port
->observed
= usbport_trig_port_observed(usbport_data
, usb_dev
,
203 len
= strlen(hub_name
) + 8;
204 port
->port_name
= kzalloc(len
, GFP_KERNEL
);
205 if (!port
->port_name
) {
209 snprintf(port
->port_name
, len
, "%s-port%d", hub_name
, portnum
);
211 sysfs_attr_init(&port
->attr
.attr
);
212 port
->attr
.attr
.name
= port
->port_name
;
213 port
->attr
.attr
.mode
= S_IRUSR
| S_IWUSR
;
214 port
->attr
.show
= usbport_trig_port_show
;
215 port
->attr
.store
= usbport_trig_port_store
;
217 err
= sysfs_add_file_to_group(&led_cdev
->dev
->kobj
, &port
->attr
.attr
,
220 goto err_free_port_name
;
222 list_add_tail(&port
->list
, &usbport_data
->ports
);
227 kfree(port
->port_name
);
234 static int usbport_trig_add_usb_dev_ports(struct usb_device
*usb_dev
,
237 struct usbport_trig_data
*usbport_data
= data
;
240 for (i
= 1; i
<= usb_dev
->maxchild
; i
++)
241 usbport_trig_add_port(usbport_data
, usb_dev
,
242 dev_name(&usb_dev
->dev
), i
);
247 static void usbport_trig_remove_port(struct usbport_trig_data
*usbport_data
,
248 struct usbport_trig_port
*port
)
250 struct led_classdev
*led_cdev
= usbport_data
->led_cdev
;
252 list_del(&port
->list
);
253 sysfs_remove_file_from_group(&led_cdev
->dev
->kobj
, &port
->attr
.attr
,
255 kfree(port
->port_name
);
259 static void usbport_trig_remove_usb_dev_ports(struct usbport_trig_data
*usbport_data
,
260 struct usb_device
*usb_dev
)
262 struct usbport_trig_port
*port
, *tmp
;
264 list_for_each_entry_safe(port
, tmp
, &usbport_data
->ports
, list
) {
265 if (port
->hub
== usb_dev
)
266 usbport_trig_remove_port(usbport_data
, port
);
270 /***************************************
272 ***************************************/
274 static int usbport_trig_notify(struct notifier_block
*nb
, unsigned long action
,
277 struct usbport_trig_data
*usbport_data
=
278 container_of(nb
, struct usbport_trig_data
, nb
);
279 struct led_classdev
*led_cdev
= usbport_data
->led_cdev
;
280 struct usb_device
*usb_dev
= data
;
283 observed
= usbport_trig_usb_dev_observed(usbport_data
, usb_dev
);
287 usbport_trig_add_usb_dev_ports(usb_dev
, usbport_data
);
288 if (observed
&& usbport_data
->count
++ == 0)
289 led_set_brightness(led_cdev
, LED_FULL
);
291 case USB_DEVICE_REMOVE
:
292 usbport_trig_remove_usb_dev_ports(usbport_data
, usb_dev
);
293 if (observed
&& --usbport_data
->count
== 0)
294 led_set_brightness(led_cdev
, LED_OFF
);
301 static void usbport_trig_activate(struct led_classdev
*led_cdev
)
303 struct usbport_trig_data
*usbport_data
;
306 usbport_data
= kzalloc(sizeof(*usbport_data
), GFP_KERNEL
);
309 usbport_data
->led_cdev
= led_cdev
;
312 INIT_LIST_HEAD(&usbport_data
->ports
);
313 err
= sysfs_create_group(&led_cdev
->dev
->kobj
, &ports_group
);
316 usb_for_each_dev(usbport_data
, usbport_trig_add_usb_dev_ports
);
317 usbport_trig_update_count(usbport_data
);
320 usbport_data
->nb
.notifier_call
= usbport_trig_notify
,
321 led_cdev
->trigger_data
= usbport_data
;
322 usb_register_notify(&usbport_data
->nb
);
324 led_cdev
->activated
= true;
331 static void usbport_trig_deactivate(struct led_classdev
*led_cdev
)
333 struct usbport_trig_data
*usbport_data
= led_cdev
->trigger_data
;
334 struct usbport_trig_port
*port
, *tmp
;
336 if (!led_cdev
->activated
)
339 list_for_each_entry_safe(port
, tmp
, &usbport_data
->ports
, list
) {
340 usbport_trig_remove_port(usbport_data
, port
);
343 usb_unregister_notify(&usbport_data
->nb
);
345 sysfs_remove_group(&led_cdev
->dev
->kobj
, &ports_group
);
349 led_cdev
->activated
= false;
352 static struct led_trigger usbport_led_trigger
= {
354 .activate
= usbport_trig_activate
,
355 .deactivate
= usbport_trig_deactivate
,
358 static int __init
usbport_trig_init(void)
360 return led_trigger_register(&usbport_led_trigger
);
363 static void __exit
usbport_trig_exit(void)
365 led_trigger_unregister(&usbport_led_trigger
);
368 module_init(usbport_trig_init
);
369 module_exit(usbport_trig_exit
);
371 MODULE_AUTHOR("Rafał Miłecki <rafal@milecki.pl>");
372 MODULE_DESCRIPTION("USB port trigger");
373 MODULE_LICENSE("GPL v2");