1 /*
2  * Copyright (c) 2017 Linaro Limited
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/led_strip.h>
8 
9 #include <errno.h>
10 #include <string.h>
11 
12 #if DT_NODE_HAS_STATUS(DT_INST(0, greeled_lpd8806), okay)
13 #define DT_DRV_COMPAT greeled_lpd8806
14 #else
15 #define DT_DRV_COMPAT greeled_lpd8803
16 #endif
17 
18 #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
19 #include <zephyr/logging/log.h>
20 LOG_MODULE_REGISTER(lpd880x);
21 
22 #include <zephyr/kernel.h>
23 #include <zephyr/device.h>
24 #include <zephyr/drivers/spi.h>
25 #include <zephyr/sys/util.h>
26 
27 /*
28  * LPD880X SPI master configuration:
29  *
30  * - mode 0 (the default), 8 bit, MSB first, one-line SPI
31  * - no shenanigans (no CS hold, release device lock, not an EEPROM)
32  */
33 #define LPD880X_SPI_OPERATION (SPI_OP_MODE_MASTER | \
34 			       SPI_TRANSFER_MSB |   \
35 			       SPI_WORD_SET(8))
36 
37 struct lpd880x_config {
38 	struct spi_dt_spec bus;
39 	size_t length;
40 };
41 
lpd880x_update(const struct device * dev,void * data,size_t size)42 static int lpd880x_update(const struct device *dev, void *data, size_t size)
43 {
44 	const struct lpd880x_config *config = dev->config;
45 	/*
46 	 * Per the AdaFruit reverse engineering notes on the protocol,
47 	 * a zero byte propagates through at most 32 LED driver ICs.
48 	 * The LPD8803 is the worst case, at 3 output channels per IC.
49 	 */
50 	uint8_t reset_size = DIV_ROUND_UP(DIV_ROUND_UP(size, 3), 32);
51 	uint8_t reset_buf[reset_size];
52 	uint8_t last = 0x00;
53 	const struct spi_buf bufs[3] = {
54 		{
55 			/* Prepares the strip to shift in new data values. */
56 			.buf = reset_buf,
57 			.len = reset_size
58 		},
59 		{
60 			/* Displays the serialized pixel data. */
61 			.buf = data,
62 			.len = size
63 		},
64 		{
65 			/* Ensures the last byte of pixel data is displayed. */
66 			.buf = &last,
67 			.len = sizeof(last)
68 		}
69 
70 	};
71 	const struct spi_buf_set tx = {
72 		.buffers = bufs,
73 		.count = 3
74 	};
75 	size_t rc;
76 
77 	(void)memset(reset_buf, 0x00, reset_size);
78 
79 	rc = spi_write_dt(&config->bus, &tx);
80 	if (rc) {
81 		LOG_ERR("can't update strip: %d", rc);
82 	}
83 
84 	return rc;
85 }
86 
lpd880x_strip_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)87 static int lpd880x_strip_update_rgb(const struct device *dev,
88 				    struct led_rgb *pixels,
89 				    size_t num_pixels)
90 {
91 	uint8_t *px = (uint8_t *)pixels;
92 	uint8_t r, g, b;
93 	size_t i;
94 
95 	/*
96 	 * Overwrite a prefix of the pixels array with its on-wire
97 	 * representation, eliminating padding/scratch garbage, if any.
98 	 */
99 	for (i = 0; i < num_pixels; i++) {
100 		r = 0x80 | (pixels[i].r >> 1);
101 		g = 0x80 | (pixels[i].g >> 1);
102 		b = 0x80 | (pixels[i].b >> 1);
103 
104 		/*
105 		 * GRB is the ordering used by commonly available
106 		 * LPD880x strips.
107 		 */
108 		*px++ = g;
109 		*px++ = r;
110 		*px++ = b;
111 	}
112 
113 	return lpd880x_update(dev, pixels, 3 * num_pixels);
114 }
115 
lpd880x_strip_update_channels(const struct device * dev,uint8_t * channels,size_t num_channels)116 static int lpd880x_strip_update_channels(const struct device *dev,
117 					 uint8_t *channels,
118 					 size_t num_channels)
119 {
120 	size_t i;
121 
122 	for (i = 0; i < num_channels; i++) {
123 		channels[i] = 0x80 | (channels[i] >> 1);
124 	}
125 
126 	return lpd880x_update(dev, channels, num_channels);
127 }
128 
lpd880x_strip_length(const struct device * dev)129 static size_t lpd880x_strip_length(const struct device *dev)
130 {
131 	const struct lpd880x_config *config = dev->config;
132 
133 	return config->length;
134 }
135 
lpd880x_strip_init(const struct device * dev)136 static int lpd880x_strip_init(const struct device *dev)
137 {
138 	const struct lpd880x_config *config = dev->config;
139 
140 	if (!spi_is_ready_dt(&config->bus)) {
141 		LOG_ERR("SPI device %s not ready", config->bus.bus->name);
142 		return -ENODEV;
143 	}
144 
145 	return 0;
146 }
147 
148 static const struct lpd880x_config lpd880x_config = {
149 	.bus = SPI_DT_SPEC_INST_GET(0, LPD880X_SPI_OPERATION, 0),
150 	.length = DT_INST_PROP(0, chain_length),
151 };
152 
153 static const struct led_strip_driver_api lpd880x_strip_api = {
154 	.update_rgb = lpd880x_strip_update_rgb,
155 	.update_channels = lpd880x_strip_update_channels,
156 	.length = lpd880x_strip_length,
157 };
158 
159 DEVICE_DT_INST_DEFINE(0, lpd880x_strip_init, NULL,
160 		      NULL, &lpd880x_config,
161 		      POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY,
162 		      &lpd880x_strip_api);
163