Skip to content

Commit cc43ae2

Browse files
author
Jeppe Odgaard
committed
drivers: led: add led_dac
Add LED driver support for DAC based LED drivers. Signed-off-by: Jeppe Odgaard <[email protected]>
1 parent d8cd8dd commit cc43ae2

File tree

6 files changed

+263
-0
lines changed

6 files changed

+263
-0
lines changed

drivers/led/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ zephyr_library()
66

77
zephyr_library_sources_ifdef(CONFIG_HT16K33 ht16k33.c)
88
zephyr_library_sources_ifdef(CONFIG_IS31FL3216A is31fl3216a.c)
9+
zephyr_library_sources_ifdef(CONFIG_LED_DAC led_dac.c)
910
zephyr_library_sources_ifdef(CONFIG_LED_GPIO led_gpio.c)
1011
zephyr_library_sources_ifdef(CONFIG_LED_NPM1300 led_npm1300.c)
1112
zephyr_library_sources_ifdef(CONFIG_LED_PWM led_pwm.c)

drivers/led/Kconfig

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ config LED_SHELL
2626
help
2727
Enable LED shell for testing.
2828

29+
source "drivers/led/Kconfig.dac"
2930
source "drivers/led/Kconfig.gpio"
3031
source "drivers/led/Kconfig.ht16k33"
3132
source "drivers/led/Kconfig.is31fl3216a"

drivers/led/Kconfig.dac

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) 2025 Prevas A/S
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config LED_DAC
5+
bool "DAC LED driver"
6+
default y
7+
depends on DT_HAS_DAC_LEDS_ENABLED
8+
select DAC
9+
help
10+
Enable DAC LED driver.
11+
12+
The driver is normally used with a VCCS (Voltage Controlled Current
13+
Source) between the DAC and the LED.
14+
15+
It might be possible to use this driver to supply an LED directly
16+
from a DAC, but LED_PWM is most likely more suited for this purpose.
17+
If the DAC is used without a VCCS, its capabilities should be checked
18+
carefully, and the output buffer should be enabled.

drivers/led/led_dac.c

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2025 Prevas A/S
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#define DT_DRV_COMPAT dac_leds
7+
8+
#include <errno.h>
9+
#include <stdint.h>
10+
#include <zephyr/device.h>
11+
#include <zephyr/drivers/dac.h>
12+
#include <zephyr/drivers/led.h>
13+
#include <zephyr/sys/math_extras.h>
14+
15+
struct led_dac_leds {
16+
const struct device *dac;
17+
struct dac_channel_cfg chan_cfg;
18+
uint32_t dac_max;
19+
uint32_t dac_min;
20+
};
21+
22+
struct led_dac_config {
23+
const struct led_dac_leds *leds;
24+
uint8_t num_leds;
25+
};
26+
27+
static int led_dac_set_raw(const struct device *dev, uint32_t led, uint32_t value)
28+
{
29+
const struct led_dac_config *config = dev->config;
30+
31+
return dac_write_value(config->leds[led].dac, config->leds[led].chan_cfg.channel_id, value);
32+
}
33+
34+
static int led_dac_set_brightness(const struct device *dev, uint32_t led, uint8_t pct)
35+
{
36+
const struct led_dac_config *config = dev->config;
37+
uint32_t value;
38+
39+
if (led >= config->num_leds) {
40+
return -EINVAL;
41+
}
42+
43+
value = pct > 0 ? config->leds[led].dac_min + (uint64_t)(config->leds[led].dac_max -
44+
config->leds[led].dac_min) *
45+
pct / 100
46+
: 0;
47+
48+
return led_dac_set_raw(dev, led, value);
49+
}
50+
51+
static inline int led_dac_on(const struct device *dev, uint32_t led)
52+
{
53+
const struct led_dac_config *config = dev->config;
54+
55+
if (led >= config->num_leds) {
56+
return -EINVAL;
57+
}
58+
59+
return led_dac_set_raw(dev, led, config->leds[led].dac_max);
60+
}
61+
62+
static inline int led_dac_off(const struct device *dev, uint32_t led)
63+
{
64+
const struct led_dac_config *config = dev->config;
65+
66+
if (led >= config->num_leds) {
67+
return -EINVAL;
68+
}
69+
70+
return led_dac_set_raw(dev, led, 0);
71+
}
72+
73+
static DEVICE_API(led, led_dac_api) = {
74+
.on = led_dac_on,
75+
.off = led_dac_off,
76+
.set_brightness = led_dac_set_brightness,
77+
};
78+
79+
static int led_dac_init(const struct device *dev)
80+
{
81+
const struct led_dac_config *config = dev->config;
82+
int ret;
83+
84+
for (uint8_t i = 0; i < config->num_leds; ++i) {
85+
const struct led_dac_leds *led = &config->leds[i];
86+
87+
if (!device_is_ready(led->dac)) {
88+
return -ENODEV;
89+
}
90+
91+
ret = dac_channel_setup(led->dac, &led->chan_cfg);
92+
if (ret != 0) {
93+
return ret;
94+
}
95+
}
96+
97+
return 0;
98+
}
99+
100+
#define LED_DAC_MAX_MV(n) DT_PROP(n, voltage_max_dac_mv)
101+
#define LED_DAC_MAX_VAL(n) (BIT(DT_PROP(n, resolution)) - 1)
102+
103+
#define LED_DAC_MAX_BRIGHTNESS(n) \
104+
COND_CODE_1(DT_NODE_HAS_PROP(n, voltage_max_brightness_mv), \
105+
(DT_PROP(n, voltage_max_brightness_mv) * LED_DAC_MAX_VAL(n) / LED_DAC_MAX_MV(n)), \
106+
(LED_DAC_MAX_VAL(n)))
107+
108+
#define LED_DAC_MIN_BRIGHTNESS(n) \
109+
COND_CODE_1(DT_NODE_HAS_PROP(n, voltage_min_brightness_mv), \
110+
(DT_PROP(n, voltage_min_brightness_mv) * LED_DAC_MAX_VAL(n) / LED_DAC_MAX_MV(n)), \
111+
(0))
112+
113+
#define LED_DAC_DT_GET(n) \
114+
{ \
115+
.dac = DEVICE_DT_GET(DT_PHANDLE(n, dac_dev)), \
116+
.chan_cfg = {.channel_id = DT_PROP(n, channel), \
117+
.resolution = DT_PROP(n, resolution), \
118+
.buffered = DT_PROP(n, output_buffer), \
119+
.internal = false}, \
120+
.dac_max = LED_DAC_MAX_BRIGHTNESS(n), .dac_min = LED_DAC_MIN_BRIGHTNESS(n) \
121+
}
122+
123+
#define LED_DAC_DEFINE(n) \
124+
BUILD_ASSERT((DT_NODE_HAS_PROP(n, voltage_max_brightness_mv) || \
125+
DT_NODE_HAS_PROP(n, voltage_min_brightness_mv)) == \
126+
DT_NODE_HAS_PROP(n, voltage_max_dac_mv), \
127+
"'voltage-max-dac-mv' must be set when 'voltage-max-brightness-mv' or " \
128+
"'voltage-max-brightness-mv' is set"); \
129+
\
130+
static const struct led_dac_leds led_dac_##n[] = { \
131+
DT_INST_FOREACH_CHILD_SEP(n, LED_DAC_DT_GET, (,))}; \
132+
\
133+
static const struct led_dac_config led_config_##n = { \
134+
.leds = led_dac_##n, \
135+
.num_leds = ARRAY_SIZE(led_dac_##n), \
136+
}; \
137+
\
138+
DEVICE_DT_INST_DEFINE(n, &led_dac_init, NULL, NULL, &led_config_##n, POST_KERNEL, \
139+
CONFIG_LED_INIT_PRIORITY, &led_dac_api);
140+
141+
DT_INST_FOREACH_STATUS_OKAY(LED_DAC_DEFINE)

dts/bindings/led/dac-leds.yaml

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright (c) 2018, Linaro Limited
2+
# Copyright (c) 2025, Prevas A/S
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
title: Group of DAC-controlled LEDs.
6+
7+
description: |
8+
Each LED is defined in a child node of the dac-leds node.
9+
10+
Here is an example which defines an LED in the node /leds:
11+
12+
/ {
13+
leds {
14+
compatible = "dac-leds";
15+
led_0 {
16+
dac-dev = <&dac1>;
17+
channel = <0>;
18+
resolution = <12>;
19+
};
20+
led_1 {
21+
dac-dev = <&dac1>;
22+
channel = <1>;
23+
resolution = <12>;
24+
voltage-min-brightness-mv = <1400>;
25+
voltage-max-brightness-mv = <2700>;
26+
voltage-max-dac-mv = <3300>;
27+
output-buffer;
28+
};
29+
};
30+
};
31+
32+
Above:
33+
34+
- led_0 uses dac1 channel 0 with 12 bit resolution.
35+
- led_1 uses dac1 channel 1 with 12 bit resolution.
36+
37+
compatible: "dac-leds"
38+
39+
child-binding:
40+
description: DAC LED child node
41+
properties:
42+
dac-dev:
43+
type: phandle
44+
required: true
45+
description: |
46+
Property containing phandle to DAC e.g. &dac.
47+
48+
channel:
49+
type: int
50+
required: true
51+
description: |
52+
The DAC channel.
53+
54+
resolution:
55+
type: int
56+
required: true
57+
description: |
58+
The DAC resolution to use.
59+
60+
voltage-min-brightness-mv:
61+
type: int
62+
description: |
63+
Voltage at brightness 0%.
64+
If not specified the minimum DAC output voltage is used.
65+
66+
voltage-max-brightness-mv:
67+
type: int
68+
description: |
69+
Voltage at brightness 100%.
70+
If not specified the maximum DAC output voltage is used.
71+
72+
voltage-max-dac-mv:
73+
type: int
74+
description: |
75+
The DAC maximum output voltage.
76+
Required if voltage-min-brightness-mv or voltage-max-brightness-mv is set.
77+
78+
output-buffer:
79+
type: boolean
80+
description: |
81+
Enable the output buffer of the DAC.
82+
This is required if it is used to drive an LED directly.

tests/drivers/build_all/led/app.overlay

+20
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,26 @@
1515
#address-cells = <1>;
1616
#size-cells = <1>;
1717

18+
test_dac: dac@dac0dac0 {
19+
compatible = "vnd,dac";
20+
reg = <0xdac0dac0 0x1000>;
21+
status = "okay";
22+
#io-channel-cells = <1>;
23+
};
24+
25+
test_dac_leds {
26+
compatible = "dac-leds";
27+
test_dac_led0: test_dac_led_0 {
28+
dac-dev = <&test_dac>;
29+
channel = <0>;
30+
resolution = <16>;
31+
voltage-min-brightness-mv = <123>;
32+
voltage-max-brightness-mv = <1234>;
33+
voltage-max-dac-mv = <3456>;
34+
output-buffer;
35+
};
36+
};
37+
1838
test_gpio: gpio@deadbeef {
1939
compatible = "vnd,gpio";
2040
gpio-controller;

0 commit comments

Comments
 (0)