mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-24 17:46:45 +00:00
1018 lines
32 KiB
C
1018 lines
32 KiB
C
/*
|
|
* lws abstract display implementation for Epd 4-gray / black-red UC8176 on spi
|
|
*
|
|
* Copyright (C) 2019 - 2022 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*
|
|
* Based on datasheet
|
|
*
|
|
* https://www.waveshare.com/w/upload/8/88/UC8176.pdf
|
|
*
|
|
* This chip takes a planar approach with two distinct framebuffers for b0 and
|
|
* b1 of the grey levels. That's OK in itself, but the problem is for whole-
|
|
* frame updates, loading the planes must be done in full plane-frames
|
|
* sequentially, ie, you must issue the whole frame of b0 and then the whole
|
|
* frame of b1s, you can't interleave them. So we must create a private,
|
|
* ephemeral side buffer for b1 data and send it afterwards (15KB heap during
|
|
* display update for 400 x 300)
|
|
*
|
|
* The driver detects at runtime if it should be in BE, BW + "red", or Gray mode
|
|
* from the details in the lws_display information. It uses direct DMA capable
|
|
* line buffer allocations and direct DMA if available.
|
|
*
|
|
* There are similar chips UC8151 and EK79686 with different RESOLUTION
|
|
* commands (smaller range), this driver attempts to cover them all by detection
|
|
* at runtime from the details in the lws_display information.
|
|
*/
|
|
|
|
#include <private-lib-core.h>
|
|
#include <dlo/private-lib-drivers-display-dlo.h>
|
|
|
|
enum {
|
|
UC8176_CMD_PANEL_SETTING = 0x00,
|
|
UC8176_CMD_POWER_SETTING = 0x01,
|
|
UC8176_CMD_POWER_OFF = 0x02,
|
|
UC8176_CMD_POWER_OFF_SEQUENCE_SETTING = 0x03,
|
|
UC8176_CMD_POWER_ON = 0x04,
|
|
UC8176_CMD_POWER_ON_MEASURE = 0x05,
|
|
UC8176_CMD_BOOSTER_SOFT_START = 0x06,
|
|
UC8176_CMD_DEEP_SLEEP = 0x07,
|
|
UC8176_CMD_DATA_START_TRANSMISSION_1 = 0x10,
|
|
UC8176_CMD_DATA_STOP = 0x11,
|
|
UC8176_CMD_DISPLAY_REFRESH = 0x12,
|
|
UC8176_CMD_DATA_START_TRANSMISSION_2 = 0x13,
|
|
UC8176_CMD_LUT_FOR_VCOM = 0x20,
|
|
UC8176_CMD_LUT_WHITE_TO_WHITE = 0x21,
|
|
UC8176_CMD_LUT_BLACK_TO_WHITE = 0x22,
|
|
UC8176_CMD_LUT_WHITE_TO_BLACK = 0x23,
|
|
UC8176_CMD_LUT_BLACK_TO_BLACK = 0x24,
|
|
UC8176_CMD_LUT25 = 0x25,
|
|
UC8176_CMD_PLL_CONTROL = 0x30,
|
|
UC8176_CMD_TEMPERATURE_SENSOR_COMMAND = 0x40,
|
|
UC8176_CMD_TEMPERATURE_SENSOR_SELECTION = 0x41,
|
|
UC8176_CMD_TEMPERATURE_SENSOR_WRITE = 0x42,
|
|
UC8176_CMD_TEMPERATURE_SENSOR_READ = 0x43,
|
|
UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING = 0x50,
|
|
UC8176_CMD_LOW_POWER_DETECTION = 0x51,
|
|
UC8176_CMD_TCON_SETTING = 0x60,
|
|
UC8176_CMD_RESOLUTION_SETTING = 0x61,
|
|
UC8176_CMD_GSST_SETTING = 0x65,
|
|
UC8176_CMD_GET_STATUS = 0x71,
|
|
UC8176_CMD_AUTO_MEASUREMENT_VCOM = 0x80,
|
|
UC8176_CMD_READ_VCOM_VALUE = 0x81,
|
|
UC8176_CMD_VCOM_DC_SETTING = 0x82,
|
|
UC8176_CMD_PARTIAL_WINDOW = 0x90,
|
|
UC8176_CMD_PARTIAL_IN = 0x91,
|
|
UC8176_CMD_PARTIAL_OUT = 0x92,
|
|
UC8176_CMD_PROGRAM_MODE = 0xA0,
|
|
UC8176_CMD_ACTIVE_PROGRAMMING = 0xA1,
|
|
UC8176_CMD_READ_OTP = 0xA2,
|
|
UC8176_CMD_POWER_SAVING = 0xE3,
|
|
};
|
|
|
|
typedef enum {
|
|
LWSDISPST_IDLE,
|
|
LWSDISPST_INIT1,
|
|
LWSDISPST_INIT2,
|
|
LWSDISPST_INIT3,
|
|
LWSDISPST_INIT4,
|
|
LWSDISPST_INIT5,
|
|
|
|
LWSDISPST_WRITE1,
|
|
LWSDISPST_WRITE2,
|
|
LWSDISPST_WRITE3,
|
|
LWSDISPST_WRITE4,
|
|
LWSDISPST_WRITE5,
|
|
} lws_display_update_state_t;
|
|
|
|
//static
|
|
const uint8_t uc8176_init1_gray[] = {
|
|
5, UC8176_CMD_POWER_SETTING, 0x03, 0x00, 0x2b, 0x2b, 0x13,
|
|
3, UC8176_CMD_BOOSTER_SOFT_START, 0x17, 0x17, 0x17,
|
|
0, UC8176_CMD_POWER_ON,
|
|
}, uc8176_init1_bw[] = {
|
|
4, UC8176_CMD_POWER_SETTING, 0x03, 0x00, 0x2b, 0x2b,
|
|
3, UC8176_CMD_BOOSTER_SOFT_START, 0x17, 0x17, 0x17,
|
|
0, UC8176_CMD_POWER_ON,
|
|
}, ek79686_init1_bw_104[] = {
|
|
5, UC8176_CMD_POWER_SETTING, 0x03, 0x00, 0x2b, 0x2b, 0x03,
|
|
3, UC8176_CMD_BOOSTER_SOFT_START, 0x17, 0x17, 0x17,
|
|
0, UC8176_CMD_POWER_ON,
|
|
}, uc8176_init1_red[] = {
|
|
4, UC8176_CMD_POWER_SETTING, 0x03, 0x00, 0x2b, 0x2b,
|
|
3, UC8176_CMD_BOOSTER_SOFT_START, 0x17, 0x17, 0x17,
|
|
0, UC8176_CMD_POWER_ON,
|
|
}, ek79686_init1_red_104[] = {
|
|
5, UC8176_CMD_POWER_SETTING, 0x03, 0x00, 0x2b, 0x2b, 0x03,
|
|
3, UC8176_CMD_BOOSTER_SOFT_START, 0x17, 0x17, 0x17,
|
|
0, UC8176_CMD_POWER_ON,
|
|
},
|
|
|
|
uc8176_init2_gray[] = {
|
|
1, UC8176_CMD_PANEL_SETTING, 0x3f,
|
|
1, UC8176_CMD_PLL_CONTROL, 0x3c,
|
|
4, UC8176_CMD_RESOLUTION_SETTING, 0x01, 0x90, 0x01, 0x2c,
|
|
1, UC8176_CMD_VCOM_DC_SETTING, 0x28,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0x97,
|
|
}, uc8176_init2_red[] = {
|
|
1, UC8176_CMD_PANEL_SETTING, 0x0f,
|
|
1, UC8176_CMD_PLL_CONTROL, 0x3c,
|
|
4, UC8176_CMD_RESOLUTION_SETTING, 0x01, 0x90, 0x01, 0x2c,
|
|
1, UC8176_CMD_VCOM_DC_SETTING, 0x28,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0x97,
|
|
}, uc8176_init2_bw[] = {
|
|
2, UC8176_CMD_PANEL_SETTING, 0xbf, 0x0d,
|
|
1, UC8176_CMD_PLL_CONTROL, 0x3c,
|
|
4, UC8176_CMD_RESOLUTION_SETTING, 0x01, 0x90, 0x01, 0x2c,
|
|
1, UC8176_CMD_VCOM_DC_SETTING, 0x28,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0x97,
|
|
44, UC8176_CMD_LUT_FOR_VCOM,
|
|
0x00, 0x17, 0x00, 0x00, 0x00, 0x02,
|
|
0x00, 0x17, 0x17, 0x00, 0x00, 0x02,
|
|
0x00, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00,
|
|
42, UC8176_CMD_LUT_WHITE_TO_WHITE,
|
|
0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
|
|
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
|
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
|
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_BLACK_TO_WHITE,
|
|
0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
|
|
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
|
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
|
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_WHITE_TO_BLACK,
|
|
0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
|
|
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
|
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
|
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_BLACK_TO_BLACK,
|
|
0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
|
|
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
|
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
|
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}, ek79686_init2_bw_104[] = {
|
|
1, UC8176_CMD_PANEL_SETTING, 0x1f,
|
|
1, UC8176_CMD_PLL_CONTROL, 0x3a,
|
|
3, UC8176_CMD_RESOLUTION_SETTING, 104, 0, 212,
|
|
1, UC8176_CMD_VCOM_DC_SETTING, 0x28,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0xb7,
|
|
44, UC8176_CMD_LUT_FOR_VCOM,
|
|
0x00, 0x08, 0x00, 0x00, 0x00, 0x02,
|
|
0x60, 0x28, 0x28, 0x00, 0x00, 0x01,
|
|
0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x12, 0x12, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00,
|
|
42, UC8176_CMD_LUT_WHITE_TO_WHITE,
|
|
0x40, 0x08, 0x00, 0x00, 0x00, 0x02,
|
|
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
|
|
0x40, 0x14, 0x00, 0x00, 0x00, 0x01,
|
|
0xA0, 0x12, 0x12, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_BLACK_TO_WHITE,
|
|
0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
|
|
0x90, 0x0F, 0x0F, 0x00, 0x00, 0x03,
|
|
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
|
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_WHITE_TO_BLACK,
|
|
0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
|
|
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
|
|
0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
|
|
0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_BLACK_TO_BLACK,
|
|
0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
|
|
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
|
|
0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
|
|
0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}, ek79686_init2_red_104[] = {
|
|
1, UC8176_CMD_PANEL_SETTING, 0x0f,
|
|
1, UC8176_CMD_PLL_CONTROL, 0x3c,
|
|
3, UC8176_CMD_RESOLUTION_SETTING, 104, 0, 212,
|
|
1, UC8176_CMD_VCOM_DC_SETTING, 0x12,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0x97,
|
|
}, uc8176_off[] = {
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0xf7,
|
|
//0, UC8176_CMD_POWER_OFF,
|
|
}, uc8176_wp1_gray[] = {
|
|
0, UC8176_CMD_PARTIAL_OUT,
|
|
1, UC8176_CMD_PANEL_SETTING, 0x3f,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0x97,
|
|
0, UC8176_CMD_DATA_START_TRANSMISSION_1,
|
|
}, uc8176_wp1_red[] = {
|
|
0, UC8176_CMD_PARTIAL_OUT,
|
|
1, UC8176_CMD_PANEL_SETTING, 0x0f,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0x97,
|
|
0, UC8176_CMD_DATA_START_TRANSMISSION_1,
|
|
}, uc8176_wp1_bw[] = {
|
|
0, UC8176_CMD_PARTIAL_OUT,
|
|
2, UC8176_CMD_PANEL_SETTING, 0xbf, 0x0d,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0x97,
|
|
0, UC8176_CMD_DATA_START_TRANSMISSION_1,
|
|
}, uc8176_wp2[] = {
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0x97,
|
|
0, UC8176_CMD_DATA_START_TRANSMISSION_2,
|
|
}, uc8176_complete_gray[] = {
|
|
42, UC8176_CMD_LUT_FOR_VCOM,
|
|
0x00, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
|
0x60, 0x14, 0x14, 0x00, 0x00, 0x01,
|
|
0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x13, 0x0A, 0x01, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_WHITE_TO_WHITE,
|
|
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
|
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
|
0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
|
0xA0, 0x13, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_BLACK_TO_WHITE,
|
|
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
|
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
|
0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
|
0x99, 0x0C, 0x01, 0x03, 0x04, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_WHITE_TO_BLACK,
|
|
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
|
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
|
0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
|
0x99, 0x0B, 0x04, 0x04, 0x01, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT_BLACK_TO_BLACK,
|
|
0x80, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
|
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
|
0x20, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
|
0x50, 0x13, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
42, UC8176_CMD_LUT25,
|
|
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
|
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
|
0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
|
0xA0, 0x13, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0, UC8176_CMD_DISPLAY_REFRESH,
|
|
}, uc8176_complete_red[] = {
|
|
0, UC8176_CMD_DISPLAY_REFRESH,
|
|
}, uc8176_complete_bw[] = {
|
|
0, UC8176_CMD_DISPLAY_REFRESH,
|
|
}, uc8176_w_dstop[] = {
|
|
1, UC8176_CMD_DATA_STOP, 0,
|
|
}, uc8176_partial_out_off[] = {
|
|
0, UC8176_CMD_PARTIAL_OUT,
|
|
1, UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING, 0xf7,
|
|
};
|
|
|
|
typedef struct lws_display_uc8176_spi_state {
|
|
struct lws_display_state *lds;
|
|
|
|
uint32_t *planebuf;
|
|
|
|
uint32_t *line[2];
|
|
lws_surface_error_t *u[2];
|
|
uint32_t *partbuf;
|
|
|
|
uint8_t pcmd[32];
|
|
|
|
lws_sorted_usec_list_t sul;
|
|
|
|
lws_box_t upd;
|
|
|
|
size_t pb_len;
|
|
size_t pb_pos;
|
|
size_t pb_sent;
|
|
size_t partbuf_len;
|
|
size_t partbuf_pos;
|
|
|
|
int state;
|
|
int budget;
|
|
int nplanes;
|
|
|
|
char ek79686;
|
|
char partial;
|
|
} lws_display_uc8176_spi_state_t;
|
|
|
|
#define lds_to_disp(_lds) (const lws_display_uc8176_spi_t *)_lds->disp;
|
|
#define lds_to_priv(_lds) (lws_display_uc8176_spi_state_t *)_lds->priv;
|
|
|
|
/*
|
|
* The lws greyscale line composition buffer is width x Y bytes linearly.
|
|
*
|
|
* For UC8176, this is processed into a private buffer layout in priv->line that
|
|
* is sent over SPI to the chip, the format is both packed and planar: the first
|
|
* half is packed width x 1bpp "B&W" bits, and the second half is packed width x
|
|
* "red" bits.
|
|
*
|
|
* UC8176 requires the whole frane of the B&W plane is sent first then the whole
|
|
* frane of the RED plane, which means we have to stash the red plane in heap.
|
|
*/
|
|
|
|
/* MSB plane is in first half of priv linebuf */
|
|
|
|
#define pack_native_pixel(np, _line, _roby, _x, _c) \
|
|
{ if (np == 2) { \
|
|
if ((_c) & 1) \
|
|
_line[(_roby >> 2)] |= 1 << ((_x & 31) ^ 7); else \
|
|
_line[(_roby >> 2)] &= ~(1 << ((_x & 31) ^ 7)); \
|
|
} \
|
|
if ((np == 2 && ((_c) & 2)) || (np == 1 && ((_c) & 1))) \
|
|
*_line |= 1 << ((_x & 31) ^ 7); else \
|
|
*_line &= ~(1 << ((_x & 31) ^ 7)); \
|
|
if (((_x) & 31) == 31) (_line)++; }
|
|
|
|
static void
|
|
async_cb(lws_sorted_usec_list_t *sul);
|
|
|
|
#define BUSY_TIMEOUT_BUDGET (20000 / 5)
|
|
|
|
static int
|
|
check_busy(lws_display_uc8176_spi_state_t *priv, int level)
|
|
{
|
|
const lws_display_uc8176_spi_t *ea = lds_to_disp(priv->lds);
|
|
|
|
if (ea->gpio->read(ea->busy_gpio) == level)
|
|
return 0; /* good */
|
|
|
|
if (!--priv->budget) {
|
|
lwsl_err("%s: timeout waiting idle %d\n", __func__, level);
|
|
return -1; /* timeout */
|
|
}
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
|
|
LWS_US_PER_MS * 5);
|
|
|
|
return 1; /* keeping on trying */
|
|
}
|
|
|
|
static void
|
|
async_cb(lws_sorted_usec_list_t *sul)
|
|
{
|
|
lws_display_uc8176_spi_state_t *priv = lws_container_of(sul,
|
|
lws_display_uc8176_spi_state_t, sul);
|
|
const lws_display_uc8176_spi_t *ea = lds_to_disp(priv->lds);
|
|
const lws_surface_info_t *ic = &ea->disp.ic;
|
|
int plane_line_bytes = ((ic->wh_px[LWS_LHPREF_WIDTH].whole + 31) / 32) * 4;
|
|
lws_spi_desc_t desc;
|
|
size_t s;
|
|
|
|
switch (priv->state) {
|
|
|
|
case LWSDISPST_INIT1:
|
|
/* take reset low for a short time */
|
|
ea->gpio->set(ea->reset_gpio, 0);
|
|
priv->state++;
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
|
|
async_cb, LWS_US_PER_MS * 10);
|
|
break;
|
|
|
|
case LWSDISPST_INIT2:
|
|
/* park reset high again and then wait a bit */
|
|
ea->gpio->set(ea->reset_gpio, 1);
|
|
priv->state++;
|
|
priv->budget = BUSY_TIMEOUT_BUDGET;
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
|
|
async_cb, LWS_US_PER_MS * 20);
|
|
break;
|
|
|
|
case LWSDISPST_INIT3:
|
|
if (check_busy(priv, 1))
|
|
return;
|
|
|
|
if (ic->greyscale) {
|
|
if (ic->palette_depth > 2) {
|
|
lwsl_notice("%s: init mode gray\n", __func__);
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_init1_gray,
|
|
LWS_ARRAY_SIZE(uc8176_init1_gray));
|
|
} else {
|
|
lwsl_notice("%s: init mode BW\n", __func__);
|
|
lws_spi_table_issue(ea->spi, 0, priv->ek79686 ? ek79686_init1_bw_104 : uc8176_init1_bw,
|
|
priv->ek79686 ? LWS_ARRAY_SIZE(ek79686_init1_bw_104) : LWS_ARRAY_SIZE(uc8176_init1_bw));
|
|
}
|
|
} else {
|
|
lwsl_err("%s: init mode RED\n", __func__);
|
|
lws_spi_table_issue(ea->spi, 0, priv->ek79686 ? ek79686_init1_red_104 : uc8176_init1_red,
|
|
priv->ek79686 ? LWS_ARRAY_SIZE(ek79686_init1_red_104) : LWS_ARRAY_SIZE(uc8176_init1_red));
|
|
}
|
|
|
|
priv->state++;
|
|
priv->budget = BUSY_TIMEOUT_BUDGET;
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
|
|
async_cb, LWS_US_PER_MS * 10);
|
|
break;
|
|
|
|
case LWSDISPST_INIT4:
|
|
if (check_busy(priv, 1))
|
|
return;
|
|
|
|
if (ic->greyscale) {
|
|
if (ic->palette_depth > 2)
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_init2_gray,
|
|
LWS_ARRAY_SIZE(uc8176_init2_gray));
|
|
else
|
|
lws_spi_table_issue(ea->spi, 0, priv->ek79686 ? ek79686_init2_bw_104 : uc8176_init2_bw,
|
|
priv->ek79686 ? LWS_ARRAY_SIZE(ek79686_init2_bw_104) : LWS_ARRAY_SIZE(uc8176_init2_bw));
|
|
} else
|
|
lws_spi_table_issue(ea->spi, 0, priv->ek79686 ? ek79686_init2_red_104 : uc8176_init2_red,
|
|
priv->ek79686 ? LWS_ARRAY_SIZE(ek79686_init2_red_104) : LWS_ARRAY_SIZE(uc8176_init2_red));
|
|
|
|
priv->state++;
|
|
priv->budget = BUSY_TIMEOUT_BUDGET;
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
|
|
async_cb, LWS_US_PER_MS * 10);
|
|
break;
|
|
|
|
case LWSDISPST_INIT5:
|
|
if (check_busy(priv, 1))
|
|
return;
|
|
|
|
priv->state = LWSDISPST_IDLE;
|
|
if (ea->cb)
|
|
ea->cb(priv->lds, 1);
|
|
break;
|
|
|
|
case LWSDISPST_WRITE1:
|
|
|
|
if (check_busy(priv, 1))
|
|
return;
|
|
|
|
lwsl_user("%s: WRITE1\n", __func__);
|
|
|
|
if (priv->nplanes == 2 && !priv->partial) {
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_wp2,
|
|
LWS_ARRAY_SIZE(uc8176_wp2));
|
|
priv->pb_sent = 0;
|
|
priv->state++;
|
|
} else
|
|
priv->state = LWSDISPST_WRITE3;
|
|
|
|
priv->budget = BUSY_TIMEOUT_BUDGET;
|
|
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
|
|
LWS_US_PER_MS * 2);
|
|
break;
|
|
|
|
case LWSDISPST_WRITE2:
|
|
|
|
if (check_busy(priv, 1))
|
|
return;
|
|
|
|
/* issue the cached, packed LSB plane plane frame */
|
|
|
|
s = priv->pb_pos - priv->pb_sent;
|
|
if (s > plane_line_bytes * 4u)
|
|
s = plane_line_bytes * 4u;
|
|
|
|
memset(&desc, 0, sizeof(desc));
|
|
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
|
|
desc.data = (const uint8_t *)priv->planebuf + priv->pb_sent;
|
|
desc.count_write = s;
|
|
ea->spi->queue(ea->spi, &desc);
|
|
|
|
priv->pb_sent += s;
|
|
|
|
if (priv->pb_sent == priv->pb_pos) {
|
|
priv->budget = BUSY_TIMEOUT_BUDGET;
|
|
priv->state++;
|
|
}
|
|
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb, 1);
|
|
break;
|
|
|
|
case LWSDISPST_WRITE3:
|
|
if (check_busy(priv, 1))
|
|
return;
|
|
|
|
if (ea->spi->free_dma)
|
|
ea->spi->free_dma(ea->spi,
|
|
(void **)&priv->line[0]);
|
|
else
|
|
lws_free_set_NULL(priv->line[0]);
|
|
lws_free_set_NULL(priv->u[0]);
|
|
|
|
/*
|
|
* Finalize the write of the planes, LUT set then REFRESH
|
|
*/
|
|
|
|
if (ic->greyscale) {
|
|
if (ic->palette_depth > 2 && !priv->partial)
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_complete_gray,
|
|
LWS_ARRAY_SIZE(uc8176_complete_gray));
|
|
else
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_complete_bw,
|
|
LWS_ARRAY_SIZE(uc8176_complete_bw));
|
|
} else
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_complete_red,
|
|
LWS_ARRAY_SIZE(uc8176_complete_red));
|
|
|
|
priv->budget = BUSY_TIMEOUT_BUDGET;
|
|
priv->state++;
|
|
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
|
|
LWS_US_PER_MS * 2);
|
|
break;
|
|
|
|
case LWSDISPST_WRITE4:
|
|
if (check_busy(priv, 1))
|
|
return;
|
|
|
|
lws_spi_table_issue(ea->spi, 0, priv->partial ? uc8176_partial_out_off : uc8176_off,
|
|
priv->partial ? LWS_ARRAY_SIZE(uc8176_partial_out_off) : LWS_ARRAY_SIZE(uc8176_off));
|
|
|
|
priv->budget = BUSY_TIMEOUT_BUDGET;
|
|
priv->state++;
|
|
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
|
|
LWS_US_PER_MS * 2);
|
|
break;
|
|
|
|
case LWSDISPST_WRITE5:
|
|
if (check_busy(priv, 1))
|
|
return;
|
|
|
|
/* fully completed the blit */
|
|
|
|
priv->state = LWSDISPST_IDLE;
|
|
if (ea->cb)
|
|
ea->cb(priv->lds, 2);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int
|
|
lws_display_uc8176_spi_init(struct lws_display_state *lds)
|
|
{
|
|
const lws_display_uc8176_spi_t *ea = lds_to_disp(lds);
|
|
const lws_surface_info_t *ic = &ea->disp.ic;
|
|
lws_display_uc8176_spi_state_t *priv;
|
|
|
|
priv = lws_zalloc(sizeof(*priv), __func__);
|
|
if (!priv)
|
|
return 1;
|
|
|
|
priv->lds = lds;
|
|
lds->priv = priv;
|
|
priv->ek79686 = (ic->wh_px[LWS_LHPREF_WIDTH].whole == 104) || (ic->wh_px[LWS_LHPREF_WIDTH].whole == 122);
|
|
priv->nplanes = 1 + ((ic->greyscale && ic->palette_depth > 2) || !ic->greyscale);
|
|
|
|
lwsl_notice("%s: ek79686: %d, nplanes: %d\n", __func__,
|
|
priv->ek79686, priv->nplanes);
|
|
|
|
ea->gpio->mode(ea->busy_gpio, LWSGGPIO_FL_READ | LWSGGPIO_FL_PULLUP);
|
|
|
|
ea->gpio->mode(ea->reset_gpio, LWSGGPIO_FL_WRITE | LWSGGPIO_FL_PULLUP);
|
|
|
|
ea->gpio->set(ea->reset_gpio, 1);
|
|
priv->state = LWSDISPST_INIT1;
|
|
lws_sul_schedule(lds->ctx, 0, &priv->sul, async_cb, LWS_US_PER_MS * 200);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* no backlight */
|
|
|
|
int
|
|
lws_display_uc8176_spi_brightness(const struct lws_display *disp, uint8_t b)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Partial updates can only do pure B&W
|
|
*/
|
|
|
|
static const lws_display_colour_t palette_partial[] = {
|
|
PALETTE_RGBY(0x00, 0x00, 0x00), /* black */
|
|
PALETTE_RGBY(0xff, 0xff, 0xff), /* white */
|
|
};
|
|
|
|
int
|
|
lws_display_uc8176_spi_blit(struct lws_display_state *lds, const uint8_t *src,
|
|
lws_box_t *box, lws_dll2_owner_t *ids)
|
|
{
|
|
const lws_display_uc8176_spi_t *ea = lds_to_disp(lds);
|
|
lws_display_uc8176_spi_state_t *priv = lds_to_priv(lds);
|
|
lws_greyscale_error_t *gedl_this, *gedl_next;
|
|
const lws_surface_info_t *ic = &ea->disp.ic;
|
|
int plane_line_bytes = ((ic->wh_px[LWS_LHPREF_WIDTH].whole + 31) / 32) * 4;
|
|
lws_colour_error_t *edl_this, *edl_next;
|
|
const uint8_t *pc = src;
|
|
lws_display_colour_t c;
|
|
lws_display_id_t *id;
|
|
lws_spi_desc_t desc;
|
|
uint32_t *lo;
|
|
int n, m;
|
|
|
|
if (priv->state) {
|
|
lwsl_warn("%s: ignoring as busy\n", __func__);
|
|
return 1; /* busy */
|
|
}
|
|
|
|
if (!priv->line[0]) {
|
|
/* compute separately, since we may be doing a partial */
|
|
int maxplanes = 1 + ((ic->greyscale && ic->palette_depth > 2) ||
|
|
!ic->greyscale);
|
|
size_t plane_alloc = plane_line_bytes * maxplanes;
|
|
|
|
/*
|
|
* We have to allocate the packed line and error diffusion
|
|
* buffers.
|
|
*
|
|
* For this chip a plane is 1bpp, there can be one or two planes
|
|
* in a line buffer depending if BW, or BWR or 4-gray, and two
|
|
* line buffers for DMA ping-pong.
|
|
*
|
|
* Because it's planar in the two-plane case, we have to send
|
|
* plane 1 linewise, but buffer plane 2 into DMA-capable memory
|
|
* and send it after all of plane 1.
|
|
*/
|
|
|
|
priv->pb_len = 0;
|
|
if (maxplanes == 2)
|
|
priv->pb_len = plane_line_bytes *
|
|
lds->disp->ic.wh_px[LWS_LHPREF_HEIGHT].whole;
|
|
|
|
if (ea->spi->alloc_dma)
|
|
priv->line[0] = ea->spi->alloc_dma(ea->spi,
|
|
(2 * plane_alloc) + priv->pb_len);
|
|
else
|
|
priv->line[0] = lws_malloc(2 * plane_alloc + priv->pb_len,
|
|
__func__);
|
|
|
|
if (!priv->line[0]) {
|
|
lwsl_err("%s: OOM\n", __func__);
|
|
priv->state = LWSDISPST_IDLE;
|
|
return 0;
|
|
}
|
|
|
|
// memset(priv->line[0], 0, plane_line_bytes * priv->nplanes);
|
|
|
|
priv->line[1] = (uint32_t *)(((uint8_t *)priv->line[0]) +
|
|
plane_alloc);
|
|
|
|
if (priv->pb_len)
|
|
priv->planebuf = (uint32_t *)(((uint8_t *)priv->line[1]) +
|
|
plane_alloc);
|
|
priv->pb_pos = 0;
|
|
|
|
if (lws_display_alloc_diffusion(ic, priv->u)) {
|
|
if (ea->spi->free_dma)
|
|
ea->spi->free_dma(ea->spi,
|
|
(void **)&priv->line[0]);
|
|
else
|
|
lws_free_set_NULL(priv->line[0]);
|
|
|
|
lwsl_err("%s: OOM\n", __func__);
|
|
priv->state = LWSDISPST_IDLE;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
switch (box->h.whole) {
|
|
case 0: /* update needs to be finalized */
|
|
|
|
priv->budget = BUSY_TIMEOUT_BUDGET;
|
|
priv->state = LWSDISPST_WRITE1;
|
|
|
|
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
|
|
LWS_US_PER_MS * 10);
|
|
break;
|
|
|
|
case 1: /* single line = issue line */
|
|
|
|
edl_this = (lws_colour_error_t *)priv->u[(box->y.whole & 1) ^ 1];
|
|
edl_next = (lws_colour_error_t *)priv->u[box->y.whole & 1];
|
|
gedl_this = (lws_greyscale_error_t *)edl_this;
|
|
gedl_next = (lws_greyscale_error_t *)edl_next;
|
|
lo = priv->line[box->y.whole & 1];
|
|
|
|
if (src == NULL) {
|
|
for (n = 0; n < ic->wh_px[LWS_LHPREF_WIDTH].whole; n++)
|
|
pack_native_pixel(priv->nplanes, lo,
|
|
plane_line_bytes, n, (uint8_t)3);
|
|
|
|
goto go;
|
|
}
|
|
|
|
if (ic->greyscale)
|
|
for (n = 0; n < ic->wh_px[LWS_LHPREF_WIDTH].whole; n++) {
|
|
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
|
|
|
|
m = lws_display_palettize_grey(ic,
|
|
priv->partial ? palette_partial : ic->palette,
|
|
priv->partial ? 2 : ic->palette_depth,
|
|
c, &gedl_this[n]);
|
|
pack_native_pixel(priv->nplanes, lo,
|
|
plane_line_bytes, n,
|
|
(uint8_t)m);
|
|
|
|
dist_err_floyd_steinberg_grey(n,
|
|
ic->wh_px[LWS_LHPREF_WIDTH].whole,
|
|
gedl_this, gedl_next);
|
|
pc++;
|
|
}
|
|
else
|
|
for (n = 0; n < ic->wh_px[LWS_LHPREF_WIDTH].whole; n++) {
|
|
c = (pc[2] << 16) | (pc[1] << 8) | pc[0];
|
|
|
|
m = lws_display_palettize_col(ic,
|
|
priv->partial ? palette_partial : ic->palette,
|
|
priv->partial ? 2: ic->palette_depth, c,
|
|
&edl_this[n]);
|
|
pack_native_pixel(priv->nplanes, lo,
|
|
plane_line_bytes, n,
|
|
(uint8_t)m);
|
|
|
|
dist_err_floyd_steinberg_col(n,
|
|
ic->wh_px[LWS_LHPREF_WIDTH].whole,
|
|
edl_this, edl_next);
|
|
|
|
pc += 3;
|
|
}
|
|
go:
|
|
/* must be u32-aligned for DMA... */
|
|
lo = priv->line[box->y.whole & 1] +
|
|
((priv->upd.x.whole / 8) / 4);
|
|
|
|
if (ea->spi->in_flight)
|
|
while (ea->spi->in_flight(ea->spi))
|
|
;
|
|
while (priv->partial && check_busy(priv, 1))
|
|
;
|
|
|
|
if (priv->pb_len && !priv->partial &&
|
|
priv->pb_pos + plane_line_bytes <= priv->pb_len) {
|
|
memcpy((uint8_t *)priv->planebuf + priv->pb_pos, lo,
|
|
(size_t)(ic->wh_px[LWS_LHPREF_WIDTH].whole + 7) / 8u);
|
|
|
|
priv->pb_pos += (ic->wh_px[LWS_LHPREF_WIDTH].whole + 7) / 8u;
|
|
}
|
|
|
|
if (!box->y.whole) {
|
|
if (priv->pb_len && !priv->partial) { /* there are two planes */
|
|
if (ic->greyscale) {
|
|
if (priv->nplanes > 2)
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_wp1_gray,
|
|
LWS_ARRAY_SIZE(uc8176_wp1_gray));
|
|
else
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_wp1_bw,
|
|
LWS_ARRAY_SIZE(uc8176_wp1_bw));
|
|
} else
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_wp1_red,
|
|
LWS_ARRAY_SIZE(uc8176_wp1_red));
|
|
} else
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_wp2,
|
|
LWS_ARRAY_SIZE(uc8176_wp2));
|
|
}
|
|
|
|
/* During partial, we are doing the second frame */
|
|
|
|
memset(&desc, 0, sizeof(desc));
|
|
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
|
|
|
|
desc.count_write = (ic->wh_px[LWS_LHPREF_WIDTH].whole + 7) / 8;
|
|
if (priv->partial && priv->partbuf) {
|
|
/* update the old copy of the partial area */
|
|
desc.count_write = (priv->upd.w.whole + 7) / 8;
|
|
memcpy((uint8_t *)priv->partbuf + priv->partbuf_pos,
|
|
(uint8_t *)lo, desc.count_write);
|
|
/* packed at byte boundaries */
|
|
priv->partbuf_pos += desc.count_write;
|
|
}
|
|
|
|
desc.data = priv->pb_len || priv->partial ? (uint8_t *)lo + plane_line_bytes :
|
|
(uint8_t *)lo;
|
|
|
|
ea->spi->queue(ea->spi, &desc);
|
|
|
|
return 0;
|
|
|
|
default: /* starting update */
|
|
|
|
/*
|
|
* The initial box we get started with attempts to reflect the
|
|
* update area. If it's (0,0)(ic->wh_px[LWS_LHPREF_WIDTH],
|
|
* ic->wh_px[LWS_LHPREF_HEIGHT]), then we do the default full update. If it's anything else, we
|
|
* take it as a wish for a partial update in that region.
|
|
*
|
|
* If partial, we should only hear about lines within the region
|
|
* although the rasterizer may choose to rasterize from the top
|
|
* and skip sending us the lines above the partial in order to
|
|
* know what to put there. It can end rasterization below the
|
|
* partial region.
|
|
*
|
|
* The renderer must apply any x offset to the line buffer
|
|
* before sending, this is so it's possible for renderers to
|
|
* ONLY prepare the partial region.
|
|
*
|
|
* "Partial update" means B&W only, no matter if red or gray
|
|
* capable and configured normally.
|
|
*/
|
|
|
|
priv->partial = 0;
|
|
if (ids && ids->count) {
|
|
id = lws_container_of(ids->head, lws_display_id_t, list);
|
|
if (id->exists)
|
|
priv->partial = 1;
|
|
}
|
|
|
|
priv->pb_pos = 0;
|
|
|
|
if (priv->partial) {
|
|
uint8_t *p = priv->pcmd;
|
|
|
|
priv->upd = id->box;
|
|
lws_display_render_dump_ids(ids);
|
|
|
|
lwsl_user("%s: PARTIAL %d: (%d,%d) %dx%d\n", __func__, (int)priv->partial,
|
|
(int)priv->upd.x.whole, (int)priv->upd.y.whole, (int)priv->upd.w.whole,
|
|
(int)priv->upd.h.whole);
|
|
|
|
/* lines packed at byte boundaries */
|
|
priv->partbuf_len = ((priv->upd.w.whole + 7) / 8) * priv->upd.h.whole;
|
|
|
|
if (!priv->partbuf_len) {
|
|
lwsl_err("%s: partbuf_len is zero\n", __func__);
|
|
priv->partial = 0;
|
|
goto fully;
|
|
}
|
|
|
|
/*
|
|
* Partial being B&W has some implications. Since it's
|
|
* only smaller than the whole surface, we can just use
|
|
* a part of the whole display dimensions line and error
|
|
* diffusion buffers for this update.
|
|
*
|
|
* Partial requires a copy of the data that was stored
|
|
* at the display in the area to be resent first. To
|
|
* avoid having to keep a framebuffer of this info, we
|
|
* require the initial partial area after the last full
|
|
* update must start all-white.
|
|
*
|
|
* After the first partial, we keep a copy of the
|
|
* partial data around until the next full update
|
|
*/
|
|
priv->nplanes = 2;
|
|
|
|
if (!priv->partbuf) {
|
|
if (ea->spi->alloc_dma)
|
|
priv->partbuf = ea->spi->alloc_dma(ea->spi,
|
|
priv->partbuf_len);
|
|
else
|
|
priv->partbuf = lws_malloc(priv->partbuf_len,
|
|
__func__);
|
|
if (!priv->partbuf) {
|
|
lwsl_err("%s: OOM: %d\n", __func__, (int)priv->partbuf_len);
|
|
priv->state = LWSDISPST_IDLE;
|
|
return -1;
|
|
}
|
|
|
|
/* we start the area off as all-white */
|
|
memset(priv->partbuf, 0, priv->partbuf_len);
|
|
}
|
|
|
|
*p++ = 1;
|
|
*p++ = UC8176_CMD_PANEL_SETTING;
|
|
*p++ = 0x0f; /* ie, BWR mode */
|
|
|
|
*p++ = 0;
|
|
*p++ = UC8176_CMD_PARTIAL_IN;
|
|
if (!priv->ek79686)
|
|
*p++ = 9;
|
|
else
|
|
*p++ = 7;
|
|
*p++ = UC8176_CMD_PARTIAL_WINDOW;
|
|
if (!priv->ek79686)
|
|
*p++ = (priv->upd.x.whole) >> 8;
|
|
*p++ = priv->upd.x.whole & 0xf8;
|
|
if (!priv->ek79686)
|
|
*p++ = (priv->upd.x.whole + priv->upd.w.whole - 1) >> 8;
|
|
*p++ = ((priv->upd.x.whole + priv->upd.w.whole - 1) & 0xf8) | 7;
|
|
*p++ = priv->upd.y.whole >> 8;
|
|
*p++ = priv->upd.y.whole & 0xff;
|
|
*p++ = (priv->upd.y.whole + priv->upd.h.whole - 1) >> 8;
|
|
*p++ = (priv->upd.y.whole + priv->upd.h.whole - 1) & 0xff;
|
|
*p++ = 0x38; // 0x28; /* ??? only b0 documented */
|
|
|
|
*p++ = 1;
|
|
*p++ = UC8176_CMD_VCOM_AND_DATA_INTERVAL_SETTING;
|
|
*p++ = 0x97;
|
|
*p++ = 0;
|
|
*p++ = UC8176_CMD_DATA_START_TRANSMISSION_1;
|
|
|
|
lwsl_hexdump_notice(priv->pcmd, lws_ptr_diff_size_t(p, &priv->pcmd));
|
|
|
|
lws_spi_table_issue(ea->spi, 0, priv->pcmd,
|
|
lws_ptr_diff_size_t(p, &priv->pcmd));
|
|
|
|
memset(&desc, 0, sizeof(desc));
|
|
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
|
|
desc.data = (uint8_t *)priv->partbuf; /* partial-old data first */
|
|
/* packed at byte boundaries */
|
|
desc.count_write = (((priv->upd.w.whole + 7) / 8) * 1) *
|
|
priv->upd.h.whole;
|
|
ea->spi->queue(ea->spi, &desc);
|
|
|
|
lwsl_user("%s: sent partial start %u\n", __func__, desc.count_write);
|
|
|
|
/* ... let that send while we start producing lines... */
|
|
|
|
priv->partbuf_pos = 0;
|
|
|
|
break;
|
|
}
|
|
fully:
|
|
/* full update */
|
|
|
|
priv->nplanes = 1 + ((ic->greyscale && ic->palette_depth > 2) ||
|
|
!ic->greyscale);
|
|
|
|
/*
|
|
* Now we're doing a full update, discard the partial buffer
|
|
*/
|
|
|
|
if (ea->spi->free_dma)
|
|
ea->spi->free_dma(ea->spi, (void **)&priv->partbuf);
|
|
else
|
|
lws_free_set_NULL(priv->partbuf);
|
|
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_display_uc8176_spi_power(lws_display_state_t *lds, int state)
|
|
{
|
|
const lws_display_uc8176_spi_t *ea = lds_to_disp(lds);
|
|
|
|
if (!state) {
|
|
lws_spi_table_issue(ea->spi, 0, uc8176_off,
|
|
LWS_ARRAY_SIZE(uc8176_off));
|
|
if (ea->gpio)
|
|
ea->gpio->set(ea->reset_gpio, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|