|  | /* | 
|  | * Copyright (c) 2020 Rohit Gujarathi | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT   sharp_ls0xx | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(ls0xx, CONFIG_DISPLAY_LOG_LEVEL); | 
|  |  | 
|  | #include <string.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/drivers/display.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/drivers/spi.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  |  | 
|  | /* Supports LS012B7DD01, LS012B7DD06, LS013B7DH03, LS013B7DH05 | 
|  | * LS013B7DH06, LS027B7DH01A, LS032B7DD02, LS044Q7DH01 | 
|  | */ | 
|  |  | 
|  | /* Note: | 
|  | * -> high/1 means white, low/0 means black | 
|  | * -> Display expects LSB first | 
|  | */ | 
|  |  | 
|  | #define LS0XX_PANEL_WIDTH   DT_INST_PROP(0, width) | 
|  | #define LS0XX_PANEL_HEIGHT  DT_INST_PROP(0, height) | 
|  |  | 
|  | #define LS0XX_PIXELS_PER_BYTE  8U | 
|  | /* Adding 2 for the line number and dummy byte | 
|  | * line_buf format for each row. | 
|  | * +-------------------+-------------------+----------------+ | 
|  | * | line num (8 bits) | data (WIDTH bits) | dummy (8 bits) | | 
|  | * +-------------------+-------------------+----------------+ | 
|  | */ | 
|  | #define LS0XX_BYTES_PER_LINE  ((LS0XX_PANEL_WIDTH / LS0XX_PIXELS_PER_BYTE) + 2) | 
|  |  | 
|  | #define LS0XX_BIT_WRITECMD    0x01 | 
|  | #define LS0XX_BIT_VCOM        0x02 | 
|  | #define LS0XX_BIT_CLEAR       0x04 | 
|  |  | 
|  | struct ls0xx_config { | 
|  | struct spi_dt_spec bus; | 
|  | #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios) | 
|  | struct gpio_dt_spec disp_en_gpio; | 
|  | #endif | 
|  | #if DT_INST_NODE_HAS_PROP(0, extcomin_gpios) | 
|  | struct gpio_dt_spec extcomin_gpio; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | #if DT_INST_NODE_HAS_PROP(0, extcomin_gpios) | 
|  | /* Driver will handle VCOM toggling */ | 
|  | static void ls0xx_vcom_toggle(void *a, void *b, void *c) | 
|  | { | 
|  | const struct ls0xx_config *config = a; | 
|  |  | 
|  | while (1) { | 
|  | gpio_pin_toggle_dt(&config->extcomin_gpio); | 
|  | k_usleep(3); | 
|  | gpio_pin_toggle_dt(&config->extcomin_gpio); | 
|  | k_msleep(1000 / DT_INST_PROP(0, extcomin_frequency)); | 
|  | } | 
|  | } | 
|  |  | 
|  | K_THREAD_STACK_DEFINE(vcom_toggle_stack, 256); | 
|  | struct k_thread vcom_toggle_thread; | 
|  | #endif | 
|  |  | 
|  | static int ls0xx_blanking_off(const struct device *dev) | 
|  | { | 
|  | #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios) | 
|  | const struct ls0xx_config *config = dev->config; | 
|  |  | 
|  | return gpio_pin_set_dt(&config->disp_en_gpio, 1); | 
|  | #else | 
|  | LOG_WRN("Unsupported"); | 
|  | return -ENOTSUP; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static int ls0xx_blanking_on(const struct device *dev) | 
|  | { | 
|  | #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios) | 
|  | const struct ls0xx_config *config = dev->config; | 
|  |  | 
|  | return gpio_pin_set_dt(&config->disp_en_gpio, 0); | 
|  | #else | 
|  | LOG_WRN("Unsupported"); | 
|  | return -ENOTSUP; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static int ls0xx_cmd(const struct device *dev, uint8_t *buf, uint8_t len) | 
|  | { | 
|  | const struct ls0xx_config *config = dev->config; | 
|  | struct spi_buf cmd_buf = { .buf = buf, .len = len }; | 
|  | struct spi_buf_set buf_set = { .buffers = &cmd_buf, .count = 1 }; | 
|  |  | 
|  | return spi_write_dt(&config->bus, &buf_set); | 
|  | } | 
|  |  | 
|  | static int ls0xx_clear(const struct device *dev) | 
|  | { | 
|  | const struct ls0xx_config *config = dev->config; | 
|  | uint8_t clear_cmd[2] = { LS0XX_BIT_CLEAR, 0 }; | 
|  | int err; | 
|  |  | 
|  | err = ls0xx_cmd(dev, clear_cmd, sizeof(clear_cmd)); | 
|  | spi_release_dt(&config->bus); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int ls0xx_update_display(const struct device *dev, | 
|  | uint16_t start_line, | 
|  | uint16_t num_lines, | 
|  | const uint8_t *data) | 
|  | { | 
|  | const struct ls0xx_config *config = dev->config; | 
|  | uint8_t write_cmd[1] = { LS0XX_BIT_WRITECMD }; | 
|  | uint8_t ln = start_line; | 
|  | uint8_t dummy = 27; | 
|  | struct spi_buf line_buf[3] = { | 
|  | { | 
|  | .len = sizeof(ln), | 
|  | .buf = &ln, | 
|  | }, | 
|  | { | 
|  | .len = LS0XX_BYTES_PER_LINE - 2, | 
|  | }, | 
|  | { | 
|  | .len = sizeof(dummy), | 
|  | .buf = &dummy, | 
|  | }, | 
|  | }; | 
|  | struct spi_buf_set line_set = { | 
|  | .buffers = line_buf, | 
|  | .count = ARRAY_SIZE(line_buf), | 
|  | }; | 
|  | int err; | 
|  |  | 
|  | LOG_DBG("Lines %d to %d", start_line, start_line + num_lines - 1); | 
|  | err = ls0xx_cmd(dev, write_cmd, sizeof(write_cmd)); | 
|  |  | 
|  | /* Send each line to the screen including | 
|  | * the line number and dummy bits | 
|  | */ | 
|  | for (; ln <= start_line + num_lines - 1; ln++) { | 
|  | line_buf[1].buf = (uint8_t *)data; | 
|  | err |= spi_write_dt(&config->bus, &line_set); | 
|  | data += LS0XX_PANEL_WIDTH / LS0XX_PIXELS_PER_BYTE; | 
|  | } | 
|  |  | 
|  | /* Send another trailing 8 bits for the last line | 
|  | * These can be any bits, it does not matter | 
|  | * just reusing the write_cmd buffer | 
|  | */ | 
|  | err |= ls0xx_cmd(dev, write_cmd, sizeof(write_cmd)); | 
|  |  | 
|  | spi_release_dt(&config->bus); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* Buffer width should be equal to display width */ | 
|  | static int ls0xx_write(const struct device *dev, const uint16_t x, | 
|  | const uint16_t y, | 
|  | const struct display_buffer_descriptor *desc, | 
|  | const void *buf) | 
|  | { | 
|  | LOG_DBG("X: %d, Y: %d, W: %d, H: %d", x, y, desc->width, desc->height); | 
|  |  | 
|  | if (buf == NULL) { | 
|  | LOG_WRN("Display buffer is not available"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (desc->width != LS0XX_PANEL_WIDTH) { | 
|  | LOG_ERR("Width not a multiple of %d", LS0XX_PANEL_WIDTH); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (desc->pitch != desc->width) { | 
|  | LOG_ERR("Unsupported mode"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if ((y + desc->height) > LS0XX_PANEL_HEIGHT) { | 
|  | LOG_ERR("Buffer out of bounds (height)"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (x != 0) { | 
|  | LOG_ERR("X-coordinate has to be 0"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Adding 1 since line numbering on the display starts with 1 */ | 
|  | return ls0xx_update_display(dev, y + 1, desc->height, buf); | 
|  | } | 
|  |  | 
|  | static int ls0xx_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 *ls0xx_get_framebuffer(const struct device *dev) | 
|  | { | 
|  | LOG_ERR("not supported"); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static int ls0xx_set_brightness(const struct device *dev, | 
|  | const uint8_t brightness) | 
|  | { | 
|  | LOG_WRN("not supported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static int ls0xx_set_contrast(const struct device *dev, uint8_t contrast) | 
|  | { | 
|  | LOG_WRN("not supported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static void ls0xx_get_capabilities(const struct device *dev, | 
|  | struct display_capabilities *caps) | 
|  | { | 
|  | memset(caps, 0, sizeof(struct display_capabilities)); | 
|  | caps->x_resolution = LS0XX_PANEL_WIDTH; | 
|  | caps->y_resolution = LS0XX_PANEL_HEIGHT; | 
|  | caps->supported_pixel_formats = PIXEL_FORMAT_MONO01; | 
|  | caps->current_pixel_format = PIXEL_FORMAT_MONO01; | 
|  | caps->screen_info = SCREEN_INFO_X_ALIGNMENT_WIDTH; | 
|  | } | 
|  |  | 
|  | static int ls0xx_set_orientation(const struct device *dev, | 
|  | const enum display_orientation orientation) | 
|  | { | 
|  | LOG_ERR("Unsupported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static int ls0xx_set_pixel_format(const struct device *dev, | 
|  | const enum display_pixel_format pf) | 
|  | { | 
|  | if (pf == PIXEL_FORMAT_MONO01) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | LOG_ERR("not supported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static int ls0xx_init(const struct device *dev) | 
|  | { | 
|  | const struct ls0xx_config *config = dev->config; | 
|  |  | 
|  | if (!spi_is_ready_dt(&config->bus)) { | 
|  | LOG_ERR("SPI bus %s not ready", config->bus.bus->name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios) | 
|  | if (!gpio_is_ready_dt(&config->disp_en_gpio)) { | 
|  | LOG_ERR("DISP port device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  | LOG_INF("Configuring DISP pin to OUTPUT_HIGH"); | 
|  | gpio_pin_configure_dt(&config->disp_en_gpio, GPIO_OUTPUT_HIGH); | 
|  | #endif | 
|  |  | 
|  | #if DT_INST_NODE_HAS_PROP(0, extcomin_gpios) | 
|  | if (!gpio_is_ready_dt(&config->extcomin_gpio)) { | 
|  | LOG_ERR("EXTCOMIN port device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  | LOG_INF("Configuring EXTCOMIN pin"); | 
|  | gpio_pin_configure_dt(&config->extcomin_gpio, GPIO_OUTPUT_LOW); | 
|  |  | 
|  | /* Start thread for toggling VCOM */ | 
|  | k_tid_t vcom_toggle_tid = k_thread_create(&vcom_toggle_thread, | 
|  | vcom_toggle_stack, | 
|  | K_THREAD_STACK_SIZEOF(vcom_toggle_stack), | 
|  | ls0xx_vcom_toggle, | 
|  | (void *)config, NULL, NULL, | 
|  | 3, 0, K_NO_WAIT); | 
|  | k_thread_name_set(vcom_toggle_tid, "ls0xx_vcom"); | 
|  | #endif  /* DT_INST_NODE_HAS_PROP(0, extcomin_gpios) */ | 
|  |  | 
|  | /* Clear display else it shows random data */ | 
|  | return ls0xx_clear(dev); | 
|  | } | 
|  |  | 
|  | static const struct ls0xx_config ls0xx_config = { | 
|  | .bus = SPI_DT_SPEC_INST_GET( | 
|  | 0, SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | | 
|  | SPI_TRANSFER_LSB | SPI_CS_ACTIVE_HIGH | | 
|  | SPI_HOLD_ON_CS | SPI_LOCK_ON, 0), | 
|  | #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios) | 
|  | .disp_en_gpio = GPIO_DT_SPEC_INST_GET(0, disp_en_gpios), | 
|  | #endif | 
|  | #if DT_INST_NODE_HAS_PROP(0, extcomin_gpios) | 
|  | .extcomin_gpio = GPIO_DT_SPEC_INST_GET(0, extcomin_gpios), | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | static struct display_driver_api ls0xx_driver_api = { | 
|  | .blanking_on = ls0xx_blanking_on, | 
|  | .blanking_off = ls0xx_blanking_off, | 
|  | .write = ls0xx_write, | 
|  | .read = ls0xx_read, | 
|  | .get_framebuffer = ls0xx_get_framebuffer, | 
|  | .set_brightness = ls0xx_set_brightness, | 
|  | .set_contrast = ls0xx_set_contrast, | 
|  | .get_capabilities = ls0xx_get_capabilities, | 
|  | .set_pixel_format = ls0xx_set_pixel_format, | 
|  | .set_orientation = ls0xx_set_orientation, | 
|  | }; | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, ls0xx_init, NULL, NULL, &ls0xx_config, POST_KERNEL, | 
|  | CONFIG_DISPLAY_INIT_PRIORITY, &ls0xx_driver_api); |