text: /*
   * 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_AND_API_INIT(gd7965, DT_INST_LABEL(0), gd7965_init,
  		    &gd7965_driver, NULL,
  		    POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY,
  		    &gd7965_driver_api);
  