Commit 59679a8a authored by Rahix's avatar Rahix
Browse files

feat(epicardium): Add a new LCD driver

Currently we're still working with the reference driver from Waveshare.
This driver seems hacked together and also does not really fit for what
we're doing with the display.  Even less so because it configures the
display upside down.

As a first step towards dropping this vendor library, introduce
a reimplementation which is purpose built to our usecase.  This new
library configures the screen right side up natively but still allows to
change to the legacy orientation on the fly to support the old codebase.
parent 1695d090
#include "os/core.h"
#include "MAX77650-Arduino-Library.h"
#include "gpio.h"
#include "mxc_delay.h"
#include "portexpander.h"
#include "spi.h"
#include <machine/endian.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* HAL Interfaces {{{ */
static const gpio_cfg_t GPIO_PIN_DC = {
PORT_1, PIN_6, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
static void lcd_hw_init(void)
{
GPIO_Config(&GPIO_PIN_DC);
/* for the reset pin */
if (!portexpander_detected()) {
/* Open-drain */
MAX77650_setDRV(false);
/* Output */
MAX77650_setDIR(false);
}
}
static void lcd_set_dc(bool state)
{
if (state) {
GPIO_OutSet(&GPIO_PIN_DC);
} else {
GPIO_OutClr(&GPIO_PIN_DC);
}
}
static void lcd_set_rst(bool state)
{
if (!portexpander_detected()) {
MAX77650_setDO(state ? true : false);
} else {
portexpander_out_put(PIN_4, state ? 0xFF : 0);
}
}
/** Bit Rate. Display has 15 MHz limit */
#define SPI_SPEED (15 * 1000 * 1000)
static void lcd_spi_write(const uint8_t *data, size_t count)
{
const sys_cfg_spi_t spi_master_cfg = {
.map = MAP_A,
.ss0 = Enable,
.ss1 = Disable,
.ss2 = Disable,
.num_io = 2,
};
spi_req_t request = {
.ssel = 0,
.deass = 1,
.ssel_pol = SPI17Y_POL_LOW,
.tx_data = data,
.rx_data = NULL,
.width = SPI17Y_WIDTH_1,
.len = count,
.bits = 8,
.rx_num = 0,
.tx_num = 0,
.callback = NULL,
};
if (SPI_Init(SPI2, 0, SPI_SPEED, spi_master_cfg) != 0) {
panic("Error configuring display SPI");
}
SPI_MasterTrans(SPI2, &request);
}
static void lcd_delay(size_t millis)
{
// TODO: Is this what we want?
mxc_delay(millis * 1000);
}
/* HAL Interfaces }}} */
enum lcd_commands {
/** Sleep Out */
LCD_SLPOUT = 0x11,
/** Display Inversion On */
LCD_INVON = 0x21,
/** Display On */
LCD_DISPON = 0x29,
/** Column Address Set */
LCD_CASET = 0x2A,
/** Row Address Set */
LCD_RASET = 0x2B,
/** Memory Write */
LCD_RAMWR = 0x2C,
/** Memory Data Access Control */
LCD_MADCTL = 0x36,
/** Interface Pixel Format */
LCD_COLMOD = 0x3A,
/** Frame Rate Control (In normal mode/ Full colors) */
LCD_FRMCTR1 = 0xB1,
/** Frame Rate Control (In Idle mode/ 8-colors) */
LCD_FRMCTR2 = 0xB2,
/** Frame Rate Control (In Partial mode/ full colors) */
LCD_FRMCTR3 = 0xB3,
/** Display Inversion Control */
LCD_INVCTR = 0xB4,
/** Power Control 1 */
LCD_PWCTR1 = 0xC0,
/** Power Control 2 */
LCD_PWCTR2 = 0xC1,
/** Power Control 3 (in Normal mode/ Full colors) */
LCD_PWCTR3 = 0xC2,
/** Power Control 4 (in Idle mode/ 8-colors) */
LCD_PWCTR4 = 0xC3,
/** Power Control 5 (in Partial mode/ full-colors) */
LCD_PWCTR5 = 0xC4,
/** VCOM Control 1 */
LCD_VMCTR1 = 0xC5,
/** Gamma (+ polarity) Correction Characteristics Setting */
LCD_GMCTRP1 = 0xE0,
/** Gamma (- polarity) Correction Characteristics Setting */
LCD_GMCTRN1 = 0xE1,
};
enum madctl_bits {
MADCTL_MY = 0x80,
MADCTL_MX = 0x40,
MADCTL_MV = 0x20,
MADCTL_ML = 0x10,
MADCTL_RGB = 0x08,
MADCTL_MH = 0x04,
};
static void
lcd_send_command(enum lcd_commands cmd, const uint8_t *args, size_t count)
{
lcd_set_dc(false);
lcd_spi_write((uint8_t *)&cmd, 1);
if (args != NULL && count != 0) {
lcd_set_dc(true);
lcd_spi_write(args, count);
}
}
static void lcd_hard_reset(void)
{
lcd_delay(20);
lcd_set_rst(false);
lcd_delay(20);
lcd_set_rst(true);
lcd_delay(20);
}
/**
* Perform a minimal initialization under the assumption that the bootloader has
* already turned on the display. This is faster and prevents visible
* reinitialization artifacts.
*/
void lcd_reconfigure(void)
{
/* Invert Display (twice for unknown reasons ...). */
lcd_send_command(LCD_INVON, NULL, 0);
lcd_send_command(LCD_INVON, NULL, 0);
/* Set framerate control values for all modes to the same values. */
const uint8_t frmctr[] = { 0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A };
lcd_send_command(LCD_FRMCTR1, frmctr, 3);
lcd_send_command(LCD_FRMCTR2, frmctr, 3);
lcd_send_command(LCD_FRMCTR3, frmctr, 6);
/* Set display inversion control (unsure what this does?). */
const uint8_t invctr[] = { 0x03 };
lcd_send_command(LCD_INVCTR, invctr, sizeof(invctr));
/* Configure GVDD voltage to 4.7V. */
const uint8_t pwctr1[] = { 0x62, 0x02, 0x04 };
lcd_send_command(LCD_PWCTR1, pwctr1, sizeof(pwctr1));
/* Configure only ignored bits? */
const uint8_t pwctr2[] = { 0xC0 };
lcd_send_command(LCD_PWCTR2, pwctr2, sizeof(pwctr2));
/*
* Configure "large" amount of current in operational amplifier and
* booster step-ups for all modes.
*/
const uint8_t pwctr3[] = { 0x0D, 0x00 }, pwctr4[] = { 0x8D, 0x6A },
pwctr5[] = { 0x8D, 0xEE };
lcd_send_command(LCD_PWCTR3, pwctr3, sizeof(pwctr3));
lcd_send_command(LCD_PWCTR4, pwctr4, sizeof(pwctr4));
lcd_send_command(LCD_PWCTR5, pwctr5, sizeof(pwctr5));
/* Configure VCOMH voltage to 2.850V. */
const uint8_t vmctr1[] = { 0x0E };
lcd_send_command(LCD_VMCTR1, vmctr1, sizeof(vmctr1));
/* Write positive and negative gamma correction values. */
const uint8_t gmctrp1[] = {
0x10, 0x0E, 0x02, 0x03, 0x0E, 0x07, 0x02, 0x07,
0x0A, 0x12, 0x27, 0x37, 0x00, 0x0D, 0x0E, 0x10,
};
const uint8_t gmctrn1[] = {
0x10, 0x0E, 0x03, 0x03, 0x0F, 0x06, 0x02, 0x08,
0x0A, 0x13, 0x26, 0x36, 0x00, 0x0D, 0x0E, 0x10,
};
lcd_send_command(LCD_GMCTRP1, gmctrp1, sizeof(gmctrp1));
lcd_send_command(LCD_GMCTRN1, gmctrn1, sizeof(gmctrn1));
/* Configure 16-bit pixel format. */
const uint8_t colmod[] = { 0x05 };
lcd_send_command(LCD_COLMOD, colmod, sizeof(colmod));
/*
* Configure "MADCTL", which defines the pixel and color access order.
*/
const uint8_t madctl[] = { MADCTL_MX | MADCTL_MV | MADCTL_RGB };
/*
* Waveshare Driver:
* const uint8_t madctl[] = { MADCTL_MY | MADCTL_MV | MADCTL_RGB };
*/
lcd_send_command(LCD_MADCTL, madctl, sizeof(madctl));
/* Turn the display on. */
lcd_send_command(LCD_DISPON, NULL, 0);
}
/**
* Perform a full initialization of the display. This will ensure the display
* is in a deterministic state.
*/
void lcd_initialize(void)
{
lcd_hw_init();
lcd_hard_reset();
lcd_send_command(LCD_SLPOUT, NULL, 0);
lcd_delay(120);
lcd_reconfigure();
}
/**
* Write a partial display update.
*
* The rectangle from column ``xstart`` to ``xend`` (inclusive) and row
* ``ystart`` to ``yend`` (inclusive) will be updated with the contents of
* ``fb``.
*
* ``fb`` **must** have a size of
* ``(xend - xstart + 1) * (yend - ystart + 1) * 2`` bytes.
*/
void lcd_write_fb_partial(
uint16_t xstart,
uint16_t ystart,
uint16_t xend,
uint16_t yend,
const uint8_t *fb
) {
uint16_t param_buffer[2];
/* Column start and end are offset by 1. */
param_buffer[0] = __htons(xstart + 1);
param_buffer[1] = __htons(xend + 1);
lcd_send_command(
LCD_CASET, (uint8_t *)param_buffer, sizeof(param_buffer)
);
/* Row start and end are offset by a magic 26. */
param_buffer[0] = __htons(ystart + 26);
param_buffer[1] = __htons(yend + 26);
lcd_send_command(
LCD_RASET, (uint8_t *)param_buffer, sizeof(param_buffer)
);
/* Now write out the actual framebuffer contents. */
size_t fb_size = (xend - xstart + 1) * (yend - ystart + 1) * 2;
lcd_send_command(LCD_RAMWR, fb, fb_size);
}
/**
* Write out a full framebuffer update.
*
* ``fb`` **must** be 160 * 80 * 2 = **25600** bytes in size. The pixels are
* ordered in rows starting at the top left of the screen. Each pixel must have
* its bytes laid out as big endian (while the CPU is little endian!).
*/
void lcd_write_fb(const uint8_t *fb)
{
lcd_write_fb_partial(0, 0, 159, 79, fb);
}
/**
* Flip the screen orientation upside down.
*
* Historically we had software perform a flip of the framebuffer before
* sending it out. This function provides a way to make the hardware accept
* such a flipped framebuffer. This exists mostly to support the
* :c:func:`epic_disp_framebuffer()` API call for legacy l0dables.
*/
void lcd_set_screenflip(bool flipped)
{
const uint8_t madctl_upright[] = { MADCTL_MX | MADCTL_MV | MADCTL_RGB };
const uint8_t madctl_flipped[] = { MADCTL_MY | MADCTL_MV | MADCTL_RGB };
if (flipped) {
lcd_send_command(LCD_MADCTL, madctl_flipped, 1);
} else {
lcd_send_command(LCD_MADCTL, madctl_upright, 1);
}
}
#pragma once
#include <stdbool.h>
#include <stdint.h>
void lcd_reconfigure(void);
void lcd_initialize(void);
void lcd_write_fb_partial(
uint16_t xstart,
uint16_t ystart,
uint16_t xend,
uint16_t yend,
const uint8_t *fb
);
void lcd_write_fb(const uint8_t *fb);
void lcd_set_screenflip(bool flipped);
......@@ -17,5 +17,7 @@ driver_sources = files(
'usb.c',
'vibra.c',
'watchdog.c',
'ws2812.c'
'ws2812.c',
'display/lcd.c',
)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment