// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2019 Broadcom */ /* * */ #include #include #include #include #include #include #include #include "bcm_bca_leds.h" struct bca_led { struct led_ops ops; uint32_t pin; struct udevice *dev; struct gpio_desc desc; struct list_head node; }; extern int bca_led_set_value(unsigned int led_num, unsigned int value); extern int bca_led_get_value(unsigned int led_num); extern int bca_led_set_brightness(unsigned int led_num, unsigned int value); extern int bca_led_set_flash_rate(unsigned int led_num, unsigned int value); extern int bca_led_setup_serial(unsigned int led_num, unsigned int polarity, unsigned int is_hw); extern int bca_led_setup_parallel(unsigned int led_num, unsigned int polarity, unsigned int is_hw); /*CLED controller APIs*/ extern int bca_cled_set_value(unsigned int led_num, unsigned int value); extern int bca_cled_get_value(unsigned int led_num); extern int bca_cled_set_brightness(unsigned int led_num, unsigned int value); extern int bca_cled_set_flash_rate(unsigned int led_num, unsigned int value); extern int bca_cled_setup_serial(unsigned int led_num, unsigned int polarity, unsigned int is_hw, struct udevice *dev, bool is_crossbar); extern int bca_cled_setup_parallel(unsigned int led_num, unsigned int polarity, unsigned int is_hw, struct udevice *dev, bool is_crossbar); LIST_HEAD(leds_list); struct udevice* find_led_device_by_pin(uint32_t pin) { struct bca_led *led; list_for_each_entry(led, &leds_list, node) { if (pin == led->pin) return led->dev; } return NULL; } static int bca_gpio_led_set(struct udevice *dev, enum led_state_t value) { struct bca_led *led = dev_get_priv(dev); if (value == LEDST_OFF || value == LEDST_ON) dm_gpio_set_value(&led->desc, value == LEDST_OFF ? 0 : 1); else { int state; state = dm_gpio_get_value(&led->desc); dm_gpio_set_value(&led->desc, state == 0 ? 1 : 0); } return 0; } static enum led_state_t bca_gpio_led_get(struct udevice *dev) { struct bca_led *led = dev_get_priv(dev); int state; state = dm_gpio_get_value(&led->desc); return state == 0 ? LEDST_OFF : LEDST_ON; } static int gpio_led_parse(struct udevice *dev, struct bca_led *led) { uint32_t gpio; int ret; unsigned long gflags = GPIOD_IS_OUT; struct led_uc_plat *uc_plat; char name[5]; if (!dev_read_bool(dev, "software_led")) { printf("Hardware LEDs over GPIO inerface is not supported\n"); return -EINVAL; } if (dev_read_u32u(dev, "pin", &gpio)) { printf("GPIO pin is not specified\n"); return -EINVAL; } if (dev_read_bool(dev, "active_low")) gflags |= GPIOD_ACTIVE_LOW; uc_plat = dev_get_uclass_platdata(dev); uc_plat->label = dev_read_prop(dev, "label", NULL) ? : dev_read_name(dev); snprintf(name, 5, "%d", gpio); ret = dm_gpio_lookup_name(name, &led->desc); if (ret) { printf("Failed to find GPIO %d(%s)\n", gpio, name); return ret; } ret = dm_gpio_request(&led->desc, uc_plat->label); if (ret) { printf("Failed to request GPIO %d\n", gpio); return ret; } led->pin = gpio; dm_gpio_set_dir_flags(&led->desc, gflags); led->ops.set_state = bca_gpio_led_set; led->ops.get_state = bca_gpio_led_get; #ifdef CONFIG_LED_BLINK led->ops.set_period = NULL; #endif return 0; } static enum led_state_t common_bca_led_get(struct udevice *dev, int is_cled) { struct bca_led *led = dev_get_priv(dev); int curr_value; if (is_cled) curr_value = bca_cled_get_value(led->pin); else curr_value = bca_led_get_value(led->pin); return curr_value == 0 ? LEDST_OFF : LEDST_ON; } static enum led_state_t bca_cled_get(struct udevice *dev) { return common_bca_led_get(dev, 1); } static enum led_state_t bca_led_get(struct udevice *dev) { return common_bca_led_get(dev, 0); } static int common_bca_led_set(struct udevice *dev, enum led_state_t value, int is_cled) { int (* led_set_value)(unsigned int led_num, unsigned int value) = NULL; int (* led_set_flash_rate)(unsigned int led_num, unsigned int value) = NULL; int (* led_get_value)(unsigned int led_num) = NULL; struct bca_led *led = dev_get_priv(dev); int curr_value; if (is_cled) { led_set_value = bca_cled_set_value; led_get_value = bca_cled_get_value; led_set_flash_rate = bca_cled_set_flash_rate; } else { led_set_value = bca_led_set_value; led_get_value = bca_led_get_value; led_set_flash_rate = bca_led_set_flash_rate; } switch (value) { case LEDST_OFF: led_set_value(led->pin, 0); led_set_flash_rate(led->pin, 0); break; #ifdef CONFIG_LED_BLINK case LEDST_BLINK: #endif case LEDST_ON: led_set_value(led->pin, 1); break; case LEDST_TOGGLE: curr_value = led_get_value(led->pin); if (curr_value > 0) led_set_value(led->pin, 0); else if (curr_value == 0) led_set_value(led->pin, 1); default: break; } return 0; } static int bca_cled_set(struct udevice *dev, enum led_state_t value) { return common_bca_led_set(dev, value, 1); } static int bca_led_set(struct udevice *dev, enum led_state_t value) { return common_bca_led_set(dev, value, 0); } static unsigned int convert_ms_to_hz(unsigned int ms) { if (ms <= 60) return 1; if (ms <= 120) return 2; if (ms <= 240) return 3; if (ms <= 480) return 4; if (ms <= 960) return 5; if (ms <= 1920) return 6; return 7; } #ifdef CONFIG_LED_BLINK static int common_bca_blink_set(struct udevice *dev, int delay_on, int is_cled) { struct bca_led *led = dev_get_priv(dev); unsigned int delay; delay = convert_ms_to_hz(delay_on); if (is_cled) bca_cled_set_flash_rate(led->pin, delay); else bca_led_set_flash_rate(led->pin, delay); return 0; } static int bca_blink_set(struct udevice *dev, int delay_on) { return common_bca_blink_set(dev, delay_on, 0); } static int bca_c_blink_set(struct udevice *dev, int delay_on) { return common_bca_blink_set(dev, delay_on, 1); } #endif static int serial_led_parse(struct udevice *dev, struct bca_led *led) { bool active_high; bool is_hw; uint32_t led_num; uint32_t init_brightness = 255; uint32_t init_flash_rate = 0; uint32_t default_state; struct led_uc_plat *uc_plat; int ret; if (dev_read_u32u(dev, "bit", &led_num)) { printf("bit property is not specified\n"); return -EINVAL; } is_hw = dev_read_bool(dev, "hardware_led"); active_high = dev_read_bool(dev, "active_high"); ret = bca_led_setup_serial(led_num, active_high, is_hw); if (ret) { if (ret != -ENODEV) printf("Failed to setup serial led %d\n", led_num); return ret; } ret = dev_read_u32u(dev, "brightness", &init_brightness); ret = dev_read_u32u(dev, "flash_rate", &init_flash_rate); bca_led_set_brightness(led_num, init_brightness == 0 ? 0 : init_brightness < 31 ? 1 : init_brightness/31); bca_led_set_flash_rate(led_num, init_flash_rate & 0x7); ret = dev_read_u32u(dev, "default_state", &default_state); if (!ret) { bca_led_set_value(led_num, default_state); } led->pin = led_num; if (is_hw) { led->ops.set_state = NULL; led->ops.get_state = NULL; #ifdef CONFIG_LED_BLINK led->ops.set_period = NULL; #endif } else { led->ops.set_state = bca_led_set; led->ops.get_state = bca_led_get; #ifdef CONFIG_LED_BLINK led->ops.set_period = bca_blink_set; #endif } uc_plat = dev_get_uclass_platdata(dev); uc_plat->label = dev_read_prop(dev, "label", NULL) ? : dev_read_name(dev); return 0; } static int serial_cled_parse(struct udevice *dev, struct bca_led *led) { bool active_high; bool is_hw; uint32_t led_num; uint32_t init_brightness = 255; uint32_t init_flash_rate = 0; uint32_t default_state; struct led_uc_plat *uc_plat; int ret; bool is_crossbar; if (dev_read_u32u(dev, "bit", &led_num)) { printf("bit property is not specified\n"); return -EINVAL; } is_hw = dev_read_bool(dev, "hardware_led"); active_high = dev_read_bool(dev, "active_high"); is_crossbar = dev_read_bool(dev, "crossbar"); ret = bca_cled_setup_serial(led_num, active_high, is_hw, dev, is_crossbar); if (ret) { if (ret != -ENODEV) printf("Failed to setup serial led %d\n", led_num); return ret; } ret = dev_read_u32u(dev, "brightness", &init_brightness); ret = dev_read_u32u(dev, "flash_rate", &init_flash_rate); bca_cled_set_brightness(led_num, init_brightness); bca_cled_set_flash_rate(led_num, init_flash_rate & 0x7); ret = dev_read_u32u(dev, "default_state", &default_state); if (!ret) { bca_cled_set_value(led_num, default_state); } led->pin = led_num; if (is_hw) { led->ops.set_state = NULL; led->ops.get_state = NULL; #ifdef CONFIG_LED_BLINK led->ops.set_period = NULL; #endif } else { led->ops.set_state = bca_cled_set; led->ops.get_state = bca_cled_get; #ifdef CONFIG_LED_BLINK led->ops.set_period = bca_c_blink_set; #endif } uc_plat = dev_get_uclass_platdata(dev); uc_plat->label = dev_read_prop(dev, "label", NULL) ? : dev_read_name(dev); return 0; } static int parallel_led_parse(struct udevice *dev, struct bca_led *led) { bool active_high; bool is_hw; uint32_t led_num; uint32_t init_brightness = 255; uint32_t init_flash_rate = 0; uint32_t default_state; struct led_uc_plat *uc_plat; int ret; if (dev_read_u32u(dev, "bit", &led_num)) { printf("bit property is not specified\n"); return -EINVAL; } is_hw = dev_read_bool(dev, "hardware_led"); active_high = dev_read_bool(dev, "active_high"); ret = bca_led_setup_parallel(led_num, active_high, is_hw); if (ret) { if (ret != -ENODEV) printf("Failed to setup parallel led %d\n", led_num); return ret; } ret = dev_read_u32u(dev, "brightness", &init_brightness); ret = dev_read_u32u(dev, "flash_rate", &init_flash_rate); bca_led_set_brightness(led_num, init_brightness == 0 ? 0 : init_brightness < 31 ? 1 : init_brightness/31); bca_led_set_flash_rate(led_num, init_flash_rate & 0x7); ret = dev_read_u32u(dev, "default_state", &default_state); if (!ret) { bca_led_set_value(led_num, default_state); } led->pin = led_num; if (is_hw) { led->ops.set_state = NULL; led->ops.get_state = NULL; #ifdef CONFIG_LED_BLINK led->ops.set_period = NULL; #endif } else { led->ops.set_state = bca_led_set; led->ops.get_state = bca_led_get; #ifdef CONFIG_LED_BLINK led->ops.set_period = bca_blink_set; #endif } uc_plat = dev_get_uclass_platdata(dev); uc_plat->label = dev_read_prop(dev, "label", NULL) ? : dev_read_name(dev); return 0; } static int parallel_cled_parse(struct udevice *dev, struct bca_led *led) { bool active_high; bool is_hw; uint32_t led_num; uint32_t init_brightness = 255; uint32_t init_flash_rate = 0; uint32_t default_state; struct led_uc_plat *uc_plat; int ret; bool is_crossbar; if (dev_read_u32u(dev, "bit", &led_num)) { printf("bit property is not specified\n"); return -EINVAL; } is_hw = dev_read_bool(dev, "hardware_led"); active_high = dev_read_bool(dev, "active_high"); is_crossbar = dev_read_bool(dev, "crossbar"); ret = bca_cled_setup_parallel(led_num, active_high, is_hw, dev, is_crossbar); if (ret) { if (ret != -ENODEV) printf("Failed to setup parallel led %d\n", led_num); return ret; } ret = dev_read_u32u(dev, "brightness", &init_brightness); ret = dev_read_u32u(dev, "flash_rate", &init_flash_rate); bca_cled_set_brightness(led_num, init_brightness); bca_cled_set_flash_rate(led_num, init_flash_rate & 0x7); ret = dev_read_u32u(dev, "default_state", &default_state); if (!ret) { bca_cled_set_value(led_num, default_state); } led->pin = led_num; if (is_hw) { led->ops.set_state = NULL; led->ops.get_state = NULL; #ifdef CONFIG_LED_BLINK led->ops.set_period = NULL; #endif } else { led->ops.set_state = bca_cled_set; led->ops.get_state = bca_cled_get; #ifdef CONFIG_LED_BLINK led->ops.set_period = bca_c_blink_set; #endif } uc_plat = dev_get_uclass_platdata(dev); uc_plat->label = dev_read_prop(dev, "label", NULL) ? : dev_read_name(dev); return 0; } static enum led_state_t bcm_led_get_state(struct udevice *dev) { struct bca_led *led; led = dev_get_priv(dev); if (led->ops.get_state) return led->ops.get_state(dev); else return LEDST_OFF; } static int bcm_led_set_state(struct udevice *dev, enum led_state_t state) { struct bca_led *led; led = dev_get_priv(dev); if (led->ops.set_state) return led->ops.set_state(dev, state); else return -EINVAL; }; #ifdef CONFIG_LED_BLINK static int bcm_led_set_period(struct udevice *dev, int period_ms) { struct bca_led *led; led = dev_get_priv(dev); if (led->ops.set_period) return led->ops.set_period(dev, period_ms); else return -EINVAL; } #endif static const struct led_ops bcm_bca_led_ops = { .get_state = bcm_led_get_state, .set_state = bcm_led_set_state, #ifdef CONFIG_LED_BLINK .set_period = bcm_led_set_period, #endif }; static const struct udevice_id bca_leds_of_match[] = { { .compatible = "brcm,serial-led", .data = (long unsigned int )serial_led_parse, }, { .compatible = "brcm,serial-cled", .data = (long unsigned int )serial_cled_parse, }, { .compatible = "brcm,gpio-led", .data = (long unsigned int)gpio_led_parse, }, { .compatible = "brcm,parallel-led", .data = (long unsigned int)parallel_led_parse, }, { .compatible = "brcm,parallel-cled", .data = (long unsigned int)parallel_cled_parse, }, { }, }; static int driver_check_compatible(const struct udevice_id *of_match, const struct udevice_id **of_idp, const char *compat) { if (!of_match) return -ENOENT; while (of_match->compatible) { if (!strcmp(of_match->compatible, compat)) { *of_idp = of_match; return 0; } of_match++; } return -ENOENT; } static int bca_leds_bind(struct udevice *dev) { const char *compat, *status; const struct udevice_id *id; int ret; compat = dev_read_prop(dev, "compatible", NULL); ret = driver_check_compatible(bca_leds_of_match, &id, compat); if (!ret) { status = dev_read_prop(dev, "status", NULL); if (!strcmp("ok", status) || !strcmp("okay", status)) dev->driver_data = id->data; else ret = -ENODEV; } return ret; } static int bca_leds_probe(struct udevice *dev) { struct bca_led *led; int ret = 0; int(*parse_func)(struct udevice *dev, struct bca_led *led); ulong data = 0; data = dev_get_driver_data(dev); if (!data) return -ENODEV; led = dev_get_priv(dev); parse_func = (int(*)(struct udevice *, struct bca_led *))data; ret = parse_func(dev, led); if (ret) goto error; if (led->ops.set_state) /* Software leds will be registered in Linux framework */ { list_add_tail(&led->node, &leds_list); printf("SW led %d registered\n", led->pin); } else { printf("HW led %d registered\n", led->pin); } return 0; error: return ret; } U_BOOT_DRIVER(bcm_bca_leds_driver) = { .name = "bcm-bca-leds", .id = UCLASS_LED, .of_match = bca_leds_of_match, .probe = bca_leds_probe, .bind = bca_leds_bind, .priv_auto_alloc_size = sizeof(struct bca_led), .ops = &bcm_bca_led_ops, }; #define led_err(fmt, ...) \ printf("%s:L%d "fmt, __FUNCTION__, __LINE__, ##__VA_ARGS__) #define led_info(fmt, ...) \ printf("%s:L%d "fmt, __FUNCTION__, __LINE__, ##__VA_ARGS__) static const ofnode of_parse_phandle(const ofnode handle, const char *phandle_name, int index) { ofnode node = ofnode_null(); struct ofnode_phandle_args out_args; if (ofnode_parse_phandle_with_args(handle, phandle_name, NULL, 0, index, &out_args) == 0) { node = out_args.node; } return node; } struct udevice * bca_led_request_sw_led(ofnode dn, const char *consumer_led_name) { ofnode led_node; uint32_t led; led_node = of_parse_phandle(dn, consumer_led_name, 0); if (led_node.of_offset == -1) { led_info("%s led not found\n", consumer_led_name); return NULL; } if (ofnode_read_u32(led_node, "bit", &led)) { if (ofnode_read_u32(led_node, "pin", &led)) { led_err("LED structure referenced by %s is in wrong format\n", consumer_led_name); return NULL; } } return find_led_device_by_pin(led); } void bca_led_request_network_leds(ofnode dn, bca_leds_info_t *led_info) { int i; ofnode led_node; uint32_t port, led; uint32_t link; uint32_t activity; int ret; led_info->port_id = 0xff; for(i = 0; i < 4; i++) { led_info->link[i] = 0; led_info->activity[i] = 0; } for(i = 0; i < 4; i++) { led_node = of_parse_phandle(dn, "network-leds", i); if (led_node.of_offset == -1) return; if (!ofnode_read_bool(led_node, "hardware_led")) { led_err("Only Hardware LEDs could be used as Network Leds node\n"); continue; } ret = ofnode_read_u32(led_node, "port", &port); if (ret) { led_err("LED structure referenced by is in wrong format\n"); continue; } if (led_info->port_id != 0xff && led_info->port_id != port) { led_err("HW LED assigned to port %d referenced from port %d\n", port, led_info->port_id); continue; } ret = ofnode_read_u32(led_node, "led", &led); if (ret) { led_err("LED structure referenced is in wrong format\n"); continue; } link = 0; activity = 0; ofnode_read_u32(led_node, "link", &link); ofnode_read_u32(led_node, "activity", &activity); led_info("network led %d for port %d link 0x%x activity 0x%x\n", led, port, link, activity); if (link == 0 && activity == 0) { led_err("At least one of the activity/link LED functionality must be specified for port %d led %d\n", port, led); continue; } led_info->port_id = port; led_info->link[led] = link; led_info->activity[led] = activity; } }