Update Files

This commit is contained in:
2025-01-22 17:22:38 +01:00
parent 89b9349629
commit 4c5e729485
5132 changed files with 1195369 additions and 0 deletions

View File

@ -0,0 +1,36 @@
# lws_display
lws provides a generic "display" object that is independent of the connection
to the display, i2c and spi implementations are provided.
Its purpose is to provide basic blit, backlight binding to lws_pwm, backlight /
power management and display info like pixels wide and high in a generic way.
The generic display object `lws_display_t` can be included at the top of a
specific display implementation object, eg, binding it to additional members
to define the actual IO operations to be used, eg, i2c or spi.
When the display is instantiated, it allocates an additional structure on heap
that contains dynamic information about display state, `lws_display_state_t`.
## Power state machine
lws_display objects have convenient power state management using a single lws
sul event loop timer that is managed automatically.
State|Meaning
---|---
OFF|The display is in sleep and not showing anything
BECOMING_ACTIVE|The display was asked to come out of sleep and is waiting for .latency_wake_ms befor proceeding to ACTIVE. The backlight if any is off. After the delay, the backlight is sequenced up to `.bl_active` using `.bl_transition` sequencer
ACTIVE|The backlight is ON and the dim timer is running
AUTODIMMED|The dim timer was not told the display was active for `.autodim_ms`, we are at `.bl_dim` brightness. After `.off_ms` we will transition to OFF
The lws_pwm sequencers are used to provide customizable, smooth transitions for
the backlight, which may be nonlinear.
## Active notification
Calling `lws_display_state_active(&lds)` on eg, user interaction causes the
display state to transition to ACTIVE smoothly, taking care of waking the display
and waiting out a display-specific wake period, and sequencing the backlight
transition to active level as specified in the display structure.

View File

@ -0,0 +1,383 @@
/*
* lws abstract display implementation for ili9341 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.
*
* This is somewhat complicated by the platform SPI may a) need special
* allocation for the display driver-private packed line buffers, and b) the
* allocated memory may have 32-bit alignment and access requirements.
*
* The allocation is handled by having ops members in the SPI driver ops struct,
* the alignment has to be observed in the display driver.
*/
#include <private-lib-core.h>
#include <dlo/private-lib-drivers-display-dlo.h>
enum {
ILI9341_NOP = 0x00,
ILI9341_SWRESET = 0x01,
ILI9341_RDDID = 0x04,
ILI9341_RDDST = 0x09,
ILI9341_SLPIN = 0x10,
ILI9341_SLPOUT = 0x11,
ILI9341_PTLON = 0x12,
ILI9341_NORON = 0x13,
ILI9341_RDMODE = 0x0a,
ILI9341_RDMADCTL = 0x0b,
ILI9341_RDPIXFMT = 0x0c,
ILI9341_RDIMGFMT = 0x0d,
ILI9341_RDSELFDIAG = 0x0f,
ILI9341_INVOFF = 0x20,
ILI9341_INVON = 0x21,
ILI9341_GAMMASET = 0x26,
ILI9341_DISPOFF = 0x28,
ILI9341_DISPON = 0x29,
ILI9341_CASET = 0x2a,
ILI9341_PASET = 0x2b,
ILI9341_RAMWR = 0x2c,
ILI9341_RAMRD = 0x2e,
ILI9341_PTLAR = 0x30,
ILI9341_VSCRDEF = 0x33,
ILI9341_MADCTL = 0x36,
ILI9341_VSCRSADD = 0x37,
ILI9341_PIXFMT = 0x3a,
ILI9341_FRMCTR1 = 0xb1,
ILI9341_FRMCTR2 = 0xb2,
ILI9341_FRMCTR3 = 0xb3,
ILI9341_INVCTR = 0xb4,
ILI9341_DFUNCTR = 0xb6,
ILI9341_PWCTR1 = 0xc0,
ILI9341_PWCTR2 = 0xc1,
ILI9341_PWCTR3 = 0xc2,
ILI9341_PWCTR4 = 0xc3,
ILI9341_PWCTR5 = 0xc4,
ILI9341_VMCTR1 = 0xc5,
ILI9341_VMCTR2 = 0xc7,
ILI9341_FACPUMPRAT = 0xcb,
ILI9341_FACPWCTRB = 0xcf,
ILI9341_RDID1 = 0xda,
ILI9341_RDID2 = 0xdb,
ILI9341_RDID3 = 0xdc,
ILI9341_RDID4 = 0xdd,
ILI9341_GMCTRP1 = 0xe0,
ILI9341_GMCTRN1 = 0xe1,
ILI9341_FACPWCTRA = 0xe8,
ILI9341_FACPWCTR1 = 0xea,
ILI9341_FACDRTIMCTRA = 0xed,
ILI9341_FACSETGAMMACRV = 0xf2,
ILI9341_FACDRTIMCTR = 0xf7,
};
typedef struct lws_display_ili9341_spi_state {
struct lws_display_state *lds;
uint32_t *line[2];
lws_surface_error_t *u[2];
lws_sorted_usec_list_t sul;
} lws_display_ili9341_spi_state_t;
#define lds_to_disp(_lds) (const lws_display_ili9341_t *)_lds->disp;
#define lds_to_priv(_lds) (lws_display_ili9341_spi_state_t *)_lds->priv;
#define pack_native_pixel(_line, _x, _c) { \
if (!(_x & 1)) \
*_line = htons(_c); \
else \
{ *_line = (*_line) | (htons(_c) << 16); _line++; } }
static const uint8_t ili9341_320x240_init[] = {
/*
* This provides 70Hz 320x240 at RGB565, we assume im[3:0] is 1110
* which is 4-bit SPI
*/
3, ILI9341_FACPWCTRB, 0x00, 0x83, 0x30,
4, ILI9341_FACDRTIMCTRA, 0x64, 0x03, 0x12, 0x81,
3, ILI9341_FACPWCTRA, 0x85, 0x01, 0x79,
5, ILI9341_FACPUMPRAT, 0x39, 0x2c, 0x00, 0x34, 0x02,
1, ILI9341_FACDRTIMCTR, 0x20,
2, ILI9341_FACPWCTR1, 0x00, 0x00,
1, ILI9341_PWCTR1, 0x26,
1, ILI9341_PWCTR2, 0x11,
2, ILI9341_VMCTR1, 0x35, 0x3e,
1, ILI9341_VMCTR2, 0xbe,
1, ILI9341_MADCTL, 0x28,
1, ILI9341_VSCRSADD, 0x00,
1, ILI9341_PIXFMT, 0x55,
2, ILI9341_FRMCTR1, 0x00, 0x1b,
1, ILI9341_FACSETGAMMACRV, 0x00,
1, ILI9341_GAMMASET, 0x01,
15, ILI9341_GMCTRP1, 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e,
0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09,
0x00,
15, ILI9341_GMCTRN1, 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31,
0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36,
0x0f,
4, ILI9341_DFUNCTR, 0x0a, 0x82, 0x27, 0x00,
0, ILI9341_SLPOUT
}, ili9341_320x240_dispon[] = {
0, ILI9341_DISPON
}, ili9341_320x240_sleep_in[] = {
0, ILI9341_SLPIN
}, ili9341_320x240_sleep_out[] = {
0, ILI9341_SLPOUT
};
int
lws_display_ili9341_spi_init(lws_display_state_t *lds)
{
const lws_display_ili9341_t *disp = lds_to_disp(lds);
lws_display_ili9341_spi_state_t *priv;
priv = lws_zalloc(sizeof(*priv), __func__);
if (!priv)
return 1;
priv->lds = lds;
lds->priv = priv;
/* hardware nRESET */
if (disp->gpio) {
disp->gpio->mode(disp->reset_gpio, LWSGGPIO_FL_WRITE |
LWSGGPIO_FL_PULLUP);
disp->gpio->set(disp->reset_gpio, 0);
lws_msleep(1);
disp->gpio->set(disp->reset_gpio, 1);
lws_msleep(1);
}
lws_spi_table_issue(disp->spi, 0, ili9341_320x240_init,
LWS_ARRAY_SIZE(ili9341_320x240_init));
lws_msleep(5);
lws_spi_table_issue(disp->spi, 0, ili9341_320x240_dispon,
LWS_ARRAY_SIZE(ili9341_320x240_dispon));
if (disp->spi->in_flight)
while (disp->spi->in_flight(disp->spi))
;
if (disp->cb)
disp->cb(priv->lds, 1);
return 0;
}
/* backlight handled by PWM */
int
lws_display_ili9341_spi_brightness(lws_display_state_t *lds, uint8_t b)
{
return 0;
}
int
lws_display_ili9341_spi_blit(lws_display_state_t *lds, const uint8_t *src,
lws_box_t *box, lws_dll2_owner_t *ids)
{
lws_display_ili9341_spi_state_t *priv = lds_to_priv(lds);
const lws_display_ili9341_t *disp = lds_to_disp(lds);
const lws_surface_info_t *ic = &lds->disp->ic;
lws_greyscale_error_t *gedl_this, *gedl_next;
lws_colour_error_t *edl_this, *edl_next;
int bytes_pl = ic->wh_px[0].whole * 2;
static DMA_ATTR uint32_t buf[5];
lws_display_list_coord_t h, y;
lws_display_colour_t c;
lws_spi_desc_t desc;
const uint8_t *pc;
uint32_t *lo;
int n, m;
if (!priv->line[0]) {
if (disp->spi->alloc_dma)
priv->line[0] = disp->spi->alloc_dma(disp->spi,
bytes_pl * 2);
else
priv->line[0] = lws_malloc(bytes_pl * 2, __func__);
if (!priv->line[0]) {
lwsl_err("%s: failed to alloc %u\n", __func__,
(unsigned int)bytes_pl * 2);
return 1;
}
priv->line[1] = (uint32_t *)((uint8_t *)priv->line[0] + bytes_pl);
if (lws_display_alloc_diffusion(ic, priv->u)) {
if (disp->spi->free_dma)
disp->spi->free_dma(disp->spi,
(void **)&priv->line[0]);
else
lws_free_set_NULL(priv->line[0]);
lwsl_err("%s: OOM\n", __func__);
return 1;
}
}
pc = src;
lo = priv->line[box->y.whole & 1];
memset(&desc, 0, sizeof(desc));
desc.count_cmd = 1;
desc.src = (uint8_t *)&buf[4];
/*
* Blit a line at a time
*/
h = box->h.whole;
y = box->y.whole;
if (h > 1) {
buf[4] = ILI9341_CASET;
desc.data = (uint8_t *)&buf[0];
desc.flags = 0;
buf[0] = ((box->w.whole & 0xff) << 24) | ((box->w.whole >> 8) << 16) | (box->x.whole) | (box->x.whole);
desc.count_write = 4;
disp->spi->queue(disp->spi, &desc);
buf[4] = ILI9341_PASET;
// buf[0] = (((y + 1) & 0xff) << 24) | (((y + 1) >> 8) << 16) | ((y & 0xff) << 8) | (y >> 8);
buf[0] = (((box->h.whole) & 0xff) << 24) | (((box->h.whole) >> 8) << 16) | ((y & 0xff) << 8) | (y >> 8);
disp->spi->queue(disp->spi, &desc);
buf[4] = ILI9341_RAMWR;
/* priv->line is already allocated for DMA */
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED | LWS_SPI_FLAG_DATA_CONTINUE;
desc.count_write = 0;
disp->spi->queue(disp->spi, &desc);
return 0;
}
if (h) {
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;
if (!pc) {
for (n = 0; n < ic->wh_px[0].whole; n++)
pack_native_pixel(lo, n, 0xffff);
goto go;
}
if (ic->greyscale)
for (n = 0; n < ic->wh_px[0].whole; n++) {
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
m = lws_display_palettize_grey(ic, ic->palette,
ic->palette_depth, c, &gedl_this[n]);
pack_native_pixel(lo, n, m);
dist_err_floyd_steinberg_grey(n, ic->wh_px[0].whole,
gedl_this, gedl_next);
pc++;
}
else
for (n = 0; n < ic->wh_px[0].whole; n++) {
c = (pc[2] << 16) | (pc[1] << 8) | pc[0];
m = lws_display_palettize_col(ic, ic->palette,
ic->palette_depth, c, &edl_this[n]);
pack_native_pixel(lo, n, m);
dist_err_floyd_steinberg_col(n, ic->wh_px[0].whole,
edl_this, edl_next);
pc += 3;
}
go:
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
if (y + 1 != ic->wh_px[1].whole)
desc.flags |= LWS_SPI_FLAG_DATA_CONTINUE;
desc.data = (uint8_t *)priv->line[box->y.whole & 1];
desc.count_write = bytes_pl;
desc.count_cmd = 0;
if (disp->spi->queue(disp->spi, &desc)) {
lwsl_err("%s: failed to queue\n", __func__);
}
src += bytes_pl;
y++;
return 0;
}
if (!box->h.whole) {
if (disp->spi->in_flight)
while (disp->spi->in_flight(disp->spi))
;
if (disp->spi->free_dma)
disp->spi->free_dma(disp->spi, (void **)&priv->line[0]);
else
lws_free_set_NULL(priv->line[0]);
lws_free_set_NULL(priv->u[0]);
if (disp->cb)
disp->cb(priv->lds, 2);
}
return 0;
}
int
lws_display_ili9341_spi_power(lws_display_state_t *lds, int state)
{
const lws_display_ili9341_t *disp = lds_to_disp(lds);
if (state)
lws_spi_table_issue(disp->spi, 0, ili9341_320x240_sleep_out,
LWS_ARRAY_SIZE(ili9341_320x240_sleep_out));
else
lws_spi_table_issue(disp->spi, 0, ili9341_320x240_sleep_in,
LWS_ARRAY_SIZE(ili9341_320x240_sleep_in));
/* we're not going to do anything useful for 5ms after this */
return 0;
}

View File

@ -0,0 +1,443 @@
/*
* lws abstract display
*
* 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.
*/
#include <private-lib-core.h>
static void
sul_autodim_cb(lws_sorted_usec_list_t *sul)
{
lws_display_state_t *lds = lws_container_of(sul, lws_display_state_t,
sul_autodim);
int next_ms = -1;
/* we fire both to dim and to blank... if already in dim state, blank */
switch (lds->state) {
case LWSDISPS_BECOMING_ACTIVE:
lws_display_state_set_brightness(lds, lds->disp->bl_active);
lds->state = LWSDISPS_ACTIVE;
next_ms = lds->autodim_ms;
break;
case LWSDISPS_ACTIVE:
/* active -> autodimmed */
lds->state = LWSDISPS_AUTODIMMED;
next_ms = lds->off_ms;
lws_display_state_set_brightness(lds, lds->disp->bl_dim);
break;
case LWSDISPS_AUTODIMMED:
/* dimmed -> OFF */
lws_display_state_set_brightness(lds, &lws_pwmseq_static_off);
lds->state = LWSDISPS_GOING_OFF;
next_ms = 600;
break;
case LWSDISPS_GOING_OFF:
/* off dimming completed, actual display OFF */
lws_display_state_off(lds);
return;
default:
return;
}
if (next_ms >= 0)
lws_sul_schedule(lds->ctx, 0, &lds->sul_autodim, sul_autodim_cb,
next_ms * LWS_US_PER_MS);
}
void
lws_display_state_init(lws_display_state_t *lds, struct lws_context *ctx,
int dim_ms, int off_ms, struct lws_led_state *bl_lcs,
const lws_display_t *disp)
{
memset(lds, 0, sizeof(*lds));
lds->disp = disp;
lds->ctx = ctx;
lds->autodim_ms = dim_ms;
lds->off_ms = off_ms;
lds->bl_lcs = bl_lcs;
lds->state = LWSDISPS_OFF;
if (lds->bl_lcs)
lws_led_transition(lds->bl_lcs, "backlight", &lws_pwmseq_static_off,
&lws_pwmseq_static_on);
disp->init(lds);
}
void
lws_display_state_set_brightness(lws_display_state_t *lds,
const lws_led_sequence_def_t *pwmseq)
{
if (lds->bl_lcs)
lws_led_transition(lds->bl_lcs, "backlight", pwmseq,
lds->disp->bl_transition);
}
void
lws_display_state_active(lws_display_state_t *lds)
{
int waiting_ms;
if (lds->state == LWSDISPS_OFF) {
/* power us up */
lds->disp->power(lds, 1);
lds->state = LWSDISPS_BECOMING_ACTIVE;
waiting_ms = lds->disp->latency_wake_ms;
} else {
if (lds->state != LWSDISPS_ACTIVE && lds->bl_lcs)
lws_display_state_set_brightness(lds,
lds->disp->bl_active);
lds->state = LWSDISPS_ACTIVE;
waiting_ms = lds->autodim_ms;
}
/* reset the autodim timer */
if (waiting_ms >= 0)
lws_sul_schedule(lds->ctx, 0, &lds->sul_autodim, sul_autodim_cb,
waiting_ms * LWS_US_PER_MS);
}
void
lws_display_state_off(lws_display_state_t *lds)
{
/* if no control over backlight, don't bother power down display
* since it would continue to emit, just show all-white or whatever */
if (lds->bl_lcs)
lds->disp->power(lds, 0);
lws_sul_cancel(&lds->sul_autodim);
lds->state = LWSDISPS_OFF;
}
int
lws_display_alloc_diffusion(const lws_surface_info_t *ic, lws_surface_error_t **se)
{
size_t size, gsize = ic->greyscale ? sizeof(lws_greyscale_error_t) :
sizeof(lws_colour_error_t), by;
if (*se)
return 0;
/* defer creation of dlo's 2px-high dlo-width, 2 bytespp or 6 bytespp
* error diffusion buffer */
by = ((ic->wh_px[0].whole + 15) / 8) * 8;
size = gsize * 2u * (unsigned int)by;
lwsl_info("%s: alloc'd %u for width %d\n", __func__, (unsigned int)size,
(int)ic->wh_px[0].whole);
se[0] = lws_zalloc(size, __func__);
if (!se[0])
return 1;
se[1] = (lws_surface_error_t *)(((uint8_t *)se[0]) + (size / 2));
return 0;
}
static void
dist_err_grey(const lws_greyscale_error_t *in, lws_greyscale_error_t *out,
int sixteenths)
{
out->rgb[0] = (int16_t)(out->rgb[0] +
(int16_t)((sixteenths * in->rgb[0]) / 16));
}
static void
dist_err_col(const lws_colour_error_t *in, lws_colour_error_t *out,
int sixteenths)
{
out->rgb[0] = (int16_t)(out->rgb[0] +
(int16_t)((sixteenths * in->rgb[0]) / 16));
out->rgb[1] = (int16_t)(out->rgb[1] +
(int16_t)((sixteenths * in->rgb[1]) / 16));
out->rgb[2] = (int16_t)(out->rgb[2] +
(int16_t)((sixteenths * in->rgb[2]) / 16));
}
void
dist_err_floyd_steinberg_grey(int n, int width, lws_greyscale_error_t *gedl_this,
lws_greyscale_error_t *gedl_next)
{
if (n < width - 1) {
dist_err_grey(&gedl_this[n], &gedl_this[n + 1], 7);
dist_err_grey(&gedl_this[n], &gedl_next[n + 1], 1);
}
if (n)
dist_err_grey(&gedl_this[n], &gedl_next[n - 1], 3);
dist_err_grey(&gedl_this[n], &gedl_next[n], 5);
gedl_this[n].rgb[0] = 0;
}
void
dist_err_floyd_steinberg_col(int n, int width, lws_colour_error_t *edl_this,
lws_colour_error_t *edl_next)
{
if (n < width - 1) {
dist_err_col(&edl_this[n], &edl_this[n + 1], 7);
dist_err_col(&edl_this[n], &edl_next[n + 1], 1);
}
if (n)
dist_err_col(&edl_this[n], &edl_next[n - 1], 3);
dist_err_col(&edl_this[n], &edl_next[n], 5);
edl_this[n].rgb[0] = 0;
edl_this[n].rgb[1] = 0;
edl_this[n].rgb[2] = 0;
}
/*
* #include <stdio.h>
* #include <math.h>
*
* void
* main(void)
* {
* int n;
*
* for (n = 0; n < 256; n++) {
* double d = (double)n / 255.0;
*
* printf("0x%02X, ", (unsigned int)(pow(d, (2.2)) * 255));
* }
*
* }
*/
static const uint8_t gamma2_2[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02,
0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04,
0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06,
0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08,
0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B,
0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0F,
0x0F, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13,
0x13, 0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x17,
0x18, 0x19, 0x19, 0x1A, 0x1B, 0x1B, 0x1C, 0x1D,
0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x21, 0x21, 0x22,
0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x28,
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E,
0x3F, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4A, 0x4B, 0x4D, 0x4E, 0x4F, 0x50,
0x51, 0x52, 0x54, 0x55, 0x56, 0x57, 0x58, 0x5A,
0x5B, 0x5C, 0x5D, 0x5F, 0x60, 0x61, 0x63, 0x64,
0x65, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x6F,
0x70, 0x72, 0x73, 0x75, 0x76, 0x77, 0x79, 0x7A,
0x7C, 0x7D, 0x7F, 0x80, 0x82, 0x83, 0x85, 0x87,
0x88, 0x8A, 0x8B, 0x8D, 0x8E, 0x90, 0x92, 0x93,
0x95, 0x97, 0x98, 0x9A, 0x9C, 0x9D, 0x9F, 0xA1,
0xA2, 0xA4, 0xA6, 0xA8, 0xA9, 0xAB, 0xAD, 0xAF,
0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBB, 0xBD,
0xBF, 0xC1, 0xC3, 0xC5, 0xC7, 0xC9, 0xCB, 0xCD,
0xCF, 0xD1, 0xD3, 0xD5, 0xD7, 0xD9, 0xDB, 0xDD,
0xDF, 0xE1, 0xE3, 0xE5, 0xE7, 0xE9, 0xEB, 0xED,
0xEF, 0xF1, 0xF4, 0xF6, 0xF8, 0xFA, 0xFC, 0xFF
};
lws_display_palette_idx_t
lws_display_palettize_grey(const lws_surface_info_t *ic,
const lws_display_colour_t *palette, size_t pdepth,
lws_display_colour_t c, lws_greyscale_error_t *ectx)
{
int best = 0x7fffffff, best_idx = 0;
lws_colour_error_t da, d, ea;
int sum, y;
size_t n;
/* put the most desirable colour (adjusted for existing error) in d */
d.rgb[0] = (int)gamma2_2[LWSDC_R(c)];
da.rgb[0] = d.rgb[0] + ectx->rgb[0];
if (da.rgb[0] < 0)
da.rgb[0] = 0;
if (da.rgb[0] > 255)
da.rgb[0] = 255;
if (ic->type == LWSSURF_565) {
y = d.rgb[0] >> 3;
ectx->rgb[0] = (int16_t)((int)da.rgb[0] - y);
return (lws_display_palette_idx_t)y;
}
/*
* Choose a palette colour considering the error diffusion adjustments
*/
for (n = 0; n < pdepth; n++) {
y = LWSDC_ALPHA(palette[n]);
ea.rgb[0] = (int16_t)((int)da.rgb[0] - (int)(LWSDC_R(palette[n])));
sum = ea.rgb[0] < 0 ? -ea.rgb[0] : ea.rgb[0];
if (sum < best) {
best_idx = (int)n;
best = sum;
}
}
/* report the error between the unadjusted colour and what we chose */
ectx->rgb[0] = (int16_t)((int)da.rgb[0] - (int)(LWSDC_R(palette[best_idx])));
return (lws_display_palette_idx_t)best_idx;
}
/*
* For error disffusion, it's better to use YUV and prioritize reducing error
* in Y (lumience)
*/
#if 0
static void
rgb_to_yuv(uint8_t *yuv, const uint8_t *rgb)
{
yuv[0] = 16 + ((257 * rgb[0]) / 1000) + ((504 * rgb[1]) / 1000) +
((98 * rgb[2]) / 1000);
yuv[1] = 128 - ((148 * rgb[0]) / 1000) - ((291 * rgb[1]) / 1000) +
((439 * rgb[2]) / 1000);
yuv[2] = 128 + ((439 * rgb[0]) / 1000) - ((368 * rgb[1]) / 1000) -
((71 * rgb[2]) / 1000);
}
static void
yuv_to_rgb(uint8_t *rgb, const uint8_t *_yuv)
{
unsigned int yuv[3];
yuv[0] = _yuv[0] - 16;
yuv[1] = _yuv[1] - 128;
yuv[2] = _yuv[2] - 128;
rgb[0] = ((1164 * yuv[0]) / 1000) + ((1596 * yuv[2]) / 1000);
rgb[1] = ((1164 * yuv[0]) / 1090) - ((392 * yuv[1]) / 1000) -
((813 * yuv[2]) / 1000);
rgb[2] = ((1164 * yuv[0]) / 1000) + ((2017 * yuv[1]) / 1000);
}
#endif
lws_display_palette_idx_t
lws_display_palettize_col(const lws_surface_info_t *ic,
const lws_display_colour_t *palette, size_t pdepth,
lws_display_colour_t c, lws_colour_error_t *ectx)
{
int best = 0x7fffffff, best_idx = 0, yd;
lws_colour_error_t da, d;
uint8_t ya[3];
size_t n;
int y, ch;
/* put the most desirable colour (adjusted for existing error) in d */
d.rgb[0] = (int)gamma2_2[LWSDC_R(c)];
da.rgb[0] = d.rgb[0] + ectx->rgb[0];
if (da.rgb[0] < 0)
da.rgb[0] = 0;
if (da.rgb[0] > 255)
da.rgb[0] = 255;
yd = da.rgb[0];
d.rgb[1] = (int)gamma2_2[LWSDC_G(c)];
d.rgb[2] = (int)gamma2_2[LWSDC_B(c)];
da.rgb[1] = d.rgb[1] + ectx->rgb[1];
if (da.rgb[1] < 0)
da.rgb[1] = 0;
if (da.rgb[1] > 255)
da.rgb[1] = 255;
da.rgb[2] = d.rgb[2] + ectx->rgb[2];
if (da.rgb[2] < 0)
da.rgb[2] = 0;
if (da.rgb[2] > 255)
da.rgb[2] = 255;
yd = RGB_TO_Y(da.rgb[0], da.rgb[1], da.rgb[2]);
if (ic->type == LWSSURF_565) {
ya[0] = d.rgb[0] >> 3;
ectx->rgb[0] = (int16_t)((int)da.rgb[0] - (ya[0] << 3));
ya[1] = d.rgb[1] >> 2;
ectx->rgb[1] = (int16_t)((int)da.rgb[1] - (ya[1] << 2));
ya[2] = d.rgb[2] >> 3;
ectx->rgb[2] = (int16_t)((int)da.rgb[2] - (ya[2] << 3));
return (lws_display_palette_idx_t)((ya[0] << 11) | (ya[1] << 5) | (ya[2]));
}
/*
* Choose a palette colour considering the error diffusion adjustments,
* separately choose the best Y match and the best RGB match
*/
for (n = 0; n < pdepth; n++) {
lws_colour_error_t ea;
int sum;
y = LWSDC_ALPHA(palette[n]);
ea.rgb[0] = (int16_t)((int)da.rgb[0] - (int)(LWSDC_R(palette[n])));
ea.rgb[1] = (int16_t)((int)da.rgb[1] - (int)(LWSDC_G(palette[n])));
ea.rgb[2] = (int16_t)((int)da.rgb[2] - (int)(LWSDC_B(palette[n])));
/* Best considering luma match */
sum = (yd > y ? (yd - y) : (y - yd));
/*
* Best considering RGB matching
*/
sum += ((ea.rgb[0] < 0 ? -ea.rgb[0] : ea.rgb[0]) +
(ea.rgb[1] < 0 ? -ea.rgb[1] : ea.rgb[1]) +
(ea.rgb[2] < 0 ? -ea.rgb[2] : ea.rgb[2]));
if (sum < best) {
best_idx = (int)n;
best = sum;
}
}
ch = best_idx;
/* report the error between the adjusted colour and what we chose */
ectx->rgb[0] = (int16_t)((int)da.rgb[0] - (int)(LWSDC_R(palette[ch])));
ectx->rgb[1] = (int16_t)((int)da.rgb[1] - (int)(LWSDC_G(palette[ch])));
ectx->rgb[2] = (int16_t)((int)da.rgb[2] - (int)(LWSDC_B(palette[ch])));
return (lws_display_palette_idx_t)ch;
}

View File

@ -0,0 +1,463 @@
/*
* lws abstract display implementation for Epd 7-colour ACEP SPD1656 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/b/bf/SPD1656_1.1.pdf
*/
#include <private-lib-core.h>
#include <dlo/private-lib-drivers-display-dlo.h>
enum {
SPD1656_CMD_PSR = 0x00,
SPD1656_CMD_PWR = 0x01,
SPD1656_CMD_POF = 0x02,
SPD1656_CMD_PFS = 0x03,
SPD1656_CMD_PON = 0x04,
SPD1656_CMD_BTST = 0x06,
SPD1656_CMD_DSLP = 0x07,
SPD1656_CMD_DTM1 = 0x10,
SPD1656_CMD_DSP = 0x11,
SPD1656_CMD_DRF = 0x12,
SPD1656_CMD_PLL = 0x30,
SPD1656_CMD_TSE = 0x41,
SPD1656_CMD_CDI = 0x50,
SPD1656_CMD_TCON = 0x60,
SPD1656_CMD_TRES = 0x61,
SPD1656_CMD_PWS = 0xe3,
};
typedef enum {
LWSDISPST_IDLE,
LWSDISPST_INIT1,
LWSDISPST_INIT2,
LWSDISPST_INIT3,
LWSDISPST_INIT4,
LWSDISPST_WRITE1,
LWSDISPST_WRITE2,
LWSDISPST_WRITE3,
LWSDISPST_WRITE4,
LWSDISPST_WRITE5,
LWSDISPRET_ASYNC = 1
} lws_display_update_state_t;
static const uint8_t spd1656_init1[] = {
2, SPD1656_CMD_PSR, 0xef, 0x08,
4, SPD1656_CMD_PWR, 0x37, 0x00, 0x23, 0x23,
1, SPD1656_CMD_PFS, 0x00,
3, SPD1656_CMD_BTST, 0xc7, 0xc7, 0x1d,
1, SPD1656_CMD_PLL, 0x39,
1, SPD1656_CMD_TSE, 0x00,
1, SPD1656_CMD_CDI, 0x37,
1, SPD1656_CMD_TCON, 0x22,
}, spd1656_init2[] = {
4, SPD1656_CMD_TRES, 0, 0, 0, 0, /* filled in */
1, SPD1656_CMD_PWS, 0xaa,
}, spd1656_init3[] = {
1, SPD1656_CMD_CDI, 0x37,
}, spd1656_off[] = {
1, SPD1656_CMD_DSLP, 0xa5,
}, spd1656_write1[] = {
4, SPD1656_CMD_TRES, 0, 0, 0, 0, /* filled in */
}, spd1656_write1a[] = {
0, SPD1656_CMD_DTM1
/* ... frame data ... */
}, spd1656_write2[] = {
0, SPD1656_CMD_PON,
}, spd1656_write3[] = {
0, SPD1656_CMD_DRF,
}, spd1656_write4[] = {
0, SPD1656_CMD_POF,
};
typedef struct lws_display_spd1656_spi_state {
struct lws_display_state *lds;
uint32_t *line[2];
lws_surface_error_t *u[2];
lws_sorted_usec_list_t sul;
int state;
int budget;
} lws_display_spd1656_spi_state_t;
#define lds_to_disp(_lds) (const lws_display_spd1656_spi_t *)_lds->disp;
#define lds_to_priv(_lds) (lws_display_spd1656_spi_state_t *)_lds->priv;
#define pack_native_pixel(_line, _x, _c) \
{ *_line = (*_line & ~(0xf << (((_x ^ 1) & 7) * 4))) | \
(_c << (((_x ^ 1) & 7) * 4)); \
if ((_x & 7) == 7) \
_line++; }
static void
async_cb(lws_sorted_usec_list_t *sul);
#define BUSY_TIMEOUT_BUDGET 60
static int
check_busy(lws_display_spd1656_spi_state_t *priv, int level)
{
const lws_display_spd1656_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 * 50);
return 1; /* keeping on trying */
}
static void
async_cb(lws_sorted_usec_list_t *sul)
{
lws_display_spd1656_spi_state_t *priv = lws_container_of(sul,
lws_display_spd1656_spi_state_t, sul);
const lws_display_spd1656_spi_t *ea = lds_to_disp(priv->lds);
uint8_t buf[32];
//int budget = 5;
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 * 2);
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 * 10);
break;
case LWSDISPST_INIT3:
if (check_busy(priv, 1))
return;
lws_spi_table_issue(ea->spi, 0, spd1656_init1,
LWS_ARRAY_SIZE(spd1656_init1));
if (ea->spi->in_flight)
while (ea->spi->in_flight(ea->spi))
;
memcpy(buf, spd1656_init2, LWS_ARRAY_SIZE(spd1656_init2));
/* width and height filled in from display struct */
buf[2] = (ea->disp.ic.wh_px[0].whole >> 8) & 0xff;
buf[3] = ea->disp.ic.wh_px[0].whole & 0xff;
buf[4] = (ea->disp.ic.wh_px[1].whole >> 8) & 0xff;
buf[5] = ea->disp.ic.wh_px[1].whole & 0xff;
lws_spi_table_issue(ea->spi, 0, buf,
LWS_ARRAY_SIZE(spd1656_init2));
if (ea->spi->in_flight)
while (ea->spi->in_flight(ea->spi))
;
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
async_cb, LWS_US_PER_MS * 10);
break;
case LWSDISPST_INIT4:
priv->state = LWSDISPST_IDLE;
lws_spi_table_issue(ea->spi, 0, spd1656_init3,
LWS_ARRAY_SIZE(spd1656_init3));
if (ea->spi->in_flight)
while (ea->spi->in_flight(ea->spi))
;
if (ea->cb)
ea->cb(priv->lds, 1);
break;
case LWSDISPST_WRITE1:
/* rendered and sent the whole frame of pixel data */
priv->state++;
priv->budget = BUSY_TIMEOUT_BUDGET;
lws_spi_table_issue(ea->spi, 0, spd1656_write2,
LWS_ARRAY_SIZE(spd1656_write2));
/* fallthru */
case LWSDISPST_WRITE2:
if (check_busy(priv, 1))
return;
priv->state++;
priv->budget = 20000 / 50;
lws_spi_table_issue(ea->spi, 0, spd1656_write3,
LWS_ARRAY_SIZE(spd1656_write3));
/*
* this is going to start the refresh, it may wait in check_busy
* for serveral seconds while it does the sequence on the panel
*/
/* fallthru */
case LWSDISPST_WRITE3:
if (check_busy(priv, 1))
return;
priv->state++;
priv->budget = BUSY_TIMEOUT_BUDGET;
lws_spi_table_issue(ea->spi, 0, spd1656_write4,
LWS_ARRAY_SIZE(spd1656_write4));
/* fallthru */
case LWSDISPST_WRITE4:
if (check_busy(priv, 1))
return;
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 200);
break;
case LWSDISPST_WRITE5:
/* fully completed the blit */
priv->state = LWSDISPST_IDLE;
if (ea->cb)
ea->cb(priv->lds, 2);
break;
default:
break;
}
}
int
lws_display_spd1656_spi_init(struct lws_display_state *lds)
{
const lws_display_spd1656_spi_t *ea = lds_to_disp(lds);
lws_display_spd1656_spi_state_t *priv;
priv = lws_zalloc(sizeof(*priv), __func__);
if (!priv)
return 1;
priv->lds = lds;
lds->priv = priv;
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_spd1656_spi_brightness(const struct lws_display *disp, uint8_t b)
{
return 0;
}
int
lws_display_spd1656_spi_blit(struct lws_display_state *lds, const uint8_t *src,
lws_box_t *box, lws_dll2_owner_t *ids)
{
lws_display_spd1656_spi_state_t *priv = lds_to_priv(lds);
const lws_display_spd1656_spi_t *ea = lds_to_disp(lds);
lws_greyscale_error_t *gedl_this, *gedl_next;
const lws_surface_info_t *ic = &ea->disp.ic;
lws_colour_error_t *edl_this, *edl_next;
size_t bytes_pl = ic->wh_px[0].whole / 2;
const uint8_t *pc = src;
lws_display_colour_t c;
lws_spi_desc_t desc;
uint8_t temp[10];
uint32_t *lo;
int n, m;
if (priv->state) {
lwsl_warn("%s: ignoring as busy\n", __func__);
return 1; /* busy */
}
if (!priv->line[0]) {
/*
* We have to allocate the packed line and error diffusion
* buffers
*/
if (ea->spi->alloc_dma)
priv->line[0] = ea->spi->alloc_dma(ea->spi, bytes_pl * 2);
else
priv->line[0] = lws_zalloc(bytes_pl * 2, __func__);
if (!priv->line[0]) {
lwsl_err("%s: OOM\n", __func__);
priv->state = LWSDISPST_IDLE;
return 1;
}
priv->line[1] = (uint32_t *)(((uint8_t *)priv->line[0]) + bytes_pl);
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 1;
}
}
lo = priv->line[box->y.whole & 1];
// lwsl_notice("%s: switch %d\n", __func__, box->h.whole);
switch (box->h.whole) {
case 0: /* update is finished */
priv->state = LWSDISPST_WRITE1;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb, 1);
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]);
return 0;
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;
memset(&desc, 0, sizeof(desc));
if (!pc) {
for (n = 0; n < ic->wh_px[0].whole; n++)
pack_native_pixel(lo, n, 1 /* white */);
goto go;
}
if (ic->greyscale)
for (n = 0; n < ic->wh_px[0].whole; n++) {
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
m = lws_display_palettize_grey(ic, ic->palette,
ic->palette_depth, c, &gedl_this[n]);
pack_native_pixel(lo, n, (uint8_t)m);
dist_err_floyd_steinberg_grey(n, ic->wh_px[0].whole,
gedl_this, gedl_next);
pc++;
}
else
for (n = 0; n < ic->wh_px[0].whole; n++) {
c = (pc[2] << 16) | (pc[1] << 8) | pc[0];
m = lws_display_palettize_col(ic, ic->palette,
ic->palette_depth, c, &edl_this[n]);
pack_native_pixel(lo, n, (uint8_t)m);
dist_err_floyd_steinberg_col(n, ic->wh_px[0].whole,
edl_this, edl_next);
pc += 3;
}
go:
/* priv->line is already allocated for DMA */
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
desc.flags |= box->y.whole + 1 != ic->wh_px[1].whole ?
LWS_SPI_FLAG_DATA_CONTINUE : 0;
desc.data = (uint8_t *)priv->line[box->y.whole & 1];
desc.count_write = ic->wh_px[0].whole / 2;
ea->spi->queue(ea->spi, &desc);
return 0;
default:
/* Start whole page update... no partial updates on this controller,
* box must be whole display */
lwsl_notice("%s: start update\n", __func__);
memcpy(temp, spd1656_write1, LWS_ARRAY_SIZE(spd1656_write1));
/* width and height filled in from display struct */
temp[2] = (lds->disp->ic.wh_px[0].whole >> 8) & 0xff;
temp[3] = lds->disp->ic.wh_px[0].whole & 0xff;
temp[4] = (lds->disp->ic.wh_px[1].whole >> 8) & 0xff;
temp[5] = lds->disp->ic.wh_px[1].whole & 0xff;
lws_spi_table_issue(ea->spi, 0, temp,
LWS_ARRAY_SIZE(spd1656_write1));
lws_spi_table_issue(ea->spi, LWS_SPI_FLAG_DATA_CONTINUE,
spd1656_write1a, LWS_ARRAY_SIZE(spd1656_write1a));
return 0;
}
}
int
lws_display_spd1656_spi_power(lws_display_state_t *lds, int state)
{
const lws_display_spd1656_spi_t *ea = lds_to_disp(lds);
if (!state) {
lws_spi_table_issue(ea->spi, 0, spd1656_off, LWS_ARRAY_SIZE(spd1656_off));
if (ea->gpio)
ea->gpio->set(ea->reset_gpio, 0);
return 0;
}
return 0;
}

View File

@ -0,0 +1,278 @@
/*
* lws abstract display implementation for ssd1306 on i2c
*
* 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.
*
*
* The OLED display is composed of 128 x 8 bytes, where the bytes contain 8
* columnar pixels in a single row. We can handle it by buffering 8 lines and
* then issuing it as 128 linear bytes.
*/
#include <private-lib-core.h>
#include <dlo/private-lib-drivers-display-dlo.h>
enum {
SSD1306_SETLOWCOLUMN = 0x00,
SSD1306_SETHIGHCOLUMN = 0x10,
SSD1306_MEMORYMODE = 0x20,
SSD1306_COLUMNADDR = 0x21,
SSD1306_PAGEADDR = 0x22,
SSD1306_DEACTIVATE_SCROLL = 0x2e,
SSD1306_SETSTARTLINE = 0x40,
SSD1306_SETCONTRAST = 0x81,
SSD1306_CHARGEPUMP = 0x8d,
SSD1306_SEGREMAP = 0xa0,
SSD1306_SETSEGMENTREMAP = 0xa1,
SSD1306_DISPLAYALLON_RESUME = 0xa4,
SSD1306_DISPLAYALLON = 0xa5,
SSD1306_NORMALDISPLAY = 0xa6,
SSD1306_INVERTDISPLAY = 0xa7,
SSD1306_SETMULTIPLEX = 0xa8,
SSD1306_DISPLAYOFF = 0xae,
SSD1306_DISPLAYON = 0xaf,
SSD1306_COMSCANINC = 0xc0,
SSD1306_COMSCANDEC = 0xc8,
SSD1306_SETDISPLAYOFFSET = 0xd3,
SSD1306_SETDISPLAYCLOCKDIV = 0xd5,
SSD1306_SETPRECHARGE = 0xd9,
SSD1306_SETCOMPINS = 0xda,
SSD1306_SETVCOMDESELECT = 0xdb,
SSD1306_NOP = 0xe3,
};
static uint8_t ssd1306_128x64_init[] = {
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0xf0,
SSD1306_SETMULTIPLEX, 64 - 1,
SSD1306_SETDISPLAYOFFSET, 0,
SSD1306_CHARGEPUMP, 0x14,
SSD1306_MEMORYMODE, 0,
SSD1306_SEGREMAP | (0 << 0),
SSD1306_COMSCANDEC,
SSD1306_SETCOMPINS, (1 << 4) | 0x02,
SSD1306_SETCONTRAST, 0x7f,
SSD1306_SETPRECHARGE, (0xf << 4) | (1 << 0),
SSD1306_SETVCOMDESELECT, (4 << 4),
SSD1306_DEACTIVATE_SCROLL,
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
//SSD1306_DISPLAYON
};
typedef struct lws_display_ssd1306_i2c_state_t {
struct lws_display_state *lds;
uint8_t *line8;
lws_surface_error_t *u[2];
lws_sorted_usec_list_t sul;
} lws_display_ssd1306_i2c_state_t;
#define lds_to_disp(_lds) (const lws_display_ssd1306_t *)_lds->disp;
#define lds_to_priv(_lds) (lws_display_ssd1306_i2c_state_t *)_lds->priv;
int
lws_display_ssd1306_i2c_init(lws_display_state_t *lds)
{
const lws_display_ssd1306_t *si = lds_to_disp(lds);
lws_display_ssd1306_i2c_state_t *priv;
priv = lws_zalloc(sizeof(*priv), __func__);
if (!priv)
return 1;
priv->lds = lds;
lds->priv = priv;
si->i2c->init(si->i2c);
if (si->gpio) {
si->gpio->mode(si->reset_gpio, LWSGGPIO_FL_WRITE |
LWSGGPIO_FL_PULLUP);
si->gpio->set(si->reset_gpio, 0);
lws_msleep(1);
si->gpio->set(si->reset_gpio, 1);
lws_msleep(1);
}
if (lws_i2c_command_list(si->i2c, si->i2c7_address,
ssd1306_128x64_init,
LWS_ARRAY_SIZE(ssd1306_128x64_init))) {
lwsl_err("%s: fail\n", __func__);
return 1;
}
if (si->cb)
si->cb(lds, 1);
return 0;
}
int
lws_display_ssd1306_i2c_contrast(lws_display_state_t *lds, uint8_t b)
{
const lws_display_ssd1306_t *si = lds_to_disp(lds);
uint8_t ba[2];
ba[0] = SSD1306_SETCONTRAST;
ba[1] = b;
return lws_i2c_command_list(si->i2c, si->i2c7_address,
ba, LWS_ARRAY_SIZE(ba));
}
int
lws_display_ssd1306_i2c_blit(lws_display_state_t *lds, const uint8_t *src,
lws_box_t *box, lws_dll2_owner_t *ids)
{
lws_display_ssd1306_i2c_state_t *priv = lds_to_priv(lds);
const lws_display_ssd1306_t *si = lds_to_disp(lds);
const lws_surface_info_t *ic = &lds->disp->ic;
lws_greyscale_error_t *gedl_this, *gedl_next;
int bytes_pl = (ic->wh_px[0].whole + 7) / 8;
lws_display_list_coord_t y = box->y.whole;
const uint8_t *pc = src;
lws_display_colour_t c;
uint8_t ba[6], *lo;
int n, m;
/*
* The display is arranged in 128x8 bands, with one byte containing
* the 8 vertical pixels of the band.
*/
if (!priv->line8) {
priv->line8 = lws_malloc(bytes_pl * 8, __func__);
if (!priv->line8)
return 1;
if (lws_display_alloc_diffusion(ic, priv->u)) {
lws_free_set_NULL(priv->line8);
lwsl_err("%s: OOM\n", __func__);
return 1;
}
}
lo = priv->line8;
switch (box->h.whole) {
default: /* start */
break;
case 0: /* end */
lws_free_set_NULL(priv->line8);
lws_free_set_NULL(priv->u[0]);
lwsl_err("%s: End of raster\n", __func__);
ba[0] = SSD1306_NORMALDISPLAY;
ba[1] = SSD1306_DISPLAYON;
if (lws_i2c_command_list(si->i2c, si->i2c7_address, ba, 2)) {
lwsl_err("%s: fail\n", __func__);
return 1;
}
if (si->cb)
si->cb(priv->lds, 2);
break;
case 1: /* per line */
gedl_this = (lws_greyscale_error_t *)priv->u[(box->y.whole & 1) ^ 1];
gedl_next = (lws_greyscale_error_t *)priv->u[box->y.whole & 1];
for (n = ic->wh_px[0].whole - 1; n >= 0; n--) {
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
m = lws_display_palettize_grey(ic, ic->palette,
ic->palette_depth, c, &gedl_this[n]);
if (m)
lo[n] = (lo[n] | (1 << (y & 7)));
else
lo[n] = (lo[n] & ~(1 << (y & 7)));
dist_err_floyd_steinberg_grey(n, ic->wh_px[0].whole,
gedl_this, gedl_next);
pc++;
}
if ((y & 7) != 7)
break;
ba[0] = SSD1306_COLUMNADDR;
ba[1] = box->x.whole;
ba[2] = box->x.whole + box->w.whole - 1;
if (lws_i2c_command_list(si->i2c, si->i2c7_address,
ba, 3)) {
lwsl_err("%s: fail\n", __func__);
return 1;
}
ba[0] = SSD1306_PAGEADDR;
ba[1] = y / 8;
ba[2] = ba[1] + ((ic->wh_px[0].whole) / 8) - 1;
if (lws_i2c_command_list(si->i2c, si->i2c7_address,
ba, 3)) {
lwsl_err("%s: fail\n", __func__);
return 1;
}
lws_bb_i2c_start(si->i2c);
lws_bb_i2c_write(si->i2c, si->i2c7_address << 1);
lws_bb_i2c_write(si->i2c, SSD1306_SETSTARTLINE | y);
for (m = 0; m < box->w.whole; m++)
lws_bb_i2c_write(si->i2c, priv->line8[m]);
lws_bb_i2c_stop(si->i2c);
break;
}
return 0;
}
int
lws_display_ssd1306_i2c_power(lws_display_state_t *lds, int state)
{
#if 0
const lws_display_ssd1306_t *si = (const lws_display_ssd1306_t *)lds->disp;
if (!state)
return lws_i2c_command(si->i2c, si->i2c7_address,
SSD1306_DISPLAYOFF | !!state);
return lws_display_ssd1306_i2c_init(lds);
#endif
return 0;
}

View File

@ -0,0 +1,495 @@
/*
* lws abstract display implementation for SSD1675B 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://cdn-learn.adafruit.com/assets/assets/000/092/748/original/SSD1675_0.pdf
*
* This chip takes a planar approach with two distinct framebuffers for b0 and
* b1 of the red levels. But the panel is B&W so we ignore red.
*
* Notice this 2.13" B&W panel needs POSITION B on the Waveshare ESP32
* prototype board DIP switch.
*/
#include <private-lib-core.h>
#include <dlo/private-lib-drivers-display-dlo.h>
enum {
SSD1675B_CMD_DRIVER_OUT_CTRL = 0x01,
SSD1675B_CMD_GATE_DRIVEV_CTRL = 0x03,
SSD1675B_CMD_SOURCE_DRIVEV_CTRL = 0x04,
SSD1675B_CMD_DEEP_SLEEP = 0x10,
SSD1675B_CMD_DATA_ENTRY_MODE = 0x11,
SSD1675B_CMD_SW_RESET = 0x12,
SSD1675B_CMD_MAIN_ACTIVATION = 0x20,
SSD1675B_CMD_DISPLAY_UPDATE_CTRL = 0x22,
SSD1675B_CMD_WRITE_BW_SRAM = 0x24,
SSD1675B_CMD_WRITE_RED_SRAM = 0x26,
SSD1675B_CMD_VCOM_VOLTAGE = 0x2C,
SSD1675B_CMD_LUT = 0x32,
SSD1675B_CMD_WRITE_DISPLAY_OPTIONS = 0x37,
SSD1675B_CMD_DUMMY_LINE = 0x3A,
SSD1675B_CMD_GATE_TIME = 0x3B,
SSD1675B_CMD_BORDER_WAVEFORM = 0x3C,
SSD1675B_CMD_SET_RAM_X = 0x44,
SSD1675B_CMD_SET_RAM_Y = 0x45,
SSD1675B_CMD_SET_COUNT_X = 0x4e,
SSD1675B_CMD_SET_COUNT_Y = 0x4f,
SSD1675B_CMD_SET_ANALOG_BLOCK_CTRL = 0x74,
SSD1675B_CMD_SET_DIGITAL_BLOCK_CTRL = 0x7e,
};
typedef enum {
LWSDISPST_IDLE,
LWSDISPST_INIT1,
LWSDISPST_INIT2,
LWSDISPST_INIT3,
LWSDISPST_INIT4,
LWSDISPST_WRITE1,
LWSDISPST_WRITE2,
LWSDISPST_WRITE3,
LWSDISPST_WRITE4,
LWSDISPST_WRITE5,
LWSDISPRET_ASYNC = 1
} lws_display_update_state_t;
//static
const uint8_t ssd1675b_init1_full[] = {
0, SSD1675B_CMD_SW_RESET,
/* wait idle */
}, ssd1675b_init1_part[] = {
1, SSD1675B_CMD_VCOM_VOLTAGE, 0x26,
/* wait idle */
}, ssd1675b_init2_full[] = {
1, SSD1675B_CMD_SET_ANALOG_BLOCK_CTRL, 0x54,
1, SSD1675B_CMD_SET_DIGITAL_BLOCK_CTRL, 0x3b,
3, SSD1675B_CMD_DRIVER_OUT_CTRL, 0xf9, 0x00, 0x00,
1, SSD1675B_CMD_DATA_ENTRY_MODE, 0x03,
2, SSD1675B_CMD_SET_RAM_X, 0x00, 0x0f,
4, SSD1675B_CMD_SET_RAM_Y, 0x00, 0x00, 0xf9, 0x00,
1, SSD1675B_CMD_BORDER_WAVEFORM, 0x03,
1, SSD1675B_CMD_VCOM_VOLTAGE, 0x55,
1, SSD1675B_CMD_GATE_DRIVEV_CTRL, 0x15,
3, SSD1675B_CMD_SOURCE_DRIVEV_CTRL, 0x41, 0xa8, 0x32,
1, SSD1675B_CMD_DUMMY_LINE, 0x30,
1, SSD1675B_CMD_GATE_TIME, 0x0a,
70, SSD1675B_CMD_LUT,
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, //LUT0: BB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, //LUT1: BW: VS 0 ~7
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, //LUT2: WB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, //LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT4: VCOM:VS 0 ~7
0x03, 0x03, 0x00, 0x00, 0x02, // TP0 A~D RP0
0x09, 0x09, 0x00, 0x00, 0x02, // TP1 A~D RP1
0x03, 0x03, 0x00, 0x00, 0x02, // TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6
1, SSD1675B_CMD_SET_COUNT_X, 0x00,
2, SSD1675B_CMD_SET_COUNT_Y, 0x00, 0x00,
}, ssd1675b_init2_part[] = {
70, SSD1675B_CMD_LUT,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT0: BB: VS 0 ~7
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT1: BW: VS 0 ~7
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT2: WB: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT4: VCOM:VS 0 ~7
0x0A, 0x00, 0x00, 0x00, 0x00, // TP0 A~D RP0
0x00, 0x00, 0x00, 0x00, 0x00, // TP1 A~D RP1
0x00, 0x00, 0x00, 0x00, 0x00, // TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6
7, SSD1675B_CMD_WRITE_DISPLAY_OPTIONS, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00,
1, SSD1675B_CMD_DISPLAY_UPDATE_CTRL, 0xc0,
0, SSD1675B_CMD_MAIN_ACTIVATION,
/* wait idle */
}, ssd1675b_init3_part[] = {
1, SSD1675B_CMD_BORDER_WAVEFORM, 0x01
}, ssd1675b_off[] = {
1, SSD1675B_CMD_DEEP_SLEEP, 0x01
}, ssd1675b_wp1[] = {
0, SSD1675B_CMD_WRITE_BW_SRAM,
}, ssd1675b_wp2[] = {
0, SSD1675B_CMD_WRITE_RED_SRAM,
}, ssd1675b_complete_full[] = {
1, SSD1675B_CMD_DISPLAY_UPDATE_CTRL, 0xc7,
0, SSD1675B_CMD_MAIN_ACTIVATION
};
typedef struct lws_display_ssd1675b_spi_state {
struct lws_display_state *lds;
uint8_t *planebuf;
uint32_t *line[2];
lws_surface_error_t *u[2];
lws_sorted_usec_list_t sul;
size_t pb_len;
size_t pb_pos;
int state;
int budget;
} lws_display_ssd1675b_spi_state_t;
#define lds_to_disp(_lds) (const lws_display_ssd1675b_spi_t *)_lds->disp;
#define lds_to_priv(_lds) (lws_display_ssd1675b_spi_state_t *)_lds->priv;
/*
* The lws greyscale line composition buffer is width x Y bytes linearly.
*
* For SSD1675B, 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. We only support B&W atm.
*/
/* MSB plane is in first half of priv linebuf */
#define pack_native_pixel(_line, _x, _c) \
{ *_line = (*_line & ~(1 << (((_x ^ 7) & 31)))) | \
(_c << (((_x ^ 7) & 31))); \
if ((_x & 31) == 31) \
_line++; }
static void
async_cb(lws_sorted_usec_list_t *sul);
#define BUSY_TIMEOUT_BUDGET 160
static int
check_busy(lws_display_ssd1675b_spi_state_t *priv, int level)
{
const lws_display_ssd1675b_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 * 50);
return 1; /* keeping on trying */
}
static int
spi_issue_table(struct lws_display_state *lds, const uint8_t *table, size_t len)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(lds);
lws_spi_desc_t desc;
size_t pos = 0;
memset(&desc, 0, sizeof(desc));
desc.count_cmd = 1;
while (pos < len) {
desc.count_write = table[pos++];
desc.src = &table[pos++];
desc.data = &table[pos];
pos += desc.count_write;
ea->spi->queue(ea->spi, &desc);
}
return 0;
}
static void
async_cb(lws_sorted_usec_list_t *sul)
{
lws_display_ssd1675b_spi_state_t *priv = lws_container_of(sul,
lws_display_ssd1675b_spi_state_t, sul);
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(priv->lds);
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, 0))
return;
spi_issue_table(priv->lds, ssd1675b_init1_full,
LWS_ARRAY_SIZE(ssd1675b_init1_full));
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
async_cb, LWS_US_PER_MS * 10);
break;
case LWSDISPST_INIT4:
if (check_busy(priv, 0))
return;
priv->state = LWSDISPST_IDLE;
spi_issue_table(priv->lds, ssd1675b_init2_full,
LWS_ARRAY_SIZE(ssd1675b_init2_full));
if (ea->cb)
ea->cb(priv->lds, 1);
break;
case LWSDISPST_WRITE1:
/*
* Finalize the write of the planes, LUT set then REFRESH
*/
spi_issue_table(priv->lds, ssd1675b_complete_full,
LWS_ARRAY_SIZE(ssd1675b_complete_full));
priv->budget = BUSY_TIMEOUT_BUDGET;
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 50);
break;
case LWSDISPST_WRITE2:
if (check_busy(priv, 0))
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]);
/* fully completed the blit */
priv->state = LWSDISPST_IDLE;
if (ea->cb)
ea->cb(priv->lds, 2);
break;
default:
break;
}
}
int
lws_display_ssd1675b_spi_init(struct lws_display_state *lds)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(lds);
lws_display_ssd1675b_spi_state_t *priv;
priv = lws_zalloc(sizeof(*priv), __func__);
if (!priv)
return 1;
priv->lds = lds;
lds->priv = priv;
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_ssd1675b_spi_brightness(const struct lws_display *disp, uint8_t b)
{
return 0;
}
int
lws_display_ssd1675b_spi_blit(struct lws_display_state *lds, const uint8_t *src,
lws_box_t *box)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(lds);
lws_display_ssd1675b_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[0].whole + 7) / 8;
lws_colour_error_t *edl_this, *edl_next;
const uint8_t *pc = src;
lws_display_colour_t c;
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]) {
/*
* We have to allocate the packed line and error diffusion
* buffers
*/
if (ea->spi->alloc_dma)
priv->line[0] = ea->spi->alloc_dma(ea->spi, (plane_line_bytes + 4) * 2);
else
priv->line[0] = lws_zalloc((plane_line_bytes + 4) * 2, __func__);
if (!priv->line[0]) {
lwsl_err("%s: OOM\n", __func__);
priv->state = LWSDISPST_IDLE;
return 1;
}
priv->line[1] = (uint32_t *)(((uint8_t *)priv->line[0]) + plane_line_bytes + 4);
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 1;
}
}
lo = priv->line[box->y.whole & 1];
switch (box->h.whole) {
case 0: /* update needs to be finalized */
priv->state = LWSDISPST_WRITE1;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 2);
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;
if (!pc) {
for (n = 0; n < ic->wh_px[0].whole; n++)
pack_native_pixel(lo, n, 1 /* white */);
goto go;
}
if (ic->greyscale) {
gedl_next[ic->wh_px[0].whole - 1].rgb[0] = 0;
for (n = 0; n < plane_line_bytes * 8; n++) {
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
m = lws_display_palettize_grey(ic, ic->palette,
ic->palette_depth, c, &gedl_this[n]);
pack_native_pixel(lo, n, (uint8_t)m);
dist_err_floyd_steinberg_grey(n, ic->wh_px[0].whole,
gedl_this, gedl_next);
if (n < ic->wh_px[0].whole)
pc++;
}
} else {
edl_next[ic->wh_px[0].whole - 1].rgb[0] = 0;
edl_next[ic->wh_px[0].whole - 1].rgb[1] = 0;
edl_next[ic->wh_px[0].whole - 1].rgb[2] = 0;
for (n = 0; n < plane_line_bytes * 8; n++) {
c = (pc[2] << 16) | (pc[1] << 8) | pc[0];
m = lws_display_palettize_col(ic, ic->palette,
ic->palette_depth, c, &edl_this[n]);
pack_native_pixel(lo, n, (uint8_t)m);
dist_err_floyd_steinberg_col(n, ic->wh_px[0].whole,
edl_this, edl_next);
if (n < ic->wh_px[0].whole)
pc += 3;
}
}
go:
memset(&desc, 0, sizeof(desc));
if (!box->y.whole)
spi_issue_table(priv->lds, ssd1675b_wp1,
LWS_ARRAY_SIZE(ssd1675b_wp1));
desc.data = (uint8_t *)priv->line[box->y.whole & 1];
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
desc.count_write = plane_line_bytes;
ea->spi->queue(ea->spi, &desc);
return 0;
default: /* starting update */
break;
}
return 0;
}
int
lws_display_ssd1675b_spi_power(lws_display_state_t *lds, int state)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(lds);
if (!state) {
spi_issue_table(lds, ssd1675b_off, LWS_ARRAY_SIZE(ssd1675b_off));
if (ea->gpio)
ea->gpio->set(ea->reset_gpio, 0);
return 0;
}
return 0;
}

File diff suppressed because it is too large Load Diff