pixel_pi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/README.md +85 -0
- data/Rakefile +16 -0
- data/examples/strandtest.rb +186 -0
- data/ext/pixel_pi/extconf.rb +18 -0
- data/ext/pixel_pi/leds.c +464 -0
- data/ext/ws2811/LICENSE +24 -0
- data/ext/ws2811/clk.h +60 -0
- data/ext/ws2811/dma.c +192 -0
- data/ext/ws2811/dma.h +146 -0
- data/ext/ws2811/gpio.h +108 -0
- data/ext/ws2811/pwm.c +112 -0
- data/ext/ws2811/pwm.h +122 -0
- data/ext/ws2811/ws2811.c +757 -0
- data/ext/ws2811/ws2811.h +68 -0
- data/lib/pixel_pi/version.rb +3 -0
- data/lib/pixel_pi.rb +2 -0
- data/pixel_pi.gemspec +23 -0
- metadata +77 -0
data/ext/ws2811/ws2811.c
ADDED
@@ -0,0 +1,757 @@
|
|
1
|
+
/*
|
2
|
+
* ws2811.c
|
3
|
+
*
|
4
|
+
* Copyright (c) 2014 Jeremy Garff <jer @ jers.net>
|
5
|
+
*
|
6
|
+
* All rights reserved.
|
7
|
+
*
|
8
|
+
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
9
|
+
* provided that the following conditions are met:
|
10
|
+
*
|
11
|
+
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
12
|
+
* conditions and the following disclaimer.
|
13
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
14
|
+
* of conditions and the following disclaimer in the documentation and/or other materials
|
15
|
+
* provided with the distribution.
|
16
|
+
* 3. Neither the name of the owner nor the names of its contributors may be used to endorse
|
17
|
+
* or promote products derived from this software without specific prior written permission.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
20
|
+
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
21
|
+
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
22
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
24
|
+
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
26
|
+
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
*
|
28
|
+
*/
|
29
|
+
|
30
|
+
|
31
|
+
#include <stdint.h>
|
32
|
+
#include <stdio.h>
|
33
|
+
#include <stdlib.h>
|
34
|
+
#include <string.h>
|
35
|
+
#include <unistd.h>
|
36
|
+
#include <sys/types.h>
|
37
|
+
#include <sys/stat.h>
|
38
|
+
#include <fcntl.h>
|
39
|
+
#include <sys/mman.h>
|
40
|
+
#include <signal.h>
|
41
|
+
|
42
|
+
#include "clk.h"
|
43
|
+
#include "gpio.h"
|
44
|
+
#include "dma.h"
|
45
|
+
#include "pwm.h"
|
46
|
+
|
47
|
+
#include "ws2811.h"
|
48
|
+
|
49
|
+
|
50
|
+
#define OSC_FREQ 19200000 // crystal frequency
|
51
|
+
|
52
|
+
/* 3 colors, 8 bits per byte, 3 symbols per bit + 55uS low for reset signal */
|
53
|
+
#define LED_RESET_uS 55
|
54
|
+
#define LED_BIT_COUNT(leds, freq) ((leds * 3 * 8 * 3) + ((LED_RESET_uS * \
|
55
|
+
(freq * 3)) / 1000000))
|
56
|
+
|
57
|
+
// Pad out to the nearest uint32 + 32-bits for idle low/high times the number of channels
|
58
|
+
#define PWM_BYTE_COUNT(leds, freq) (((((LED_BIT_COUNT(leds, freq) >> 3) & ~0x7) + 4) + 4) * \
|
59
|
+
RPI_PWM_CHANNELS)
|
60
|
+
|
61
|
+
#define SYMBOL_HIGH 0x6 // 1 1 0
|
62
|
+
#define SYMBOL_LOW 0x4 // 1 0 0
|
63
|
+
|
64
|
+
#define ARRAY_SIZE(stuff) (sizeof(stuff) / sizeof(stuff[0]))
|
65
|
+
|
66
|
+
|
67
|
+
typedef struct ws2811_device
|
68
|
+
{
|
69
|
+
volatile uint8_t *pwm_raw;
|
70
|
+
volatile dma_t *dma;
|
71
|
+
volatile pwm_t *pwm;
|
72
|
+
volatile dma_cb_t *dma_cb;
|
73
|
+
uint32_t dma_cb_addr;
|
74
|
+
dma_page_t page_head;
|
75
|
+
volatile gpio_t *gpio;
|
76
|
+
volatile cm_pwm_t *cm_pwm;
|
77
|
+
int max_count;
|
78
|
+
} ws2811_device_t;
|
79
|
+
|
80
|
+
|
81
|
+
// ARM gcc built-in function, fortunately works when root w/virtual addrs
|
82
|
+
void __clear_cache(char *begin, char *end);
|
83
|
+
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Iterate through the channels and find the largest led count.
|
87
|
+
*
|
88
|
+
* @param ws2811 ws2811 instance pointer.
|
89
|
+
*
|
90
|
+
* @returns Maximum number of LEDs in all channels.
|
91
|
+
*/
|
92
|
+
static int max_channel_led_count(ws2811_t *ws2811)
|
93
|
+
{
|
94
|
+
int chan, max = 0;
|
95
|
+
|
96
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
97
|
+
{
|
98
|
+
if (ws2811->channel[chan].count > max)
|
99
|
+
{
|
100
|
+
max = ws2811->channel[chan].count;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
return max;
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Map a physical address and length into userspace virtual memory.
|
109
|
+
*
|
110
|
+
* @param phys Physical 32-bit address of device registers.
|
111
|
+
* @param len Length of mapped region.
|
112
|
+
*
|
113
|
+
* @returns Virtual address pointer to physical memory region, NULL on error.
|
114
|
+
*/
|
115
|
+
static void *map_device(const uint32_t phys, const uint32_t len)
|
116
|
+
{
|
117
|
+
uint32_t start_page_addr = phys & PAGE_MASK;
|
118
|
+
uint32_t end_page_addr = (phys + len) & PAGE_MASK;
|
119
|
+
uint32_t pages = end_page_addr - start_page_addr + 1;
|
120
|
+
int fd = open("/dev/mem", O_RDWR | O_SYNC);
|
121
|
+
void *virt;
|
122
|
+
|
123
|
+
if (fd < 0)
|
124
|
+
{
|
125
|
+
perror("Can't open /dev/mem");
|
126
|
+
close(fd);
|
127
|
+
return NULL;
|
128
|
+
}
|
129
|
+
|
130
|
+
virt = mmap(NULL, PAGE_SIZE * pages, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
|
131
|
+
start_page_addr);
|
132
|
+
if (virt == MAP_FAILED)
|
133
|
+
{
|
134
|
+
perror("map_device() mmap() failed");
|
135
|
+
close(fd);
|
136
|
+
return NULL;
|
137
|
+
}
|
138
|
+
|
139
|
+
close(fd);
|
140
|
+
|
141
|
+
return (void *)(((uint8_t *)virt) + PAGE_OFFSET(phys));
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Unmap a physical address and length from virtual memory.
|
146
|
+
*
|
147
|
+
* @param addr Virtual address pointer of device registers.
|
148
|
+
* @param len Length of mapped region.
|
149
|
+
*
|
150
|
+
* @returns None
|
151
|
+
*/
|
152
|
+
static void unmap_device(volatile void *addr, const uint32_t len)
|
153
|
+
{
|
154
|
+
uint32_t virt = (uint32_t)addr;
|
155
|
+
uint32_t start_page_addr = virt & PAGE_MASK;
|
156
|
+
uint32_t end_page_addr = (virt + len) & PAGE_MASK;
|
157
|
+
uint32_t pages = end_page_addr - start_page_addr + 1;
|
158
|
+
|
159
|
+
munmap((void *)addr, PAGE_SIZE * pages);
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Map all devices into userspace memory.
|
164
|
+
*
|
165
|
+
* @param ws2811 ws2811 instance pointer.
|
166
|
+
*
|
167
|
+
* @returns 0 on success, -1 otherwise.
|
168
|
+
*/
|
169
|
+
static int map_registers(ws2811_t *ws2811)
|
170
|
+
{
|
171
|
+
ws2811_device_t *device = ws2811->device;
|
172
|
+
uint32_t dma_addr = dmanum_to_phys(ws2811->dmanum);
|
173
|
+
|
174
|
+
if (!dma_addr)
|
175
|
+
{
|
176
|
+
return -1;
|
177
|
+
}
|
178
|
+
|
179
|
+
device->dma = map_device(dma_addr, sizeof(dma_t));
|
180
|
+
if (!device->dma)
|
181
|
+
{
|
182
|
+
return -1;
|
183
|
+
}
|
184
|
+
|
185
|
+
device->pwm = map_device(PWM, sizeof(pwm_t));
|
186
|
+
if (!device->pwm)
|
187
|
+
{
|
188
|
+
return -1;
|
189
|
+
}
|
190
|
+
|
191
|
+
device->gpio = map_device(GPIO, sizeof(gpio_t));
|
192
|
+
if (!device->gpio)
|
193
|
+
{
|
194
|
+
return -1;
|
195
|
+
}
|
196
|
+
|
197
|
+
device->cm_pwm = map_device(CM_PWM, sizeof(cm_pwm_t));
|
198
|
+
if (!device->cm_pwm)
|
199
|
+
{
|
200
|
+
return -1;
|
201
|
+
}
|
202
|
+
|
203
|
+
return 0;
|
204
|
+
}
|
205
|
+
|
206
|
+
/**
|
207
|
+
* Unmap all devices from virtual memory.
|
208
|
+
*
|
209
|
+
* @param ws2811 ws2811 instance pointer.
|
210
|
+
*
|
211
|
+
* @returns None
|
212
|
+
*/
|
213
|
+
static void unmap_registers(ws2811_t *ws2811)
|
214
|
+
{
|
215
|
+
ws2811_device_t *device = ws2811->device;
|
216
|
+
|
217
|
+
if (device->dma)
|
218
|
+
{
|
219
|
+
unmap_device(device->dma, sizeof(dma_t));
|
220
|
+
}
|
221
|
+
|
222
|
+
if (device->pwm)
|
223
|
+
{
|
224
|
+
unmap_device(device->pwm, sizeof(pwm_t));
|
225
|
+
}
|
226
|
+
|
227
|
+
if (device->cm_pwm)
|
228
|
+
{
|
229
|
+
unmap_device(device->cm_pwm, sizeof(cm_pwm_t));
|
230
|
+
}
|
231
|
+
|
232
|
+
if (device->gpio)
|
233
|
+
{
|
234
|
+
unmap_device(device->gpio, sizeof(gpio_t));
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
/**
|
239
|
+
* Given a userspace address pointer, return the matching bus address used by DMA.
|
240
|
+
* Note: The bus address is not the same as the CPU physical address.
|
241
|
+
*
|
242
|
+
* @param addr Userspace virtual address pointer.
|
243
|
+
*
|
244
|
+
* @returns Bus address for use by DMA.
|
245
|
+
*/
|
246
|
+
static uint32_t addr_to_bus(const volatile void *addr)
|
247
|
+
{
|
248
|
+
char filename[40];
|
249
|
+
uint64_t pfn;
|
250
|
+
int fd;
|
251
|
+
|
252
|
+
sprintf(filename, "/proc/%d/pagemap", getpid());
|
253
|
+
fd = open(filename, O_RDONLY);
|
254
|
+
if (fd < 0)
|
255
|
+
{
|
256
|
+
perror("addr_to_bus() can't open pagemap");
|
257
|
+
return ~0UL;
|
258
|
+
}
|
259
|
+
|
260
|
+
if (lseek(fd, (uint32_t)addr >> 9, SEEK_SET) !=
|
261
|
+
(uint32_t)addr >> 9)
|
262
|
+
{
|
263
|
+
perror("addr_to_bus() lseek() failed");
|
264
|
+
close(fd);
|
265
|
+
return ~0UL;
|
266
|
+
}
|
267
|
+
|
268
|
+
if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn))
|
269
|
+
{
|
270
|
+
perror("addr_to_bus() read() failed");
|
271
|
+
close(fd);
|
272
|
+
return ~0UL;
|
273
|
+
}
|
274
|
+
|
275
|
+
close(fd);
|
276
|
+
|
277
|
+
return ((uint32_t)pfn << 12) | 0x40000000 | ((uint32_t)addr & 0xfff);
|
278
|
+
}
|
279
|
+
|
280
|
+
/**
|
281
|
+
* Stop the PWM controller.
|
282
|
+
*
|
283
|
+
* @param ws2811 ws2811 instance pointer.
|
284
|
+
*
|
285
|
+
* @returns None
|
286
|
+
*/
|
287
|
+
static void stop_pwm(ws2811_t *ws2811)
|
288
|
+
{
|
289
|
+
ws2811_device_t *device = ws2811->device;
|
290
|
+
volatile pwm_t *pwm = device->pwm;
|
291
|
+
volatile cm_pwm_t *cm_pwm = device->cm_pwm;
|
292
|
+
|
293
|
+
// Turn off the PWM in case already running
|
294
|
+
pwm->ctl = 0;
|
295
|
+
usleep(10);
|
296
|
+
|
297
|
+
// Kill the clock if it was already running
|
298
|
+
cm_pwm->ctl = CM_PWM_CTL_PASSWD | CM_PWM_CTL_KILL;
|
299
|
+
usleep(10);
|
300
|
+
while (cm_pwm->ctl & CM_PWM_CTL_BUSY)
|
301
|
+
;
|
302
|
+
}
|
303
|
+
|
304
|
+
/**
|
305
|
+
* Setup the PWM controller in serial mode on both channels using DMA to feed the PWM FIFO.
|
306
|
+
*
|
307
|
+
* @param ws2811 ws2811 instance pointer.
|
308
|
+
*
|
309
|
+
* @returns None
|
310
|
+
*/
|
311
|
+
static int setup_pwm(ws2811_t *ws2811)
|
312
|
+
{
|
313
|
+
ws2811_device_t *device = ws2811->device;
|
314
|
+
volatile dma_t *dma = device->dma;
|
315
|
+
volatile dma_cb_t *dma_cb = device->dma_cb;
|
316
|
+
volatile pwm_t *pwm = device->pwm;
|
317
|
+
volatile cm_pwm_t *cm_pwm = device->cm_pwm;
|
318
|
+
int maxcount = max_channel_led_count(ws2811);
|
319
|
+
uint32_t freq = ws2811->freq;
|
320
|
+
dma_page_t *page;
|
321
|
+
int32_t byte_count;
|
322
|
+
|
323
|
+
stop_pwm(ws2811);
|
324
|
+
|
325
|
+
// Setup the PWM Clock - Use OSC @ 19.2Mhz w/ 3 clocks/tick
|
326
|
+
cm_pwm->div = CM_PWM_DIV_PASSWD | CM_PWM_DIV_DIVI(OSC_FREQ / (3 * freq));
|
327
|
+
cm_pwm->ctl = CM_PWM_CTL_PASSWD | CM_PWM_CTL_SRC_OSC;
|
328
|
+
cm_pwm->ctl = CM_PWM_CTL_PASSWD | CM_PWM_CTL_SRC_OSC | CM_PWM_CTL_ENAB;
|
329
|
+
usleep(10);
|
330
|
+
while (!(cm_pwm->ctl & CM_PWM_CTL_BUSY))
|
331
|
+
;
|
332
|
+
|
333
|
+
// Setup the PWM, use delays as the block is rumored to lock up without them. Make
|
334
|
+
// sure to use a high enough priority to avoid any FIFO underruns, especially if
|
335
|
+
// the CPU is busy doing lots of memory accesses, or another DMA controller is
|
336
|
+
// busy. The FIFO will clock out data at a much slower rate (2.6Mhz max), so
|
337
|
+
// the odds of a DMA priority boost are extremely low.
|
338
|
+
|
339
|
+
pwm->rng1 = 32; // 32-bits per word to serialize
|
340
|
+
usleep(10);
|
341
|
+
pwm->ctl = RPI_PWM_CTL_CLRF1;
|
342
|
+
usleep(10);
|
343
|
+
pwm->dmac = RPI_PWM_DMAC_ENAB | RPI_PWM_DMAC_PANIC(7) | RPI_PWM_DMAC_DREQ(3);
|
344
|
+
usleep(10);
|
345
|
+
pwm->ctl = RPI_PWM_CTL_USEF1 | RPI_PWM_CTL_MODE1 |
|
346
|
+
RPI_PWM_CTL_USEF2 | RPI_PWM_CTL_MODE2;
|
347
|
+
usleep(10);
|
348
|
+
pwm->ctl |= RPI_PWM_CTL_PWEN1 | RPI_PWM_CTL_PWEN2;
|
349
|
+
|
350
|
+
// Initialize the DMA control blocks to chain together all the DMA pages
|
351
|
+
page = &device->page_head;
|
352
|
+
byte_count = PWM_BYTE_COUNT(maxcount, freq);
|
353
|
+
while ((page = dma_page_next(&device->page_head, page)) &&
|
354
|
+
byte_count)
|
355
|
+
{
|
356
|
+
int32_t page_bytes = PAGE_SIZE < byte_count ? PAGE_SIZE : byte_count;
|
357
|
+
|
358
|
+
dma_cb->ti = RPI_DMA_TI_NO_WIDE_BURSTS | // 32-bit transfers
|
359
|
+
RPI_DMA_TI_WAIT_RESP | // wait for write complete
|
360
|
+
RPI_DMA_TI_DEST_DREQ | // user peripheral flow control
|
361
|
+
RPI_DMA_TI_PERMAP(5) | // PWM peripheral
|
362
|
+
RPI_DMA_TI_SRC_INC; // Increment src addr
|
363
|
+
|
364
|
+
dma_cb->source_ad = addr_to_bus(page->addr);
|
365
|
+
if (dma_cb->source_ad == ~0L)
|
366
|
+
{
|
367
|
+
return -1;
|
368
|
+
}
|
369
|
+
|
370
|
+
dma_cb->dest_ad = (uint32_t)&((pwm_t *)PWM_PERIPH)->fif1;
|
371
|
+
dma_cb->txfr_len = page_bytes;
|
372
|
+
dma_cb->stride = 0;
|
373
|
+
dma_cb->nextconbk = addr_to_bus(dma_cb + 1);
|
374
|
+
|
375
|
+
byte_count -= page_bytes;
|
376
|
+
if (!dma_page_next(&device->page_head, page))
|
377
|
+
{
|
378
|
+
break;
|
379
|
+
}
|
380
|
+
|
381
|
+
dma_cb++;
|
382
|
+
}
|
383
|
+
|
384
|
+
// Terminate the final control block to stop DMA
|
385
|
+
dma_cb->nextconbk = 0;
|
386
|
+
|
387
|
+
dma->cs = 0;
|
388
|
+
dma->txfr_len = 0;
|
389
|
+
|
390
|
+
return 0;
|
391
|
+
}
|
392
|
+
|
393
|
+
/**
|
394
|
+
* Start the DMA feeding the PWM FIFO. This will stream the entire DMA buffer out of both
|
395
|
+
* PWM channels.
|
396
|
+
*
|
397
|
+
* @param ws2811 ws2811 instance pointer.
|
398
|
+
*
|
399
|
+
* @returns None
|
400
|
+
*/
|
401
|
+
static void dma_start(ws2811_t *ws2811)
|
402
|
+
{
|
403
|
+
ws2811_device_t *device = ws2811->device;
|
404
|
+
volatile dma_t *dma = device->dma;
|
405
|
+
uint32_t dma_cb_addr = device->dma_cb_addr;
|
406
|
+
|
407
|
+
dma->conblk_ad = dma_cb_addr;
|
408
|
+
dma->cs = RPI_DMA_CS_WAIT_OUTSTANDING_WRITES |
|
409
|
+
RPI_DMA_CS_PANIC_PRIORITY(15) |
|
410
|
+
RPI_DMA_CS_PRIORITY(15) |
|
411
|
+
RPI_DMA_CS_ACTIVE;
|
412
|
+
}
|
413
|
+
|
414
|
+
/**
|
415
|
+
* Initialize the application selected GPIO pins for PWM operation.
|
416
|
+
*
|
417
|
+
* @param ws2811 ws2811 instance pointer.
|
418
|
+
*
|
419
|
+
* @returns 0 on success, -1 on unsupported pin
|
420
|
+
*/
|
421
|
+
static int gpio_init(ws2811_t *ws2811)
|
422
|
+
{
|
423
|
+
volatile gpio_t *gpio = ws2811->device->gpio;
|
424
|
+
int chan;
|
425
|
+
|
426
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
427
|
+
{
|
428
|
+
int pinnum = ws2811->channel[chan].gpionum;
|
429
|
+
|
430
|
+
if (pinnum)
|
431
|
+
{
|
432
|
+
int altnum = pwm_pin_alt(chan, pinnum);
|
433
|
+
|
434
|
+
if (altnum < 0)
|
435
|
+
{
|
436
|
+
return -1;
|
437
|
+
}
|
438
|
+
|
439
|
+
gpio_function_set(gpio, pinnum, altnum);
|
440
|
+
}
|
441
|
+
}
|
442
|
+
|
443
|
+
return 0;
|
444
|
+
}
|
445
|
+
|
446
|
+
/**
|
447
|
+
* Initialize the PWM DMA buffer with all zeros for non-inverted operation, or
|
448
|
+
* ones for inverted operation. The DMA buffer length is assumed to be a word
|
449
|
+
* multiple.
|
450
|
+
*
|
451
|
+
* @param ws2811 ws2811 instance pointer.
|
452
|
+
*
|
453
|
+
* @returns None
|
454
|
+
*/
|
455
|
+
void pwm_raw_init(ws2811_t *ws2811)
|
456
|
+
{
|
457
|
+
volatile uint32_t *pwm_raw = (uint32_t *)ws2811->device->pwm_raw;
|
458
|
+
int maxcount = max_channel_led_count(ws2811);
|
459
|
+
int wordcount = (PWM_BYTE_COUNT(maxcount, ws2811->freq) / sizeof(uint32_t)) /
|
460
|
+
RPI_PWM_CHANNELS;
|
461
|
+
int chan;
|
462
|
+
|
463
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
464
|
+
{
|
465
|
+
ws2811_channel_t *channel = &ws2811->channel[chan];
|
466
|
+
int i, wordpos = chan;
|
467
|
+
|
468
|
+
for (i = 0; i < wordcount; i++)
|
469
|
+
{
|
470
|
+
if (channel->invert)
|
471
|
+
{
|
472
|
+
pwm_raw[wordpos] = ~0L;
|
473
|
+
}
|
474
|
+
else
|
475
|
+
{
|
476
|
+
pwm_raw[wordpos] = 0x0;
|
477
|
+
}
|
478
|
+
|
479
|
+
wordpos += 2;
|
480
|
+
}
|
481
|
+
}
|
482
|
+
}
|
483
|
+
|
484
|
+
/**
|
485
|
+
* Cleanup previously allocated device memory and buffers.
|
486
|
+
*
|
487
|
+
* @param ws2811 ws2811 instance pointer.
|
488
|
+
*
|
489
|
+
* @returns None
|
490
|
+
*/
|
491
|
+
void ws2811_cleanup(ws2811_t *ws2811)
|
492
|
+
{
|
493
|
+
int chan;
|
494
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
495
|
+
{
|
496
|
+
if (ws2811->channel[chan].leds)
|
497
|
+
{
|
498
|
+
free(ws2811->channel[chan].leds);
|
499
|
+
}
|
500
|
+
ws2811->channel[chan].leds = NULL;
|
501
|
+
}
|
502
|
+
|
503
|
+
ws2811_device_t *device = ws2811->device;
|
504
|
+
if (device) {
|
505
|
+
|
506
|
+
if (device->pwm_raw)
|
507
|
+
{
|
508
|
+
dma_page_free((uint8_t *)device->pwm_raw,
|
509
|
+
PWM_BYTE_COUNT(max_channel_led_count(ws2811),
|
510
|
+
ws2811->freq));
|
511
|
+
device->pwm_raw = NULL;
|
512
|
+
}
|
513
|
+
|
514
|
+
if (device->dma_cb)
|
515
|
+
{
|
516
|
+
dma_page_free((dma_cb_t *)device->dma_cb, sizeof(dma_cb_t));
|
517
|
+
device->dma_cb = NULL;
|
518
|
+
}
|
519
|
+
|
520
|
+
free(device);
|
521
|
+
}
|
522
|
+
ws2811->device = NULL;
|
523
|
+
}
|
524
|
+
|
525
|
+
|
526
|
+
/*
|
527
|
+
*
|
528
|
+
* Application API Functions
|
529
|
+
*
|
530
|
+
*/
|
531
|
+
|
532
|
+
|
533
|
+
/**
|
534
|
+
* Allocate and initialize memory, buffers, pages, PWM, DMA, and GPIO.
|
535
|
+
*
|
536
|
+
* @param ws2811 ws2811 instance pointer.
|
537
|
+
*
|
538
|
+
* @returns 0 on success, -1 otherwise.
|
539
|
+
*/
|
540
|
+
int ws2811_init(ws2811_t *ws2811)
|
541
|
+
{
|
542
|
+
ws2811_device_t *device = NULL;
|
543
|
+
int chan;
|
544
|
+
|
545
|
+
ws2811->device = malloc(sizeof(*ws2811->device));
|
546
|
+
if (!ws2811->device)
|
547
|
+
{
|
548
|
+
return -1;
|
549
|
+
}
|
550
|
+
device = ws2811->device;
|
551
|
+
|
552
|
+
// Initialize all pointers to NULL. Any non-NULL pointers will be freed on cleanup.
|
553
|
+
device->pwm_raw = NULL;
|
554
|
+
device->dma_cb = NULL;
|
555
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
556
|
+
{
|
557
|
+
ws2811->channel[chan].leds = NULL;
|
558
|
+
}
|
559
|
+
|
560
|
+
dma_page_init(&device->page_head);
|
561
|
+
|
562
|
+
// Allocate the LED buffers
|
563
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
564
|
+
{
|
565
|
+
ws2811_channel_t *channel = &ws2811->channel[chan];
|
566
|
+
|
567
|
+
channel->leds = malloc(sizeof(ws2811_led_t) * channel->count);
|
568
|
+
if (!channel->leds)
|
569
|
+
{
|
570
|
+
goto err;
|
571
|
+
}
|
572
|
+
|
573
|
+
memset(channel->leds, 0, sizeof(ws2811_led_t) * channel->count);
|
574
|
+
}
|
575
|
+
|
576
|
+
// Allocate the DMA buffer
|
577
|
+
device->pwm_raw = dma_alloc(&device->page_head,
|
578
|
+
PWM_BYTE_COUNT(max_channel_led_count(ws2811),
|
579
|
+
ws2811->freq));
|
580
|
+
if (!device->pwm_raw)
|
581
|
+
{
|
582
|
+
goto err;
|
583
|
+
}
|
584
|
+
|
585
|
+
pwm_raw_init(ws2811);
|
586
|
+
|
587
|
+
// Allocate the DMA control block
|
588
|
+
device->dma_cb = dma_desc_alloc(MAX_PAGES);
|
589
|
+
if (!device->dma_cb)
|
590
|
+
{
|
591
|
+
goto err;
|
592
|
+
}
|
593
|
+
memset((dma_cb_t *)device->dma_cb, 0, sizeof(dma_cb_t));
|
594
|
+
|
595
|
+
// Cache the DMA control block bus address
|
596
|
+
device->dma_cb_addr = addr_to_bus(device->dma_cb);
|
597
|
+
if (device->dma_cb_addr == ~0L)
|
598
|
+
{
|
599
|
+
goto err;
|
600
|
+
}
|
601
|
+
|
602
|
+
// Map the physical registers into userspace
|
603
|
+
if (map_registers(ws2811))
|
604
|
+
{
|
605
|
+
goto err;
|
606
|
+
}
|
607
|
+
|
608
|
+
// Initialize the GPIO pins
|
609
|
+
if (gpio_init(ws2811))
|
610
|
+
{
|
611
|
+
unmap_registers(ws2811);
|
612
|
+
goto err;
|
613
|
+
}
|
614
|
+
|
615
|
+
// Setup the PWM, clocks, and DMA
|
616
|
+
if (setup_pwm(ws2811))
|
617
|
+
{
|
618
|
+
unmap_registers(ws2811);
|
619
|
+
goto err;
|
620
|
+
}
|
621
|
+
|
622
|
+
return 0;
|
623
|
+
|
624
|
+
err:
|
625
|
+
ws2811_cleanup(ws2811);
|
626
|
+
|
627
|
+
return -1;
|
628
|
+
}
|
629
|
+
|
630
|
+
/**
|
631
|
+
* Shut down DMA, PWM, and cleanup memory.
|
632
|
+
*
|
633
|
+
* @param ws2811 ws2811 instance pointer.
|
634
|
+
*
|
635
|
+
* @returns None
|
636
|
+
*/
|
637
|
+
void ws2811_fini(ws2811_t *ws2811)
|
638
|
+
{
|
639
|
+
ws2811_wait(ws2811);
|
640
|
+
stop_pwm(ws2811);
|
641
|
+
|
642
|
+
unmap_registers(ws2811);
|
643
|
+
|
644
|
+
ws2811_cleanup(ws2811);
|
645
|
+
}
|
646
|
+
|
647
|
+
/**
|
648
|
+
* Wait for any executing DMA operation to complete before returning.
|
649
|
+
*
|
650
|
+
* @param ws2811 ws2811 instance pointer.
|
651
|
+
*
|
652
|
+
* @returns 0 on success, -1 on DMA competion error
|
653
|
+
*/
|
654
|
+
int ws2811_wait(ws2811_t *ws2811)
|
655
|
+
{
|
656
|
+
volatile dma_t *dma = ws2811->device->dma;
|
657
|
+
|
658
|
+
while ((dma->cs & RPI_DMA_CS_ACTIVE) &&
|
659
|
+
!(dma->cs & RPI_DMA_CS_ERROR))
|
660
|
+
{
|
661
|
+
usleep(10);
|
662
|
+
}
|
663
|
+
|
664
|
+
if (dma->cs & RPI_DMA_CS_ERROR)
|
665
|
+
{
|
666
|
+
fprintf(stderr, "DMA Error: %08x\n", dma->debug);
|
667
|
+
return -1;
|
668
|
+
}
|
669
|
+
|
670
|
+
return 0;
|
671
|
+
}
|
672
|
+
|
673
|
+
/**
|
674
|
+
* Render the PWM DMA buffer from the user supplied LED arrays and start the DMA
|
675
|
+
* controller. This will update all LEDs on both PWM channels.
|
676
|
+
*
|
677
|
+
* @param ws2811 ws2811 instance pointer.
|
678
|
+
*
|
679
|
+
* @returns None
|
680
|
+
*/
|
681
|
+
int ws2811_render(ws2811_t *ws2811)
|
682
|
+
{
|
683
|
+
volatile uint8_t *pwm_raw = ws2811->device->pwm_raw;
|
684
|
+
int maxcount = max_channel_led_count(ws2811);
|
685
|
+
int bitpos = 31;
|
686
|
+
int i, j, k, l, chan;
|
687
|
+
|
688
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) // Channel
|
689
|
+
{
|
690
|
+
ws2811_channel_t *channel = &ws2811->channel[chan];
|
691
|
+
int wordpos = chan;
|
692
|
+
int scale = (channel->brightness & 0xff) + 1;
|
693
|
+
|
694
|
+
for (i = 0; i < channel->count; i++) // Led
|
695
|
+
{
|
696
|
+
uint8_t color[] =
|
697
|
+
{
|
698
|
+
(((channel->leds[i] >> 8) & 0xff) * scale) >> 8, // green
|
699
|
+
(((channel->leds[i] >> 16) & 0xff) * scale) >> 8, // red
|
700
|
+
(((channel->leds[i] >> 0) & 0xff) * scale) >> 8, // blue
|
701
|
+
};
|
702
|
+
|
703
|
+
for (j = 0; j < ARRAY_SIZE(color); j++) // Color
|
704
|
+
{
|
705
|
+
for (k = 7; k >= 0; k--) // Bit
|
706
|
+
{
|
707
|
+
uint8_t symbol = SYMBOL_LOW;
|
708
|
+
|
709
|
+
if (color[j] & (1 << k))
|
710
|
+
{
|
711
|
+
symbol = SYMBOL_HIGH;
|
712
|
+
}
|
713
|
+
|
714
|
+
if (channel->invert)
|
715
|
+
{
|
716
|
+
symbol = ~symbol & 0x7;
|
717
|
+
}
|
718
|
+
|
719
|
+
for (l = 2; l >= 0; l--) // Symbol
|
720
|
+
{
|
721
|
+
uint32_t *wordptr = &((uint32_t *)pwm_raw)[wordpos];
|
722
|
+
|
723
|
+
*wordptr &= ~(1 << bitpos);
|
724
|
+
if (symbol & (1 << l))
|
725
|
+
{
|
726
|
+
*wordptr |= (1 << bitpos);
|
727
|
+
}
|
728
|
+
|
729
|
+
bitpos--;
|
730
|
+
if (bitpos < 0)
|
731
|
+
{
|
732
|
+
// Every other word is on the same channel
|
733
|
+
wordpos += 2;
|
734
|
+
|
735
|
+
bitpos = 31;
|
736
|
+
}
|
737
|
+
}
|
738
|
+
}
|
739
|
+
}
|
740
|
+
}
|
741
|
+
}
|
742
|
+
|
743
|
+
// Ensure the CPU data cache is flushed before the DMA is started.
|
744
|
+
__clear_cache((char *)pwm_raw,
|
745
|
+
(char *)&pwm_raw[PWM_BYTE_COUNT(maxcount, ws2811->freq)]);
|
746
|
+
|
747
|
+
// Wait for any previous DMA operation to complete.
|
748
|
+
if (ws2811_wait(ws2811))
|
749
|
+
{
|
750
|
+
return -1;
|
751
|
+
}
|
752
|
+
|
753
|
+
dma_start(ws2811);
|
754
|
+
|
755
|
+
return 0;
|
756
|
+
}
|
757
|
+
|