| /* |
| * Copyright (c) 2022-2023 Jamie McCrae |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT noritake_itron |
| |
| #include <string.h> |
| #include <zephyr/device.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/drivers/auxdisplay.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/uart.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/logging/log.h> |
| #include "auxdisplay_itron.h" |
| |
| LOG_MODULE_REGISTER(auxdisplay_itron, CONFIG_AUXDISPLAY_LOG_LEVEL); |
| |
| /* Display commands */ |
| #define AUXDISPLAY_ITRON_CMD_USER_SETTING 0x1f |
| #define AUXDISPLAY_ITRON_CMD_ESCAPE 0x1b |
| #define AUXDISPLAY_ITRON_CMD_BRIGHTNESS 0x58 |
| #define AUXDISPLAY_ITRON_CMD_DISPLAY_CLEAR 0x0c |
| #define AUXDISPLAY_ITRON_CMD_CURSOR 0x43 |
| #define AUXDISPLAY_ITRON_CMD_CURSOR_SET 0x24 |
| #define AUXDISPLAY_ITRON_CMD_ACTION 0x28 |
| #define AUXDISPLAY_ITRON_CMD_N 0x61 |
| #define AUXDISPLAY_ITRON_CMD_SCREEN_SAVER 0x40 |
| |
| /* Time values when multithreading is disabled */ |
| #define AUXDISPLAY_ITRON_RESET_TIME K_MSEC(2) |
| #define AUXDISPLAY_ITRON_RESET_WAIT_TIME K_MSEC(101) |
| #define AUXDISPLAY_ITRON_BUSY_DELAY_TIME_CHECK K_MSEC(4) |
| #define AUXDISPLAY_ITRON_BUSY_WAIT_LOOPS 125 |
| |
| /* Time values when multithreading is enabled */ |
| #define AUXDISPLAY_ITRON_BUSY_MAX_TIME K_MSEC(500) |
| |
| struct auxdisplay_itron_data { |
| uint16_t character_x; |
| uint16_t character_y; |
| uint8_t brightness; |
| bool powered; |
| #ifdef CONFIG_MULTITHREADING |
| struct k_sem lock_sem; |
| struct k_sem busy_wait_sem; |
| struct gpio_callback busy_wait_callback; |
| #endif |
| }; |
| |
| struct auxdisplay_itron_config { |
| const struct device *uart; |
| struct auxdisplay_capabilities capabilities; |
| struct gpio_dt_spec reset_gpio; |
| struct gpio_dt_spec busy_gpio; |
| }; |
| |
| static int send_cmd(const struct device *dev, const uint8_t *command, uint8_t length, bool pm, |
| bool lock); |
| static int auxdisplay_itron_is_busy(const struct device *dev); |
| static int auxdisplay_itron_clear(const struct device *dev); |
| static int auxdisplay_itron_set_powered(const struct device *dev, bool enabled); |
| |
| #ifdef CONFIG_MULTITHREADING |
| void auxdisplay_itron_busy_gpio_change_callback(const struct device *port, |
| struct gpio_callback *cb, |
| gpio_port_pins_t pins) |
| { |
| struct auxdisplay_itron_data *data = CONTAINER_OF(cb, |
| struct auxdisplay_itron_data, busy_wait_callback); |
| k_sem_give(&data->busy_wait_sem); |
| } |
| #endif |
| |
| static int auxdisplay_itron_init(const struct device *dev) |
| { |
| const struct auxdisplay_itron_config *config = dev->config; |
| struct auxdisplay_itron_data *data = dev->data; |
| int rc; |
| |
| if (!device_is_ready(config->uart)) { |
| LOG_ERR("UART device not ready"); |
| return -ENODEV; |
| } |
| |
| /* Configure and set busy GPIO */ |
| if (config->busy_gpio.port) { |
| rc = gpio_pin_configure_dt(&config->busy_gpio, GPIO_INPUT); |
| |
| if (rc < 0) { |
| LOG_ERR("Configuration of text display busy GPIO failed: %d", rc); |
| return rc; |
| } |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_init(&data->lock_sem, 1, 1); |
| k_sem_init(&data->busy_wait_sem, 0, 1); |
| |
| gpio_init_callback(&data->busy_wait_callback, |
| auxdisplay_itron_busy_gpio_change_callback, |
| BIT(config->busy_gpio.pin)); |
| rc = gpio_add_callback(config->busy_gpio.port, &data->busy_wait_callback); |
| |
| if (rc != 0) { |
| LOG_ERR("Configuration of busy interrupt failed: %d", rc); |
| return rc; |
| } |
| #endif |
| } |
| |
| /* Configure and set reset GPIO */ |
| if (config->reset_gpio.port) { |
| rc = gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE); |
| if (rc < 0) { |
| LOG_ERR("Configuration of text display reset GPIO failed"); |
| return rc; |
| } |
| } |
| |
| data->character_x = 0; |
| data->character_y = 0; |
| data->brightness = 0; |
| |
| /* Reset display to known configuration */ |
| if (config->reset_gpio.port) { |
| uint8_t wait_loops = 0; |
| |
| gpio_pin_set_dt(&config->reset_gpio, 1); |
| k_sleep(AUXDISPLAY_ITRON_RESET_TIME); |
| gpio_pin_set_dt(&config->reset_gpio, 0); |
| k_sleep(AUXDISPLAY_ITRON_RESET_WAIT_TIME); |
| |
| while (auxdisplay_itron_is_busy(dev) == 1) { |
| /* Display is busy, wait */ |
| k_sleep(AUXDISPLAY_ITRON_BUSY_DELAY_TIME_CHECK); |
| ++wait_loops; |
| |
| if (wait_loops >= AUXDISPLAY_ITRON_BUSY_WAIT_LOOPS) { |
| /* Waited long enough for display not to be busy, bailing */ |
| return -EIO; |
| } |
| } |
| } else { |
| /* Ensure display is powered on so that it can be initialised */ |
| (void)auxdisplay_itron_set_powered(dev, true); |
| auxdisplay_itron_clear(dev); |
| } |
| |
| return 0; |
| } |
| |
| static int auxdisplay_itron_set_powered(const struct device *dev, bool enabled) |
| { |
| uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_ACTION, |
| AUXDISPLAY_ITRON_CMD_N, AUXDISPLAY_ITRON_CMD_SCREEN_SAVER, 0}; |
| |
| if (enabled) { |
| cmd[4] = 1; |
| } |
| |
| return send_cmd(dev, cmd, sizeof(cmd), true, true); |
| } |
| |
| static bool auxdisplay_itron_is_powered(const struct device *dev) |
| { |
| struct auxdisplay_itron_data *data = dev->data; |
| bool is_powered; |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_take(&data->lock_sem, K_FOREVER); |
| #endif |
| |
| is_powered = data->powered; |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_give(&data->lock_sem); |
| #endif |
| |
| return is_powered; |
| } |
| |
| static int auxdisplay_itron_display_on(const struct device *dev) |
| { |
| return auxdisplay_itron_set_powered(dev, true); |
| } |
| |
| static int auxdisplay_itron_display_off(const struct device *dev) |
| { |
| return auxdisplay_itron_set_powered(dev, false); |
| } |
| |
| static int auxdisplay_itron_cursor_set_enabled(const struct device *dev, bool enabled) |
| { |
| uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_CURSOR, |
| (uint8_t)enabled}; |
| |
| return send_cmd(dev, cmd, sizeof(cmd), false, true); |
| } |
| |
| static int auxdisplay_itron_cursor_position_set(const struct device *dev, |
| enum auxdisplay_position type, |
| int16_t x, int16_t y) |
| { |
| uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_CURSOR_SET, |
| 0, 0, 0, 0}; |
| |
| if (type != AUXDISPLAY_POSITION_ABSOLUTE) { |
| return -EINVAL; |
| } |
| |
| sys_put_le16(x, &cmd[2]); |
| sys_put_le16(y, &cmd[4]); |
| |
| return send_cmd(dev, cmd, sizeof(cmd), false, true); |
| } |
| |
| static int auxdisplay_itron_capabilities_get(const struct device *dev, |
| struct auxdisplay_capabilities *capabilities) |
| { |
| const struct auxdisplay_itron_config *config = dev->config; |
| |
| memcpy(capabilities, &config->capabilities, sizeof(struct auxdisplay_capabilities)); |
| |
| return 0; |
| } |
| |
| static int auxdisplay_itron_clear(const struct device *dev) |
| { |
| uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_DISPLAY_CLEAR}; |
| |
| return send_cmd(dev, cmd, sizeof(cmd), false, true); |
| } |
| |
| static int auxdisplay_itron_brightness_get(const struct device *dev, uint8_t *brightness) |
| { |
| struct auxdisplay_itron_data *data = dev->data; |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_take(&data->lock_sem, K_FOREVER); |
| #endif |
| |
| *brightness = data->brightness; |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_give(&data->lock_sem); |
| #endif |
| |
| return 0; |
| } |
| |
| static int auxdisplay_itron_brightness_set(const struct device *dev, uint8_t brightness) |
| { |
| struct auxdisplay_itron_data *data = dev->data; |
| uint8_t cmd[] = {AUXDISPLAY_ITRON_CMD_USER_SETTING, AUXDISPLAY_ITRON_CMD_BRIGHTNESS, |
| brightness}; |
| int rc; |
| |
| if (brightness < AUXDISPLAY_ITRON_BRIGHTNESS_MIN || |
| brightness > AUXDISPLAY_ITRON_BRIGHTNESS_MAX) { |
| return -EINVAL; |
| } |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_take(&data->lock_sem, K_FOREVER); |
| #endif |
| |
| rc = send_cmd(dev, cmd, sizeof(cmd), false, false); |
| |
| if (rc == 0) { |
| data->brightness = brightness; |
| } |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_give(&data->lock_sem); |
| #endif |
| |
| return rc; |
| } |
| |
| static int auxdisplay_itron_is_busy(const struct device *dev) |
| { |
| const struct auxdisplay_itron_config *config = dev->config; |
| int rc; |
| |
| if (config->busy_gpio.port == NULL) { |
| return -ENOTSUP; |
| } |
| |
| rc = gpio_pin_get_dt(&config->busy_gpio); |
| |
| return rc; |
| } |
| |
| static int auxdisplay_itron_is_busy_check(const struct device *dev) |
| { |
| struct auxdisplay_itron_data *data = dev->data; |
| int rc; |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_take(&data->lock_sem, K_FOREVER); |
| #endif |
| |
| rc = auxdisplay_itron_is_busy(dev); |
| |
| #ifdef CONFIG_MULTITHREADING |
| k_sem_give(&data->lock_sem); |
| #endif |
| |
| return rc; |
| } |
| |
| static int send_cmd(const struct device *dev, const uint8_t *command, uint8_t length, bool pm, |
| bool lock) |
| { |
| uint8_t i = 0; |
| const struct auxdisplay_itron_config *config = dev->config; |
| const struct device *uart = config->uart; |
| int rc = 0; |
| #ifdef CONFIG_MULTITHREADING |
| struct auxdisplay_itron_data *data = dev->data; |
| #endif |
| |
| if (pm == false && auxdisplay_itron_is_powered(dev) == false) { |
| /* Display is not powered, only PM commands can be used */ |
| return -ESHUTDOWN; |
| } |
| |
| #ifdef CONFIG_MULTITHREADING |
| if (lock) { |
| k_sem_take(&data->lock_sem, K_FOREVER); |
| } |
| #endif |
| |
| #ifdef CONFIG_MULTITHREADING |
| /* Enable interrupt triggering */ |
| rc = gpio_pin_interrupt_configure_dt(&config->busy_gpio, GPIO_INT_EDGE_TO_INACTIVE); |
| |
| if (rc != 0) { |
| LOG_ERR("Failed to enable busy interrupt: %d", rc); |
| goto end; |
| } |
| #endif |
| |
| while (i < length) { |
| #ifdef CONFIG_MULTITHREADING |
| if (auxdisplay_itron_is_busy(dev) == 1) { |
| if (k_sem_take(&data->busy_wait_sem, |
| AUXDISPLAY_ITRON_BUSY_MAX_TIME) != 0) { |
| rc = -EIO; |
| goto cleanup; |
| } |
| } |
| #else |
| uint8_t wait_loops = 0; |
| |
| while (auxdisplay_itron_is_busy(dev) == 1) { |
| /* Display is busy, wait */ |
| k_sleep(AUXDISPLAY_ITRON_BUSY_DELAY_TIME_CHECK); |
| ++wait_loops; |
| |
| if (wait_loops >= AUXDISPLAY_ITRON_BUSY_WAIT_LOOPS) { |
| /* Waited long enough for display not to be busy, bailing */ |
| return -EIO; |
| } |
| } |
| #endif |
| |
| uart_poll_out(uart, command[i]); |
| ++i; |
| } |
| |
| #ifdef CONFIG_MULTITHREADING |
| cleanup: |
| (void)gpio_pin_interrupt_configure_dt(&config->busy_gpio, GPIO_INT_DISABLE); |
| #endif |
| |
| end: |
| #ifdef CONFIG_MULTITHREADING |
| if (lock) { |
| k_sem_give(&data->lock_sem); |
| } |
| #endif |
| |
| return rc; |
| } |
| |
| static int auxdisplay_itron_write(const struct device *dev, const uint8_t *data, uint16_t len) |
| { |
| uint16_t i = 0; |
| |
| /* Check all characters are valid */ |
| while (i < len) { |
| if (data[i] < AUXDISPLAY_ITRON_CHARACTER_MIN && |
| data[i] != AUXDISPLAY_ITRON_CHARACTER_BACK_SPACE && |
| data[i] != AUXDISPLAY_ITRON_CHARACTER_TAB && |
| data[i] != AUXDISPLAY_ITRON_CHARACTER_LINE_FEED && |
| data[i] != AUXDISPLAY_ITRON_CHARACTER_CARRIAGE_RETURN) { |
| return -EINVAL; |
| } |
| |
| ++i; |
| } |
| |
| return send_cmd(dev, data, len, false, true); |
| } |
| |
| static const struct auxdisplay_driver_api auxdisplay_itron_auxdisplay_api = { |
| .display_on = auxdisplay_itron_display_on, |
| .display_off = auxdisplay_itron_display_off, |
| .cursor_set_enabled = auxdisplay_itron_cursor_set_enabled, |
| .cursor_position_set = auxdisplay_itron_cursor_position_set, |
| .capabilities_get = auxdisplay_itron_capabilities_get, |
| .clear = auxdisplay_itron_clear, |
| .brightness_get = auxdisplay_itron_brightness_get, |
| .brightness_set = auxdisplay_itron_brightness_set, |
| .is_busy = auxdisplay_itron_is_busy_check, |
| .write = auxdisplay_itron_write, |
| }; |
| |
| #define AUXDISPLAY_ITRON_DEVICE(inst) \ |
| static struct auxdisplay_itron_data auxdisplay_itron_data_##inst; \ |
| static const struct auxdisplay_itron_config auxdisplay_itron_config_##inst = { \ |
| .uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \ |
| .capabilities = { \ |
| .columns = DT_INST_PROP(inst, columns), \ |
| .rows = DT_INST_PROP(inst, rows), \ |
| .mode = AUXDISPLAY_ITRON_MODE_UART, \ |
| .brightness.minimum = AUXDISPLAY_ITRON_BRIGHTNESS_MIN, \ |
| .brightness.maximum = AUXDISPLAY_ITRON_BRIGHTNESS_MAX, \ |
| .backlight.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \ |
| .backlight.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \ |
| }, \ |
| .busy_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, busy_gpios, {0}), \ |
| .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(inst, \ |
| &auxdisplay_itron_init, \ |
| NULL, \ |
| &auxdisplay_itron_data_##inst, \ |
| &auxdisplay_itron_config_##inst, \ |
| POST_KERNEL, \ |
| CONFIG_AUXDISPLAY_INIT_PRIORITY, \ |
| &auxdisplay_itron_auxdisplay_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_ITRON_DEVICE) |