2018-02-20 21:10:54 +08:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018 Intel Corporation
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <led_strip.h>
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
2018-10-09 06:23:11 +08:00
|
|
|
#define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
|
|
|
|
#include <logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(ws2812b_sw);
|
2018-02-20 21:10:54 +08:00
|
|
|
|
|
|
|
#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) {
|
2018-10-09 06:23:11 +08:00
|
|
|
LOG_ERR("Unable to get HF clock");
|
2018-02-20 21:10:54 +08:00
|
|
|
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)
|
|
|
|
{
|
2018-10-09 06:23:11 +08:00
|
|
|
LOG_ERR("update_channels not implemented");
|
2018-02-20 21:10:54 +08:00
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ws2812b_sw_init(struct device *dev)
|
|
|
|
{
|
|
|
|
struct device *gpio;
|
|
|
|
|
|
|
|
gpio = device_get_binding(CONFIG_WS2812B_SW_GPIO_NAME);
|
|
|
|
if (!gpio) {
|
2018-10-09 06:23:11 +08:00
|
|
|
LOG_ERR("Unable to find %s", CONFIG_WS2812B_SW_GPIO_NAME);
|
2018-02-20 21:10:54 +08:00
|
|
|
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);
|