diff --git a/Controllers/RaspberryPiController/RGBController_RaspberryPi_Linux.cpp b/Controllers/RaspberryPiController/RGBController_RaspberryPi_Linux.cpp new file mode 100644 index 000000000..5cca3f17a --- /dev/null +++ b/Controllers/RaspberryPiController/RGBController_RaspberryPi_Linux.cpp @@ -0,0 +1,138 @@ +/*---------------------------------------------------------*\ +| RGBController_RaspberryPi_Linux.cpp | +| | +| RGBController for Raspberry Pi GPIO ARGB | +| | +| Adam Honse (CalcProgrammer1) 21 Jul 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include "RGBController_RaspberryPi_Linux.h" + +/**------------------------------------------------------------------*\ + @name Raspberry Pi + @category Motherboard + @type GPIO + @save :x: + @direct :white_check_mark: + @effects :x: + @detectors DetectRaspberryPiControllers + @comment +\*-------------------------------------------------------------------*/ + +RGBController_RaspberryPi::RGBController_RaspberryPi(RaspberryPiController* controller_ptr) +{ + controller = controller_ptr; + + name = "Raspberry Pi"; + type = DEVICE_TYPE_MOTHERBOARD; + vendor = "Raspberry Pi"; + description = "Raspberry Pi GPIO Device"; + location = controller->GetDeviceLocation(); + + mode Direct; + Direct.name = "Direct"; + Direct.value = 0; + Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR; + Direct.color_mode = MODE_COLORS_PER_LED; + modes.push_back(Direct); + + SetupZones(); +} + +void RGBController_RaspberryPi::SetupZones() +{ + /*-------------------------------------------------*\ + | Only set LED count on the first run | + \*-------------------------------------------------*/ + bool first_run = false; + + if(zones.size() == 0) + { + first_run = true; + } + + /*-------------------------------------------------*\ + | Clear any existing color/LED configuration | + \*-------------------------------------------------*/ + leds.clear(); + colors.clear(); + zones.resize(1); + + /*-------------------------------------------------*\ + | Set zones and leds | + \*-------------------------------------------------*/ + for (unsigned int channel_idx = 0; channel_idx < 1; channel_idx++) + { + char ch_idx_string[2]; + snprintf(ch_idx_string, 2, "%d", channel_idx + 1); + + zones[channel_idx].name = "Channel "; + zones[channel_idx].name.append(ch_idx_string); + zones[channel_idx].type = ZONE_TYPE_LINEAR; + zones[channel_idx].leds_min = 0; + zones[channel_idx].leds_max = 1000; + + if(first_run) + { + zones[channel_idx].leds_count = 0; + } + + zones[channel_idx].matrix_map = NULL; + + for (unsigned int led_ch_idx = 0; led_ch_idx < zones[channel_idx].leds_count; led_ch_idx++) + { + char led_idx_string[4]; + snprintf(led_idx_string, 4, "%d", led_ch_idx + 1); + + led new_led; + new_led.name = "Channel "; + new_led.name.append(ch_idx_string); + new_led.name.append(", LED "); + new_led.name.append(led_idx_string); + + leds.push_back(new_led); + } + } + + controller->SetSize(zones[0].leds_count); + + SetupColors(); +} + +void RGBController_RaspberryPi::ResizeZone(int zone, int new_size) +{ + if((size_t) zone >= zones.size()) + { + return; + } + + if(((unsigned int)new_size >= zones[zone].leds_min) && ((unsigned int)new_size <= zones[zone].leds_max)) + { + zones[zone].leds_count = new_size; + + SetupZones(); + } +} + +void RGBController_RaspberryPi::DeviceUpdateLEDs() +{ + DeviceUpdateMode(); +} + +void RGBController_RaspberryPi::UpdateZoneLEDs(int /*zone*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_RaspberryPi::UpdateSingleLED(int /*led*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_RaspberryPi::DeviceUpdateMode() +{ + controller->SetColors(&colors[0]); +} diff --git a/Controllers/RaspberryPiController/RGBController_RaspberryPi_Linux.h b/Controllers/RaspberryPiController/RGBController_RaspberryPi_Linux.h new file mode 100644 index 000000000..a8a397d35 --- /dev/null +++ b/Controllers/RaspberryPiController/RGBController_RaspberryPi_Linux.h @@ -0,0 +1,33 @@ +/*---------------------------------------------------------*\ +| RGBController_RaspberryPi_Linux.h | +| | +| RGBController for Raspberry Pi GPIO ARGB | +| | +| Adam Honse (CalcProgrammer1) 21 Jul 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include "RaspberryPiController_Linux.h" +#include "RGBController.h" + +class RGBController_RaspberryPi : public RGBController +{ +public: + RGBController_RaspberryPi(RaspberryPiController* controller_ptr); + + void SetupZones(); + void ResizeZone(int zone, int new_size); + + void DeviceUpdateLEDs(); + void UpdateZoneLEDs(int zone); + void UpdateSingleLED(int led); + + void DeviceUpdateMode(); + +private: + RaspberryPiController* controller; +}; diff --git a/Controllers/RaspberryPiController/RaspberryPiControllerDetect_Linux.cpp b/Controllers/RaspberryPiController/RaspberryPiControllerDetect_Linux.cpp new file mode 100644 index 000000000..5d8fd97db --- /dev/null +++ b/Controllers/RaspberryPiController/RaspberryPiControllerDetect_Linux.cpp @@ -0,0 +1,36 @@ +/*---------------------------------------------------------*\ +| RaspberryPiControllerDetect_Linux.cpp | +| | +| Detector for Raspberry Pi GPIO ARGB | +| | +| Adam Honse (CalcProgrammer1) 21 Jul 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include +#include "Detector.h" +#include "RaspberryPiController_Linux.h" +#include "RGBController_RaspberryPi_Linux.h" + +/******************************************************************************************\ + * * + * DetectRaspberryPiControllers * + * * + * Detect Raspberry Pi controllers * + * * +\******************************************************************************************/ + +void DetectRaspberryPiControllers() +{ + RaspberryPiController* controller = new RaspberryPiController(); + + if(controller->Initialize()) + { + RGBController_RaspberryPi* rgb_controller = new RGBController_RaspberryPi(controller); + ResourceManager::get()->RegisterRGBController(rgb_controller); + } +} + +REGISTER_DETECTOR("Raspberry Pi", DetectRaspberryPiControllers); diff --git a/Controllers/RaspberryPiController/RaspberryPiController_Linux.cpp b/Controllers/RaspberryPiController/RaspberryPiController_Linux.cpp new file mode 100644 index 000000000..ca70cd8c8 --- /dev/null +++ b/Controllers/RaspberryPiController/RaspberryPiController_Linux.cpp @@ -0,0 +1,73 @@ +/*---------------------------------------------------------*\ +| RaspberryPiController_Linux.cpp | +| | +| Driver for Raspberry Pi GPIO ARGB | +| | +| Adam Honse (CalcProgrammer1) 21 Jul 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include "LogManager.h" +#include "RaspberryPiController_Linux.h" + +RaspberryPiController::RaspberryPiController() +{ + memset(&rpi_ws2811, 0x00, sizeof(rpi_ws2811)); + + rpi_ws2811.freq = WS2811_TARGET_FREQ; + rpi_ws2811.dmanum = RPI_WS2811_DMA; + rpi_ws2811.render_wait_time = 0; + + rpi_ws2811.channel[0].gpionum = RPI_WS2811_GPIO; + rpi_ws2811.channel[0].invert = 0; + rpi_ws2811.channel[0].count = 0; + rpi_ws2811.channel[0].strip_type = WS2811_STRIP_GBR; + rpi_ws2811.channel[0].brightness = 255; + + rpi_ws2811.channel[1].gpionum = 0; + rpi_ws2811.channel[1].invert = 0; + rpi_ws2811.channel[1].count = 0; + rpi_ws2811.channel[1].strip_type = 0; + rpi_ws2811.channel[1].brightness = 0; +} + +RaspberryPiController::~RaspberryPiController() +{ + ws2811_fini(&rpi_ws2811); +} + +std::string RaspberryPiController::GetDeviceLocation() +{ + return("GPIO: 13, DMA: 10"); +} + +bool RaspberryPiController::Initialize() +{ + if(ws2811_init(&rpi_ws2811) != WS2811_SUCCESS) + { + LOG_ERROR( "RaspberryPi: ws2811_init() failure"); + return(false); + } + + return(true); +} + +void RaspberryPiController::SetColors(RGBColor * colors) +{ + for(int i = 0; i < 2; i++) + { + rpi_ws2811.channel[0].leds[i] = colors[i]; + } + + ws2811_render(&rpi_ws2811); +} + +void RaspberryPiController::SetSize(unsigned int new_size) +{ + rpi_ws2811.channel[0].count = new_size; + + ws2811_fini(&rpi_ws2811); + Initialize(); +} diff --git a/Controllers/RaspberryPiController/RaspberryPiController_Linux.h b/Controllers/RaspberryPiController/RaspberryPiController_Linux.h new file mode 100644 index 000000000..d5d4af111 --- /dev/null +++ b/Controllers/RaspberryPiController/RaspberryPiController_Linux.h @@ -0,0 +1,35 @@ +/*---------------------------------------------------------*\ +| RaspberryPiController_Linux.h | +| | +| Driver for Raspberry Pi GPIO ARGB | +| | +| Adam Honse (CalcProgrammer1) 21 Jul 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include "RGBController.h" +#include "ws2811.h" + +#define RPI_WS2811_DMA 10 +#define RPI_WS2811_GPIO 18 + +class RaspberryPiController +{ +public: + RaspberryPiController(); + ~RaspberryPiController(); + + std::string GetDeviceLocation(); + + bool Initialize(); + void SetColors(RGBColor * colors); + void SetSize(unsigned int new_size); + +private: + ws2811_t rpi_ws2811; +}; diff --git a/OpenRGB.pro b/OpenRGB.pro index b54cab391..c12d68f12 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -468,6 +468,9 @@ contains(QMAKE_PLATFORM, linux) { i2c_smbus/i2c_smbus_linux.h \ AutoStart/AutoStart-Linux.h \ + INCLUDEPATH += \ + dependencies/rpi_ws281x/ \ + LIBS += \ -lmbedx509 \ -lmbedtls \ @@ -508,6 +511,12 @@ contains(QMAKE_PLATFORM, linux) { SOURCES += \ dependencies/hueplusplus-1.1.0/src/LinHttpHandler.cpp \ + dependencies/rpi_ws281x/dma.c \ + dependencies/rpi_ws281x/mailbox.c \ + dependencies/rpi_ws281x/pcm.c \ + dependencies/rpi_ws281x/pwm.c \ + dependencies/rpi_ws281x/rpihw.c \ + dependencies/rpi_ws281x/ws2811.c \ i2c_smbus/i2c_smbus_linux.cpp \ scsiapi/scsiapi_linux.c \ serial_port/find_usb_serial_port_linux.cpp \ diff --git a/dependencies/rpi_ws281x/.gitignore b/dependencies/rpi_ws281x/.gitignore new file mode 100644 index 000000000..409a42c40 --- /dev/null +++ b/dependencies/rpi_ws281x/.gitignore @@ -0,0 +1,13 @@ +.sconsign.dblite +version.h +*.pyc +test +newtest +*.o +*.a +*.swp + +build/ +release/ +debug/ +settings.json \ No newline at end of file diff --git a/dependencies/rpi_ws281x/CMakeLists.txt b/dependencies/rpi_ws281x/CMakeLists.txt new file mode 100644 index 000000000..ba5dd9035 --- /dev/null +++ b/dependencies/rpi_ws281x/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_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() diff --git a/dependencies/rpi_ws281x/DEBIAN/control b/dependencies/rpi_ws281x/DEBIAN/control new file mode 100644 index 000000000..ab028912f --- /dev/null +++ b/dependencies/rpi_ws281x/DEBIAN/control @@ -0,0 +1,9 @@ +package: libws2811 +Version: 1.1.0-1 +Section: base +Priority: optional +Architecture: armhf +Depends: +Maintainer: Jeremy Garff (jer@jers.net) +Description: Raspberry Pi WS281X Library + LED Control Library for the Raspberry Pi. diff --git a/dependencies/rpi_ws281x/DEBIAN/postinst b/dependencies/rpi_ws281x/DEBIAN/postinst new file mode 100644 index 000000000..70ad83987 --- /dev/null +++ b/dependencies/rpi_ws281x/DEBIAN/postinst @@ -0,0 +1,45 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +# source debconf library +. /usr/share/debconf/confmodule + +# Source dbconfig-common functions +if [ -f /usr/share/dbconfig-common/dpkg/postinst.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/postinst.pgsql +fi + +case "$1" in + + configure) + ldconfig + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + exit 0 + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; + +esac + +db_stop + +exit 0 +~ diff --git a/dependencies/rpi_ws281x/DEBIAN/postrm b/dependencies/rpi_ws281x/DEBIAN/postrm new file mode 100644 index 000000000..689222c5f --- /dev/null +++ b/dependencies/rpi_ws281x/DEBIAN/postrm @@ -0,0 +1,45 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +# source debconf library +. /usr/share/debconf/confmodule + +# Source dbconfig-common functions +if [ -f /usr/share/dbconfig-common/dpkg/postinst.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/postinst.pgsql +fi + +case "$1" in + + remove) + ldconfig + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + exit 0 + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; + +esac + +db_stop + +exit 0 +~ diff --git a/dependencies/rpi_ws281x/DEBIAN/prerm b/dependencies/rpi_ws281x/DEBIAN/prerm new file mode 100644 index 000000000..b90c5b069 --- /dev/null +++ b/dependencies/rpi_ws281x/DEBIAN/prerm @@ -0,0 +1,44 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +# source debconf library +. /usr/share/debconf/confmodule + +# Source dbconfig-common functions +if [ -f /usr/share/dbconfig-common/dpkg/postinst.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/postinst.pgsql +fi + +case "$1" in + + remove|upgrade|deconfigure) + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + exit 0 + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; + +esac + +db_stop + +exit 0 + diff --git a/dependencies/rpi_ws281x/LICENSE b/dependencies/rpi_ws281x/LICENSE new file mode 100644 index 000000000..2d1979be2 --- /dev/null +++ b/dependencies/rpi_ws281x/LICENSE @@ -0,0 +1,24 @@ +Copyright (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. + diff --git a/dependencies/rpi_ws281x/README.md b/dependencies/rpi_ws281x/README.md new file mode 100644 index 000000000..e878ae7e3 --- /dev/null +++ b/dependencies/rpi_ws281x/README.md @@ -0,0 +1,235 @@ +rpi_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. diff --git a/dependencies/rpi_ws281x/SConscript b/dependencies/rpi_ws281x/SConscript new file mode 100644 index 000000000..39540a1d9 --- /dev/null +++ b/dependencies/rpi_ws281x/SConscript @@ -0,0 +1,95 @@ +# +# 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) + diff --git a/dependencies/rpi_ws281x/SConstruct b/dependencies/rpi_ws281x/SConstruct new file mode 100644 index 000000000..543a82433 --- /dev/null +++ b/dependencies/rpi_ws281x/SConstruct @@ -0,0 +1,77 @@ +# +# 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'); + diff --git a/dependencies/rpi_ws281x/clk.h b/dependencies/rpi_ws281x/clk.h new file mode 100644 index 000000000..6673d4261 --- /dev/null +++ b/dependencies/rpi_ws281x/clk.h @@ -0,0 +1,66 @@ +/* + * 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__ */ diff --git a/dependencies/rpi_ws281x/dma.c b/dependencies/rpi_ws281x/dma.c new file mode 100644 index 000000000..1926bc397 --- /dev/null +++ b/dependencies/rpi_ws281x/dma.c @@ -0,0 +1,78 @@ +/* + * 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]; +} + + diff --git a/dependencies/rpi_ws281x/dma.h b/dependencies/rpi_ws281x/dma.h new file mode 100644 index 000000000..661a55b33 --- /dev/null +++ b/dependencies/rpi_ws281x/dma.h @@ -0,0 +1,127 @@ +/* + * 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__ */ diff --git a/dependencies/rpi_ws281x/gpio.h b/dependencies/rpi_ws281x/gpio.h new file mode 100644 index 000000000..6ed3e37e4 --- /dev/null +++ b/dependencies/rpi_ws281x/gpio.h @@ -0,0 +1,109 @@ +/* + * 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__ */ diff --git a/dependencies/rpi_ws281x/linux.py b/dependencies/rpi_ws281x/linux.py new file mode 100644 index 000000000..ee4c148f2 --- /dev/null +++ b/dependencies/rpi_ws281x/linux.py @@ -0,0 +1,84 @@ +# +# 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)] + diff --git a/dependencies/rpi_ws281x/mailbox.c b/dependencies/rpi_ws281x/mailbox.c new file mode 100644 index 000000000..a9bf802a2 --- /dev/null +++ b/dependencies/rpi_ws281x/mailbox.c @@ -0,0 +1,292 @@ +/* +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); +} diff --git a/dependencies/rpi_ws281x/mailbox.h b/dependencies/rpi_ws281x/mailbox.h new file mode 100644 index 000000000..e5b5563be --- /dev/null +++ b/dependencies/rpi_ws281x/mailbox.h @@ -0,0 +1,49 @@ +/* +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); diff --git a/dependencies/rpi_ws281x/main.c b/dependencies/rpi_ws281x/main.c new file mode 100644 index 000000000..f032d4da6 --- /dev/null +++ b/dependencies/rpi_ws281x/main.c @@ -0,0 +1,419 @@ +/* + * 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, + .invert = 0, + .count = LED_COUNT, + .strip_type = STRIP_TYPE, + .brightness = 255, + }, + [1] = + { + .gpionum = 0, + .invert = 0, + .count = 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; +} diff --git a/dependencies/rpi_ws281x/pcm.c b/dependencies/rpi_ws281x/pcm.c new file mode 100644 index 000000000..51dac351a --- /dev/null +++ b/dependencies/rpi_ws281x/pcm.c @@ -0,0 +1,127 @@ +/* + * 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; +} + diff --git a/dependencies/rpi_ws281x/pcm.h b/dependencies/rpi_ws281x/pcm.h new file mode 100644 index 000000000..1ff425b7e --- /dev/null +++ b/dependencies/rpi_ws281x/pcm.h @@ -0,0 +1,159 @@ +/* + * 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__ */ diff --git a/dependencies/rpi_ws281x/pkg-config.pc.in b/dependencies/rpi_ws281x/pkg-config.pc.in new file mode 100644 index 000000000..9050ec989 --- /dev/null +++ b/dependencies/rpi_ws281x/pkg-config.pc.in @@ -0,0 +1,9 @@ +libdir=@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} diff --git a/dependencies/rpi_ws281x/pwm.c b/dependencies/rpi_ws281x/pwm.c new file mode 100644 index 000000000..aaea7f4e4 --- /dev/null +++ b/dependencies/rpi_ws281x/pwm.c @@ -0,0 +1,104 @@ +/* + * 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; +} + diff --git a/dependencies/rpi_ws281x/pwm.h b/dependencies/rpi_ws281x/pwm.h new file mode 100644 index 000000000..0fa0bd1c8 --- /dev/null +++ b/dependencies/rpi_ws281x/pwm.h @@ -0,0 +1,123 @@ +/* + * 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__ */ diff --git a/dependencies/rpi_ws281x/rpihw.c b/dependencies/rpi_ws281x/rpihw.c new file mode 100644 index 000000000..58147c988 --- /dev/null +++ b/dependencies/rpi_ws281x/rpihw.c @@ -0,0 +1,637 @@ +/* + * 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 = 0xa03115, + .type = RPI_HWVER_TYPE_PI4, + .periph_base = PERIPH_BASE_RPI4, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Pi 4 Model B - 1GB v1.5" + }, + { + .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" + }, + { + .hwver = 0xa03141, + .type = RPI_HWVER_TYPE_PI4, + .periph_base = PERIPH_BASE_RPI4, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Compute Module 4 Rev 1.1" + }, + { + .hwver = 0xb03141, + .type = RPI_HWVER_TYPE_PI4, + .periph_base = PERIPH_BASE_RPI4, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Compute Module 4 Rev 1.1" + }, + { + .hwver = 0xc03141, + .type = RPI_HWVER_TYPE_PI4, + .periph_base = PERIPH_BASE_RPI4, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Compute Module 4 Rev 1.1" + }, + { + .hwver = 0xd03141, + .type = RPI_HWVER_TYPE_PI4, + .periph_base = PERIPH_BASE_RPI4, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Compute Module 4 Rev 1.1" + }, + + // + // 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", + }, + { + .hwver = 0xa020a0, + .type = RPI_HWVER_TYPE_PI2, + .periph_base = PERIPH_BASE_RPI2, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Compute Module 3/L3", + }, + { + .hwver = 0xa02100, + .type = RPI_HWVER_TYPE_PI2, + .periph_base = PERIPH_BASE_RPI2, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Compute Module 3+", + }, + { + .hwver = 0xa220a0, + .type = RPI_HWVER_TYPE_PI2, + .periph_base = PERIPH_BASE_RPI2, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Compute Module 3 Rev 1.0", + }, + + // + // 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 = 0xa020d4, + .type = RPI_HWVER_TYPE_PI2, + .periph_base = PERIPH_BASE_RPI2, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Pi 3 B+", + }, + { + .hwver = 0xa020d3, + .type = RPI_HWVER_TYPE_PI2, + .periph_base = PERIPH_BASE_RPI2, + .videocore_base = VIDEOCORE_BASE_RPI2, + .desc = "Pi 3 B+", + }, + { + .hwver = 0xa32082, + .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+", + } +}; + + +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; +} diff --git a/dependencies/rpi_ws281x/rpihw.h b/dependencies/rpi_ws281x/rpihw.h new file mode 100644 index 000000000..906ca5a67 --- /dev/null +++ b/dependencies/rpi_ws281x/rpihw.h @@ -0,0 +1,52 @@ +/* + * 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__ */ diff --git a/dependencies/rpi_ws281x/version.h.in b/dependencies/rpi_ws281x/version.h.in new file mode 100644 index 000000000..311699812 --- /dev/null +++ b/dependencies/rpi_ws281x/version.h.in @@ -0,0 +1,10 @@ +/* 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@__ */ \ No newline at end of file diff --git a/dependencies/rpi_ws281x/version.py b/dependencies/rpi_ws281x/version.py new file mode 100644 index 000000000..4ddd3b312 --- /dev/null +++ b/dependencies/rpi_ws281x/version.py @@ -0,0 +1,71 @@ +# +# 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)] + + diff --git a/dependencies/rpi_ws281x/ws2811.c b/dependencies/rpi_ws281x/ws2811.c new file mode 100644 index 000000000..e51fe9545 --- /dev/null +++ b/dependencies/rpi_ws281x/ws2811.c @@ -0,0 +1,1326 @@ +/* + * 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) + +// 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) +{ + static uint8_t convert_table[3][256] = + { + { + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, + 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, + 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, + 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, + 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, + 0xD3, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, + 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, + 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB + }, + { + 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, + 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, + 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, + 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, + 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, + 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, + 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, + 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, + 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D + }, + { + 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, + 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, + 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, + 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, + 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, + 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, + 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, + 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, + 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, + 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, + 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, + 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, + 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, + 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, + 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, + 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, + 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, + 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, + 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, + 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6 + } + }; + + volatile uint8_t *pxl_raw = ws2811->device->pxl_raw; + int driver_mode = ws2811->device->driver_mode; + int i, l, chan; + unsigned j; + ws2811_return_t ret = WS2811_SUCCESS; + uint32_t protocol_time = 0; + static uint64_t previous_timestamp = 0; + + 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(l = 0; l < 3; ++l) + { + uint8_t pos = driver_mode == SPI ? bytepos : 3 - bytepos; + uint8_t val = convert_table[l][color[j]]; + if ((driver_mode != PWM) && channel->invert) val = ~val; + + pxl_raw[wordpos * 4 + pos] = val; + if(++bytepos == 4) + { + bytepos = 0; + wordpos += driver_mode == PWM ? 2 : 1; + } + } + } + } + } + + // 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; + + } + } + + } +} diff --git a/dependencies/rpi_ws281x/ws2811.h b/dependencies/rpi_ws281x/ws2811.h new file mode 100644 index 000000000..148fe7a13 --- /dev/null +++ b/dependencies/rpi_ws281x/ws2811.h @@ -0,0 +1,133 @@ +/* + * 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__ */