diff --git a/examples/Basic/AnalogMeter/AnalogMeter.ino b/examples/Basic/AnalogMeter/AnalogMeter.ino index 1897f0a5..548adb94 100644 --- a/examples/Basic/AnalogMeter/AnalogMeter.ino +++ b/examples/Basic/AnalogMeter/AnalogMeter.ino @@ -20,9 +20,12 @@ M5GFX display; //M5AtomDisplay display; // default setting //M5AtomDisplay display ( 320, 180 ); // width, height +// #include +// M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 + static constexpr float deg_to_rad = 0.017453292519943295769236907684886; -static constexpr int TFT_GREY = 0x5AEB; +static constexpr int TFT_METER_GREY = 0x5AEB; static constexpr int LOOP_PERIOD = 35; // Display updates every 35 ms int value[6] = {0, 0, 0, 0, 0, 0}; @@ -111,7 +114,7 @@ void analogMeter() void plotLinear(const char *label, int x, int y, int w, int h) { - display.drawRect(x, y, w, h, TFT_GREY); + display.drawRect(x, y, w, h, TFT_METER_GREY); display.fillRect(x + 2, y + 18, w - 3, h - 36, TFT_WHITE); display.setTextColor(TFT_CYAN, TFT_BLACK); display.setTextDatum(textdatum_t::middle_center); diff --git a/examples/Basic/BarGraph/BarGraph.ino b/examples/Basic/BarGraph/BarGraph.ino index 32a91acb..d9b9eb71 100644 --- a/examples/Basic/BarGraph/BarGraph.ino +++ b/examples/Basic/BarGraph/BarGraph.ino @@ -17,6 +17,9 @@ M5GFX display; // #include // M5AtomDisplay display; +//#include +//M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 + static constexpr size_t BAR_COUNT = 64; static int max_y[BAR_COUNT]; static int prev_y[BAR_COUNT]; diff --git a/examples/Basic/GameOfLife/GameOfLife.ino b/examples/Basic/GameOfLife/GameOfLife.ino index 7107a151..0993dd6f 100644 --- a/examples/Basic/GameOfLife/GameOfLife.ino +++ b/examples/Basic/GameOfLife/GameOfLife.ino @@ -22,6 +22,9 @@ M5GFX display; //#include //M5AtomDisplay display; +//#include +//M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 + M5Canvas canvas[2]; void setup(void) diff --git a/examples/Basic/LongTextScroll/LongTextScroll.ino b/examples/Basic/LongTextScroll/LongTextScroll.ino index e904c994..c871f269 100644 --- a/examples/Basic/LongTextScroll/LongTextScroll.ino +++ b/examples/Basic/LongTextScroll/LongTextScroll.ino @@ -17,6 +17,9 @@ M5GFX display; //#include //M5AtomDisplay display; +//#include +//M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 + M5Canvas canvas(&display); static constexpr char text[] = "Hello world ! こんにちは世界! this is long long string sample. 寿限無、寿限無、五劫の擦り切れ、海砂利水魚の、水行末・雲来末・風来末、喰う寝る処に住む処、藪ら柑子の藪柑子、パイポ・パイポ・パイポのシューリンガン、シューリンガンのグーリンダイ、グーリンダイのポンポコピーのポンポコナの、長久命の長助"; diff --git a/examples/Basic/ScrollGraph/ScrollGraph.ino b/examples/Basic/ScrollGraph/ScrollGraph.ino index 91f4ea34..14914d52 100644 --- a/examples/Basic/ScrollGraph/ScrollGraph.ino +++ b/examples/Basic/ScrollGraph/ScrollGraph.ino @@ -20,6 +20,9 @@ M5GFX display; //#include //M5AtomDisplay display; +//#include +//M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 + #define LINE_COUNT 6 diff --git a/examples/Basic/SpinTile/SpinTile.ino b/examples/Basic/SpinTile/SpinTile.ino index 3a902c1b..28f098a2 100644 --- a/examples/Basic/SpinTile/SpinTile.ino +++ b/examples/Basic/SpinTile/SpinTile.ino @@ -18,6 +18,9 @@ M5GFX display; //M5AtomDisplay display; // default setting //M5AtomDisplay display ( 320, 180 ); // width, height +//#include +//M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 + static constexpr const int qsintab[256]={ 0x8000,0x80c9,0x8192,0x825b,0x8324,0x83ee,0x84b7,0x8580, 0x8649,0x8712,0x87db,0x88a4,0x896c,0x8a35,0x8afe,0x8bc6, diff --git a/examples/Basic/TFT_graphicstest_PDQ/TFT_graphicstest_PDQ.ino b/examples/Basic/TFT_graphicstest_PDQ/TFT_graphicstest_PDQ.ino index 23224f66..28b8b356 100644 --- a/examples/Basic/TFT_graphicstest_PDQ/TFT_graphicstest_PDQ.ino +++ b/examples/Basic/TFT_graphicstest_PDQ/TFT_graphicstest_PDQ.ino @@ -17,6 +17,9 @@ M5GFX tft; // M5AtomDisplay tft(640, 360); // M5AtomDisplay tft(480, 270); // M5AtomDisplay tft(320, 180); + +// #include +// M5UnitPoEP4HDMI tft; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 /* M5AtomDisplay tft( 240 // width , 360 // height @@ -30,201 +33,6 @@ M5GFX tft; unsigned long total = 0; unsigned long tn = 0; -void setup() { - Serial.begin(115200); - Serial.println(""); Serial.println(""); - Serial.println("M5GFX library Test!"); - - tft.init(); -//tft.setRotation(0); - tft.startWrite(); -} - -void loop(void) -{ - - Serial.println(F("Benchmark Time (microseconds)")); - - uint32_t usecHaD = testHaD(); - Serial.print(F("HaD pushColor ")); - Serial.println(usecHaD); - delay(100); - - uint32_t usecFillScreen = testFillScreen(); - Serial.print(F("Screen fill ")); - Serial.println(usecFillScreen); - delay(100); - - uint32_t usecText = testText(); - Serial.print(F("Text ")); - Serial.println(usecText); - delay(100); - - uint32_t usecPixels = testPixels(); - Serial.print(F("Pixels ")); - Serial.println(usecPixels); - delay(100); - - uint32_t usecLines = testLines(TFT_BLUE); - Serial.print(F("Lines ")); - Serial.println(usecLines); - delay(100); - - uint32_t usecFastLines = testFastLines(TFT_RED, TFT_BLUE); - Serial.print(F("Horiz/Vert Lines ")); - Serial.println(usecFastLines); - delay(100); - - uint32_t usecRects = testRects(TFT_GREEN); - Serial.print(F("Rectangles (outline) ")); - Serial.println(usecRects); - delay(100); - - uint32_t usecFilledRects = testFilledRects(TFT_YELLOW, TFT_MAGENTA); - Serial.print(F("Rectangles (filled) ")); - Serial.println(usecFilledRects); - delay(100); - - uint32_t usecFilledCircles = testFilledCircles(10, TFT_MAGENTA); - Serial.print(F("Circles (filled) ")); - Serial.println(usecFilledCircles); - delay(100); - - uint32_t usecCircles = testCircles(10, TFT_WHITE); - Serial.print(F("Circles (outline) ")); - Serial.println(usecCircles); - delay(100); - - uint32_t usecTriangles = testTriangles(); - Serial.print(F("Triangles (outline) ")); - Serial.println(usecTriangles); - delay(100); - - uint32_t usecFilledTrangles = testFilledTriangles(); - Serial.print(F("Triangles (filled) ")); - Serial.println(usecFilledTrangles); - delay(100); - - uint32_t usecRoundRects = testRoundRects(); - Serial.print(F("Rounded rects (outline) ")); - Serial.println(usecRoundRects); - delay(100); - - uint32_t usedFilledRoundRects = testFilledRoundRects(); - Serial.print(F("Rounded rects (filled) ")); - Serial.println(usedFilledRoundRects); - delay(100); - - Serial.println(F("Done!")); - - uint16_t c = 4; - int8_t d = 1; - for (int32_t i = 0; i < tft.height(); i++) - { - tft.drawFastHLine(0, i, tft.width(), c); - c += d; - if (c <= 4 || c >= 11) - d = -d; - } - - tft.setCursor(0, 0); - tft.setTextColor(TFT_MAGENTA); - tft.setTextSize(2); - - tft.println(F(" M5GFX test")); - - tft.setTextSize(1); - tft.setTextColor(TFT_WHITE); - tft.println(F("")); - tft.setTextSize(1); - tft.println(F("")); - tft.setTextColor(tft.color565(0x80, 0x80, 0x80)); - - tft.println(F("")); - - - tft.setTextColor(TFT_GREEN); - tft.println(F(" Benchmark microseconds")); - tft.println(F("")); - tft.setTextColor(TFT_YELLOW); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("HaD pushColor ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecHaD); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Screen fill ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecFillScreen); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Text ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecText); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Pixels ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecPixels); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Lines ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecLines); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Horiz/Vert Lines ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecFastLines); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Rectangles ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecRects); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Rectangles-filled ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecFilledRects); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Circles ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecCircles); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Circles-filled ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecFilledCircles); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Triangles ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecTriangles); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Triangles-filled ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecFilledTrangles); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Rounded rects ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usecRoundRects); - - tft.setTextColor(TFT_CYAN); tft.setTextSize(1); - tft.print(F("Rounded rects-fill ")); - tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); - printnice(usedFilledRoundRects); - - tft.setTextSize(1); - tft.println(F("")); - tft.setTextColor(TFT_GREEN); tft.setTextSize(2); - tft.print(F("Benchmark Complete!")); - - delay(60 * 1000L); -} void printnice(int32_t v) { @@ -744,6 +552,204 @@ uint32_t testFilledRoundRects() return micros() - start; } + +void setup() { + Serial.begin(115200); + Serial.println(""); Serial.println(""); + Serial.println("M5GFX library Test!"); + + tft.init(); +//tft.setRotation(0); + tft.startWrite(); +} + +void loop(void) +{ + + Serial.println(F("Benchmark Time (microseconds)")); + + uint32_t usecHaD = testHaD(); + Serial.print(F("HaD pushColor ")); + Serial.println(usecHaD); + delay(100); + + uint32_t usecFillScreen = testFillScreen(); + Serial.print(F("Screen fill ")); + Serial.println(usecFillScreen); + delay(100); + + uint32_t usecText = testText(); + Serial.print(F("Text ")); + Serial.println(usecText); + delay(100); + + uint32_t usecPixels = testPixels(); + Serial.print(F("Pixels ")); + Serial.println(usecPixels); + delay(100); + + uint32_t usecLines = testLines(TFT_BLUE); + Serial.print(F("Lines ")); + Serial.println(usecLines); + delay(100); + + uint32_t usecFastLines = testFastLines(TFT_RED, TFT_BLUE); + Serial.print(F("Horiz/Vert Lines ")); + Serial.println(usecFastLines); + delay(100); + + uint32_t usecRects = testRects(TFT_GREEN); + Serial.print(F("Rectangles (outline) ")); + Serial.println(usecRects); + delay(100); + + uint32_t usecFilledRects = testFilledRects(TFT_YELLOW, TFT_MAGENTA); + Serial.print(F("Rectangles (filled) ")); + Serial.println(usecFilledRects); + delay(100); + + uint32_t usecFilledCircles = testFilledCircles(10, TFT_MAGENTA); + Serial.print(F("Circles (filled) ")); + Serial.println(usecFilledCircles); + delay(100); + + uint32_t usecCircles = testCircles(10, TFT_WHITE); + Serial.print(F("Circles (outline) ")); + Serial.println(usecCircles); + delay(100); + + uint32_t usecTriangles = testTriangles(); + Serial.print(F("Triangles (outline) ")); + Serial.println(usecTriangles); + delay(100); + + uint32_t usecFilledTrangles = testFilledTriangles(); + Serial.print(F("Triangles (filled) ")); + Serial.println(usecFilledTrangles); + delay(100); + + uint32_t usecRoundRects = testRoundRects(); + Serial.print(F("Rounded rects (outline) ")); + Serial.println(usecRoundRects); + delay(100); + + uint32_t usedFilledRoundRects = testFilledRoundRects(); + Serial.print(F("Rounded rects (filled) ")); + Serial.println(usedFilledRoundRects); + delay(100); + + Serial.println(F("Done!")); + + uint16_t c = 4; + int8_t d = 1; + for (int32_t i = 0; i < tft.height(); i++) + { + tft.drawFastHLine(0, i, tft.width(), c); + c += d; + if (c <= 4 || c >= 11) + d = -d; + } + + tft.setCursor(0, 0); + tft.setTextColor(TFT_MAGENTA); + tft.setTextSize(2); + + tft.println(F(" M5GFX test")); + + tft.setTextSize(1); + tft.setTextColor(TFT_WHITE); + tft.println(F("")); + tft.setTextSize(1); + tft.println(F("")); + tft.setTextColor(tft.color565(0x80, 0x80, 0x80)); + + tft.println(F("")); + + + tft.setTextColor(TFT_GREEN); + tft.println(F(" Benchmark microseconds")); + tft.println(F("")); + tft.setTextColor(TFT_YELLOW); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("HaD pushColor ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecHaD); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Screen fill ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecFillScreen); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Text ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecText); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Pixels ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecPixels); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Lines ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecLines); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Horiz/Vert Lines ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecFastLines); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Rectangles ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecRects); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Rectangles-filled ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecFilledRects); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Circles ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecCircles); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Circles-filled ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecFilledCircles); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Triangles ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecTriangles); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Triangles-filled ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecFilledTrangles); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Rounded rects ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usecRoundRects); + + tft.setTextColor(TFT_CYAN); tft.setTextSize(1); + tft.print(F("Rounded rects-fill ")); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + printnice(usedFilledRoundRects); + + tft.setTextSize(1); + tft.println(F("")); + tft.setTextColor(TFT_GREEN); tft.setTextSize(2); + tft.print(F("Benchmark Complete!")); + + delay(60 * 1000L); +} + + /*************************************************** Original sketch text: diff --git a/examples/Basic/TextLogScroll/TextLogScroll.ino b/examples/Basic/TextLogScroll/TextLogScroll.ino index 49dc4c02..8aec03c8 100644 --- a/examples/Basic/TextLogScroll/TextLogScroll.ino +++ b/examples/Basic/TextLogScroll/TextLogScroll.ino @@ -18,6 +18,9 @@ M5GFX display; //M5AtomDisplay display; // default setting //M5AtomDisplay display ( 320, 180 ); // width, height +//#include +//M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 + static constexpr char text0[] = "hello world"; static constexpr char text1[] = "this"; static constexpr char text2[] = "is"; diff --git a/examples/Basic/drawImageData/drawImageData.ino b/examples/Basic/drawImageData/drawImageData.ino index 482fe253..4661da9e 100644 --- a/examples/Basic/drawImageData/drawImageData.ino +++ b/examples/Basic/drawImageData/drawImageData.ino @@ -25,6 +25,9 @@ M5GFX display; //#include //M5AtomDisplay display; +//#include +//M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60 + extern const uint8_t jpg[]; void setup() diff --git a/src/M5GFX.cpp b/src/M5GFX.cpp index e027f087..9d4c1289 100644 --- a/src/M5GFX.cpp +++ b/src/M5GFX.cpp @@ -3025,6 +3025,7 @@ The usage of each pin is as follows. case board_M5VAMeter: title = "M5VAMeter"; break; case board_M5StampPLC: title = "M5StampPLC"; break; case board_M5Tab5: title = "M5Tab5"; break; + case board_M5UnitPoEP4: title = "M5UnitPoEP4"; break; case board_ArduinoNessoN1: title = "ArduinoNessoN1"; break; default: title = "M5GFX"; break; } diff --git a/src/M5UnitPoEP4HDMI.h b/src/M5UnitPoEP4HDMI.h new file mode 100644 index 00000000..c3befa54 --- /dev/null +++ b/src/M5UnitPoEP4HDMI.h @@ -0,0 +1,129 @@ +#ifndef __M5GFX_M5UNITPOEP4HDMI__ +#define __M5GFX_M5UNITPOEP4HDMI__ + +#include "M5GFX.h" + +#if __has_include() +#include +#endif + +#if defined(CONFIG_IDF_TARGET_ESP32P4) +#include +#include +#define M5UNITPOEP4HDMI_ENABLED +#endif + +namespace m5 +{ + class I2C_Class; +} + +class M5UnitPoEP4HDMI : public M5GFX +{ +public: + struct config_t + { + // Supported timings: 1280x720@60 or 1920x1080@30 only. + uint16_t width = 1280; + uint16_t height = 720; + uint8_t refresh_rate = 60; + uint8_t fb_num = 1; + + uint8_t dsi_bus_id = 0; + uint8_t dsi_lane_num = 2; + uint16_t dsi_lane_mbps = 1000; + uint8_t dsi_ldo_chan_id = 3; + uint16_t dsi_ldo_voltage_mv = 2500; + + uint32_t i2c_freq = 100000; + lgfx::color_depth_t output_depth = lgfx::rgb888_3Byte; + }; + + M5UnitPoEP4HDMI(void) : M5UnitPoEP4HDMI(config_t{}) {} + + explicit M5UnitPoEP4HDMI(const config_t& cfg) : _config(cfg) + { + _board = lgfx::board_t::board_M5UnitPoEP4HDMI; + } + + config_t config(void) const { return _config; } + void config(const config_t& cfg) { _config = cfg; } + + void setI2C(m5::I2C_Class* i2c) { _i2c = i2c; } + + static bool isSupportedTiming(uint16_t width, uint16_t height, uint8_t refresh_rate) + { + return (width == 1280 && height == 720 && refresh_rate == 60) + || (width == 1920 && height == 1080 && refresh_rate == 30); + } + + bool init_impl(bool use_reset, bool use_clear) override + { + if (_panel_last.get() != nullptr) { + return true; + } + +#if defined(M5UNITPOEP4HDMI_ENABLED) + if (!isSupportedTiming(_config.width, _config.height, _config.refresh_rate)) { +#if defined(ESP_LOGE) + ESP_LOGE("M5UnitPoEP4HDMI", "unsupported timing: %ux%u@%u. Supported timings: 1280x720@60, 1920x1080@30", + static_cast(_config.width), static_cast(_config.height), + static_cast(_config.refresh_rate)); +#endif + return false; + } + + auto bus = new lgfx::Bus_DSI(); + auto panel = new lgfx::Panel_LT8912B(); + if (!bus || !panel) { + delete panel; + delete bus; + return false; + } + + { + auto cfg = bus->config(); + cfg.bus_id = _config.dsi_bus_id; + cfg.lane_num = _config.dsi_lane_num; + cfg.lane_mbps = _config.dsi_lane_mbps; + cfg.ldo_chan_id = _config.dsi_ldo_chan_id; + cfg.ldo_voltage_mv = _config.dsi_ldo_voltage_mv; + bus->config(cfg); + } + + { + auto cfg = panel->config_detail(); + cfg.h_res = _config.width; + cfg.v_res = _config.height; + cfg.refresh_rate = _config.refresh_rate; + cfg.fb_num = _config.fb_num; + cfg.lane_num = _config.dsi_lane_num; + cfg.i2c = _i2c; + cfg.i2c_freq = _config.i2c_freq; + cfg.output_depth = _config.output_depth; + panel->config_detail(cfg); + } + + panel->setBus(bus); + setPanel(panel); + _bus_last.reset(bus); + _panel_last.reset(panel); + + if (lgfx::LGFX_Device::init_impl(use_reset, use_clear)) { + return true; + } + + setPanel(nullptr); + _panel_last.reset(); + _bus_last.reset(); +#endif + + return false; + } + +protected: + config_t _config; + m5::I2C_Class* _i2c = nullptr; +}; + +#endif diff --git a/src/lgfx/boards.hpp b/src/lgfx/boards.hpp index d66c4229..0f6c18d5 100644 --- a/src/lgfx/boards.hpp +++ b/src/lgfx/boards.hpp @@ -79,6 +79,7 @@ namespace lgfx // This should not be changed to "m5gfx" , board_M5UnitRCA , board_M5ModuleDisplay , board_M5ModuleRCA + , board_M5UnitPoEP4HDMI , board_FrameBuffer = 512 }; diff --git a/src/lgfx/v1/platforms/esp32p4/Panel_LT8912B.cpp b/src/lgfx/v1/platforms/esp32p4/Panel_LT8912B.cpp new file mode 100644 index 00000000..d5a9535c --- /dev/null +++ b/src/lgfx/v1/platforms/esp32p4/Panel_LT8912B.cpp @@ -0,0 +1,1182 @@ +/*----------------------------------------------------------------------------/ + 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_LT8912B.hpp" + +#if SOC_MIPI_DSI_SUPPORTED + +#include "../common.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 3) +#include +#include +#include +#include + +// ESP-IDF 5.5.1 keeps the DSI bus internals private, but the handle is this +// struct in esp_lcd/dsi/mipi_dsi_priv.h. Keep this shim version-gated. +struct esp_lcd_dsi_bus_t { + int bus_id; + mipi_dsi_hal_context_t hal; + esp_pm_lock_handle_t pm_lock; +}; +#endif + +#if __has_include() +#include +#define LGFX_PANEL_LT8912B_HAS_M5_I2C 1 +#endif + +// LT8912B esp_lcd compatibility implementation. Kept in this file to match +// the M5GFX Panel_xxx convention while Panel_LT8912B wraps it for LovyanGFX. +static const char *TAG = "lt8912b"; + +static constexpr uint8_t LT8912B_IO_I2C_MAIN_ADDRESS = 0x48; +static constexpr uint8_t LT8912B_IO_I2C_CEC_ADDRESS = 0x49; +static constexpr uint8_t LT8912B_IO_I2C_AVI_ADDRESS = 0x4A; +static constexpr uint8_t LT8912B_ASPECT_RATIO_16_9 = 0x02; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 3) +static void apply_idf551_dsi_compat(esp_lcd_dsi_bus_handle_t dsi_bus, + const esp_lcd_dpi_panel_config_t* dpi_config, + uint32_t lane_bit_rate_mbps) +{ + if (!dsi_bus) { + return; + } + + auto round_u32 = [](float value) -> uint32_t { return static_cast(value + 0.5f); }; + auto hal = &dsi_bus->hal; + + if (!dpi_config) { + const uint32_t timeout_div = round_u32(lane_bit_rate_mbps / 8.0f / 10.0f); + const uint32_t esc_div = round_u32(lane_bit_rate_mbps / 8.0f / 18.0f); + mipi_dsi_host_ll_set_timeout_clock_division(hal->host, timeout_div); + mipi_dsi_host_ll_set_escape_clock_division(hal->host, esc_div); + ESP_LOGI(TAG, "Applied IDF 5.5.1 DSI divider compat: TimeoutDiv=%" PRIu32 ", EscDiv=%" PRIu32, + timeout_div, esc_div); + return; + } + + if (dpi_config->dpi_clock_freq_mhz == 0) { + return; + } + + const auto& t = dpi_config->video_timing; + const float ratio = static_cast(lane_bit_rate_mbps) / dpi_config->dpi_clock_freq_mhz / 8.0f; + const uint32_t host_hsw = round_u32(t.hsync_pulse_width * ratio); + const uint32_t host_hbp = round_u32(t.hsync_back_porch * ratio); + const uint32_t host_hfp = round_u32(t.hsync_front_porch * ratio); + int32_t host_act = static_cast(round_u32(t.h_size * ratio)); + + const uint32_t htotal = t.hsync_pulse_width + t.hsync_back_porch + t.h_size + t.hsync_front_porch; + const uint32_t host_htotal = round_u32(htotal * ratio); + const int32_t compensation = static_cast(host_htotal) + - static_cast(host_hsw + host_hbp + host_act + host_hfp); + host_act += compensation; + if (host_act < 0) { + host_act = 0; + } + + mipi_dsi_host_ll_dpi_set_horizontal_timing(hal->host, host_hsw, host_hbp, + static_cast(host_act), host_hfp); + ESP_LOGI(TAG, "Applied IDF 5.5.1 DSI horizontal compat: hsw=%" PRIu32 + ", hbp=%" PRIu32 ", act=%" PRId32 ", hfp=%" PRIu32, + host_hsw, host_hbp, host_act, host_hfp); +} +#endif + +struct lt8912b_io_t { + esp_lcd_panel_io_handle_t main; + esp_lcd_panel_io_handle_t cec_dsi; + esp_lcd_panel_io_handle_t avi; +}; + +struct lt8912b_video_timing_t { + uint16_t hfp; + uint16_t hs; + uint16_t hbp; + uint16_t hact; + uint16_t htotal; + uint16_t vfp; + uint16_t vs; + uint16_t vbp; + uint16_t vact; + uint16_t vtotal; + bool h_polarity; + bool v_polarity; + uint16_t vic; + uint8_t aspect_ratio; + uint32_t pclk_mhz; +}; + +struct lt8912b_vendor_config_t { + lt8912b_video_timing_t video_timing; + const uint8_t* init_commands; + struct { + esp_lcd_dsi_bus_handle_t dsi_bus; + const esp_lcd_dpi_panel_config_t* dpi_config; + uint8_t lane_num; + } mipi_config; +}; + +static esp_lcd_panel_io_i2c_config_t make_lt8912b_io_config(uint32_t clk_speed_hz, uint8_t address) +{ + esp_lcd_panel_io_i2c_config_t config = {}; + config.dev_addr = address; + config.control_phase_bytes = 1; + config.lcd_cmd_bits = 8; + config.lcd_param_bits = 8; + config.flags.disable_control_phase = 1; + config.scl_speed_hz = clk_speed_hz; + return config; +} + +static void fill_dpi_config(esp_lcd_dpi_panel_config_t* dpi_config, + const lt8912b_video_timing_t& timing, + uint8_t fb_num) +{ + *dpi_config = {}; + dpi_config->virtual_channel = 0; + dpi_config->dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT; + dpi_config->dpi_clock_freq_mhz = timing.pclk_mhz; + dpi_config->pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB888; + dpi_config->num_fbs = fb_num; + dpi_config->video_timing.h_size = timing.hact; + dpi_config->video_timing.v_size = timing.vact; + dpi_config->video_timing.hsync_pulse_width = timing.hs; + dpi_config->video_timing.hsync_back_porch = timing.hbp; + dpi_config->video_timing.hsync_front_porch = timing.hfp; + dpi_config->video_timing.vsync_pulse_width = timing.vs; + dpi_config->video_timing.vsync_back_porch = timing.vbp; + dpi_config->video_timing.vsync_front_porch = timing.vfp; + dpi_config->flags.use_dma2d = true; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + dpi_config->flags.disable_lp = true; +#endif +} + +static bool make_lt8912b_timing(uint16_t h_res, + uint16_t v_res, + uint8_t refresh_rate, + uint8_t fb_num, + esp_lcd_dpi_panel_config_t* dpi_config, + lt8912b_video_timing_t* video_timing) +{ + if (!dpi_config || !video_timing) { + return false; + } + + if (h_res == 1280 && v_res == 720 && refresh_rate == 60) { + *video_timing = { + .hfp = 110, + .hs = 40, + .hbp = 220, + .hact = 1280, + .htotal = 1650, + .vfp = 5, + .vs = 5, + .vbp = 20, + .vact = 720, + .vtotal = 750, + .h_polarity = true, + .v_polarity = true, + .vic = 4, + .aspect_ratio = LT8912B_ASPECT_RATIO_16_9, + .pclk_mhz = 80, + }; + fill_dpi_config(dpi_config, *video_timing, fb_num); + return true; + } + + if (h_res == 1920 && v_res == 1080 && refresh_rate == 30) { + *video_timing = { + .hfp = 48, + .hs = 32, + .hbp = 80, + .hact = 1920, + .htotal = 2080, + .vfp = 3, + .vs = 5, + .vbp = 8, + .vact = 1080, + .vtotal = 1096, + .h_polarity = true, + .v_polarity = false, + .vic = 0, + .aspect_ratio = LT8912B_ASPECT_RATIO_16_9, + .pclk_mhz = 80, + }; + fill_dpi_config(dpi_config, *video_timing, fb_num); + return true; + } + + return false; +} + +static esp_err_t panel_lt8912b_del(esp_lcd_panel_t *panel); +static esp_err_t panel_lt8912b_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_lt8912b_init(esp_lcd_panel_t *panel); +static esp_err_t panel_lt8912b_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_lt8912b_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_lt8912b_disp_on_off(esp_lcd_panel_t *panel, bool off); +static esp_err_t panel_lt8912b_sleep(esp_lcd_panel_t *panel, bool sleep); + +static esp_err_t _panel_lt8912b_detect_input_mipi(esp_lcd_panel_t *panel); + +typedef struct { + lt8912b_io_t io; + lt8912b_video_timing_t video_timing; + int reset_gpio_num; + bool reset_level; + uint8_t lane_num; + // To save the original functions of MIPI DPI panel + esp_err_t (*del)(esp_lcd_panel_t *panel); + esp_err_t (*init)(esp_lcd_panel_t *panel); + const uint8_t* init_commands; +} lt8912b_panel_t; + +// Store the LT8912B context globally because panel->user_data must stay owned by DPI. +static lt8912b_panel_t *g_lt8912b_context = nullptr; + +static esp_err_t new_panel_lt8912b(const lt8912b_io_t *io, const lt8912b_vendor_config_t *vendor_config, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) +{ + ESP_RETURN_ON_FALSE(io && io->main && io->cec_dsi && io->avi && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG, "invalid vendor config"); + + + esp_err_t ret = ESP_OK; + auto lt8912b = static_cast(std::calloc(1, sizeof(lt8912b_panel_t))); + ESP_RETURN_ON_FALSE(lt8912b, ESP_ERR_NO_MEM, TAG, "no mem for lt8912b panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_config_t io_conf = {}; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + io_conf.mode = GPIO_MODE_OUTPUT; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + lt8912b->io = *io; + lt8912b->video_timing = vendor_config->video_timing; + lt8912b->init_commands = vendor_config->init_commands; + lt8912b->lane_num = vendor_config->mipi_config.lane_num; + lt8912b->reset_gpio_num = panel_dev_config->reset_gpio_num; + lt8912b->reset_level = panel_dev_config->flags.reset_active_high; + + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, ret_panel), err, TAG, + "create MIPI DPI panel failed"); + ESP_LOGD(TAG, "new MIPI DPI panel @%p", *ret_panel); + + lt8912b->del = (*ret_panel)->del; + lt8912b->init = (*ret_panel)->init; + + (*ret_panel)->del = panel_lt8912b_del; + (*ret_panel)->init = panel_lt8912b_init; + (*ret_panel)->reset = panel_lt8912b_reset; + (*ret_panel)->mirror = panel_lt8912b_mirror; + (*ret_panel)->invert_color = panel_lt8912b_invert_color; + (*ret_panel)->disp_on_off = panel_lt8912b_disp_on_off; + (*ret_panel)->disp_sleep = panel_lt8912b_sleep; + + g_lt8912b_context = lt8912b; + ESP_LOGD(TAG, "new lt8912b panel @%p", lt8912b); + + return ESP_OK; + +err: + if (lt8912b) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(static_cast(panel_dev_config->reset_gpio_num)); + } + std::free(lt8912b); + } + return ret; +} + + +static esp_err_t panel_lt8912b_del(esp_lcd_panel_t *panel) +{ + lt8912b_panel_t *lt8912b = g_lt8912b_context; + + if (!lt8912b) { + ESP_LOGE(TAG, "LT8912B context is NULL!"); + return ESP_ERR_INVALID_STATE; + } + + if (lt8912b->reset_gpio_num >= 0) { + gpio_reset_pin(static_cast(lt8912b->reset_gpio_num)); + } + lt8912b->del(panel); + std::free(lt8912b); + g_lt8912b_context = nullptr; + return ESP_OK; +} + +static esp_err_t panel_lt8912b_reset(esp_lcd_panel_t *panel) +{ + lt8912b_panel_t *lt8912b = g_lt8912b_context; + + if (!lt8912b) { + ESP_LOGE(TAG, "LT8912B context is NULL!"); + return ESP_ERR_INVALID_STATE; + } + + esp_lcd_panel_io_handle_t io_main = lt8912b->io.main; + + // perform hardware reset + if (lt8912b->reset_gpio_num >= 0) { + gpio_set_level(static_cast(lt8912b->reset_gpio_num), lt8912b->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(static_cast(lt8912b->reset_gpio_num), !lt8912b->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } else { // perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io_main, LCD_CMD_SWRESET, nullptr, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command + } + + return ESP_OK; +} + +static esp_err_t _panel_lt8912b_send_data(esp_lcd_panel_io_handle_t io, uint8_t reg, uint8_t data) +{ + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, reg, &data, 1), TAG, "send command failed"); + + return ESP_OK; +} + +struct lt8912b_reg_t { + uint8_t reg; + uint8_t data; +}; + +template +static esp_err_t _panel_lt8912b_send_regs(esp_lcd_panel_io_handle_t io, const lt8912b_reg_t (®s)[N]) +{ + for (const auto& reg : regs) { + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_data(io, reg.reg, reg.data), TAG, "send command failed"); + } + return ESP_OK; +} + +static esp_err_t _panel_lt8912b_send_word(esp_lcd_panel_io_handle_t io, uint8_t reg_lsb, uint16_t value) +{ + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_data(io, reg_lsb, static_cast(value & 0xff)), TAG, + "send low byte failed"); + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_data(io, reg_lsb + 1, static_cast(value >> 8)), TAG, + "send high byte failed"); + return ESP_OK; +} + +static esp_err_t _panel_lt8912b_pulse_reg(esp_lcd_panel_io_handle_t io, + uint8_t reg, + uint8_t active_value, + uint8_t idle_value, + uint32_t delay_ms = 10) +{ + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_data(io, reg, active_value), TAG, "send pulse active failed"); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_data(io, reg, idle_value), TAG, "send pulse idle failed"); + return ESP_OK; +} + +static uint8_t _panel_lt8912b_lane_cfg(uint8_t lane_num) +{ + switch (lane_num) { + case 1: + case 2: + case 3: + return lane_num; + default: + return 0x00; + } +} + +static esp_err_t _panel_lt8912b_send_mipi_analog(esp_lcd_panel_io_handle_t io_main, bool pn_swap) +{ + const lt8912b_reg_t regs[] = { + {0x3e, static_cast(pn_swap ? 0xf6 : 0xd6)}, + {0x3f, 0xd4}, + {0x41, 0x3c}, + }; + return _panel_lt8912b_send_regs(io_main, regs); +} + +static esp_err_t _panel_lt8912b_send_mipi_basic_set(esp_lcd_panel_io_handle_t io_cec, uint8_t lane_count, bool lane_swap) +{ + const lt8912b_reg_t regs[] = { + {0x10, 0x01}, // term enable + {0x11, 0x10}, // settle + {0x13, lane_count}, + {0x14, 0x00}, + {0x15, static_cast(lane_swap ? 0xa8 : 0x00)}, + {0x1a, 0x03}, + {0x1b, 0x03}, + }; + return _panel_lt8912b_send_regs(io_cec, regs); +} + +static esp_err_t _panel_lt8912b_send_video_setup(esp_lcd_panel_t *panel) +{ + lt8912b_panel_t *lt8912b = g_lt8912b_context; + + if (!lt8912b) { + ESP_LOGE(TAG, "LT8912B context is NULL!"); + return ESP_ERR_INVALID_STATE; + } + (void)panel; + + esp_lcd_panel_io_handle_t io_cec_dsi = lt8912b->io.cec_dsi; + const auto& timing = lt8912b->video_timing; + + const lt8912b_reg_t regs8[] = { + {0x18, static_cast(timing.hs)}, + {0x19, static_cast(timing.vs)}, + {0x2f, 0x0c}, + }; + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_cec_dsi, regs8), TAG, "send video timing byte regs failed"); + + const struct { + uint8_t reg_lsb; + uint16_t value; + } regs[] = { + {0x1c, timing.hact}, + {0x34, timing.htotal}, + {0x36, timing.vtotal}, + {0x38, timing.vbp}, + {0x3a, timing.vfp}, + {0x3c, timing.hbp}, + {0x3e, timing.hfp}, + }; + for (const auto& reg : regs) { + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_word(io_cec_dsi, reg.reg_lsb, reg.value), TAG, "send video timing failed"); + } + + return ESP_OK; +} + +static esp_err_t _panel_lt8912b_send_avi_infoframe(esp_lcd_panel_t *panel) +{ + lt8912b_panel_t *lt8912b = g_lt8912b_context; + + if (!lt8912b) { + ESP_LOGE(TAG, "LT8912B context is NULL!"); + return ESP_ERR_INVALID_STATE; + } + (void)panel; + + esp_lcd_panel_io_handle_t io_main = lt8912b->io.main; + esp_lcd_panel_io_handle_t io_avi = lt8912b->io.avi; + const auto& timing = lt8912b->video_timing; + + const uint8_t sync_polarity = (timing.h_polarity ? 0x02 : 0x00) | (timing.v_polarity ? 0x01 : 0x00); + const uint8_t pb2 = (timing.aspect_ratio << 4) + 0x08; + const uint8_t pb4 = timing.vic; + const uint8_t pb0 = (((pb2 + pb4) <= 0x5f) ? (0x5f - pb2 - pb4) : (0x15f - pb2 - pb4)); + + const lt8912b_reg_t avi_pre[] = { + {0x3c, 0x41}, // enable null packet + }; + const lt8912b_reg_t main_regs[] = { + {0xab, sync_polarity}, + }; + const lt8912b_reg_t avi_infoframe[] = { + {0x43, pb0}, + {0x44, 0x10}, + {0x45, pb2}, + {0x46, 0x00}, + {0x47, pb4}, + }; + + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_avi, avi_pre), TAG, "send AVI preamble failed"); + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_main, main_regs), TAG, "send sync polarity failed"); + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_avi, avi_infoframe), TAG, "send AVI infoframe failed"); + + return ESP_OK; +} + +static esp_err_t _panel_lt8912b_mipi_rx_logic_reset(esp_lcd_panel_io_handle_t io_main) +{ + ESP_RETURN_ON_ERROR(_panel_lt8912b_pulse_reg(io_main, 0x03, 0x7f, 0xff), TAG, "reset MIPI RX logic failed"); + ESP_RETURN_ON_ERROR(_panel_lt8912b_pulse_reg(io_main, 0x05, 0xfb, 0xff), TAG, "reset DDS failed"); + return ESP_OK; +} + +static esp_err_t _panel_lt8912b_detect_input_mipi(esp_lcd_panel_t *panel) +{ + lt8912b_panel_t *lt8912b = g_lt8912b_context; + + if (!lt8912b) { + ESP_LOGE(TAG, "LT8912B context is NULL!"); + return ESP_ERR_INVALID_STATE; + } + (void)panel; + + esp_lcd_panel_io_handle_t io_main = lt8912b->io.main; + + uint8_t val_c2 = 0, val_c3 = 0, val_c4 = 0; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_rx_param(io_main, 0xC2, &val_c2, 1), TAG, "read 0xC2 failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_rx_param(io_main, 0xC3, &val_c3, 1), TAG, "read 0xC3 failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_rx_param(io_main, 0xC4, &val_c4, 1), TAG, "read 0xC4 failed"); + + const uint16_t h_res = ((uint16_t)(val_c4 & 0xF0) << 4) | val_c2; + const uint16_t v_res = ((uint16_t)(val_c4 & 0x0F) << 8) | val_c3; + ESP_LOGD(TAG, "MIPI input: H=%u, V=%u", h_res, v_res); + + return ESP_OK; +} + + +static esp_err_t _panel_lt8912b_lvds_output(esp_lcd_panel_io_handle_t io_main, bool on) +{ + if (on) { + const lt8912b_reg_t regs[] = { + {0x02, 0xf7}, + {0x02, 0xff}, + {0x03, 0xcb}, + {0x03, 0xfb}, + {0x03, 0xff}, + {0x44, 0x30}, + }; + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_main, regs), TAG, "enable LVDS output failed"); + ESP_LOGD(TAG, "LT8912B LVDS output enabled"); + } else { + const lt8912b_reg_t regs[] = {{0x44, 0x31}}; + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_main, regs), TAG, "disable LVDS output failed"); + ESP_LOGD(TAG, "LT8912B LVDS output disabled"); + } + + return ESP_OK; +} + +static esp_err_t _panel_lt8912b_hdmi_output(esp_lcd_panel_io_handle_t io_main, bool on) +{ + const lt8912b_reg_t regs[] = { + {0x33, static_cast(on ? 0x0e : 0x0c)}, + }; + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_main, regs), TAG, "set HDMI output failed"); + if (on) { + ESP_LOGD(TAG, "LT8912B HDMI output enabled"); + } else { + ESP_LOGD(TAG, "LT8912B HDMI output disabled"); + } + + return ESP_OK; +} + +static esp_lcd_panel_io_handle_t _panel_lt8912b_get_io(lt8912b_panel_t *lt8912b, uint8_t token) +{ + using namespace lgfx::v1::detail; + switch (token) { + case lt8912b_seq_main: + return lt8912b->io.main; + case lt8912b_seq_cec_dsi: + return lt8912b->io.cec_dsi; + case lt8912b_seq_avi: + return lt8912b->io.avi; + default: + return nullptr; + } +} + +static esp_err_t _panel_lt8912b_exec_init_sequence(esp_lcd_panel_t *panel, const uint8_t *cmds) +{ + using namespace lgfx::v1::detail; + + lt8912b_panel_t *lt8912b = g_lt8912b_context; + ESP_RETURN_ON_FALSE(lt8912b && cmds, ESP_ERR_INVALID_STATE, TAG, "invalid LT8912B init state"); + + for (;;) { + const uint8_t token = *cmds++; + if (token == lt8912b_seq_end) { + return ESP_OK; + } + + if (auto io = _panel_lt8912b_get_io(lt8912b, token)) { + const uint8_t reg = *cmds++; + const uint8_t len = *cmds++; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, reg, cmds, len), TAG, "send command failed"); + cmds += len; + continue; + } + + switch (token) { + case lt8912b_seq_mipi_analog: + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_mipi_analog(lt8912b->io.main, false), TAG, "mipi analog failed"); + break; + case lt8912b_seq_mipi_basic: + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_mipi_basic_set(lt8912b->io.cec_dsi, + _panel_lt8912b_lane_cfg(lt8912b->lane_num), + false), + TAG, "mipi basic set failed"); + break; + case lt8912b_seq_video_setup: + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_video_setup(panel), TAG, "video setup failed"); + break; + case lt8912b_seq_detect_mipi: + ESP_RETURN_ON_ERROR(_panel_lt8912b_detect_input_mipi(panel), TAG, "detect MIPI failed"); + break; + case lt8912b_seq_avi_infoframe: + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_avi_infoframe(panel), TAG, "avi infoframe failed"); + break; + case lt8912b_seq_mipi_rx_reset: + ESP_RETURN_ON_ERROR(_panel_lt8912b_mipi_rx_logic_reset(lt8912b->io.main), TAG, "mipi rx reset failed"); + break; + case lt8912b_seq_lvds_off: + ESP_RETURN_ON_ERROR(_panel_lt8912b_lvds_output(lt8912b->io.main, false), TAG, "lvds disable failed"); + break; + case lt8912b_seq_hdmi_on: + ESP_RETURN_ON_ERROR(_panel_lt8912b_hdmi_output(lt8912b->io.main, true), TAG, "enable HDMI failed"); + break; + case lt8912b_seq_dpi_init: + ESP_RETURN_ON_ERROR(lt8912b->init(panel), TAG, "init MIPI DPI panel failed"); + break; + default: + ESP_LOGE(TAG, "unknown init sequence token: 0x%02x", token); + return ESP_ERR_INVALID_ARG; + } + } +} + +static esp_err_t panel_lt8912b_init(esp_lcd_panel_t *panel) +{ + lt8912b_panel_t *lt8912b = g_lt8912b_context; + + if (!lt8912b) { + ESP_LOGE(TAG, "LT8912B context is NULL!"); + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGI(TAG, "Initializing LT8912B HDMI bridge"); + ESP_RETURN_ON_ERROR(_panel_lt8912b_exec_init_sequence(panel, lt8912b->init_commands), TAG, "init sequence failed"); + ESP_LOGI(TAG, "LT8912B HDMI bridge initialized"); + return ESP_OK; +} + +static esp_err_t panel_lt8912b_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + (void)panel; + (void)invert_color_data; + return ESP_ERR_NOT_SUPPORTED; +} + +static esp_err_t panel_lt8912b_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + (void)panel; + (void)mirror_x; + (void)mirror_y; + ESP_LOGW(TAG, "Mirror is not supported in LT8912B driver. Please use SW rotation."); + return ESP_ERR_NOT_SUPPORTED; +} + +static esp_err_t panel_lt8912b_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + (void)panel; + (void)on_off; + return ESP_ERR_NOT_SUPPORTED; +} + +static esp_err_t panel_lt8912b_sleep(esp_lcd_panel_t *panel, bool sleep) +{ + lt8912b_panel_t *lt8912b = g_lt8912b_context; + + if (!lt8912b) { + ESP_LOGE(TAG, "LT8912B context is NULL!"); + return ESP_ERR_INVALID_STATE; + } + (void)panel; + + esp_lcd_panel_io_handle_t io_main = lt8912b->io.main; + + if (sleep) { + const lt8912b_reg_t regs[] = { + {0x54, 0x1d}, + {0x51, 0x15}, + {0x44, 0x31}, + {0x41, 0xbd}, + {0x5c, 0x11}, + }; + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_main, regs), TAG, "enter sleep failed"); + } else { + const lt8912b_reg_t regs[] = { + {0x5c, 0x10}, + {0x54, 0x1c}, + {0x51, 0x2d}, + {0x44, 0x30}, + {0x41, 0xbc}, + }; + ESP_RETURN_ON_ERROR(_panel_lt8912b_send_regs(io_main, regs), TAG, "exit sleep failed"); + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_RETURN_ON_ERROR(_panel_lt8912b_pulse_reg(io_main, 0x03, 0x7f, 0xff), TAG, "reset MIPI RX logic failed"); + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_RETURN_ON_ERROR(_panel_lt8912b_pulse_reg(io_main, 0x05, 0xfb, 0xff), TAG, "reset DDS failed"); + } + vTaskDelay(pdMS_TO_TICKS(100)); + + return ESP_OK; +} + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + static constexpr const char* TAG = "Panel_LT8912B"; + +#if defined(LGFX_PANEL_LT8912B_HAS_M5_I2C) + struct M5I2CPanelIO + { + esp_lcd_panel_io_t base; + m5::I2C_Class* i2c = nullptr; + uint8_t address = 0; + uint32_t freq = 100000; + }; + + static M5I2CPanelIO* to_m5_i2c_io(esp_lcd_panel_io_t* io) + { + return reinterpret_cast(io); + } + + static esp_err_t m5_i2c_io_tx_param(esp_lcd_panel_io_t* io, int lcd_cmd, const void* param, size_t param_size) + { + auto ctx = to_m5_i2c_io(io); + if (!ctx || !ctx->i2c || !ctx->i2c->isEnabled() || lcd_cmd < 0 || lcd_cmd > 0xFF) { + return ESP_ERR_INVALID_ARG; + } + if (param_size && !param) { + return ESP_ERR_INVALID_ARG; + } + + if (!ctx->i2c->start(ctx->address, false, ctx->freq)) { + return ESP_FAIL; + } + + bool ok = ctx->i2c->write(static_cast(lcd_cmd)); + if (ok && param_size) { + ok = ctx->i2c->write(static_cast(param), param_size); + } + ok = ctx->i2c->stop() && ok; + return ok ? ESP_OK : ESP_FAIL; + } + + static esp_err_t m5_i2c_io_rx_param(esp_lcd_panel_io_t* io, int lcd_cmd, void* param, size_t param_size) + { + auto ctx = to_m5_i2c_io(io); + if (!ctx || !ctx->i2c || !ctx->i2c->isEnabled() || lcd_cmd < 0 || lcd_cmd > 0xFF) { + return ESP_ERR_INVALID_ARG; + } + if (param_size == 0) { + return ESP_OK; + } + if (!param) { + return ESP_ERR_INVALID_ARG; + } + + const bool ok = ctx->i2c->readRegister(ctx->address, + static_cast(lcd_cmd), + static_cast(param), + param_size, + ctx->freq); + return ok ? ESP_OK : ESP_FAIL; + } + + static esp_err_t m5_i2c_io_tx_color(esp_lcd_panel_io_t* io, int lcd_cmd, const void* color, size_t color_size) + { + return m5_i2c_io_tx_param(io, lcd_cmd, color, color_size); + } + + static esp_err_t m5_i2c_io_del(esp_lcd_panel_io_t* io) + { + delete to_m5_i2c_io(io); + return ESP_OK; + } + + static esp_err_t m5_i2c_io_register_event_callbacks(esp_lcd_panel_io_t* io, + const esp_lcd_panel_io_callbacks_t* cbs, + void* user_ctx) + { + (void)io; + (void)cbs; + (void)user_ctx; + return ESP_OK; + } + + static esp_lcd_panel_io_handle_t new_m5_i2c_panel_io(m5::I2C_Class* i2c, uint8_t address, uint32_t freq) + { + auto io = new (std::nothrow) M5I2CPanelIO(); + if (!io) { + return nullptr; + } + + io->base.rx_param = m5_i2c_io_rx_param; + io->base.tx_param = m5_i2c_io_tx_param; + io->base.tx_color = m5_i2c_io_tx_color; + io->base.del = m5_i2c_io_del; + io->base.register_event_callbacks = m5_i2c_io_register_event_callbacks; + io->i2c = i2c; + io->address = address; + io->freq = freq; + return &io->base; + } +#endif + + Panel_LT8912B::~Panel_LT8912B(void) + { + release_panel(); + } + + color_depth_t Panel_LT8912B::setColorDepth(color_depth_t depth) + { + (void)depth; + _write_depth = _config_detail.output_depth; + _read_depth = _config_detail.output_depth; + return _write_depth; + } + + bool Panel_LT8912B::get_i2c_bus(void) + { + if (_config_detail.i2c_master_bus) { + _i2c_bus = _config_detail.i2c_master_bus; + _i2c_bus_owned = false; + return true; + } + if (_i2c_bus) { + return true; + } + + const auto port = static_cast(_config_detail.i2c_port); + esp_err_t ret = ESP_FAIL; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + ret = i2c_master_get_bus_handle(port, &_i2c_bus); + if (ret == ESP_OK) { + _i2c_bus_owned = false; + return true; + } + ESP_LOGW(TAG, "get existing I2C bus %d failed: %s; create fallback bus SDA=%d SCL=%d", + _config_detail.i2c_port, esp_err_to_name(ret), + _config_detail.i2c_sda, _config_detail.i2c_scl); +#endif + + i2c_master_bus_config_t bus_config = {}; + bus_config.i2c_port = port; + bus_config.sda_io_num = static_cast(_config_detail.i2c_sda); + bus_config.scl_io_num = static_cast(_config_detail.i2c_scl); + bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + bus_config.glitch_ignore_cnt = 7; + bus_config.flags.enable_internal_pullup = true; + bus_config.intr_priority = 1; + + ret = i2c_new_master_bus(&bus_config, &_i2c_bus); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "create fallback I2C bus %d failed: %s", _config_detail.i2c_port, esp_err_to_name(ret)); + _i2c_bus = nullptr; + return false; + } + _i2c_bus_owned = true; + return true; + } + + bool Panel_LT8912B::init_panel(Bus_DSI* bus) + { + if (_panel_handle) { + return true; + } + if (!bus || !bus->getMipiDsiBus()) { + return false; + } + + _config_detail.fb_num = std::max(1, std::min(_config_detail.fb_num, 3)); + + esp_lcd_dpi_panel_config_t dpi_config; + lt8912b_video_timing_t video_timing; + if (!make_lt8912b_timing(_config_detail.h_res, _config_detail.v_res, + _config_detail.refresh_rate, _config_detail.fb_num, + &dpi_config, &video_timing)) { + ESP_LOGE(TAG, "unsupported timing: %ux%u@%u", _config_detail.h_res, _config_detail.v_res, + _config_detail.refresh_rate); + return false; + } + + esp_err_t ret = ESP_OK; +#if defined(LGFX_PANEL_LT8912B_HAS_M5_I2C) + if (_config_detail.i2c) { + if (!_config_detail.i2c->isEnabled()) { + ESP_LOGE(TAG, "M5.In_I2C is not initialized"); + return false; + } + _io_main = new_m5_i2c_panel_io(_config_detail.i2c, LT8912B_IO_I2C_MAIN_ADDRESS, _config_detail.i2c_freq); + _io_cec = new_m5_i2c_panel_io(_config_detail.i2c, LT8912B_IO_I2C_CEC_ADDRESS, _config_detail.i2c_freq); + _io_avi = new_m5_i2c_panel_io(_config_detail.i2c, LT8912B_IO_I2C_AVI_ADDRESS, _config_detail.i2c_freq); + if (!_io_main || !_io_cec || !_io_avi) { + ESP_LOGE(TAG, "create LT8912B M5.In_I2C IO failed"); + return false; + } + } else +#else + if (_config_detail.i2c) { + ESP_LOGE(TAG, "M5Unified I2C_Class is not available"); + return false; + } +#endif + { + if (!get_i2c_bus()) { + return false; + } + + esp_lcd_panel_io_i2c_config_t main_cfg = make_lt8912b_io_config(_config_detail.i2c_freq, LT8912B_IO_I2C_MAIN_ADDRESS); + esp_lcd_panel_io_i2c_config_t cec_cfg = make_lt8912b_io_config(_config_detail.i2c_freq, LT8912B_IO_I2C_CEC_ADDRESS); + esp_lcd_panel_io_i2c_config_t avi_cfg = make_lt8912b_io_config(_config_detail.i2c_freq, LT8912B_IO_I2C_AVI_ADDRESS); + + ret = esp_lcd_new_panel_io_i2c_v2(_i2c_bus, &main_cfg, &_io_main); + if (ret == ESP_OK) { + ret = esp_lcd_new_panel_io_i2c_v2(_i2c_bus, &cec_cfg, &_io_cec); + } + if (ret == ESP_OK) { + ret = esp_lcd_new_panel_io_i2c_v2(_i2c_bus, &avi_cfg, &_io_avi); + } + if (ret != ESP_OK) { + ESP_LOGE(TAG, "create LT8912B I2C IO failed: %s", esp_err_to_name(ret)); + return false; + } + } + + if (!_io_main || !_io_cec || !_io_avi) { + ESP_LOGE(TAG, "create LT8912B I2C IO failed"); + return false; + } + + lt8912b_vendor_config_t vendor_config = {}; + vendor_config.video_timing = video_timing; + vendor_config.init_commands = getInitCommands(0); + vendor_config.mipi_config.dsi_bus = bus->getMipiDsiBus(); + vendor_config.mipi_config.dpi_config = &dpi_config; + vendor_config.mipi_config.lane_num = _config_detail.lane_num; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 24; + lt8912b_io_t panel_io = {}; + panel_io.main = _io_main; + panel_io.cec_dsi = _io_cec; + panel_io.avi = _io_avi; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 3) + auto dsi_bus = bus->getMipiDsiBus(); + apply_idf551_dsi_compat(dsi_bus, nullptr, bus->config().lane_mbps); +#endif + + ESP_LOGI(TAG, "init LT8912B HDMI: %ux%u@%u, fb=%u", _config_detail.h_res, _config_detail.v_res, + _config_detail.refresh_rate, _config_detail.fb_num); + + ret = new_panel_lt8912b(&panel_io, &vendor_config, &panel_config, &_panel_handle); + if (ret == ESP_OK) { + ret = esp_lcd_panel_reset(_panel_handle); + } + if (ret == ESP_OK) { + ret = esp_lcd_panel_init(_panel_handle); + } + if (ret != ESP_OK) { + ESP_LOGE(TAG, "init LT8912B panel failed: %s", esp_err_to_name(ret)); + return false; + } +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 3) + apply_idf551_dsi_compat(dsi_bus, &dpi_config, bus->config().lane_mbps); +#endif + + switch (_config_detail.fb_num) { + case 1: + ret = esp_lcd_dpi_panel_get_frame_buffer(_panel_handle, 1, &_frame_buffers[0]); + break; + case 2: + ret = esp_lcd_dpi_panel_get_frame_buffer(_panel_handle, 2, &_frame_buffers[0], &_frame_buffers[1]); + break; + default: + ret = esp_lcd_dpi_panel_get_frame_buffer(_panel_handle, 3, &_frame_buffers[0], &_frame_buffers[1], + &_frame_buffers[2]); + break; + } + if (ret != ESP_OK || !_frame_buffers[0]) { + ESP_LOGE(TAG, "get framebuffer failed: %s", esp_err_to_name(ret)); + return false; + } + + if (!_refresh_done_sem) { + _refresh_done_sem = xSemaphoreCreateBinary(); + } + if (_refresh_done_sem) { + esp_lcd_dpi_panel_event_callbacks_t callbacks = {}; + callbacks.on_refresh_done = on_refresh_done; + (void)esp_lcd_dpi_panel_register_event_callbacks(_panel_handle, &callbacks, this); + } + + return true; + } + + bool Panel_LT8912B::setup_framebuffer(void) + { + auto fb = static_cast(_frame_buffers[0]); + if (!fb) { + return false; + } + + const auto height = _cfg.panel_height; + const size_t line_array_size = height * sizeof(void*); + auto line_array = static_cast(heap_alloc_dma(line_array_size)); + if (!line_array) { + return false; + } + std::fill_n(line_array, height, nullptr); + + const size_t line_length = ((_cfg.panel_width * _write_bits >> 3) + 3) & ~3; + for (int y = 0; y < height; ++y) { + line_array[y] = fb; + fb += line_length; + } + _lines_buffer = line_array; + return true; + } + + bool Panel_LT8912B::init(bool use_reset) + { + if (_lines_buffer) { + return false; + } + + _cfg.memory_width = _config_detail.h_res; + _cfg.memory_height = _config_detail.v_res; + _cfg.panel_width = _config_detail.h_res; + _cfg.panel_height = _config_detail.v_res; + setColorDepth(_config_detail.output_depth); + + if (!Panel_FrameBufferBase::init(use_reset)) { + return false; + } + + auto bus = getBusDSI(); + if (!bus) { + ESP_LOGE(TAG, "Bus_DSI is required"); + return false; + } + + if (!init_panel(bus) || !setup_framebuffer()) { + release_panel(); + return false; + } + return true; + } + + void Panel_LT8912B::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) + { + Panel_FrameBufferBase::display(x, y, w, h); + if (_config_detail.use_draw_bitmap && _panel_handle && _frame_buffers[0]) { + (void)esp_lcd_panel_draw_bitmap(_panel_handle, 0, 0, _cfg.panel_width, _cfg.panel_height, _frame_buffers[0]); + } + } + + void Panel_LT8912B::waitDisplay(void) + { + if (_refresh_done_sem) { + (void)xSemaphoreTake(_refresh_done_sem, pdMS_TO_TICKS(100)); + } + } + + void Panel_LT8912B::setSleep(bool flg_sleep) + { + if (_panel_handle) { + (void)esp_lcd_panel_disp_sleep(_panel_handle, flg_sleep); + } + } + + bool IRAM_ATTR Panel_LT8912B::on_refresh_done(esp_lcd_panel_handle_t panel, + esp_lcd_dpi_panel_event_data_t* edata, + void* user_ctx) + { + (void)panel; + (void)edata; + + auto self = static_cast(user_ctx); + if (!self || !self->_refresh_done_sem) { + return false; + } + + BaseType_t high_task_woken = pdFALSE; + xSemaphoreGiveFromISR(self->_refresh_done_sem, &high_task_woken); + return high_task_woken == pdTRUE; + } + + void Panel_LT8912B::release_panel(void) + { + if (_refresh_done_sem) { + vSemaphoreDelete(_refresh_done_sem); + _refresh_done_sem = nullptr; + } + if (_lines_buffer) { + heap_free(_lines_buffer); + _lines_buffer = nullptr; + } + if (_panel_handle) { + esp_lcd_panel_del(_panel_handle); + _panel_handle = nullptr; + } + if (_io_avi) { + esp_lcd_panel_io_del(_io_avi); + _io_avi = nullptr; + } + if (_io_cec) { + esp_lcd_panel_io_del(_io_cec); + _io_cec = nullptr; + } + if (_io_main) { + esp_lcd_panel_io_del(_io_main); + _io_main = nullptr; + } + if (_i2c_bus_owned && _i2c_bus) { + i2c_del_master_bus(_i2c_bus); + _i2c_bus_owned = false; + } + _i2c_bus = nullptr; + for (auto& fb : _frame_buffers) { + fb = nullptr; + } + } + +//---------------------------------------------------------------------------- + } +} + +#endif diff --git a/src/lgfx/v1/platforms/esp32p4/Panel_LT8912B.hpp b/src/lgfx/v1/platforms/esp32p4/Panel_LT8912B.hpp new file mode 100644 index 00000000..a5b72cfa --- /dev/null +++ b/src/lgfx/v1/platforms/esp32p4/Panel_LT8912B.hpp @@ -0,0 +1,257 @@ +/*----------------------------------------------------------------------------/ + 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 + +#if __has_include() +#include +#if SOC_MIPI_DSI_SUPPORTED + +#include "../../panel/Panel_FrameBufferBase.hpp" + +#include "Bus_DSI.hpp" + +#include +#include +#include +#include + +namespace m5 +{ + class I2C_Class; +} + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + class Bus_DSI; + + namespace detail + { + enum lt8912b_init_token_t : uint8_t + { + lt8912b_seq_main = 0x00, + lt8912b_seq_cec_dsi = 0x01, + lt8912b_seq_avi = 0x02, + lt8912b_seq_mipi_analog = 0xF0, + lt8912b_seq_mipi_basic = 0xF1, + lt8912b_seq_video_setup = 0xF2, + lt8912b_seq_detect_mipi = 0xF3, + lt8912b_seq_avi_infoframe = 0xF4, + lt8912b_seq_mipi_rx_reset = 0xF5, + lt8912b_seq_lvds_off = 0xF6, + lt8912b_seq_hdmi_on = 0xF7, + lt8912b_seq_dpi_init = 0xF8, + lt8912b_seq_end = 0xFF, + }; + } + + struct Panel_LT8912B : public Panel_FrameBufferBase + { + public: + struct config_detail_t + { + uint16_t h_res = 1280; + uint16_t v_res = 720; + uint8_t refresh_rate = 60; + + // Prefer M5Unified's already-started internal I2C bus. + m5::I2C_Class* i2c = nullptr; + + // Fallback for projects that expose an existing ESP-IDF driver_ng bus. + int i2c_port = 1; + int i2c_sda = GPIO_NUM_0; // fallback SDA when no existing I2C bus is found. + int i2c_scl = GPIO_NUM_1; // fallback SCL when no existing I2C bus is found. + uint32_t i2c_freq = 100000; + i2c_master_bus_handle_t i2c_master_bus = nullptr; + + uint8_t fb_num = 1; + uint8_t lane_num = 2; + bool use_draw_bitmap = false; + + color_depth_t output_depth = rgb888_3Byte; + }; + + Panel_LT8912B(void) { _cfg.bus_shared = false; } + ~Panel_LT8912B(void) override; + + bool init(bool use_reset) override; + void waitDisplay(void) override; + bool displayBusy(void) override { return false; } + void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override; + + color_depth_t setColorDepth(color_depth_t depth) override; + + void setSleep(bool flg_sleep) override; + void setPowerSave(bool flg_idle) override { setSleep(flg_idle); } + + const config_detail_t& config_detail(void) const { return _config_detail; } + void config_detail(const config_detail_t& config_detail) { _config_detail = config_detail; } + + esp_lcd_panel_handle_t getPanelHandle(void) const { return _panel_handle; } + void* getFrameBuffer(size_t index = 0) const { return index < 3 ? _frame_buffers[index] : nullptr; } + + Bus_DSI* getBusDSI(void) const + { + auto b = getBus(); + return (b && b->busType() == bus_type_t::bus_dsi) + ? static_cast(b) + : nullptr; + } + + private: + bool get_i2c_bus(void); + bool init_panel(Bus_DSI* bus); + bool setup_framebuffer(void); + void release_panel(void); + + static bool on_refresh_done(esp_lcd_panel_handle_t panel, + esp_lcd_dpi_panel_event_data_t* edata, + void* user_ctx); + + const uint8_t* getInitCommands(uint8_t listno) const override + { + using namespace detail; + // Custom sequence: target, register, data length, data...; action tokens handle dynamic steps. + static constexpr uint8_t list0[] = { + lt8912b_seq_main, 0x02, 1, 0xF7, + lt8912b_seq_main, 0x08, 1, 0xFF, + lt8912b_seq_main, 0x09, 1, 0xFF, + lt8912b_seq_main, 0x0A, 1, 0xFF, + lt8912b_seq_main, 0x0B, 1, 0x7C, + lt8912b_seq_main, 0x0C, 1, 0xFF, + lt8912b_seq_main, 0x31, 1, 0xE1, + lt8912b_seq_main, 0x32, 1, 0xE1, + lt8912b_seq_main, 0x33, 1, 0x0C, + lt8912b_seq_main, 0x37, 1, 0x00, + lt8912b_seq_main, 0x38, 1, 0x22, + lt8912b_seq_main, 0x60, 1, 0x82, + lt8912b_seq_main, 0x39, 1, 0x45, + lt8912b_seq_main, 0x3A, 1, 0x00, + lt8912b_seq_main, 0x3B, 1, 0x00, + lt8912b_seq_main, 0x44, 1, 0x31, + lt8912b_seq_main, 0x55, 1, 0x44, + lt8912b_seq_main, 0x57, 1, 0x01, + lt8912b_seq_main, 0x5A, 1, 0x02, + + lt8912b_seq_mipi_analog, + lt8912b_seq_mipi_basic, + + lt8912b_seq_cec_dsi, 0x4E, 1, 0x93, + lt8912b_seq_cec_dsi, 0x4F, 1, 0x3E, + lt8912b_seq_cec_dsi, 0x50, 1, 0x29, + lt8912b_seq_cec_dsi, 0x51, 1, 0x80, + lt8912b_seq_cec_dsi, 0x1E, 1, 0x4F, + lt8912b_seq_cec_dsi, 0x1F, 1, 0x5E, + lt8912b_seq_cec_dsi, 0x20, 1, 0x01, + lt8912b_seq_cec_dsi, 0x21, 1, 0x2C, + lt8912b_seq_cec_dsi, 0x22, 1, 0x01, + lt8912b_seq_cec_dsi, 0x23, 1, 0xFA, + lt8912b_seq_cec_dsi, 0x24, 1, 0x00, + lt8912b_seq_cec_dsi, 0x25, 1, 0xC8, + lt8912b_seq_cec_dsi, 0x26, 1, 0x00, + lt8912b_seq_cec_dsi, 0x27, 1, 0x5E, + lt8912b_seq_cec_dsi, 0x28, 1, 0x01, + lt8912b_seq_cec_dsi, 0x29, 1, 0x2C, + lt8912b_seq_cec_dsi, 0x2A, 1, 0x01, + lt8912b_seq_cec_dsi, 0x2B, 1, 0xFA, + lt8912b_seq_cec_dsi, 0x2C, 1, 0x00, + lt8912b_seq_cec_dsi, 0x2D, 1, 0xC8, + lt8912b_seq_cec_dsi, 0x2E, 1, 0x00, + lt8912b_seq_cec_dsi, 0x42, 1, 0x64, + lt8912b_seq_cec_dsi, 0x43, 1, 0x00, + lt8912b_seq_cec_dsi, 0x44, 1, 0x04, + lt8912b_seq_cec_dsi, 0x45, 1, 0x00, + lt8912b_seq_cec_dsi, 0x46, 1, 0x59, + lt8912b_seq_cec_dsi, 0x47, 1, 0x00, + lt8912b_seq_cec_dsi, 0x48, 1, 0xF2, + lt8912b_seq_cec_dsi, 0x49, 1, 0x06, + lt8912b_seq_cec_dsi, 0x4A, 1, 0x00, + lt8912b_seq_cec_dsi, 0x4B, 1, 0x72, + lt8912b_seq_cec_dsi, 0x4C, 1, 0x45, + lt8912b_seq_cec_dsi, 0x4D, 1, 0x00, + lt8912b_seq_cec_dsi, 0x52, 1, 0x08, + lt8912b_seq_cec_dsi, 0x53, 1, 0x00, + lt8912b_seq_cec_dsi, 0x54, 1, 0xB2, + lt8912b_seq_cec_dsi, 0x55, 1, 0x00, + lt8912b_seq_cec_dsi, 0x56, 1, 0xE4, + lt8912b_seq_cec_dsi, 0x57, 1, 0x0D, + lt8912b_seq_cec_dsi, 0x58, 1, 0x00, + lt8912b_seq_cec_dsi, 0x59, 1, 0xE4, + lt8912b_seq_cec_dsi, 0x5A, 1, 0x8A, + lt8912b_seq_cec_dsi, 0x5B, 1, 0x00, + lt8912b_seq_cec_dsi, 0x5C, 1, 0x34, + lt8912b_seq_cec_dsi, 0x51, 1, 0x00, + + lt8912b_seq_video_setup, + lt8912b_seq_detect_mipi, + lt8912b_seq_video_setup, + lt8912b_seq_avi_infoframe, + lt8912b_seq_mipi_rx_reset, + + lt8912b_seq_main, 0xB2, 1, 0x01, + lt8912b_seq_avi, 0x06, 1, 0x08, + lt8912b_seq_avi, 0x07, 1, 0xF0, + lt8912b_seq_avi, 0x34, 1, 0xD2, + lt8912b_seq_avi, 0x0F, 1, 0x2B, + + lt8912b_seq_main, 0x44, 1, 0x30, + lt8912b_seq_main, 0x51, 1, 0x05, + lt8912b_seq_main, 0x50, 1, 0x24, + lt8912b_seq_main, 0x51, 1, 0x2D, + lt8912b_seq_main, 0x52, 1, 0x04, + lt8912b_seq_main, 0x69, 1, 0x0E, + lt8912b_seq_main, 0x69, 1, 0x8E, + lt8912b_seq_main, 0x6A, 1, 0x00, + lt8912b_seq_main, 0x6C, 1, 0xB8, + lt8912b_seq_main, 0x6B, 1, 0x51, + lt8912b_seq_main, 0x04, 1, 0xFB, + lt8912b_seq_main, 0x04, 1, 0xFF, + lt8912b_seq_main, 0x7F, 1, 0x00, + lt8912b_seq_main, 0xA8, 1, 0x13, + + lt8912b_seq_lvds_off, + lt8912b_seq_hdmi_on, + lt8912b_seq_dpi_init, + lt8912b_seq_end, + }; + switch (listno) { + case 0: return list0; + default: return nullptr; + } + } + + config_detail_t _config_detail; + + esp_lcd_panel_handle_t _panel_handle = nullptr; + esp_lcd_panel_io_handle_t _io_main = nullptr; + esp_lcd_panel_io_handle_t _io_cec = nullptr; + esp_lcd_panel_io_handle_t _io_avi = nullptr; + i2c_master_bus_handle_t _i2c_bus = nullptr; + bool _i2c_bus_owned = false; + void* _frame_buffers[3] = {}; + SemaphoreHandle_t _refresh_done_sem = nullptr; + }; + +//---------------------------------------------------------------------------- + } +} + +#endif +#endif