| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the GPL v2 as described in the | ||
| 10 | * included LICENSE.GPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include "fpp-json.h" | ||
| 16 | |||
| 17 | #include <sys/mman.h> | ||
| 18 | #include <fcntl.h> | ||
| 19 | #include <unistd.h> | ||
| 20 | |||
| 21 | #include "../log.h" | ||
| 22 | |||
| 23 | #include "ILI9488.h" | ||
| 24 | |||
| 25 | #define PAGE_SIZE (4 * 1024) | ||
| 26 | #define BLOCK_SIZE (4 * 1024) | ||
| 27 | |||
| 28 | // GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y) | ||
| 29 | #define INP_GPIO(g) *(m_gpio + ((g) / 10)) = *(m_gpio + ((g) / 10)) & ~(7 << (((g) % 10) * 3)) | ||
| 30 | #define OUT_GPIO(g) *(m_gpio + ((g) / 10)) = *(m_gpio + ((g) / 10)) | (1 << (((g) % 10) * 3)) | ||
| 31 | #define SET_GPIO_ALT(g, a) *(m_gpio + (((g) / 10))) = *(m_gpio + (((g) / 10))) | (((a) <= 3 ? (a) + 4 : (a) == 4 ? 3 \ | ||
| 32 | : 2) \ | ||
| 33 | << (((g) % 10) * 3)) | ||
| 34 | |||
| 35 | #define GPIO_SET *(m_gpio + 7) // sets bits which are 1 ignores bits which are 0 | ||
| 36 | #define GPIO_CLR *(m_gpio + 10) // clears bits which are 1 ignores bits which are 0 | ||
| 37 | |||
| 38 | #define GET_GPIO(g) (*(m_gpio + 13) & (1 << g)) // 0 if LOW, (1<<g) if HIGH | ||
| 39 | |||
| 40 | #define GPIO_PULL *(m_gpio + 37) // Pull up/pull down | ||
| 41 | #define GPIO_PULLCLK0 *(m_gpio + 38) // Pull up/pull down clock | ||
| 42 | |||
| 43 | #define ILI_PIN_CSX 4 | ||
| 44 | #define ILI_PIN_WRX 17 | ||
| 45 | #define ILI_PIN_DCX 18 | ||
| 46 | #define ILI_PIN_D1 22 | ||
| 47 | #define ILI_PIN_D2 23 | ||
| 48 | #define ILI_PIN_D3 24 | ||
| 49 | #define ILI_PIN_D4 10 | ||
| 50 | #define ILI_PIN_D5 25 | ||
| 51 | #define ILI_PIN_D6 9 | ||
| 52 | #define ILI_PIN_D7 11 | ||
| 53 | #define ILI_PIN_D8 8 | ||
| 54 | #define ILI_PIN_RST 7 | ||
| 55 | |||
| 56 | const unsigned ILI_dataBits = | ||
| 57 | 1 << ILI_PIN_D1 | | ||
| 58 | 1 << ILI_PIN_D2 | | ||
| 59 | 1 << ILI_PIN_D3 | | ||
| 60 | 1 << ILI_PIN_D4 | | ||
| 61 | 1 << ILI_PIN_D5 | | ||
| 62 | 1 << ILI_PIN_D6 | | ||
| 63 | 1 << ILI_PIN_D7 | | ||
| 64 | 1 << ILI_PIN_D8; | ||
| 65 | |||
| 66 | const int ILI_allPins[12] = { ILI_PIN_CSX, ILI_PIN_WRX, ILI_PIN_DCX, ILI_PIN_D1, ILI_PIN_D2, ILI_PIN_D3, ILI_PIN_D4, ILI_PIN_D5, ILI_PIN_D6, ILI_PIN_D7, ILI_PIN_D8, ILI_PIN_RST }; | ||
| 67 | const int ILI_dataPins[8] = { ILI_PIN_D1, ILI_PIN_D2, ILI_PIN_D3, ILI_PIN_D4, ILI_PIN_D5, ILI_PIN_D6, ILI_PIN_D7, ILI_PIN_D8 }; | ||
| 68 | |||
| 69 | #include "Plugin.h" | ||
| 70 | class ILI9488Plugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin { | ||
| 71 | public: | ||
| 72 | ✗ | ILI9488Plugin() : | |
| 73 | ✗ | FPPPlugins::Plugin("ILI9488") { | |
| 74 | ✗ | } | |
| 75 | ✗ | virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override { | |
| 76 | ✗ | return new ILI9488Output(startChannel, channelCount); | |
| 77 | } | ||
| 78 | }; | ||
| 79 | |||
| 80 | extern "C" { | ||
| 81 | ✗ | FPPPlugins::Plugin* createPlugin() { | |
| 82 | ✗ | return new ILI9488Plugin(); | |
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | ///////////////////////////////////////////////////////////////////////////// | ||
| 87 | |||
| 88 | /* | ||
| 89 | * | ||
| 90 | */ | ||
| 91 | ✗ | ILI9488Output::ILI9488Output(unsigned int startChannel, unsigned int channelCount) : | |
| 92 | ThreadedChannelOutput(startChannel, channelCount), | ||
| 93 | ✗ | m_initialized(0), | |
| 94 | ✗ | m_rows(480), | |
| 95 | ✗ | m_cols(320), | |
| 96 | ✗ | m_pixels(0), | |
| 97 | ✗ | m_bpp(24), | |
| 98 | ✗ | m_gpio_map(NULL), | |
| 99 | ✗ | m_gpio(NULL), | |
| 100 | ✗ | m_clearWRXDataBits(0), | |
| 101 | ✗ | m_bitDCX(0), | |
| 102 | ✗ | m_bitWRX(0) { | |
| 103 | ✗ | LogDebug(VB_CHANNELOUT, "ILI9488Output::ILI9488Output(%u, %u)\n", | |
| 104 | startChannel, channelCount); | ||
| 105 | |||
| 106 | ✗ | m_pixels = m_rows * m_cols; | |
| 107 | ✗ | m_useDoubleBuffer = 1; | |
| 108 | ✗ | } | |
| 109 | |||
| 110 | /* | ||
| 111 | * | ||
| 112 | */ | ||
| 113 | ✗ | ILI9488Output::~ILI9488Output() { | |
| 114 | ✗ | LogDebug(VB_CHANNELOUT, "ILI9488Output::~ILI9488Output()\n"); | |
| 115 | ✗ | } | |
| 116 | |||
| 117 | ///////////////////////////////////////////////////////////////////////////// | ||
| 118 | |||
| 119 | /* | ||
| 120 | * | ||
| 121 | */ | ||
| 122 | ✗ | int ILI9488Output::Init(Json::Value config) { | |
| 123 | ✗ | LogDebug(VB_CHANNELOUT, "ILI9488Output::Init(JSON)\n"); | |
| 124 | |||
| 125 | ✗ | if (config.isMember("bpp")) | |
| 126 | ✗ | m_bpp = config["bpp"].asInt(); | |
| 127 | |||
| 128 | // RawSendData() sends 3 channels a pixel for the whole panel | ||
| 129 | ✗ | if (m_channelCount < (unsigned int)m_pixels * 3) { | |
| 130 | ✗ | LogErr(VB_CHANNELOUT, "ILI9488: the panel needs %d channels, the output has %u\n", | |
| 131 | m_pixels * 3, m_channelCount); | ||
| 132 | ✗ | return 0; | |
| 133 | } | ||
| 134 | |||
| 135 | ✗ | if (!ILI9488_Init()) { | |
| 136 | ✗ | return 0; | |
| 137 | } | ||
| 138 | |||
| 139 | ✗ | return ThreadedChannelOutput::Init(config); | |
| 140 | } | ||
| 141 | |||
| 142 | /* | ||
| 143 | * | ||
| 144 | */ | ||
| 145 | ✗ | int ILI9488Output::Close(void) { | |
| 146 | ✗ | LogDebug(VB_CHANNELOUT, "ILI9488Output::Close()\n"); | |
| 147 | |||
| 148 | ✗ | ILI9488_Cleanup(); | |
| 149 | |||
| 150 | ✗ | return ThreadedChannelOutput::Close(); | |
| 151 | } | ||
| 152 | |||
| 153 | /* | ||
| 154 | * | ||
| 155 | */ | ||
| 156 | ✗ | int ILI9488Output::RawSendData(unsigned char* channelData) { | |
| 157 | ✗ | LogExcess(VB_CHANNELOUT, "ILI9488Output::RawSendData(%p)\n", channelData); | |
| 158 | |||
| 159 | ✗ | if (!m_initialized) | |
| 160 | ✗ | return 0; | |
| 161 | |||
| 162 | ✗ | unsigned char* c = channelData; | |
| 163 | |||
| 164 | ✗ | ILI9488_Command(0x2c); // Write memory start | |
| 165 | |||
| 166 | ✗ | GPIO_SET = m_bitDCX; // Set the 'data mode' bit | |
| 167 | |||
| 168 | unsigned int rgb565; | ||
| 169 | |||
| 170 | ✗ | for (int p = 0; p < m_pixels; p++) { | |
| 171 | ✗ | if (m_bpp == 16) { | |
| 172 | // byte by byte: an unsigned long load read 8 bytes at each | ||
| 173 | // 3 byte pixel, past the end of the data at the last ones | ||
| 174 | ✗ | rgb565 = ((c[0] << 8) & 0b1111100000000000) | | |
| 175 | ✗ | ((c[1] << 3) & 0b0000011111100000) | | |
| 176 | ✗ | (c[2] >> 3); | |
| 177 | |||
| 178 | ✗ | ILI9488_SendByte(rgb565 >> 8); | |
| 179 | ✗ | ILI9488_SendByte(rgb565); | |
| 180 | |||
| 181 | ✗ | c += 3; | |
| 182 | } else { | ||
| 183 | ✗ | ILI9488_SendByte(*c++); | |
| 184 | ✗ | ILI9488_SendByte(*c++); | |
| 185 | ✗ | ILI9488_SendByte(*c++); | |
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | ✗ | ILI9488_Command(0x00); // Null command, ends write | |
| 190 | |||
| 191 | ✗ | return m_channelCount; | |
| 192 | } | ||
| 193 | |||
| 194 | /* | ||
| 195 | * | ||
| 196 | */ | ||
| 197 | ✗ | void ILI9488Output::DumpConfig(void) { | |
| 198 | ✗ | LogDebug(VB_CHANNELOUT, "ILI9488Output::DumpConfig()\n"); | |
| 199 | ✗ | LogDebug(VB_CHANNELOUT, " Columns: %d\n", m_cols); | |
| 200 | ✗ | LogDebug(VB_CHANNELOUT, " Rows : %d\n", m_rows); | |
| 201 | ✗ | LogDebug(VB_CHANNELOUT, " Pixels : %d\n", m_pixels); | |
| 202 | |||
| 203 | ✗ | ThreadedChannelOutput::DumpConfig(); | |
| 204 | ✗ | } | |
| 205 | |||
| 206 | /* | ||
| 207 | * | ||
| 208 | */ | ||
| 209 | ✗ | bool ILI9488Output::ILI9488_Init(void) { | |
| 210 | ✗ | LogDebug(VB_CHANNELOUT, "ILI9488Output::ILI9488_Init()\n"); | |
| 211 | |||
| 212 | ✗ | int mem_fd = 0; | |
| 213 | |||
| 214 | ✗ | if ((mem_fd = open("/dev/gpiomem", O_RDWR | O_SYNC)) < 0) { | |
| 215 | // not exit(): fppd outlives an output that cannot start | ||
| 216 | ✗ | LogErr(VB_CHANNELOUT, "Can't open /dev/gpiomem\n"); | |
| 217 | ✗ | return false; | |
| 218 | } | ||
| 219 | ✗ | LogDebug(VB_CHANNELOUT, "Opened /dev/gpiomem, mem_fd = %d\n", mem_fd); | |
| 220 | |||
| 221 | ✗ | off_t gpio_base = 0x200000; | |
| 222 | |||
| 223 | ✗ | m_gpio_map = mmap( | |
| 224 | NULL, //Any adddress in our space will do | ||
| 225 | BLOCK_SIZE, //Map length | ||
| 226 | PROT_READ | PROT_WRITE, // Enable reading & writting to mapped memory | ||
| 227 | MAP_SHARED, //Shared with other processes | ||
| 228 | mem_fd, //File to map | ||
| 229 | gpio_base //Offset to GPIO peripheral | ||
| 230 | ); | ||
| 231 | |||
| 232 | ✗ | close(mem_fd); //No need to keep mem_fd open after mmap | |
| 233 | |||
| 234 | ✗ | if (m_gpio_map == MAP_FAILED) { | |
| 235 | ✗ | LogErr(VB_CHANNELOUT, "mmap error %p\n", m_gpio_map); //errno also set! | |
| 236 | ✗ | m_gpio_map = NULL; | |
| 237 | ✗ | return false; | |
| 238 | } | ||
| 239 | |||
| 240 | // Always use volatile pointer! | ||
| 241 | ✗ | m_gpio = (volatile unsigned*)m_gpio_map; | |
| 242 | |||
| 243 | ✗ | LogDebug(VB_CHANNELOUT, "Initializing GPIO pins for output\n"); | |
| 244 | ✗ | int i = 0; | |
| 245 | ✗ | for (i = 0; i < 12; i++) { | |
| 246 | ✗ | INP_GPIO(ILI_allPins[i]); | |
| 247 | ✗ | OUT_GPIO(ILI_allPins[i]); | |
| 248 | } | ||
| 249 | |||
| 250 | ✗ | LogDebug(VB_CHANNELOUT, "Initializing data lookup table\n"); | |
| 251 | |||
| 252 | // Initialize our lookup table | ||
| 253 | int bit; | ||
| 254 | ✗ | for (i = 0; i < 256; i++) { | |
| 255 | ✗ | m_dataValues[i] = 0; | |
| 256 | ✗ | for (bit = 0; bit < 8; bit++) { | |
| 257 | ✗ | m_dataValues[i] |= (1 & (i >> bit)) << ILI_dataPins[bit]; | |
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | ✗ | m_clearWRXDataBits = (1 << ILI_PIN_WRX) | ILI_dataBits; | |
| 262 | ✗ | m_bitWRX = 1 << ILI_PIN_WRX; | |
| 263 | ✗ | m_bitDCX = 1 << ILI_PIN_DCX; | |
| 264 | |||
| 265 | ✗ | GPIO_CLR = 1 << ILI_PIN_CSX; | |
| 266 | |||
| 267 | ✗ | ILI9488_Command(0x11); // Exit sleep | |
| 268 | ✗ | usleep(100000); | |
| 269 | |||
| 270 | // ILI9488_Command(0x01); // Software Reset | ||
| 271 | // usleep(150000); | ||
| 272 | |||
| 273 | // ILI9488_Command(0x13); // Normal Display Mode | ||
| 274 | |||
| 275 | ✗ | ILI9488_Command(0x36); // Memory Access Control | |
| 276 | ✗ | ILI9488_Data(0b10001000); // RGB with top closest to Pi GPIO connector | |
| 277 | //ILI9488_Data(0x48); | ||
| 278 | // 0x48 = 0b01001000 | ||
| 279 | // 0 - Row Address Order (vertical flip of frame data) | ||
| 280 | // 1 - Column Address Order (horizontal flip of frame data) | ||
| 281 | // 0 - Row/Column exchange (1 = 480x320, 0 = 320x480 | ||
| 282 | // 0 - Vertical Refresh Order | ||
| 283 | // 1 - RGB or BGR (0 = RGB, 1 = BGR) | ||
| 284 | // 0 - Horizontal Refresh Order | ||
| 285 | // 0 - no hflip | ||
| 286 | // 0 - no flip | ||
| 287 | |||
| 288 | ✗ | ILI9488_Command(0xE0); // Positive Gamma Control | |
| 289 | ✗ | ILI9488_Data(0x00); | |
| 290 | ✗ | ILI9488_Data(0x04); | |
| 291 | ✗ | ILI9488_Data(0x0E); | |
| 292 | ✗ | ILI9488_Data(0x08); | |
| 293 | ✗ | ILI9488_Data(0x17); | |
| 294 | ✗ | ILI9488_Data(0x0A); | |
| 295 | ✗ | ILI9488_Data(0x40); | |
| 296 | ✗ | ILI9488_Data(0x79); | |
| 297 | ✗ | ILI9488_Data(0x4D); | |
| 298 | ✗ | ILI9488_Data(0x07); | |
| 299 | ✗ | ILI9488_Data(0x0E); | |
| 300 | ✗ | ILI9488_Data(0x0A); | |
| 301 | ✗ | ILI9488_Data(0x1A); | |
| 302 | ✗ | ILI9488_Data(0x1D); | |
| 303 | ✗ | ILI9488_Data(0x0F); | |
| 304 | |||
| 305 | ✗ | ILI9488_Command(0xE1); // Negative Gamma Control | |
| 306 | ✗ | ILI9488_Data(0x00); | |
| 307 | ✗ | ILI9488_Data(0x1B); | |
| 308 | ✗ | ILI9488_Data(0x1F); | |
| 309 | ✗ | ILI9488_Data(0x02); | |
| 310 | ✗ | ILI9488_Data(0x10); | |
| 311 | ✗ | ILI9488_Data(0x05); | |
| 312 | ✗ | ILI9488_Data(0x32); | |
| 313 | ✗ | ILI9488_Data(0x34); | |
| 314 | ✗ | ILI9488_Data(0x43); | |
| 315 | ✗ | ILI9488_Data(0x02); | |
| 316 | ✗ | ILI9488_Data(0x0A); | |
| 317 | ✗ | ILI9488_Data(0x09); | |
| 318 | ✗ | ILI9488_Data(0x33); | |
| 319 | ✗ | ILI9488_Data(0x37); | |
| 320 | ✗ | ILI9488_Data(0x0F); | |
| 321 | |||
| 322 | ✗ | ILI9488_Command(0xB1); // Frame Rate | |
| 323 | ✗ | ILI9488_Data(0x00); // as low as it can go, ~28hz | |
| 324 | |||
| 325 | ✗ | ILI9488_Command(0xB0); // Interface mode control | |
| 326 | ✗ | ILI9488_Data(0x00); | |
| 327 | |||
| 328 | ✗ | ILI9488_Command(0x3A); // Set pixel format | |
| 329 | |||
| 330 | ✗ | if (m_bpp == 16) | |
| 331 | ✗ | ILI9488_Data(0x55); // 16bpp RGB565 | |
| 332 | else | ||
| 333 | ✗ | ILI9488_Data(0x66); // 18bpp (24bpp w/ lower 2bpp bits ignored) | |
| 334 | |||
| 335 | ✗ | SetRegion(0, 0, m_cols - 1, m_rows - 1); | |
| 336 | |||
| 337 | ✗ | ILI9488_Command(0x29); //display on | |
| 338 | |||
| 339 | // Not sure if we need to do this twice or not, but sample python did | ||
| 340 | ✗ | SetRegion(0, 0, m_cols - 1, m_rows - 1); | |
| 341 | |||
| 342 | ✗ | m_initialized = 1; | |
| 343 | |||
| 344 | ✗ | LogDebug(VB_CHANNELOUT, "Initialization complete\n"); | |
| 345 | ✗ | return true; | |
| 346 | } | ||
| 347 | |||
| 348 | /* | ||
| 349 | * | ||
| 350 | */ | ||
| 351 | ✗ | inline void ILI9488Output::ILI9488_SendByte(unsigned char byte) { | |
| 352 | ✗ | GPIO_CLR = m_clearWRXDataBits; | |
| 353 | |||
| 354 | ✗ | GPIO_SET = m_dataValues[byte]; | |
| 355 | |||
| 356 | ✗ | GPIO_SET = m_bitWRX; | |
| 357 | ✗ | } | |
| 358 | |||
| 359 | /* | ||
| 360 | * | ||
| 361 | */ | ||
| 362 | ✗ | void ILI9488Output::ILI9488_Command(unsigned char cmd) { | |
| 363 | ✗ | LogExcess(VB_CHANNELOUT, "ILI9488_Command(0x%02x)\n", cmd); | |
| 364 | |||
| 365 | ✗ | GPIO_CLR = m_bitDCX; | |
| 366 | ✗ | ILI9488_SendByte(cmd); | |
| 367 | ✗ | } | |
| 368 | |||
| 369 | /* | ||
| 370 | * | ||
| 371 | */ | ||
| 372 | ✗ | void ILI9488Output::ILI9488_Data(unsigned char data) { | |
| 373 | ✗ | LogExcess(VB_CHANNELOUT, "ILI9488_Data(0x%02x)\n", data); | |
| 374 | |||
| 375 | ✗ | GPIO_SET = m_bitDCX; | |
| 376 | ✗ | ILI9488_SendByte(data); | |
| 377 | ✗ | } | |
| 378 | |||
| 379 | /* | ||
| 380 | * | ||
| 381 | */ | ||
| 382 | ✗ | void ILI9488Output::ILI9488_Cleanup(void) { | |
| 383 | ✗ | LogDebug(VB_CHANNELOUT, "ILI9488_Cleanup()\n"); | |
| 384 | |||
| 385 | ✗ | GPIO_CLR = 1 << ILI_PIN_CSX; | |
| 386 | ✗ | } | |
| 387 | |||
| 388 | /* | ||
| 389 | * | ||
| 390 | */ | ||
| 391 | ✗ | inline void ILI9488Output::SetColumnRange(unsigned int x1, unsigned int x2) { | |
| 392 | // ILI9488_Command( 0x2B ); // Use 'row' setting here because we rotate display | ||
| 393 | ✗ | ILI9488_Command(0x2A); | |
| 394 | ✗ | ILI9488_Data(x1 >> 8); | |
| 395 | ✗ | ILI9488_Data(x1); | |
| 396 | ✗ | ILI9488_Data(x2 >> 8); | |
| 397 | ✗ | ILI9488_Data(x2); | |
| 398 | ✗ | } | |
| 399 | |||
| 400 | /* | ||
| 401 | * | ||
| 402 | */ | ||
| 403 | ✗ | inline void ILI9488Output::SetRowRange(unsigned int y1, unsigned int y2) { | |
| 404 | // ILI9488_Command( 0x2A ); // Use 'column' setting here because we rotate display | ||
| 405 | ✗ | ILI9488_Command(0x2B); | |
| 406 | ✗ | ILI9488_Data(y1 >> 8); | |
| 407 | ✗ | ILI9488_Data(y1); | |
| 408 | ✗ | ILI9488_Data(y2 >> 8); | |
| 409 | ✗ | ILI9488_Data(y2); | |
| 410 | ✗ | } | |
| 411 | |||
| 412 | /* | ||
| 413 | * | ||
| 414 | */ | ||
| 415 | ✗ | inline void ILI9488Output::SetRegion(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) { | |
| 416 | ✗ | SetColumnRange(x1, x2); | |
| 417 | ✗ | SetRowRange(y1, y2); | |
| 418 | ✗ | } | |
| 419 |