/* * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL #include LOG_MODULE_REGISTER(ws2812b_sw); #include #include #include #include #include #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 = 0U; clock = device_get_binding(DT_NORDIC_NRF_CLOCK_0_LABEL "_16M"); 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);