text: /*
   * Copyright (c) 2018 PHYTEC Messtechnik GmbH
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  
  #include <stdint.h>
  
  #include "os/mynewt.h"
  #include "hal/hal_gpio.h"
  #include "hal/hal_spi.h"
  #include "modlog/modlog.h"
  
  #include "display/display.h"
  #include "display/cfb.h"
  
  #include "ssd1673_regs.h"
  
  #define EPD_PANEL_WIDTH			250
  #define EPD_PANEL_HEIGHT		120
  #define EPD_PANEL_NUMOF_COLUMS		250
  #define EPD_PANEL_NUMOF_ROWS_PER_PAGE	8
  #define EPD_PANEL_NUMOF_PAGES		(EPD_PANEL_HEIGHT / \
  					 EPD_PANEL_NUMOF_ROWS_PER_PAGE)
  
  #define SSD1673_PANEL_FIRST_PAGE	0
  #define SSD1673_PANEL_LAST_PAGE		(EPD_PANEL_NUMOF_PAGES - 1)
  #define SSD1673_PANEL_FIRST_GATE	0
  #define SSD1673_PANEL_LAST_GATE		249
  
  struct ssd1673_data {
  	struct display_driver_api driver_api;
  	struct hal_spi_settings spi_config;
  	uint8_t contrast;
  	uint8_t scan_mode;
  	uint8_t last_lut;
  	uint8_t numof_part_cycles;
  };
  
  static struct ssd1673_data ssd1673_driver;
  static struct os_dev ssd1673;
  
  #define SSD1673_LAST_LUT_INITIAL		0
  #define SSD1673_LAST_LUT_DEFAULT		255
  #define SSD1673_LUT_SIZE			29
  
  static uint8_t ssd1673_lut_initial[SSD1673_LUT_SIZE] = {
  	0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
  	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  	0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
  	0x01, 0x00, 0x00, 0x00, 0x00
  };
  
  static uint8_t ssd1673_lut_default[SSD1673_LUT_SIZE] = {
  	0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  	0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  	0x00, 0x00, 0x00, 0x00, 0x00
  };
  
  #define SSD1673_BUSY_DELAY_TICKS os_time_ms_to_ticks32(SSD1673_BUSY_DELAY)
  #define SSD1673_RESET_DELAY_TICKS os_time_ms_to_ticks32(SSD1673_RESET_DELAY)
  
  #define CONFIG_SSD1673_OS_DEV_NAME	MYNEWT_VAL(SSD1673_OS_DEV_NAME)
  #define CONFIG_SSD1673_BUSY_PIN		MYNEWT_VAL(SSD1673_BUSY_PIN)
  #define CONFIG_SSD1673_RESET_PIN	MYNEWT_VAL(SSD1673_RESET_PIN)
  #define CONFIG_SSD1673_DC_PIN		MYNEWT_VAL(SSD1673_DC_PIN)
  #define CONFIG_SSD1673_CS_PIN		MYNEWT_VAL(SSD1673_CS_PIN)
  #define CONFIG_SSD1673_SPI_FREQ		MYNEWT_VAL(SSD1673_SPI_FREQ)
  #define CONFIG_SSD1673_SPI_DEV		MYNEWT_VAL(SSD1673_SPI_DEV)
  
  static inline int ssd1673_write_cmd(struct ssd1673_data *driver,
  				    uint8_t cmd, uint8_t *data, size_t len)
  {
  	hal_gpio_write(CONFIG_SSD1673_DC_PIN, 0);
  	hal_gpio_write(CONFIG_SSD1673_CS_PIN, 0);
  
  	if (hal_spi_txrx(CONFIG_SSD1673_SPI_DEV,
  			 &cmd, NULL, sizeof(cmd))) {
  		hal_gpio_write(CONFIG_SSD1673_CS_PIN, 1);
  		return -1;
  	}
  
  	if (data != NULL) {
  		hal_gpio_write(CONFIG_SSD1673_DC_PIN, 1);
  		if (hal_spi_txrx(CONFIG_SSD1673_SPI_DEV,
  				 data, NULL, len)) {
  			hal_gpio_write(CONFIG_SSD1673_CS_PIN, 1);
  			return -1;
  		}
  	}
  
  	hal_gpio_write(CONFIG_SSD1673_CS_PIN, 1);
  	return 0;
  }
  
  static inline void ssd1673_busy_wait(struct ssd1673_data *driver)
  {
  	int val = 0;
  
  	val = hal_gpio_read(CONFIG_SSD1673_BUSY_PIN);
  	while (val) {
  		os_cputime_delay_ticks(SSD1673_BUSY_DELAY_TICKS);
  		val = hal_gpio_read(CONFIG_SSD1673_BUSY_PIN);
  	};
  }
  
  static inline int ssd1673_set_ram_param(struct ssd1673_data *driver,
  					uint8_t sx, uint8_t ex,
  					uint8_t sy, uint8_t ey)
  {
  	uint8_t tmp[2];
  
  	tmp[0] = sx; tmp[1] = ex;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_RAM_XPOS_CTRL,
  			      tmp, sizeof(tmp))) {
  		return -1;
  	}
  
  	tmp[0] = sy; tmp[1] = ey;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_RAM_YPOS_CTRL,
  			      tmp, sizeof(tmp))) {
  		return -1;
  	}
  
  	return 0;
  }
  
  static inline int ssd1673_set_ram_ptr(struct ssd1673_data *driver,
  				       uint8_t x, uint8_t y)
  {
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_RAM_XPOS_CNTR,
  			      &x, sizeof(x))) {
  		return -1;
  	}
  
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_RAM_YPOS_CNTR,
  			      &y, sizeof(y))) {
  		return -1;
  	}
  
  	return 0;
  }
  
  static inline void ssd1673_set_orientation(struct ssd1673_data *driver)
  
  {
  #if CONFIG_SSD1673_ORIENTATION_FLIPPED == 1
  	driver->scan_mode = SSD1673_DATA_ENTRY_XIYDY;
  #else
  	driver->scan_mode = SSD1673_DATA_ENTRY_XDYIY;
  #endif
  }
  
  int ssd1673_resume(const struct os_dev *dev)
  {
  	struct ssd1673_data *driver = dev->od_init_arg;
  	uint8_t tmp;
  
  	/*
  	 * Uncomment for voltage measurement
  	 * tmp = SSD1673_CTRL2_ENABLE_CLK;
  	 * ssd1673_write_cmd(SSD1673_CMD_UPDATE_CTRL2, &tmp, sizeof(tmp));
  	 * ssd1673_write_cmd(SSD1673_CMD_MASTER_ACTIVATION, NULL, 0);
  	 */
  
  	tmp = SSD1673_SLEEP_MODE_PON;
  	return ssd1673_write_cmd(driver, SSD1673_CMD_SLEEP_MODE,
  				 &tmp, sizeof(tmp));
  }
  
  static int ssd1673_suspend(const struct os_dev *dev)
  {
  	struct ssd1673_data *driver = dev->od_init_arg;
  	uint8_t tmp = SSD1673_SLEEP_MODE_DSM;
  
  	return ssd1673_write_cmd(driver, SSD1673_CMD_SLEEP_MODE,
  				 &tmp, sizeof(tmp));
  }
  
  static int ssd1673_update_display(const struct os_dev *dev, bool initial)
  {
  	struct ssd1673_data *driver = dev->od_init_arg;
  	uint8_t tmp;
  
  	tmp = SSD1673_CTRL1_INITIAL_UPDATE_LH;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_UPDATE_CTRL1,
  			      &tmp, sizeof(tmp))) {
  		return -1;
  	}
  
  	if (initial) {
  		driver->numof_part_cycles = 0;
  		driver->last_lut = SSD1673_LAST_LUT_INITIAL;
  		if (ssd1673_write_cmd(driver, SSD1673_CMD_UPDATE_LUT,
  				      ssd1673_lut_initial,
  				      sizeof(ssd1673_lut_initial))) {
  			return -1;
  		}
  
  	} else {
  		driver->numof_part_cycles++;
  		if (driver->last_lut != SSD1673_LAST_LUT_DEFAULT) {
  			driver->last_lut = SSD1673_LAST_LUT_DEFAULT;
  			if (ssd1673_write_cmd(driver, SSD1673_CMD_UPDATE_LUT,
  					      ssd1673_lut_default,
  					      sizeof(ssd1673_lut_default))) {
  				return -1;
  			}
  		}
  	}
  
  	tmp = (SSD1673_CTRL2_ENABLE_CLK |
  	       SSD1673_CTRL2_ENABLE_ANALOG |
  	       SSD1673_CTRL2_TO_PATTERN |
  	       SSD1673_CTRL2_DISABLE_ANALOG |
  	       SSD1673_CTRL2_DISABLE_CLK);
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_UPDATE_CTRL2,
  			      &tmp, sizeof(tmp))) {
  		return -1;
  	}
  
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_MASTER_ACTIVATION,
  			      NULL, 0)) {
  		return -1;
  	}
  
  	return 0;
  }
  
  static int ssd1673_write(const struct os_dev *dev, const uint16_t x,
  			 const uint16_t y,
  			 const struct display_buffer_descriptor *desc,
  			 const void *buf)
  {
  	struct ssd1673_data *driver = dev->od_init_arg;
  	uint8_t cmd = SSD1673_CMD_WRITE_RAM;
  	uint8_t dummy_page[SSD1673_RAM_YRES];
  	bool update = true;
  
  	if (desc->pitch < desc->width) {
  		DFLT_LOG_ERROR("Pitch is smaller then width");
  		return -1;
  	}
  
  	if (buf == NULL || desc->buf_size == 0) {
  		DFLT_LOG_ERROR("Display buffer is not available");
  		return -1;
  	}
  
  	if (desc->pitch > desc->width) {
  		DFLT_LOG_ERROR("Unsupported mode");
  		return -1;
  	}
  
  	if (x != 0 && y != 0) {
  		DFLT_LOG_ERROR("Unsupported origin");
  		return -1;
  	}
  
  
  	ssd1673_busy_wait(driver);
  	memset(dummy_page, 0xff, sizeof(dummy_page));
  
  	switch (driver->scan_mode) {
  	case SSD1673_DATA_ENTRY_XIYDY:
  		if (ssd1673_set_ram_param(driver,
  					  SSD1673_PANEL_FIRST_PAGE,
  					  SSD1673_PANEL_LAST_PAGE + 1,
  					  SSD1673_PANEL_LAST_GATE,
  					  SSD1673_PANEL_FIRST_GATE)) {
  			return -1;
  		}
  
  		if (ssd1673_set_ram_ptr(driver,
  					SSD1673_PANEL_FIRST_PAGE,
  					SSD1673_PANEL_LAST_GATE)) {
  			return -1;
  		}
  
  		break;
  
  	case SSD1673_DATA_ENTRY_XDYIY:
  		if (ssd1673_set_ram_param(driver,
  					  SSD1673_PANEL_LAST_PAGE + 1,
  					  SSD1673_PANEL_FIRST_PAGE,
  					  SSD1673_PANEL_FIRST_GATE,
  					  SSD1673_PANEL_LAST_GATE)) {
  			return -1;
  		}
  
  		if (ssd1673_set_ram_ptr(driver,
  					SSD1673_PANEL_LAST_PAGE + 1,
  					SSD1673_PANEL_FIRST_GATE)) {
  			return -1;
  		}
  
  		break;
  	default:
  		return -1;
  	}
  
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_ENTRY_MODE,
  			      &driver->scan_mode, sizeof(driver->scan_mode))) {
  		return -1;
  	}
  
  	hal_gpio_write(CONFIG_SSD1673_DC_PIN, 0);
  	hal_gpio_write(CONFIG_SSD1673_CS_PIN, 0);
  
  	if (hal_spi_txrx(CONFIG_SSD1673_SPI_DEV, &cmd, NULL, sizeof(cmd))) {
  		hal_gpio_write(CONFIG_SSD1673_CS_PIN, 1);
  		return -1;
  	}
  
  	hal_gpio_write(CONFIG_SSD1673_DC_PIN, 1);
  	/* clear unusable page */
  	if (driver->scan_mode == SSD1673_DATA_ENTRY_XDYIY) {
  		if (hal_spi_txrx(CONFIG_SSD1673_SPI_DEV, dummy_page,
  				 NULL, sizeof(dummy_page))) {
  			hal_gpio_write(CONFIG_SSD1673_CS_PIN, 1);
  			return -1;
  		}
  	}
  
  	if (hal_spi_txrx(CONFIG_SSD1673_SPI_DEV, (uint8_t *)buf,
  			 NULL, desc->buf_size)) {
  		hal_gpio_write(CONFIG_SSD1673_CS_PIN, 1);
  		return -1;
  	}
  
  	/* clear unusable page */
  	if (driver->scan_mode == SSD1673_DATA_ENTRY_XIYDY) {
  		if (hal_spi_txrx(CONFIG_SSD1673_SPI_DEV, dummy_page,
  				 NULL, sizeof(dummy_page))) {
  			hal_gpio_write(CONFIG_SSD1673_CS_PIN, 1);
  			return -1;
  		}
  	}
  
  	hal_gpio_write(CONFIG_SSD1673_CS_PIN, 1);
  
  	if (update) {
  		if (driver->contrast) {
  			return ssd1673_update_display(dev, true);
  		}
  		return ssd1673_update_display(dev, false);
  	}
  
  	return 0;
  }
  
  static int ssd1673_read(const struct os_dev *dev, const uint16_t x,
  			const uint16_t y,
  			const struct display_buffer_descriptor *desc,
  			void *buf)
  {
  	DFLT_LOG_ERROR("not supported");
  	return -1;
  }
  
  static void *ssd1673_get_framebuffer(const struct os_dev *dev)
  {
  	DFLT_LOG_ERROR("not supported");
  	return NULL;
  }
  
  static int ssd1673_set_brightness(const struct os_dev *dev,
  				  const uint8_t brightness)
  {
  	DFLT_LOG_WARN("not supported");
  	return -1;
  }
  
  static int ssd1673_set_contrast(const struct os_dev *dev, uint8_t contrast)
  {
  	struct ssd1673_data *driver = dev->od_init_arg;
  
  	driver->contrast = contrast;
  
  	return 0;
  }
  
  static void ssd1673_get_capabilities(const struct os_dev *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_VTILED |
  			    SCREEN_INFO_MONO_MSB_FIRST |
  			    SCREEN_INFO_EPD;
  }
  
  static int ssd1673_set_pixel_format(const struct os_dev *dev,
  				    const enum display_pixel_format pf)
  {
  	DFLT_LOG_ERROR("not supported");
  	return -1;
  }
  
  static int ssd1673_controller_init(struct os_dev *dev)
  {
  	struct ssd1673_data *driver = dev->od_init_arg;
  	uint8_t tmp[3];
  
  	DFLT_LOG_DEBUG("");
  
  	hal_gpio_write(CONFIG_SSD1673_RESET_PIN, 0);
  	os_time_delay(SSD1673_RESET_DELAY_TICKS);
  	hal_gpio_write(CONFIG_SSD1673_RESET_PIN, 1);
  	os_time_delay(SSD1673_RESET_DELAY_TICKS);
  	ssd1673_busy_wait(driver);
  
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_SW_RESET, NULL, 0)) {
  		return -1;
  	}
  	ssd1673_busy_wait(driver);
  
  	tmp[0] = (SSD1673_RAM_YRES - 1);
  	tmp[1] = 0;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_GDO_CTRL, tmp, 2)) {
  		return -1;
  	}
  
  	tmp[0] = SSD1673_VAL_GDV_CTRL_A;
  	tmp[1] = SSD1673_VAL_GDV_CTRL_B;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_GDV_CTRL, tmp, 2)) {
  		return -1;
  	}
  
  	tmp[0] = SSD1673_VAL_SDV_CTRL;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_SDV_CTRL, tmp, 1)) {
  		return -1;
  	}
  
  	tmp[0] = SSD1673_VAL_VCOM_VOLTAGE;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_VCOM_VOLTAGE, tmp, 1)) {
  		return -1;
  	}
  
  	tmp[0] = SSD1673_VAL_DUMMY_LINE;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_DUMMY_LINE, tmp, 1)) {
  		return -1;
  	}
  
  	tmp[0] = SSD1673_VAL_GATE_LWIDTH;
  	if (ssd1673_write_cmd(driver, SSD1673_CMD_GATE_LINE_WIDTH, tmp, 1)) {
  		return -1;
  	}
  
  	ssd1673_set_orientation(driver);
  	driver->numof_part_cycles = 0;
  	driver->last_lut = SSD1673_LAST_LUT_INITIAL;
  	driver->contrast = 0;
  
  	return 0;
  }
  
  static int ssd1673_init(struct os_dev *dev, void *arg)
  {
  	struct ssd1673_data *driver = dev->od_init_arg;
  	int rc;
  
  	DFLT_LOG_DEBUG("");
  
  	driver->spi_config.baudrate = CONFIG_SSD1673_SPI_FREQ;
  	driver->spi_config.data_mode = HAL_SPI_MODE0;
  	driver->spi_config.data_order = HAL_SPI_MSB_FIRST;
  	driver->spi_config.word_size = HAL_SPI_WORD_SIZE_8BIT;
  	rc = hal_spi_config(0, &driver->spi_config);
  	assert(rc == 0);
  
  	driver->driver_api.blanking_on = ssd1673_resume,
  	driver->driver_api.blanking_off = ssd1673_suspend,
  	driver->driver_api.write = ssd1673_write,
  	driver->driver_api.read = ssd1673_read,
  	driver->driver_api.get_framebuffer = ssd1673_get_framebuffer,
  	driver->driver_api.set_brightness = ssd1673_set_brightness,
  	driver->driver_api.set_contrast = ssd1673_set_contrast,
  	driver->driver_api.get_capabilities = ssd1673_get_capabilities,
  	driver->driver_api.set_pixel_format = ssd1673_set_pixel_format,
  	driver->driver_api.set_orientation = NULL,
  
  	rc = hal_gpio_init_out(CONFIG_SSD1673_RESET_PIN, 1);
  	assert(rc == 0);
  	rc = hal_gpio_init_out(CONFIG_SSD1673_DC_PIN, 1);
  	assert(rc == 0);
  	rc = hal_gpio_init_out(CONFIG_SSD1673_CS_PIN, 1);
  	assert(rc == 0);
  	rc = hal_gpio_init_in(CONFIG_SSD1673_BUSY_PIN, HAL_GPIO_PULL_NONE);
  	assert(rc == 0);
  
  	ssd1673_controller_init(dev);
  
  	return 0;
  }
  
  int ssd1673_pkg_init(void)
  {
  	int rc;
  
  	/* Ensure this function only gets called by sysinit. */
  	SYSINIT_ASSERT_ACTIVE();
  
  	rc = os_dev_create(&ssd1673, CONFIG_SSD1673_OS_DEV_NAME,
  			   OS_DEV_INIT_SECONDARY, OS_DEV_INIT_PRIO_DEFAULT,
  			   ssd1673_init, &ssd1673_driver);
  	SYSINIT_PANIC_ASSERT(rc == 0);
  
  	return 0;
  }
  