drivers: auxdisplay: Add noritake itron VFD auxiliary display

Adds the driver for a Noritake Itron VFD auxiliary display.

Signed-off-by: Jamie McCrae <spam@helper3000.net>
This commit is contained in:
Jamie McCrae 2022-11-13 20:33:46 +00:00 committed by Carles Cufí
commit 7d1c79aa8c
9 changed files with 535 additions and 0 deletions

View file

@ -24,6 +24,7 @@ add_subdirectory_ifdef(CONFIG_CRYPTO crypto)
add_subdirectory_ifdef(CONFIG_DAC dac) add_subdirectory_ifdef(CONFIG_DAC dac)
add_subdirectory_ifdef(CONFIG_DAI dai) add_subdirectory_ifdef(CONFIG_DAI dai)
add_subdirectory_ifdef(CONFIG_DISPLAY display) add_subdirectory_ifdef(CONFIG_DISPLAY display)
add_subdirectory_ifdef(CONFIG_AUXDISPLAY auxdisplay)
add_subdirectory_ifdef(CONFIG_DMA dma) add_subdirectory_ifdef(CONFIG_DMA dma)
add_subdirectory_ifdef(CONFIG_EDAC edac) add_subdirectory_ifdef(CONFIG_EDAC edac)
add_subdirectory_ifdef(CONFIG_EEPROM eeprom) add_subdirectory_ifdef(CONFIG_EEPROM eeprom)

View file

@ -6,6 +6,7 @@
menu "Device Drivers" menu "Device Drivers"
source "drivers/adc/Kconfig" source "drivers/adc/Kconfig"
source "drivers/auxdisplay/Kconfig"
source "drivers/audio/Kconfig" source "drivers/audio/Kconfig"
source "drivers/bbram/Kconfig" source "drivers/bbram/Kconfig"
source "drivers/bluetooth/Kconfig" source "drivers/bluetooth/Kconfig"

View file

@ -0,0 +1,4 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_ITRON auxdisplay_itron.c)

View file

@ -0,0 +1,25 @@
# Auxiliary Display drivers
# Copyright (c) 2022 Jamie McCrae
# SPDX-License-Identifier: Apache-2.0
menuconfig AUXDISPLAY
bool "Auxiliary (textual) Display Drivers"
help
Enable auxiliary/texual display drivers (e.g. alphanumerical displays)
if AUXDISPLAY
config AUXDISPLAY_INIT_PRIORITY
int "Auxiliary display devices init priority"
default 85
help
Auxiliary (textual) display devices initialization priority.
module = AUXDISPLAY
module-str = auxdisplay
source "subsys/logging/Kconfig.template.log_config"
source "drivers/auxdisplay/Kconfig.itron"
endif # AUXDISPLAY

View file

@ -0,0 +1,11 @@
# Copyright (c) 2022 Jamie McCrae
# SPDX-License-Identifier: Apache-2.0
config AUXDISPLAY_ITRON
bool "Noritake Itron VFD driver"
default y
select GPIO
select SERIAL
depends on DT_HAS_NORITAKE_ITRON_ENABLED
help
Enable driver for Noritake Itron VFD.

View file

@ -0,0 +1,448 @@
/*
* 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)
{
int rc = 0;
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)

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022-2023 Jamie McCrae
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef H_AUXDISPLAY_ITRON_
#define H_AUXDISPLAY_ITRON_
#define AUXDISPLAY_ITRON_BRIGHTNESS_MIN 1
#define AUXDISPLAY_ITRON_BRIGHTNESS_MAX 8
#define AUXDISPLAY_ITRON_CHARACTER_MIN 0x20
#define AUXDISPLAY_ITRON_CHARACTER_BACK_SPACE 0x08
#define AUXDISPLAY_ITRON_CHARACTER_TAB 0x09
#define AUXDISPLAY_ITRON_CHARACTER_LINE_FEED 0x0a
#define AUXDISPLAY_ITRON_CHARACTER_CARRIAGE_RETURN 0x0d
enum {
AUXDISPLAY_ITRON_MODE_UNKNOWN = 0,
AUXDISPLAY_ITRON_MODE_UART,
};
#endif /* H_AUXDISPLAY_ITRON_ */

View file

@ -0,0 +1,20 @@
#
# Copyright (c) 2022 Jamie McCrae
#
# SPDX-License-Identifier: Apache-2.0
#
description: Noritake Itron VFD
compatible: "noritake,itron"
include: [auxdisplay-device.yaml, uart-device.yaml]
properties:
reset-gpios:
type: phandle-array
description: Optional GPIO used to reset the display
busy-gpios:
type: phandle-array
description: Optional GPIO used for busy detection

View file

@ -416,6 +416,7 @@ nintendo Nintendo
nlt NLT Technologies, Ltd. nlt NLT Technologies, Ltd.
nokia Nokia nokia Nokia
nordic Nordic Semiconductor nordic Nordic Semiconductor
noritake Noritake Co., Inc. Electronics Division
novtech NovTech, Inc. novtech NovTech, Inc.
nutsboard NutsBoard nutsboard NutsBoard
nuclei Nuclei System Technology nuclei Nuclei System Technology