|  | /* | 
|  | * 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(void *p1, void *p2, void *p3) | 
|  | { | 
|  | ARG_UNUSED(p2); | 
|  | ARG_UNUSED(p3); | 
|  |  | 
|  | struct ht16k33_data *data = p1; | 
|  | 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 (!gpio_is_ready_dt(&config->irq)) { | 
|  | 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, | 
|  | 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) |