1 /*
2  * Copyright (c) 2018 Google LLC.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT apa_apa102
8 
9 #include <errno.h>
10 #include <zephyr/drivers/led_strip.h>
11 #include <zephyr/drivers/spi.h>
12 #include <zephyr/drivers/gpio.h>
13 #include <zephyr/sys/util.h>
14 
15 struct apa102_config {
16 	struct spi_dt_spec bus;
17 	size_t length;
18 	uint8_t *const end_frame;
19 	const size_t end_frame_size;
20 };
21 
apa102_update(const struct device * dev,void * buf,size_t size)22 static int apa102_update(const struct device *dev, void *buf, size_t size)
23 {
24 	const struct apa102_config *config = dev->config;
25 	static const uint8_t zeros[] = { 0, 0, 0, 0 };
26 
27 	const struct spi_buf tx_bufs[] = {
28 		{
29 			/* Start frame: at least 32 zeros */
30 			.buf = (uint8_t *)zeros,
31 			.len = sizeof(zeros),
32 		},
33 		{
34 			/* LED data itself */
35 			.buf = buf,
36 			.len = size,
37 		},
38 		{
39 			/* End frame: at least 32 ones to clock the
40 			 * remaining bits to the LEDs at the end of
41 			 * the strip.
42 			 */
43 			.buf = (uint8_t *)config->end_frame,
44 			.len = config->end_frame_size,
45 		},
46 	};
47 	const struct spi_buf_set tx = {
48 		.buffers = tx_bufs,
49 		.count = ARRAY_SIZE(tx_bufs)
50 	};
51 
52 	return spi_write_dt(&config->bus, &tx);
53 }
54 
apa102_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t count)55 static int apa102_update_rgb(const struct device *dev, struct led_rgb *pixels,
56 			     size_t count)
57 {
58 	uint8_t *p = (uint8_t *)pixels;
59 	size_t i;
60 	/* SOF (3 bits) followed by the 0 to 31 global dimming level */
61 	uint8_t prefix = 0xE0 | 31;
62 
63 	/* Rewrite to the on-wire format */
64 	for (i = 0; i < count; i++) {
65 		uint8_t r = pixels[i].r;
66 		uint8_t g = pixels[i].g;
67 		uint8_t b = pixels[i].b;
68 
69 		*p++ = prefix;
70 		*p++ = b;
71 		*p++ = g;
72 		*p++ = r;
73 	}
74 
75 	BUILD_ASSERT(sizeof(struct led_rgb) == 4);
76 	return apa102_update(dev, pixels, sizeof(struct led_rgb) * count);
77 }
78 
apa102_length(const struct device * dev)79 static size_t apa102_length(const struct device *dev)
80 {
81 	const struct apa102_config *config = dev->config;
82 
83 	return config->length;
84 }
85 
apa102_init(const struct device * dev)86 static int apa102_init(const struct device *dev)
87 {
88 	const struct apa102_config *config = dev->config;
89 
90 	if (!spi_is_ready_dt(&config->bus)) {
91 		return -ENODEV;
92 	}
93 
94 	memset(config->end_frame, 0xFF, config->end_frame_size);
95 
96 	return 0;
97 }
98 
99 static DEVICE_API(led_strip, apa102_api) = {
100 	.update_rgb = apa102_update_rgb,
101 	.length = apa102_length,
102 };
103 
104 /*
105  * The "End frame" is statically allocated, as a sequence of 0xFF bytes
106  * The only function of the “End frame” is to supply more clock pulses
107  * to the string until the data has permeated to the last LED. The
108  * number of clock pulses required is exactly half the total number
109  * of LEDs in the string. See below `end_frame`.
110  */
111 #define APA102_DEVICE(idx)						 \
112 	static uint8_t apa102_end_frame_##idx				 \
113 		[(DT_INST_PROP(idx, chain_length) /			 \
114 			sizeof(struct led_rgb) / 2) + 1];		 \
115 	static const struct apa102_config apa102_##idx##_config = {	 \
116 		.bus = SPI_DT_SPEC_INST_GET(				 \
117 			idx,						 \
118 			SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), \
119 			0),						 \
120 		.length = DT_INST_PROP(idx, chain_length),		 \
121 		.end_frame = apa102_end_frame_##idx,			 \
122 		.end_frame_size = (DT_INST_PROP(idx, chain_length) /	 \
123 				sizeof(struct led_rgb) / 2) + 1,	 \
124 	};								 \
125 									 \
126 	DEVICE_DT_INST_DEFINE(idx,					 \
127 			      apa102_init,				 \
128 			      NULL,					 \
129 			      NULL,					 \
130 			      &apa102_##idx##_config,			 \
131 			      POST_KERNEL,				 \
132 			      CONFIG_LED_STRIP_INIT_PRIORITY,		 \
133 			      &apa102_api);
134 
135 DT_INST_FOREACH_STATUS_OKAY(APA102_DEVICE)
136