/** * Initialize the PWM DMA buffer with all zeros for non-inverted operation, or * ones for inverted operation. 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 *pwm_raw = (uint32_t *)ws2811->device->pwm_raw; int maxcount = max_channel_led_count(ws2811); int wordcount = (PWM_BYTE_COUNT(maxcount, ws2811->freq) / sizeof(uint32_t)) / RPI_PWM_CHANNELS; int chan; for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) { ws2811_channel_t *channel = &ws2811->channel[chan]; int i, wordpos = chan; for (i = 0; i < wordcount; i++) { if (channel->invert) { pwm_raw[wordpos] = ~0L; } else { pwm_raw[wordpos] = 0x0; } wordpos += 2; } } }
/** * 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_pwm_t *cm_pwm = device->cm_pwm; int maxcount = max_channel_led_count(ws2811); uint32_t freq = ws2811->freq; int32_t byte_count; stop_pwm(ws2811); // Setup the PWM Clock - Use OSC @ 19.2Mhz w/ 3 clocks/tick cm_pwm->div = CM_PWM_DIV_PASSWD | CM_PWM_DIV_DIVI(OSC_FREQ / (3 * freq)); cm_pwm->ctl = CM_PWM_CTL_PASSWD | CM_PWM_CTL_SRC_OSC; cm_pwm->ctl = CM_PWM_CTL_PASSWD | CM_PWM_CTL_SRC_OSC | CM_PWM_CTL_ENAB; usleep(10); while (!(cm_pwm->ctl & CM_PWM_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; 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->pwm_raw); dma_cb->dest_ad = (uint32_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; }
/** * Allocate and initialize memory, buffers, pages, PWM, DMA, and GPIO. * * @param ws2811 ws2811 instance pointer. * * @returns 0 on success, -1 otherwise. */ int ws2811_init(ws2811_t *ws2811) { ws2811_device_t *device = NULL; int chan; // Zero mbox; non-zero values indicate action needed on cleanup memset(&mbox, 0, sizeof(mbox)); ws2811->device = malloc(sizeof(*ws2811->device)); if (!ws2811->device) { return -1; } device = ws2811->device; // Determine how much physical memory we need for DMA mbox.size = PWM_BYTE_COUNT(max_channel_led_count(ws2811), ws2811->freq) + + sizeof(dma_cb_t); // Round up to page size multiple mbox.size = (mbox.size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); // Use the mailbox interface to request memory from the VideoCore // We specifiy (-1) for the handle rather than calling mbox_open() // so multiple users can share the resource. mbox.handle = -1; // mbox_open(); mbox.mem_ref = mem_alloc(mbox.handle, mbox.size, PAGE_SIZE, board_info_sdram_address() == 0x40000000 ? 0xC : 0x4); if (mbox.mem_ref == (unsigned) ~0) { return -1; } mbox.bus_addr = mem_lock(mbox.handle, mbox.mem_ref); if (mbox.bus_addr == (unsigned) ~0) { mem_free(mbox.handle, mbox.size); return -1; } mbox.virt_addr = mapmem(BUS_TO_PHYS(mbox.bus_addr), mbox.size); // Initialize all pointers to NULL. Any non-NULL pointers will be freed on cleanup. device->pwm_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) { goto err; } memset(channel->leds, 0, sizeof(ws2811_led_t) * channel->count); } device->dma_cb = (dma_cb_t *)mbox.virt_addr; device->pwm_raw = (uint8_t *)mbox.virt_addr + sizeof(dma_cb_t); pwm_raw_init(ws2811); 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->dma_cb); // Map the physical registers into userspace if (map_registers(ws2811)) { goto err; } // Initialize the GPIO pins if (gpio_init(ws2811)) { unmap_registers(ws2811); goto err; } // Setup the PWM, clocks, and DMA if (setup_pwm(ws2811)) { unmap_registers(ws2811); goto err; } return 0; err: ws2811_cleanup(ws2811); return -1; }
/** * Allocate and initialize memory, buffers, pages, PWM, DMA, and GPIO. * * @param ws2811 ws2811 instance pointer. * * @returns 0 on success, -1 otherwise. */ int 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 -1; } rpi_hw = ws2811->rpi_hw; ws2811->device = malloc(sizeof(*ws2811->device)); if (!ws2811->device) { return -1; } device = ws2811->device; // Determine how much physical memory we need for DMA device->mbox.size = PWM_BYTE_COUNT(max_channel_led_count(ws2811), ws2811->freq) + sizeof(dma_cb_t); // 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 -1; } 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 -1; } 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 -1; } device->mbox.virt_addr = mapmem(BUS_TO_PHYS(device->mbox.bus_addr), device->mbox.size); // Initialize all pointers to NULL. Any non-NULL pointers will be freed on cleanup. device->pwm_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) { goto err; } memset(channel->leds, 0, sizeof(ws2811_led_t) * channel->count); if (!channel->strip_type) { channel->strip_type=WS2811_STRIP_RGB; } } device->dma_cb = (dma_cb_t *)device->mbox.virt_addr; device->pwm_raw = (uint8_t *)device->mbox.virt_addr + sizeof(dma_cb_t); pwm_raw_init(ws2811); 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)) { goto err; } // Initialize the GPIO pins if (gpio_init(ws2811)) { unmap_registers(ws2811); goto err; } // Setup the PWM, clocks, and DMA if (setup_pwm(ws2811)) { unmap_registers(ws2811); goto err; } return 0; err: ws2811_cleanup(ws2811); return -1; }