1 /*
2  * Copyright (c) 2016, Freescale Semiconductor, Inc.
3  * Copyright 2016-2017, 2020-2021 NXP
4  * All rights reserved.
5  *
6  * SPDX-License-Identifier: BSD-3-Clause
7  */
8 
9 #include "fsl_ssd1963.h"
10 
11 /*******************************************************************************
12  * Definitations
13  ******************************************************************************/
14 #define SSD1963_ORIENTATION_MODE_MASK \
15     (SSD1963_ADDR_MODE_PAGE_ADDR_ORDER | SSD1963_ADDR_MODE_PAG_COL_ADDR_ORDER | SSD1963_ADDR_MODE_COL_ADDR_ORDER)
16 
17 #define SSD1963_FLIP_MODE_MASK (SSD1963_ADDR_MODE_FLIP_VERT | SSD1963_ADDR_MODE_FLIP_HORZ)
18 
19 /* The PLL VCO clock must be in the range of (250MHz, 800MHz). */
20 #define SSD1963_VCO_MIN_HZ    250000000U
21 #define SSD1963_VCO_MAX_HZ    800000000U
22 #define SSD1963_PLL_MULTI_MIN 0x0U
23 #define SSD1963_PLL_MULTI_MAX 0xFFU
24 #define SSD1963_PLL_DIV_MIN   0x0U
25 #define SSD1963_PLL_DIV_MAX   0x1FU
26 
27 /* The PLL output frequency will be configured to about 100MHz. */
28 #define SSD1963_PLL_FREQ_HZ 100000000U
29 
30 /* The max value of LCDC_FPR to generate the lshift clock (pixel clock). */
31 #define SSD1963_LCDC_FPR_MAX 0xFFFFFU
32 
33 #if (SSD1963_DATA_WITDH != 16) && (SSD1963_DATA_WITDH != 8)
34 #error Only support 8-bit or 16-bit data bus
35 #endif
36 
37 #define SSD1963_DATA_WITDH_BYTE (((uint8_t)SSD1963_DATA_WITDH) / 8U)
38 
39 #define SSD1963_RET(x)                 \
40     do                                 \
41     {                                  \
42         status = (x);                  \
43         if (kStatus_Success != status) \
44         {                              \
45             return status;             \
46         }                              \
47     } while (false)
48 
49 /*******************************************************************************
50  * Prototypes
51  ******************************************************************************/
52 
53 /*!
54  * @brief Use loop to delay.
55  *
56  * @param loops Number of the loops.
57  */
58 static void SSD1963_Delay(uint32_t loops);
59 
60 /*!
61  * @brief Get the multiplier and divider setting for PLL.
62  *
63  * This function gets the multiplier and divider to generate PLL frequency at
64  * about 100MHz. The actually PLL frequency is returned.
65  *
66  * @param multi The multiplier value.
67  * @param div The divider value.
68  * @param srcClock_Hz The external reference clock(XTAL or CLK) frequency in Hz.
69  * @return Generated PLL frequency with the @p multi and @p div. If could not get
70  * the desired PLL frequency, this function returns 0.
71  */
72 static uint32_t SSD1963_GetPllDivider(uint8_t *multi, uint8_t *div, uint32_t srcClock_Hz);
73 
74 /*******************************************************************************
75  * Variables
76  ******************************************************************************/
77 
78 /*******************************************************************************
79  * Code
80  ******************************************************************************/
81 
SSD1963_Delay(uint32_t loops)82 static void SSD1963_Delay(uint32_t loops)
83 {
84     while (0U != (loops--))
85     {
86         __NOP();
87     }
88 }
89 
SSD1963_GetPllDivider(uint8_t * multi,uint8_t * div,uint32_t srcClock_Hz)90 static uint32_t SSD1963_GetPllDivider(uint8_t *multi, uint8_t *div, uint32_t srcClock_Hz)
91 {
92     uint32_t multiCur, divCur, pllFreqCur, vcoCur, diffCur;
93     uint32_t multiCandidate   = 0U;
94     uint32_t divCandidate     = 0U;
95     uint32_t pllFreqCandidate = 0U;
96     uint32_t diff             = 0xFFFFFFFFU;
97 
98     for (multiCur = SSD1963_PLL_MULTI_MIN; multiCur <= SSD1963_PLL_MULTI_MAX; multiCur++)
99     {
100         vcoCur = srcClock_Hz * (multiCur + 1U);
101 
102         /* VCO must be larger than SSD1963_VCO_MIN_HZ. */
103         if (vcoCur <= SSD1963_VCO_MIN_HZ)
104         {
105             continue;
106         }
107 
108         /* VCO must be smaller than SSD1963_VCO_MAX_HZ. */
109         if (vcoCur >= SSD1963_VCO_MAX_HZ)
110         {
111             break;
112         }
113 
114         divCur = ((vcoCur + (SSD1963_PLL_FREQ_HZ / 2U)) / SSD1963_PLL_FREQ_HZ) - 1U;
115 
116         /*
117          * VCO frequency must be in the range of (250MHz, 800MHz). The desired
118          * PLL output frequency is 100MHz, then the divCur here must be in the
119          * range of (1, 8). In this case, it is not necessary to check whether
120          * divCur is in the range of (0, 31). But for safty when the desired
121          * PLL frequency is changed, here check the upper range.
122          */
123 #if ((((SSD1963_VCO_MAX_HZ + (SSD1963_PLL_FREQ_HZ / 2U)) / SSD1963_PLL_FREQ_HZ) - 1U) > SSD1963_PLL_DIV_MAX)
124         if (divCur > SSD1963_PLL_DIV_MAX)
125         {
126             divCur = SSD1963_PLL_DIV_MAX;
127         }
128 #endif
129 
130         pllFreqCur = vcoCur / (divCur + 1U);
131 
132         if (SSD1963_PLL_FREQ_HZ > pllFreqCur)
133         {
134             diffCur = SSD1963_PLL_FREQ_HZ - pllFreqCur;
135         }
136         else
137         {
138             diffCur = pllFreqCur - SSD1963_PLL_FREQ_HZ;
139         }
140 
141         /* Find better multi and divider. */
142         if (diff > diffCur)
143         {
144             diff             = diffCur;
145             multiCandidate   = multiCur;
146             divCandidate     = divCur;
147             pllFreqCandidate = pllFreqCur;
148         }
149     }
150 
151     *multi = (uint8_t)multiCandidate;
152     *div   = (uint8_t)divCandidate;
153 
154     return pllFreqCandidate;
155 }
156 
SSD1963_Init(ssd1963_handle_t * handle,const ssd1963_config_t * config,const dbi_xfer_ops_t * xferOps,void * xferOpsData,uint32_t srcClock_Hz)157 status_t SSD1963_Init(ssd1963_handle_t *handle,
158                       const ssd1963_config_t *config,
159                       const dbi_xfer_ops_t *xferOps,
160                       void *xferOpsData,
161                       uint32_t srcClock_Hz)
162 {
163     assert(handle);
164     assert(config);
165 
166     uint8_t multi, div;
167     uint32_t pllFreq_Hz;
168     uint32_t fpr; /* Pixel clock = PLL clock * ((fpr + 1) / 2^20) */
169     float fprFloat;
170 #if (16 == SSD1963_DATA_WITDH)
171     uint16_t commandParam[8];
172 #else
173     uint8_t commandParam[8];
174 #endif
175     uint16_t vt, vps, ht, hps;
176     status_t status;
177 
178     pllFreq_Hz = SSD1963_GetPllDivider(&multi, &div, srcClock_Hz);
179 
180     /* Could not set the PLL to desired frequency. */
181     if (0U == pllFreq_Hz)
182     {
183         return kStatus_InvalidArgument;
184     }
185 
186     fprFloat = ((float)config->pclkFreq_Hz / (float)pllFreq_Hz) * (float)1048576.0f; /* 1048576 = 1<<20 */
187     fpr      = (uint32_t)fprFloat;
188 
189     if ((fpr < 1U) || (fpr > (SSD1963_LCDC_FPR_MAX + 1U)))
190     {
191         return kStatus_InvalidArgument;
192     }
193 
194     fpr--;
195 
196     /* Initialize the handle. */
197     (void)memset(handle, 0, sizeof(ssd1963_handle_t));
198 
199     handle->panelWidth  = config->panelWidth;
200     handle->panelHeight = config->panelHeight;
201     handle->xferOps     = xferOps;
202     handle->xferOpsData = xferOpsData;
203 
204     /* Soft reset. */
205     SSD1963_RET(handle->xferOps->writeCommand(xferOpsData, SSD1963_SOFT_RESET));
206     SSD1963_Delay(50000);
207 
208     /* Setup the PLL. */
209     /* Set the multiplier and divider. */
210     commandParam[0] = multi;
211     commandParam[1] = (uint8_t)(div | (1U << 5U));
212     commandParam[2] = 1U << 2U;
213     SSD1963_RET(handle->xferOps->writeCommand(xferOpsData, SSD1963_SET_PLL_MN));
214     SSD1963_RET(handle->xferOps->writeData(xferOpsData, commandParam, 3U * SSD1963_DATA_WITDH_BYTE));
215 
216     /* Enable PLL. */
217     commandParam[0] = 0x01U;
218     SSD1963_RET(handle->xferOps->writeCommand(xferOpsData, SSD1963_SET_PLL));
219     SSD1963_RET(handle->xferOps->writeData(xferOpsData, commandParam, 1U * SSD1963_DATA_WITDH_BYTE));
220 
221     /* Delay at least 100us, to wait for the PLL stable. */
222     SSD1963_Delay(500);
223 
224     /* Use the PLL. */
225     commandParam[0] = 0x03U;
226     SSD1963_RET(handle->xferOps->writeCommand(xferOpsData, SSD1963_SET_PLL));
227     SSD1963_RET(handle->xferOps->writeData(xferOpsData, commandParam, 1U * SSD1963_DATA_WITDH_BYTE));
228 
229     /* Configure the pixel clock. */
230     commandParam[0] = (uint8_t)((fpr & 0xFF0000U) >> 16U);
231     commandParam[1] = (uint8_t)((fpr & 0xFF00U) >> 8U);
232     commandParam[2] = (uint8_t)((fpr & 0xFFU));
233     SSD1963_RET(handle->xferOps->writeCommand(xferOpsData, SSD1963_SET_LSHIFT_FREQ));
234     SSD1963_RET(handle->xferOps->writeData(xferOpsData, commandParam, 3U * SSD1963_DATA_WITDH_BYTE));
235 
236     /* Configure LCD panel. */
237     commandParam[0] =
238         (uint8_t)((uint8_t)config->panelDataWidth | (uint8_t)config->polarityFlags); /* Not enable FRC, dithering. */
239     commandParam[1] = 0x20U;                                                         /* TFT mode. */
240     commandParam[2] = (uint8_t)((config->panelWidth - 1U) >> 8);
241     commandParam[3] = (uint8_t)((config->panelWidth - 1U) & 0xFFU);
242     commandParam[4] = (uint8_t)((config->panelHeight - 1U) >> 8);
243     commandParam[5] = (uint8_t)((config->panelHeight - 1U) & 0xFFU);
244     commandParam[6] = 0;
245     SSD1963_RET(handle->xferOps->writeCommand(xferOpsData, SSD1963_SET_LCD_MODE));
246     SSD1963_RET(handle->xferOps->writeData(xferOpsData, commandParam, 7U * SSD1963_DATA_WITDH_BYTE));
247 
248     /* Horizontal period setting. */
249     ht              = config->panelWidth + config->hsw + config->hfp + config->hbp;
250     hps             = config->hsw + config->hbp;
251     commandParam[0] = (uint8_t)((ht - 1U) >> 8U);
252     commandParam[1] = (uint8_t)((ht - 1U) & 0xFFU);
253     commandParam[2] = (uint8_t)(hps >> 8U);
254     commandParam[3] = (uint8_t)(hps & 0xFFU);
255     commandParam[4] = (uint8_t)(config->hsw - 1U);
256     commandParam[5] = 0U;
257     commandParam[6] = 0U;
258     commandParam[7] = 0U;
259     SSD1963_RET(handle->xferOps->writeCommand(xferOpsData, SSD1963_SET_HORI_PERIOD));
260     SSD1963_RET(handle->xferOps->writeData(xferOpsData, commandParam, 8U * SSD1963_DATA_WITDH_BYTE));
261 
262     /* Vertical period setting. */
263     vt              = config->panelHeight + config->vsw + config->vfp + config->vbp;
264     vps             = config->vsw + config->vbp;
265     commandParam[0] = (uint8_t)((vt - 1U) >> 8U);
266     commandParam[1] = (uint8_t)((vt - 1U) & 0xFFU);
267     commandParam[2] = (uint8_t)(vps >> 8U);
268     commandParam[3] = (uint8_t)(vps & 0xFFU);
269     commandParam[4] = (uint8_t)(config->vsw - 1U);
270     commandParam[5] = 0U;
271     commandParam[6] = 0U;
272     SSD1963_RET(handle->xferOps->writeCommand(xferOpsData, SSD1963_SET_VERT_PERIOD));
273     SSD1963_RET(handle->xferOps->writeData(xferOpsData, commandParam, 7U * SSD1963_DATA_WITDH_BYTE));
274 
275     /* Pixel format. */
276     return SSD1963_SetPixelFormat(handle, config->pixelInterface);
277 }
278 
SSD1963_SetMemoryDoneCallback(ssd1963_handle_t * handle,dbi_mem_done_callback_t callback,void * userData)279 void SSD1963_SetMemoryDoneCallback(ssd1963_handle_t *handle, dbi_mem_done_callback_t callback, void *userData)
280 {
281     assert(handle);
282 
283     handle->xferOps->setMemoryDoneCallback(handle->xferOpsData, callback, userData);
284 }
285 
SSD1963_Deinit(ssd1963_handle_t * handle)286 void SSD1963_Deinit(ssd1963_handle_t *handle)
287 {
288     assert(handle);
289 
290     (void)memset(handle, 0, sizeof(ssd1963_handle_t));
291 }
292 
SSD1963_StartDisplay(ssd1963_handle_t * handle)293 status_t SSD1963_StartDisplay(ssd1963_handle_t *handle)
294 {
295     return handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_DISPLAY_ON);
296 }
297 
SSD1963_StopDisplay(ssd1963_handle_t * handle)298 status_t SSD1963_StopDisplay(ssd1963_handle_t *handle)
299 {
300     return handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_DISPLAY_OFF);
301 }
302 
SSD1963_SetFlipMode(ssd1963_handle_t * handle,ssd1963_flip_mode_t mode)303 status_t SSD1963_SetFlipMode(ssd1963_handle_t *handle, ssd1963_flip_mode_t mode)
304 {
305     status_t status;
306 
307 #if (16 == SSD1963_DATA_WITDH)
308     uint16_t newAddrMode;
309 
310     newAddrMode = (uint16_t)(handle->addrMode & ~SSD1963_FLIP_MODE_MASK) | (uint16_t)mode;
311 #else
312     uint8_t newAddrMode;
313 
314     newAddrMode = (uint8_t)(handle->addrMode & ~SSD1963_FLIP_MODE_MASK) | (uint8_t)mode;
315 #endif
316 
317     SSD1963_RET(handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_ADDRESS_MODE));
318     SSD1963_RET(handle->xferOps->writeData(handle->xferOpsData, &newAddrMode, 1U * SSD1963_DATA_WITDH_BYTE));
319 
320     handle->addrMode = (uint8_t)newAddrMode;
321 
322     return kStatus_Success;
323 }
324 
SSD1963_SetOrientationMode(ssd1963_handle_t * handle,ssd1963_orientation_mode_t mode)325 status_t SSD1963_SetOrientationMode(ssd1963_handle_t *handle, ssd1963_orientation_mode_t mode)
326 {
327     status_t status;
328 #if (16 == SSD1963_DATA_WITDH)
329     uint16_t newAddrMode;
330 
331     newAddrMode = (uint16_t)(handle->addrMode & ~SSD1963_ORIENTATION_MODE_MASK) | (uint16_t)mode;
332 #else
333     uint8_t newAddrMode;
334 
335     newAddrMode = (uint8_t)(handle->addrMode & ~SSD1963_ORIENTATION_MODE_MASK) | (uint8_t)mode;
336 #endif
337 
338     SSD1963_RET(handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_ADDRESS_MODE));
339     SSD1963_RET(handle->xferOps->writeData(handle->xferOpsData, &newAddrMode, 1U * SSD1963_DATA_WITDH_BYTE));
340 
341     handle->addrMode = (uint8_t)newAddrMode;
342 
343     return kStatus_Success;
344 }
345 
SSD1963_SelectArea(ssd1963_handle_t * handle,uint16_t startX,uint16_t startY,uint16_t endX,uint16_t endY)346 status_t SSD1963_SelectArea(ssd1963_handle_t *handle, uint16_t startX, uint16_t startY, uint16_t endX, uint16_t endY)
347 {
348     uint16_t sc; /* Start of column number. */
349     uint16_t ec; /* End of column number. */
350     uint16_t sp; /* Start of page number. */
351     uint16_t ep; /* End of page number. */
352     ssd1963_orientation_mode_t mode;
353 #if (16 == SSD1963_DATA_WITDH)
354     uint16_t commandParam[4]; /* Command parameters for set_page_address and set_column_address. */
355 #else
356     uint8_t commandParam[4]; /* Command parameters for set_page_address and set_column_address. */
357 #endif
358     status_t status;
359 
360     mode = (ssd1963_orientation_mode_t)(uint8_t)(handle->addrMode & SSD1963_ORIENTATION_MODE_MASK);
361 
362     switch (mode)
363     {
364         default:
365             /* For MISRA-C 2012 rule 16.4. */
366         case kSSD1963_Orientation0:
367             sp = startY;
368             ep = endY;
369             sc = startX;
370             ec = endX;
371             break;
372 
373         case kSSD1963_Orientation90:
374             sp = handle->panelHeight - 1U - endX;
375             ep = handle->panelHeight - 1U - startX;
376             sc = startY;
377             ec = endY;
378             break;
379 
380         case kSSD1963_Orientation180:
381             sp = handle->panelHeight - 1U - endY;
382             ep = handle->panelHeight - 1U - startY;
383             sc = handle->panelWidth - 1U - endX;
384             ec = handle->panelWidth - 1U - startX;
385             break;
386 
387         case kSSD1963_Orientation270:
388             sp = startX;
389             ep = endX;
390             sc = handle->panelWidth - 1U - endY;
391             ec = handle->panelWidth - 1U - startY;
392             break;
393     }
394 
395     /* Send the set_page_address command. */
396     commandParam[0] = (uint8_t)((sp & 0xFF00U) >> 8U);
397     commandParam[1] = (uint8_t)(sp & 0xFFU);
398     commandParam[2] = (uint8_t)((ep & 0xFF00U) >> 8U);
399     commandParam[3] = (uint8_t)(ep & 0xFFU);
400 
401     SSD1963_RET(handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_PAGE_ADDRESS));
402     SSD1963_RET(handle->xferOps->writeData(handle->xferOpsData, commandParam, 4U * SSD1963_DATA_WITDH_BYTE));
403 
404     /* Send the set_column_address command. */
405     commandParam[0] = (uint8_t)((sc & 0xFF00U) >> 8U);
406     commandParam[1] = (uint8_t)(sc & 0xFFU);
407     commandParam[2] = (uint8_t)((ec & 0xFF00U) >> 8U);
408     commandParam[3] = (uint8_t)(ec & 0xFFU);
409 
410     SSD1963_RET(handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_COLUMN_ADDRESS));
411     return handle->xferOps->writeData(handle->xferOpsData, commandParam, 4U * SSD1963_DATA_WITDH_BYTE);
412 }
413 
414 #if (16 == SSD1963_DATA_WITDH)
SSD1963_WritePixels(ssd1963_handle_t * handle,const uint16_t * pixels,uint32_t length)415 status_t SSD1963_WritePixels(ssd1963_handle_t *handle, const uint16_t *pixels, uint32_t length)
416 {
417     return handle->xferOps->writeMemory(handle->xferOpsData, SSD1963_WRITE_MEMORY_START, (const uint8_t *)pixels,
418                                         length * 2U);
419 }
420 
SSD1963_ReadPixels(ssd1963_handle_t * handle,uint16_t * pixels,uint32_t length)421 status_t SSD1963_ReadPixels(ssd1963_handle_t *handle, uint16_t *pixels, uint32_t length)
422 {
423     return handle->xferOps->readMemory(handle->xferOpsData, SSD1963_READ_MEMORY_START, (uint8_t *)pixels, length * 2U);
424 }
425 #endif
426 
SSD1963_SetBackLight(ssd1963_handle_t * handle,uint8_t value)427 status_t SSD1963_SetBackLight(ssd1963_handle_t *handle, uint8_t value)
428 {
429     status_t status;
430 #if (16 == SSD1963_DATA_WITDH)
431     uint16_t commandParam[] = {0x06U, value, 0x01U, 0xFFU, 0x00U, 0x01U};
432 #else
433     uint8_t commandParam[] = {0x06U, value, 0x01U, 0xFFU, 0x00U, 0x01U};
434 #endif
435 
436     SSD1963_RET(handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_PWM_CONF));
437     return handle->xferOps->writeData(handle->xferOpsData, commandParam, sizeof(commandParam));
438 }
439 
SSD1963_EnableTearEffect(ssd1963_handle_t * handle,bool enable)440 status_t SSD1963_EnableTearEffect(ssd1963_handle_t *handle, bool enable)
441 {
442     uint16_t regVal = 0;
443     status_t status;
444 
445     if (enable)
446     {
447         status = handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_TEAR_ON);
448         status = handle->xferOps->writeData(handle->xferOpsData, &regVal, SSD1963_DATA_WITDH_BYTE);
449     }
450     else
451     {
452         status = handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_TEAR_OFF);
453     }
454 
455     return status;
456 }
457 
SSD1963_SetPixelFormat(ssd1963_handle_t * handle,ssd1963_pixel_interface_t pixelFormat)458 status_t SSD1963_SetPixelFormat(ssd1963_handle_t *handle, ssd1963_pixel_interface_t pixelFormat)
459 {
460     status_t status;
461 #if (16 == SSD1963_DATA_WITDH)
462     uint16_t commandParam[1];
463 #else
464     uint8_t commandParam[1];
465 #endif
466 
467     /* Data interface. */
468 #if (8 == SSD1963_DATA_WITDH)
469     commandParam[0] = 0;
470 #else
471     commandParam[0] = 3;
472 #endif
473     SSD1963_RET(handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_PIXEL_DATA_INTERFACE));
474     SSD1963_RET(handle->xferOps->writeData(handle->xferOpsData, commandParam, 1U * SSD1963_DATA_WITDH_BYTE));
475 
476     /* Address mode. */
477     handle->addrMode &= (uint8_t)(~SSD1963_ADDR_MODE_BGR);
478 #if (8 == SSD1963_DATA_WITDH)
479     if (kSSD1963_RGB888 == pixelFormat)
480     {
481         handle->addrMode |= SSD1963_ADDR_MODE_BGR;
482     }
483 #else
484     if (kSSD1963_BGR565 == pixelFormat)
485     {
486         handle->addrMode |= SSD1963_ADDR_MODE_BGR;
487     }
488 #endif
489 
490     commandParam[0] = handle->addrMode;
491     SSD1963_RET(handle->xferOps->writeCommand(handle->xferOpsData, SSD1963_SET_ADDRESS_MODE));
492     return handle->xferOps->writeData(handle->xferOpsData, commandParam, 1U * SSD1963_DATA_WITDH_BYTE);
493 }
494 
SSD1963_ReadMemory(ssd1963_handle_t * handle,uint8_t * data,uint32_t length)495 status_t SSD1963_ReadMemory(ssd1963_handle_t *handle, uint8_t *data, uint32_t length)
496 {
497     return handle->xferOps->readMemory(handle->xferOpsData, SSD1963_READ_MEMORY_START, data, length);
498 }
499 
SSD1963_WriteMemory(ssd1963_handle_t * handle,const uint8_t * data,uint32_t length)500 status_t SSD1963_WriteMemory(ssd1963_handle_t *handle, const uint8_t *data, uint32_t length)
501 {
502     return handle->xferOps->writeMemory(handle->xferOpsData, SSD1963_WRITE_MEMORY_START, data, length);
503 }
504