From f316c915cd763dcf3d5a5caae7a8a14d8d813299 Mon Sep 17 00:00:00 2001 From: tobozo Date: Tue, 20 Aug 2024 12:16:36 +0200 Subject: [PATCH] backported QSPI from PR #388 --- src/lgfx/v1/Bus.hpp | 1 + src/lgfx/v1/panel/Panel_SH8601Z.cpp | 596 ++++++++++++ src/lgfx/v1/panel/Panel_SH8601Z.hpp | 73 ++ src/lgfx/v1/platforms/device.hpp | 2 + src/lgfx/v1/platforms/esp32/Bus_QSPI.cpp | 1103 ++++++++++++++++++++++ src/lgfx/v1/platforms/esp32/Bus_QSPI.hpp | 214 +++++ src/lgfx/v1/platforms/esp32/Bus_SPI.hpp | 2 +- src/lgfx/v1/platforms/esp32/common.cpp | 94 ++ src/lgfx/v1/platforms/esp32/common.hpp | 1 + src/lgfx/v1_init.hpp | 1 + src/lgfx_user/LGFX_Monica.hpp | 107 +++ 11 files changed, 2193 insertions(+), 1 deletion(-) create mode 100644 src/lgfx/v1/panel/Panel_SH8601Z.cpp create mode 100644 src/lgfx/v1/panel/Panel_SH8601Z.hpp create mode 100644 src/lgfx/v1/platforms/esp32/Bus_QSPI.cpp create mode 100644 src/lgfx/v1/platforms/esp32/Bus_QSPI.hpp create mode 100644 src/lgfx_user/LGFX_Monica.hpp diff --git a/src/lgfx/v1/Bus.hpp b/src/lgfx/v1/Bus.hpp index c5baa5fa..5f674b3c 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..9a7db94c --- /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..4eeeade4 --- /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 409cadf5..7bceb147 100644 --- a/src/lgfx/v1/platforms/device.hpp +++ b/src/lgfx/v1/platforms/device.hpp @@ -38,6 +38,7 @@ Original Source: #include "esp32/Light_PWM.hpp" #include "esp32/Bus_SPI.hpp" + #include "esp32/Bus_QSPI.hpp" #include "esp32/Bus_I2C.hpp" #include "esp32s2/Bus_Parallel8.hpp" #include "esp32s2/Bus_Parallel16.hpp" @@ -46,6 +47,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" 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..8cb71164 --- /dev/null +++ b/src/lgfx/v1/platforms/esp32/Bus_QSPI.cpp @@ -0,0 +1,1103 @@ +/*----------------------------------------------------------------------------/ + * 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 + +#include "Bus_QSPI.hpp" + +/// ESP32-S3をターゲットにした際にREG_SPI_BASEが定義されていなかったので応急処置 ; +#if defined ( CONFIG_IDF_TARGET_ESP32S3 ) + #if !defined( REG_SPI_BASE ) + #define REG_SPI_BASE(i) (DR_REG_SPI1_BASE + (((i)>1) ? (((i)* 0x1000) + 0x20000) : (((~(i)) & 1)* 0x1000 ))) + #endif +#elif defined ( CONFIG_IDF_TARGET_ESP32 ) || !defined ( CONFIG_IDF_TARGET ) + #define LGFX_SPIDMA_WORKAROUND +#endif + +#include "../../misc/pixelcopy.hpp" + +#if __has_include () + #include +#endif + +#include +#include +#include + +#if __has_include () + #include +#else + #include +#endif + +#if defined (ARDUINO) // Arduino ESP32 + #include + #include +#endif +#include + +#if defined ESP_IDF_VERSION_MAJOR && ESP_IDF_VERSION_MAJOR >= 5 + #include // dispatched by core +#elif defined ( CONFIG_IDF_TARGET_ESP32S3 ) && __has_include () + #include // dispatched by config +#elif defined ( CONFIG_IDF_TARGET_ESP32S2 ) && __has_include () + #include // dispatched by config +#elif defined ( CONFIG_IDF_TARGET_ESP32 ) && __has_include () + #include +#else + #include // dispatched by core +#endif + +#ifndef SPI_PIN_REG + #define SPI_PIN_REG SPI_MISC_REG +#endif + +#if defined (SOC_GDMA_SUPPORTED) // for C3/C6/S3 + #include + #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 + #if defined (GDMA_OUTFIFO_EMPTY_L3_CH0) + #define DMA_OUTFIFO_EMPTY_CH0 GDMA_OUTFIFO_EMPTY_L3_CH0 + #else + #define DMA_OUTFIFO_EMPTY_CH0 GDMA_OUTFIFO_EMPTY_CH0 + #endif + #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..e9f91605 --- /dev/null +++ b/src/lgfx/v1/platforms/esp32/Bus_QSPI.hpp @@ -0,0 +1,214 @@ +/*----------------------------------------------------------------------------/ + * 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 defined (CONFIG_IDF_TARGET_ESP32S3) && __has_include() + #include +#elif defined (CONFIG_IDF_TARGET_ESP32S2) && __has_include() + #include +#elif defined (CONFIG_IDF_TARGET_ESP32C3) && __has_include() + #include +#elif __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/Bus_SPI.hpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp index 3d205912..53a529c5 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp @@ -50,7 +50,7 @@ Original Source: #endif #ifndef LGFX_ESP32_SPI_DMA_CH -#define LGFX_ESP32_SPI_DMA_CH 0 + #define LGFX_ESP32_SPI_DMA_CH 0 #endif #include "../../Bus.hpp" diff --git a/src/lgfx/v1/platforms/esp32/common.cpp b/src/lgfx/v1/platforms/esp32/common.cpp index ffd53fe7..b019a1a9 100644 --- a/src/lgfx/v1/platforms/esp32/common.cpp +++ b/src/lgfx/v1/platforms/esp32/common.cpp @@ -531,6 +531,100 @@ 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 + #pragma message "Quad SPI.begin() postponed to core 3.1.0, see https://github.com/espressif/arduino-esp32/issues/7063#issuecomment-1954558942" + // also see https://github.com/moononournation/Arduino_GFX/blob/master/src/databus/Arduino_ESP32QSPI.cpp / hpp + // + // 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 33ccc432..37550496 100644 --- a/src/lgfx/v1/platforms/esp32/common.hpp +++ b/src/lgfx/v1/platforms/esp32/common.hpp @@ -258,6 +258,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 a7fd0fc7..d04f922e 100644 --- a/src/lgfx/v1_init.hpp +++ b/src/lgfx/v1_init.hpp @@ -48,6 +48,7 @@ Original Source: #include "v1/panel/Panel_ST7735.hpp" #include "v1/panel/Panel_ST7789.hpp" #include "v1/panel/Panel_ST7796.hpp" +#include "v1/panel/Panel_SH8601Z.hpp" // EPD #include "v1/panel/Panel_GDEW0154M09.hpp" diff --git a/src/lgfx_user/LGFX_Monica.hpp b/src/lgfx_user/LGFX_Monica.hpp new file mode 100644 index 00000000..128ae577 --- /dev/null +++ b/src/lgfx_user/LGFX_Monica.hpp @@ -0,0 +1,107 @@ +/** + * @file hal_disp.hpp + * @author Forairaaaaa + * @brief + * @version 0.1 + * @date 2023-05-20 + * + * @copyright Copyright (c) 2023 + * + */ +#pragma once +#include + + +/// 独自の設定を行うクラスを、LGFX_Deviceから派生して作成します。 +class LGFX_Monica : public lgfx::LGFX_Device { + // 接続するパネルの型にあったインスタンスを用意します。 + lgfx::Panel_SH8601Z _panel_instance; + // パネルを接続するバスの種類にあったインスタンスを用意します。 + lgfx::Bus_QSPI _bus_instance; + +public: + // コンストラクタを作成し、ここで各種設定を行います。 + // クラス名を変更した場合はコンストラクタも同じ名前を指定してください。 + LGFX_Monica(void) + { + { // バス制御の設定を行います。 + auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。 + + // SPIバスの設定 + cfg.spi_host = SPI3_HOST; // 使用するSPIを選択 ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST + // ※ ESP-IDFバージョンアップに伴い、VSPI_HOST , HSPI_HOSTの記述は非推奨になるため、エラーが出る場合は代わりにSPI2_HOST , SPI3_HOSTを使用してください。 + cfg.spi_mode = 1; // SPI通信モードを設定 (0 ~ 3) + // cfg.freq_write = 1*1000*1000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます) + // cfg.freq_write = 10*1000*1000; + cfg.freq_write = 40*1000*1000; + cfg.freq_read = 16000000; // 受信時のSPIクロック + cfg.spi_3wire = true; // 受信をMOSIピンで行う場合はtrueを設定 + cfg.use_lock = true; // トランザクションロックを使用する場合はtrueを設定 + cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設 定) + + cfg.pin_sclk = 7; + cfg.pin_io0 = 9; + cfg.pin_io1 = 8; + cfg.pin_io2 = 5; + cfg.pin_io3 = 6; + // cfg.pin_dc = -1; + + _bus_instance.config(cfg); // 設定値をバスに反映します。 + _panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。 + } + + { // 表示パネル制御の設定を行います。 + auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。 + + cfg.pin_cs = 13; // CSが接続されているピン番号 (-1 = disable) + cfg.pin_rst = 1; // RSTが接続されているピン番号 (-1 = disable) + cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable) + + // ※ 以下の設定値はパネル毎に一般的な初期値が設定されていますので、不明な項目はコメントアウトして試してみてください。 + + cfg.panel_width = 368; // 実際に表示可能な幅 + cfg.panel_height = 448; // 実際に表示可能な高さ + + // cfg.panel_width = 320; // 実際に表示可能な幅 + // cfg.panel_height = 240; // 実際に表示可能な高さ + + + cfg.offset_x = 0; // パネルのX方向オフセット量 + cfg.offset_y = 0; // パネルのY方向オフセット量 + cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転) + cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数 + cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数 + cfg.readable = true; // データ読出しが可能な場合 trueに設定 + cfg.invert = true; // パネルの明暗が反転してしまう場合 trueに設定 + cfg.rgb_order = true; // パネルの赤と青が入れ替わってしまう場合 trueに設定 + cfg.dlen_16bit = false; // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定 + cfg.bus_shared = true; // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います) + + // 以下はST7735やILI9163のようにピクセル数が可変のドライバで表示がずれる場合にのみ設定してください。 + cfg.memory_width = 480; // ドライバICがサポートしている最大の幅 + cfg.memory_height = 480; // ドライバICがサポートしている最大の高さ + + _panel_instance.config(cfg); + } + setPanel(&_panel_instance); // 使用するパネルをセットします。 + } + + + inline bool init(void) { + + /* PEN pin */ + gpio_reset_pin(GPIO_NUM_4); + gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(GPIO_NUM_4, GPIO_PULLUP_PULLDOWN); + gpio_set_level(GPIO_NUM_4, 1); + // vTaskDelay(pdMS_TO_TICKS(10)); + + /* TE pin */ + gpio_reset_pin(GPIO_NUM_2); + gpio_set_direction(GPIO_NUM_2, GPIO_MODE_INPUT); + // vTaskDelay(pdMS_TO_TICKS(10)); + + /* Lgfx */ + return lgfx::LGFX_Device::init(); + } +};