diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig index 5248fd0ff7a..d4529dbd55b 100755 --- a/components/drivers/Kconfig +++ b/components/drivers/Kconfig @@ -21,6 +21,7 @@ rsource "touch/Kconfig" rsource "graphic/Kconfig" rsource "hwcrypto/Kconfig" rsource "wlan/Kconfig" +rsource "led/Kconfig" rsource "mailbox/Kconfig" rsource "phye/Kconfig" rsource "ata/Kconfig" diff --git a/components/drivers/include/drivers/led.h b/components/drivers/include/drivers/led.h new file mode 100644 index 00000000000..aeb80a6d64f --- /dev/null +++ b/components/drivers/include/drivers/led.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-3-08 GuEe-GUI the first version + */ + +#ifndef __LED_H__ +#define __LED_H__ + +#include +#include + +struct rt_led_ops; + +enum rt_led_state +{ + RT_LED_S_OFF, + RT_LED_S_ON, + RT_LED_S_TOGGLE, + RT_LED_S_BLINK, + + RT_LED_STATE_NR, +}; + +struct rt_led_device +{ + struct rt_device parent; + + const struct rt_led_ops *ops; + + struct rt_spinlock spinlock; + + void *sysdata; + void *priv; +}; + +struct rt_led_ops +{ + rt_err_t (*set_state)(struct rt_led_device *led, enum rt_led_state state); + rt_err_t (*get_state)(struct rt_led_device *led, enum rt_led_state *out_state); + rt_err_t (*set_period)(struct rt_led_device *led, rt_uint32_t period_ms); + rt_err_t (*set_brightness)(struct rt_led_device *led, rt_uint32_t brightness); +}; + +rt_err_t rt_hw_led_register(struct rt_led_device *led); +rt_err_t rt_hw_led_unregister(struct rt_led_device *led); + +rt_err_t rt_led_set_state(struct rt_led_device *led, enum rt_led_state state); +rt_err_t rt_led_get_state(struct rt_led_device *led, enum rt_led_state *out_state); +rt_err_t rt_led_set_period(struct rt_led_device *led, rt_uint32_t period_ms); +rt_err_t rt_led_set_brightness(struct rt_led_device *led, rt_uint32_t brightness); + +#endif /* __LED_H__ */ diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h index ef395d3192c..5cafd26b33c 100644 --- a/components/drivers/include/rtdevice.h +++ b/components/drivers/include/rtdevice.h @@ -51,6 +51,10 @@ extern "C" { #endif /* RT_ATA_AHCI */ #endif /* RT_USING_ATA */ +#ifdef RT_USING_LED +#include "drivers/led.h" +#endif + #ifdef RT_USING_MBOX #include "drivers/mailbox.h" #endif /* RT_USING_MBOX */ diff --git a/components/drivers/led/Kconfig b/components/drivers/led/Kconfig new file mode 100644 index 00000000000..462aa0bac04 --- /dev/null +++ b/components/drivers/led/Kconfig @@ -0,0 +1,15 @@ +menuconfig RT_USING_LED + bool "Using Light Emitting Diode (LED) device drivers" + depends on RT_USING_DM + default n + +config RT_LED_GPIO + bool "GPIO connected LEDs Support" + depends on RT_USING_LED + depends on RT_USING_PINCTRL + depends on RT_USING_OFW + default n + +if RT_USING_LED + osource "$(SOC_DM_LED_DIR)/Kconfig" +endif diff --git a/components/drivers/led/SConscript b/components/drivers/led/SConscript new file mode 100644 index 00000000000..276ca0bd8b8 --- /dev/null +++ b/components/drivers/led/SConscript @@ -0,0 +1,18 @@ +from building import * + +group = [] + +if not GetDepend(['RT_USING_DM', 'RT_USING_LED']): + Return('group') + +cwd = GetCurrentDir() +CPPPATH = [cwd + '/../include'] + +src = ['led.c'] + +if GetDepend(['RT_LED_GPIO']): + src += ['led-gpio.c'] + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/drivers/led/led-gpio.c b/components/drivers/led/led-gpio.c new file mode 100644 index 00000000000..0b6bd6875ee --- /dev/null +++ b/components/drivers/led/led-gpio.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-3-08 GuEe-GUI the first version + */ + +#include +#include + +#define DBG_TAG "led.gpio" +#define DBG_LVL DBG_INFO +#include + +struct gpio_led +{ + struct rt_led_device parent; + + rt_base_t pin; + rt_uint8_t active_val; +}; + +#define raw_to_gpio_led(raw) rt_container_of(raw, struct gpio_led, parent); + +static rt_err_t gpio_led_set_state(struct rt_led_device *led, enum rt_led_state state) +{ + rt_err_t err = RT_EOK; + struct gpio_led *gled = raw_to_gpio_led(led); + + rt_pin_mode(gled->pin, PIN_MODE_OUTPUT); + + switch (state) + { + case RT_LED_S_OFF: + rt_pin_write(gled->pin, !gled->active_val); + break; + + case RT_LED_S_ON: + rt_pin_write(gled->pin, gled->active_val); + break; + + case RT_LED_S_TOGGLE: + err = led->ops->get_state(led, &state); + + if (!err) + { + err = led->ops->set_state(led, state == RT_LED_S_OFF ? RT_LED_S_ON : RT_LED_S_OFF); + } + break; + + default: + return -RT_ENOSYS; + } + + return err; +} + +static rt_err_t gpio_led_get_state(struct rt_led_device *led, enum rt_led_state *out_state) +{ + struct gpio_led *gled = raw_to_gpio_led(led); + + switch (rt_pin_read(gled->pin)) + { + case PIN_LOW: + *out_state = RT_LED_S_OFF; + break; + + case PIN_HIGH: + *out_state = RT_LED_S_ON; + break; + + default: + return -RT_ERROR; + } + + return RT_EOK; +} + +const static struct rt_led_ops gpio_led_ops = +{ + .set_state = gpio_led_set_state, + .get_state = gpio_led_get_state, +}; + +static rt_err_t ofw_append_gpio_led(struct rt_ofw_node *np) +{ + rt_err_t err; + enum rt_led_state led_state = RT_LED_S_OFF; + const char *propname, *state, *trigger; + struct gpio_led *gled = rt_malloc(sizeof(*gled)); + + if (!gled) + { + return -RT_ENOMEM; + } + + gled->pin = rt_ofw_get_named_pin(np, RT_NULL, 0, RT_NULL, &gled->active_val); + + if (gled->pin < 0) + { + err = gled->pin; + + goto _fail; + } + + gled->parent.ops = &gpio_led_ops; + + if ((err = rt_hw_led_register(&gled->parent))) + { + goto _fail; + } + + if (!rt_ofw_prop_read_string(np, "default-state", &state)) + { + if (!rt_strcmp(state, "on")) + { + led_state = RT_LED_S_ON; + } + } + + if ((propname = rt_ofw_get_prop_fuzzy_name(np, "default-trigger$"))) + { + if (!rt_ofw_prop_read_string(np, propname, &trigger)) + { + if (!rt_strcmp(trigger, "heartbeat") || + !rt_strcmp(trigger, "timer")) + { + led_state = RT_LED_S_BLINK; + } + } + } + + rt_led_set_state(&gled->parent, led_state); + + rt_ofw_data(np) = &gled->parent; + + return RT_EOK; + +_fail: + rt_free(gled); + + return err; +} + +static rt_err_t gpio_led_probe(struct rt_platform_device *pdev) +{ + rt_bool_t pinctrl_apply = RT_FALSE; + struct rt_ofw_node *led_np, *np = pdev->parent.ofw_node; + + if (rt_ofw_prop_read_bool(np, "pinctrl-0")) + { + pinctrl_apply = RT_TRUE; + rt_pin_ctrl_confs_apply_by_name(&pdev->parent, RT_NULL); + } + + rt_ofw_foreach_available_child_node(np, led_np) + { + rt_err_t err = ofw_append_gpio_led(led_np); + + if (err == -RT_ENOMEM) + { + rt_ofw_node_put(led_np); + + return err; + } + else if (err) + { + LOG_E("%s: create LED fail", rt_ofw_node_full_name(led_np)); + continue; + } + + if (!pinctrl_apply) + { + struct rt_device dev_tmp; + + dev_tmp.ofw_node = led_np; + rt_pin_ctrl_confs_apply_by_name(&dev_tmp, RT_NULL); + } + } + + return RT_EOK; +} + +static rt_err_t gpio_led_remove(struct rt_platform_device *pdev) +{ + struct gpio_led *gled; + struct rt_led_device *led_dev; + struct rt_ofw_node *led_np, *np = pdev->parent.ofw_node; + + rt_ofw_foreach_available_child_node(np, led_np) + { + led_dev = rt_ofw_data(led_np); + + if (!led_dev) + { + continue; + } + + gled = rt_container_of(led_dev, struct gpio_led, parent); + + rt_ofw_data(led_np) = RT_NULL; + + rt_hw_led_unregister(&gled->parent); + + rt_free(gled); + } + + return RT_EOK; +} + +static const struct rt_ofw_node_id gpio_led_ofw_ids[] = +{ + { .compatible = "gpio-leds" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver gpio_led_driver = +{ + .name = "led-gpio", + .ids = gpio_led_ofw_ids, + + .probe = gpio_led_probe, + .remove = gpio_led_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(gpio_led_driver); diff --git a/components/drivers/led/led.c b/components/drivers/led/led.c new file mode 100644 index 00000000000..8bfd44bfe20 --- /dev/null +++ b/components/drivers/led/led.c @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-3-08 GuEe-GUI the first version + */ + +#include + +#define DBG_TAG "rtdm.led" +#define DBG_LVL DBG_INFO +#include + +#include +#include + +struct blink_timer +{ + rt_bool_t toggle; + rt_bool_t enabled; + struct rt_timer timer; +}; + +static struct rt_dm_ida led_ida = RT_DM_IDA_INIT(LED); + +static const char * const _led_states[] = +{ + [RT_LED_S_OFF] = "off", + [RT_LED_S_ON] = "on", + [RT_LED_S_TOGGLE] = "toggle", + [RT_LED_S_BLINK] = "blink", +}; + +static rt_ssize_t _led_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) +{ + rt_ssize_t res; + rt_size_t state_len; + enum rt_led_state state; + struct rt_led_device *led = rt_container_of(dev, struct rt_led_device, parent); + + if ((res = rt_led_get_state(led, &state))) + { + return res; + } + + state_len = rt_strlen(_led_states[state]); + + if (pos < state_len) + { + size = rt_min_t(rt_size_t, size, size - pos); + ((char *)buffer)[size - 1] = '\0'; + rt_strncpy(buffer, &_led_states[state][pos], size); + + return size; + } + else + { + return 0; + } +} + +static rt_ssize_t _led_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) +{ + rt_uint32_t brightness = 0; + const char *value = buffer; + struct rt_led_device *led = rt_container_of(dev, struct rt_led_device, parent); + + for (int i = 0; i < RT_ARRAY_SIZE(_led_states); ++i) + { + if (!rt_strncpy((char *)_led_states[i], buffer, size)) + { + return rt_led_set_state(led, i) ? : size; + } + } + + while (*value) + { + if (*value < '0' || *value > '9') + { + return -RT_EINVAL; + } + + brightness *= 10; + brightness += *value - '0'; + + ++value; + } + + rt_led_set_brightness(led, brightness); + + return size; +} + +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops _led_ops = +{ + .read = _led_read, + .write = _led_write, +}; +#endif + +static void _led_blink_timerout(void *param) +{ + struct rt_led_device *led = param; + struct blink_timer *btimer = led->sysdata; + + if (btimer->toggle) + { + led->ops->set_state(led, RT_LED_S_OFF); + } + else + { + led->ops->set_state(led, RT_LED_S_ON); + } + + btimer->toggle = !btimer->toggle; +} + +rt_err_t rt_hw_led_register(struct rt_led_device *led) +{ + rt_err_t err; + int device_id; + const char *dev_name; + struct blink_timer *btimer = RT_NULL; + + if (!led || !led->ops) + { + return -RT_EINVAL; + } + + if ((device_id = rt_dm_ida_alloc(&led_ida)) < 0) + { + return -RT_EFULL; + } + + rt_dm_dev_set_name(&led->parent, "led%u", device_id); + dev_name = rt_dm_dev_get_name(&led->parent); + + led->sysdata = RT_NULL; + rt_spin_lock_init(&led->spinlock); + + if (!led->ops->set_period && led->ops->set_state) + { + btimer = rt_malloc(sizeof(*btimer)); + + if (!btimer) + { + LOG_E("%s create blink timer failed", dev_name); + + err = -RT_ENOMEM; + goto _fail; + } + + led->sysdata = btimer; + + btimer->toggle = RT_FALSE; + btimer->enabled = RT_FALSE; + rt_timer_init(&btimer->timer, dev_name, _led_blink_timerout, led, + rt_tick_from_millisecond(500), RT_TIMER_FLAG_PERIODIC); + } + + led->parent.type = RT_Device_Class_Char; +#ifdef RT_USING_DEVICE_OPS + led->parent.ops = &_led_ops; +#else + led->parent.read = _led_read; + led->parent.write = _led_write; +#endif + led->parent.master_id = led_ida.master_id; + led->parent.device_id = device_id; + + if ((err = rt_device_register(&led->parent, dev_name, RT_DEVICE_FLAG_RDWR))) + { + goto _fail; + } + + return RT_EOK; + +_fail: + rt_dm_ida_free(&led_ida, device_id); + + if (btimer) + { + rt_timer_detach(&btimer->timer); + rt_free(btimer); + + led->sysdata = RT_NULL; + } + + return err; +} + +rt_err_t rt_hw_led_unregister(struct rt_led_device *led) +{ + if (!led) + { + return -RT_EINVAL; + } + + rt_led_set_state(led, RT_LED_S_OFF); + + if (led->sysdata) + { + struct blink_timer *btimer = led->sysdata; + + rt_timer_detach(&btimer->timer); + + rt_free(btimer); + } + + rt_dm_ida_free(&led_ida, led->parent.device_id); + + rt_device_unregister(&led->parent); + + return RT_EOK; +} + +rt_err_t rt_led_set_state(struct rt_led_device *led, enum rt_led_state state) +{ + rt_err_t err; + struct blink_timer *btimer; + + if (!led) + { + return -RT_EINVAL; + } + + if (!led->ops->set_state) + { + return -RT_ENOSYS; + } + + rt_spin_lock(&led->spinlock); + + btimer = led->sysdata; + + if (btimer && btimer->enabled) + { + rt_timer_stop(&btimer->timer); + } + + err = led->ops->set_state(led, state); + + if (state == RT_LED_S_BLINK) + { + if (err == -RT_ENOSYS && btimer && !btimer->enabled) + { + btimer->enabled = RT_TRUE; + rt_timer_start(&btimer->timer); + } + } + else if (btimer && btimer->enabled) + { + if (err) + { + rt_timer_start(&btimer->timer); + } + else + { + btimer->enabled = RT_FALSE; + } + } + + rt_spin_unlock(&led->spinlock); + + return err; +} + +rt_err_t rt_led_get_state(struct rt_led_device *led, enum rt_led_state *out_state) +{ + rt_err_t err; + + if (!led || !out_state) + { + return -RT_EINVAL; + } + + if (!led->ops->get_state) + { + return -RT_ENOSYS; + } + + rt_spin_lock(&led->spinlock); + + err = led->ops->get_state(led, out_state); + + rt_spin_unlock(&led->spinlock); + + return err; +} + +rt_err_t rt_led_set_period(struct rt_led_device *led, rt_uint32_t period_ms) +{ + rt_err_t err; + + if (!led) + { + return -RT_EINVAL; + } + + if (!led->ops->set_period && !led->sysdata) + { + return -RT_ENOSYS; + } + + rt_spin_lock(&led->spinlock); + + if (led->ops->set_period) + { + err = led->ops->set_period(led, period_ms); + } + else + { + struct blink_timer *btimer = led->sysdata; + rt_tick_t tick = rt_tick_from_millisecond(period_ms); + + err = rt_timer_control(&btimer->timer, RT_TIMER_CTRL_SET_TIME, &tick); + } + + rt_spin_unlock(&led->spinlock); + + return err; +} + +rt_err_t rt_led_set_brightness(struct rt_led_device *led, rt_uint32_t brightness) +{ + rt_err_t err; + + if (!led) + { + return -RT_EINVAL; + } + + if (!led->ops->set_brightness) + { + return -RT_ENOSYS; + } + + rt_spin_lock(&led->spinlock); + + err = led->ops->set_brightness(led, brightness); + + rt_spin_unlock(&led->spinlock); + + return err; +}