diff --git a/src/lgfx/v1/Bus.hpp b/src/lgfx/v1/Bus.hpp index b6c116e1..5da522a6 100644 --- a/src/lgfx/v1/Bus.hpp +++ b/src/lgfx/v1/Bus.hpp @@ -33,6 +33,7 @@ namespace lgfx { bus_unknown, bus_spi, + bus_qspi, bus_i2c, bus_parallel8, bus_parallel16, diff --git a/src/lgfx/v1/panel/Panel_SH8601Z.cpp b/src/lgfx/v1/panel/Panel_SH8601Z.cpp new file mode 100644 index 00000000..1c613fa9 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_SH8601Z.cpp @@ -0,0 +1,596 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#include "Panel_SH8601Z.hpp" +#include "../Bus.hpp" +#include "../platforms/common.hpp" +#include "../misc/pixelcopy.hpp" +#include "../misc/colortype.hpp" +#include "driver/spi_master.h" +#include "esp_log.h" + + +/** + * @brief Bug list + * + * > Write image (pushSprite) works fine, bugs down below are from writing directly + * + * 1> Write function is block even with DMA (manual CS wait data) + * 2> In spi 40MHz draw vertical line incomplete, but 10MHz OK (Likely because my dupont line connection) + * 3> After implement write/draw pixel funcs, "testFilledRects" stucks sometime, acts differently to the different sck freq + * 4> Haven't find the way to set rotation by reg + */ + + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + + /* Panel init */ + bool Panel_SH8601Z::init(bool use_reset) + { + // ESP_LOGD("SH8601Z","pannel init %d", use_reset); + + if (!Panel_Device::init(use_reset)) { + return false; + } + + + /* Store pannel resolution */ + _width = _cfg.panel_width; + _height = _cfg.panel_height; + + + startWrite(); + + /* Sleep out */ + cs_control(false); + write_cmd(0x11); + _bus->wait(); + cs_control(true); + delay(150); + + cs_control(false); + write_cmd(0x44); + _bus->writeCommand(0x01, 8); + _bus->writeCommand(0x66, 8); + _bus->wait(); + cs_control(true); + delay(1); + + /* TE on */ + cs_control(false); + write_cmd(0x35); + _bus->writeCommand(0x00, 8); + _bus->wait(); + cs_control(true); + delay(1); + + /* Interface Pixel Format 16bit/pixel */ + cs_control(false); + write_cmd(0x3A); + _bus->writeCommand(0x55, 8); + _bus->wait(); + cs_control(true); + delay(1); + + cs_control(false); + write_cmd(0x53); + _bus->writeCommand(0x20, 8); + _bus->wait(); + cs_control(true); + delay(10); + + /* Write Display Brightness MAX_VAL=0XFF */ + cs_control(false); + write_cmd(0x51); + _bus->writeCommand(0x00, 8); + _bus->wait(); + cs_control(true); + delay(10); + + /* Display on */ + cs_control(false); + write_cmd(0x29); + _bus->wait(); + cs_control(true); + delay(10); + + /* Write Display Brightness MAX_VAL=0XFF */ + cs_control(false); + write_cmd(0x51); + _bus->writeCommand(0xFF, 8); + _bus->wait(); + cs_control(true); + delay(1); + + endWrite(); + + return true; + } + + + + void Panel_SH8601Z::setBrightness(uint8_t brightness) + { + // ESP_LOGD("SH8601Z","setBrightness %d", brightness); + + startWrite(); + + /* Write Display Brightness MAX_VAL=0XFF */ + cs_control(false); + write_cmd(0x51); + _bus->writeCommand(brightness, 8); + _bus->wait(); + cs_control(true); + + endWrite(); + } + + + void Panel_SH8601Z::setRotation(uint_fast8_t r) + { + // ESP_LOGD("SH8601Z","setRotation %d", r); + + r &= 7; + _rotation = r; + // offset_rotationを加算 (0~3:回転方向、 4:上下反転フラグ); + _internal_rotation = ((r + _cfg.offset_rotation) & 3) | ((r & 4) ^ (_cfg.offset_rotation & 4)); + + auto ox = _cfg.offset_x; + auto oy = _cfg.offset_y; + auto pw = _cfg.panel_width; + auto ph = _cfg.panel_height; + auto mw = _cfg.memory_width; + auto mh = _cfg.memory_height; + if (_internal_rotation & 1) + { + std::swap(ox, oy); + std::swap(pw, ph); + std::swap(mw, mh); + } + _width = pw; + _height = ph; + // _colstart = (_internal_rotation & 2) + // ? mw - (pw + ox) : ox; + + // _rowstart = ((1 << _internal_rotation) & 0b10010110) // case 1:2:4:7 + // ? mh - (ph + oy) : oy; + + _xs = _xe = _ys = _ye = INT16_MAX; + + // update_madctl(); + } + + + void Panel_SH8601Z::setInvert(bool invert) + { + // ESP_LOGD("SH8601Z","setInvert %d", invert); + + cs_control(false); + + if (invert) { + /* Inversion On */ + write_cmd(0x21); + } + else { + /* Inversion Off */ + write_cmd(0x20); + } + _bus->wait(); + + cs_control(true); + } + + + void Panel_SH8601Z::setSleep(bool flg) + { + // ESP_LOGD("SH8601Z","setSleep %d", flg); + + cs_control(false); + + if (flg) { + /* Sleep in */ + write_cmd(0x10); + } + else { + /* Sleep out */ + write_cmd(0x11); + delay(150); + } + _bus->wait(); + + cs_control(true); + } + + + void Panel_SH8601Z::setPowerSave(bool flg) + { + // ESP_LOGD("SH8601Z","setPowerSave"); + } + + + void Panel_SH8601Z::waitDisplay(void) + { + // ESP_LOGD("SH8601Z","waitDisplay"); + } + + + bool Panel_SH8601Z::displayBusy(void) + { + // ESP_LOGD("SH8601Z","displayBusy"); + return false; + } + + + color_depth_t Panel_SH8601Z::setColorDepth(color_depth_t depth) + { + // ESP_LOGD("SH8601Z","setColorDepth %d", depth); + + /* 0x55: 16bit/pixel */ + /* 0x66: 18bit/pixel */ + /* 0x77: 24bit/pixel */ + uint8_t cmd_send = 0; + if (depth == rgb565_2Byte) { + cmd_send = 0x55; + } + else if (depth == rgb666_3Byte) { + cmd_send = 0x66; + } + else if (depth == rgb888_3Byte) { + cmd_send = 0x77; + } + else { + return _write_depth; + } + _write_depth = depth; + + /* Set interface Pixel Format */ + startWrite(); + + cs_control(false); + write_cmd(0x3A); + _bus->writeCommand(cmd_send, 8); + _bus->wait(); + cs_control(true); + + endWrite(); + + return _write_depth; + } + + + void Panel_SH8601Z::write_cmd(uint8_t cmd) + { + uint8_t cmd_buffer[4] = {0x02, 0x00, 0x00, 0x00}; + cmd_buffer[2] = cmd; + // _bus->writeBytes(cmd_buffer, 4, 0, false); + for (int i = 0; i < 4; i++) { + _bus->writeCommand(cmd_buffer[i], 8); + } + } + + + void Panel_SH8601Z::start_qspi() + { + /* Begin QSPI */ + cs_control(false); + _bus->writeCommand(0x32, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x2C, 8); + _bus->writeCommand(0x00, 8); + _bus->wait(); + } + + void Panel_SH8601Z::end_qspi() + { + /* Stop QSPI */ + _bus->writeCommand(0x32, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x00, 8); + _bus->wait(); + cs_control(true); + } + + + void Panel_SH8601Z::beginTransaction(void) + { + // ESP_LOGD("SH8601Z","beginTransaction"); + if (_in_transaction) return; + _in_transaction = true; + _bus->beginTransaction(); + } + + + void Panel_SH8601Z::endTransaction(void) + { + // ESP_LOGD("SH8601Z","endTransaction"); + // if (!_in_transaction) return; + // _in_transaction = false; + // _bus->endTransaction(); + + if (!_in_transaction) return; + _in_transaction = false; + + if (_has_align_data) + { + _has_align_data = false; + _bus->writeData(0, 8); + } + + _bus->endTransaction(); + } + + + void Panel_SH8601Z::write_bytes(const uint8_t* data, uint32_t len, bool use_dma) + { + start_qspi(); + _bus->writeBytes(data, len, true, use_dma); + _bus->wait(); + end_qspi(); + } + + + void Panel_SH8601Z::setWindow(uint_fast16_t xs, uint_fast16_t ys, uint_fast16_t xe, uint_fast16_t ye) + { + // ESP_LOGD("SH8601Z","setWindow %d %d %d %d", xs, ys, xe, ye); + + /* Set limit */ + if ((xe - xs) >= _width) { xs = 0; xe = _width - 1; } + if ((ye - ys) >= _height) { ys = 0; ye = _height - 1; } + + /* Set Column Start Address */ + cs_control(false); + write_cmd(0x2A); + _bus->writeCommand(xs >> 8, 8); + _bus->writeCommand(xs & 0xFF, 8); + _bus->writeCommand(xe >> 8, 8); + _bus->writeCommand(xe & 0xFF, 8); + _bus->wait(); + cs_control(true); + + /* Set Row Start Address */ + cs_control(false); + write_cmd(0x2B); + _bus->writeCommand(ys >> 8, 8); + _bus->writeCommand(ys & 0xFF, 8); + _bus->writeCommand(ye >> 8, 8); + _bus->writeCommand(ye & 0xFF, 8); + _bus->wait(); + cs_control(true); + + /* Memory Write */ + cs_control(false); + write_cmd(0x2C); + _bus->wait(); + cs_control(true); + } + + + void Panel_SH8601Z::writeBlock(uint32_t rawcolor, uint32_t len) + { + // ESP_LOGD("SH8601Z","writeBlock 0x%lx %ld", rawcolor, len); + + /* Push color */ + start_qspi(); + _bus->writeDataRepeat(rawcolor, _write_bits, len); + _bus->wait(); + end_qspi(); + } + + + + + void Panel_SH8601Z::writePixels(pixelcopy_t* param, uint32_t len, bool use_dma) + { + // ESP_LOGD("SH8601Z","writePixels %ld %d", len, use_dma); + + start_qspi(); + + if (param->no_convert) { + _bus->writeBytes(reinterpret_cast(param->src_data), len * _write_bits >> 3, true, use_dma); + } + else { + _bus->writePixels(param, len); + } + if (_cfg.dlen_16bit && (_write_bits & 15) && (len & 1)) { + _has_align_data = !_has_align_data; + } + + _bus->wait(); + end_qspi(); + } + + + void Panel_SH8601Z::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) + { + // ESP_LOGD("SH8601Z","drawPixelPreclipped %d %d 0x%lX", x, y, rawcolor); + + setWindow(x,y,x,y); + if (_cfg.dlen_16bit) { _has_align_data = (_write_bits & 15); } + + start_qspi(); + + _bus->writeData(rawcolor, _write_bits); + + _bus->wait(); + end_qspi(); + } + + + void Panel_SH8601Z::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) + { + // ESP_LOGD("SH8601Z","writeFillRectPreclipped %d %d %d %d 0x%lX", x, y, w, h, rawcolor); + + uint32_t len = w * h; + uint_fast16_t xe = w + x - 1; + uint_fast16_t ye = y + h - 1; + + setWindow(x,y,xe,ye); + // if (_cfg.dlen_16bit) { _has_align_data = (_write_bits & 15) && (len & 1); } + + start_qspi(); + _bus->writeDataRepeat(rawcolor, _write_bits, len); + _bus->wait(); + end_qspi(); + } + + + + void Panel_SH8601Z::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) + { + // ESP_LOGD("SH8601Z","writeImage %d %d %d %d %d", x, y, w, h, use_dma); + // use_dma = false; + + auto bytes = param->dst_bits >> 3; + auto src_x = param->src_x; + + if (param->transp == pixelcopy_t::NON_TRANSP) + { + if (param->no_convert) + { + auto wb = w * bytes; + uint32_t i = (src_x + param->src_y * param->src_bitwidth) * bytes; + auto src = &((const uint8_t*)param->src_data)[i]; + setWindow(x, y, x + w - 1, y + h - 1); + if (param->src_bitwidth == w || h == 1) + { + write_bytes(src, wb * h, use_dma); + } + else + { + auto add = param->src_bitwidth * bytes; + if (use_dma) + { + if (_cfg.dlen_16bit && ((wb * h) & 1)) + { + _has_align_data = !_has_align_data; + } + do + { + _bus->addDMAQueue(src, wb); + src += add; + } while (--h); + _bus->execDMAQueue(); + } + else + { + do + { + write_bytes(src, wb, false); + src += add; + } while (--h); + } + } + } + else + { + if (!_bus->busy()) + { + static constexpr uint32_t WRITEPIXELS_MAXLEN = 32767; + + setWindow(x, y, x + w - 1, y + h - 1); + // bool nogap = (param->src_bitwidth == w || h == 1); + bool nogap = (h == 1) || (param->src_y32_add == 0 && ((param->src_bitwidth << pixelcopy_t::FP_SCALE) == (w * param->src_x32_add))); + if (nogap && (w * h <= WRITEPIXELS_MAXLEN)) + { + writePixels(param, w * h, use_dma); + } + else + { + uint_fast16_t h_step = nogap ? WRITEPIXELS_MAXLEN / w : 1; + uint_fast16_t h_len = (h_step > 1) ? ((h - 1) % h_step) + 1 : 1; + writePixels(param, w * h_len, use_dma); + if (h -= h_len) + { + param->src_y += h_len; + do + { + param->src_x = src_x; + writePixels(param, w * h_step, use_dma); + param->src_y += h_step; + } while (h -= h_step); + } + } + } + else + { + size_t wb = w * bytes; + auto buf = _bus->getDMABuffer(wb); + param->fp_copy(buf, 0, w, param); + setWindow(x, y, x + w - 1, y + h - 1); + write_bytes(buf, wb, true); + _has_align_data = (_cfg.dlen_16bit && (_write_bits & 15) && (w & h & 1)); + while (--h) + { + param->src_x = src_x; + param->src_y++; + buf = _bus->getDMABuffer(wb); + param->fp_copy(buf, 0, w, param); + write_bytes(buf, wb, true); + } + } + } + } + else + { + h += y; + uint32_t wb = w * bytes; + do + { + uint32_t i = 0; + while (w != (i = param->fp_skip(i, w, param))) + { + auto buf = _bus->getDMABuffer(wb); + int32_t len = param->fp_copy(buf, 0, w - i, param); + setWindow(x + i, y, x + i + len - 1, y); + write_bytes(buf, len * bytes, true); + if (w == (i += len)) break; + } + param->src_x = src_x; + param->src_y++; + } while (++y != h); + } + } + + + + + uint32_t Panel_SH8601Z::readCommand(uint_fast16_t cmd, uint_fast8_t index, uint_fast8_t len) + { + // ESP_LOGD("SH8601Z","readCommand"); + return 0; + } + + uint32_t Panel_SH8601Z::readData(uint_fast8_t index, uint_fast8_t len) + { + // ESP_LOGD("SH8601Z","readData"); + return 0; + } + + void Panel_SH8601Z::readRect(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, void* dst, pixelcopy_t* param) + { + // ESP_LOGD("SH8601Z","readRect"); + } + + +//---------------------------------------------------------------------------- + } +} diff --git a/src/lgfx/v1/panel/Panel_SH8601Z.hpp b/src/lgfx/v1/panel/Panel_SH8601Z.hpp new file mode 100644 index 00000000..6342c73a --- /dev/null +++ b/src/lgfx/v1/panel/Panel_SH8601Z.hpp @@ -0,0 +1,73 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#pragma once + +#include "Panel_Device.hpp" + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + + struct Panel_SH8601Z : public Panel_Device + { + public: + Panel_SH8601Z(void) {} + + bool init(bool use_reset) override; + void beginTransaction(void) override; + void endTransaction(void) override; + + color_depth_t setColorDepth(color_depth_t depth) override; + void setRotation(uint_fast8_t r) override; + void setInvert(bool invert) override; + void setSleep(bool flg) override; + void setPowerSave(bool flg) override; + + void waitDisplay(void) override; + bool displayBusy(void) override; + + void writePixels(pixelcopy_t* param, uint32_t len, bool use_dma) override; + void writeBlock(uint32_t rawcolor, uint32_t len) override; + + void setWindow(uint_fast16_t xs, uint_fast16_t ys, uint_fast16_t xe, uint_fast16_t ye) override; + void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override; + void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override; + void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) override; + + uint32_t readCommand(uint_fast16_t cmd, uint_fast8_t index, uint_fast8_t len) override; + uint32_t readData(uint_fast8_t index, uint_fast8_t len) override; + void readRect(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, void* dst, pixelcopy_t* param) override; + + /* Override */ + void setBrightness(uint8_t brightness) override; + + + protected: + bool _in_transaction = false; + + void write_cmd(uint8_t cmd); + void start_qspi(); + void end_qspi(); + void write_bytes(const uint8_t* data, uint32_t len, bool use_dma); + }; + +//---------------------------------------------------------------------------- + } +} diff --git a/src/lgfx/v1/platforms/device.hpp b/src/lgfx/v1/platforms/device.hpp index aca102a3..2dd2bc93 100644 --- a/src/lgfx/v1/platforms/device.hpp +++ b/src/lgfx/v1/platforms/device.hpp @@ -40,6 +40,7 @@ Original Source: #include "esp32/Light_PWM.hpp" #include "esp32/Bus_SPI.hpp" + #include "esp32/Bus_QSPI.hpp" #include "esp32/Bus_I2C.hpp" #include "esp32s3/Bus_Parallel8.hpp" #include "esp32s3/Bus_Parallel16.hpp" @@ -48,6 +49,7 @@ Original Source: #include "esp32/Light_PWM.hpp" #include "esp32/Bus_SPI.hpp" + #include "esp32/Bus_QSPI.hpp" #include "esp32/Bus_I2C.hpp" #include "esp32/Bus_Parallel8.hpp" #include "esp32/Bus_HUB75.hpp" diff --git a/src/lgfx/v1/platforms/esp32/Bus_QSPI.cpp b/src/lgfx/v1/platforms/esp32/Bus_QSPI.cpp new file mode 100644 index 00000000..58e2ee7f --- /dev/null +++ b/src/lgfx/v1/platforms/esp32/Bus_QSPI.cpp @@ -0,0 +1,1102 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#if defined (ESP_PLATFORM) +#include + +/// ESP32-S3をターゲットにした際にREG_SPI_BASEが定義されていなかったので応急処置 ; +#if defined ( CONFIG_IDF_TARGET_ESP32S3 ) + #define REG_SPI_BASE(i) (DR_REG_SPI1_BASE + (((i)>1) ? (((i)* 0x1000) + 0x20000) : (((~(i)) & 1)* 0x1000 ))) +#elif defined ( CONFIG_IDF_TARGET_ESP32 ) || !defined ( CONFIG_IDF_TARGET ) + #define LGFX_SPIDMA_WORKAROUND +#endif + +#include "Bus_QSPI.hpp" + +#include "../../misc/pixelcopy.hpp" + +#include +#include +#include +#include + +#if __has_include () + #include +#else + #include +#endif + +#if defined (ARDUINO) // Arduino ESP32 + #include + #include +#else + #include + + #if defined ( CONFIG_IDF_TARGET_ESP32S3 ) + #if __has_include () + #include + #else + #include + #endif + #elif defined ( CONFIG_IDF_TARGET_ESP32S2 ) + #if __has_include () + #include + #else + #include + #endif + #else + #if __has_include () + #include + #else + #include + #endif + #endif +#endif + +#ifndef SPI_PIN_REG + #define SPI_PIN_REG SPI_MISC_REG +#endif + +#if defined (SOC_GDMA_SUPPORTED) // for C3/S3 + #include + #include + #if !defined DMA_OUT_LINK_CH0_REG + #define DMA_OUT_LINK_CH0_REG GDMA_OUT_LINK_CH0_REG + #define DMA_OUTFIFO_STATUS_CH0_REG GDMA_OUTFIFO_STATUS_CH0_REG + #define DMA_OUTLINK_START_CH0 GDMA_OUTLINK_START_CH0 + #define DMA_OUTFIFO_EMPTY_CH0 GDMA_OUTFIFO_EMPTY_L3_CH0 + #endif +#endif + +#include "common.hpp" + +#include + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + + void Bus_QSPI::config(const config_t& cfg) + { + _cfg = cfg; + + auto spi_port = (uint32_t)(cfg.spi_host) + 1; // FSPI=1 HSPI=2 VSPI=3; + _spi_port = spi_port; + _spi_w0_reg = reg(SPI_W0_REG( spi_port)); + _spi_cmd_reg = reg(SPI_CMD_REG( spi_port)); + _spi_user_reg = reg(SPI_USER_REG( spi_port)); + _spi_mosi_dlen_reg = reg(SPI_MOSI_DLEN_REG( spi_port)); +#if defined ( SOC_GDMA_SUPPORTED ) +#elif defined ( SPI_DMA_STATUS_REG ) + _spi_dma_out_link_reg = reg(SPI_DMA_OUT_LINK_REG(spi_port)); + _spi_dma_outstatus_reg = reg(SPI_DMA_STATUS_REG(spi_port)); +#else + _spi_dma_out_link_reg = reg(SPI_DMA_OUT_LINK_REG(spi_port)); + _spi_dma_outstatus_reg = reg(SPI_DMA_OUTSTATUS_REG(spi_port)); +#endif + if (cfg.pin_dc < 0) + { // D/Cピン不使用の場合はGPIOレジスタの代わりにダミーとしてmask_reg_dcのアドレスを設定しておく; + _mask_reg_dc = 0; + _gpio_reg_dc[0] = &_mask_reg_dc; + _gpio_reg_dc[1] = &_mask_reg_dc; + } + else + { + _mask_reg_dc = (1ul << (cfg.pin_dc & 31)); + _gpio_reg_dc[0] = get_gpio_lo_reg(cfg.pin_dc); + _gpio_reg_dc[1] = get_gpio_hi_reg(cfg.pin_dc); + } + _last_freq_apb = 0; + + auto spi_mode = cfg.spi_mode; + _user_reg = (spi_mode == 1 || spi_mode == 2) ? SPI_CK_OUT_EDGE | SPI_USR_MOSI : SPI_USR_MOSI; +//ESP_LOGI("LGFX","Bus_QSPI::config spi_port:%d dc:%0d %02x", spi_port, _cfg.pin_dc, _mask_reg_dc); + } + + bool Bus_QSPI::init(void) + { +//ESP_LOGI("LGFX","Bus_QSPI::init"); + dc_control(true); + pinMode(_cfg.pin_dc, pin_mode_t::output); + + int dma_ch = _cfg.dma_channel; +#if defined (ESP_IDF_VERSION) + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) + dma_ch = dma_ch ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED; + #endif +#endif + _inited = spi::initQuad(_cfg.spi_host, _cfg.pin_sclk, _cfg.pin_io0, _cfg.pin_io1, _cfg.pin_io2, _cfg.pin_io3, dma_ch).has_value(); + +#if defined ( SOC_GDMA_SUPPORTED ) + // 割当られたDMAチャネル番号を取得する + +#if defined ( SOC_GDMA_TRIG_PERIPH_SPI3 ) + int peri_sel = (_spi_port == 3) ? SOC_GDMA_TRIG_PERIPH_SPI3 : SOC_GDMA_TRIG_PERIPH_SPI2; +#else + int peri_sel = SOC_GDMA_TRIG_PERIPH_SPI2; +#endif + + int assigned_dma_ch = search_dma_out_ch(peri_sel); + + if (assigned_dma_ch >= 0) + { // DMAチャンネルが特定できたらそれを使用する; + _spi_dma_out_link_reg = reg(DMA_OUT_LINK_CH0_REG + assigned_dma_ch * 0xC0); + _spi_dma_outstatus_reg = reg(DMA_OUTFIFO_STATUS_CH0_REG + assigned_dma_ch * 0xC0); + } +#elif defined ( CONFIG_IDF_TARGET_ESP32 ) || !defined ( CONFIG_IDF_TARGET ) + + dma_ch = (*reg(DPORT_SPI_DMA_CHAN_SEL_REG) >> (_cfg.spi_host * 2)) & 3; + // ESP_LOGE("LGFX", "SPI_HOST: %d / DMA_CH: %d", _cfg.spi_host, dma_ch); + +#endif + + _dma_ch = dma_ch; + + return _inited; + } + + static void gpio_reset(size_t pin) + { + if (pin >= GPIO_NUM_MAX) return; + gpio_reset_pin( (gpio_num_t)pin); + gpio_matrix_out((gpio_num_t)pin, 0x100, 0, 0); + gpio_matrix_in( (gpio_num_t)pin, 0x100, 0 ); + } + + void Bus_QSPI::release(void) + { +//ESP_LOGI("LGFX","Bus_QSPI::release"); + if (!_inited) return; + _inited = false; + spi::release(_cfg.spi_host); + gpio_reset(_cfg.pin_dc ); + // gpio_reset(_cfg.pin_mosi); + // gpio_reset(_cfg.pin_miso); + // gpio_reset(_cfg.pin_sclk); + gpio_reset(_cfg.pin_io0); + gpio_reset(_cfg.pin_io1); + gpio_reset(_cfg.pin_io2); + gpio_reset(_cfg.pin_io3); + gpio_reset(_cfg.pin_sclk); + } + + void Bus_QSPI::beginTransaction(void) + { +//ESP_LOGI("LGFX","Bus_QSPI::beginTransaction"); + uint32_t freq_apb = getApbFrequency(); + uint32_t clkdiv_write = _clkdiv_write; + if (_last_freq_apb != freq_apb) + { + _last_freq_apb = freq_apb; + _clkdiv_read = FreqToClockDiv(freq_apb, _cfg.freq_read); + clkdiv_write = FreqToClockDiv(freq_apb, _cfg.freq_write); + _clkdiv_write = clkdiv_write; + dc_control(true); + pinMode(_cfg.pin_dc, pin_mode_t::output); + } + + auto spi_mode = _cfg.spi_mode; + uint32_t pin = (spi_mode & 2) ? SPI_CK_IDLE_EDGE : 0; + + if (_cfg.use_lock) spi::beginTransaction(_cfg.spi_host); + + *_spi_user_reg = _user_reg; + auto spi_port = _spi_port; + (void)spi_port; + *reg(SPI_PIN_REG(spi_port)) = pin; + *reg(SPI_CLOCK_REG(spi_port)) = clkdiv_write; +#if defined ( SPI_UPDATE ) + *_spi_cmd_reg = SPI_UPDATE; +#endif + } + + void Bus_QSPI::endTransaction(void) + { + dc_control(true); +#if defined ( LGFX_SPIDMA_WORKAROUND ) + if (_dma_ch) { spicommon_dmaworkaround_idle(_dma_ch); } +#endif + if (_cfg.use_lock) spi::endTransaction(_cfg.spi_host); +#if defined (ARDUINO) // Arduino ESP32 + *_spi_user_reg = SPI_USR_MOSI | SPI_USR_MISO | SPI_DOUTDIN; // for other SPI device (e.g. SD card) +#endif + } + + void Bus_QSPI::wait(void) + { + auto spi_cmd_reg = _spi_cmd_reg; + // while (*spi_cmd_reg & SPI_USR); + uint32_t time_out = esp_timer_get_time(); + while (*spi_cmd_reg & SPI_USR) { + if ((esp_timer_get_time() - time_out) > 20000) { break; } + } + } + + bool Bus_QSPI::busy(void) const + { + return (*_spi_cmd_reg & SPI_USR); + } + + bool Bus_QSPI::writeCommand(uint32_t data, uint_fast8_t bit_length) + { +//ESP_LOGI("LGFX","writeCmd: %02x len:%d dc:%02x", data, bit_length, _mask_reg_dc); + --bit_length; + auto spi_mosi_dlen_reg = _spi_mosi_dlen_reg; + auto spi_w0_reg = _spi_w0_reg; + auto spi_cmd_reg = _spi_cmd_reg; + auto gpio_reg_dc = _gpio_reg_dc[0]; + auto mask_reg_dc = _mask_reg_dc; + + /* Send data in 1-bit mode */ + auto spi_user_reg = _spi_user_reg; + uint32_t user = (*spi_user_reg & (~SPI_FWRITE_QUAD)); + +#if !defined ( CONFIG_IDF_TARGET ) || defined ( CONFIG_IDF_TARGET_ESP32 ) + while (*spi_cmd_reg & SPI_USR) {} // wait SPI +#else + auto dma = _clear_dma_reg; + if (dma) + { + _clear_dma_reg = nullptr; + while (*spi_cmd_reg & SPI_USR) {} // wait SPI + *dma = 0; + } + else + { + while (*spi_cmd_reg & SPI_USR) {} // wait SPI + } +#endif + *spi_user_reg = user; + *spi_mosi_dlen_reg = bit_length; // set bitlength + *spi_w0_reg = data; // set data + *gpio_reg_dc = mask_reg_dc; // D/C + *spi_cmd_reg = SPI_EXECUTE; // exec SPI + return true; + } + + + void Bus_QSPI::writeData(uint32_t data, uint_fast8_t bit_length) + { +//ESP_LOGI("LGFX","writeData: %02x len:%d", data, bit_length); + --bit_length; + auto spi_mosi_dlen_reg = _spi_mosi_dlen_reg; + auto spi_w0_reg = _spi_w0_reg; + auto spi_cmd_reg = _spi_cmd_reg; + auto gpio_reg_dc = _gpio_reg_dc[1]; + auto mask_reg_dc = _mask_reg_dc; + + /* Send data in 4-bit mode */ + auto spi_user_reg = _spi_user_reg; + uint32_t user = (*spi_user_reg | SPI_FWRITE_QUAD); + + +#if !defined ( CONFIG_IDF_TARGET ) || defined ( CONFIG_IDF_TARGET_ESP32 ) + while (*spi_cmd_reg & SPI_USR) {} // wait SPI +#else + auto dma = _clear_dma_reg; + if (dma) + { + _clear_dma_reg = nullptr; + while (*spi_cmd_reg & SPI_USR) {} // wait SPI + *dma = 0; + } + else + { + while (*spi_cmd_reg & SPI_USR) {} // wait SPI + } +#endif + *spi_user_reg = user; + *spi_mosi_dlen_reg = bit_length; // set bitlength + *spi_w0_reg = data; // set data + *gpio_reg_dc = mask_reg_dc; // D/C + *spi_cmd_reg = SPI_EXECUTE; // exec SPI + } + + + void Bus_QSPI::writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) + { + auto spi_mosi_dlen_reg = _spi_mosi_dlen_reg; + auto spi_w0_reg = _spi_w0_reg; + auto spi_cmd_reg = _spi_cmd_reg; + auto gpio_reg_dc = _gpio_reg_dc[1]; + auto mask_reg_dc = _mask_reg_dc; + + /* Send data in 4-bit mode */ + auto spi_user_reg = _spi_user_reg; + uint32_t user = (*spi_user_reg | SPI_FWRITE_QUAD); + +#if defined ( CONFIG_IDF_TARGET ) && !defined ( CONFIG_IDF_TARGET_ESP32 ) + auto dma = _clear_dma_reg; + if (dma) { _clear_dma_reg = nullptr; } +#endif + if (1 == count) + { + --bit_length; + while (*spi_cmd_reg & SPI_USR); // wait SPI +#if defined ( CONFIG_IDF_TARGET ) && !defined ( CONFIG_IDF_TARGET_ESP32 ) + if (dma) { *dma = 0; } +#endif + *spi_user_reg = user; + *gpio_reg_dc = mask_reg_dc; // D/C high (data) + *spi_mosi_dlen_reg = bit_length; // set bitlength + *spi_w0_reg = data; // set data + *spi_cmd_reg = SPI_EXECUTE; // exec SPI + return; + } + + uint32_t regbuf0 = data | data << bit_length; + uint32_t regbuf1; + uint32_t regbuf2; + // make 12Bytes data. + bool bits24 = (bit_length == 24); + if (bits24) { + regbuf1 = regbuf0 >> 8 | regbuf0 << 16; + regbuf2 = regbuf0 >>16 | regbuf0 << 8; + } else { + if (bit_length == 8) { regbuf0 |= regbuf0 << 16; } + regbuf1 = regbuf0; + regbuf2 = regbuf0; + } + + uint32_t length = bit_length * count; // convert to bitlength. + uint32_t len = (length <= 96u) ? length : (length <= 144u) ? 48u : 96u; // 1st send length = max 12Byte (96bit). + + length -= len; + --len; + + while (*spi_cmd_reg & SPI_USR) {} // wait SPI +#if defined ( CONFIG_IDF_TARGET ) && !defined ( CONFIG_IDF_TARGET_ESP32 ) + if (dma) { *dma = 0; } +#endif + *spi_user_reg = user; + *gpio_reg_dc = mask_reg_dc; // D/C high (data) + *spi_mosi_dlen_reg = len; + // copy to SPI buffer register + spi_w0_reg[0] = regbuf0; + spi_w0_reg[1] = regbuf1; + spi_w0_reg[2] = regbuf2; + *spi_cmd_reg = SPI_EXECUTE; // exec SPI + if (0 == length) return; + + uint32_t regbuf[7] = { regbuf0, regbuf1, regbuf2, regbuf0, regbuf1, regbuf2, regbuf0 } ; + + // copy to SPI buffer register + memcpy((void*)&spi_w0_reg[3], regbuf, 24); + memcpy((void*)&spi_w0_reg[9], regbuf, 28); + + // limit = 64Byte / depth_bytes; + // When 24bit color, 504 bits out of 512bit buffer are used. + // When 16bit color, it uses exactly 512 bytes. but, it behaves like a ring buffer, can specify a larger size. + uint32_t limit; + if (bits24) + { + limit = 504; + len = length % limit; + } + else + { +#if defined ( CONFIG_IDF_TARGET_ESP32 ) + limit = (1 << 11); +#else + limit = (1 << 9); +#endif + len = length & (limit - 1); + } + + if (len) + { + length -= len; + --len; + while (*spi_cmd_reg & SPI_USR); + *spi_mosi_dlen_reg = len; + *spi_cmd_reg = SPI_EXECUTE; + if (0 == length) return; + } + + while (*spi_cmd_reg & SPI_USR); + *spi_mosi_dlen_reg = limit - 1; + *spi_cmd_reg = SPI_EXECUTE; + while (length -= limit) + { + while (*spi_cmd_reg & SPI_USR); + *spi_cmd_reg = SPI_EXECUTE; + } + } + + + void Bus_QSPI::writePixels(pixelcopy_t* param, uint32_t length) + { + /* Send data in 4-bit mode */ + auto spi_user_reg = _spi_user_reg; + uint32_t user = (*spi_user_reg | SPI_FWRITE_QUAD); + *spi_user_reg = user; + + const uint8_t bytes = param->dst_bits >> 3; + if (_cfg.dma_channel) + { + uint32_t limit = (bytes == 2) ? 32 : 24; + uint32_t len; + do + { + len = (limit << 1) <= length ? limit : length; + if (limit <= 256) limit <<= 1; + auto dmabuf = _flip_buffer.getBuffer(len * bytes); + param->fp_copy(dmabuf, 0, len, param); + writeBytes(dmabuf, len * bytes, true, true); + } while (length -= len); + return; + } + +/// ESP32-C3 で HIGHPART を使用すると異常動作するため分岐する; +#if defined ( SPI_UPDATE ) // for C3/S3 + + const uint32_t limit = (bytes == 2) ? 32 : 21; + uint32_t l = (length - 1) / limit; + uint32_t len = length - (l * limit); + length = l; + uint32_t regbuf[16]; + param->fp_copy(regbuf, 0, len, param); + + auto spi_w0_reg = _spi_w0_reg; + + dc_control(true); + set_write_len(len * bytes << 3); + + memcpy((void*)spi_w0_reg, regbuf, (len * bytes + 3) & (~3)); + + exec_spi(); + if (0 == length) return; + + + param->fp_copy(regbuf, 0, limit, param); + wait_spi(); + set_write_len(limit * bytes << 3); + memcpy((void*)spi_w0_reg, regbuf, limit * bytes); + exec_spi(); + + + while (--length) + { + param->fp_copy(regbuf, 0, limit, param); + wait_spi(); + memcpy((void*)spi_w0_reg, regbuf, limit * bytes); + exec_spi(); + } + +#else + + const uint32_t limit = (bytes == 2) ? 16 : 10; // limit = 32/bytes (bytes==2 is 16 bytes==3 is 10) + uint32_t len = (length - 1) / limit; + uint32_t highpart = (len & 1) << 3; + len = length - (len * limit); + uint32_t regbuf[8]; + param->fp_copy(regbuf, 0, len, param); + + auto spi_w0_reg = _spi_w0_reg; + + uint32_t user_reg = *_spi_user_reg; + + dc_control(true); + set_write_len(len * bytes << 3); + + memcpy((void*)&spi_w0_reg[highpart], regbuf, (len * bytes + 3) & (~3)); + if (highpart) *_spi_user_reg = user_reg | SPI_USR_MOSI_HIGHPART; + exec_spi(); + if (0 == (length -= len)) return; + + for (; length; length -= limit) + { + param->fp_copy(regbuf, 0, limit, param); + memcpy((void*)&spi_w0_reg[highpart ^= 0x08], regbuf, limit * bytes); + uint32_t user = user_reg; + if (highpart) user |= SPI_USR_MOSI_HIGHPART; + if (len != limit) + { + len = limit; + wait_spi(); + set_write_len(limit * bytes << 3); + *_spi_user_reg = user; + exec_spi(); + } + else + { + wait_spi(); + *_spi_user_reg = user; + exec_spi(); + } + } + +#endif + + } + + + void Bus_QSPI::writeBytes(const uint8_t* data, uint32_t length, bool dc, bool use_dma) + { + /* Send data in 4-bit mode */ + auto spi_user_reg = _spi_user_reg; + uint32_t user = (*spi_user_reg | SPI_FWRITE_QUAD); + *spi_user_reg = user; + + if (length <= 64) + { + auto spi_w0_reg = _spi_w0_reg; + auto aligned_len = (length + 3) & (~3); + length <<= 3; + dc_control(dc); + set_write_len(length); + memcpy((void*)spi_w0_reg, data, aligned_len); + exec_spi(); + return; + } + + if (_cfg.dma_channel) + { + if (false == use_dma && length < 1024) + { + use_dma = true; + auto buf = _flip_buffer.getBuffer(length); + memcpy(buf, data, length); + data = buf; + } + if (use_dma) + { + auto spi_dma_out_link_reg = _spi_dma_out_link_reg; + auto cmd = _spi_cmd_reg; + while (*cmd & SPI_USR) {} + *spi_dma_out_link_reg = 0; + _setup_dma_desc_links(data, length); +#if defined ( SOC_GDMA_SUPPORTED ) + auto dma = reg(SPI_DMA_CONF_REG(_spi_port)); + *dma = 0; /// Clear previous transfer + uint32_t len = ((length - 1) & ((SPI_MS_DATA_BITLEN)>>3)) + 1; + *spi_dma_out_link_reg = DMA_OUTLINK_START_CH0 | ((int)(&_dmadesc[0]) & 0xFFFFF); + *dma = SPI_DMA_TX_ENA; + _clear_dma_reg = dma; +#else + auto dma_conf_reg = reg(SPI_DMA_CONF_REG(_spi_port)); + auto dma_conf = *dma_conf_reg & ~(SPI_OUT_DATA_BURST_EN | SPI_AHBM_RST | SPI_AHBM_FIFO_RST | SPI_OUT_RST); + *dma_conf_reg = dma_conf | SPI_AHBM_RST | SPI_AHBM_FIFO_RST | SPI_OUT_RST; + + // 送信長が4の倍数の場合のみバーストモードを使用する + // ※ 以下の3つの条件が揃うと、DMA転送の末尾付近でデータが化ける現象が起きる。 + // 1.送信クロック80MHz (APBクロックと1:1) + // 2.DMAバースト読出し有効 + // 3.送信データ長が4の倍数ではない (1Byte~3Byteの端数がある場合) + dma_conf |= (length & 3) ? (SPI_OUTDSCR_BURST_EN) : (SPI_OUTDSCR_BURST_EN | SPI_OUT_DATA_BURST_EN); + + *dma_conf_reg = dma_conf; + uint32_t len = length; + *spi_dma_out_link_reg = SPI_OUTLINK_START | ((int)(&_dmadesc[0]) & 0xFFFFF); + _clear_dma_reg = spi_dma_out_link_reg; +#endif + set_write_len(len << 3); + *_gpio_reg_dc[dc] = _mask_reg_dc; + + // DMA準備完了待ち; +#if defined ( SOC_GDMA_SUPPORTED ) + while (*_spi_dma_outstatus_reg & DMA_OUTFIFO_EMPTY_CH0 ) {} +#elif defined (SPI_DMA_OUTFIFO_EMPTY) + while (*_spi_dma_outstatus_reg & SPI_DMA_OUTFIFO_EMPTY ) {} +#else + #if defined ( LGFX_SPIDMA_WORKAROUND ) + if (_dma_ch) { spicommon_dmaworkaround_transfer_active(_dma_ch); } + #endif +#endif + exec_spi(); + +#if defined ( SOC_GDMA_SUPPORTED ) + if (length -= len) + { + while (*cmd & SPI_USR) {} + set_write_len(SPI_MS_DATA_BITLEN + 1); + goto label_start; + do + { + while (*cmd & SPI_USR) {} +label_start: + exec_spi(); + } while (length -= ((SPI_MS_DATA_BITLEN + 1) >> 3)); + } +#endif + return; + } + } + + auto spi_w0_reg = _spi_w0_reg; + +/// ESP32-C3 で HIGHPART を使用すると異常動作するため分岐する; +#if defined ( SPI_UPDATE ) // for C3/S3 + + uint32_t regbuf[16]; + constexpr uint32_t limit = 64; + uint32_t len = ((length - 1) & 0x3F) + 1; + + memcpy(regbuf, data, len); + dc_control(dc); + set_write_len(len << 3); + + memcpy((void*)spi_w0_reg, regbuf, (len + 3) & (~3)); + exec_spi(); + if (0 == (length -= len)) return; + + data += len; + memcpy(regbuf, data, limit); + wait_spi(); + set_write_len(limit << 3); + memcpy((void*)spi_w0_reg, regbuf, limit); + exec_spi(); + if (0 == (length -= limit)) return; + + do + { + data += limit; + memcpy(regbuf, data, limit); + wait_spi(); + memcpy((void*)spi_w0_reg, regbuf, limit); + exec_spi(); + } while (0 != (length -= limit)); + +#else + + constexpr uint32_t limit = 32; + uint32_t len = ((length - 1) & 0x1F) + 1; + uint32_t highpart = ((length - 1) & limit) >> 2; // 8 or 0 + + uint32_t user_reg = _user_reg; + user_reg = user_reg | SPI_FWRITE_QUAD; + + dc_control(dc); + set_write_len(len << 3); + + memcpy((void*)&spi_w0_reg[highpart], data, (len + 3) & (~3)); + if (highpart) *_spi_user_reg = user_reg | SPI_USR_MOSI_HIGHPART; + exec_spi(); + if (0 == (length -= len)) return; + + for (; length; length -= limit) + { + data += len; + memcpy((void*)&spi_w0_reg[highpart ^= 0x08], data, limit); + uint32_t user = user_reg; + if (highpart) user |= SPI_USR_MOSI_HIGHPART; + if (len != limit) + { + len = limit; + wait_spi(); + set_write_len(limit << 3); + *_spi_user_reg = user; + exec_spi(); + } + else + { + wait_spi(); + *_spi_user_reg = user; + exec_spi(); + } + } + +#endif + + } + + + + + void Bus_QSPI::addDMAQueue(const uint8_t* data, uint32_t length) + { + if (!_cfg.dma_channel) + { + writeBytes(data, length, true, true); + return; + } + + _dma_queue_bytes += length; + size_t index = _dma_queue_size; + size_t new_size = index + ((length-1) / SPI_MAX_DMA_LEN) + 1; + + if (_dma_queue_capacity < new_size) + { + _dma_queue_capacity = new_size + 8; + auto new_queue = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t) * _dma_queue_capacity, MALLOC_CAP_DMA); + if (index) + { + memcpy(new_queue, _dma_queue, sizeof(lldesc_t) * index); + } + if (_dma_queue != nullptr) { heap_free(_dma_queue); } + _dma_queue = new_queue; + } + _dma_queue_size = new_size; + lldesc_t *dmadesc = &_dma_queue[index]; + + while (length > SPI_MAX_DMA_LEN) + { + *(uint32_t*)dmadesc = SPI_MAX_DMA_LEN | SPI_MAX_DMA_LEN << 12 | 0x80000000; + dmadesc->buf = const_cast(data); + dmadesc++; + data += SPI_MAX_DMA_LEN; + length -= SPI_MAX_DMA_LEN; + } + *(uint32_t*)dmadesc = ((length + 3) & ( ~3 )) | length << 12 | 0x80000000; + dmadesc->buf = const_cast(data); + } + + + void Bus_QSPI::execDMAQueue(void) + { + if (0 == _dma_queue_size) return; + + /* Send data in 4-bit mode */ + auto spi_user_reg = _spi_user_reg; + uint32_t user = (*spi_user_reg | SPI_FWRITE_QUAD); + *spi_user_reg = user; + + int index = _dma_queue_size - 1; + _dma_queue_size = 0; + _dma_queue[index].eof = 1; + _dma_queue[index].qe.stqe_next = nullptr; + while (--index >= 0) + { + _dma_queue[index].qe.stqe_next = &_dma_queue[index + 1]; + } + + std::swap(_dmadesc, _dma_queue); + std::swap(_dmadesc_size, _dma_queue_capacity); + + dc_control(true); + *_spi_dma_out_link_reg = 0; + +#if defined ( SOC_GDMA_SUPPORTED ) + *_spi_dma_out_link_reg = DMA_OUTLINK_START_CH0 | ((int)(&_dmadesc[0]) & 0xFFFFF); + auto dma = reg(SPI_DMA_CONF_REG(_spi_port)); + *dma = SPI_DMA_TX_ENA; + _clear_dma_reg = dma; + uint32_t len = ((_dma_queue_bytes - 1) & ((SPI_MS_DATA_BITLEN)>>3)) + 1; +#else + auto dma_conf_reg = reg(SPI_DMA_CONF_REG(_spi_port)); + auto dma_conf = *dma_conf_reg & ~(SPI_OUT_DATA_BURST_EN | SPI_AHBM_RST | SPI_AHBM_FIFO_RST | SPI_OUT_RST); + dma_conf |= SPI_OUTDSCR_BURST_EN; + *dma_conf_reg = dma_conf | SPI_AHBM_RST | SPI_AHBM_FIFO_RST | SPI_OUT_RST; + *dma_conf_reg = dma_conf; + + *_spi_dma_out_link_reg = SPI_OUTLINK_START | ((int)(&_dmadesc[0]) & 0xFFFFF); + _clear_dma_reg = _spi_dma_out_link_reg; + uint32_t len = _dma_queue_bytes; + _dma_queue_bytes = 0; +#endif + + set_write_len(len << 3); + // DMA準備完了待ち; +#if defined ( SOC_GDMA_SUPPORTED ) + while (*_spi_dma_outstatus_reg & DMA_OUTFIFO_EMPTY_CH0 ) {} +#elif defined (SPI_DMA_OUTFIFO_EMPTY) + while (*_spi_dma_outstatus_reg & SPI_DMA_OUTFIFO_EMPTY ) {} +#else + #if defined ( LGFX_SPIDMA_WORKAROUND ) + if (_dma_ch) { spicommon_dmaworkaround_transfer_active(_dma_ch); } + #endif +#endif + exec_spi(); + +#if defined ( SOC_GDMA_SUPPORTED ) + uint32_t length = _dma_queue_bytes - len; + _dma_queue_bytes = 0; + if (length) + { + wait_spi(); + set_write_len(SPI_MS_DATA_BITLEN + 1); + goto label_start; + do + { + wait_spi(); +label_start: + exec_spi(); + } while (length -= ((SPI_MS_DATA_BITLEN + 1) >> 3)); + } +#endif + } + + + + void Bus_QSPI::beginRead(uint_fast8_t dummy_bits) + { + beginRead(); + if (!dummy_bits) { return; } + +#if defined ( SPI_UPDATE ) // for C3/S3 + + /// ESP32-C3とS3は、1bitの送受信ができないため、CPOLの極性を反転させてダミークロックを生成する。; + if (dummy_bits == 1) + { + auto pin_reg = reg(SPI_PIN_REG(_spi_port)); + auto cmd_reg = _spi_cmd_reg; + auto value = *pin_reg; + *pin_reg = value ^ SPI_CK_IDLE_EDGE; + *cmd_reg = SPI_UPDATE; + *pin_reg = value; + *cmd_reg = SPI_UPDATE; + return; + } + +#endif + + readData(dummy_bits); + } + + void Bus_QSPI::beginRead(void) + { + uint32_t pin = (_cfg.spi_mode & 2) ? SPI_CK_IDLE_EDGE : 0; + uint32_t user = ((_cfg.spi_mode == 1 || _cfg.spi_mode == 2) ? SPI_CK_OUT_EDGE | SPI_USR_MISO : SPI_USR_MISO) + | (_cfg.spi_3wire ? SPI_SIO : 0); + dc_control(true); + *_spi_user_reg = user; + *reg(SPI_PIN_REG(_spi_port)) = pin; + *reg(SPI_CLOCK_REG(_spi_port)) = _clkdiv_read; +#if defined ( SPI_UPDATE ) + *_spi_cmd_reg = SPI_UPDATE; +#endif + } + + void Bus_QSPI::endRead(void) + { + uint32_t pin = (_cfg.spi_mode & 2) ? SPI_CK_IDLE_EDGE : 0; + *_spi_user_reg = _user_reg; + *reg(SPI_PIN_REG(_spi_port)) = pin; + *reg(SPI_CLOCK_REG(_spi_port)) = _clkdiv_write; +#if defined ( SPI_UPDATE ) + *_spi_cmd_reg = SPI_UPDATE; +#endif + } + + uint32_t Bus_QSPI::readData(uint_fast8_t bit_length) + { + set_read_len(bit_length); + auto spi_cmd_reg = _spi_cmd_reg; + *spi_cmd_reg = SPI_EXECUTE; + auto spi_w0_reg = _spi_w0_reg; + uint32_t mask = (32 > bit_length) ? ~getSwap32((1 << (32 - bit_length))-1) : ~0; + while (*spi_cmd_reg & SPI_USR); + return *spi_w0_reg & mask; + } + + bool Bus_QSPI::readBytes(uint8_t* dst, uint32_t length, bool use_dma) + { +#if defined ( SPI_DMA_IN_LINK_REG ) + if (_cfg.dma_channel && use_dma) { + wait_spi(); + set_read_len(length << 3); + + auto dma_conf_reg = reg(SPI_DMA_CONF_REG(_spi_port)); + auto dma_conf = *dma_conf_reg & ~(SPI_AHBM_RST | SPI_AHBM_FIFO_RST | SPI_OUT_RST); + *dma_conf_reg = dma_conf | SPI_AHBM_RST | SPI_AHBM_FIFO_RST | SPI_IN_RST; + *dma_conf_reg = dma_conf | SPI_INDSCR_BURST_EN; + + _setup_dma_desc_links(dst, length); + *reg(SPI_DMA_IN_LINK_REG(_spi_port)) = SPI_INLINK_START | ((int)(&_dmadesc[0]) & 0xFFFFF); + #if defined ( LGFX_SPIDMA_WORKAROUND ) + if (_dma_ch) { spicommon_dmaworkaround_transfer_active(_dma_ch); } + #endif + exec_spi(); + } + else +#endif + { + auto len1 = std::min(length, 32u); // 32 Byte read. + auto len2 = len1; + wait_spi(); + set_read_len(len1 << 3); + exec_spi(); + +/// ESP32-C3 で HIGHPART を使用すると異常動作するため分岐する; +#if defined ( SPI_UPDATE ) // for C3/S3 + + auto spi_w0_reg = _spi_w0_reg; + do { + if (0 == (length -= len1)) { + len2 = len1; + wait_spi(); + memcpy(dst, (void*)spi_w0_reg, (len2 + 3) & ~3u); + } else { + if (length < len1) { + len1 = length; + wait_spi(); + set_read_len(len1 << 3); + } else { + wait_spi(); + } + memcpy(dst, (void*)spi_w0_reg, (len2 + 3) & ~3u); + exec_spi(); + } + dst += len2; + } while (length); + +#else + + uint32_t userreg = *_spi_user_reg; + uint32_t highpart = 8; + auto spi_w0_reg = _spi_w0_reg; + do { + if (0 == (length -= len1)) { + len2 = len1; + wait_spi(); + *_spi_user_reg = userreg; + } else { + uint32_t user = userreg; + if (highpart) user = userreg | SPI_USR_MISO_HIGHPART; + if (length < len1) { + len1 = length; + wait_spi(); + set_read_len(len1 << 3); + } else { + wait_spi(); + } + *_spi_user_reg = user; + exec_spi(); + } + memcpy(dst, (void*)&spi_w0_reg[highpart ^= 8], (len2+3)&~3u); + dst += len2; + } while (length); + +#endif + + } + return true; + } + + void Bus_QSPI::readPixels(void* dst, pixelcopy_t* param, uint32_t length) + { + auto len1 = std::min(length, 10u); // 10 pixel read + auto len2 = len1; + auto len_read_pixel = param->src_bits; + uint32_t regbuf[8]; + wait_spi(); + set_read_len(len_read_pixel * len1); + exec_spi(); + param->src_data = regbuf; + int32_t dstindex = 0; + auto spi_w0_reg = _spi_w0_reg; + +/// ESP32-C3 で HIGHPART を使用すると異常動作するため分岐する; +#if defined ( SPI_UPDATE ) // for C3/S3 + + do { + if (0 == (length -= len1)) { + len2 = len1; + wait_spi(); + memcpy(regbuf, (void*)spi_w0_reg, ((len2 * len_read_pixel >> 3) + 3) & ~3); + } else { + if (length < len1) { + len1 = length; + wait_spi(); + set_read_len(len_read_pixel * len1); + } else { + wait_spi(); + } + memcpy(regbuf, (void*)spi_w0_reg, ((len2 * len_read_pixel >> 3) + 3) & ~3); + exec_spi(); + } + param->src_x = 0; + dstindex = param->fp_copy(dst, dstindex, dstindex + len2, param); + } while (length); + +#else + + uint32_t userreg = *_spi_user_reg; + uint32_t highpart = 8; + do { + if (0 == (length -= len1)) { + len2 = len1; + wait_spi(); + *_spi_user_reg = userreg; + } else { + uint32_t user = userreg; + if (highpart) user = userreg | SPI_USR_MISO_HIGHPART; + if (length < len1) { + len1 = length; + wait_spi(); + set_read_len(len_read_pixel * len1); + } else { + wait_spi(); + } + *_spi_user_reg = user; + exec_spi(); + } + memcpy(regbuf, (void*)&spi_w0_reg[highpart ^= 8], ((len2 * len_read_pixel >> 3)+3)&~3u); + param->src_x = 0; + dstindex = param->fp_copy(dst, dstindex, dstindex + len2, param); + } while (length); + +#endif + + } + + void Bus_QSPI::_alloc_dmadesc(size_t len) + { + if (_dmadesc) heap_caps_free(_dmadesc); + _dmadesc_size = len; + _dmadesc = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t) * len, MALLOC_CAP_DMA); + } + + void Bus_QSPI::_spi_dma_reset(void) + { +#if defined( SOC_GDMA_SUPPORTED ) // for C3/S3 + +#elif defined( CONFIG_IDF_TARGET_ESP32S2 ) + if (_cfg.spi_host == SPI2_HOST) + { + periph_module_reset( PERIPH_SPI2_DMA_MODULE ); + } + else if (_cfg.spi_host == SPI3_HOST) + { + periph_module_reset( PERIPH_SPI3_DMA_MODULE ); + } +#else + periph_module_reset( PERIPH_SPI_DMA_MODULE ); +#endif + } + + void Bus_QSPI::_setup_dma_desc_links(const uint8_t *data, int32_t len) + { //spicommon_setup_dma_desc_links + if (!_cfg.dma_channel) return; + + if (_dmadesc_size * SPI_MAX_DMA_LEN < len) + { + _alloc_dmadesc(len / SPI_MAX_DMA_LEN + 1); + } + lldesc_t *dmadesc = _dmadesc; + + while (len > SPI_MAX_DMA_LEN) + { + len -= SPI_MAX_DMA_LEN; + dmadesc->buf = (uint8_t *)data; + data += SPI_MAX_DMA_LEN; + *(uint32_t*)dmadesc = SPI_MAX_DMA_LEN | SPI_MAX_DMA_LEN << 12 | 0x80000000; + dmadesc->qe.stqe_next = dmadesc + 1; + dmadesc++; + } + *(uint32_t*)dmadesc = ((len + 3) & ( ~3 )) | len << 12 | 0xC0000000; + dmadesc->buf = (uint8_t *)data; + dmadesc->qe.stqe_next = nullptr; + } + +//---------------------------------------------------------------------------- + } +} + +#endif diff --git a/src/lgfx/v1/platforms/esp32/Bus_QSPI.hpp b/src/lgfx/v1/platforms/esp32/Bus_QSPI.hpp new file mode 100644 index 00000000..656dd43d --- /dev/null +++ b/src/lgfx/v1/platforms/esp32/Bus_QSPI.hpp @@ -0,0 +1,208 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#pragma once + +#include + +#if __has_include() + #include +#else + #include +#endif + +#if __has_include() + // ESP-IDF v5 + #include +#elif __has_include() + // ESP-IDF v4 + #include +#endif + +#include +#include + +#if __has_include() + #include + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) + #define LGFX_ESP32_SPI_DMA_CH SPI_DMA_CH_AUTO + #endif +#endif + +#ifndef LGFX_ESP32_SPI_DMA_CH +#define LGFX_ESP32_SPI_DMA_CH 0 +#endif + +#include "../../Bus.hpp" +#include "../common.hpp" + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + + class Bus_QSPI : public IBus + { +#if !defined (SPI_MOSI_DLEN_REG) + static constexpr uint32_t SPI_EXECUTE = SPI_USR | SPI_UPDATE; + #define SPI_MOSI_DLEN_REG(i) (REG_SPI_BASE(i) + 0x1C) + #define SPI_MISO_DLEN_REG(i) (REG_SPI_BASE(i) + 0x1C) +#else + static constexpr uint32_t SPI_EXECUTE = SPI_USR; +#endif + public: + struct config_t + { + // max80MHz , 40MHz , 26.67MHz , 20MHz , 16MHz , and more ... + uint32_t freq_write = 16000000; + uint32_t freq_read = 8000000; + int16_t pin_sclk = -1; + // int16_t pin_miso = -1; + // int16_t pin_mosi = -1; + int16_t pin_io0 = -1; + int16_t pin_io1 = -1; + int16_t pin_io2 = -1; + int16_t pin_io3 = -1; + int16_t pin_dc = -1; + uint8_t spi_mode = 0; + bool spi_3wire = true; + bool use_lock = true; + uint8_t dma_channel = LGFX_ESP32_SPI_DMA_CH; +#if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + spi_host_device_t spi_host = VSPI_HOST; +#else + spi_host_device_t spi_host = SPI2_HOST; +#endif + }; + + constexpr Bus_QSPI(void) = default; + + const config_t& config(void) const { return _cfg; } + + void config(const config_t& config); + + bus_type_t busType(void) const override { return bus_type_t::bus_spi; } + + bool init(void) override; + void release(void) override; + + void beginTransaction(void) override; + void endTransaction(void) override; + void wait(void) override; + bool busy(void) const override; + uint32_t getClock(void) const override { return _cfg.freq_write; } + void setClock(uint32_t freq) override { if (_cfg.freq_write != freq) { _cfg.freq_write = freq; _last_freq_apb = 0; } } + uint32_t getReadClock(void) const override { return _cfg.freq_read; } + void setReadClock(uint32_t freq) override { if (_cfg.freq_read != freq) { _cfg.freq_read = freq; _last_freq_apb = 0; } } + + void flush(void) override {} + bool writeCommand(uint32_t data, uint_fast8_t bit_length) override; + void writeData(uint32_t data, uint_fast8_t bit_length) override; + // void writeDataQuad(uint32_t data, uint_fast8_t bit_length) override; + void writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) override; + // void writeDataRepeatQuad(uint32_t data, uint_fast8_t bit_length, uint32_t count) override; + void writePixels(pixelcopy_t* pc, uint32_t length) override; + void writeBytes(const uint8_t* data, uint32_t length, bool dc, bool use_dma) override; + // void writeBytesQuad(const uint8_t* data, uint32_t length, bool dc, bool use_dma) override; + + + + void initDMA(void) override {} + void addDMAQueue(const uint8_t* data, uint32_t length) override; + // void addDMAQueueQuad(const uint8_t* data, uint32_t length); + void execDMAQueue(void) override; + // void execDMAQueueQuad(void); + uint8_t* getDMABuffer(uint32_t length) override { return _flip_buffer.getBuffer(length); } + + void beginRead(uint_fast8_t dummy_bits) override; + void beginRead(void) override; + void endRead(void) override; + uint32_t readData(uint_fast8_t bit_length) override; + bool readBytes(uint8_t* dst, uint32_t length, bool use_dma) override; + void readPixels(void* dst, pixelcopy_t* pc, uint32_t length) override; + + private: + + static __attribute__ ((always_inline)) inline volatile uint32_t* reg(uint32_t addr) { return (volatile uint32_t *)ETS_UNCACHED_ADDR(addr); } + __attribute__ ((always_inline)) inline void exec_spi(void) { *_spi_cmd_reg = SPI_EXECUTE; } + __attribute__ ((always_inline)) inline void wait_spi(void) { while (*_spi_cmd_reg & SPI_USR); } + __attribute__ ((always_inline)) inline void set_write_len(uint32_t bitlen) { *_spi_mosi_dlen_reg = bitlen - 1; } + __attribute__ ((always_inline)) inline void set_read_len( uint32_t bitlen) { *reg(SPI_MISO_DLEN_REG(_spi_port)) = bitlen - 1; } + + void dc_control(bool flg) + { + auto reg = _gpio_reg_dc[flg]; + auto mask = _mask_reg_dc; + auto spi_cmd_reg = _spi_cmd_reg; +#if !defined ( CONFIG_IDF_TARGET ) || defined ( CONFIG_IDF_TARGET_ESP32 ) + while (*spi_cmd_reg & SPI_USR) {} // wait SPI +#else + #if defined ( SOC_GDMA_SUPPORTED ) + auto dma = _clear_dma_reg; + if (dma) + { + _clear_dma_reg = nullptr; + while (*spi_cmd_reg & SPI_USR) {} // wait SPI + *dma = 0; + } + else + { + while (*spi_cmd_reg & SPI_USR) {} // wait SPI + } + #else + auto dma = _spi_dma_out_link_reg; + while (*spi_cmd_reg & SPI_USR) {} // wait SPI + *dma = 0; + #endif +#endif + *reg = mask; + } + + void _alloc_dmadesc(size_t len); + void _spi_dma_reset(void); + void _setup_dma_desc_links(const uint8_t *data, int32_t len); + + config_t _cfg; + FlipBuffer _flip_buffer; + volatile uint32_t* _gpio_reg_dc[2] = { nullptr, nullptr }; + volatile uint32_t* _spi_mosi_dlen_reg = nullptr; + volatile uint32_t* _spi_w0_reg = nullptr; + volatile uint32_t* _spi_cmd_reg = nullptr; + volatile uint32_t* _spi_user_reg = nullptr; + volatile uint32_t* _spi_dma_out_link_reg = nullptr; + volatile uint32_t* _spi_dma_outstatus_reg = nullptr; + volatile uint32_t* _clear_dma_reg = nullptr; + uint32_t _last_freq_apb = 0; + uint32_t _clkdiv_write = 0; + uint32_t _clkdiv_read = 0; + uint32_t _user_reg = 0; + uint32_t _mask_reg_dc = 0; + uint32_t _dma_queue_bytes = 0; + lldesc_t* _dmadesc = nullptr; + uint32_t _dmadesc_size = 0; + lldesc_t* _dma_queue = nullptr; + uint32_t _dma_queue_size = 0; + uint32_t _dma_queue_capacity = 0; + uint8_t _spi_port = 0; + uint8_t _dma_ch = 0; + bool _inited = false; + }; + +//---------------------------------------------------------------------------- + } +} diff --git a/src/lgfx/v1/platforms/esp32/common.cpp b/src/lgfx/v1/platforms/esp32/common.cpp index ceddcb16..9ca798ab 100644 --- a/src/lgfx/v1/platforms/esp32/common.cpp +++ b/src/lgfx/v1/platforms/esp32/common.cpp @@ -349,6 +349,88 @@ namespace lgfx return {}; } + cpp::result initQuad(int spi_host, int spi_sclk, int spi_io0, int spi_io1, int spi_io2, int spi_io3) + { + return initQuad(spi_host, spi_sclk, spi_io0, spi_io1, spi_io2, spi_io3, 0); // SPI_DMA_CH_AUTO; + } + + cpp::result initQuad(int spi_host, int spi_sclk, int spi_io0, int spi_io1, int spi_io2, int spi_io3, int dma_channel) + { +//ESP_LOGI("LGFX","spi::init host:%d, sclk:%d, miso:%d, mosi:%d, dma:%d", spi_host, spi_sclk, spi_miso, spi_mosi, dma_channel); + uint32_t spi_port = (spi_host + 1); + (void)spi_port; + + if (spi_sclk >= 0) { + gpio_lo(spi_sclk); // ここでLOWにしておくことで、pinMode変更によるHIGHパルスが出力されるのを防止する (CSなしパネル対策); + } +#if defined (ARDUINO) // Arduino ESP32 + if (spi_host == default_spi_host) + { + SPI.end(); + SPI.begin(spi_sclk, spi_miso, spi_mosi); + _spi_handle[spi_host] = SPI.bus(); + } + if (_spi_handle[spi_host] == nullptr) + { + _spi_handle[spi_host] = spiStartBus(spi_port, SPI_CLK_EQU_SYSCLK, 0, 0); + } + +#endif + + // バスの設定にはESP-IDFのSPIドライバを使用する。; + if (_spi_dev_handle[spi_host] == nullptr) + { + spi_bus_config_t buscfg; + memset(&buscfg, ~0u, sizeof(spi_bus_config_t)); + // buscfg.mosi_io_num = spi_mosi; + // buscfg.miso_io_num = spi_miso; + buscfg.data0_io_num = spi_io0; + buscfg.data1_io_num = spi_io1; + buscfg.data2_io_num = spi_io2; + buscfg.data3_io_num = spi_io3; + + buscfg.sclk_io_num = spi_sclk; + buscfg.max_transfer_sz = 1; + buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_QUAD; + buscfg.intr_flags = 0; + + if (ESP_OK != spi_bus_initialize(static_cast(spi_host), &buscfg, dma_channel)) + { + ESP_LOGW("LGFX", "Failed to spi_bus_initialize. "); + } + + spi_device_interface_config_t devcfg = { + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .mode = 0, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = (int)getApbFrequency()>>1, + .input_delay_ns = 0, + .spics_io_num = -1, + .flags = SPI_DEVICE_3WIRE | SPI_DEVICE_HALFDUPLEX, + .queue_size = 1, + .pre_cb = nullptr, + .post_cb = nullptr}; + if (ESP_OK != spi_bus_add_device(static_cast(spi_host), &devcfg, &_spi_dev_handle[spi_host])) { + ESP_LOGW("LGFX", "Failed to spi_bus_add_device. "); + } + } + + *reg(SPI_USER_REG(spi_port)) = SPI_USR_MOSI | SPI_USR_MISO | SPI_DOUTDIN; // need SD card access (full duplex setting) + *reg(SPI_CTRL_REG(spi_port)) = 0; +#if defined ( SPI_CTRL1_REG ) + *reg(SPI_CTRL1_REG(spi_port)) = 0; +#endif +#if defined ( SPI_CTRL2_REG ) + *reg(SPI_CTRL2_REG(spi_port)) = 0; +#endif + + return {}; + } + void release(int spi_host) { //ESP_LOGI("LGFX","spi::release"); diff --git a/src/lgfx/v1/platforms/esp32/common.hpp b/src/lgfx/v1/platforms/esp32/common.hpp index 5ca9789f..b74733d0 100644 --- a/src/lgfx/v1/platforms/esp32/common.hpp +++ b/src/lgfx/v1/platforms/esp32/common.hpp @@ -231,6 +231,7 @@ namespace lgfx namespace spi { cpp::result init(int spi_host, int spi_sclk, int spi_miso, int spi_mosi, int dma_channel); + cpp::result initQuad(int spi_host, int spi_sclk, int spi_io0, int spi_io1, int spi_io2, int spi_io3, int dma_channel); void beginTransaction(int spi_host); } diff --git a/src/lgfx/v1_init.hpp b/src/lgfx/v1_init.hpp index a39fce13..7be7a908 100644 --- a/src/lgfx/v1_init.hpp +++ b/src/lgfx/v1_init.hpp @@ -55,6 +55,7 @@ Original Source: // other #include "v1/panel/Panel_HUB75.hpp" #include "v1/panel/Panel_M5UnitLCD.hpp" +#include "v1/panel/Panel_SH8601Z.hpp" // TouchScreen #include "v1/touch/Touch_FT5x06.hpp"