Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2018 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <led_strip.h>

#include <string.h>

#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LED_STRIP_LEVEL
#include <logging/sys_log.h>

#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) {
		SYS_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)
{
	SYS_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) {
		SYS_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);