|  | /* | 
|  | * Copyright (c) 2022 Andreas Sandberg | 
|  | * Copyright (c) 2020 PHYTEC Messtechnik GmbH | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <string.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <zephyr/drivers/display.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/drivers/spi.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  |  | 
|  | #include "uc81xx_regs.h" | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(uc81xx, CONFIG_DISPLAY_LOG_LEVEL); | 
|  |  | 
|  | /** | 
|  | * UC81XX compatible EPD controller driver. | 
|  | * | 
|  | * Currently only the black/white panels are supported (KW mode), | 
|  | * also first gate/source should be 0. | 
|  | */ | 
|  |  | 
|  | #define UC81XX_PIXELS_PER_BYTE		8U | 
|  |  | 
|  | struct uc81xx_quirks { | 
|  | uint16_t max_width; | 
|  | uint16_t max_height; | 
|  |  | 
|  | int (*set_cdi)(const struct device *dev, bool border); | 
|  | }; | 
|  |  | 
|  | struct uc81xx_dt_array { | 
|  | uint8_t *data; | 
|  | uint8_t len; | 
|  | }; | 
|  |  | 
|  | struct uc81xx_config { | 
|  | const struct uc81xx_quirks *quirks; | 
|  |  | 
|  | struct spi_dt_spec bus; | 
|  | struct gpio_dt_spec dc_gpio; | 
|  | struct gpio_dt_spec busy_gpio; | 
|  | struct gpio_dt_spec reset_gpio; | 
|  |  | 
|  | uint16_t height; | 
|  | uint16_t width; | 
|  |  | 
|  | uint8_t cdi; | 
|  | bool override_cdi; | 
|  | uint8_t tcon; | 
|  | bool override_tcon; | 
|  | struct uc81xx_dt_array softstart; | 
|  | struct uc81xx_dt_array pwr; | 
|  | }; | 
|  |  | 
|  | struct uc81xx_data { | 
|  | bool blanking_on; | 
|  | }; | 
|  |  | 
|  | static inline int uc81xx_write_cmd(const struct device *dev, uint8_t cmd, | 
|  | const uint8_t *data, size_t len) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  | struct spi_buf buf = {.buf = &cmd, .len = sizeof(cmd)}; | 
|  | struct spi_buf_set buf_set = {.buffers = &buf, .count = 1}; | 
|  | int err; | 
|  |  | 
|  | err = gpio_pin_set_dt(&config->dc_gpio, 1); | 
|  | if (err < 0) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | err = spi_write_dt(&config->bus, &buf_set); | 
|  | if (err < 0) { | 
|  | goto spi_out; | 
|  | } | 
|  |  | 
|  | if (data != NULL) { | 
|  | buf.buf = (void *)data; | 
|  | buf.len = len; | 
|  |  | 
|  | err = gpio_pin_set_dt(&config->dc_gpio, 0); | 
|  | if (err < 0) { | 
|  | goto spi_out; | 
|  | } | 
|  |  | 
|  | err = spi_write_dt(&config->bus, &buf_set); | 
|  | if (err < 0) { | 
|  | goto spi_out; | 
|  | } | 
|  | } | 
|  |  | 
|  | spi_out: | 
|  | spi_release_dt(&config->bus); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static inline int uc81xx_write_cmd_pattern(const struct device *dev, | 
|  | uint8_t cmd, | 
|  | uint8_t pattern, size_t len) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  | struct spi_buf buf = {.buf = &cmd, .len = sizeof(cmd)}; | 
|  | struct spi_buf_set buf_set = {.buffers = &buf, .count = 1}; | 
|  | int err; | 
|  | uint8_t data[64]; | 
|  |  | 
|  | err = gpio_pin_set_dt(&config->dc_gpio, 1); | 
|  | if (err < 0) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | err = spi_write_dt(&config->bus, &buf_set); | 
|  | if (err < 0) { | 
|  | goto spi_out; | 
|  | } | 
|  |  | 
|  | err = gpio_pin_set_dt(&config->dc_gpio, 0); | 
|  | if (err < 0) { | 
|  | goto spi_out; | 
|  | } | 
|  |  | 
|  | memset(data, pattern, sizeof(data)); | 
|  | while (len) { | 
|  | buf.buf = data; | 
|  | buf.len = MIN(len, sizeof(data)); | 
|  |  | 
|  | err = spi_write_dt(&config->bus, &buf_set); | 
|  | if (err < 0) { | 
|  | goto spi_out; | 
|  | } | 
|  |  | 
|  | len -= buf.len; | 
|  | } | 
|  |  | 
|  | spi_out: | 
|  | spi_release_dt(&config->bus); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static inline int uc81xx_write_cmd_uint8(const struct device *dev, uint8_t cmd, | 
|  | uint8_t data) | 
|  | { | 
|  | return uc81xx_write_cmd(dev, cmd, &data, 1); | 
|  | } | 
|  |  | 
|  | static inline int uc81xx_write_array_opt(const struct device *dev, uint8_t cmd, | 
|  | const struct uc81xx_dt_array *array) | 
|  | { | 
|  | if (array->len && array->data) { | 
|  | return uc81xx_write_cmd(dev, cmd, array->data, array->len); | 
|  | } else { | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | static inline void uc81xx_busy_wait(const struct device *dev) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  | int pin = gpio_pin_get_dt(&config->busy_gpio); | 
|  |  | 
|  | while (pin > 0) { | 
|  | __ASSERT(pin >= 0, "Failed to get pin level"); | 
|  | LOG_DBG("wait %u", pin); | 
|  | k_sleep(K_MSEC(UC81XX_BUSY_DELAY)); | 
|  | pin = gpio_pin_get_dt(&config->busy_gpio); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int uc81xx_update_display(const struct device *dev) | 
|  | { | 
|  | LOG_DBG("Trigger update sequence"); | 
|  |  | 
|  | /* Turn on: booster, controller, regulators, and sensor. */ | 
|  | if (uc81xx_write_cmd(dev, UC81XX_CMD_PON, NULL, 0)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | k_sleep(K_MSEC(UC81XX_PON_DELAY)); | 
|  | uc81xx_busy_wait(dev); | 
|  |  | 
|  | if (uc81xx_write_cmd(dev, UC81XX_CMD_DRF, NULL, 0)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | k_sleep(K_MSEC(UC81XX_BUSY_DELAY)); | 
|  | uc81xx_busy_wait(dev); | 
|  |  | 
|  | /* Turn on: booster, controller, regulators, and sensor. */ | 
|  | if (uc81xx_write_cmd(dev, UC81XX_CMD_POF, NULL, 0)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int uc81xx_blanking_off(const struct device *dev) | 
|  | { | 
|  | struct uc81xx_data *data = dev->data; | 
|  |  | 
|  | if (data->blanking_on) { | 
|  | /* Update EPD panel in normal mode */ | 
|  | uc81xx_busy_wait(dev); | 
|  | if (uc81xx_update_display(dev)) { | 
|  | return -EIO; | 
|  | } | 
|  | } | 
|  |  | 
|  | data->blanking_on = false; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int uc81xx_blanking_on(const struct device *dev) | 
|  | { | 
|  | struct uc81xx_data *data = dev->data; | 
|  |  | 
|  | data->blanking_on = true; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int uc81xx_write(const struct device *dev, const uint16_t x, const uint16_t y, | 
|  | const struct display_buffer_descriptor *desc, | 
|  | const void *buf) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  | struct uc81xx_data *data = dev->data; | 
|  |  | 
|  | uint16_t x_end_idx = x + desc->width - 1; | 
|  | uint16_t y_end_idx = y + desc->height - 1; | 
|  | const struct uc81xx_ptl ptl = { | 
|  | .hrst = sys_cpu_to_be16(x), | 
|  | .hred = sys_cpu_to_be16(x_end_idx), | 
|  | .vrst = sys_cpu_to_be16(y), | 
|  | .vred = sys_cpu_to_be16(y_end_idx), | 
|  | .flags = UC81XX_PTL_FLAG_PT_SCAN, | 
|  | }; | 
|  | 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 / UC81XX_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 % UC81XX_PIXELS_PER_BYTE), | 
|  | "Buffer width not multiple of %d", UC81XX_PIXELS_PER_BYTE); | 
|  |  | 
|  | if ((y_end_idx > (config->height - 1)) || | 
|  | (x_end_idx > (config->width - 1))) { | 
|  | LOG_ERR("Position out of bounds"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Setup Partial Window and enable Partial Mode */ | 
|  | LOG_HEXDUMP_DBG(&ptl, sizeof(ptl), "ptl"); | 
|  |  | 
|  | uc81xx_busy_wait(dev); | 
|  | if (uc81xx_write_cmd(dev, UC81XX_CMD_PTIN, NULL, 0)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (uc81xx_write_cmd(dev, UC81XX_CMD_PTL, | 
|  | (const void *)&ptl, sizeof(ptl))) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Disable border output */ | 
|  | if (config->quirks->set_cdi(dev, false)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (uc81xx_write_cmd(dev, UC81XX_CMD_DTM2, (uint8_t *)buf, buf_len)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Update partial window and disable Partial Mode */ | 
|  | if (data->blanking_on == false) { | 
|  | if (uc81xx_update_display(dev)) { | 
|  | return -EIO; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Enable border output */ | 
|  | if (config->quirks->set_cdi(dev, true)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (uc81xx_write_cmd(dev, UC81XX_CMD_PTOUT, NULL, 0)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int uc81xx_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 *uc81xx_get_framebuffer(const struct device *dev) | 
|  | { | 
|  | LOG_ERR("not supported"); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static int uc81xx_set_brightness(const struct device *dev, | 
|  | const uint8_t brightness) | 
|  | { | 
|  | LOG_WRN("not supported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static int uc81xx_set_contrast(const struct device *dev, uint8_t contrast) | 
|  | { | 
|  | LOG_WRN("not supported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static void uc81xx_get_capabilities(const struct device *dev, | 
|  | struct display_capabilities *caps) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  |  | 
|  | memset(caps, 0, sizeof(struct display_capabilities)); | 
|  | caps->x_resolution = config->width; | 
|  | caps->y_resolution = config->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 uc81xx_set_orientation(const struct device *dev, | 
|  | const enum display_orientation | 
|  | orientation) | 
|  | { | 
|  | LOG_ERR("Unsupported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static int uc81xx_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 uc81xx_clear_and_write_buffer(const struct device *dev, | 
|  | uint8_t pattern, bool update) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  | const int size = config->width * config->height | 
|  | / UC81XX_PIXELS_PER_BYTE; | 
|  |  | 
|  | if (uc81xx_write_cmd_pattern(dev, UC81XX_CMD_DTM1, pattern, size)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (uc81xx_write_cmd_pattern(dev, UC81XX_CMD_DTM2, pattern, size)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (update == true) { | 
|  | if (uc81xx_update_display(dev)) { | 
|  | return -EIO; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int uc81xx_controller_init(const struct device *dev) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  | struct uc81xx_data *data = dev->data; | 
|  | const uint8_t psr_kw = | 
|  | UC81XX_PSR_KW_R | | 
|  | UC81XX_PSR_UD | | 
|  | UC81XX_PSR_SHL | | 
|  | UC81XX_PSR_SHD | | 
|  | UC81XX_PSR_RST; | 
|  | const struct uc81xx_tres tres = { | 
|  | .hres = sys_cpu_to_be16(config->width), | 
|  | .vres = sys_cpu_to_be16(config->height), | 
|  | }; | 
|  |  | 
|  | data->blanking_on = true; | 
|  |  | 
|  | gpio_pin_set_dt(&config->reset_gpio, 1); | 
|  | k_sleep(K_MSEC(UC81XX_RESET_DELAY)); | 
|  | gpio_pin_set_dt(&config->reset_gpio, 0); | 
|  | k_sleep(K_MSEC(UC81XX_RESET_DELAY)); | 
|  | uc81xx_busy_wait(dev); | 
|  |  | 
|  | LOG_DBG("Initialize UC81XX controller"); | 
|  |  | 
|  | if (uc81xx_write_array_opt(dev, UC81XX_CMD_PWR, &config->pwr)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (uc81xx_write_array_opt(dev, UC81XX_CMD_BTST, &config->softstart)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Panel settings, KW mode */ | 
|  | if (uc81xx_write_cmd_uint8(dev, UC81XX_CMD_PSR, psr_kw)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Set panel resolution */ | 
|  | LOG_HEXDUMP_DBG(&tres, sizeof(tres), "TRES"); | 
|  | if (uc81xx_write_cmd(dev, UC81XX_CMD_TRES, | 
|  | (const void *)&tres, sizeof(tres))) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Set CDI and enable border output */ | 
|  | if (config->quirks->set_cdi(dev, true)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (config->override_tcon) { | 
|  | if (uc81xx_write_cmd_uint8(dev, UC81XX_CMD_TCON, | 
|  | config->tcon)) { | 
|  | return -EIO; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (uc81xx_clear_and_write_buffer(dev, 0xff, false)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int uc81xx_init(const struct device *dev) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  |  | 
|  | LOG_DBG(""); | 
|  |  | 
|  | if (!spi_is_ready(&config->bus)) { | 
|  | LOG_ERR("SPI bus %s not ready", config->bus.bus->name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (!device_is_ready(config->reset_gpio.port)) { | 
|  | LOG_ERR("Reset GPIO device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE); | 
|  |  | 
|  | if (!device_is_ready(config->dc_gpio.port)) { | 
|  | LOG_ERR("DC GPIO device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | gpio_pin_configure_dt(&config->dc_gpio, GPIO_OUTPUT_INACTIVE); | 
|  |  | 
|  |  | 
|  | if (!device_is_ready(config->busy_gpio.port)) { | 
|  | LOG_ERR("Busy GPIO device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | gpio_pin_configure_dt(&config->busy_gpio, GPIO_INPUT); | 
|  |  | 
|  | if (config->width > config->quirks->max_width || | 
|  | config->height > config->quirks->max_height) { | 
|  | LOG_ERR("Display size out of range."); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return uc81xx_controller_init(dev); | 
|  | } | 
|  |  | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(ultrachip_uc8176) | 
|  | static int uc8176_set_cdi(const struct device *dev, bool border) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  | uint8_t cdi = | 
|  | UC8176_CDI_VBD1 | UC8176_CDI_DDX0 | | 
|  | (config->cdi & UC8176_CDI_CDI_MASK); | 
|  |  | 
|  | if (!config->override_cdi) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (!border) { | 
|  | /* Floating border */ | 
|  | cdi |= UC8176_CDI_VBD1 | UC8176_CDI_VBD0; | 
|  | } | 
|  |  | 
|  | LOG_DBG("CDI: %#hhx", cdi); | 
|  | return uc81xx_write_cmd_uint8(dev, UC81XX_CMD_CDI, cdi); | 
|  | } | 
|  |  | 
|  | static const struct uc81xx_quirks uc8176_quirks = { | 
|  | .max_width = 400, | 
|  | .max_height = 300, | 
|  |  | 
|  | .set_cdi = uc8176_set_cdi, | 
|  | }; | 
|  | #endif | 
|  |  | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(ultrachip_uc8179) | 
|  | static int uc8179_set_cdi(const struct device *dev, bool border) | 
|  | { | 
|  | const struct uc81xx_config *config = dev->config; | 
|  | uint8_t cdi[UC8179_CDI_REG_LENGTH] = { | 
|  | UC8179_CDI_BDV1 | UC8179_CDI_N2OCP | UC8179_CDI_DDX0, | 
|  | config->cdi, | 
|  | }; | 
|  |  | 
|  | if (!config->override_cdi) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | cdi[UC8179_CDI_BDZ_DDX_IDX] |= border ? 0 : UC8179_CDI_BDZ; | 
|  |  | 
|  | LOG_HEXDUMP_DBG(cdi, sizeof(cdi), "CDI"); | 
|  | return uc81xx_write_cmd(dev, UC81XX_CMD_CDI, cdi, sizeof(cdi)); | 
|  | } | 
|  |  | 
|  | static const struct uc81xx_quirks uc8179_quirks = { | 
|  | .max_width = 800, | 
|  | .max_height = 600, | 
|  |  | 
|  | .set_cdi = uc8179_set_cdi, | 
|  | }; | 
|  | #endif | 
|  |  | 
|  | static struct display_driver_api uc81xx_driver_api = { | 
|  | .blanking_on = uc81xx_blanking_on, | 
|  | .blanking_off = uc81xx_blanking_off, | 
|  | .write = uc81xx_write, | 
|  | .read = uc81xx_read, | 
|  | .get_framebuffer = uc81xx_get_framebuffer, | 
|  | .set_brightness = uc81xx_set_brightness, | 
|  | .set_contrast = uc81xx_set_contrast, | 
|  | .get_capabilities = uc81xx_get_capabilities, | 
|  | .set_pixel_format = uc81xx_set_pixel_format, | 
|  | .set_orientation = uc81xx_set_orientation, | 
|  | }; | 
|  |  | 
|  | #define UC81XX_MAKE_ARRAY_OPT(n, p)					\ | 
|  | static uint8_t data_ ## n ## _ ## p[] = DT_PROP_OR(n, p, {}) | 
|  |  | 
|  | #define UC81XX_MAKE_ARRAY(n, p)						\ | 
|  | static uint8_t data_ ## n ## _ ## p[] = DT_PROP(n, p) | 
|  |  | 
|  | #define UC81XX_ASSIGN_ARRAY(n, p)					\ | 
|  | {								\ | 
|  | .data = data_ ## n ## _ ## p,				\ | 
|  | .len = sizeof(data_ ## n ## _ ## p),			\ | 
|  | } | 
|  |  | 
|  | #define UC81XX_DEFINE(n, quirks_ptr)					\ | 
|  | UC81XX_MAKE_ARRAY_OPT(n, softstart);				\ | 
|  | UC81XX_MAKE_ARRAY_OPT(n, pwr);					\ | 
|  | \ | 
|  | static const struct uc81xx_config uc81xx_cfg_ ## n = {		\ | 
|  | .quirks = quirks_ptr,					\ | 
|  | .bus = SPI_DT_SPEC_GET(n,				\ | 
|  | SPI_OP_MODE_MASTER | SPI_WORD_SET(8) |		\ | 
|  | SPI_LOCK_ON,					\ | 
|  | 0),						\ | 
|  | .reset_gpio = GPIO_DT_SPEC_GET(n, reset_gpios),		\ | 
|  | .dc_gpio = GPIO_DT_SPEC_GET(n, dc_gpios),		\ | 
|  | .busy_gpio = GPIO_DT_SPEC_GET(n, busy_gpios),		\ | 
|  | \ | 
|  | .height = DT_PROP(n, height),				\ | 
|  | .width = DT_PROP(n, width),				\ | 
|  | \ | 
|  | .cdi = DT_PROP_OR(n, cdi, 0),				\ | 
|  | .override_cdi = DT_NODE_HAS_PROP(n, cdi),		\ | 
|  | .tcon = DT_PROP_OR(n, tcon, 0),				\ | 
|  | .override_tcon = DT_NODE_HAS_PROP(n, tcon),		\ | 
|  | .softstart = UC81XX_ASSIGN_ARRAY(n, softstart),		\ | 
|  | .pwr = UC81XX_ASSIGN_ARRAY(n, pwr),			\ | 
|  | };								\ | 
|  | \ | 
|  | static struct uc81xx_data uc81xx_data_##n = {};			\ | 
|  | \ | 
|  | DEVICE_DT_DEFINE(n, uc81xx_init, NULL,				\ | 
|  | &uc81xx_data_ ## n,				\ | 
|  | &uc81xx_cfg_ ## n,				\ | 
|  | POST_KERNEL,					\ | 
|  | CONFIG_DISPLAY_INIT_PRIORITY,			\ | 
|  | &uc81xx_driver_api); | 
|  |  | 
|  | DT_FOREACH_STATUS_OKAY_VARGS(ultrachip_uc8176, UC81XX_DEFINE, | 
|  | &uc8176_quirks); | 
|  |  | 
|  | DT_FOREACH_STATUS_OKAY_VARGS(ultrachip_uc8179, UC81XX_DEFINE, | 
|  | &uc8179_quirks); |