475 lines
12 KiB
C
475 lines
12 KiB
C
/*
|
|
* Copyright (c) 2019 Henrik Brix Andersen <henrik@brixandersen.dk>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT holtek_ht16k33
|
|
|
|
/**
|
|
* @file
|
|
* @brief LED driver for the HT16K33 I2C LED driver with keyscan
|
|
*/
|
|
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/drivers/led.h>
|
|
#include <zephyr/drivers/led/ht16k33.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(ht16k33, CONFIG_LED_LOG_LEVEL);
|
|
|
|
#include "led_context.h"
|
|
|
|
/* HT16K33 commands and options */
|
|
#define HT16K33_CMD_DISP_DATA_ADDR 0x00
|
|
|
|
#define HT16K33_CMD_SYSTEM_SETUP 0x20
|
|
#define HT16K33_OPT_S BIT(0)
|
|
|
|
#define HT16K33_CMD_KEY_DATA_ADDR 0x40
|
|
|
|
#define HT16K33_CMD_INT_FLAG_ADDR 0x60
|
|
|
|
#define HT16K33_CMD_DISP_SETUP 0x80
|
|
#define HT16K33_OPT_D BIT(0)
|
|
#define HT16K33_OPT_B0 BIT(1)
|
|
#define HT16K33_OPT_B1 BIT(2)
|
|
#define HT16K33_OPT_BLINK_OFF 0
|
|
#define HT16K33_OPT_BLINK_2HZ HT16K33_OPT_B0
|
|
#define HT16K33_OPT_BLINK_1HZ HT16K33_OPT_B1
|
|
#define HT16K33_OPT_BLINK_05HZ (HT16K33_OPT_B1 | HT16K33_OPT_B0)
|
|
|
|
#define HT16K33_CMD_ROW_INT_SET 0xa0
|
|
#define HT16K33_OPT_ROW_INT BIT(0)
|
|
#define HT16K33_OPT_ACT BIT(1)
|
|
#define HT16K33_OPT_ROW 0
|
|
#define HT16K33_OPT_INT_LOW HT16K33_OPT_ROW_INT
|
|
#define HT16K33_OPT_INT_HIGH (HT16K33_OPT_ACT | HT16K33_OPT_ROW_INT)
|
|
|
|
#define HT16K33_CMD_DIMMING_SET 0xe0
|
|
|
|
/* HT16K33 size definitions */
|
|
#define HT16K33_DISP_ROWS 16
|
|
#define HT16K33_DISP_COLS 8
|
|
#define HT16K33_DISP_DATA_SIZE HT16K33_DISP_ROWS
|
|
#define HT16K33_DISP_SEGMENTS (HT16K33_DISP_ROWS * HT16K33_DISP_COLS)
|
|
#define HT16K33_DIMMING_LEVELS 16
|
|
#define HT16K33_KEYSCAN_ROWS 3
|
|
#define HT16K33_KEYSCAN_COLS 13
|
|
#define HT16K33_KEYSCAN_DATA_SIZE 6
|
|
|
|
struct ht16k33_cfg {
|
|
struct i2c_dt_spec i2c;
|
|
bool irq_enabled;
|
|
#ifdef CONFIG_HT16K33_KEYSCAN
|
|
struct gpio_dt_spec irq;
|
|
#endif /* CONFIG_HT16K33_KEYSCAN */
|
|
};
|
|
|
|
struct ht16k33_data {
|
|
const struct device *dev;
|
|
struct led_data dev_data;
|
|
/* Shadow buffer for the display data RAM */
|
|
uint8_t buffer[HT16K33_DISP_DATA_SIZE];
|
|
#ifdef CONFIG_HT16K33_KEYSCAN
|
|
struct k_mutex lock;
|
|
const struct device *child;
|
|
kscan_callback_t kscan_cb;
|
|
struct gpio_callback irq_cb;
|
|
struct k_thread irq_thread;
|
|
struct k_sem irq_sem;
|
|
struct k_timer timer;
|
|
uint16_t key_state[HT16K33_KEYSCAN_ROWS];
|
|
|
|
K_KERNEL_STACK_MEMBER(irq_thread_stack,
|
|
CONFIG_HT16K33_KEYSCAN_IRQ_THREAD_STACK_SIZE);
|
|
#endif /* CONFIG_HT16K33_KEYSCAN */
|
|
};
|
|
|
|
static int ht16k33_led_blink(const struct device *dev, uint32_t led,
|
|
uint32_t delay_on, uint32_t delay_off)
|
|
{
|
|
/* The HT16K33 blinks all LEDs at the same frequency */
|
|
ARG_UNUSED(led);
|
|
|
|
const struct ht16k33_cfg *config = dev->config;
|
|
struct ht16k33_data *data = dev->data;
|
|
struct led_data *dev_data = &data->dev_data;
|
|
uint32_t period;
|
|
uint8_t cmd;
|
|
|
|
period = delay_on + delay_off;
|
|
if (period < dev_data->min_period || period > dev_data->max_period) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
cmd = HT16K33_CMD_DISP_SETUP | HT16K33_OPT_D;
|
|
if (delay_off == 0) {
|
|
cmd |= HT16K33_OPT_BLINK_OFF;
|
|
} else if (period > 1500) {
|
|
cmd |= HT16K33_OPT_BLINK_05HZ;
|
|
} else if (period > 750) {
|
|
cmd |= HT16K33_OPT_BLINK_1HZ;
|
|
} else {
|
|
cmd |= HT16K33_OPT_BLINK_2HZ;
|
|
}
|
|
|
|
if (i2c_write_dt(&config->i2c, &cmd, sizeof(cmd))) {
|
|
LOG_ERR("Setting HT16K33 blink frequency failed");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ht16k33_led_set_brightness(const struct device *dev, uint32_t led,
|
|
uint8_t value)
|
|
{
|
|
ARG_UNUSED(led);
|
|
|
|
const struct ht16k33_cfg *config = dev->config;
|
|
struct ht16k33_data *data = dev->data;
|
|
struct led_data *dev_data = &data->dev_data;
|
|
uint8_t dim;
|
|
uint8_t cmd;
|
|
|
|
if (value < dev_data->min_brightness ||
|
|
value > dev_data->max_brightness) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
dim = (value * (HT16K33_DIMMING_LEVELS - 1)) / dev_data->max_brightness;
|
|
cmd = HT16K33_CMD_DIMMING_SET | dim;
|
|
|
|
if (i2c_write_dt(&config->i2c, &cmd, sizeof(cmd))) {
|
|
LOG_ERR("Setting HT16K33 brightness failed");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ht16k33_led_set_state(const struct device *dev, uint32_t led,
|
|
bool on)
|
|
{
|
|
const struct ht16k33_cfg *config = dev->config;
|
|
struct ht16k33_data *data = dev->data;
|
|
uint8_t cmd[2];
|
|
uint8_t addr;
|
|
uint8_t bit;
|
|
|
|
if (led >= HT16K33_DISP_SEGMENTS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
addr = led / HT16K33_DISP_COLS;
|
|
bit = led % HT16K33_DISP_COLS;
|
|
|
|
cmd[0] = HT16K33_CMD_DISP_DATA_ADDR | addr;
|
|
if (on) {
|
|
cmd[1] = data->buffer[addr] | BIT(bit);
|
|
} else {
|
|
cmd[1] = data->buffer[addr] & ~BIT(bit);
|
|
}
|
|
|
|
if (data->buffer[addr] == cmd[1]) {
|
|
return 0;
|
|
}
|
|
|
|
if (i2c_write_dt(&config->i2c, cmd, sizeof(cmd))) {
|
|
LOG_ERR("Setting HT16K33 LED %s failed", on ? "on" : "off");
|
|
return -EIO;
|
|
}
|
|
|
|
data->buffer[addr] = cmd[1];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ht16k33_led_on(const struct device *dev, uint32_t led)
|
|
{
|
|
return ht16k33_led_set_state(dev, led, true);
|
|
}
|
|
|
|
static int ht16k33_led_off(const struct device *dev, uint32_t led)
|
|
{
|
|
return ht16k33_led_set_state(dev, led, false);
|
|
}
|
|
|
|
#ifdef CONFIG_HT16K33_KEYSCAN
|
|
static bool ht16k33_process_keyscan_data(const struct device *dev)
|
|
{
|
|
const struct ht16k33_cfg *config = dev->config;
|
|
struct ht16k33_data *data = dev->data;
|
|
uint8_t keys[HT16K33_KEYSCAN_DATA_SIZE];
|
|
bool pressed = false;
|
|
uint16_t state;
|
|
uint16_t changed;
|
|
int row;
|
|
int col;
|
|
int err;
|
|
|
|
err = i2c_burst_read_dt(&config->i2c, HT16K33_CMD_KEY_DATA_ADDR, keys, sizeof(keys));
|
|
if (err) {
|
|
LOG_WRN("Failed to to read HT16K33 key data (err %d)", err);
|
|
/* Reprocess */
|
|
return true;
|
|
}
|
|
|
|
k_mutex_lock(&data->lock, K_FOREVER);
|
|
|
|
for (row = 0; row < HT16K33_KEYSCAN_ROWS; row++) {
|
|
state = sys_get_le16(&keys[row * 2]);
|
|
changed = data->key_state[row] ^ state;
|
|
data->key_state[row] = state;
|
|
|
|
if (state) {
|
|
pressed = true;
|
|
}
|
|
|
|
if (data->kscan_cb == NULL) {
|
|
continue;
|
|
}
|
|
|
|
for (col = 0; col < HT16K33_KEYSCAN_COLS; col++) {
|
|
if (changed & BIT(col)) {
|
|
data->kscan_cb(data->child, row, col,
|
|
state & BIT(col));
|
|
}
|
|
}
|
|
}
|
|
|
|
k_mutex_unlock(&data->lock);
|
|
|
|
return pressed;
|
|
}
|
|
|
|
static void ht16k33_irq_thread(struct ht16k33_data *data)
|
|
{
|
|
bool pressed;
|
|
|
|
while (true) {
|
|
k_sem_take(&data->irq_sem, K_FOREVER);
|
|
|
|
do {
|
|
k_sem_reset(&data->irq_sem);
|
|
pressed = ht16k33_process_keyscan_data(data->dev);
|
|
k_msleep(CONFIG_HT16K33_KEYSCAN_DEBOUNCE_MSEC);
|
|
} while (pressed);
|
|
}
|
|
}
|
|
|
|
static void ht16k33_irq_callback(const struct device *gpiob,
|
|
struct gpio_callback *cb, uint32_t pins)
|
|
{
|
|
struct ht16k33_data *data;
|
|
|
|
ARG_UNUSED(gpiob);
|
|
ARG_UNUSED(pins);
|
|
|
|
data = CONTAINER_OF(cb, struct ht16k33_data, irq_cb);
|
|
k_sem_give(&data->irq_sem);
|
|
}
|
|
|
|
static void ht16k33_timer_callback(struct k_timer *timer)
|
|
{
|
|
struct ht16k33_data *data;
|
|
|
|
data = CONTAINER_OF(timer, struct ht16k33_data, timer);
|
|
k_sem_give(&data->irq_sem);
|
|
}
|
|
|
|
int ht16k33_register_keyscan_callback(const struct device *parent,
|
|
const struct device *child,
|
|
kscan_callback_t callback)
|
|
{
|
|
struct ht16k33_data *data = parent->data;
|
|
|
|
k_mutex_lock(&data->lock, K_FOREVER);
|
|
data->child = child;
|
|
data->kscan_cb = callback;
|
|
k_mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_HT16K33_KEYSCAN */
|
|
|
|
static int ht16k33_init(const struct device *dev)
|
|
{
|
|
const struct ht16k33_cfg *config = dev->config;
|
|
struct ht16k33_data *data = dev->data;
|
|
struct led_data *dev_data = &data->dev_data;
|
|
uint8_t cmd[1 + HT16K33_DISP_DATA_SIZE]; /* 1 byte command + data */
|
|
int err;
|
|
|
|
data->dev = dev;
|
|
|
|
if (!device_is_ready(config->i2c.bus)) {
|
|
LOG_ERR("I2C bus device not ready");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(&data->buffer, 0, sizeof(data->buffer));
|
|
|
|
/* Hardware specific limits */
|
|
dev_data->min_period = 0U;
|
|
dev_data->max_period = 2000U;
|
|
dev_data->min_brightness = 0U;
|
|
dev_data->max_brightness = 100U;
|
|
|
|
/* System oscillator on */
|
|
cmd[0] = HT16K33_CMD_SYSTEM_SETUP | HT16K33_OPT_S;
|
|
err = i2c_write_dt(&config->i2c, cmd, 1);
|
|
if (err) {
|
|
LOG_ERR("Enabling HT16K33 system oscillator failed (err %d)",
|
|
err);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Clear display RAM */
|
|
memset(cmd, 0, sizeof(cmd));
|
|
cmd[0] = HT16K33_CMD_DISP_DATA_ADDR;
|
|
err = i2c_write_dt(&config->i2c, cmd, sizeof(cmd));
|
|
if (err) {
|
|
LOG_ERR("Clearing HT16K33 display RAM failed (err %d)", err);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Full brightness */
|
|
cmd[0] = HT16K33_CMD_DIMMING_SET | 0x0f;
|
|
err = i2c_write_dt(&config->i2c, cmd, 1);
|
|
if (err) {
|
|
LOG_ERR("Setting HT16K33 brightness failed (err %d)", err);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Display on, blinking off */
|
|
cmd[0] = HT16K33_CMD_DISP_SETUP | HT16K33_OPT_D | HT16K33_OPT_BLINK_OFF;
|
|
err = i2c_write_dt(&config->i2c, cmd, 1);
|
|
if (err) {
|
|
LOG_ERR("Enabling HT16K33 display failed (err %d)", err);
|
|
return -EIO;
|
|
}
|
|
|
|
#ifdef CONFIG_HT16K33_KEYSCAN
|
|
k_mutex_init(&data->lock);
|
|
k_sem_init(&data->irq_sem, 0, 1);
|
|
|
|
/* Configure interrupt */
|
|
if (config->irq_enabled) {
|
|
uint8_t keys[HT16K33_KEYSCAN_DATA_SIZE];
|
|
|
|
if (!device_is_ready(config->irq.port)) {
|
|
LOG_ERR("IRQ device not ready");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = gpio_pin_configure_dt(&config->irq, GPIO_INPUT);
|
|
if (err) {
|
|
LOG_ERR("Failed to configure IRQ pin (err %d)", err);
|
|
return -EINVAL;
|
|
}
|
|
|
|
gpio_init_callback(&data->irq_cb, &ht16k33_irq_callback,
|
|
BIT(config->irq.pin));
|
|
|
|
err = gpio_add_callback(config->irq.port, &data->irq_cb);
|
|
if (err) {
|
|
LOG_ERR("Failed to add IRQ callback (err %d)", err);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Enable interrupt pin */
|
|
cmd[0] = HT16K33_CMD_ROW_INT_SET | HT16K33_OPT_INT_LOW;
|
|
if (i2c_write_dt(&config->i2c, cmd, 1)) {
|
|
LOG_ERR("Enabling HT16K33 IRQ output failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Flush key data before enabling interrupt */
|
|
err = i2c_burst_read_dt(&config->i2c, HT16K33_CMD_KEY_DATA_ADDR, keys,
|
|
sizeof(keys));
|
|
if (err) {
|
|
LOG_ERR("Failed to to read HT16K33 key data");
|
|
return -EIO;
|
|
}
|
|
|
|
err = gpio_pin_interrupt_configure_dt(&config->irq,
|
|
GPIO_INT_EDGE_FALLING);
|
|
if (err) {
|
|
LOG_ERR("Failed to configure IRQ pin flags (err %d)",
|
|
err);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
/* No interrupt pin, enable ROW15 */
|
|
cmd[0] = HT16K33_CMD_ROW_INT_SET | HT16K33_OPT_ROW;
|
|
if (i2c_write_dt(&config->i2c, cmd, 1)) {
|
|
LOG_ERR("Enabling HT16K33 ROW15 output failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Setup timer for polling key data */
|
|
k_timer_init(&data->timer, ht16k33_timer_callback, NULL);
|
|
k_timer_start(&data->timer, K_NO_WAIT,
|
|
K_MSEC(CONFIG_HT16K33_KEYSCAN_POLL_MSEC));
|
|
}
|
|
|
|
k_thread_create(&data->irq_thread, data->irq_thread_stack,
|
|
CONFIG_HT16K33_KEYSCAN_IRQ_THREAD_STACK_SIZE,
|
|
(k_thread_entry_t)ht16k33_irq_thread, data, NULL, NULL,
|
|
K_PRIO_COOP(CONFIG_HT16K33_KEYSCAN_IRQ_THREAD_PRIO),
|
|
0, K_NO_WAIT);
|
|
#endif /* CONFIG_HT16K33_KEYSCAN */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct led_driver_api ht16k33_leds_api = {
|
|
.blink = ht16k33_led_blink,
|
|
.set_brightness = ht16k33_led_set_brightness,
|
|
.on = ht16k33_led_on,
|
|
.off = ht16k33_led_off,
|
|
};
|
|
|
|
#define HT16K33_DEVICE(id) \
|
|
static const struct ht16k33_cfg ht16k33_##id##_cfg = { \
|
|
.i2c = I2C_DT_SPEC_INST_GET(id), \
|
|
.irq_enabled = false, \
|
|
}; \
|
|
\
|
|
static struct ht16k33_data ht16k33_##id##_data; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(id, &ht16k33_init, NULL, \
|
|
&ht16k33_##id##_data, \
|
|
&ht16k33_##id##_cfg, POST_KERNEL, \
|
|
CONFIG_LED_INIT_PRIORITY, &ht16k33_leds_api)
|
|
|
|
#ifdef CONFIG_HT16K33_KEYSCAN
|
|
#define HT16K33_DEVICE_WITH_IRQ(id) \
|
|
static const struct ht16k33_cfg ht16k33_##id##_cfg = { \
|
|
.i2c = I2C_DT_SPEC_INST_GET(id), \
|
|
.irq_enabled = true, \
|
|
.irq = GPIO_DT_SPEC_INST_GET(id, irq_gpios), \
|
|
}; \
|
|
\
|
|
static struct ht16k33_data ht16k33_##id##_data; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(id, &ht16k33_init, NULL, \
|
|
&ht16k33_##id##_data, \
|
|
&ht16k33_##id##_cfg, POST_KERNEL, \
|
|
CONFIG_LED_INIT_PRIORITY, &ht16k33_leds_api)
|
|
#else /* ! CONFIG_HT16K33_KEYSCAN */
|
|
#define HT16K33_DEVICE_WITH_IRQ(id) HT16K33_DEVICE(id)
|
|
#endif /* ! CONFIG_HT16K33_KEYSCAN */
|
|
|
|
#define HT16K33_INSTANTIATE(id) \
|
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(id, irq_gpios), \
|
|
(HT16K33_DEVICE_WITH_IRQ(id)), \
|
|
(HT16K33_DEVICE(id)));
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(HT16K33_INSTANTIATE)
|