| /* |
| * 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 <drivers/gpio.h> |
| #include <drivers/i2c.h> |
| #include <kernel.h> |
| #include <drivers/led.h> |
| #include <drivers/led/ht16k33.h> |
| #include <sys/byteorder.h> |
| #include <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 { |
| const struct device *i2c_dev; |
| uint16_t i2c_addr; |
| 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(config->i2c_dev, &cmd, sizeof(cmd), config->i2c_addr)) { |
| 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(config->i2c_dev, &cmd, sizeof(cmd), config->i2c_addr)) { |
| 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(config->i2c_dev, cmd, sizeof(cmd), config->i2c_addr)) { |
| 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(config->i2c_dev, config->i2c_addr, |
| 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_dev)) { |
| 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(config->i2c_dev, cmd, 1, config->i2c_addr); |
| 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(config->i2c_dev, cmd, sizeof(cmd), config->i2c_addr); |
| 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(config->i2c_dev, cmd, 1, config->i2c_addr); |
| 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(config->i2c_dev, cmd, 1, config->i2c_addr); |
| 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(config->i2c_dev, cmd, 1, config->i2c_addr)) { |
| LOG_ERR("Enabling HT16K33 IRQ output failed"); |
| return -EIO; |
| } |
| |
| /* Flush key data before enabling interrupt */ |
| err = i2c_burst_read(config->i2c_dev, config->i2c_addr, |
| 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(config->i2c_dev, cmd, 1, config->i2c_addr)) { |
| 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_dev = DEVICE_DT_GET(DT_INST_BUS(id)), \ |
| .i2c_addr = DT_INST_REG_ADDR(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_dev = DEVICE_DT_GET(DT_INST_BUS(id)), \ |
| .i2c_addr = DT_INST_REG_ADDR(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) |