473 lines
12 KiB
C
473 lines
12 KiB
C
/*
|
|
* Copyright (c) 2020 PHYTEC Messtechnik GmbH
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT gooddisplay_gd7965
|
|
|
|
#include <string.h>
|
|
#include <device.h>
|
|
#include <init.h>
|
|
#include <drivers/display.h>
|
|
#include <drivers/gpio.h>
|
|
#include <drivers/spi.h>
|
|
#include <sys/byteorder.h>
|
|
|
|
#include "gd7965_regs.h"
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(gd7965, CONFIG_DISPLAY_LOG_LEVEL);
|
|
|
|
/**
|
|
* GD7965 compatible EPD controller driver.
|
|
*
|
|
* Currently only the black/white pannels are supported (KW mode),
|
|
* also first gate/source should be 0.
|
|
*/
|
|
|
|
#define GD7965_SPI_FREQ DT_INST_PROP(0, spi_max_frequency)
|
|
#define GD7965_BUS_NAME DT_INST_BUS_LABEL(0)
|
|
#define GD7965_DC_PIN DT_INST_GPIO_PIN(0, dc_gpios)
|
|
#define GD7965_DC_FLAGS DT_INST_GPIO_FLAGS(0, dc_gpios)
|
|
#define GD7965_DC_CNTRL DT_INST_GPIO_LABEL(0, dc_gpios)
|
|
#define GD7965_CS_PIN DT_INST_SPI_DEV_CS_GPIOS_PIN(0)
|
|
#define GD7965_CS_FLAGS DT_INST_SPI_DEV_CS_GPIOS_FLAGS(0)
|
|
#if DT_INST_SPI_DEV_HAS_CS_GPIOS(0)
|
|
#define GD7965_CS_CNTRL DT_INST_SPI_DEV_CS_GPIOS_LABEL(0)
|
|
#endif
|
|
#define GD7965_BUSY_PIN DT_INST_GPIO_PIN(0, busy_gpios)
|
|
#define GD7965_BUSY_CNTRL DT_INST_GPIO_LABEL(0, busy_gpios)
|
|
#define GD7965_BUSY_FLAGS DT_INST_GPIO_FLAGS(0, busy_gpios)
|
|
#define GD7965_RESET_PIN DT_INST_GPIO_PIN(0, reset_gpios)
|
|
#define GD7965_RESET_CNTRL DT_INST_GPIO_LABEL(0, reset_gpios)
|
|
#define GD7965_RESET_FLAGS DT_INST_GPIO_FLAGS(0, reset_gpios)
|
|
|
|
#define EPD_PANEL_WIDTH DT_INST_PROP(0, width)
|
|
#define EPD_PANEL_HEIGHT DT_INST_PROP(0, height)
|
|
#define GD7965_PIXELS_PER_BYTE 8U
|
|
|
|
/* Horizontally aligned page! */
|
|
#define GD7965_NUMOF_PAGES (EPD_PANEL_WIDTH / \
|
|
GD7965_PIXELS_PER_BYTE)
|
|
#define GD7965_PANEL_FIRST_GATE 0U
|
|
#define GD7965_PANEL_LAST_GATE (EPD_PANEL_HEIGHT - 1)
|
|
#define GD7965_PANEL_FIRST_PAGE 0U
|
|
#define GD7965_PANEL_LAST_PAGE (GD7965_NUMOF_PAGES - 1)
|
|
|
|
|
|
struct gd7965_data {
|
|
struct device *reset;
|
|
struct device *dc;
|
|
struct device *busy;
|
|
struct device *spi_dev;
|
|
struct spi_config spi_config;
|
|
#if defined(GD7965_CS_CNTRL)
|
|
struct spi_cs_control cs_ctrl;
|
|
#endif
|
|
};
|
|
|
|
static uint8_t gd7965_softstart[] = DT_INST_PROP(0, softstart);
|
|
static uint8_t gd7965_pwr[] = DT_INST_PROP(0, pwr);
|
|
|
|
/* Border and data polarity settings */
|
|
static uint8_t bdd_polarity;
|
|
|
|
static bool blanking_on = true;
|
|
|
|
static inline int gd7965_write_cmd(struct gd7965_data *driver,
|
|
uint8_t cmd, uint8_t *data, size_t len)
|
|
{
|
|
struct spi_buf buf = {.buf = &cmd, .len = sizeof(cmd)};
|
|
struct spi_buf_set buf_set = {.buffers = &buf, .count = 1};
|
|
|
|
gpio_pin_set(driver->dc, GD7965_DC_PIN, 1);
|
|
if (spi_write(driver->spi_dev, &driver->spi_config, &buf_set)) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (data != NULL) {
|
|
buf.buf = data;
|
|
buf.len = len;
|
|
gpio_pin_set(driver->dc, GD7965_DC_PIN, 0);
|
|
if (spi_write(driver->spi_dev, &driver->spi_config, &buf_set)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void gd7965_busy_wait(struct gd7965_data *driver)
|
|
{
|
|
int pin = gpio_pin_get(driver->busy, GD7965_BUSY_PIN);
|
|
|
|
while (pin > 0) {
|
|
__ASSERT(pin >= 0, "Failed to get pin level");
|
|
LOG_DBG("wait %u", pin);
|
|
k_sleep(K_MSEC(GD7965_BUSY_DELAY));
|
|
pin = gpio_pin_get(driver->busy, GD7965_BUSY_PIN);
|
|
}
|
|
}
|
|
|
|
static int gd7965_update_display(const struct device *dev)
|
|
{
|
|
struct gd7965_data *driver = dev->driver_data;
|
|
|
|
LOG_DBG("Trigger update sequence");
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_DRF, NULL, 0)) {
|
|
return -EIO;
|
|
}
|
|
|
|
k_sleep(K_MSEC(GD7965_BUSY_DELAY));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gd7965_blanking_off(const struct device *dev)
|
|
{
|
|
struct gd7965_data *driver = dev->driver_data;
|
|
|
|
if (blanking_on) {
|
|
/* Update EPD pannel in normal mode */
|
|
gd7965_busy_wait(driver);
|
|
if (gd7965_update_display(dev)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
blanking_on = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gd7965_blanking_on(const struct device *dev)
|
|
{
|
|
blanking_on = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gd7965_write(const struct device *dev, const uint16_t x, const uint16_t y,
|
|
const struct display_buffer_descriptor *desc,
|
|
const void *buf)
|
|
{
|
|
struct gd7965_data *driver = dev->driver_data;
|
|
uint16_t x_end_idx = x + desc->width - 1;
|
|
uint16_t y_end_idx = y + desc->height - 1;
|
|
uint8_t ptl[GD7965_PTL_REG_LENGTH] = {0};
|
|
size_t buf_len;
|
|
|
|
LOG_DBG("x %u, y %u, height %u, width %u, pitch %u",
|
|
x, y, desc->height, desc->width, desc->pitch);
|
|
|
|
buf_len = MIN(desc->buf_size,
|
|
desc->height * desc->width / GD7965_PIXELS_PER_BYTE);
|
|
__ASSERT(desc->width <= desc->pitch, "Pitch is smaller then width");
|
|
__ASSERT(buf != NULL, "Buffer is not available");
|
|
__ASSERT(buf_len != 0U, "Buffer of length zero");
|
|
__ASSERT(!(desc->width % GD7965_PIXELS_PER_BYTE),
|
|
"Buffer width not multiple of %d", GD7965_PIXELS_PER_BYTE);
|
|
|
|
if ((y_end_idx > (EPD_PANEL_HEIGHT - 1)) ||
|
|
(x_end_idx > (EPD_PANEL_WIDTH - 1))) {
|
|
LOG_ERR("Position out of bounds");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Setup Partial Window and enable Partial Mode */
|
|
sys_put_be16(x, &ptl[GD7965_PTL_HRST_IDX]);
|
|
sys_put_be16(x_end_idx, &ptl[GD7965_PTL_HRED_IDX]);
|
|
sys_put_be16(y, &ptl[GD7965_PTL_VRST_IDX]);
|
|
sys_put_be16(y_end_idx, &ptl[GD7965_PTL_VRED_IDX]);
|
|
ptl[sizeof(ptl) - 1] = GD7965_PTL_PT_SCAN;
|
|
LOG_HEXDUMP_DBG(ptl, sizeof(ptl), "ptl");
|
|
|
|
gd7965_busy_wait(driver);
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_PTIN, NULL, 0)) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_PTL, ptl, sizeof(ptl))) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Disable boarder output */
|
|
bdd_polarity |= GD7965_CDI_BDZ;
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_CDI,
|
|
&bdd_polarity, sizeof(bdd_polarity))) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_DTM2, (uint8_t *)buf, buf_len)) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Update partial window and disable Partial Mode */
|
|
if (blanking_on == false) {
|
|
if (gd7965_update_display(dev)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
/* Enable boarder output */
|
|
bdd_polarity &= ~GD7965_CDI_BDZ;
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_CDI,
|
|
&bdd_polarity, sizeof(bdd_polarity))) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_PTOUT, NULL, 0)) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gd7965_read(const struct device *dev, const uint16_t x, const uint16_t y,
|
|
const struct display_buffer_descriptor *desc, void *buf)
|
|
{
|
|
LOG_ERR("not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static void *gd7965_get_framebuffer(const struct device *dev)
|
|
{
|
|
LOG_ERR("not supported");
|
|
return NULL;
|
|
}
|
|
|
|
static int gd7965_set_brightness(const struct device *dev,
|
|
const uint8_t brightness)
|
|
{
|
|
LOG_WRN("not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int gd7965_set_contrast(const struct device *dev, uint8_t contrast)
|
|
{
|
|
LOG_WRN("not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static void gd7965_get_capabilities(const struct device *dev,
|
|
struct display_capabilities *caps)
|
|
{
|
|
memset(caps, 0, sizeof(struct display_capabilities));
|
|
caps->x_resolution = EPD_PANEL_WIDTH;
|
|
caps->y_resolution = EPD_PANEL_HEIGHT;
|
|
caps->supported_pixel_formats = PIXEL_FORMAT_MONO10;
|
|
caps->current_pixel_format = PIXEL_FORMAT_MONO10;
|
|
caps->screen_info = SCREEN_INFO_MONO_MSB_FIRST | SCREEN_INFO_EPD;
|
|
}
|
|
|
|
static int gd7965_set_orientation(const struct device *dev,
|
|
const enum display_orientation
|
|
orientation)
|
|
{
|
|
LOG_ERR("Unsupported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int gd7965_set_pixel_format(const struct device *dev,
|
|
const enum display_pixel_format pf)
|
|
{
|
|
if (pf == PIXEL_FORMAT_MONO10) {
|
|
return 0;
|
|
}
|
|
|
|
LOG_ERR("not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int gd7965_clear_and_write_buffer(struct device *dev,
|
|
uint8_t pattern, bool update)
|
|
{
|
|
struct display_buffer_descriptor desc = {
|
|
.buf_size = GD7965_NUMOF_PAGES,
|
|
.width = EPD_PANEL_WIDTH,
|
|
.height = 1,
|
|
.pitch = EPD_PANEL_WIDTH,
|
|
};
|
|
uint8_t *line;
|
|
|
|
line = k_malloc(GD7965_NUMOF_PAGES);
|
|
if (line == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memset(line, pattern, GD7965_NUMOF_PAGES);
|
|
for (int i = 0; i < EPD_PANEL_HEIGHT; i++) {
|
|
gd7965_write(dev, 0, i, &desc, line);
|
|
}
|
|
|
|
k_free(line);
|
|
|
|
if (update == true) {
|
|
if (gd7965_update_display(dev)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gd7965_controller_init(struct device *dev)
|
|
{
|
|
struct gd7965_data *driver = dev->driver_data;
|
|
uint8_t tmp[GD7965_TRES_REG_LENGTH];
|
|
|
|
gpio_pin_set(driver->reset, GD7965_RESET_PIN, 1);
|
|
k_sleep(K_MSEC(GD7965_RESET_DELAY));
|
|
gpio_pin_set(driver->reset, GD7965_RESET_PIN, 0);
|
|
k_sleep(K_MSEC(GD7965_RESET_DELAY));
|
|
gd7965_busy_wait(driver);
|
|
|
|
LOG_DBG("Initialize GD7965 controller");
|
|
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_PWR, gd7965_pwr,
|
|
sizeof(gd7965_pwr))) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_BTST,
|
|
gd7965_softstart, sizeof(gd7965_softstart))) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Turn on: booster, controller, regulators, and sensor. */
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_PON, NULL, 0)) {
|
|
return -EIO;
|
|
}
|
|
|
|
k_sleep(K_MSEC(GD7965_PON_DELAY));
|
|
gd7965_busy_wait(driver);
|
|
|
|
/* Pannel settings, KW mode */
|
|
tmp[0] = GD7965_PSR_KW_R |
|
|
GD7965_PSR_UD |
|
|
GD7965_PSR_SHL |
|
|
GD7965_PSR_SHD |
|
|
GD7965_PSR_RST;
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_PSR, tmp, 1)) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Set panel resolution */
|
|
sys_put_be16(EPD_PANEL_WIDTH, &tmp[GD7965_TRES_HRES_IDX]);
|
|
sys_put_be16(EPD_PANEL_HEIGHT, &tmp[GD7965_TRES_VRES_IDX]);
|
|
LOG_HEXDUMP_DBG(tmp, sizeof(tmp), "TRES");
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_TRES,
|
|
tmp, GD7965_TRES_REG_LENGTH)) {
|
|
return -EIO;
|
|
}
|
|
|
|
bdd_polarity = GD7965_CDI_BDV1 |
|
|
GD7965_CDI_N2OCP | GD7965_CDI_DDX0;
|
|
tmp[GD7965_CDI_BDZ_DDX_IDX] = bdd_polarity;
|
|
tmp[GD7965_CDI_CDI_IDX] = DT_INST_PROP(0, cdi);
|
|
LOG_HEXDUMP_DBG(tmp, GD7965_CDI_REG_LENGTH, "CDI");
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_CDI, tmp,
|
|
GD7965_CDI_REG_LENGTH)) {
|
|
return -EIO;
|
|
}
|
|
|
|
tmp[0] = DT_INST_PROP(0, tcon);
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_TCON, tmp, 1)) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Enable Auto Sequence */
|
|
tmp[0] = GD7965_AUTO_PON_DRF_POF;
|
|
if (gd7965_write_cmd(driver, GD7965_CMD_AUTO, tmp, 1)) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (gd7965_clear_and_write_buffer(dev, 0xff, false)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gd7965_init(struct device *dev)
|
|
{
|
|
struct gd7965_data *driver = dev->driver_data;
|
|
|
|
LOG_DBG("");
|
|
|
|
driver->spi_dev = device_get_binding(GD7965_BUS_NAME);
|
|
if (driver->spi_dev == NULL) {
|
|
LOG_ERR("Could not get SPI device for GD7965");
|
|
return -EIO;
|
|
}
|
|
|
|
driver->spi_config.frequency = GD7965_SPI_FREQ;
|
|
driver->spi_config.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8);
|
|
driver->spi_config.slave = DT_INST_REG_ADDR(0);
|
|
driver->spi_config.cs = NULL;
|
|
|
|
driver->reset = device_get_binding(GD7965_RESET_CNTRL);
|
|
if (driver->reset == NULL) {
|
|
LOG_ERR("Could not get GPIO port for GD7965 reset");
|
|
return -EIO;
|
|
}
|
|
|
|
gpio_pin_configure(driver->reset, GD7965_RESET_PIN,
|
|
GPIO_OUTPUT_INACTIVE | GD7965_RESET_FLAGS);
|
|
|
|
driver->dc = device_get_binding(GD7965_DC_CNTRL);
|
|
if (driver->dc == NULL) {
|
|
LOG_ERR("Could not get GPIO port for GD7965 DC signal");
|
|
return -EIO;
|
|
}
|
|
|
|
gpio_pin_configure(driver->dc, GD7965_DC_PIN,
|
|
GPIO_OUTPUT_INACTIVE | GD7965_DC_FLAGS);
|
|
|
|
driver->busy = device_get_binding(GD7965_BUSY_CNTRL);
|
|
if (driver->busy == NULL) {
|
|
LOG_ERR("Could not get GPIO port for GD7965 busy signal");
|
|
return -EIO;
|
|
}
|
|
|
|
gpio_pin_configure(driver->busy, GD7965_BUSY_PIN,
|
|
GPIO_INPUT | GD7965_BUSY_FLAGS);
|
|
|
|
#if defined(GD7965_CS_CNTRL)
|
|
driver->cs_ctrl.gpio_dev = device_get_binding(GD7965_CS_CNTRL);
|
|
if (!driver->cs_ctrl.gpio_dev) {
|
|
LOG_ERR("Unable to get SPI GPIO CS device");
|
|
return -EIO;
|
|
}
|
|
|
|
driver->cs_ctrl.gpio_pin = GD7965_CS_PIN;
|
|
driver->cs_ctrl.gpio_dt_flags = GD7965_CS_FLAGS;
|
|
driver->cs_ctrl.delay = 0U;
|
|
driver->spi_config.cs = &driver->cs_ctrl;
|
|
#endif
|
|
|
|
return gd7965_controller_init(dev);
|
|
}
|
|
|
|
static struct gd7965_data gd7965_driver;
|
|
|
|
static struct display_driver_api gd7965_driver_api = {
|
|
.blanking_on = gd7965_blanking_on,
|
|
.blanking_off = gd7965_blanking_off,
|
|
.write = gd7965_write,
|
|
.read = gd7965_read,
|
|
.get_framebuffer = gd7965_get_framebuffer,
|
|
.set_brightness = gd7965_set_brightness,
|
|
.set_contrast = gd7965_set_contrast,
|
|
.get_capabilities = gd7965_get_capabilities,
|
|
.set_pixel_format = gd7965_set_pixel_format,
|
|
.set_orientation = gd7965_set_orientation,
|
|
};
|
|
|
|
|
|
DEVICE_AND_API_INIT(gd7965, DT_INST_LABEL(0), gd7965_init,
|
|
&gd7965_driver, NULL,
|
|
POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY,
|
|
&gd7965_driver_api);
|