xref: /openbmc/linux/drivers/usb/core/ledtrig-usbport.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
20f247626SRafał Miłecki /*
30f247626SRafał Miłecki  * USB port LED trigger
40f247626SRafał Miłecki  *
50f247626SRafał Miłecki  * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
60f247626SRafał Miłecki  */
70f247626SRafał Miłecki 
80f247626SRafał Miłecki #include <linux/device.h>
90f247626SRafał Miłecki #include <linux/leds.h>
100f247626SRafał Miłecki #include <linux/module.h>
114f04c210SRafał Miłecki #include <linux/of.h>
120f247626SRafał Miłecki #include <linux/slab.h>
130f247626SRafał Miłecki #include <linux/usb.h>
144f04c210SRafał Miłecki #include <linux/usb/of.h>
150f247626SRafał Miłecki 
160f247626SRafał Miłecki struct usbport_trig_data {
170f247626SRafał Miłecki 	struct led_classdev *led_cdev;
180f247626SRafał Miłecki 	struct list_head ports;
190f247626SRafał Miłecki 	struct notifier_block nb;
200f247626SRafał Miłecki 	int count; /* Amount of connected matching devices */
210f247626SRafał Miłecki };
220f247626SRafał Miłecki 
230f247626SRafał Miłecki struct usbport_trig_port {
240f247626SRafał Miłecki 	struct usbport_trig_data *data;
250f247626SRafał Miłecki 	struct usb_device *hub;
260f247626SRafał Miłecki 	int portnum;
270f247626SRafał Miłecki 	char *port_name;
280f247626SRafał Miłecki 	bool observed;
290f247626SRafał Miłecki 	struct device_attribute attr;
300f247626SRafał Miłecki 	struct list_head list;
310f247626SRafał Miłecki };
320f247626SRafał Miłecki 
330f247626SRafał Miłecki /***************************************
340f247626SRafał Miłecki  * Helpers
350f247626SRafał Miłecki  ***************************************/
360f247626SRafał Miłecki 
375bef1132SLee Jones /*
380f247626SRafał Miłecki  * usbport_trig_usb_dev_observed - Check if dev is connected to observed port
390f247626SRafał Miłecki  */
usbport_trig_usb_dev_observed(struct usbport_trig_data * usbport_data,struct usb_device * usb_dev)400f247626SRafał Miłecki static bool usbport_trig_usb_dev_observed(struct usbport_trig_data *usbport_data,
410f247626SRafał Miłecki 					  struct usb_device *usb_dev)
420f247626SRafał Miłecki {
430f247626SRafał Miłecki 	struct usbport_trig_port *port;
440f247626SRafał Miłecki 
450f247626SRafał Miłecki 	if (!usb_dev->parent)
460f247626SRafał Miłecki 		return false;
470f247626SRafał Miłecki 
480f247626SRafał Miłecki 	list_for_each_entry(port, &usbport_data->ports, list) {
490f247626SRafał Miłecki 		if (usb_dev->parent == port->hub &&
500f247626SRafał Miłecki 		    usb_dev->portnum == port->portnum)
510f247626SRafał Miłecki 			return port->observed;
520f247626SRafał Miłecki 	}
530f247626SRafał Miłecki 
540f247626SRafał Miłecki 	return false;
550f247626SRafał Miłecki }
560f247626SRafał Miłecki 
usbport_trig_usb_dev_check(struct usb_device * usb_dev,void * data)570f247626SRafał Miłecki static int usbport_trig_usb_dev_check(struct usb_device *usb_dev, void *data)
580f247626SRafał Miłecki {
590f247626SRafał Miłecki 	struct usbport_trig_data *usbport_data = data;
600f247626SRafał Miłecki 
610f247626SRafał Miłecki 	if (usbport_trig_usb_dev_observed(usbport_data, usb_dev))
620f247626SRafał Miłecki 		usbport_data->count++;
630f247626SRafał Miłecki 
640f247626SRafał Miłecki 	return 0;
650f247626SRafał Miłecki }
660f247626SRafał Miłecki 
675bef1132SLee Jones /*
680f247626SRafał Miłecki  * usbport_trig_update_count - Recalculate amount of connected matching devices
690f247626SRafał Miłecki  */
usbport_trig_update_count(struct usbport_trig_data * usbport_data)700f247626SRafał Miłecki static void usbport_trig_update_count(struct usbport_trig_data *usbport_data)
710f247626SRafał Miłecki {
720f247626SRafał Miłecki 	struct led_classdev *led_cdev = usbport_data->led_cdev;
730f247626SRafał Miłecki 
740f247626SRafał Miłecki 	usbport_data->count = 0;
750f247626SRafał Miłecki 	usb_for_each_dev(usbport_data, usbport_trig_usb_dev_check);
7689778ba3SRafał Miłecki 	led_set_brightness(led_cdev, usbport_data->count ? LED_FULL : LED_OFF);
770f247626SRafał Miłecki }
780f247626SRafał Miłecki 
790f247626SRafał Miłecki /***************************************
800f247626SRafał Miłecki  * Device attr
810f247626SRafał Miłecki  ***************************************/
820f247626SRafał Miłecki 
usbport_trig_port_show(struct device * dev,struct device_attribute * attr,char * buf)830f247626SRafał Miłecki static ssize_t usbport_trig_port_show(struct device *dev,
840f247626SRafał Miłecki 				      struct device_attribute *attr, char *buf)
850f247626SRafał Miłecki {
860f247626SRafał Miłecki 	struct usbport_trig_port *port = container_of(attr,
870f247626SRafał Miłecki 						      struct usbport_trig_port,
880f247626SRafał Miłecki 						      attr);
890f247626SRafał Miłecki 
900f247626SRafał Miłecki 	return sprintf(buf, "%d\n", port->observed) + 1;
910f247626SRafał Miłecki }
920f247626SRafał Miłecki 
usbport_trig_port_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)930f247626SRafał Miłecki static ssize_t usbport_trig_port_store(struct device *dev,
940f247626SRafał Miłecki 				       struct device_attribute *attr,
950f247626SRafał Miłecki 				       const char *buf, size_t size)
960f247626SRafał Miłecki {
970f247626SRafał Miłecki 	struct usbport_trig_port *port = container_of(attr,
980f247626SRafał Miłecki 						      struct usbport_trig_port,
990f247626SRafał Miłecki 						      attr);
1000f247626SRafał Miłecki 
1010f247626SRafał Miłecki 	if (!strcmp(buf, "0") || !strcmp(buf, "0\n"))
1020f247626SRafał Miłecki 		port->observed = 0;
1030f247626SRafał Miłecki 	else if (!strcmp(buf, "1") || !strcmp(buf, "1\n"))
1040f247626SRafał Miłecki 		port->observed = 1;
1050f247626SRafał Miłecki 	else
1060f247626SRafał Miłecki 		return -EINVAL;
1070f247626SRafał Miłecki 
1080f247626SRafał Miłecki 	usbport_trig_update_count(port->data);
1090f247626SRafał Miłecki 
1100f247626SRafał Miłecki 	return size;
1110f247626SRafał Miłecki }
1120f247626SRafał Miłecki 
1130f247626SRafał Miłecki static struct attribute *ports_attrs[] = {
1140f247626SRafał Miłecki 	NULL,
1150f247626SRafał Miłecki };
1166f7b0badSUwe Kleine-König 
1170f247626SRafał Miłecki static const struct attribute_group ports_group = {
1180f247626SRafał Miłecki 	.name = "ports",
1190f247626SRafał Miłecki 	.attrs = ports_attrs,
1200f247626SRafał Miłecki };
1210f247626SRafał Miłecki 
1220f247626SRafał Miłecki /***************************************
1230f247626SRafał Miłecki  * Adding & removing ports
1240f247626SRafał Miłecki  ***************************************/
1250f247626SRafał Miłecki 
1265bef1132SLee Jones /*
1274f04c210SRafał Miłecki  * usbport_trig_port_observed - Check if port should be observed
1284f04c210SRafał Miłecki  */
usbport_trig_port_observed(struct usbport_trig_data * usbport_data,struct usb_device * usb_dev,int port1)1294f04c210SRafał Miłecki static bool usbport_trig_port_observed(struct usbport_trig_data *usbport_data,
1304f04c210SRafał Miłecki 				       struct usb_device *usb_dev, int port1)
1314f04c210SRafał Miłecki {
1324f04c210SRafał Miłecki 	struct device *dev = usbport_data->led_cdev->dev;
1334f04c210SRafał Miłecki 	struct device_node *led_np = dev->of_node;
1344f04c210SRafał Miłecki 	struct of_phandle_args args;
1354f04c210SRafał Miłecki 	struct device_node *port_np;
1364f04c210SRafał Miłecki 	int count, i;
1374f04c210SRafał Miłecki 
1384f04c210SRafał Miłecki 	if (!led_np)
1394f04c210SRafał Miłecki 		return false;
1404f04c210SRafał Miłecki 
14103310a15SJohan Hovold 	/*
14203310a15SJohan Hovold 	 * Get node of port being added
14303310a15SJohan Hovold 	 *
14403310a15SJohan Hovold 	 * FIXME: This is really the device node of the connected device
14503310a15SJohan Hovold 	 */
1467739376eSJohan Hovold 	port_np = usb_of_get_device_node(usb_dev, port1);
1474f04c210SRafał Miłecki 	if (!port_np)
1484f04c210SRafał Miłecki 		return false;
1494f04c210SRafał Miłecki 
15003310a15SJohan Hovold 	of_node_put(port_np);
15103310a15SJohan Hovold 
1524f04c210SRafał Miłecki 	/* Amount of trigger sources for this LED */
1534f04c210SRafał Miłecki 	count = of_count_phandle_with_args(led_np, "trigger-sources",
1544f04c210SRafał Miłecki 					   "#trigger-source-cells");
1554f04c210SRafał Miłecki 	if (count < 0) {
156d9241ff2SRob Herring 		dev_warn(dev, "Failed to get trigger sources for %pOF\n",
157d9241ff2SRob Herring 			 led_np);
1584f04c210SRafał Miłecki 		return false;
1594f04c210SRafał Miłecki 	}
1604f04c210SRafał Miłecki 
1614f04c210SRafał Miłecki 	/* Check list of sources for this specific port */
1624f04c210SRafał Miłecki 	for (i = 0; i < count; i++) {
1634f04c210SRafał Miłecki 		int err;
1644f04c210SRafał Miłecki 
1654f04c210SRafał Miłecki 		err = of_parse_phandle_with_args(led_np, "trigger-sources",
1664f04c210SRafał Miłecki 						 "#trigger-source-cells", i,
1674f04c210SRafał Miłecki 						 &args);
1684f04c210SRafał Miłecki 		if (err) {
1694f04c210SRafał Miłecki 			dev_err(dev, "Failed to get trigger source phandle at index %d: %d\n",
1704f04c210SRafał Miłecki 				i, err);
1714f04c210SRafał Miłecki 			continue;
1724f04c210SRafał Miłecki 		}
1734f04c210SRafał Miłecki 
1744f04c210SRafał Miłecki 		of_node_put(args.np);
1754f04c210SRafał Miłecki 
1764f04c210SRafał Miłecki 		if (args.np == port_np)
1774f04c210SRafał Miłecki 			return true;
1784f04c210SRafał Miłecki 	}
1794f04c210SRafał Miłecki 
1804f04c210SRafał Miłecki 	return false;
1814f04c210SRafał Miłecki }
1824f04c210SRafał Miłecki 
usbport_trig_add_port(struct usbport_trig_data * usbport_data,struct usb_device * usb_dev,const char * hub_name,int portnum)1830f247626SRafał Miłecki static int usbport_trig_add_port(struct usbport_trig_data *usbport_data,
1840f247626SRafał Miłecki 				 struct usb_device *usb_dev,
1850f247626SRafał Miłecki 				 const char *hub_name, int portnum)
1860f247626SRafał Miłecki {
1870f247626SRafał Miłecki 	struct led_classdev *led_cdev = usbport_data->led_cdev;
1880f247626SRafał Miłecki 	struct usbport_trig_port *port;
1890f247626SRafał Miłecki 	size_t len;
1900f247626SRafał Miłecki 	int err;
1910f247626SRafał Miłecki 
1920f247626SRafał Miłecki 	port = kzalloc(sizeof(*port), GFP_KERNEL);
1930f247626SRafał Miłecki 	if (!port) {
1940f247626SRafał Miłecki 		err = -ENOMEM;
1950f247626SRafał Miłecki 		goto err_out;
1960f247626SRafał Miłecki 	}
1970f247626SRafał Miłecki 
1980f247626SRafał Miłecki 	port->data = usbport_data;
1990f247626SRafał Miłecki 	port->hub = usb_dev;
2000f247626SRafał Miłecki 	port->portnum = portnum;
2014f04c210SRafał Miłecki 	port->observed = usbport_trig_port_observed(usbport_data, usb_dev,
2024f04c210SRafał Miłecki 						    portnum);
2030f247626SRafał Miłecki 
2040f247626SRafał Miłecki 	len = strlen(hub_name) + 8;
2050f247626SRafał Miłecki 	port->port_name = kzalloc(len, GFP_KERNEL);
2060f247626SRafał Miłecki 	if (!port->port_name) {
2070f247626SRafał Miłecki 		err = -ENOMEM;
2080f247626SRafał Miłecki 		goto err_free_port;
2090f247626SRafał Miłecki 	}
2100f247626SRafał Miłecki 	snprintf(port->port_name, len, "%s-port%d", hub_name, portnum);
2110f247626SRafał Miłecki 
212aa759365SChristian Lamparter 	sysfs_attr_init(&port->attr.attr);
2130f247626SRafał Miłecki 	port->attr.attr.name = port->port_name;
2140f247626SRafał Miłecki 	port->attr.attr.mode = S_IRUSR | S_IWUSR;
2150f247626SRafał Miłecki 	port->attr.show = usbport_trig_port_show;
2160f247626SRafał Miłecki 	port->attr.store = usbport_trig_port_store;
2170f247626SRafał Miłecki 
2180f247626SRafał Miłecki 	err = sysfs_add_file_to_group(&led_cdev->dev->kobj, &port->attr.attr,
2190f247626SRafał Miłecki 				      ports_group.name);
2200f247626SRafał Miłecki 	if (err)
2210f247626SRafał Miłecki 		goto err_free_port_name;
2220f247626SRafał Miłecki 
2230f247626SRafał Miłecki 	list_add_tail(&port->list, &usbport_data->ports);
2240f247626SRafał Miłecki 
2250f247626SRafał Miłecki 	return 0;
2260f247626SRafał Miłecki 
2270f247626SRafał Miłecki err_free_port_name:
2280f247626SRafał Miłecki 	kfree(port->port_name);
2290f247626SRafał Miłecki err_free_port:
2300f247626SRafał Miłecki 	kfree(port);
2310f247626SRafał Miłecki err_out:
2320f247626SRafał Miłecki 	return err;
2330f247626SRafał Miłecki }
2340f247626SRafał Miłecki 
usbport_trig_add_usb_dev_ports(struct usb_device * usb_dev,void * data)2350f247626SRafał Miłecki static int usbport_trig_add_usb_dev_ports(struct usb_device *usb_dev,
2360f247626SRafał Miłecki 					  void *data)
2370f247626SRafał Miłecki {
2380f247626SRafał Miłecki 	struct usbport_trig_data *usbport_data = data;
2390f247626SRafał Miłecki 	int i;
2400f247626SRafał Miłecki 
2410f247626SRafał Miłecki 	for (i = 1; i <= usb_dev->maxchild; i++)
2420f247626SRafał Miłecki 		usbport_trig_add_port(usbport_data, usb_dev,
2430f247626SRafał Miłecki 				      dev_name(&usb_dev->dev), i);
2440f247626SRafał Miłecki 
2450f247626SRafał Miłecki 	return 0;
2460f247626SRafał Miłecki }
2470f247626SRafał Miłecki 
usbport_trig_remove_port(struct usbport_trig_data * usbport_data,struct usbport_trig_port * port)2480f247626SRafał Miłecki static void usbport_trig_remove_port(struct usbport_trig_data *usbport_data,
2490f247626SRafał Miłecki 				     struct usbport_trig_port *port)
2500f247626SRafał Miłecki {
2510f247626SRafał Miłecki 	struct led_classdev *led_cdev = usbport_data->led_cdev;
2520f247626SRafał Miłecki 
2530f247626SRafał Miłecki 	list_del(&port->list);
2540f247626SRafał Miłecki 	sysfs_remove_file_from_group(&led_cdev->dev->kobj, &port->attr.attr,
2550f247626SRafał Miłecki 				     ports_group.name);
2560f247626SRafał Miłecki 	kfree(port->port_name);
2570f247626SRafał Miłecki 	kfree(port);
2580f247626SRafał Miłecki }
2590f247626SRafał Miłecki 
usbport_trig_remove_usb_dev_ports(struct usbport_trig_data * usbport_data,struct usb_device * usb_dev)2600f247626SRafał Miłecki static void usbport_trig_remove_usb_dev_ports(struct usbport_trig_data *usbport_data,
2610f247626SRafał Miłecki 					      struct usb_device *usb_dev)
2620f247626SRafał Miłecki {
2630f247626SRafał Miłecki 	struct usbport_trig_port *port, *tmp;
2640f247626SRafał Miłecki 
2650f247626SRafał Miłecki 	list_for_each_entry_safe(port, tmp, &usbport_data->ports, list) {
2660f247626SRafał Miłecki 		if (port->hub == usb_dev)
2670f247626SRafał Miłecki 			usbport_trig_remove_port(usbport_data, port);
2680f247626SRafał Miłecki 	}
2690f247626SRafał Miłecki }
2700f247626SRafał Miłecki 
2710f247626SRafał Miłecki /***************************************
2720f247626SRafał Miłecki  * Init, exit, etc.
2730f247626SRafał Miłecki  ***************************************/
2740f247626SRafał Miłecki 
usbport_trig_notify(struct notifier_block * nb,unsigned long action,void * data)2750f247626SRafał Miłecki static int usbport_trig_notify(struct notifier_block *nb, unsigned long action,
2760f247626SRafał Miłecki 			       void *data)
2770f247626SRafał Miłecki {
2780f247626SRafał Miłecki 	struct usbport_trig_data *usbport_data =
2790f247626SRafał Miłecki 		container_of(nb, struct usbport_trig_data, nb);
2800f247626SRafał Miłecki 	struct led_classdev *led_cdev = usbport_data->led_cdev;
2810f247626SRafał Miłecki 	struct usb_device *usb_dev = data;
2820f247626SRafał Miłecki 	bool observed;
2830f247626SRafał Miłecki 
2840f247626SRafał Miłecki 	observed = usbport_trig_usb_dev_observed(usbport_data, usb_dev);
2850f247626SRafał Miłecki 
2860f247626SRafał Miłecki 	switch (action) {
2870f247626SRafał Miłecki 	case USB_DEVICE_ADD:
2880f247626SRafał Miłecki 		usbport_trig_add_usb_dev_ports(usb_dev, usbport_data);
2890f247626SRafał Miłecki 		if (observed && usbport_data->count++ == 0)
29089778ba3SRafał Miłecki 			led_set_brightness(led_cdev, LED_FULL);
2910f247626SRafał Miłecki 		return NOTIFY_OK;
2920f247626SRafał Miłecki 	case USB_DEVICE_REMOVE:
2930f247626SRafał Miłecki 		usbport_trig_remove_usb_dev_ports(usbport_data, usb_dev);
2940f247626SRafał Miłecki 		if (observed && --usbport_data->count == 0)
29589778ba3SRafał Miłecki 			led_set_brightness(led_cdev, LED_OFF);
2960f247626SRafał Miłecki 		return NOTIFY_OK;
2970f247626SRafał Miłecki 	}
2980f247626SRafał Miłecki 
2990f247626SRafał Miłecki 	return NOTIFY_DONE;
3000f247626SRafał Miłecki }
3010f247626SRafał Miłecki 
usbport_trig_activate(struct led_classdev * led_cdev)3022282e125SUwe Kleine-König static int usbport_trig_activate(struct led_classdev *led_cdev)
3030f247626SRafał Miłecki {
3040f247626SRafał Miłecki 	struct usbport_trig_data *usbport_data;
30591f7d2e8SChristian Lamparter 	int err;
3060f247626SRafał Miłecki 
3070f247626SRafał Miłecki 	usbport_data = kzalloc(sizeof(*usbport_data), GFP_KERNEL);
3080f247626SRafał Miłecki 	if (!usbport_data)
3096f7b0badSUwe Kleine-König 		return -ENOMEM;
3100f247626SRafał Miłecki 	usbport_data->led_cdev = led_cdev;
3110f247626SRafał Miłecki 
3120f247626SRafał Miłecki 	/* List of ports */
3130f247626SRafał Miłecki 	INIT_LIST_HEAD(&usbport_data->ports);
31491f7d2e8SChristian Lamparter 	err = sysfs_create_group(&led_cdev->dev->kobj, &ports_group);
31591f7d2e8SChristian Lamparter 	if (err)
31691f7d2e8SChristian Lamparter 		goto err_free;
3170f247626SRafał Miłecki 	usb_for_each_dev(usbport_data, usbport_trig_add_usb_dev_ports);
3184f04c210SRafał Miłecki 	usbport_trig_update_count(usbport_data);
3190f247626SRafał Miłecki 
3200f247626SRafał Miłecki 	/* Notifications */
3216f7b0badSUwe Kleine-König 	usbport_data->nb.notifier_call = usbport_trig_notify;
3226f7b0badSUwe Kleine-König 	led_set_trigger_data(led_cdev, usbport_data);
3230f247626SRafał Miłecki 	usb_register_notify(&usbport_data->nb);
3242282e125SUwe Kleine-König 	return 0;
32591f7d2e8SChristian Lamparter 
32691f7d2e8SChristian Lamparter err_free:
32791f7d2e8SChristian Lamparter 	kfree(usbport_data);
32891f7d2e8SChristian Lamparter 	return err;
3290f247626SRafał Miłecki }
3300f247626SRafał Miłecki 
usbport_trig_deactivate(struct led_classdev * led_cdev)3310f247626SRafał Miłecki static void usbport_trig_deactivate(struct led_classdev *led_cdev)
3320f247626SRafał Miłecki {
3336f7b0badSUwe Kleine-König 	struct usbport_trig_data *usbport_data = led_get_trigger_data(led_cdev);
3340f247626SRafał Miłecki 	struct usbport_trig_port *port, *tmp;
3350f247626SRafał Miłecki 
3360f247626SRafał Miłecki 	list_for_each_entry_safe(port, tmp, &usbport_data->ports, list) {
3370f247626SRafał Miłecki 		usbport_trig_remove_port(usbport_data, port);
3380f247626SRafał Miłecki 	}
3390f247626SRafał Miłecki 
34091f7d2e8SChristian Lamparter 	sysfs_remove_group(&led_cdev->dev->kobj, &ports_group);
34191f7d2e8SChristian Lamparter 
3420f247626SRafał Miłecki 	usb_unregister_notify(&usbport_data->nb);
3430f247626SRafał Miłecki 
3440f247626SRafał Miłecki 	kfree(usbport_data);
3450f247626SRafał Miłecki }
3460f247626SRafał Miłecki 
3470f247626SRafał Miłecki static struct led_trigger usbport_led_trigger = {
3480f247626SRafał Miłecki 	.name     = "usbport",
3490f247626SRafał Miłecki 	.activate = usbport_trig_activate,
3500f247626SRafał Miłecki 	.deactivate = usbport_trig_deactivate,
3510f247626SRafał Miłecki };
3520f247626SRafał Miłecki 
353*20deab8bSLi Zetao module_led_trigger(usbport_led_trigger);
3540f247626SRafał Miłecki 
3550f247626SRafał Miłecki MODULE_AUTHOR("Rafał Miłecki <rafal@milecki.pl>");
3560f247626SRafał Miłecki MODULE_DESCRIPTION("USB port trigger");
3570f247626SRafał Miłecki MODULE_LICENSE("GPL v2");
358