zephyr/drivers/led_strip/ws2812b_sw.c

150 lines
3.2 KiB
C

/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <led_strip.h>
#include <string.h>
#define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(ws2812b_sw);
#include <zephyr.h>
#include <board.h>
#include <gpio.h>
#include <device.h>
#include <clock_control.h>
#define BLOCKING ((void *)1)
static int send_buf(u8_t *buf, size_t len)
{
/* Address of OUTSET. OUTCLR is OUTSET + 4 */
volatile u32_t *base = (u32_t *)(NRF_GPIO_BASE + 0x508);
u32_t pin = BIT(CONFIG_WS2812B_SW_GPIO_PIN);
struct device *clock;
unsigned int key;
/* Initilization of i is strictly not needed, but it avoids an
* uninitialized warning with the inline assembly.
*/
u32_t i = 0;
clock = device_get_binding(CONFIG_CLOCK_CONTROL_NRF5_M16SRC_DRV_NAME);
if (!clock) {
LOG_ERR("Unable to get HF clock");
return -EIO;
}
/* The inline assembly further below is designed to work only with
* the 16 MHz clock enabled.
*/
clock_control_on(clock, BLOCKING);
key = irq_lock();
while (len--) {
u32_t b = *buf++;
/* Generate signal out of the bits, MSB. 1-bit should be
* roughly 0.85us high, 0.4us low, whereas a 0-bit should be
* roughly 0.4us high, 0.85us low.
*/
__asm volatile ("movs %[i], #8\n" /* i = 8 */
".start_bit:\n"
/* OUTSET = BIT(LED_PIN) */
"strb %[p], [%[r], #0]\n"
/* if (b & 0x80) goto .long */
"tst %[b], %[m]\n"
"bne .long\n"
/* 0-bit */
"nop\nnop\n"
/* OUTCLR = BIT(LED_PIN) */
"strb %[p], [%[r], #4]\n"
"nop\nnop\nnop\n"
"b .next_bit\n"
/* 1-bit */
".long:\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\n"
/* OUTCLR = BIT(LED_PIN) */
"strb %[p], [%[r], #4]\n"
".next_bit:\n"
/* b <<= 1 */
"lsl %[b], #1\n"
/* i-- */
"sub %[i], #1\n"
/* if (i > 0) goto .start_bit */
"bne .start_bit\n"
:
[i] "+r" (i)
:
[b] "l" (b),
[m] "l" (0x80),
[r] "l" (base),
[p] "r" (pin)
:);
}
irq_unlock(key);
clock_control_off(clock, NULL);
return 0;
}
static int ws2812b_sw_update_rgb(struct device *dev, struct led_rgb *pixels,
size_t num_pixels)
{
u8_t *ptr = (u8_t *)pixels;
size_t i;
/* Convert from RGB to GRB format */
for (i = 0; i < num_pixels; i++) {
u8_t r = pixels[i].r;
u8_t b = pixels[i].b;
u8_t g = pixels[i].g;
*ptr++ = g;
*ptr++ = r;
*ptr++ = b;
}
return send_buf((u8_t *)pixels, num_pixels * 3);
}
static int ws2812b_sw_update_channels(struct device *dev, u8_t *channels,
size_t num_channels)
{
LOG_ERR("update_channels not implemented");
return -ENOSYS;
}
static int ws2812b_sw_init(struct device *dev)
{
struct device *gpio;
gpio = device_get_binding(CONFIG_WS2812B_SW_GPIO_NAME);
if (!gpio) {
LOG_ERR("Unable to find %s", CONFIG_WS2812B_SW_GPIO_NAME);
return -ENODEV;
}
gpio_pin_configure(gpio, CONFIG_WS2812B_SW_GPIO_PIN, GPIO_DIR_OUT);
return 0;
}
static const struct led_strip_driver_api ws2812b_sw_api = {
.update_rgb = ws2812b_sw_update_rgb,
.update_channels = ws2812b_sw_update_channels,
};
DEVICE_AND_API_INIT(ws2812b_sw, CONFIG_WS2812B_SW_NAME, ws2812b_sw_init, NULL,
NULL, POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY,
&ws2812b_sw_api);