diff --git a/arch/xtensa/src/common/espressif/esp_ws2812.c b/arch/xtensa/src/common/espressif/esp_ws2812.c new file mode 100644 index 0000000000..7604b9aab7 --- /dev/null +++ b/arch/xtensa/src/common/espressif/esp_ws2812.c @@ -0,0 +1,614 @@ +/**************************************************************************** + * arch/xtensa/src/common/espressif/esp_ws2812.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "hal/rmt_types.h" +#include "soc/soc.h" + +#include "esp_rmt.h" + +#include "esp_ws2812.h" + +#ifdef CONFIG_WS2812 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define APB_PERIOD_PS (1000000000000 / APB_CLK_FREQ) +#define RMT_PERIOD_PS (APB_PERIOD_PS / RMT_DEFAULT_CLK_DIV) // ps + +#define T0H ((uint16_t)(350000 / RMT_PERIOD_PS)) // cyles +#define T0L ((uint16_t)(900000 / RMT_PERIOD_PS)) // cyles +#define T1H ((uint16_t)(900000 / RMT_PERIOD_PS)) // cyles +#define T1L ((uint16_t)(350000 / RMT_PERIOD_PS)) // cyles +#define RES ((uint16_t)(60000000 / RMT_PERIOD_PS)) // cyles + +#define rmt_item32_t rmt_symbol_word_t + +/**************************************************************************** + * Private Type + ****************************************************************************/ + +struct rgbw_led_s +{ + union + { + struct + { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t w; + }; + uint32_t val; + }; +}; + +struct esp_ws2812_dev_s +{ + struct rmt_dev_s *rmt; + uint8_t *buf; + size_t buflen; + size_t open_count; /* Number of opens on this instance. */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static uint32_t map_byte_to_words(uint8_t byte, uint32_t *dst); +static int map_leds_to_words(struct rgbw_led_s *leds, + uint32_t n_leds, + uint32_t *dst, + bool has_white); +static int esp_open(struct file *filep); +static int esp_close(struct file *filep); +static int esp_write(struct file *filep, const char *data, size_t len); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: map_byte_to_words + * + * Description: + * Maps a byte to a sequence of RMT items. Each bit in the byte is + * represented by an RMT item (32-bit value). Iterates over each bit in + * the byte, creating an RMT item for each bit, set or not. The created + * RMT items are stored in the destination array. + * + * Input Parameters: + * byte - The byte to be mapped. + * dst - Destination array for the RMT items. + * + * Returned Value: + * Number of RMT items mapped. + * + ****************************************************************************/ + +static uint32_t map_byte_to_words(uint8_t byte, uint32_t *dst) +{ + uint32_t mapped; + uint8_t mask; + + mapped = 0; + mask = 0x80; + for (int i = 0; i < 8; i++) + { + uint32_t word; + uint8_t bit = (byte & mask); + + mask >>= 1; + + if (bit) + { + word = (T1L << 16) | (0x8000 | T1H); + } + else + { + word = (T0L << 16) | (0x8000 | T0H); + } + + *dst = word; + dst++; + mapped++; + } + + return mapped; +} + +/**************************************************************************** + * Name: map_leds_to_words + * + * Description: + * Maps an array of LEDs to a sequence of RMT items. Each LED in the array + * is represented by a sequence of RMT items, one for each bit in the RGB + * (and optionally white) values. Iterates over each LED in the array, + * mapping the RGB (and optionally white) values to RMT items using the + * map_byte_to_words function. The RMT items are stored in the destination + * array. + * + * Input Parameters: + * leds - Pointer to the array of LEDs. + * n_leds - Number of LEDs in the array. + * dst - Destination array for the RMT items. + * has_white - Flag indicating if the LEDs include a white component. + * + * Returned Value: + * Number of RMT items mapped; a negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int map_leds_to_words(struct rgbw_led_s *leds, + uint32_t n_leds, + uint32_t *dst, + bool has_white) +{ + uint32_t dst_offset; + + if (!dst || !leds) + { + return -EINVAL; + } + + dst_offset = 0; + for (uint32_t led_idx = 0; led_idx < n_leds; led_idx++) + { + dst_offset += map_byte_to_words(leds[led_idx].g, dst + dst_offset); + dst_offset += map_byte_to_words(leds[led_idx].r, dst + dst_offset); + dst_offset += map_byte_to_words(leds[led_idx].b, dst + dst_offset); + if (has_white) + { + dst_offset += map_byte_to_words(leds[led_idx].w, dst + dst_offset); + } + } + + return dst_offset; +} + +/**************************************************************************** + * Name: esp_open + * + * Description: + * This function opens a WS2812 device instance. It locks the device, + * checks if the device has already been initialized, and if not, it + * allocates and initializes the pixel buffer. It then increases the open + * count and unlocks the device. + * + * Input Parameters: + * filep - Pointer to the file structure. + * + * Returned Value: + * Returns OK on successful open of the device; a negated errno value + * is returned on any failure. + * + ****************************************************************************/ + +static int esp_open(struct file *filep) +{ + struct inode *inode = filep->f_inode; + struct ws2812_dev_s *dev_data = inode->i_private; + struct esp_ws2812_dev_s *priv; + uint8_t colors; + irqstate_t flags; + size_t buflen; + int i; + int ret; + + priv = (struct esp_ws2812_dev_s *)dev_data->private; + + flags = enter_critical_section(); + + if (priv->buf != NULL) + { + /* We've already been initialized. Keep on truckin' */ + + ledinfo("esp_ws2812 re-open dev: 0x%p\n", dev_data); + + ret = OK; + goto post_and_return; + } + + ledinfo("esp_ws2812 open dev: 0x%p\n", dev_data); + + /* Allocate the pixel buffer */ + + if (priv->open_count == 0) + { + struct rgbw_led_s *led; + + /* Number of colors of each LED */ + + colors = (dev_data->has_white ? 4 : 3); + + /* Each LED color is represented by 8 RMT items + 1 last item. Each RMT + * item is 32-bit long. + */ + + buflen = (dev_data->nleds * colors * 8 + 1) * sizeof(rmt_item32_t); + + priv->buf = kmm_zalloc(buflen); + + if (priv->buf == NULL) + { + lederr("esp_ws2812 open: out of memory\n"); + + ret = -ENOMEM; + goto post_and_return; + } + + priv->buflen = buflen; + + /* Clear all LEDs in the LED strip */ + + led = kmm_zalloc(sizeof(struct rgbw_led_s)); + + if (led == NULL) + { + lederr("esp_ws2812 open: out of memory\n"); + + ret = -ENOMEM; + goto post_and_return; + } + + for (i = 0; i < dev_data->nleds; i++) + { + map_leds_to_words(led, 1, + ((uint32_t *)priv->buf + i * colors * 8), + dev_data->has_white); + } + + kmm_free(led); + } + else + { + ledwarn("esp_ws2812 open: already open\n"); + } + + priv->open_count += 1; + + ret = OK; + +post_and_return: + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: esp_close + * + * Description: + * This function closes a previously opened WS2812 device instance. It + * locks the device, decreases the open count, and if no other instances + * are open, it frees the buffer associated with the device. It then + * unlocks the device and returns OK. + * + * Input Parameters: + * filep - Pointer to the file structure. + * + * Returned Value: + * Returns OK on successful close of the device; a negated errno value + * is returned on any failure. + * + ****************************************************************************/ + +static int esp_close(struct file *filep) +{ + struct inode *inode = filep->f_inode; + struct ws2812_dev_s *dev_data = inode->i_private; + struct esp_ws2812_dev_s *priv; + + priv = (struct esp_ws2812_dev_s *)dev_data->private; + + nxmutex_lock(&dev_data->lock); + + ledinfo("esp_ws2812 close dev: 0x%p\n", dev_data); + + priv->open_count -= 1; + + if (priv->open_count == 0) + { + kmm_free(priv->buf); + priv->buf = NULL; + } + + nxmutex_unlock(&dev_data->lock); + return OK; +} + +/**************************************************************************** + * Name: esp_write + * + * Description: + * This function writes the LED data to the WS2812 device. It checks if the + * data and length are valid, locks the device, maps the LED data to the + * buffer, updates the file position, writes the buffer to the RMT device, + * and unlocks the device. It returns the number of LED pixels that had + * their values changed. + * + * Input Parameters: + * filep - Pointer to the file structure. + * data - Pointer to the LED data to be written. + * len - The length of the data to be written. + * + * Returned Value: + * Returns the number of LED pixels that had their values changed on + * successful write; a negated errno value is returned on any failure. + * + ****************************************************************************/ + +static ssize_t esp_write(struct file *filep, const char *data, size_t len) +{ + struct inode *inode = filep->f_inode; + struct ws2812_dev_s *dev = inode->i_private; + struct esp_ws2812_dev_s *priv = + (struct esp_ws2812_dev_s *)dev->private; + int position = filep->f_pos; + uint32_t n_leds = len / WS2812_RW_PIXEL_SIZE; + uint8_t colors = (dev->has_white ? 4 : 3); + uint8_t *bp = priv->buf + position; + int rmt_bytes; + int n_leds_written; + int ret; + + /* Check if LED data isn't NULL */ + + if (data == NULL) + { + lederr("esp_ws2812 write failed: NULL data\n"); + set_errno(EINVAL); + return 0; + } + + /* Check if the number of LEDs to be updated is valid */ + + if (n_leds > dev->nleds) + { + lederr("esp_ws2812 write failed: invalid len for the LEDs buffer\n"); + set_errno(EINVAL); + return 0; + } + + nxmutex_lock(&dev->lock); + + if (len > 0) + { + /* Check if the lenght to be updated, considering the current position, + * is valid. The number of LEDs to be updated should, starting from the + * current offset should be less than the LED strip total length. + */ + + if (((position + len) / WS2812_RW_PIXEL_SIZE) > dev->nleds) + { + ledwarn("esp_ws2812 write truncated:\n\t\tLED position: %d\n" + "\t\tLED requested to be written: %d\n" + "\t\tLED strip LED count: %d\n" + "\t\tLED being written: %d\n", + position / WS2812_RW_PIXEL_SIZE, + n_leds, + dev->nleds, + dev->nleds - (position / WS2812_RW_PIXEL_SIZE)); + n_leds = dev->nleds - (position / WS2812_RW_PIXEL_SIZE); + } + + ret = map_leds_to_words((struct rgbw_led_s *)data, + n_leds, + (uint32_t *)bp, + dev->has_white); + if (ret < 0) + { + lederr("esp_ws2812 write failed: %d\n", ret); + nxmutex_unlock(&dev->lock); + set_errno(-ret); + return ret; + } + + /* Update the file position: each LED color is represented by 8 RMT + * items. The position is, then, the number of LEDs to be update times + * the size of a LED color in bytes. + */ + + position += n_leds * WS2812_RW_PIXEL_SIZE; + + filep->f_pos = position; + } + + /* Write the buffer to the RMT device */ + + rmt_bytes = priv->rmt->ops->write(priv->rmt, + (const char *)priv->buf, + priv->buflen); + + /* n_leds_written is the number of LEDs that had their values changed: + * Each LED color is represented by 8 RMT items. We also added a last + * RMT item to the buffer, so we need to subtract 1 from the total number. + * Finally, we divide by the number of colors to get the number of LEDs. + */ + + n_leds_written = ((rmt_bytes / sizeof(rmt_item32_t)) - 1) / (colors * 8); + + /* Compare n_leds_written with the value representing the full LED strip */ + + if (n_leds_written < dev->nleds) + { + lederr("esp_ws2812 write failed: %d\n", n_leds_written); + nxmutex_unlock(&dev->lock); + set_errno(-EIO); + return -EIO; + } + + nxmutex_unlock(&dev->lock); + + /* Return the number of LEDs pixels that had their values changed */ + + return n_leds * WS2812_RW_PIXEL_SIZE; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp_ws2812_setup + * + * Description: + * This function sets up a WS2812 device instance. It allocates memory for + * the device structures, initializes the device with the provided + * parameters, and registers the device with the system. + * + * Input Parameters: + * path - The device path. + * rmt - Pointer to the RMT device structure. + * pixel_count - The number of pixels in the WS2812 strip. + * has_white - Flag indicating if the WS2812 strip includes a white LED. + * + * Returned Value: + * Returns a pointer to the WS2812 device structure on successful setup; + * NULL is returned on any failure, with errno set appropriately. + * + ****************************************************************************/ + +struct ws2812_dev_s *esp_ws2812_setup(const char *path, + struct rmt_dev_s *rmt, + uint16_t pixel_count, + bool has_white) +{ + struct ws2812_dev_s *dev; + struct esp_ws2812_dev_s *priv; + int err; + + /* Allocate struct holding generic WS2812 device data */ + + dev = kmm_zalloc(sizeof(struct ws2812_dev_s)); + + if (dev == NULL) + { + lederr("esp_ws2812 setup: out of memory\n"); + set_errno(ENOMEM); + return NULL; + } + + /* Allocate struct holding Espressif's WS2812 (RMT-enabled) device data */ + + priv = kmm_zalloc(sizeof(struct esp_ws2812_dev_s)); + + if (priv == NULL) + { + lederr("esp_ws2812 open: out of memory\n"); + kmm_free(dev); + set_errno(ENOMEM); + return NULL; + } + + priv->rmt = rmt; + + dev->open = esp_open; + dev->close = esp_close; + dev->write = esp_write; + dev->private = priv; + dev->clock = CONFIG_WS2812_FREQUENCY; + dev->port = priv->rmt->minor; + dev->nleds = pixel_count; + dev->has_white = has_white; + + nxmutex_init(&dev->lock); + + ledinfo("register dev: 0x%p\n", dev); + + /* Register the WS2812 RGB addressable LED strip device */ + + err = ws2812_register(path, dev); + + if (err != OK) + { + set_errno(err); + return NULL; + } + + return (void *)dev; +} + +/**************************************************************************** + * Name: esp_ws2812_release + * + * Description: + * This function releases a previously opened WS2812 device instance. It + * checks if the device is currently open, and if not, it frees the private + * data structure and sets the private field of the device to NULL. If the + * device is still open, it returns an error. + * + * Input Parameters: + * driver - Pointer to the instance of the WS2812 device driver to be + * released. + * + * Returned Value: + * Returns OK on successful release of the device; a negated errno value + * is returned on any failure. + * + ****************************************************************************/ + +int esp_ws2812_release(void * driver) +{ + struct ws2812_dev_s *dev = driver; + struct esp_ws2812_dev_s *priv; + int ret = OK; + + priv = (struct esp_ws2812_dev_s *)dev->private; + + nxmutex_lock(&dev->lock); + + if (priv->open_count == 0) + { + dev->private = NULL; + + nxmutex_unlock(&dev->lock); + + kmm_free(priv); + } + else + { + ret = -EBUSY; + nxmutex_unlock(&dev->lock); + } + + return ret; +} + +#endif /* CONFIG_WS2812 */ diff --git a/arch/xtensa/src/common/espressif/esp_ws2812.h b/arch/xtensa/src/common/espressif/esp_ws2812.h new file mode 100644 index 0000000000..e81c7a6f4a --- /dev/null +++ b/arch/xtensa/src/common/espressif/esp_ws2812.h @@ -0,0 +1,101 @@ +/**************************************************************************** + * arch/xtensa/src/common/espressif/esp_ws2812.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_COMMON_ESPRESSIF_ESP_WS2812_H +#define __ARCH_XTENSA_SRC_COMMON_ESPRESSIF_ESP_WS2812_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifndef __ASSEMBLY__ +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +#ifdef CONFIG_WS2812 + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp_ws2812_setup + * + * Description: + * This function sets up a WS2812 device instance. It allocates memory for + * the device structures, initializes the device with the provided + * parameters, and registers the device with the system. + * + * Input Parameters: + * path - The device path. + * rmt - Pointer to the RMT device structure. + * pixel_count - The number of pixels in the WS2812 strip. + * has_white - Flag indicating if the WS2812 strip includes a white LED. + * + * Returned Value: + * Returns a pointer to the WS2812 device structure on successful setup; + * NULL is returned on any failure, with errno set appropriately. + * + ****************************************************************************/ + +struct ws2812_dev_s *esp_ws2812_setup(const char *path, + struct rmt_dev_s *rmt, + uint16_t pixel_count, + bool has_white); + +/**************************************************************************** + * Name: esp_ws2812_release + * + * Description: + * This function releases a previously opened WS2812 device instance. It + * checks if the device is currently open, and if not, it frees the private + * data structure and sets the private field of the device to NULL. If the + * device is still open, it returns an error. + * + * Input Parameters: + * driver - Pointer to the instance of the WS2812 device driver to be + * released. + * + * Returned Value: + * Returns OK on successful release of the device; a negated errno value + * is returned on any failure. + * + ****************************************************************************/ + +int esp_ws2812_release(void * driver); + +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_XTENSA_SRC_COMMON_ESPRESSIF_ESP_WS2812_H */