drivers: led: add led_dac

Add LED driver support for DAC based LED drivers.

Signed-off-by: Jeppe Odgaard <jeppe.odgaard@prevas.dk>
This commit is contained in:
Jeppe Odgaard 2025-04-07 15:18:47 +02:00 committed by Benjamin Cabé
parent 4bd1de02bb
commit 634ba6955c
6 changed files with 268 additions and 0 deletions

View File

@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_IS31FL3194 is31fl3194.c)
zephyr_library_sources_ifdef(CONFIG_IS31FL3216A is31fl3216a.c)
zephyr_library_sources_ifdef(CONFIG_IS31FL3733 is31fl3733.c)
zephyr_library_sources_ifdef(CONFIG_LED_AXP192_AXP2101 led_axp192.c)
zephyr_library_sources_ifdef(CONFIG_LED_DAC led_dac.c)
zephyr_library_sources_ifdef(CONFIG_LED_GPIO led_gpio.c)
zephyr_library_sources_ifdef(CONFIG_LED_NPM1300 led_npm1300.c)
zephyr_library_sources_ifdef(CONFIG_LED_PWM led_pwm.c)

View File

@ -28,6 +28,7 @@ config LED_SHELL
# zephyr-keep-sorted-start
source "drivers/led/Kconfig.axp192"
source "drivers/led/Kconfig.dac"
source "drivers/led/Kconfig.gpio"
source "drivers/led/Kconfig.ht16k33"
source "drivers/led/Kconfig.is31fl3194"

18
drivers/led/Kconfig.dac Normal file
View File

@ -0,0 +1,18 @@
# Copyright (c) 2025 Prevas A/S
# SPDX-License-Identifier: Apache-2.0
config LED_DAC
bool "DAC LED driver"
default y
depends on DT_HAS_DAC_LEDS_ENABLED
select DAC
help
Enable DAC LED driver.
The driver is normally used with a VCCS (Voltage Controlled Current
Source) between the DAC and the LED.
It might be possible to use this driver to supply an LED directly
from a DAC, but LED_PWM is most likely more suited for this purpose.
If the DAC is used without a VCCS, its capabilities should be checked
carefully, and the output buffer should be enabled.

146
drivers/led/led_dac.c Normal file
View File

@ -0,0 +1,146 @@
/*
* Copyright (c) 2025 Prevas A/S
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT dac_leds
#include <errno.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/drivers/dac.h>
#include <zephyr/drivers/led.h>
#include <zephyr/sys/math_extras.h>
struct led_dac_leds {
const struct device *dac;
struct dac_channel_cfg chan_cfg;
uint32_t dac_max_brightness;
uint32_t dac_min_brightness;
};
struct led_dac_config {
const struct led_dac_leds *leds;
uint8_t num_leds;
};
static int led_dac_set_raw(const struct device *dev, uint32_t led, uint32_t value)
{
const struct led_dac_config *config = dev->config;
return dac_write_value(config->leds[led].dac, config->leds[led].chan_cfg.channel_id, value);
}
static int led_dac_set_brightness(const struct device *dev, uint32_t led, uint8_t pct)
{
const struct led_dac_config *config = dev->config;
uint32_t value;
if (led >= config->num_leds) {
return -EINVAL;
}
value = pct > 0 ? config->leds[led].dac_min_brightness +
(uint64_t)(config->leds[led].dac_max_brightness -
config->leds[led].dac_min_brightness) *
pct / 100
: 0;
return led_dac_set_raw(dev, led, value);
}
static inline int led_dac_on(const struct device *dev, uint32_t led)
{
const struct led_dac_config *config = dev->config;
if (led >= config->num_leds) {
return -EINVAL;
}
return led_dac_set_raw(dev, led, config->leds[led].dac_max_brightness);
}
static inline int led_dac_off(const struct device *dev, uint32_t led)
{
const struct led_dac_config *config = dev->config;
if (led >= config->num_leds) {
return -EINVAL;
}
return led_dac_set_raw(dev, led, 0);
}
static DEVICE_API(led, led_dac_api) = {
.on = led_dac_on,
.off = led_dac_off,
.set_brightness = led_dac_set_brightness,
};
static int led_dac_init(const struct device *dev)
{
const struct led_dac_config *config = dev->config;
int ret;
for (uint8_t i = 0; i < config->num_leds; ++i) {
const struct led_dac_leds *led = &config->leds[i];
if (!device_is_ready(led->dac)) {
return -ENODEV;
}
ret = dac_channel_setup(led->dac, &led->chan_cfg);
if (ret != 0) {
return ret;
}
}
return 0;
}
#define LED_DAC_MAX_MV(n) DT_PROP(n, voltage_max_dac_mv)
#define LED_DAC_MAX_VAL(n) (BIT(DT_PROP(n, resolution)) - 1)
#define LED_DAC_MAX_BRIGHTNESS(n) \
COND_CODE_1(DT_NODE_HAS_PROP(n, voltage_max_brightness_mv), \
(DT_PROP(n, voltage_max_brightness_mv) * LED_DAC_MAX_VAL(n) / LED_DAC_MAX_MV(n)), \
(LED_DAC_MAX_VAL(n)))
#define LED_DAC_MIN_BRIGHTNESS(n) \
COND_CODE_1(DT_NODE_HAS_PROP(n, voltage_min_brightness_mv), \
(DT_PROP(n, voltage_min_brightness_mv) * LED_DAC_MAX_VAL(n) / LED_DAC_MAX_MV(n)), \
(0))
#define LED_DAC_DT_GET(n) \
{ \
.dac = DEVICE_DT_GET(DT_PHANDLE(n, dac_dev)), \
.chan_cfg = \
{ \
.channel_id = DT_PROP(n, channel), \
.resolution = DT_PROP(n, resolution), \
.buffered = DT_PROP(n, output_buffer), \
.internal = false, \
}, \
.dac_max_brightness = LED_DAC_MAX_BRIGHTNESS(n), \
.dac_min_brightness = LED_DAC_MIN_BRIGHTNESS(n), \
}
#define LED_DAC_DEFINE(n) \
BUILD_ASSERT((DT_NODE_HAS_PROP(n, voltage_max_brightness_mv) || \
DT_NODE_HAS_PROP(n, voltage_min_brightness_mv)) == \
DT_NODE_HAS_PROP(n, voltage_max_dac_mv), \
"'voltage-max-dac-mv' must be set when 'voltage-max-brightness-mv' or " \
"'voltage-max-brightness-mv' is set"); \
\
static const struct led_dac_leds led_dac_##n[] = { \
DT_INST_FOREACH_CHILD_SEP(n, LED_DAC_DT_GET, (,))}; \
\
static const struct led_dac_config led_config_##n = { \
.leds = led_dac_##n, \
.num_leds = ARRAY_SIZE(led_dac_##n), \
}; \
\
DEVICE_DT_INST_DEFINE(n, &led_dac_init, NULL, NULL, &led_config_##n, POST_KERNEL, \
CONFIG_LED_INIT_PRIORITY, &led_dac_api);
DT_INST_FOREACH_STATUS_OKAY(LED_DAC_DEFINE)

View File

@ -0,0 +1,82 @@
# Copyright (c) 2018, Linaro Limited
# Copyright (c) 2025, Prevas A/S
# SPDX-License-Identifier: Apache-2.0
title: Group of DAC-controlled LEDs.
description: |
Each LED is defined in a child node of the dac-leds node.
Here is an example which defines an LED in the node /leds:
/ {
leds {
compatible = "dac-leds";
led_0 {
dac-dev = <&dac1>;
channel = <0>;
resolution = <12>;
};
led_1 {
dac-dev = <&dac1>;
channel = <1>;
resolution = <12>;
voltage-min-brightness-mv = <1400>;
voltage-max-brightness-mv = <2700>;
voltage-max-dac-mv = <3300>;
output-buffer;
};
};
};
Above:
- led_0 uses dac1 channel 0 with 12 bit resolution.
- led_1 uses dac1 channel 1 with 12 bit resolution and setup to supply an LED directly.
compatible: "dac-leds"
child-binding:
description: DAC LED child node
properties:
dac-dev:
type: phandle
required: true
description: |
Property containing phandle to DAC e.g. &dac.
channel:
type: int
required: true
description: |
The DAC channel.
resolution:
type: int
required: true
description: |
The DAC resolution to use.
voltage-min-brightness-mv:
type: int
description: |
Voltage at brightness 0%.
If not specified the minimum DAC output voltage is used.
voltage-max-brightness-mv:
type: int
description: |
Voltage at brightness 100%.
If not specified the maximum DAC output voltage is used.
voltage-max-dac-mv:
type: int
description: |
The DAC maximum output voltage.
Required if voltage-min-brightness-mv or voltage-max-brightness-mv is set.
output-buffer:
type: boolean
description: |
Enable the output buffer of the DAC.
This is required if it is used to drive an LED directly.

View File

@ -15,6 +15,26 @@
#address-cells = <1>;
#size-cells = <1>;
test_dac: dac@dac0dac0 {
compatible = "vnd,dac";
reg = <0xdac0dac0 0x1000>;
status = "okay";
#io-channel-cells = <1>;
};
test_dac_leds {
compatible = "dac-leds";
test_dac_led0: test_dac_led_0 {
dac-dev = <&test_dac>;
channel = <0>;
resolution = <16>;
voltage-min-brightness-mv = <123>;
voltage-max-brightness-mv = <1234>;
voltage-max-dac-mv = <3456>;
output-buffer;
};
};
test_gpio: gpio@deadbeef {
compatible = "vnd,gpio";
gpio-controller;