diff options
Diffstat (limited to 'drivers/leds/leds-netxbig.c')
| -rw-r--r-- | drivers/leds/leds-netxbig.c | 512 |
1 files changed, 373 insertions, 139 deletions
diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index c61c5ebcc08e..99df46f2d9f5 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -1,34 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * leds-netxbig.c - Driver for the 2Big and 5Big Network series LEDs * * Copyright (C) 2010 LaCie * * Author: Simon Guinot <sguinot@lacie.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/module.h> -#include <linux/init.h> #include <linux/irq.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/platform_device.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/leds.h> -#include <linux/platform_data/leds-kirkwood-netxbig.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +struct netxbig_gpio_ext { + struct gpio_desc **addr; + int num_addr; + struct gpio_desc **data; + int num_data; + struct gpio_desc *enable; +}; + +enum netxbig_led_mode { + NETXBIG_LED_OFF, + NETXBIG_LED_ON, + NETXBIG_LED_SATA, + NETXBIG_LED_TIMER1, + NETXBIG_LED_TIMER2, + NETXBIG_LED_MODE_NUM, +}; + +#define NETXBIG_LED_INVALID_MODE NETXBIG_LED_MODE_NUM + +struct netxbig_led_timer { + unsigned long delay_on; + unsigned long delay_off; + enum netxbig_led_mode mode; +}; + +struct netxbig_led { + const char *name; + const char *default_trigger; + int mode_addr; + int *mode_val; + int bright_addr; + int bright_max; +}; + +struct netxbig_led_platform_data { + struct netxbig_gpio_ext *gpio_ext; + struct netxbig_led_timer *timer; + int num_timer; + struct netxbig_led *leds; + int num_leds; +}; /* * GPIO extension bus. @@ -41,7 +70,7 @@ static void gpio_ext_set_addr(struct netxbig_gpio_ext *gpio_ext, int addr) int pin; for (pin = 0; pin < gpio_ext->num_addr; pin++) - gpio_set_value(gpio_ext->addr[pin], (addr >> pin) & 1); + gpiod_set_value(gpio_ext->addr[pin], (addr >> pin) & 1); } static void gpio_ext_set_data(struct netxbig_gpio_ext *gpio_ext, int data) @@ -49,14 +78,14 @@ static void gpio_ext_set_data(struct netxbig_gpio_ext *gpio_ext, int data) int pin; for (pin = 0; pin < gpio_ext->num_data; pin++) - gpio_set_value(gpio_ext->data[pin], (data >> pin) & 1); + gpiod_set_value(gpio_ext->data[pin], (data >> pin) & 1); } static void gpio_ext_enable_select(struct netxbig_gpio_ext *gpio_ext) { /* Enable select is done on the raising edge. */ - gpio_set_value(gpio_ext->enable, 0); - gpio_set_value(gpio_ext->enable, 1); + gpiod_set_value(gpio_ext->enable, 0); + gpiod_set_value(gpio_ext->enable, 1); } static void gpio_ext_set_value(struct netxbig_gpio_ext *gpio_ext, @@ -71,58 +100,6 @@ static void gpio_ext_set_value(struct netxbig_gpio_ext *gpio_ext, spin_unlock_irqrestore(&gpio_ext_lock, flags); } -static int gpio_ext_init(struct netxbig_gpio_ext *gpio_ext) -{ - int err; - int i; - - if (unlikely(!gpio_ext)) - return -EINVAL; - - /* Configure address GPIOs. */ - for (i = 0; i < gpio_ext->num_addr; i++) { - err = gpio_request_one(gpio_ext->addr[i], GPIOF_OUT_INIT_LOW, - "GPIO extension addr"); - if (err) - goto err_free_addr; - } - /* Configure data GPIOs. */ - for (i = 0; i < gpio_ext->num_data; i++) { - err = gpio_request_one(gpio_ext->data[i], GPIOF_OUT_INIT_LOW, - "GPIO extension data"); - if (err) - goto err_free_data; - } - /* Configure "enable select" GPIO. */ - err = gpio_request_one(gpio_ext->enable, GPIOF_OUT_INIT_LOW, - "GPIO extension enable"); - if (err) - goto err_free_data; - - return 0; - -err_free_data: - for (i = i - 1; i >= 0; i--) - gpio_free(gpio_ext->data[i]); - i = gpio_ext->num_addr; -err_free_addr: - for (i = i - 1; i >= 0; i--) - gpio_free(gpio_ext->addr[i]); - - return err; -} - -static void gpio_ext_free(struct netxbig_gpio_ext *gpio_ext) -{ - int i; - - gpio_free(gpio_ext->enable); - for (i = gpio_ext->num_addr - 1; i >= 0; i--) - gpio_free(gpio_ext->addr[i]); - for (i = gpio_ext->num_data - 1; i >= 0; i--) - gpio_free(gpio_ext->data[i]); -} - /* * Class LED driver. */ @@ -133,7 +110,6 @@ struct netxbig_led_data { int mode_addr; int *mode_val; int bright_addr; - int bright_max; struct netxbig_led_timer *timer; int num_timer; enum netxbig_led_mode mode; @@ -195,7 +171,7 @@ static void netxbig_led_set(struct led_classdev *led_cdev, struct netxbig_led_data *led_dat = container_of(led_cdev, struct netxbig_led_data, cdev); enum netxbig_led_mode mode; - int mode_val, bright_val; + int mode_val; int set_brightness = 1; unsigned long flags; @@ -221,19 +197,16 @@ static void netxbig_led_set(struct led_classdev *led_cdev, * SATA LEDs. So, change the brightness setting for a single * SATA LED will affect all the others. */ - if (set_brightness) { - bright_val = DIV_ROUND_UP(value * led_dat->bright_max, - LED_FULL); + if (set_brightness) gpio_ext_set_value(led_dat->gpio_ext, - led_dat->bright_addr, bright_val); - } + led_dat->bright_addr, value); spin_unlock_irqrestore(&led_dat->lock, flags); } -static ssize_t netxbig_led_sata_store(struct device *dev, - struct device_attribute *attr, - const char *buff, size_t count) +static ssize_t sata_store(struct device *dev, + struct device_attribute *attr, + const char *buff, size_t count) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct netxbig_led_data *led_dat = @@ -282,8 +255,8 @@ exit_unlock: return ret; } -static ssize_t netxbig_led_sata_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t sata_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct netxbig_led_data *led_dat = @@ -292,23 +265,19 @@ static ssize_t netxbig_led_sata_show(struct device *dev, return sprintf(buf, "%d\n", led_dat->sata); } -static DEVICE_ATTR(sata, 0644, netxbig_led_sata_show, netxbig_led_sata_store); +static DEVICE_ATTR_RW(sata); -static void delete_netxbig_led(struct netxbig_led_data *led_dat) -{ - if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE) - device_remove_file(led_dat->cdev.dev, &dev_attr_sata); - led_classdev_unregister(&led_dat->cdev); -} +static struct attribute *netxbig_led_attrs[] = { + &dev_attr_sata.attr, + NULL +}; +ATTRIBUTE_GROUPS(netxbig_led); -static int -create_netxbig_led(struct platform_device *pdev, - struct netxbig_led_data *led_dat, - const struct netxbig_led *template) +static int create_netxbig_led(struct platform_device *pdev, + struct netxbig_led_platform_data *pdata, + struct netxbig_led_data *led_dat, + const struct netxbig_led *template) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; - int ret; - spin_lock_init(&led_dat->lock); led_dat->gpio_ext = pdata->gpio_ext; led_dat->cdev.name = template->name; @@ -327,90 +296,355 @@ create_netxbig_led(struct platform_device *pdev, */ led_dat->sata = 0; led_dat->cdev.brightness = LED_OFF; + led_dat->cdev.max_brightness = template->bright_max; led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; led_dat->mode_addr = template->mode_addr; led_dat->mode_val = template->mode_val; led_dat->bright_addr = template->bright_addr; - led_dat->bright_max = (1 << pdata->gpio_ext->num_data) - 1; led_dat->timer = pdata->timer; led_dat->num_timer = pdata->num_timer; + /* + * If available, expose the SATA activity blink capability through + * a "sata" sysfs attribute. + */ + if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE) + led_dat->cdev.groups = netxbig_led_groups; - ret = led_classdev_register(&pdev->dev, &led_dat->cdev); - if (ret < 0) + return devm_led_classdev_register(&pdev->dev, &led_dat->cdev); +} + +/** + * netxbig_gpio_ext_remove() - Clean up GPIO extension data + * @data: managed resource data to clean up + * + * Since we pick GPIO descriptors from another device than the device our + * driver is probing to, we need to register a specific callback to free + * these up using managed resources. + */ +static void netxbig_gpio_ext_remove(void *data) +{ + struct netxbig_gpio_ext *gpio_ext = data; + int i; + + for (i = 0; i < gpio_ext->num_addr; i++) + gpiod_put(gpio_ext->addr[i]); + for (i = 0; i < gpio_ext->num_data; i++) + gpiod_put(gpio_ext->data[i]); + gpiod_put(gpio_ext->enable); +} + +/** + * netxbig_gpio_ext_get() - Obtain GPIO extension device data + * @dev: main LED device + * @gpio_ext_dev: the GPIO extension device + * @gpio_ext: the data structure holding the GPIO extension data + * + * This function walks the subdevice that only contain GPIO line + * handles in the device tree and obtains the GPIO descriptors from that + * device. + */ +static int netxbig_gpio_ext_get(struct device *dev, + struct device *gpio_ext_dev, + struct netxbig_gpio_ext *gpio_ext) +{ + struct gpio_desc **addr, **data; + int num_addr, num_data; + struct gpio_desc *gpiod; + int ret; + int i; + + ret = gpiod_count(gpio_ext_dev, "addr"); + if (ret < 0) { + dev_err(dev, + "Failed to count GPIOs in DT property addr-gpios\n"); return ret; + } + num_addr = ret; + addr = devm_kcalloc(dev, num_addr, sizeof(*addr), GFP_KERNEL); + if (!addr) + return -ENOMEM; + + gpio_ext->addr = addr; + gpio_ext->num_addr = 0; /* - * If available, expose the SATA activity blink capability through - * a "sata" sysfs attribute. + * We cannot use devm_ managed resources with these GPIO descriptors + * since they are associated with the "GPIO extension device" which + * does not probe any driver. The device tree parser will however + * populate a platform device for it so we can anyway obtain the + * GPIO descriptors from the device. */ - if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE) { - ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); - if (ret) - led_classdev_unregister(&led_dat->cdev); + for (i = 0; i < num_addr; i++) { + gpiod = gpiod_get_index(gpio_ext_dev, "addr", i, + GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + goto err_set_code; + gpiod_set_consumer_name(gpiod, "GPIO extension addr"); + addr[i] = gpiod; + gpio_ext->num_addr++; } + ret = gpiod_count(gpio_ext_dev, "data"); + if (ret < 0) { + dev_err(dev, + "Failed to count GPIOs in DT property data-gpios\n"); + goto err_free_addr; + } + num_data = ret; + data = devm_kcalloc(dev, num_data, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto err_free_addr; + } + + gpio_ext->data = data; + gpio_ext->num_data = 0; + + for (i = 0; i < num_data; i++) { + gpiod = gpiod_get_index(gpio_ext_dev, "data", i, + GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + goto err_free_data; + gpiod_set_consumer_name(gpiod, "GPIO extension data"); + data[i] = gpiod; + gpio_ext->num_data++; + } + + gpiod = gpiod_get(gpio_ext_dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) { + dev_err(dev, + "Failed to get GPIO from DT property enable-gpio\n"); + goto err_free_data; + } + gpiod_set_consumer_name(gpiod, "GPIO extension enable"); + gpio_ext->enable = gpiod; + + return devm_add_action_or_reset(dev, netxbig_gpio_ext_remove, gpio_ext); + +err_free_data: + for (i = 0; i < gpio_ext->num_data; i++) + gpiod_put(gpio_ext->data[i]); +err_set_code: + ret = PTR_ERR(gpiod); +err_free_addr: + for (i = 0; i < gpio_ext->num_addr; i++) + gpiod_put(gpio_ext->addr[i]); return ret; } -static int netxbig_led_probe(struct platform_device *pdev) +static int netxbig_leds_get_of_pdata(struct device *dev, + struct netxbig_led_platform_data *pdata) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; - struct netxbig_led_data *leds_data; - int i; + struct device_node *np = dev_of_node(dev); + struct device_node *gpio_ext_np; + struct platform_device *gpio_ext_pdev; + struct device *gpio_ext_dev; + struct netxbig_gpio_ext *gpio_ext; + struct netxbig_led_timer *timers; + struct netxbig_led *leds, *led; + int num_timers; + int num_leds = 0; int ret; + int i; - if (!pdata) + /* GPIO extension */ + gpio_ext_np = of_parse_phandle(np, "gpio-ext", 0); + if (!gpio_ext_np) { + dev_err(dev, "Failed to get DT handle gpio-ext\n"); return -EINVAL; + } + gpio_ext_pdev = of_find_device_by_node(gpio_ext_np); + if (!gpio_ext_pdev) { + of_node_put(gpio_ext_np); + dev_err(dev, "Failed to find platform device for gpio-ext\n"); + return -ENODEV; + } + gpio_ext_dev = &gpio_ext_pdev->dev; - leds_data = devm_kzalloc(&pdev->dev, - sizeof(struct netxbig_led_data) * pdata->num_leds, GFP_KERNEL); - if (!leds_data) - return -ENOMEM; + gpio_ext = devm_kzalloc(dev, sizeof(*gpio_ext), GFP_KERNEL); + if (!gpio_ext) { + of_node_put(gpio_ext_np); + ret = -ENOMEM; + goto put_device; + } + ret = netxbig_gpio_ext_get(dev, gpio_ext_dev, gpio_ext); + of_node_put(gpio_ext_np); + if (ret) + goto put_device; + pdata->gpio_ext = gpio_ext; + + /* Timers (optional) */ + ret = of_property_count_u32_elems(np, "timers"); + if (ret > 0) { + if (ret % 3) { + ret = -EINVAL; + goto put_device; + } - ret = gpio_ext_init(pdata->gpio_ext); - if (ret < 0) - return ret; + num_timers = ret / 3; + timers = devm_kcalloc(dev, num_timers, sizeof(*timers), + GFP_KERNEL); + if (!timers) { + ret = -ENOMEM; + goto put_device; + } + for (i = 0; i < num_timers; i++) { + u32 tmp; + + of_property_read_u32_index(np, "timers", 3 * i, + &timers[i].mode); + if (timers[i].mode >= NETXBIG_LED_MODE_NUM) { + ret = -EINVAL; + goto put_device; + } + of_property_read_u32_index(np, "timers", + 3 * i + 1, &tmp); + timers[i].delay_on = tmp; + of_property_read_u32_index(np, "timers", + 3 * i + 2, &tmp); + timers[i].delay_off = tmp; + } + pdata->timer = timers; + pdata->num_timer = num_timers; + } - for (i = 0; i < pdata->num_leds; i++) { - ret = create_netxbig_led(pdev, &leds_data[i], &pdata->leds[i]); - if (ret < 0) - goto err_free_leds; + /* LEDs */ + num_leds = of_get_available_child_count(np); + if (!num_leds) { + dev_err(dev, "No LED subnodes found in DT\n"); + ret = -ENODEV; + goto put_device; } - platform_set_drvdata(pdev, leds_data); + leds = devm_kcalloc(dev, num_leds, sizeof(*leds), GFP_KERNEL); + if (!leds) { + ret = -ENOMEM; + goto put_device; + } - return 0; + led = leds; + for_each_available_child_of_node_scoped(np, child) { + const char *string; + int *mode_val; + int num_modes; + + ret = of_property_read_u32(child, "mode-addr", + &led->mode_addr); + if (ret) + goto put_device; + + ret = of_property_read_u32(child, "bright-addr", + &led->bright_addr); + if (ret) + goto put_device; + + ret = of_property_read_u32(child, "max-brightness", + &led->bright_max); + if (ret) + goto put_device; + + mode_val = + devm_kcalloc(dev, + NETXBIG_LED_MODE_NUM, sizeof(*mode_val), + GFP_KERNEL); + if (!mode_val) { + ret = -ENOMEM; + goto put_device; + } + + for (i = 0; i < NETXBIG_LED_MODE_NUM; i++) + mode_val[i] = NETXBIG_LED_INVALID_MODE; + + ret = of_property_count_u32_elems(child, "mode-val"); + if (ret < 0 || ret % 2) { + ret = -EINVAL; + goto put_device; + } + num_modes = ret / 2; + if (num_modes > NETXBIG_LED_MODE_NUM) { + ret = -EINVAL; + goto put_device; + } + + for (i = 0; i < num_modes; i++) { + int mode; + int val; + + of_property_read_u32_index(child, + "mode-val", 2 * i, &mode); + of_property_read_u32_index(child, + "mode-val", 2 * i + 1, &val); + if (mode >= NETXBIG_LED_MODE_NUM) { + ret = -EINVAL; + goto put_device; + } + mode_val[mode] = val; + } + led->mode_val = mode_val; + + if (!of_property_read_string(child, "label", &string)) + led->name = string; + else + led->name = child->name; -err_free_leds: - for (i = i - 1; i >= 0; i--) - delete_netxbig_led(&leds_data[i]); + if (!of_property_read_string(child, + "linux,default-trigger", &string)) + led->default_trigger = string; - gpio_ext_free(pdata->gpio_ext); + led++; + } + + pdata->leds = leds; + pdata->num_leds = num_leds; + + return 0; + +put_device: + put_device(gpio_ext_dev); return ret; } -static int netxbig_led_remove(struct platform_device *pdev) +static const struct of_device_id of_netxbig_leds_match[] = { + { .compatible = "lacie,netxbig-leds", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_netxbig_leds_match); + +static int netxbig_led_probe(struct platform_device *pdev) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; + struct netxbig_led_platform_data *pdata; struct netxbig_led_data *leds_data; int i; + int ret; - leds_data = platform_get_drvdata(pdev); + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + ret = netxbig_leds_get_of_pdata(&pdev->dev, pdata); + if (ret) + return ret; - for (i = 0; i < pdata->num_leds; i++) - delete_netxbig_led(&leds_data[i]); + leds_data = devm_kcalloc(&pdev->dev, + pdata->num_leds, sizeof(*leds_data), + GFP_KERNEL); + if (!leds_data) + return -ENOMEM; - gpio_ext_free(pdata->gpio_ext); + for (i = 0; i < pdata->num_leds; i++) { + ret = create_netxbig_led(pdev, pdata, + &leds_data[i], &pdata->leds[i]); + if (ret < 0) + return ret; + } return 0; } static struct platform_driver netxbig_led_driver = { .probe = netxbig_led_probe, - .remove = netxbig_led_remove, .driver = { - .name = "leds-netxbig", - .owner = THIS_MODULE, + .name = "leds-netxbig", + .of_match_table = of_netxbig_leds_match, }, }; |
