#include "led_strip.h" #ifdef USE_BK72XX #include "esphome/core/helpers.h" #include "esphome/core/log.h" extern "C" { #include "rtos_pub.h" #include "spi.h" #include "arm_arch.h" #include "general_dma_pub.h" #include "gpio_pub.h" #include "icu_pub.h" #undef SPI_DAT #undef SPI_BASE }; static const uint32_t SPI_TX_DMA_CHANNEL = GDMA_CHANNEL_3; // TODO: Check if SPI_PERI_CLK_DCO depends on the chip variant static const uint32_t SPI_PERI_CLK_26M = 26000000; static const uint32_t SPI_PERI_CLK_DCO = 120000000; static const uint32_t SPI_BASE = 0x00802700; static const uint32_t SPI_DAT = SPI_BASE + 3 * 4; static const uint32_t SPI_CONFIG = SPI_BASE + 1 * 4; static const uint32_t SPI_TX_EN = 1 << 0; static const uint32_t CTRL_NSSMD_3 = 1 << 17; static const uint32_t SPI_TX_FINISH_EN = 1 << 2; static const uint32_t SPI_RX_FINISH_EN = 1 << 3; namespace esphome { namespace beken_spi_led_strip { static const char *const TAG = "beken_spi_led_strip"; struct spi_data_t { SemaphoreHandle_t dma_tx_semaphore; volatile bool tx_in_progress; bool first_run; }; static spi_data_t *spi_data = nullptr; static void set_spi_ctrl_register(unsigned long bit, bool val) { uint32_t value = REG_READ(SPI_CTRL); if (val == 0) { value &= ~bit; } else if (val == 1) { value |= bit; } REG_WRITE(SPI_CTRL, value); } static void set_spi_config_register(unsigned long bit, bool val) { uint32_t value = REG_READ(SPI_CONFIG); if (val == 0) { value &= ~bit; } else if (val == 1) { value |= bit; } REG_WRITE(SPI_CONFIG, value); } void spi_dma_tx_enable(bool enable) { GDMA_CFG_ST en_cfg; set_spi_config_register(SPI_TX_EN, enable ? 1 : 0); en_cfg.channel = SPI_TX_DMA_CHANNEL; en_cfg.param = enable ? 1 : 0; sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg); } static void spi_set_clock(uint32_t max_hz) { int source_clk = 0; int spi_clk = 0; int div = 0; uint32_t param; if (max_hz > 4333000) { if (max_hz > 30000000) { spi_clk = 30000000; } else { spi_clk = max_hz; } sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_DOWN, ¶m); source_clk = SPI_PERI_CLK_DCO; param = PCLK_POSI_SPI; sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_DCO, ¶m); param = PWD_SPI_CLK_BIT; sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, ¶m); } else { spi_clk = max_hz; #if CFG_XTAL_FREQUENCE source_clk = CFG_XTAL_FREQUENCE; #else source_clk = SPI_PERI_CLK_26M; #endif param = PCLK_POSI_SPI; sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, ¶m); } div = ((source_clk >> 1) / spi_clk); if (div < 2) { div = 2; } else if (div >= 255) { div = 255; } param = REG_READ(SPI_CTRL); param &= ~(SPI_CKR_MASK << SPI_CKR_POSI); param |= (div << SPI_CKR_POSI); REG_WRITE(SPI_CTRL, param); ESP_LOGD(TAG, "target frequency: %d, actual frequency: %d", max_hz, source_clk / 2 / div); } void spi_dma_tx_finish_callback(unsigned int param) { spi_data->tx_in_progress = false; xSemaphoreGive(spi_data->dma_tx_semaphore); spi_dma_tx_enable(0); } void BekenSPILEDStripLightOutput::setup() { ESP_LOGCONFIG(TAG, "Running setup"); size_t buffer_size = this->get_buffer_size_(); size_t dma_buffer_size = (buffer_size * 8) + (2 * 64); ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); this->buf_ = allocator.allocate(buffer_size); if (this->buf_ == nullptr) { ESP_LOGE(TAG, "Cannot allocate LED buffer!"); this->mark_failed(); return; } this->effect_data_ = allocator.allocate(this->num_leds_); if (this->effect_data_ == nullptr) { ESP_LOGE(TAG, "Cannot allocate effect data!"); this->mark_failed(); return; } this->dma_buf_ = allocator.allocate(dma_buffer_size); if (this->dma_buf_ == nullptr) { ESP_LOGE(TAG, "Cannot allocate DMA buffer!"); this->mark_failed(); return; } memset(this->buf_, 0, buffer_size); memset(this->effect_data_, 0, this->num_leds_); memset(this->dma_buf_, 0, dma_buffer_size); uint32_t value = PCLK_POSI_SPI; sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, &value); value = PWD_SPI_CLK_BIT; sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &value); if (spi_data != nullptr) { ESP_LOGE(TAG, "SPI device already initialized!"); this->mark_failed(); return; } spi_data = (spi_data_t *) calloc(1, sizeof(spi_data_t)); if (spi_data == nullptr) { ESP_LOGE(TAG, "Cannot allocate spi_data!"); this->mark_failed(); return; } spi_data->dma_tx_semaphore = xSemaphoreCreateBinary(); if (spi_data->dma_tx_semaphore == nullptr) { ESP_LOGE(TAG, "TX Semaphore init faild!"); this->mark_failed(); return; } spi_data->first_run = true; set_spi_ctrl_register(MSTEN, 0); set_spi_ctrl_register(BIT_WDTH, 0); spi_set_clock(this->spi_frequency_); set_spi_ctrl_register(CKPOL, 0); set_spi_ctrl_register(CKPHA, 0); set_spi_ctrl_register(MSTEN, 1); set_spi_ctrl_register(SPIEN, 1); set_spi_ctrl_register(TXINT_EN, 0); set_spi_ctrl_register(RXINT_EN, 0); set_spi_config_register(SPI_TX_FINISH_EN, 1); set_spi_config_register(SPI_RX_FINISH_EN, 1); set_spi_ctrl_register(RXOVR_EN, 0); set_spi_ctrl_register(TXOVR_EN, 0); value = REG_READ(SPI_CTRL); value &= ~CTRL_NSSMD_3; value |= (1 << 17); REG_WRITE(SPI_CTRL, value); value = GFUNC_MODE_SPI_DMA; sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value); set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, 0); GDMA_CFG_ST en_cfg; GDMACFG_TPYES_ST init_cfg; memset(&init_cfg, 0, sizeof(GDMACFG_TPYES_ST)); init_cfg.dstdat_width = 8; init_cfg.srcdat_width = 32; init_cfg.dstptr_incr = 0; init_cfg.srcptr_incr = 1; init_cfg.src_start_addr = this->dma_buf_; init_cfg.dst_start_addr = (void *) SPI_DAT; // SPI_DMA_REG4_TXFIFO init_cfg.channel = SPI_TX_DMA_CHANNEL; init_cfg.prio = 0; // 10 init_cfg.u.type4.src_loop_start_addr = this->dma_buf_; init_cfg.u.type4.src_loop_end_addr = this->dma_buf_ + dma_buffer_size; init_cfg.half_fin_handler = nullptr; init_cfg.fin_handler = spi_dma_tx_finish_callback; init_cfg.src_module = GDMA_X_SRC_DTCM_RD_REQ; init_cfg.dst_module = GDMA_X_DST_GSPI_TX_REQ; // GDMA_X_DST_HSSPI_TX_REQ sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_TYPE4, (void *) &init_cfg); en_cfg.channel = SPI_TX_DMA_CHANNEL; en_cfg.param = dma_buffer_size; sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_TRANS_LENGTH, (void *) &en_cfg); en_cfg.channel = SPI_TX_DMA_CHANNEL; en_cfg.param = 0; sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_WORK_MODE, (void *) &en_cfg); en_cfg.channel = SPI_TX_DMA_CHANNEL; en_cfg.param = 0; sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_SRCADDR_LOOP, &en_cfg); spi_dma_tx_enable(0); value = REG_READ(SPI_CONFIG); value &= ~(0xFFF << 8); value |= ((dma_buffer_size & 0xFFF) << 8); REG_WRITE(SPI_CONFIG, value); } void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency) { this->bit0_ = bit0; this->bit1_ = bit1; this->spi_frequency_ = spi_frequency; } void BekenSPILEDStripLightOutput::write_state(light::LightState *state) { // protect from refreshing too often uint32_t now = micros(); if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) { // try again next loop iteration, so that this change won't get lost this->schedule_show(); return; } this->last_refresh_ = now; this->mark_shown_(); ESP_LOGVV(TAG, "Writing RGB values to bus"); if (spi_data == nullptr) { ESP_LOGE(TAG, "SPI not initialized"); this->status_set_warning(); return; } if (!spi_data->first_run && !xSemaphoreTake(spi_data->dma_tx_semaphore, 10 / portTICK_PERIOD_MS)) { ESP_LOGE(TAG, "Timed out waiting for semaphore"); return; } if (spi_data->tx_in_progress) { ESP_LOGE(TAG, "tx_in_progress is set"); this->status_set_warning(); return; } spi_data->tx_in_progress = true; size_t buffer_size = this->get_buffer_size_(); size_t size = 0; uint8_t *psrc = this->buf_; uint8_t *pdest = this->dma_buf_ + 64; // The 64 byte padding is a workaround for a SPI DMA bug where the // output doesn't exactly start at the beginning of dma_buf_ while (size < buffer_size) { uint8_t b = *psrc; for (int i = 0; i < 8; i++) { *pdest++ = b & (1 << (7 - i)) ? this->bit1_ : this->bit0_; } size++; psrc++; } spi_data->first_run = false; spi_dma_tx_enable(1); this->status_clear_warning(); } light::ESPColorView BekenSPILEDStripLightOutput::get_view_internal(int32_t index) const { int32_t r = 0, g = 0, b = 0; switch (this->rgb_order_) { case ORDER_RGB: r = 0; g = 1; b = 2; break; case ORDER_RBG: r = 0; g = 2; b = 1; break; case ORDER_GRB: r = 1; g = 0; b = 2; break; case ORDER_GBR: r = 2; g = 0; b = 1; break; case ORDER_BGR: r = 2; g = 1; b = 0; break; case ORDER_BRG: r = 1; g = 2; b = 0; break; } uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3; uint8_t white = this->is_wrgb_ ? 0 : 3; return {this->buf_ + (index * multiplier) + r + this->is_wrgb_, this->buf_ + (index * multiplier) + g + this->is_wrgb_, this->buf_ + (index * multiplier) + b + this->is_wrgb_, this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr, &this->effect_data_[index], &this->correction_}; } void BekenSPILEDStripLightOutput::dump_config() { ESP_LOGCONFIG(TAG, "Beken SPI LED Strip:\n" " Pin: %u", this->pin_); const char *rgb_order; switch (this->rgb_order_) { case ORDER_RGB: rgb_order = "RGB"; break; case ORDER_RBG: rgb_order = "RBG"; break; case ORDER_GRB: rgb_order = "GRB"; break; case ORDER_GBR: rgb_order = "GBR"; break; case ORDER_BGR: rgb_order = "BGR"; break; case ORDER_BRG: rgb_order = "BRG"; break; default: rgb_order = "UNKNOWN"; break; } ESP_LOGCONFIG(TAG, " RGB Order: %s\n" " Max refresh rate: %" PRIu32 "\n" " Number of LEDs: %u", rgb_order, *this->max_refresh_rate_, this->num_leds_); } float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; } } // namespace beken_spi_led_strip } // namespace esphome #endif // USE_BK72XX