zephyr/drivers/display/gd7965.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 {
const struct device *reset;
const struct device *dc;
const struct device *busy;
const 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->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->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->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(const 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(const struct device *dev)
{
struct gd7965_data *driver = dev->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(const struct device *dev)
{
struct gd7965_data *driver = dev->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_DT_INST_DEFINE(0, gd7965_init, device_pm_control_nop,
&gd7965_driver, NULL,
POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY,
&gd7965_driver_api);