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