./0000775000175000017500000000000014216462170012144 5ustar jawn-smithjawn-smith./ws2811.c0000664000175000017500000011111014216157244013253 0ustar jawn-smithjawn-smith/* * ws2811.c * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mailbox.h" #include "clk.h" #include "gpio.h" #include "dma.h" #include "pwm.h" #include "pcm.h" #include "rpihw.h" #include "ws2811.h" #define BUS_TO_PHYS(x) ((x)&~0xC0000000) #define OSC_FREQ 19200000 // crystal frequency #define OSC_FREQ_PI4 54000000 // Pi 4 crystal frequency /* 4 colors (R, G, B + W), 8 bits per byte, 3 symbols per bit + 55uS low for reset signal */ #define LED_COLOURS 4 #define LED_RESET_uS 55 #define LED_BIT_COUNT(leds, freq) ((leds * LED_COLOURS * 8 * 3) + ((LED_RESET_uS * \ (freq * 3)) / 1000000)) /* Minimum time to wait for reset to occur in microseconds. */ #define LED_RESET_WAIT_TIME 300 // Pad out to the nearest uint32 + 32-bits for idle low/high times the number of channels #define PWM_BYTE_COUNT(leds, freq) (((((LED_BIT_COUNT(leds, freq) >> 3) & ~0x7) + 4) + 4) * \ RPI_PWM_CHANNELS) #define PCM_BYTE_COUNT(leds, freq) ((((LED_BIT_COUNT(leds, freq) >> 3) & ~0x7) + 4) + 4) // Symbol definitions #define SYMBOL_HIGH 0x6 // 1 1 0 #define SYMBOL_LOW 0x4 // 1 0 0 // Symbol definitions for software inversion (PCM and SPI only) #define SYMBOL_HIGH_INV 0x1 // 0 0 1 #define SYMBOL_LOW_INV 0x3 // 0 1 1 // Driver mode definitions #define NONE 0 #define PWM 1 #define PCM 2 #define SPI 3 // We use the mailbox interface to request memory from the VideoCore. // This lets us request one physically contiguous chunk, find its // physical address, and map it 'uncached' so that writes from this // code are immediately visible to the DMA controller. This struct // holds data relevant to the mailbox interface. typedef struct videocore_mbox { int handle; /* From mbox_open() */ unsigned mem_ref; /* From mem_alloc() */ unsigned bus_addr; /* From mem_lock() */ unsigned size; /* Size of allocation */ uint8_t *virt_addr; /* From mapmem() */ } videocore_mbox_t; typedef struct ws2811_device { int driver_mode; volatile uint8_t *pxl_raw; volatile dma_t *dma; volatile pwm_t *pwm; volatile pcm_t *pcm; int spi_fd; volatile dma_cb_t *dma_cb; uint32_t dma_cb_addr; volatile gpio_t *gpio; volatile cm_clk_t *cm_clk; videocore_mbox_t mbox; int max_count; } ws2811_device_t; /** * Provides monotonic timestamp in microseconds. * * @returns Current timestamp in microseconds or 0 on error. */ static uint64_t get_microsecond_timestamp() { struct timespec t; if (clock_gettime(CLOCK_MONOTONIC_RAW, &t) != 0) { return 0; } return (uint64_t) t.tv_sec * 1000000 + t.tv_nsec / 1000; } /** * Iterate through the channels and find the largest led count. * * @param ws2811 ws2811 instance pointer. * * @returns Maximum number of LEDs in all channels. */ static int max_channel_led_count(ws2811_t *ws2811) { int chan, max = 0; for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) { if (ws2811->channel[chan].count > max) { max = ws2811->channel[chan].count; } } return max; } /** * Map all devices into userspace memory. * Not called for SPI * * @param ws2811 ws2811 instance pointer. * * @returns 0 on success, -1 otherwise. */ static int map_registers(ws2811_t *ws2811) { ws2811_device_t *device = ws2811->device; const rpi_hw_t *rpi_hw = ws2811->rpi_hw; uint32_t base = ws2811->rpi_hw->periph_base; uint32_t dma_addr; uint32_t offset = 0; dma_addr = dmanum_to_offset(ws2811->dmanum); if (!dma_addr) { return -1; } dma_addr += rpi_hw->periph_base; device->dma = mapmem(dma_addr, sizeof(dma_t), DEV_MEM); if (!device->dma) { return -1; } switch (device->driver_mode) { case PWM: device->pwm = mapmem(PWM_OFFSET + base, sizeof(pwm_t), DEV_MEM); if (!device->pwm) { return -1; } break; case PCM: device->pcm = mapmem(PCM_OFFSET + base, sizeof(pcm_t), DEV_MEM); if (!device->pcm) { return -1; } break; } /* * The below call can potentially work with /dev/gpiomem instead. * However, it used /dev/mem before, so I'm leaving it as such. */ device->gpio = mapmem(GPIO_OFFSET + base, sizeof(gpio_t), DEV_MEM); if (!device->gpio) { return -1; } switch (device->driver_mode) { case PWM: offset = CM_PWM_OFFSET; break; case PCM: offset = CM_PCM_OFFSET; break; } device->cm_clk = mapmem(offset + base, sizeof(cm_clk_t), DEV_MEM); if (!device->cm_clk) { return -1; } return 0; } /** * Unmap all devices from virtual memory. * * @param ws2811 ws2811 instance pointer. * * @returns None */ static void unmap_registers(ws2811_t *ws2811) { ws2811_device_t *device = ws2811->device; if (device->dma) { unmapmem((void *)device->dma, sizeof(dma_t)); } if (device->pwm) { unmapmem((void *)device->pwm, sizeof(pwm_t)); } if (device->pcm) { unmapmem((void *)device->pcm, sizeof(pcm_t)); } if (device->cm_clk) { unmapmem((void *)device->cm_clk, sizeof(cm_clk_t)); } if (device->gpio) { unmapmem((void *)device->gpio, sizeof(gpio_t)); } } /** * Given a userspace address pointer, return the matching bus address used by DMA. * Note: The bus address is not the same as the CPU physical address. * * @param addr Userspace virtual address pointer. * * @returns Bus address for use by DMA. */ static uint32_t addr_to_bus(ws2811_device_t *device, const volatile void *virt) { videocore_mbox_t *mbox = &device->mbox; uint32_t offset = (uint8_t *)virt - mbox->virt_addr; return mbox->bus_addr + offset; } /** * Stop the PWM controller. * * @param ws2811 ws2811 instance pointer. * * @returns None */ static void stop_pwm(ws2811_t *ws2811) { ws2811_device_t *device = ws2811->device; volatile pwm_t *pwm = device->pwm; volatile cm_clk_t *cm_clk = device->cm_clk; // Turn off the PWM in case already running pwm->ctl = 0; usleep(10); // Kill the clock if it was already running cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_KILL; usleep(10); while (cm_clk->ctl & CM_CLK_CTL_BUSY) ; } /** * Stop the PCM controller. * * @param ws2811 ws2811 instance pointer. * * @returns None */ static void stop_pcm(ws2811_t *ws2811) { ws2811_device_t *device = ws2811->device; volatile pcm_t *pcm = device->pcm; volatile cm_clk_t *cm_clk = device->cm_clk; // Turn off the PCM in case already running pcm->cs = 0; usleep(10); // Kill the clock if it was already running cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_KILL; usleep(10); while (cm_clk->ctl & CM_CLK_CTL_BUSY) ; } /** * Setup the PWM controller in serial mode on both channels using DMA to feed the PWM FIFO. * * @param ws2811 ws2811 instance pointer. * * @returns None */ static int setup_pwm(ws2811_t *ws2811) { ws2811_device_t *device = ws2811->device; volatile dma_t *dma = device->dma; volatile dma_cb_t *dma_cb = device->dma_cb; volatile pwm_t *pwm = device->pwm; volatile cm_clk_t *cm_clk = device->cm_clk; int maxcount = device->max_count; uint32_t freq = ws2811->freq; int32_t byte_count; const rpi_hw_t *rpi_hw = ws2811->rpi_hw; const uint32_t rpi_type = rpi_hw->type; uint32_t osc_freq = OSC_FREQ; if(rpi_type == RPI_HWVER_TYPE_PI4){ osc_freq = OSC_FREQ_PI4; } stop_pwm(ws2811); // Setup the Clock - Use OSC @ 19.2Mhz w/ 3 clocks/tick cm_clk->div = CM_CLK_DIV_PASSWD | CM_CLK_DIV_DIVI(osc_freq / (3 * freq)); cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_SRC_OSC; cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_SRC_OSC | CM_CLK_CTL_ENAB; usleep(10); while (!(cm_clk->ctl & CM_CLK_CTL_BUSY)) ; // Setup the PWM, use delays as the block is rumored to lock up without them. Make // sure to use a high enough priority to avoid any FIFO underruns, especially if // the CPU is busy doing lots of memory accesses, or another DMA controller is // busy. The FIFO will clock out data at a much slower rate (2.6Mhz max), so // the odds of a DMA priority boost are extremely low. pwm->rng1 = 32; // 32-bits per word to serialize usleep(10); pwm->ctl = RPI_PWM_CTL_CLRF1; usleep(10); pwm->dmac = RPI_PWM_DMAC_ENAB | RPI_PWM_DMAC_PANIC(7) | RPI_PWM_DMAC_DREQ(3); usleep(10); pwm->ctl = RPI_PWM_CTL_USEF1 | RPI_PWM_CTL_MODE1 | RPI_PWM_CTL_USEF2 | RPI_PWM_CTL_MODE2; if (ws2811->channel[0].invert) { pwm->ctl |= RPI_PWM_CTL_POLA1; } if (ws2811->channel[1].invert) { pwm->ctl |= RPI_PWM_CTL_POLA2; } usleep(10); pwm->ctl |= RPI_PWM_CTL_PWEN1 | RPI_PWM_CTL_PWEN2; // Initialize the DMA control block byte_count = PWM_BYTE_COUNT(maxcount, freq); dma_cb->ti = RPI_DMA_TI_NO_WIDE_BURSTS | // 32-bit transfers RPI_DMA_TI_WAIT_RESP | // wait for write complete RPI_DMA_TI_DEST_DREQ | // user peripheral flow control RPI_DMA_TI_PERMAP(5) | // PWM peripheral RPI_DMA_TI_SRC_INC; // Increment src addr dma_cb->source_ad = addr_to_bus(device, device->pxl_raw); dma_cb->dest_ad = (uintptr_t)&((pwm_t *)PWM_PERIPH_PHYS)->fif1; dma_cb->txfr_len = byte_count; dma_cb->stride = 0; dma_cb->nextconbk = 0; dma->cs = 0; dma->txfr_len = 0; return 0; } /** * Setup the PCM controller with one 32-bit channel in a 32-bit frame using DMA to feed the PCM FIFO. * * @param ws2811 ws2811 instance pointer. * * @returns None */ static int setup_pcm(ws2811_t *ws2811) { ws2811_device_t *device = ws2811->device; volatile dma_t *dma = device->dma; volatile dma_cb_t *dma_cb = device->dma_cb; volatile pcm_t *pcm = device->pcm; volatile cm_clk_t *cm_clk = device->cm_clk; //int maxcount = max_channel_led_count(ws2811); int maxcount = device->max_count; uint32_t freq = ws2811->freq; int32_t byte_count; const rpi_hw_t *rpi_hw = ws2811->rpi_hw; const uint32_t rpi_type = rpi_hw->type; uint32_t osc_freq = OSC_FREQ; if(rpi_type == RPI_HWVER_TYPE_PI4){ osc_freq = OSC_FREQ_PI4; } stop_pcm(ws2811); // Setup the PCM Clock - Use OSC @ 19.2Mhz w/ 3 clocks/tick cm_clk->div = CM_CLK_DIV_PASSWD | CM_CLK_DIV_DIVI(osc_freq / (3 * freq)); cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_SRC_OSC; cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_SRC_OSC | CM_CLK_CTL_ENAB; usleep(10); while (!(cm_clk->ctl & CM_CLK_CTL_BUSY)) ; // Setup the PCM, use delays as the block is rumored to lock up without them. Make // sure to use a high enough priority to avoid any FIFO underruns, especially if // the CPU is busy doing lots of memory accesses, or another DMA controller is // busy. The FIFO will clock out data at a much slower rate (2.6Mhz max), so // the odds of a DMA priority boost are extremely low. pcm->cs = RPI_PCM_CS_EN; // Enable PCM hardware pcm->mode = (RPI_PCM_MODE_FLEN(31) | RPI_PCM_MODE_FSLEN(1)); // Framelength 32, clock enabled, frame sync pulse pcm->txc = RPI_PCM_TXC_CH1WEX | RPI_PCM_TXC_CH1EN | RPI_PCM_TXC_CH1POS(0) | RPI_PCM_TXC_CH1WID(8); // Single 32-bit channel pcm->cs |= RPI_PCM_CS_TXCLR; // Reset transmit fifo usleep(10); pcm->cs |= RPI_PCM_CS_DMAEN; // Enable DMA DREQ pcm->dreq = (RPI_PCM_DREQ_TX(0x3F) | RPI_PCM_DREQ_TX_PANIC(0x10)); // Set FIFO tresholds // Initialize the DMA control block byte_count = PCM_BYTE_COUNT(maxcount, freq); dma_cb->ti = RPI_DMA_TI_NO_WIDE_BURSTS | // 32-bit transfers RPI_DMA_TI_WAIT_RESP | // wait for write complete RPI_DMA_TI_DEST_DREQ | // user peripheral flow control RPI_DMA_TI_PERMAP(2) | // PCM TX peripheral RPI_DMA_TI_SRC_INC; // Increment src addr dma_cb->source_ad = addr_to_bus(device, device->pxl_raw); dma_cb->dest_ad = (uintptr_t)&((pcm_t *)PCM_PERIPH_PHYS)->fifo; dma_cb->txfr_len = byte_count; dma_cb->stride = 0; dma_cb->nextconbk = 0; dma->cs = 0; dma->txfr_len = 0; return 0; } /** * Start the DMA feeding the PWM FIFO. This will stream the entire DMA buffer out of both * PWM channels. * * @param ws2811 ws2811 instance pointer. * * @returns None */ static void dma_start(ws2811_t *ws2811) { ws2811_device_t *device = ws2811->device; volatile dma_t *dma = device->dma; volatile pcm_t *pcm = device->pcm; uint32_t dma_cb_addr = device->dma_cb_addr; dma->cs = RPI_DMA_CS_RESET; usleep(10); dma->cs = RPI_DMA_CS_INT | RPI_DMA_CS_END; usleep(10); dma->conblk_ad = dma_cb_addr; dma->debug = 7; // clear debug error flags dma->cs = RPI_DMA_CS_WAIT_OUTSTANDING_WRITES | RPI_DMA_CS_PANIC_PRIORITY(15) | RPI_DMA_CS_PRIORITY(15) | RPI_DMA_CS_ACTIVE; if (device->driver_mode == PCM) { pcm->cs |= RPI_PCM_CS_TXON; // Start transmission } } /** * Initialize the application selected GPIO pins for PWM/PCM operation. * * @param ws2811 ws2811 instance pointer. * * @returns 0 on success, -1 on unsupported pin */ static int gpio_init(ws2811_t *ws2811) { volatile gpio_t *gpio = ws2811->device->gpio; int chan; int altnum; for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) { int pinnum = ws2811->channel[chan].gpionum; if (pinnum) { switch (ws2811->device->driver_mode) { case PWM: altnum = pwm_pin_alt(chan, pinnum); break; case PCM: altnum = pcm_pin_alt(PCMFUN_DOUT, pinnum); break; default: altnum = -1; } if (altnum < 0) { return -1; } gpio_function_set(gpio, pinnum, altnum); } } return 0; } /** * Initialize the PWM DMA buffer with all zeros, inverted operation will be * handled by hardware. The DMA buffer length is assumed to be a word * multiple. * * @param ws2811 ws2811 instance pointer. * * @returns None */ void pwm_raw_init(ws2811_t *ws2811) { volatile uint32_t *pxl_raw = (uint32_t *)ws2811->device->pxl_raw; int maxcount = ws2811->device->max_count; int wordcount = (PWM_BYTE_COUNT(maxcount, ws2811->freq) / sizeof(uint32_t)) / RPI_PWM_CHANNELS; int chan; for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) { int i, wordpos = chan; for (i = 0; i < wordcount; i++) { pxl_raw[wordpos] = 0x0; wordpos += 2; } } } /** * Initialize the PCM DMA buffer with all zeros. * The DMA buffer length is assumed to be a word multiple. * * @param ws2811 ws2811 instance pointer. * * @returns None */ void pcm_raw_init(ws2811_t *ws2811) { volatile uint32_t *pxl_raw = (uint32_t *)ws2811->device->pxl_raw; int maxcount = ws2811->device->max_count; int wordcount = PCM_BYTE_COUNT(maxcount, ws2811->freq) / sizeof(uint32_t); int i; for (i = 0; i < wordcount; i++) { pxl_raw[i] = 0x0; } } /** * Cleanup previously allocated device memory and buffers. * * @param ws2811 ws2811 instance pointer. * * @returns None */ void ws2811_cleanup(ws2811_t *ws2811) { ws2811_device_t *device = ws2811->device; int chan; for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) { if (ws2811->channel[chan].leds) { free(ws2811->channel[chan].leds); } ws2811->channel[chan].leds = NULL; if (ws2811->channel[chan].gamma) { free(ws2811->channel[chan].gamma); } ws2811->channel[chan].gamma = NULL; } if (device->mbox.handle != -1) { videocore_mbox_t *mbox = &device->mbox; unmapmem(mbox->virt_addr, mbox->size); mem_unlock(mbox->handle, mbox->mem_ref); mem_free(mbox->handle, mbox->mem_ref); mbox_close(mbox->handle); mbox->handle = -1; } if (device && (device->spi_fd > 0)) { close(device->spi_fd); } if (device) { free(device); } ws2811->device = NULL; } static int set_driver_mode(ws2811_t *ws2811, int gpionum) { int gpionum2; if (gpionum == 18 || gpionum == 12) { ws2811->device->driver_mode = PWM; // Check gpio for PWM1 (2nd channel) is OK if used gpionum2 = ws2811->channel[1].gpionum; if (gpionum2 == 0 || gpionum2 == 13 || gpionum2 == 19) { return 0; } } else if (gpionum == 21 || gpionum == 31) { ws2811->device->driver_mode = PCM; } else if (gpionum == 10) { ws2811->device->driver_mode = SPI; } else { fprintf(stderr, "gpionum %d not allowed\n", gpionum); return -1; } // For PCM and SPI zero the 2nd channel memset(&ws2811->channel[1], 0, sizeof(ws2811_channel_t)); return 0; } static int check_hwver_and_gpionum(ws2811_t *ws2811) { const rpi_hw_t *rpi_hw; int hwver, gpionum; int gpionums_B1[] = { 10, 18, 21 }; int gpionums_B2[] = { 10, 18, 31 }; int gpionums_40p[] = { 10, 12, 18, 21}; int i; rpi_hw = ws2811->rpi_hw; hwver = rpi_hw->hwver & 0x0000ffff; gpionum = ws2811->channel[0].gpionum; if (hwver < 0x0004) // Model B Rev 1 { for ( i = 0; i < (int)(sizeof(gpionums_B1) / sizeof(gpionums_B1[0])); i++) { if (gpionums_B1[i] == gpionum) { // Set driver mode (PWM, PCM, or SPI) return set_driver_mode(ws2811, gpionum); } } } else if (hwver >= 0x0004 && hwver <= 0x000f) // Models B Rev2, A { for ( i = 0; i < (int)(sizeof(gpionums_B2) / sizeof(gpionums_B2[0])); i++) { if (gpionums_B2[i] == gpionum) { // Set driver mode (PWM, PCM, or SPI) return set_driver_mode(ws2811, gpionum); } } } else if (hwver >= 0x010) // Models B+, A+, 2B, 3B, Zero Zero-W { if ((ws2811->channel[0].count == 0) && (ws2811->channel[1].count > 0)) { // Special case: nothing in channel 0, channel 1 only PWM1 allowed // PWM1 only available on 40 pin GPIO interface gpionum = ws2811->channel[1].gpionum; if ((gpionum == 13) || (gpionum == 19)) { ws2811->device->driver_mode = PWM; return 0; } else { return -1; } } for ( i = 0; i < (int)(sizeof(gpionums_40p) / sizeof(gpionums_40p[0])); i++) { if (gpionums_40p[i] == gpionum) { // Set driver mode (PWM, PCM, or SPI) return set_driver_mode(ws2811, gpionum); } } } fprintf(stderr, "Gpio %d is illegal for LED channel 0\n", gpionum); return -1; } static ws2811_return_t spi_init(ws2811_t *ws2811) { int spi_fd; static uint8_t mode; static uint8_t bits = 8; uint32_t speed = ws2811->freq * 3; ws2811_device_t *device = ws2811->device; uint32_t base = ws2811->rpi_hw->periph_base; int pinnum = ws2811->channel[0].gpionum; spi_fd = open("/dev/spidev0.0", O_RDWR); if (spi_fd < 0) { fprintf(stderr, "Cannot open /dev/spidev0.0. spi_bcm2835 module not loaded?\n"); return WS2811_ERROR_SPI_SETUP; } device->spi_fd = spi_fd; // SPI mode if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) < 0) { return WS2811_ERROR_SPI_SETUP; } if (ioctl(spi_fd, SPI_IOC_RD_MODE, &mode) < 0) { return WS2811_ERROR_SPI_SETUP; } // Bits per word if (ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) { return WS2811_ERROR_SPI_SETUP; } if (ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) { return WS2811_ERROR_SPI_SETUP; } // Max speed Hz if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) { return WS2811_ERROR_SPI_SETUP; } if (ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) { return WS2811_ERROR_SPI_SETUP; } // Initialize device structure elements to not used // except driver_mode, spi_fd and max_count (already defined when spi_init called) device->pxl_raw = NULL; device->dma = NULL; device->pwm = NULL; device->pcm = NULL; device->dma_cb = NULL; device->dma_cb_addr = 0; device->cm_clk = NULL; device->mbox.handle = -1; // Set SPI-MOSI pin device->gpio = mapmem(GPIO_OFFSET + base, sizeof(gpio_t), DEV_GPIOMEM); if (!device->gpio) { return WS2811_ERROR_SPI_SETUP; } gpio_function_set(device->gpio, pinnum, 0); // SPI-MOSI ALT0 // Allocate LED buffer ws2811_channel_t *channel = &ws2811->channel[0]; channel->leds = malloc(sizeof(ws2811_led_t) * channel->count); if (!channel->leds) { ws2811_cleanup(ws2811); return WS2811_ERROR_OUT_OF_MEMORY; } memset(channel->leds, 0, sizeof(ws2811_led_t) * channel->count); if (!channel->strip_type) { channel->strip_type=WS2811_STRIP_RGB; } // Set default uncorrected gamma table if (!channel->gamma) { channel->gamma = malloc(sizeof(uint8_t) * 256); int x; for(x = 0; x < 256; x++){ channel->gamma[x] = x; } } channel->wshift = (channel->strip_type >> 24) & 0xff; channel->rshift = (channel->strip_type >> 16) & 0xff; channel->gshift = (channel->strip_type >> 8) & 0xff; channel->bshift = (channel->strip_type >> 0) & 0xff; // Allocate SPI transmit buffer (same size as PCM) device->pxl_raw = malloc(PCM_BYTE_COUNT(device->max_count, ws2811->freq)); if (device->pxl_raw == NULL) { ws2811_cleanup(ws2811); return WS2811_ERROR_OUT_OF_MEMORY; } pcm_raw_init(ws2811); return WS2811_SUCCESS; } static ws2811_return_t spi_transfer(ws2811_t *ws2811) { int ret; struct spi_ioc_transfer tr; memset(&tr, 0, sizeof(struct spi_ioc_transfer)); tr.tx_buf = (unsigned long)ws2811->device->pxl_raw; tr.rx_buf = 0; tr.len = PCM_BYTE_COUNT(ws2811->device->max_count, ws2811->freq); ret = ioctl(ws2811->device->spi_fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 1) { fprintf(stderr, "Can't send spi message"); return WS2811_ERROR_SPI_TRANSFER; } return WS2811_SUCCESS; } /* * * Application API Functions * */ /** * Allocate and initialize memory, buffers, pages, PWM, DMA, and GPIO. * * @param ws2811 ws2811 instance pointer. * * @returns 0 on success, -1 otherwise. */ ws2811_return_t ws2811_init(ws2811_t *ws2811) { ws2811_device_t *device; const rpi_hw_t *rpi_hw; int chan; ws2811->rpi_hw = rpi_hw_detect(); if (!ws2811->rpi_hw) { return WS2811_ERROR_HW_NOT_SUPPORTED; } rpi_hw = ws2811->rpi_hw; ws2811->device = malloc(sizeof(*ws2811->device)); if (!ws2811->device) { return WS2811_ERROR_OUT_OF_MEMORY; } memset(ws2811->device, 0, sizeof(*ws2811->device)); device = ws2811->device; if (check_hwver_and_gpionum(ws2811) < 0) { return WS2811_ERROR_ILLEGAL_GPIO; } device->max_count = max_channel_led_count(ws2811); if (device->driver_mode == SPI) { return spi_init(ws2811); } // Determine how much physical memory we need for DMA switch (device->driver_mode) { case PWM: device->mbox.size = PWM_BYTE_COUNT(device->max_count, ws2811->freq) + sizeof(dma_cb_t); break; case PCM: device->mbox.size = PCM_BYTE_COUNT(device->max_count, ws2811->freq) + sizeof(dma_cb_t); break; } // Round up to page size multiple device->mbox.size = (device->mbox.size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); device->mbox.handle = mbox_open(); if (device->mbox.handle == -1) { return WS2811_ERROR_MAILBOX_DEVICE; } device->mbox.mem_ref = mem_alloc(device->mbox.handle, device->mbox.size, PAGE_SIZE, rpi_hw->videocore_base == 0x40000000 ? 0xC : 0x4); if (device->mbox.mem_ref == 0) { return WS2811_ERROR_OUT_OF_MEMORY; } device->mbox.bus_addr = mem_lock(device->mbox.handle, device->mbox.mem_ref); if (device->mbox.bus_addr == (uint32_t) ~0UL) { mem_free(device->mbox.handle, device->mbox.size); return WS2811_ERROR_MEM_LOCK; } device->mbox.virt_addr = mapmem(BUS_TO_PHYS(device->mbox.bus_addr), device->mbox.size, DEV_MEM); if (!device->mbox.virt_addr) { mem_unlock(device->mbox.handle, device->mbox.mem_ref); mem_free(device->mbox.handle, device->mbox.size); ws2811_cleanup(ws2811); return WS2811_ERROR_MMAP; } // Initialize all pointers to NULL. Any non-NULL pointers will be freed on cleanup. device->pxl_raw = NULL; device->dma_cb = NULL; for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) { ws2811->channel[chan].leds = NULL; } // Allocate the LED buffers for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) { ws2811_channel_t *channel = &ws2811->channel[chan]; channel->leds = malloc(sizeof(ws2811_led_t) * channel->count); if (!channel->leds) { ws2811_cleanup(ws2811); return WS2811_ERROR_OUT_OF_MEMORY; } memset(channel->leds, 0, sizeof(ws2811_led_t) * channel->count); if (!channel->strip_type) { channel->strip_type=WS2811_STRIP_RGB; } // Set default uncorrected gamma table if (!channel->gamma) { channel->gamma = malloc(sizeof(uint8_t) * 256); int x; for(x = 0; x < 256; x++){ channel->gamma[x] = x; } } channel->wshift = (channel->strip_type >> 24) & 0xff; channel->rshift = (channel->strip_type >> 16) & 0xff; channel->gshift = (channel->strip_type >> 8) & 0xff; channel->bshift = (channel->strip_type >> 0) & 0xff; } device->dma_cb = (dma_cb_t *)device->mbox.virt_addr; device->pxl_raw = (uint8_t *)device->mbox.virt_addr + sizeof(dma_cb_t); switch (device->driver_mode) { case PWM: pwm_raw_init(ws2811); break; case PCM: pcm_raw_init(ws2811); break; } memset((dma_cb_t *)device->dma_cb, 0, sizeof(dma_cb_t)); // Cache the DMA control block bus address device->dma_cb_addr = addr_to_bus(device, device->dma_cb); // Map the physical registers into userspace if (map_registers(ws2811)) { ws2811_cleanup(ws2811); return WS2811_ERROR_MAP_REGISTERS; } // Initialize the GPIO pins if (gpio_init(ws2811)) { unmap_registers(ws2811); ws2811_cleanup(ws2811); return WS2811_ERROR_GPIO_INIT; } switch (device->driver_mode) { case PWM: // Setup the PWM, clocks, and DMA if (setup_pwm(ws2811)) { unmap_registers(ws2811); ws2811_cleanup(ws2811); return WS2811_ERROR_PWM_SETUP; } break; case PCM: // Setup the PCM, clock, and DMA if (setup_pcm(ws2811)) { unmap_registers(ws2811); ws2811_cleanup(ws2811); return WS2811_ERROR_PCM_SETUP; } break; } return WS2811_SUCCESS; } /** * Shut down DMA, PWM, and cleanup memory. * * @param ws2811 ws2811 instance pointer. * * @returns None */ void ws2811_fini(ws2811_t *ws2811) { volatile pcm_t *pcm = ws2811->device->pcm; ws2811_wait(ws2811); switch (ws2811->device->driver_mode) { case PWM: stop_pwm(ws2811); break; case PCM: while (!(pcm->cs & RPI_PCM_CS_TXE)) ; // Wait till TX FIFO is empty stop_pcm(ws2811); break; } unmap_registers(ws2811); ws2811_cleanup(ws2811); } /** * Wait for any executing DMA operation to complete before returning. * * @param ws2811 ws2811 instance pointer. * * @returns 0 on success, -1 on DMA competion error */ ws2811_return_t ws2811_wait(ws2811_t *ws2811) { volatile dma_t *dma = ws2811->device->dma; if (ws2811->device->driver_mode == SPI) // Nothing to do for SPI { return WS2811_SUCCESS; } while ((dma->cs & RPI_DMA_CS_ACTIVE) && !(dma->cs & RPI_DMA_CS_ERROR)) { usleep(10); } if (dma->cs & RPI_DMA_CS_ERROR) { fprintf(stderr, "DMA Error: %08x\n", dma->debug); return WS2811_ERROR_DMA; } return WS2811_SUCCESS; } /** * Render the DMA buffer from the user supplied LED arrays and start the DMA * controller. This will update all LEDs on both PWM channels. * * @param ws2811 ws2811 instance pointer. * * @returns None */ ws2811_return_t ws2811_render(ws2811_t *ws2811) { volatile uint8_t *pxl_raw = ws2811->device->pxl_raw; int driver_mode = ws2811->device->driver_mode; int bitpos; int i, k, l, chan; unsigned j; ws2811_return_t ret = WS2811_SUCCESS; uint32_t protocol_time = 0; static uint64_t previous_timestamp = 0; bitpos = (driver_mode == SPI ? 7 : 31); for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) // Channel { ws2811_channel_t *channel = &ws2811->channel[chan]; int wordpos = chan; // PWM & PCM int bytepos = 0; // SPI const int scale = (channel->brightness & 0xff) + 1; uint8_t array_size = 3; // Assume 3 color LEDs, RGB // If our shift mask includes the highest nibble, then we have 4 LEDs, RBGW. if (channel->strip_type & SK6812_SHIFT_WMASK) { array_size = 4; } // 1.25µs per bit const uint32_t channel_protocol_time = channel->count * array_size * 8 * 1.25; // Only using the channel which takes the longest as both run in parallel if (channel_protocol_time > protocol_time) { protocol_time = channel_protocol_time; } for (i = 0; i < channel->count; i++) // Led { uint8_t color[] = { channel->gamma[(((channel->leds[i] >> channel->rshift) & 0xff) * scale) >> 8], // red channel->gamma[(((channel->leds[i] >> channel->gshift) & 0xff) * scale) >> 8], // green channel->gamma[(((channel->leds[i] >> channel->bshift) & 0xff) * scale) >> 8], // blue channel->gamma[(((channel->leds[i] >> channel->wshift) & 0xff) * scale) >> 8], // white }; for (j = 0; j < array_size; j++) // Color { for (k = 7; k >= 0; k--) // Bit { // Inversion is handled by hardware for PWM, otherwise by software here uint8_t symbol = SYMBOL_LOW; if ((driver_mode != PWM) && channel->invert) symbol = SYMBOL_LOW_INV; if (color[j] & (1 << k)) { symbol = SYMBOL_HIGH; if ((driver_mode != PWM) && channel->invert) symbol = SYMBOL_HIGH_INV; } for (l = 2; l >= 0; l--) // Symbol { uint32_t *wordptr = &((uint32_t *)pxl_raw)[wordpos]; // PWM & PCM volatile uint8_t *byteptr = &pxl_raw[bytepos]; // SPI if (driver_mode == SPI) { *byteptr &= ~(1 << bitpos); if (symbol & (1 << l)) { *byteptr |= (1 << bitpos); } } else // PWM & PCM { *wordptr &= ~(1 << bitpos); if (symbol & (1 << l)) { *wordptr |= (1 << bitpos); } } bitpos--; if (bitpos < 0) { if (driver_mode == SPI) { bytepos++; bitpos = 7; } else // PWM & PCM { // Every other word is on the same channel for PWM wordpos += (driver_mode == PWM ? 2 : 1); bitpos = 31; } } } } } } } // Wait for any previous DMA operation to complete. if ((ret = ws2811_wait(ws2811)) != WS2811_SUCCESS) { return ret; } if (ws2811->render_wait_time != 0) { const uint64_t current_timestamp = get_microsecond_timestamp(); uint64_t time_diff = current_timestamp - previous_timestamp; if (ws2811->render_wait_time > time_diff) { usleep(ws2811->render_wait_time - time_diff); } } if (driver_mode != SPI) { dma_start(ws2811); } else { ret = spi_transfer(ws2811); } // LED_RESET_WAIT_TIME is added to allow enough time for the reset to occur. previous_timestamp = get_microsecond_timestamp(); ws2811->render_wait_time = protocol_time + LED_RESET_WAIT_TIME; return ret; } const char * ws2811_get_return_t_str(const ws2811_return_t state) { const int index = -state; static const char * const ret_state_str[] = { WS2811_RETURN_STATES(WS2811_RETURN_STATES_STRING) }; if (index < (int)(sizeof(ret_state_str) / sizeof(ret_state_str[0]))) { return ret_state_str[index]; } return ""; } void ws2811_set_custom_gamma_factor(ws2811_t *ws2811, double gamma_factor) { int chan, counter; for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) { ws2811_channel_t *channel = &ws2811->channel[chan]; if (channel->gamma) { for(counter = 0; counter < 256; counter++) { channel->gamma[counter] = (gamma_factor > 0)? (int)(pow((float)counter / (float)255.00, gamma_factor) * 255.00 + 0.5) : counter; } } } } ./CMakeLists.txt0000664000175000017500000000426214216157244014713 0ustar jawn-smithjawn-smithcmake_minimum_required(VERSION 3.0.0) # read and parse version file file(READ version PROJECT_VERSION) string(STRIP ${PROJECT_VERSION} PROJECT_VERSION) string(REGEX REPLACE "([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" VERSION_MAJOR ${PROJECT_VERSION}) string(REGEX REPLACE "[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" VERSION_MINOR ${PROJECT_VERSION}) string(REGEX REPLACE "[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" VERSION_MICRO ${PROJECT_VERSION}) set(HEADERDEF "${VERSION_MAJOR}_${VERSION_MINOR}_${VERSION_MICRO}") project(rpi_ws281x VERSION ${PROJECT_VERSION}) option(BUILD_SHARED "Build as shared library" OFF) option(BUILD_TEST "Build test application" ON) set(CMAKE_C_STANDARD 11) set(LIB_TARGET ws2811) set(TEST_TARGET test) set(LIB_PUBLIC_HEADERS ws2811.h rpihw.h pwm.h clk.h dma.h gpio.h mailbox.h pcm.h ) set(LIB_SOURCES mailbox.c ws2811.c pwm.c pcm.c dma.c rpihw.c ) set(TEST_SOURCES main.c ) include(GNUInstallDirs) configure_file(version.h.in version.h) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/pkg-config.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/libws2811.pc" @ONLY ) set(DEST_HEADERS_DIR include/ws2811) set(DEST_LIB_DIR lib) if(BUILD_SHARED) add_library(${LIB_TARGET} SHARED ${LIB_SOURCES}) else() add_library(${LIB_TARGET} ${LIB_SOURCES}) endif() target_link_libraries(${LIB_TARGET} m) set_target_properties(${LIB_TARGET} PROPERTIES PUBLIC_HEADER "${LIB_PUBLIC_HEADERS}") install(TARGETS ${LIB_TARGET} ARCHIVE DESTINATION ${DEST_LIB_DIR} PUBLIC_HEADER DESTINATION ${DEST_HEADERS_DIR} ) INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/libws2811.pc" DESTINATION lib/pkgconfig) install(CODE "execute_process(COMMAND /sbin/ldconfig RESULT_VARIABLE EXIT_STATUS ERROR_QUIET) if (NOT EXIT_STATUS EQUAL 0) message(\"Warning: Could not run ldconfig. You may need to manually run ldconfig as root to cache the newly installed libraries.\") endif()") if(BUILD_TEST) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_executable(${TEST_TARGET} ${TEST_SOURCES}) target_link_libraries(${TEST_TARGET} ${LIB_TARGET}) endif() ./SConstruct0000664000175000017500000000512614216157244014205 0ustar jawn-smithjawn-smith# # SConstruct # # Copyright (c) 2014 Jeremy Garff # # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted # provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this list of # conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, this list # of conditions and the following disclaimer in the documentation and/or other materials # provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be used to endorse # or promote products derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import os opts = Variables() opts.Add(BoolVariable('V', 'Verbose build', False)) opts.Add('TOOLCHAIN', 'Set toolchain for cross compilation (e.g. arm-linux-gnueabihf)', '') platforms = [ [ 'userspace', # Target Name [ 'linux', 'version' ], # Scons tool (linux, avr, etc.) { # Special environment setup 'CPPPATH' : [ ], 'LINKFLAGS' : [ "-lrt", "-lm", ], }, ], ] clean_envs = {} for platform, tool, flags in platforms: env = Environment( options = opts, tools = tool, toolpath = ['.'], ENV = {'PATH' : os.environ['PATH']}, LIBS = [], ) env.MergeFlags(flags) clean_envs[platform] = env Help(opts.GenerateHelpText(clean_envs)) if env['TOOLCHAIN'] != '': env['CC'] = env['TOOLCHAIN'] + '-gcc' env['AR'] = env['TOOLCHAIN'] + '-ar' Export(['clean_envs']) SConscript('SConscript'); ./version0000664000175000017500000000000614216157244013553 0ustar jawn-smithjawn-smith1.1.0 ./dma.c0000664000175000017500000000447214216157244013063 0ustar jawn-smithjawn-smith/* * dma.c * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include "dma.h" // DMA address mapping by DMA number index static const uint32_t dma_offset[] = { DMA0_OFFSET, DMA1_OFFSET, DMA2_OFFSET, DMA3_OFFSET, DMA4_OFFSET, DMA5_OFFSET, DMA6_OFFSET, DMA7_OFFSET, DMA8_OFFSET, DMA9_OFFSET, DMA10_OFFSET, DMA11_OFFSET, DMA12_OFFSET, DMA13_OFFSET, DMA14_OFFSET, DMA15_OFFSET, }; uint32_t dmanum_to_offset(int dmanum) { int array_size = sizeof(dma_offset) / sizeof(dma_offset[0]); if (dmanum >= array_size) { return 0; } return dma_offset[dmanum]; } ./SConscript0000664000175000017500000000564614216157244014174 0ustar jawn-smithjawn-smith# # SConscript # # Copyright (c) 2014 Jeremy Garff # # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted # provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this list of # conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, this list # of conditions and the following disclaimer in the documentation and/or other materials # provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be used to endorse # or promote products derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Import(['clean_envs']) tools_env = clean_envs['userspace'].Clone() # Build Library lib_srcs = Split(''' mailbox.c ws2811.c pwm.c pcm.c dma.c rpihw.c ''') version_hdr = tools_env.Version('version') ws2811_lib = tools_env.Library('libws2811', lib_srcs) tools_env['LIBS'].append(ws2811_lib) # Shared library (if required) ws2811_slib = tools_env.SharedLibrary('libws2811', lib_srcs) # Test Program srcs = Split(''' main.c ''') objs = [] for src in srcs: objs.append(tools_env.Object(src)) test = tools_env.Program('test', objs + tools_env['LIBS']) Default([test, ws2811_lib]) package_version = "1.1.0-1" package_name = 'libws2811_%s' % package_version debian_files = [ 'DEBIAN/control', 'DEBIAN/postinst', 'DEBIAN/prerm', 'DEBIAN/postrm', ] package_files_desc = [ [ '/usr/lib', ws2811_slib ], ] package_files = [] for target in package_files_desc: package_files.append(tools_env.Install(package_name + target[0], target[1])) for deb_file in debian_files: package_files.append( tools_env.Command('%s/%s' % (package_name, deb_file), deb_file, [ Copy("$TARGET", "$SOURCE"), Chmod("$TARGET", 0o755) ]) ) package = tools_env.Command('%s.deb' % package_name, package_files, 'cd %s; dpkg-deb --build %s' % (Dir('.').abspath, package_name)); Alias("deb", package) ./main.c0000664000175000017500000002356514216157244013252 0ustar jawn-smithjawn-smith/* * newtest.c * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ static char VERSION[] = "XX.YY.ZZ"; #include #include #include #include #include #include #include #include #include #include #include #include #include "clk.h" #include "gpio.h" #include "dma.h" #include "pwm.h" #include "version.h" #include "ws2811.h" #define ARRAY_SIZE(stuff) (sizeof(stuff) / sizeof(stuff[0])) // defaults for cmdline options #define TARGET_FREQ WS2811_TARGET_FREQ #define GPIO_PIN 18 #define DMA 10 //#define STRIP_TYPE WS2811_STRIP_RGB // WS2812/SK6812RGB integrated chip+leds #define STRIP_TYPE WS2811_STRIP_GBR // WS2812/SK6812RGB integrated chip+leds //#define STRIP_TYPE SK6812_STRIP_RGBW // SK6812RGBW (NOT SK6812RGB) #define WIDTH 8 #define HEIGHT 8 #define LED_COUNT (WIDTH * HEIGHT) int width = WIDTH; int height = HEIGHT; int led_count = LED_COUNT; int clear_on_exit = 0; ws2811_t ledstring = { .freq = TARGET_FREQ, .dmanum = DMA, .channel = { [0] = { .gpionum = GPIO_PIN, .count = LED_COUNT, .invert = 0, .brightness = 255, .strip_type = STRIP_TYPE, }, [1] = { .gpionum = 0, .count = 0, .invert = 0, .brightness = 0, }, }, }; ws2811_led_t *matrix; static uint8_t running = 1; void matrix_render(void) { int x, y; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { ledstring.channel[0].leds[(y * width) + x] = matrix[y * width + x]; } } } void matrix_raise(void) { int x, y; for (y = 0; y < (height - 1); y++) { for (x = 0; x < width; x++) { // This is for the 8x8 Pimoroni Unicorn-HAT where the LEDS in subsequent // rows are arranged in opposite directions matrix[y * width + x] = matrix[(y + 1)*width + width - x - 1]; } } } void matrix_clear(void) { int x, y; for (y = 0; y < (height ); y++) { for (x = 0; x < width; x++) { matrix[y * width + x] = 0; } } } int dotspos[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ws2811_led_t dotcolors[] = { 0x00200000, // red 0x00201000, // orange 0x00202000, // yellow 0x00002000, // green 0x00002020, // lightblue 0x00000020, // blue 0x00100010, // purple 0x00200010, // pink }; ws2811_led_t dotcolors_rgbw[] = { 0x00200000, // red 0x10200000, // red + W 0x00002000, // green 0x10002000, // green + W 0x00000020, // blue 0x10000020, // blue + W 0x00101010, // white 0x10101010, // white + W }; void matrix_bottom(void) { int i; for (i = 0; i < (int)(ARRAY_SIZE(dotspos)); i++) { dotspos[i]++; if (dotspos[i] > (width - 1)) { dotspos[i] = 0; } if (ledstring.channel[0].strip_type == SK6812_STRIP_RGBW) { matrix[dotspos[i] + (height - 1) * width] = dotcolors_rgbw[i]; } else { matrix[dotspos[i] + (height - 1) * width] = dotcolors[i]; } } } static void ctrl_c_handler(int signum) { (void)(signum); running = 0; } static void setup_handlers(void) { struct sigaction sa = { .sa_handler = ctrl_c_handler, }; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); } void parseargs(int argc, char **argv, ws2811_t *ws2811) { int index; int c; static struct option longopts[] = { {"help", no_argument, 0, 'h'}, {"dma", required_argument, 0, 'd'}, {"gpio", required_argument, 0, 'g'}, {"invert", no_argument, 0, 'i'}, {"clear", no_argument, 0, 'c'}, {"strip", required_argument, 0, 's'}, {"height", required_argument, 0, 'y'}, {"width", required_argument, 0, 'x'}, {"version", no_argument, 0, 'v'}, {0, 0, 0, 0} }; while (1) { index = 0; c = getopt_long(argc, argv, "cd:g:his:vx:y:", longopts, &index); if (c == -1) break; switch (c) { case 0: /* handle flag options (array's 3rd field non-0) */ break; case 'h': fprintf(stderr, "%s version %s\n", argv[0], VERSION); fprintf(stderr, "Usage: %s \n" "-h (--help) - this information\n" "-s (--strip) - strip type - rgb, grb, gbr, rgbw\n" "-x (--width) - matrix width (default 8)\n" "-y (--height) - matrix height (default 8)\n" "-d (--dma) - dma channel to use (default 10)\n" "-g (--gpio) - GPIO to use\n" " If omitted, default is 18 (PWM0)\n" "-i (--invert) - invert pin output (pulse LOW)\n" "-c (--clear) - clear matrix on exit.\n" "-v (--version) - version information\n" , argv[0]); exit(-1); case 'D': break; case 'g': if (optarg) { int gpio = atoi(optarg); /* PWM0, which can be set to use GPIOs 12, 18, 40, and 52. Only 12 (pin 32) and 18 (pin 12) are available on the B+/2B/3B PWM1 which can be set to use GPIOs 13, 19, 41, 45 and 53. Only 13 is available on the B+/2B/PiZero/3B, on pin 33 PCM_DOUT, which can be set to use GPIOs 21 and 31. Only 21 is available on the B+/2B/PiZero/3B, on pin 40. SPI0-MOSI is available on GPIOs 10 and 38. Only GPIO 10 is available on all models. The library checks if the specified gpio is available on the specific model (from model B rev 1 till 3B) */ ws2811->channel[0].gpionum = gpio; } break; case 'i': ws2811->channel[0].invert=1; break; case 'c': clear_on_exit=1; break; case 'd': if (optarg) { int dma = atoi(optarg); if (dma < 14) { ws2811->dmanum = dma; } else { printf ("invalid dma %d\n", dma); exit (-1); } } break; case 'y': if (optarg) { height = atoi(optarg); if (height > 0) { ws2811->channel[0].count = height * width; } else { printf ("invalid height %d\n", height); exit (-1); } } break; case 'x': if (optarg) { width = atoi(optarg); if (width > 0) { ws2811->channel[0].count = height * width; } else { printf ("invalid width %d\n", width); exit (-1); } } break; case 's': if (optarg) { if (!strncasecmp("rgb", optarg, 4)) { ws2811->channel[0].strip_type = WS2811_STRIP_RGB; } else if (!strncasecmp("rbg", optarg, 4)) { ws2811->channel[0].strip_type = WS2811_STRIP_RBG; } else if (!strncasecmp("grb", optarg, 4)) { ws2811->channel[0].strip_type = WS2811_STRIP_GRB; } else if (!strncasecmp("gbr", optarg, 4)) { ws2811->channel[0].strip_type = WS2811_STRIP_GBR; } else if (!strncasecmp("brg", optarg, 4)) { ws2811->channel[0].strip_type = WS2811_STRIP_BRG; } else if (!strncasecmp("bgr", optarg, 4)) { ws2811->channel[0].strip_type = WS2811_STRIP_BGR; } else if (!strncasecmp("rgbw", optarg, 4)) { ws2811->channel[0].strip_type = SK6812_STRIP_RGBW; } else if (!strncasecmp("grbw", optarg, 4)) { ws2811->channel[0].strip_type = SK6812_STRIP_GRBW; } else { printf ("invalid strip %s\n", optarg); exit (-1); } } break; case 'v': fprintf(stderr, "%s version %s\n", argv[0], VERSION); exit(-1); case '?': /* getopt_long already reported error? */ exit(-1); default: exit(-1); } } } int main(int argc, char *argv[]) { ws2811_return_t ret; sprintf(VERSION, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); parseargs(argc, argv, &ledstring); matrix = malloc(sizeof(ws2811_led_t) * width * height); setup_handlers(); if ((ret = ws2811_init(&ledstring)) != WS2811_SUCCESS) { fprintf(stderr, "ws2811_init failed: %s\n", ws2811_get_return_t_str(ret)); return ret; } while (running) { matrix_raise(); matrix_bottom(); matrix_render(); if ((ret = ws2811_render(&ledstring)) != WS2811_SUCCESS) { fprintf(stderr, "ws2811_render failed: %s\n", ws2811_get_return_t_str(ret)); break; } // 15 frames /sec usleep(1000000 / 15); } if (clear_on_exit) { matrix_clear(); matrix_render(); ws2811_render(&ledstring); } ws2811_fini(&ledstring); printf ("\n"); return ret; } ./mailbox.h0000664000175000017500000000460514216157244013760 0ustar jawn-smithjawn-smith/* Copyright (c) 2012, Broadcom Europe Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #define MAJOR_NUM 100 #define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *) #define DEV_MEM "/dev/mem" #define DEV_GPIOMEM "/dev/gpiomem" int mbox_open(void); void mbox_close(int file_desc); unsigned get_version(int file_desc); unsigned mem_alloc(int file_desc, unsigned size, unsigned align, unsigned flags); unsigned mem_free(int file_desc, unsigned handle); unsigned mem_lock(int file_desc, unsigned handle); unsigned mem_unlock(int file_desc, unsigned handle); void *mapmem(unsigned base, unsigned size, const char *mem_dev); void *unmapmem(void *addr, unsigned size); unsigned execute_code(int file_desc, unsigned code, unsigned r0, unsigned r1, unsigned r2, unsigned r3, unsigned r4, unsigned r5); unsigned execute_qpu(int file_desc, unsigned num_qpus, unsigned control, unsigned noflush, unsigned timeout); unsigned qpu_enable(int file_desc, unsigned enable); ./linux.py0000664000175000017500000000543214216157244013664 0ustar jawn-smithjawn-smith# # linux.py # # Copyright (c) 2014 Jeremy Garff # # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted # provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this list of # conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, this list # of conditions and the following disclaimer in the documentation and/or other materials # provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be used to endorse # or promote products derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import SCons import string import array import os tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas'] def linux_tools(env): for tool in tools: env.Tool(tool) if not env['V']: env['ARCOMSTR'] = 'AR ${TARGET}' env['ASCOMSTR'] = 'AS ${TARGET}' env['CCCOMSTR'] = 'CC ${TARGET}' env['CXXCOMSTR'] = 'C++ ${TARGET}' env['LINKCOMSTR'] = 'LINK ${TARGET}' env['RANLIBCOMSTR'] = 'RANLIB ${TARGET}' def linux_flags(env): env.MergeFlags({ 'CPPFLAGS' : ''' -fPIC -g -O2 -Wall -Wextra -Werror '''.split(), }), env.MergeFlags({ 'LINKFLAGS' : ''' '''.split() }) def linux_builders(env): env.Append(BUILDERS = { 'Program' : SCons.Builder.Builder( action = SCons.Action.Action('${LINK} -o ${TARGET} ${SOURCES} ${LINKFLAGS}', '${LINKCOMSTR}'), ), }) return 1 # The following are required functions by SCons when incorporating through tools def exists(env): return 1 def generate(env, **kwargs): [f(env) for f in (linux_tools, linux_flags, linux_builders)] ./pkg-config.pc.in0000664000175000017500000000040514216157244015123 0ustar jawn-smithjawn-smithlibdir=@CMAKE_INSTALL_FULL_LIBDIR@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@/ws2811 Name: libws2811 Description: Raspberry Pi WS281X Library Version: @VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_MICRO@ Requires: Libs: -L${libdir} -lws2811 Cflags: -I${includedir} ./rpihw.h0000664000175000017500000000404014216157244013447 0ustar jawn-smithjawn-smith/* * rpihw.h * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef __RPIHW_H__ #define __RPIHW_H__ #include typedef struct { uint32_t type; #define RPI_HWVER_TYPE_UNKNOWN 0 #define RPI_HWVER_TYPE_PI1 1 #define RPI_HWVER_TYPE_PI2 2 #define RPI_HWVER_TYPE_PI4 3 uint32_t hwver; uint32_t periph_base; uint32_t videocore_base; char *desc; } rpi_hw_t; const rpi_hw_t *rpi_hw_detect(void); #endif /* __RPIHW_H__ */ ./pcm.c0000664000175000017500000000651014216157244013074 0ustar jawn-smithjawn-smith/* * pcm.c * * Copyright (c) 2014 Jeremy Garff * PCM version Copyright (c) 2016 Ton van Overbeek * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include "pcm.h" // Mapping of Pin to alternate function for PCM_CLK const pcm_pin_table_t pcm_pin_clk[] = { { .pinnum = 18, .altnum = 0, }, { .pinnum = 28, .altnum = 2, }, }; // Mapping of Pin to alternate function for PCM_FS const pcm_pin_table_t pcm_pin_fs[] = { { .pinnum = 19, .altnum = 0, }, { .pinnum = 29, .altnum = 2, }, }; // Mapping of Pin to alternate function for PCM_DIN const pcm_pin_table_t pcm_pin_din[] = { { .pinnum = 20, .altnum = 0, }, { .pinnum = 30, .altnum = 2, }, }; // Mapping of Pin to alternate function for PCM_DOUT const pcm_pin_table_t pcm_pin_dout[] = { { .pinnum = 21, .altnum = 0, }, { .pinnum = 31, .altnum = 2, }, }; const pcm_pin_tables_t pcm_pin_tables[NUM_PCMFUNS] = { { .pins = pcm_pin_clk, .count = sizeof(pcm_pin_clk) / sizeof(pcm_pin_clk[0]), }, { .pins = pcm_pin_fs, .count = sizeof(pcm_pin_fs) / sizeof(pcm_pin_fs[0]), }, { .pins = pcm_pin_din, .count = sizeof(pcm_pin_din) / sizeof(pcm_pin_din[0]), }, { .pins = pcm_pin_dout, .count = sizeof(pcm_pin_dout) / sizeof(pcm_pin_dout[0]), }, }; int pcm_pin_alt(int pcmfun, int pinnum) { if (pcmfun < 0 || pcmfun > 3) { return -1; } const pcm_pin_tables_t *pintable = &pcm_pin_tables[pcmfun]; int i; for (i = 0; i < pintable->count; i++) { if (pintable->pins[i].pinnum == pinnum) { return pintable->pins[i].altnum; } } return -1; } ./pwm.c0000664000175000017500000000545014216157244013122 0ustar jawn-smithjawn-smith/* * pwm.c * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include "ws2811.h" #include "pwm.h" // Mapping of Pin to alternate function for PWM channel 0 const pwm_pin_table_t pwm_pin_chan0[] = { { .pinnum = 12, .altnum = 0, }, { .pinnum = 18, .altnum = 5, }, { .pinnum = 40, .altnum = 0, }, }; // Mapping of Pin to alternate function for PWM channel 1 const pwm_pin_table_t pwm_pin_chan1[] = { { .pinnum = 13, .altnum = 0, }, { .pinnum = 19, .altnum = 5, }, { .pinnum = 41, .altnum = 0, }, { .pinnum = 45, .altnum = 0, }, }; const pwm_pin_tables_t pwm_pin_tables[RPI_PWM_CHANNELS] = { { .pins = pwm_pin_chan0, .count = sizeof(pwm_pin_chan0) / sizeof(pwm_pin_chan0[0]), }, { .pins = pwm_pin_chan1, .count = sizeof(pwm_pin_chan1) / sizeof(pwm_pin_chan1[0]), }, }; int pwm_pin_alt(int chan, int pinnum) { const pwm_pin_tables_t *pintable = &pwm_pin_tables[chan]; int i; for (i = 0; i < pintable->count; i++) { if (pintable->pins[i].pinnum == pinnum) { return pintable->pins[i].altnum; } } return -1; } ./mailbox.c0000664000175000017500000001637614216157244013763 0ustar jawn-smithjawn-smith/* Copyright (c) 2012, Broadcom Europe Ltd. Copyright (c) 2016, Jeremy Garff All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mailbox.h" void *mapmem(uint32_t base, uint32_t size, const char *mem_dev) { uint32_t pagemask = ~0UL ^ (getpagesize() - 1); uint32_t offsetmask = getpagesize() - 1; int mem_fd; void *mem; mem_fd = open(mem_dev, O_RDWR | O_SYNC); if (mem_fd < 0) { perror("Can't open /dev/mem"); return NULL; } mem = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, base & pagemask); if (mem == MAP_FAILED) { perror("mmap error\n"); return NULL; } close(mem_fd); return (char *)mem + (base & offsetmask); } void *unmapmem(void *addr, uint32_t size) { uint32_t pagemask = ~0UL ^ (getpagesize() - 1); uintptr_t baseaddr = (uintptr_t)addr & pagemask; int s; s = munmap((void *)baseaddr, size); if (s != 0) { perror("munmap error\n"); } return NULL; } /* * use ioctl to send mbox property message */ static int mbox_property(int file_desc, void *buf) { int fd = file_desc; int ret_val = -1; if (fd < 0) { fd = mbox_open(); } if (fd >= 0) { ret_val = ioctl(fd, IOCTL_MBOX_PROPERTY, buf); if (ret_val < 0) { perror("ioctl_set_msg failed\n"); } } if (file_desc < 0) { mbox_close(fd); } return ret_val; } uint32_t mem_alloc(int file_desc, uint32_t size, uint32_t align, uint32_t flags) { int i=0; uint32_t p[32]; p[i++] = 0; // size p[i++] = 0x00000000; // process request p[i++] = 0x3000c; // (the tag id) p[i++] = 12; // (size of the buffer) p[i++] = 12; // (size of the data) p[i++] = size; // (num bytes? or pages?) p[i++] = align; // (alignment) p[i++] = flags; // (MEM_FLAG_L1_NONALLOCATING) p[i++] = 0x00000000; // end tag p[0] = i*sizeof *p; // actual size if (mbox_property(file_desc, p) < 0) return 0; else return p[5]; } uint32_t mem_free(int file_desc, uint32_t handle) { int i=0; uint32_t p[32]; p[i++] = 0; // size p[i++] = 0x00000000; // process request p[i++] = 0x3000f; // (the tag id) p[i++] = 4; // (size of the buffer) p[i++] = 4; // (size of the data) p[i++] = handle; p[i++] = 0x00000000; // end tag p[0] = i*sizeof *p; // actual size mbox_property(file_desc, p); return p[5]; } uint32_t mem_lock(int file_desc, uint32_t handle) { int i=0; uint32_t p[32]; p[i++] = 0; // size p[i++] = 0x00000000; // process request p[i++] = 0x3000d; // (the tag id) p[i++] = 4; // (size of the buffer) p[i++] = 4; // (size of the data) p[i++] = handle; p[i++] = 0x00000000; // end tag p[0] = i*sizeof *p; // actual size if (mbox_property(file_desc, p) < 0) return ~0; else return p[5]; } uint32_t mem_unlock(int file_desc, uint32_t handle) { int i=0; uint32_t p[32]; p[i++] = 0; // size p[i++] = 0x00000000; // process request p[i++] = 0x3000e; // (the tag id) p[i++] = 4; // (size of the buffer) p[i++] = 4; // (size of the data) p[i++] = handle; p[i++] = 0x00000000; // end tag p[0] = i * sizeof(*p); // actual size mbox_property(file_desc, p); return p[5]; } uint32_t execute_code(int file_desc, uint32_t code, uint32_t r0, uint32_t r1, uint32_t r2, uint32_t r3, uint32_t r4, uint32_t r5) { int i=0; uint32_t p[32]; p[i++] = 0; // size p[i++] = 0x00000000; // process request p[i++] = 0x30010; // (the tag id) p[i++] = 28; // (size of the buffer) p[i++] = 28; // (size of the data) p[i++] = code; p[i++] = r0; p[i++] = r1; p[i++] = r2; p[i++] = r3; p[i++] = r4; p[i++] = r5; p[i++] = 0x00000000; // end tag p[0] = i * sizeof(*p); // actual size mbox_property(file_desc, p); return p[5]; } uint32_t qpu_enable(int file_desc, uint32_t enable) { int i=0; uint32_t p[32]; p[i++] = 0; // size p[i++] = 0x00000000; // process request p[i++] = 0x30012; // (the tag id) p[i++] = 4; // (size of the buffer) p[i++] = 4; // (size of the data) p[i++] = enable; p[i++] = 0x00000000; // end tag p[0] = i * sizeof(*p); // actual size mbox_property(file_desc, p); return p[5]; } uint32_t execute_qpu(int file_desc, uint32_t num_qpus, uint32_t control, uint32_t noflush, uint32_t timeout) { int i = 0; uint32_t p[32]; p[i++] = 0; // size p[i++] = 0x00000000; // process request p[i++] = 0x30011; // (the tag id) p[i++] = 16; // (size of the buffer) p[i++] = 16; // (size of the data) p[i++] = num_qpus; p[i++] = control; p[i++] = noflush; p[i++] = timeout; // ms p[i++] = 0x00000000; // end tag p[0] = i * sizeof(*p); // actual size mbox_property(file_desc, p); return p[5]; } int mbox_open(void) { int file_desc; char filename[64]; file_desc = open("/dev/vcio", 0); if (file_desc >= 0) { return file_desc; } // open a char device file used for communicating with kernel mbox driver sprintf(filename, "/tmp/mailbox-%d", getpid()); unlink(filename); if (mknod(filename, S_IFCHR|0600, makedev(100, 0)) < 0) { perror("Failed to create mailbox device\n"); return -1; } file_desc = open(filename, 0); if (file_desc < 0) { perror("Can't open device file\n"); unlink(filename); return -1; } unlink(filename); return file_desc; } void mbox_close(int file_desc) { close(file_desc); } ./version.h.in0000664000175000017500000000042114216157244014407 0ustar jawn-smithjawn-smith/* Auto Generated Header built by version.py - DO NOT MODIFY */ #ifndef __@HEADERDEF@__ #define __@HEADERDEF@__ #define VERSION_MAJOR @VERSION_MAJOR@ #define VERSION_MINOR @VERSION_MINOR@ #define VERSION_MICRO @VERSION_MICRO@ #endif /* __@HEADERDEF@__ */./clk.h0000664000175000017500000000575014216157244013100 0ustar jawn-smithjawn-smith/* * clk.h * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef __CLK_H__ #define __CLK_H__ #include typedef struct { uint32_t ctl; #define CM_CLK_CTL_PASSWD (0x5a << 24) #define CM_CLK_CTL_MASH(val) ((val & 0x3) << 9) #define CM_CLK_CTL_FLIP (1 << 8) #define CM_CLK_CTL_BUSY (1 << 7) #define CM_CLK_CTL_KILL (1 << 5) #define CM_CLK_CTL_ENAB (1 << 4) #define CM_CLK_CTL_SRC_GND (0 << 0) #define CM_CLK_CTL_SRC_OSC (1 << 0) #define CM_CLK_CTL_SRC_TSTDBG0 (2 << 0) #define CM_CLK_CTL_SRC_TSTDBG1 (3 << 0) #define CM_CLK_CTL_SRC_PLLA (4 << 0) #define CM_CLK_CTL_SRC_PLLC (5 << 0) #define CM_CLK_CTL_SRC_PLLD (6 << 0) #define CM_CLK_CTL_SRC_HDMIAUX (7 << 0) uint32_t div; #define CM_CLK_DIV_PASSWD (0x5a << 24) #define CM_CLK_DIV_DIVI(val) ((val & 0xfff) << 12) #define CM_CLK_DIV_DIVF(val) ((val & 0xfff) << 0) } __attribute__((packed, aligned(4))) cm_clk_t; /* * PWM and PCM clock offsets from https://www.scribd.com/doc/127599939/BCM2835-Audio-clocks * */ #define CM_PCM_OFFSET (0x00101098) #define CM_PWM_OFFSET (0x001010a0) #endif /* __CLK_H__ */ ./dma.h0000664000175000017500000001300114216157244013054 0ustar jawn-smithjawn-smith/* * dma.h * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef __DMA_H__ #define __DMA_H__ #include /* * DMA Control Block in Main Memory * * Note: Must start at a 256 byte aligned address. * Use corresponding register field definitions. */ typedef struct { uint32_t ti; uint32_t source_ad; uint32_t dest_ad; uint32_t txfr_len; uint32_t stride; uint32_t nextconbk; uint32_t resvd_0x18[2]; } __attribute__((packed, aligned(4))) dma_cb_t; /* * DMA register set */ typedef struct { uint32_t cs; #define RPI_DMA_CS_RESET (1 << 31) #define RPI_DMA_CS_ABORT (1 << 30) #define RPI_DMA_CS_DISDEBUG (1 << 29) #define RPI_DMA_CS_WAIT_OUTSTANDING_WRITES (1 << 28) #define RPI_DMA_CS_PANIC_PRIORITY(val) ((val & 0xf) << 20) #define RPI_DMA_CS_PRIORITY(val) ((val & 0xf) << 16) #define RPI_DMA_CS_ERROR (1 << 8) #define RPI_DMA_CS_WAITING_OUTSTANDING_WRITES (1 << 6) #define RPI_DMA_CS_DREQ_STOPS_DMA (1 << 5) #define RPI_DMA_CS_PAUSED (1 << 4) #define RPI_DMA_CS_DREQ (1 << 3) #define RPI_DMA_CS_INT (1 << 2) #define RPI_DMA_CS_END (1 << 1) #define RPI_DMA_CS_ACTIVE (1 << 0) uint32_t conblk_ad; uint32_t ti; #define RPI_DMA_TI_NO_WIDE_BURSTS (1 << 26) #define RPI_DMA_TI_WAITS(val) ((val & 0x1f) << 21) #define RPI_DMA_TI_PERMAP(val) ((val & 0x1f) << 16) #define RPI_DMA_TI_BURST_LENGTH(val) ((val & 0xf) << 12) #define RPI_DMA_TI_SRC_IGNORE (1 << 11) #define RPI_DMA_TI_SRC_DREQ (1 << 10) #define RPI_DMA_TI_SRC_WIDTH (1 << 9) #define RPI_DMA_TI_SRC_INC (1 << 8) #define RPI_DMA_TI_DEST_IGNORE (1 << 7) #define RPI_DMA_TI_DEST_DREQ (1 << 6) #define RPI_DMA_TI_DEST_WIDTH (1 << 5) #define RPI_DMA_TI_DEST_INC (1 << 4) #define RPI_DMA_TI_WAIT_RESP (1 << 3) #define RPI_DMA_TI_TDMODE (1 << 1) #define RPI_DMA_TI_INTEN (1 << 0) uint32_t source_ad; uint32_t dest_ad; uint32_t txfr_len; #define RPI_DMA_TXFR_LEN_YLENGTH(val) ((val & 0xffff) << 16) #define RPI_DMA_TXFR_LEN_XLENGTH(val) ((val & 0xffff) << 0) uint32_t stride; #define RPI_DMA_STRIDE_D_STRIDE(val) ((val & 0xffff) << 16) #define RPI_DMA_STRIDE_S_STRIDE(val) ((val & 0xffff) << 0) uint32_t nextconbk; uint32_t debug; } __attribute__((packed, aligned(4))) dma_t; #define DMA0_OFFSET (0x00007000) #define DMA1_OFFSET (0x00007100) #define DMA2_OFFSET (0x00007200) #define DMA3_OFFSET (0x00007300) #define DMA4_OFFSET (0x00007400) #define DMA5_OFFSET (0x00007500) #define DMA6_OFFSET (0x00007600) #define DMA7_OFFSET (0x00007700) #define DMA8_OFFSET (0x00007800) #define DMA9_OFFSET (0x00007900) #define DMA10_OFFSET (0x00007a00) #define DMA11_OFFSET (0x00007b00) #define DMA12_OFFSET (0x00007c00) #define DMA13_OFFSET (0x00007d00) #define DMA14_OFFSET (0x00007e00) #define DMA15_OFFSET (0x00e05000) #define PAGE_SIZE (1 << 12) #define PAGE_MASK (~(PAGE_SIZE - 1)) #define PAGE_OFFSET(page) (page & (PAGE_SIZE - 1)) uint32_t dmanum_to_offset(int dmanum); #endif /* __DMA_H__ */ ./LICENSE0000664000175000017500000000241214216157244013153 0ustar jawn-smithjawn-smithCopyright (c) 2014, jgarff All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ./README.md0000664000175000017500000001772414216157244013441 0ustar jawn-smithjawn-smithrpi_ws281x ========== Userspace Raspberry Pi library for controlling WS281X LEDs. This includes WS2812 and SK6812RGB RGB LEDs Preliminary support is now included for SK6812RGBW LEDs (yes, RGB + W) The LEDs can be controlled by either the PWM (2 independent channels) or PCM controller (1 channel) or the SPI interface (1 channel). ### Bindings: Language-specific bindings for rpi_ws281x are available in: * Python - https://github.com/rpi-ws281x/rpi-ws281x-python * Rust - https://github.com/rpi-ws281x/rpi-ws281x-rust * Powershell - https://github.com/rpi-ws281x/rpi-ws281x-powershell * Java - https://github.com/rpi-ws281x/rpi-ws281x-java * CSharp - https://github.com/rpi-ws281x/rpi-ws281x-csharp * Go - https://github.com/rpi-ws281x/rpi-ws281x-go ### Background: The BCM2835 in the Raspberry Pi has both a PWM and a PCM module that are well suited to driving individually controllable WS281X LEDs. Using the DMA, PWM or PCM FIFO, and serial mode in the PWM, it's possible to control almost any number of WS281X LEDs in a chain connected to the appropriate output pin. For SPI the Raspbian spidev driver is used (`/dev/spidev0.0`). This library and test program set the clock rate to 3X the desired output frequency and creates a bit pattern in RAM from an array of colors where each bit is represented by 3 bits as follows. Bit 1 - 1 1 0 Bit 0 - 1 0 0 ### GPIO Usage: The GPIOs that can be used are limited by the hardware of the Pi and will vary based on the method used to drive them (PWM, PCM or SPI). Beware that the GPIO numbers are not the same as the physical pin numbers on the header. PWM: ``` PWM0, which can be set to use GPIOs 12, 18, 40, and 52. Only 12 (pin 32) and 18 (pin 12) are available on the B+/2B/3B PWM1 which can be set to use GPIOs 13, 19, 41, 45 and 53. Only 13 is available on the B+/2B/PiZero/3B, on pin 33 ``` PCM: ``` PCM_DOUT, which can be set to use GPIOs 21 and 31. Only 21 is available on the B+/2B/PiZero/3B, on pin 40. ``` SPI: ``` SPI0-MOSI is available on GPIOs 10 and 38. Only GPIO 10 is available on all models. See also note for RPi 3 below. ``` ### Power and voltage requirements WS281X LEDs are generally driven at 5V. Depending on your actual LED model and data line length you might be able to successfully drive the data input with 3.3V. However in the general case you probably want to use a level shifter to convert from the Raspberry Pi GPIO/PWM to 5V. It is also possible to run the LEDs from a 3.3V - 3.6V power source, and connect the GPIO directly at a cost of brightness, but this isn't recommended. The test program is designed to drive a 8x8 grid of LEDs e.g.from Adafruit (http://www.adafruit.com/products/1487) or Pimoroni (https://shop.pimoroni.com/products/unicorn-hat). Please see the Adafruit and Pimoroni websites for more information. Know what you're doing with the hardware and electricity. I take no reponsibility for damage, harm, or mistakes. ### Build: #### Build with SCons: - Install Scons (on raspbian, `apt-get install scons`). - Make sure to adjust the parameters in main.c to suit your hardware. - Signal rate (400kHz to 800kHz). Default 800kHz. - ledstring.invert=1 if using a inverting level shifter. - Width and height of LED matrix (height=1 for LED string). - Type `scons` from inside the source directory. #### Build and install with CMake: - Install CMake - Configure your build: For example: ``` mkdir build cd build cmake -D BUILD_SHARED=OFF -D BUILD_TEST=ON .. ``` See also for available options in `CMakeLists.txt`. - Type `cmake --build .` to build - To install built binaries and headers into your system type: ``` sudo make install ``` ### Running: - Type `sudo ./test` (default uses PWM channel 0). - That's it. You should see a moving rainbow scroll across the display. - More options are available, `./test -h` should show them: ``` ./test version 1.1.0 Usage: ./test -h (--help) - this information -s (--strip) - strip type - rgb, grb, gbr, rgbw -x (--width) - matrix width (default 8) -y (--height) - matrix height (default 8) -d (--dma) - dma channel to use (default 10) -g (--gpio) - GPIO to use If omitted, default is 18 (PWM0) -i (--invert) - invert pin output (pulse LOW) -c (--clear) - clear matrix on exit. -v (--version) - version information ``` ### Important warning about DMA channels You must make sure that the DMA channel you choose to use for the LEDs is not [already in use](https://www.raspberrypi.org/forums/viewtopic.php?p=609380#p609380) by the operating system. For example, **using DMA channel 5 [will cause](https://github.com/jgarff/rpi_ws281x/issues/224) filesystem corruption** on the Raspberry Pi 3 Model B. The default DMA channel (10) should be safe for the Raspberry Pi 3 Model B, but this may change in future software releases. ### Limitations: #### PWM Since this library and the onboard Raspberry Pi audio both use the PWM, they cannot be used together. You will need to blacklist the Broadcom audio kernel module by creating a file `/etc/modprobe.d/snd-blacklist.conf` with blacklist snd_bcm2835 If the audio device is still loading after blacklisting, you may also need to comment it out in the /etc/modules file. On headless systems you may also need to force audio through hdmi Edit config.txt and add: hdmi_force_hotplug=1 hdmi_force_edid_audio=1 A reboot is required for this change to take effect Some distributions use audio by default, even if nothing is being played. If audio is needed, you can use a USB audio device instead. #### PCM When using PCM you cannot use digital audio devices which use I2S since I2S uses the PCM hardware, but you can use analog audio. #### SPI When using SPI the led string is the only device which can be connected to the SPI bus. Both digital (I2S/PCM) and analog (PWM) audio can be used. Many distributions have a maximum SPI transfer of 4096 bytes. This can be changed in `/boot/cmdline.txt` by appending ``` spidev.bufsiz=32768 ``` On an RPi 3 you have to change the GPU core frequency to 250 MHz, otherwise the SPI clock has the wrong frequency. Do this by adding the following line to /boot/config.txt and reboot: ``` core_freq=250 ``` On an RPi 4 you must set a fixed frequency to avoid the idle CPU scaling changing the SPI frequency and breaking the ws281x timings: Do this by adding the following lines to /boot/config.txt and reboot: ``` core_freq=500 core_freq_min=500 ``` SPI requires you to be in the `gpio` group if you wish to control your LEDs without root. ### Comparison PWM/PCM/SPI Both PWM and PCM use DMA transfer to output the control signal for the LEDs. The max size of a DMA transfer is 65536 bytes. Since each LED needs 12 bytes (4 colors, 8 symbols per color, 3 bits per symbol) this means you can control approximately 5400 LEDs for a single strand in PCM and 2700 LEDs per string for PWM (Only PWM can control 2 independent strings simultaneously) SPI uses the SPI device driver in the kernel. For transfers larger than 96 bytes the kernel driver also uses DMA. Of course there are practical limits on power and signal quality. These will be more constraining in practice than the theoretical limits above. When controlling a LED string of 240 LEDs the CPU load on the original Pi 2 (BCM2836) are: PWM 5% PCM 5% SPI 1% ### Usage: The API is very simple. Make sure to create and initialize the `ws2811_t` structure as seen in [`main.c`](main.c). From there it can be initialized by calling `ws2811_init()`. LEDs are changed by modifying the color in the `.led[index]` array and calling `ws2811_render()`. The rest is handled by the library, which either creates the DMA memory and starts the DMA for PWM and PCM or prepares the SPI transfer buffer and sends it out on the MISO pin. Make sure to hook a signal handler for SIGKILL to do cleanup. From the handler make sure to call `ws2811_fini()`. It'll make sure that the DMA is finished before program execution stops and cleans up after itself. ./gpio.h0000664000175000017500000001012114216157244013251 0ustar jawn-smithjawn-smith/* * gpio.h * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef __GPIO_H__ #define __GPIO_H__ #include typedef struct { uint32_t fsel[6]; // GPIO Function Select uint32_t resvd_0x18; uint32_t set[2]; // GPIO Pin Output Set uint32_t resvd_0x24; uint32_t clr[2]; // GPIO Pin Output Clear uint32_t resvd_0x30; uint32_t lev[2]; // GPIO Pin Level uint32_t resvd_0x3c; uint32_t eds[2]; // GPIO Pin Event Detect Status uint32_t resvd_0x48; uint32_t ren[2]; // GPIO Pin Rising Edge Detect Enable uint32_t resvd_0x54; uint32_t fen[2]; // GPIO Pin Falling Edge Detect Enable uint32_t resvd_0x60; uint32_t hen[2]; // GPIO Pin High Detect Enable uint32_t resvd_0x6c; uint32_t len[2]; // GPIO Pin Low Detect Enable uint32_t resvd_0x78; uint32_t aren[2]; // GPIO Pin Async Rising Edge Detect uint32_t resvd_0x84; uint32_t afen[2]; // GPIO Pin Async Falling Edge Detect uint32_t resvd_0x90; uint32_t pud; // GPIO Pin Pull up/down Enable uint32_t pudclk[2]; // GPIO Pin Pull up/down Enable Clock uint32_t resvd_0xa0[4]; uint32_t test; } __attribute__((packed, aligned(4))) gpio_t; #define GPIO_OFFSET (0x00200000) static inline void gpio_function_set(volatile gpio_t *gpio, uint8_t pin, uint8_t function) { int regnum = pin / 10; int offset = (pin % 10) * 3; uint8_t funcmap[] = { 4, 5, 6, 7, 3, 2 }; // See datasheet for mapping if (function > 5) { return; } gpio->fsel[regnum] &= ~(0x7 << offset); gpio->fsel[regnum] |= ((funcmap[function]) << offset); } static inline void gpio_level_set(volatile gpio_t *gpio, uint8_t pin, uint8_t level) { int regnum = pin >> 5; int offset = (pin & 0x1f); if (level) { gpio->set[regnum] = (1 << offset); } else { gpio->clr[regnum] = (1 << offset); } } static inline void gpio_output_set(volatile gpio_t *gpio, uint8_t pin, uint8_t output) { int regnum = pin / 10; int offset = (pin % 10) * 3; uint8_t function = output ? 1 : 0; // See datasheet for mapping gpio->fsel[regnum] &= ~(0x7 << offset); gpio->fsel[regnum] |= ((function & 0x7) << offset); } #endif /* __GPIO_H__ */ ./ws2811.h0000664000175000017500000001554514216157244013277 0ustar jawn-smithjawn-smith/* * ws2811.h * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef __WS2811_H__ #define __WS2811_H__ #ifdef __cplusplus extern "C" { #endif #include #include "rpihw.h" #include "pwm.h" #define WS2811_TARGET_FREQ 800000 // Can go as low as 400000 // 4 color R, G, B and W ordering #define SK6812_STRIP_RGBW 0x18100800 #define SK6812_STRIP_RBGW 0x18100008 #define SK6812_STRIP_GRBW 0x18081000 #define SK6812_STRIP_GBRW 0x18080010 #define SK6812_STRIP_BRGW 0x18001008 #define SK6812_STRIP_BGRW 0x18000810 #define SK6812_SHIFT_WMASK 0xf0000000 // 3 color R, G and B ordering #define WS2811_STRIP_RGB 0x00100800 #define WS2811_STRIP_RBG 0x00100008 #define WS2811_STRIP_GRB 0x00081000 #define WS2811_STRIP_GBR 0x00080010 #define WS2811_STRIP_BRG 0x00001008 #define WS2811_STRIP_BGR 0x00000810 // predefined fixed LED types #define WS2812_STRIP WS2811_STRIP_GRB #define SK6812_STRIP WS2811_STRIP_GRB #define SK6812W_STRIP SK6812_STRIP_GRBW struct ws2811_device; typedef uint32_t ws2811_led_t; //< 0xWWRRGGBB typedef struct ws2811_channel_t { int gpionum; //< GPIO Pin with PWM alternate function, 0 if unused int invert; //< Invert output signal int count; //< Number of LEDs, 0 if channel is unused int strip_type; //< Strip color layout -- one of WS2811_STRIP_xxx constants ws2811_led_t *leds; //< LED buffers, allocated by driver based on count uint8_t brightness; //< Brightness value between 0 and 255 uint8_t wshift; //< White shift value uint8_t rshift; //< Red shift value uint8_t gshift; //< Green shift value uint8_t bshift; //< Blue shift value uint8_t *gamma; //< Gamma correction table } ws2811_channel_t; typedef struct ws2811_t { uint64_t render_wait_time; //< time in µs before the next render can run struct ws2811_device *device; //< Private data for driver use const rpi_hw_t *rpi_hw; //< RPI Hardware Information uint32_t freq; //< Required output frequency int dmanum; //< DMA number _not_ already in use ws2811_channel_t channel[RPI_PWM_CHANNELS]; } ws2811_t; #define WS2811_RETURN_STATES(X) \ X(0, WS2811_SUCCESS, "Success"), \ X(-1, WS2811_ERROR_GENERIC, "Generic failure"), \ X(-2, WS2811_ERROR_OUT_OF_MEMORY, "Out of memory"), \ X(-3, WS2811_ERROR_HW_NOT_SUPPORTED, "Hardware revision is not supported"), \ X(-4, WS2811_ERROR_MEM_LOCK, "Memory lock failed"), \ X(-5, WS2811_ERROR_MMAP, "mmap() failed"), \ X(-6, WS2811_ERROR_MAP_REGISTERS, "Unable to map registers into userspace"), \ X(-7, WS2811_ERROR_GPIO_INIT, "Unable to initialize GPIO"), \ X(-8, WS2811_ERROR_PWM_SETUP, "Unable to initialize PWM"), \ X(-9, WS2811_ERROR_MAILBOX_DEVICE, "Failed to create mailbox device"), \ X(-10, WS2811_ERROR_DMA, "DMA error"), \ X(-11, WS2811_ERROR_ILLEGAL_GPIO, "Selected GPIO not possible"), \ X(-12, WS2811_ERROR_PCM_SETUP, "Unable to initialize PCM"), \ X(-13, WS2811_ERROR_SPI_SETUP, "Unable to initialize SPI"), \ X(-14, WS2811_ERROR_SPI_TRANSFER, "SPI transfer error") \ #define WS2811_RETURN_STATES_ENUM(state, name, str) name = state #define WS2811_RETURN_STATES_STRING(state, name, str) str typedef enum { WS2811_RETURN_STATES(WS2811_RETURN_STATES_ENUM), WS2811_RETURN_STATE_COUNT } ws2811_return_t; ws2811_return_t ws2811_init(ws2811_t *ws2811); //< Initialize buffers/hardware void ws2811_fini(ws2811_t *ws2811); //< Tear it all down ws2811_return_t ws2811_render(ws2811_t *ws2811); //< Send LEDs off to hardware ws2811_return_t ws2811_wait(ws2811_t *ws2811); //< Wait for DMA completion const char * ws2811_get_return_t_str(const ws2811_return_t state); //< Get string representation of the given return state void ws2811_set_custom_gamma_factor(ws2811_t *ws2811, double gamma_factor); //< Set a custom Gamma correction array based on a gamma correction factor #ifdef __cplusplus } #endif #endif /* __WS2811_H__ */ ./pwm.h0000664000175000017500000001056414216157244013131 0ustar jawn-smithjawn-smith/* * pwm.h * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef __PWM_H__ #define __PWM_H__ #include /* * * Pin mappint of alternate pin configuration for PWM * * GPIO ALT PWM0 ALT PWM1 * * 12 0 * 13 0 * 18 5 * 19 5 * 40 0 * 41 0 * 45 0 * 52 1 * 53 1 * */ #define RPI_PWM_CHANNELS 2 typedef struct { uint32_t ctl; #define RPI_PWM_CTL_MSEN2 (1 << 15) #define RPI_PWM_CTL_USEF2 (1 << 13) #define RPI_PWM_CTL_POLA2 (1 << 12) #define RPI_PWM_CTL_SBIT2 (1 << 11) #define RPI_PWM_CTL_RPTL2 (1 << 10) #define RPI_PWM_CTL_MODE2 (1 << 9) #define RPI_PWM_CTL_PWEN2 (1 << 8) #define RPI_PWM_CTL_MSEN1 (1 << 7) #define RPI_PWM_CTL_CLRF1 (1 << 6) #define RPI_PWM_CTL_USEF1 (1 << 5) #define RPI_PWM_CTL_POLA1 (1 << 4) #define RPI_PWM_CTL_SBIT1 (1 << 3) #define RPI_PWM_CTL_RPTL1 (1 << 2) #define RPI_PWM_CTL_MODE1 (1 << 1) #define RPI_PWM_CTL_PWEN1 (1 << 0) uint32_t sta; #define RPI_PWM_STA_STA4 (1 << 12) #define RPI_PWM_STA_STA3 (1 << 11) #define RPI_PWM_STA_STA2 (1 << 10) #define RPI_PWM_STA_STA1 (1 << 9) #define RPI_PWM_STA_BERR (1 << 8) #define RPI_PWM_STA_GAP04 (1 << 7) #define RPI_PWM_STA_GAP03 (1 << 6) #define RPI_PWM_STA_GAP02 (1 << 5) #define RPI_PWM_STA_GAP01 (1 << 4) #define RPI_PWM_STA_RERR1 (1 << 3) #define RPI_PWM_STA_WERR1 (1 << 2) #define RPI_PWM_STA_EMPT1 (1 << 1) #define RPI_PWM_STA_FULL1 (1 << 0) uint32_t dmac; #define RPI_PWM_DMAC_ENAB (1 << 31) #define RPI_PWM_DMAC_PANIC(val) ((val & 0xff) << 8) #define RPI_PWM_DMAC_DREQ(val) ((val & 0xff) << 0) uint32_t resvd_0x0c; uint32_t rng1; uint32_t dat1; uint32_t fif1; uint32_t resvd_0x1c; uint32_t rng2; uint32_t dat2; } __attribute__((packed, aligned(4))) pwm_t; #define PWM_OFFSET (0x0020c000) #define PWM_PERIPH_PHYS (0x7e20c000) typedef struct { int pinnum; int altnum; } pwm_pin_table_t; typedef struct { const int count; const pwm_pin_table_t *pins; } pwm_pin_tables_t; int pwm_pin_alt(int chan, int pinnum); #endif /* __PWM_H__ */ ./pcm.h0000664000175000017500000001542114216157244013102 0ustar jawn-smithjawn-smith/* * pcm.h * * Copyright (c) 2014 Jeremy Garff * PCM version Copyright (c) Ton van Overbeek * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef __PCM_H__ #define __PCM_H__ #include /* * * Pin mapping of alternate pin configuration for PCM * * GPIO ALT PCM_CLK ALT PCM-FS ALT PCM_DIN ALT PCM_DOUT * * 18 0 * 19 0 * 20 0 * 21 0 * 28 2 * 29 2 * 30 2 * 31 2 * */ typedef struct { uint32_t cs; #define RPI_PCM_CS_STBY (1 << 25) #define RPI_PCM_CS_SYNC (1 << 24) #define RPI_PCM_CS_RXSEX (1 << 23) #define RPI_PCM_CS_RXF (1 << 22) #define RPI_PCM_CS_TXE (1 << 21) #define RPI_PCM_CS_RXD (1 << 20) #define RPI_PCM_CS_TXD (1 << 19) #define RPI_PCM_CS_RXR (1 << 18) #define RPI_PCM_CS_TXW (1 << 17) #define RPI_PCM_CS_RXERR (1 << 16) #define RPI_PCM_CS_TXERR (1 << 15) #define RPI_PCM_CS_RXSYNC (1 << 14) #define RPI_PCM_CS_TXSYNC (1 << 13) #define RPI_PCM_CS_DMAEN (1 << 9) #define RPI_PCM_CS_RXTHR(val) ((val & 0x03) << 7) #define RPI_PCM_CS_TXTHR(val) ((val & 0x03) << 5) #define RPI_PCM_CS_RXCLR (1 << 4) #define RPI_PCM_CS_TXCLR (1 << 3) #define RPI_PCM_CS_TXON (1 << 2) #define RPI_PCM_CS_RXON (1 << 1) #define RPI_PCM_CS_EN (1 << 0) uint32_t fifo; uint32_t mode; #define RPI_PCM_MODE_CLK_DIS (1 << 28) #define RPI_PCM_MODE_PDMN (1 << 27) #define RPI_PCM_MODE_PDME (1 << 26) #define RPI_PCM_MODE_FRXP (1 << 25) #define RPI_PCM_MODE_FTXP (1 << 24) #define RPI_PCM_MODE_CLKM (1 << 23) #define RPI_PCM_MODE_CLKI (1 << 22) #define RPI_PCM_MODE_FSM (1 << 21) #define RPI_PCM_MODE_FSI (1 << 20) #define RPI_PCM_MODE_FLEN(val) ((val & 0x3ff) << 10) #define RPI_PCM_MODE_FSLEN(val) ((val & 0x3ff) << 0) uint32_t rxc; #define RPI_PCM_RXC_CH1WEX (1 << 31) #define RPI_PCM_RXC_CH1EN (1 << 30) #define RPI_PCM_RXC_CH1POS(val) ((val & 0x3ff) << 20) #define RPI_PCM_RXC_CH1WID(val) ((val & 0x0f) << 16) #define RPI_PCM_RXC_CH2WEX (1 << 15) #define RPI_PCM_RXC_CH2EN (1 << 14) #define RPI_PCM_RXC_CH2POS(val) ((val & 0x3ff) << 4) #define RPI_PCM_RXC_CH2WID(val) ((val & 0x0f) << 0) uint32_t txc; #define RPI_PCM_TXC_CH1WEX (1 << 31) #define RPI_PCM_TXC_CH1EN (1 << 30) #define RPI_PCM_TXC_CH1POS(val) ((val & 0x3ff) << 20) #define RPI_PCM_TXC_CH1WID(val) ((val & 0x0f) << 16) #define RPI_PCM_TXC_CH2WEX (1 << 15) #define RPI_PCM_TXC_CH2EN (1 << 14) #define RPI_PCM_TXC_CH2POS(val) ((val & 0x3ff) << 4) #define RPI_PCM_TXC_CH2WID(val) ((val & 0x0f) << 0) uint32_t dreq; #define RPI_PCM_DREQ_TX_PANIC(val) ((val & 0x7f) << 24) #define RPI_PCM_DREQ_RX_PANIC(val) ((val & 0x7f) << 16) #define RPI_PCM_DREQ_TX(val) ((val & 0x7f) << 8) #define RPI_PCM_DREQ_RX(val) ((val & 0x7f) << 0) uint32_t inten; #define RPI_PCM_INTEN_RXERR (1 << 3) #define RPI_PCM_INTEN_TXERR (1 << 2) #define RPI_PCM_INTEN_RXR (1 << 1) #define RPI_PCM_INTEN_TXW (1 << 0) uint32_t intstc; #define RPI_PCM_INTSTC_RXERR (1 << 3) #define RPI_PCM_INTSTC_TXERR (1 << 2) #define RPI_PCM_INTSTC_RXR (1 << 1) #define RPI_PCM_INTSTC_TXW (1 << 0) uint32_t gray; #define RPI_PCM_GRAY_RXFIFOLEVEL(val) ((val & 0x3f) << 16) #define RPI_PCM_GRAY_FLUSHED(val) ((val & 0x3f) << 10 #define RPI_PCM_GRAY_RXLEVEL(val) ((val & 0x3f) << 4) #define RPI_PCM_GRAY_FLUSH (1 << 2) #define RPI_PCM_GRAY_CLR (1 << 1) #define RPI_PCM_GRAY_EN (1 << 0) } __attribute__((packed, aligned(4))) pcm_t; #define PCM_OFFSET (0x00203000) #define PCM_PERIPH_PHYS (0x7e203000) #define NUM_PCMFUNS 4 #define PCMFUN_CLK 0 #define PCMFUN_FS 1 #define PCMFUN_DIN 2 #define PCMFUN_DOUT 3 typedef struct { int pinnum; int altnum; } pcm_pin_table_t; typedef struct { const int count; const pcm_pin_table_t *pins; } pcm_pin_tables_t; int pcm_pin_alt(int pcmfun, int pinnum); #endif /* __PCM_H__ */ ./.gitignore0000664000175000017500000000014114216157244014133 0ustar jawn-smithjawn-smith.sconsign.dblite version.h *.pyc test newtest *.o *.a *.swp build/ release/ debug/ settings.json./version.py0000664000175000017500000000554614216157244014220 0ustar jawn-smithjawn-smith# # SConstruct # # Copyright (c) 2016 Jeremy Garff # # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted # provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this list of # conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, this list # of conditions and the following disclaimer in the documentation and/or other materials # provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be used to endorse # or promote products derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import SCons, os def version_flags(env): if not env['V']: env['VERSIONCOMSTR'] = 'Version ${TARGET}' def version_builders(env): def generate_version_header(target, source, env): headername = os.path.basename(target[0].abspath) headerdef = headername.replace('.', '_').replace('-', '_').upper() try: version = open(source[0].abspath, 'r').readline().strip().split('.') except: version = [ '0', '0', '0' ] f = open(headername, 'w') f.write('/* Auto Generated Header built by version.py - DO NOT MODIFY */\n') f.write('\n') f.write('#ifndef __%s__\n' % (headerdef)) f.write('#define __%s__\n' % (headerdef)) f.write('\n') f.write('#define VERSION_MAJOR %s\n' % version[0]) f.write('#define VERSION_MINOR %s\n' % version[1]) f.write('#define VERSION_MICRO %s\n' % version[2]) f.write('\n') f.write('#endif /* __%s__ */\n' % (headerdef)) f.close() env.Append(BUILDERS = { 'Version' : SCons.Builder.Builder( action = SCons.Action.Action(generate_version_header, '${VERSIONCOMSTR}'), suffix = '.h', ), }) def exists(env): return 1 def generate(env, **kwargs): [f(env) for f in (version_flags, version_builders)] ./rpihw.c0000664000175000017500000003644614216157244013461 0ustar jawn-smithjawn-smith/* * rpihw.c * * Copyright (c) 2014 Jeremy Garff * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * 3. Neither the name of the owner nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include "rpihw.h" #define LINE_WIDTH_MAX 80 #define HW_VER_STRING "Revision" #define PERIPH_BASE_RPI 0x20000000 #define PERIPH_BASE_RPI2 0x3f000000 #define PERIPH_BASE_RPI4 0xfe000000 #define VIDEOCORE_BASE_RPI 0x40000000 #define VIDEOCORE_BASE_RPI2 0xc0000000 #define RPI_MANUFACTURER_MASK (0xf << 16) #define RPI_WARRANTY_MASK (0x3 << 24) static const rpi_hw_t rpi_hw_info[] = { // // Raspberry Pi 400 // { .hwver = 0xc03130, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 400 - 4GB v1.0" }, { .hwver = 0xc03131, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 400 - 4GB v1.1" }, // // Raspberry Pi 4 // { .hwver = 0xA03111, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 1GB v1.1" }, { .hwver = 0xB03111, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 2GB v.1.1" }, { .hwver = 0xC03111, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 4GB v1.1" }, { .hwver = 0xA03112, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 1GB v1.2" }, { .hwver = 0xB03112, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 2GB v.1.2" }, { .hwver = 0xC03112, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 4GB v1.2" }, { .hwver = 0xb03114, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 2GB v1.4" }, { .hwver = 0xD03114, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 8GB v1.4" }, { .hwver = 0xc03114, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 4GB v1.4" }, { .hwver = 0xb03115, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 2GB v1.5" }, { .hwver = 0xc03115, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 4GB v1.5" }, { .hwver = 0xd03115, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 4 Model B - 8GB v1.5" }, // // Compute Module 4 // { .hwver = 0xa03140, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Compute Module 4 v1.0 eMMC" }, { .hwver = 0xb03140, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Compute Module 4 v1.0 Lite" }, { .hwver = 0xc03140, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Compute Module 4 v1.0 WiFi" }, { .hwver = 0xd03140, .type = RPI_HWVER_TYPE_PI4, .periph_base = PERIPH_BASE_RPI4, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Compute Module 4 v1.0 WiFi 8GB" }, // // Model B Rev 1.0 // { .hwver = 0x02, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B", }, { .hwver = 0x03, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B", }, // // Model B Rev 2.0 // { .hwver = 0x04, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B", }, { .hwver = 0x05, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B", }, { .hwver = 0x06, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B", }, // // Model A // { .hwver = 0x07, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model A", }, { .hwver = 0x08, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model A", }, { .hwver = 0x09, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model A", }, // // Model B // { .hwver = 0x0d, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B", }, { .hwver = 0x0e, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B", }, { .hwver = 0x0f, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B", }, // // Model B+ // { .hwver = 0x10, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B+", }, { .hwver = 0x13, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B+", }, { .hwver = 0x900032, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model B+", }, // // Compute Module // { .hwver = 0x11, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Compute Module 1", }, { .hwver = 0x14, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Compute Module 1", }, // // Pi Zero // { .hwver = 0x900092, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Pi Zero v1.2", }, { .hwver = 0x900093, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Pi Zero v1.3", }, { .hwver = 0x920093, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Pi Zero v1.3", }, { .hwver = 0x9200c1, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Pi Zero W v1.1", }, { .hwver = 0x9000c1, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Pi Zero W v1.1", }, // // Model Zero 2 W // { .hwver = 0x902120, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi Zero 2 W v1.0", }, // // Model A+ // { .hwver = 0x12, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model A+", }, { .hwver = 0x15, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model A+", }, { .hwver = 0x900021, .type = RPI_HWVER_TYPE_PI1, .periph_base = PERIPH_BASE_RPI, .videocore_base = VIDEOCORE_BASE_RPI, .desc = "Model A+", }, // // Pi 2 Model B // { .hwver = 0xa01041, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 2", }, { .hwver = 0xa01040, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 2", }, { .hwver = 0xa21041, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 2", }, // // Pi 2 with BCM2837 // { .hwver = 0xa22042, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 2", }, // // Pi 3 Model B // { .hwver = 0xa020d3, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 3 B+", }, { .hwver = 0xa02082, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 3", }, { .hwver = 0xa02083, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 3", }, { .hwver = 0xa22082, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 3", }, { .hwver = 0xa22083, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Pi 3", }, { .hwver = 0x9020e0, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Model 3 A+", }, // // Pi Compute Module 3 // { .hwver = 0xa020a0, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Compute Module 3/L3", }, // // Pi Compute Module 3+ // { .hwver = 0xa02100, .type = RPI_HWVER_TYPE_PI2, .periph_base = PERIPH_BASE_RPI2, .videocore_base = VIDEOCORE_BASE_RPI2, .desc = "Compute Module 3+", }, }; const rpi_hw_t *rpi_hw_detect(void) { const rpi_hw_t *result = NULL; uint32_t rev; unsigned i; #ifdef __aarch64__ // On ARM64, read revision from /proc/device-tree as it is not shown in // /proc/cpuinfo FILE *f = fopen("/proc/device-tree/system/linux,revision", "r"); if (!f) { return NULL; } size_t read = fread(&rev, 1, sizeof(uint32_t), f); if (read != sizeof(uint32_t)) goto done; #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ rev = bswap_32(rev); // linux,revision appears to be in big endian #endif for (i = 0; i < (sizeof(rpi_hw_info) / sizeof(rpi_hw_info[0])); i++) { uint32_t hwver = rpi_hw_info[i].hwver; if (rev == hwver) { result = &rpi_hw_info[i]; goto done; } } #else FILE *f = fopen("/proc/cpuinfo", "r"); char line[LINE_WIDTH_MAX]; if (!f) { return NULL; } while (fgets(line, LINE_WIDTH_MAX - 1, f)) { if (strstr(line, HW_VER_STRING)) { char *substr; substr = strstr(line, ": "); if (!substr) { continue; } errno = 0; rev = strtoul(&substr[1], NULL, 16); // Base 16 if (errno) { continue; } for (i = 0; i < (sizeof(rpi_hw_info) / sizeof(rpi_hw_info[0])); i++) { uint32_t hwver = rpi_hw_info[i].hwver; // Take out warranty and manufacturer bits hwver &= ~(RPI_WARRANTY_MASK | RPI_MANUFACTURER_MASK); rev &= ~(RPI_WARRANTY_MASK | RPI_MANUFACTURER_MASK); if (rev == hwver) { result = &rpi_hw_info[i]; goto done; } } } } #endif done: fclose(f); return result; }