zephyr/drivers/display/gd7965.c
Daniel Leung 8b8e8f99ba display: introduce CONFIG_DISPLAY_INIT_PRIORITY
This introduces a new kconfig CONFIG_DISPLAY_INIT_PRIORITY
to specify the initialization priority for display devices.
Most of the display devices are using APPLICATION and
CONFIG_APPLICATION_INIT_PRIORITY which is not entirely
appropriate for devices. Due to linking order, the display
device may be initialized after application code at same
init level and priority. This results in the display device
not ready to be used for application code. So this kconfig
option allows the display devices to be initialized earlier
if needed.

For the drivers using CONFIG_APPLICATION_INIT_PRIORITY,
they have been changed to use CONFIG_DISPLAY_INIT_PRIORITY
instead.

Note that the default value for CONFIG_DISPLAY_INIT_PRIORITY
is the same as CONFIG_APPLICATION_INIT_PRIORITY at 90 to
avoid any functional changes.

Signed-off-by: Daniel Leung <daniel.leung@intel.com>
2021-08-12 17:00:39 +02:00

455 lines
11 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_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_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 gd7965_config *config;
const struct device *reset;
const struct device *dc;
const struct device *busy;
};
struct gd7965_config {
struct spi_dt_spec bus;
};
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_dt(&driver->config->bus, &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_dt(&driver->config->bus, &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)
{
const struct gd7965_config *config = dev->config;
struct gd7965_data *driver = dev->data;
LOG_DBG("");
if (!spi_is_ready(&config->bus)) {
LOG_ERR("SPI bus %s not ready", config->bus.bus->name);
return -ENODEV;
}
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);
return gd7965_controller_init(dev);
}
static struct gd7965_data gd7965_driver = {
.config = &gd7965_config
};
static const struct gd7965_config gd7965_config {
.bus = SPI_DT_SPEC_INST_GET(
0, SPI_OP_MODE_MASTER | SPI_WORD_SET(8), 0)
};
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, NULL,
&gd7965_driver, &gd7965_config,
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY,
&gd7965_driver_api);