380 lines
10 KiB
C
380 lines
10 KiB
C
/*
|
|
* Copyright (c) 2015 Intel Corporation
|
|
* Copyright (c) 2022 Nordic Semiconductor ASA
|
|
* Copyright (c) 2022-2023 Jamie McCrae
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT jhd_jhd1313
|
|
|
|
#include <string.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/auxdisplay.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(auxdisplay_jhd1313, CONFIG_AUXDISPLAY_LOG_LEVEL);
|
|
|
|
#define JHD1313_BACKLIGHT_ADDR (0x62)
|
|
|
|
/* Defines for the JHD1313_CMD_CURSOR_SHIFT */
|
|
#define JHD1313_CS_DISPLAY_SHIFT (1 << 3)
|
|
#define JHD1313_CS_RIGHT_SHIFT (1 << 2)
|
|
|
|
/* Defines for the JHD1313_CMD_INPUT_SET to change text direction */
|
|
#define JHD1313_IS_INCREMENT (1 << 1)
|
|
#define JHD1313_IS_DECREMENT (0 << 1)
|
|
#define JHD1313_IS_SHIFT (1 << 0)
|
|
|
|
/* Defines for the JHD1313_CMD_FUNCTION_SET */
|
|
#define JHD1313_FS_8BIT_MODE (1 << 4)
|
|
#define JHD1313_FS_ROWS_2 (1 << 3)
|
|
#define JHD1313_FS_ROWS_1 (0 << 3)
|
|
#define JHD1313_FS_DOT_SIZE_BIG (1 << 2)
|
|
#define JHD1313_FS_DOT_SIZE_LITTLE (0 << 2)
|
|
|
|
/* LCD Display Commands */
|
|
#define JHD1313_CMD_SCREEN_CLEAR (1 << 0)
|
|
#define JHD1313_CMD_CURSOR_RETURN (1 << 1)
|
|
#define JHD1313_CMD_INPUT_SET (1 << 2)
|
|
#define JHD1313_CMD_DISPLAY_SWITCH (1 << 3)
|
|
#define JHD1313_CMD_CURSOR_SHIFT (1 << 4)
|
|
#define JHD1313_CMD_FUNCTION_SET (1 << 5)
|
|
#define JHD1313_CMD_SET_CGRAM_ADDR (1 << 6)
|
|
#define JHD1313_CMD_SET_DDRAM_ADDR (1 << 7)
|
|
|
|
#define JHD1313_DS_DISPLAY_ON (1 << 2)
|
|
#define JHD1313_DS_CURSOR_ON (1 << 1)
|
|
#define JHD1313_DS_BLINK_ON (1 << 0)
|
|
|
|
#define JHD1313_LED_REG_R 0x04
|
|
#define JHD1313_LED_REG_G 0x03
|
|
#define JHD1313_LED_REG_B 0x02
|
|
|
|
#define JHD1313_LINE_FIRST 0x80
|
|
#define JHD1313_LINE_SECOND 0xC0
|
|
|
|
#define CLEAR_DELAY_MS 20
|
|
#define UPDATE_DELAY_MS 5
|
|
|
|
struct auxdisplay_jhd1313_data {
|
|
uint8_t input_set;
|
|
bool power;
|
|
bool cursor;
|
|
bool blinking;
|
|
uint8_t function;
|
|
uint8_t backlight;
|
|
};
|
|
|
|
struct auxdisplay_jhd1313_config {
|
|
struct auxdisplay_capabilities capabilities;
|
|
struct i2c_dt_spec bus;
|
|
};
|
|
|
|
static const uint8_t colour_define[][4] = {
|
|
{ 0, 0, 0 }, /* Off */
|
|
{ 255, 255, 255 }, /* White */
|
|
{ 255, 0, 0 }, /* Red */
|
|
{ 0, 255, 0 }, /* Green */
|
|
{ 0, 0, 255 }, /* Blue */
|
|
};
|
|
|
|
static void auxdisplay_jhd1313_reg_set(const struct device *i2c, uint8_t addr, uint8_t data)
|
|
{
|
|
uint8_t command[2] = { addr, data };
|
|
|
|
i2c_write(i2c, command, sizeof(command), JHD1313_BACKLIGHT_ADDR);
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_print(const struct device *dev, const uint8_t *data, uint16_t size)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
uint8_t buf[] = { JHD1313_CMD_SET_CGRAM_ADDR, 0 };
|
|
int rc = 0;
|
|
int16_t i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
buf[1] = data[i];
|
|
rc = i2c_write_dt(&config->bus, buf, sizeof(buf));
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_cursor_position_set(const struct device *dev,
|
|
enum auxdisplay_position type, int16_t x,
|
|
int16_t y)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
unsigned char data[2];
|
|
|
|
if (type != AUXDISPLAY_POSITION_ABSOLUTE) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (y == 0U) {
|
|
x |= JHD1313_LINE_FIRST;
|
|
} else {
|
|
x |= JHD1313_LINE_SECOND;
|
|
}
|
|
|
|
data[0] = JHD1313_CMD_SET_DDRAM_ADDR;
|
|
data[1] = x;
|
|
|
|
return i2c_write_dt(&config->bus, data, 2);
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_clear(const struct device *dev)
|
|
{
|
|
int rc;
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
uint8_t clear[] = { 0, JHD1313_CMD_SCREEN_CLEAR };
|
|
|
|
rc = i2c_write_dt(&config->bus, clear, sizeof(clear));
|
|
LOG_DBG("Clear, delay 20 ms");
|
|
|
|
k_sleep(K_MSEC(CLEAR_DELAY_MS));
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_update_display_state(
|
|
const struct auxdisplay_jhd1313_config *config,
|
|
struct auxdisplay_jhd1313_data *data)
|
|
{
|
|
int rc;
|
|
uint8_t buf[] = { 0, JHD1313_CMD_DISPLAY_SWITCH };
|
|
|
|
if (data->power) {
|
|
buf[1] |= JHD1313_DS_DISPLAY_ON;
|
|
}
|
|
|
|
if (data->cursor) {
|
|
buf[1] |= JHD1313_DS_CURSOR_ON;
|
|
}
|
|
|
|
if (data->blinking) {
|
|
buf[1] |= JHD1313_DS_BLINK_ON;
|
|
}
|
|
|
|
rc = i2c_write_dt(&config->bus, buf, sizeof(buf));
|
|
|
|
LOG_DBG("Set display_state options, delay 5 ms");
|
|
k_sleep(K_MSEC(UPDATE_DELAY_MS));
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_cursor_set_enabled(const struct device *dev, bool enabled)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
|
|
data->cursor = enabled;
|
|
return auxdisplay_jhd1313_update_display_state(config, data);
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_position_blinking_set_enabled(const struct device *dev, bool enabled)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
|
|
data->blinking = enabled;
|
|
return auxdisplay_jhd1313_update_display_state(config, data);
|
|
}
|
|
|
|
static void auxdisplay_jhd1313_input_state_set(const struct device *dev, uint8_t opt)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
uint8_t buf[] = { 0, 0 };
|
|
|
|
data->input_set = opt;
|
|
buf[1] = (opt | JHD1313_CMD_INPUT_SET);
|
|
|
|
i2c_write_dt(&config->bus, buf, sizeof(buf));
|
|
LOG_DBG("Set the input_set, no delay");
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_backlight_set(const struct device *dev, uint8_t colour)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
|
|
if (colour > ARRAY_SIZE(colour_define)) {
|
|
LOG_WRN("Selected colour is too high a value");
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->backlight = colour;
|
|
|
|
auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_R, colour_define[colour][0]);
|
|
auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_G, colour_define[colour][1]);
|
|
auxdisplay_jhd1313_reg_set(config->bus.bus, JHD1313_LED_REG_B, colour_define[colour][2]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_backlight_get(const struct device *dev, uint8_t *backlight)
|
|
{
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
|
|
*backlight = data->backlight;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void auxdisplay_jhd1313_function_set(const struct device *dev, uint8_t opt)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
uint8_t buf[] = { 0, 0 };
|
|
|
|
data->function = opt;
|
|
buf[1] = (opt | JHD1313_CMD_FUNCTION_SET);
|
|
|
|
i2c_write_dt(&config->bus, buf, sizeof(buf));
|
|
|
|
LOG_DBG("Set function options, delay 5 ms");
|
|
k_sleep(K_MSEC(5));
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_initialize(const struct device *dev)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
uint8_t cmd;
|
|
|
|
LOG_DBG("Initialize called");
|
|
|
|
if (!device_is_ready(config->bus.bus)) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* Initialization sequence from the data sheet:
|
|
* 1 - Power on
|
|
* - Wait for more than 30 ms AFTER VDD rises to 4.5v
|
|
* 2 - Send FUNCTION set
|
|
* - Wait for 39 us
|
|
* 3 - Send DISPLAY Control
|
|
* - wait for 39 us
|
|
* 4 - send DISPLAY Clear
|
|
* - wait for 1.5 ms
|
|
* 5 - send ENTRY Mode
|
|
* 6 - Initialization is done
|
|
*/
|
|
|
|
/*
|
|
* We're here! Let's just make sure we've had enough time for the
|
|
* VDD to power on, so pause a little here, 30 ms min, so we go 50
|
|
*/
|
|
LOG_DBG("Delay 50 ms while the VDD powers on");
|
|
k_sleep(K_MSEC(50));
|
|
|
|
/* Configure everything for the display function first */
|
|
cmd = JHD1313_CMD_FUNCTION_SET | JHD1313_FS_ROWS_2;
|
|
auxdisplay_jhd1313_function_set(dev, cmd);
|
|
|
|
/* Turn the display on - by default no cursor and no blinking */
|
|
auxdisplay_jhd1313_update_display_state(config, data);
|
|
|
|
/* Clear the screen */
|
|
auxdisplay_jhd1313_clear(dev);
|
|
|
|
/*
|
|
* Initialize to the default text direction for romance languages
|
|
* (increment, no shift)
|
|
*/
|
|
cmd = JHD1313_IS_INCREMENT;
|
|
|
|
auxdisplay_jhd1313_input_state_set(dev, cmd);
|
|
|
|
/* Now power on the background RGB control */
|
|
LOG_INF("Configuring the RGB background");
|
|
auxdisplay_jhd1313_reg_set(config->bus.bus, 0x00, 0x00);
|
|
auxdisplay_jhd1313_reg_set(config->bus.bus, 0x01, 0x05);
|
|
auxdisplay_jhd1313_reg_set(config->bus.bus, 0x08, 0xAA);
|
|
|
|
/* Now set the background colour to black */
|
|
LOG_DBG("Background set to off");
|
|
auxdisplay_jhd1313_backlight_set(dev, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_display_on(const struct device *dev)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
|
|
data->power = true;
|
|
return auxdisplay_jhd1313_update_display_state(config, data);
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_display_off(const struct device *dev)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
struct auxdisplay_jhd1313_data *data = dev->data;
|
|
|
|
data->power = false;
|
|
return auxdisplay_jhd1313_update_display_state(config, data);
|
|
}
|
|
|
|
static int auxdisplay_jhd1313_capabilities_get(const struct device *dev,
|
|
struct auxdisplay_capabilities *capabilities)
|
|
{
|
|
const struct auxdisplay_jhd1313_config *config = dev->config;
|
|
|
|
memcpy(capabilities, &config->capabilities, sizeof(struct auxdisplay_capabilities));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct auxdisplay_driver_api auxdisplay_jhd1313_auxdisplay_api = {
|
|
.display_on = auxdisplay_jhd1313_display_on,
|
|
.display_off = auxdisplay_jhd1313_display_off,
|
|
.cursor_set_enabled = auxdisplay_jhd1313_cursor_set_enabled,
|
|
.position_blinking_set_enabled = auxdisplay_jhd1313_position_blinking_set_enabled,
|
|
.cursor_position_set = auxdisplay_jhd1313_cursor_position_set,
|
|
.capabilities_get = auxdisplay_jhd1313_capabilities_get,
|
|
.clear = auxdisplay_jhd1313_clear,
|
|
.backlight_get = auxdisplay_jhd1313_backlight_get,
|
|
.backlight_set = auxdisplay_jhd1313_backlight_set,
|
|
.write = auxdisplay_jhd1313_print,
|
|
};
|
|
|
|
#define AUXDISPLAY_JHD1313_DEVICE(inst) \
|
|
static const struct auxdisplay_jhd1313_config auxdisplay_jhd1313_config_##inst = { \
|
|
.capabilities = { \
|
|
.columns = 16, \
|
|
.rows = 2, \
|
|
.mode = 0, \
|
|
.brightness.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \
|
|
.brightness.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \
|
|
.backlight.minimum = 0, \
|
|
.backlight.maximum = ARRAY_SIZE(colour_define), \
|
|
.custom_characters = 0, \
|
|
}, \
|
|
.bus = I2C_DT_SPEC_INST_GET(inst), \
|
|
}; \
|
|
static struct auxdisplay_jhd1313_data auxdisplay_jhd1313_data_##inst = { \
|
|
.power = true, \
|
|
.cursor = false, \
|
|
.blinking = false, \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(inst, \
|
|
&auxdisplay_jhd1313_initialize, \
|
|
NULL, \
|
|
&auxdisplay_jhd1313_data_##inst, \
|
|
&auxdisplay_jhd1313_config_##inst, \
|
|
POST_KERNEL, \
|
|
CONFIG_AUXDISPLAY_INIT_PRIORITY, \
|
|
&auxdisplay_jhd1313_auxdisplay_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_JHD1313_DEVICE)
|