drivers: gpio: ht16k33: add GPIO driver for Holtek HT16K33 LED driver

The HT16K33 is a memory mapping, multifunction LED controller
driver. The controller supports up to 128 LEDs (up to 16 rows and 8
commons) and matrix key scan circuit of up to 13x3 keys.

This commit adds support for the keyscan functionality of the HT16K33.

Signed-off-by: Henrik Brix Andersen <henrik@brixandersen.dk>
diff --git a/CODEOWNERS b/CODEOWNERS
index 7143f63..d057ee9 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -110,6 +110,7 @@
 /drivers/ethernet/                        @jukkar @tbursztyka @pfalcon
 /drivers/flash/                           @nashif
 /drivers/flash/*stm32*                    @superna9999
+/drivers/gpio/*ht16k33*                   @henrikbrixandersen
 /drivers/gpio/*stm32*                     @rsalveti @idlethread
 /drivers/hwinfo/                          @alexanderwachter
 /drivers/i2s/i2s_ll_stm32*                @avisconti
@@ -195,6 +196,7 @@
 /include/display.h                        @vanwinkeljan
 /include/display/                         @vanwinkeljan
 /include/drivers/bluetooth/               @joerchan @jhedberg @Vudentz
+/include/drivers/led/ht16k33.h            @henrikbrixandersen
 /include/drivers/modem/                   @mike-scott
 /include/drivers/ioapic.h                 @andrewboie
 /include/drivers/loapic.h                 @andrewboie
diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt
index a5cf0d0..9d81acd 100644
--- a/drivers/gpio/CMakeLists.txt
+++ b/drivers/gpio/CMakeLists.txt
@@ -29,6 +29,7 @@
 zephyr_library_sources_ifdef(CONFIG_GPIO_INTEL_APL  gpio_intel_apl.c)
 zephyr_library_sources_ifdef(CONFIG_GPIO_STELLARIS  gpio_stellaris.c)
 zephyr_library_sources_ifdef(CONFIG_GPIO_RV32M1     gpio_rv32m1.c)
+zephyr_library_sources_ifdef(CONFIG_GPIO_HT16K33    gpio_ht16k33.c)
 
 zephyr_library_sources_ifdef(CONFIG_GPIO_SHELL      gpio_shell.c)
 
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index e97b356..ae08c30 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -74,4 +74,6 @@
 
 source "drivers/gpio/Kconfig.rv32m1"
 
+source "drivers/gpio/Kconfig.ht16k33"
+
 endif # GPIO
diff --git a/drivers/gpio/Kconfig.ht16k33 b/drivers/gpio/Kconfig.ht16k33
new file mode 100644
index 0000000..f58f394
--- /dev/null
+++ b/drivers/gpio/Kconfig.ht16k33
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2019 Henrik Brix Andersen <henrik@brixandersen.dk>
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+menuconfig GPIO_HT16K33
+	bool "HT16K33 keyscan driver"
+	depends on HT16K33_KEYSCAN && GPIO
+	help
+	  Enable keyscan driver for HT16K33.
+
+	  The HT16K33 is a memory mapping, multifunction LED
+	  controller driver. The controller supports matrix key scan
+	  circuit of up to 13x3 keys.
+
+	  The keyscan functionality is exposed as up to 3 GPIO
+	  controller drivers, each supporting GPIO callbacks for
+	  keyscan event notifications.
+
+if GPIO_HT16K33
+
+config GPIO_HT16K33_INIT_PRIORITY
+	int "Driver init priority"
+	default 99
+	help
+	  Device driver initialization priority. This driver must be
+	  initilized after the HT16K33 LED driver.
+
+endif #GPIO_HT16K33
diff --git a/drivers/gpio/gpio_ht16k33.c b/drivers/gpio/gpio_ht16k33.c
new file mode 100644
index 0000000..12eabc0
--- /dev/null
+++ b/drivers/gpio/gpio_ht16k33.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2019 Henrik Brix Andersen <henrik@brixandersen.dk>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @file
+ * @brief GPIO driver for the HT16K33 I2C LED driver with keyscan
+ */
+
+#include <gpio.h>
+#include <zephyr.h>
+
+#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
+#include <logging/log.h>
+LOG_MODULE_REGISTER(gpio_ht16k33);
+
+#include <led/ht16k33.h>
+
+#include "gpio_utils.h"
+
+/* HT16K33 size definitions */
+#define HT16K33_KEYSCAN_ROWS 3
+
+struct gpio_ht16k33_cfg {
+	char *parent_dev_name;
+	u8_t keyscan_idx;
+};
+
+struct gpio_ht16k33_data {
+	struct device *parent;
+	sys_slist_t callbacks;
+};
+
+static int gpio_ht16k33_cfg(struct device *dev, int access_op,
+			       u32_t pin, int flags)
+{
+	ARG_UNUSED(dev);
+	ARG_UNUSED(access_op);
+	ARG_UNUSED(pin);
+
+	/* Keyscan is input-only */
+	if ((flags & GPIO_DIR_MASK) != GPIO_DIR_IN) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int gpio_ht16k33_write(struct device *dev, int access_op,
+			      u32_t pin, u32_t value)
+{
+	ARG_UNUSED(dev);
+	ARG_UNUSED(access_op);
+	ARG_UNUSED(pin);
+	ARG_UNUSED(value);
+
+	/* Keyscan is input-only */
+	return -ENOTSUP;
+}
+
+static int gpio_ht16k33_read(struct device *dev, int access_op,
+			     u32_t pin, u32_t *value)
+{
+	ARG_UNUSED(dev);
+	ARG_UNUSED(access_op);
+	ARG_UNUSED(pin);
+	ARG_UNUSED(value);
+
+	/* Keyscan only supports interrupt mode */
+	return -ENOTSUP;
+}
+
+void ht16k33_process_keyscan_row_data(struct device *dev,
+				      u32_t keys)
+{
+	struct gpio_ht16k33_data *data = dev->driver_data;
+
+	gpio_fire_callbacks(&data->callbacks, dev, keys);
+}
+
+static int gpio_ht16k33_manage_callback(struct device *dev,
+					struct gpio_callback *callback,
+					bool set)
+{
+	struct gpio_ht16k33_data *data = dev->driver_data;
+
+	return gpio_manage_callback(&data->callbacks, callback, set);
+}
+
+static int gpio_ht16k33_enable_callback(struct device *dev,
+					int access_op,
+					u32_t pin)
+{
+	/* All callbacks are always enabled */
+	return 0;
+}
+
+static int gpio_ht16k33_disable_callback(struct device *dev,
+					int access_op,
+					u32_t pin)
+{
+	/* Individual callbacks can not be disabled */
+	return -ENOTSUP;
+}
+
+static u32_t gpio_ht16k33_get_pending_int(struct device *dev)
+{
+	struct gpio_ht16k33_data *data = dev->driver_data;
+
+	return ht16k33_get_pending_int(data->parent);
+}
+
+static int gpio_ht16k33_init(struct device *dev)
+{
+	const struct gpio_ht16k33_cfg *config = dev->config->config_info;
+	struct gpio_ht16k33_data *data = dev->driver_data;
+
+	if (config->keyscan_idx >= HT16K33_KEYSCAN_ROWS) {
+		LOG_ERR("HT16K33 keyscan index out of bounds (%d)",
+			config->keyscan_idx);
+		return -EINVAL;
+	}
+
+	/* Establish reference to parent and vice versa */
+	data->parent = device_get_binding(config->parent_dev_name);
+	if (!data->parent) {
+		LOG_ERR("HT16K33 parent device '%s' not found",
+			config->parent_dev_name);
+		return -EINVAL;
+	}
+
+	return ht16k33_register_keyscan_device(data->parent, dev,
+					       config->keyscan_idx);
+}
+
+static const struct gpio_driver_api gpio_ht16k33_api = {
+	.config = gpio_ht16k33_cfg,
+	.write = gpio_ht16k33_write,
+	.read = gpio_ht16k33_read,
+	.manage_callback = gpio_ht16k33_manage_callback,
+	.enable_callback = gpio_ht16k33_enable_callback,
+	.disable_callback = gpio_ht16k33_disable_callback,
+	.get_pending_int = gpio_ht16k33_get_pending_int,
+};
+
+#define GPIO_HT16K33_DEVICE(id)						\
+	static const struct gpio_ht16k33_cfg gpio_ht16k33_##id##_cfg = {\
+		.parent_dev_name =					\
+			DT_HOLTEK_HT16K33_KEYSCAN_##id##_BUS_NAME,	\
+		.keyscan_idx     =					\
+			DT_HOLTEK_HT16K33_KEYSCAN_##id##_BASE_ADDRESS,	\
+	};								\
+									\
+	static struct gpio_ht16k33_data gpio_ht16k33_##id##_data;	\
+									\
+	DEVICE_AND_API_INIT(gpio_ht16k33_##id,				\
+			    DT_HOLTEK_HT16K33_KEYSCAN_##id##_LABEL,	\
+			    &gpio_ht16k33_init,				\
+			    &gpio_ht16k33_##id##_data,			\
+			    &gpio_ht16k33_##id##_cfg, POST_KERNEL,	\
+			    CONFIG_GPIO_HT16K33_INIT_PRIORITY,		\
+			    &gpio_ht16k33_api)
+
+/* Support up to eight HT16K33 devices, each with three keyscan devices */
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_0
+GPIO_HT16K33_DEVICE(0);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_1
+GPIO_HT16K33_DEVICE(1);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_2
+GPIO_HT16K33_DEVICE(2);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_3
+GPIO_HT16K33_DEVICE(3);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_4
+GPIO_HT16K33_DEVICE(4);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_5
+GPIO_HT16K33_DEVICE(5);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_6
+GPIO_HT16K33_DEVICE(6);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_7
+GPIO_HT16K33_DEVICE(7);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_8
+GPIO_HT16K33_DEVICE(8);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_9
+GPIO_HT16K33_DEVICE(9);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_10
+GPIO_HT16K33_DEVICE(10);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_11
+GPIO_HT16K33_DEVICE(11);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_12
+GPIO_HT16K33_DEVICE(12);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_13
+GPIO_HT16K33_DEVICE(13);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_14
+GPIO_HT16K33_DEVICE(14);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_15
+GPIO_HT16K33_DEVICE(15);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_16
+GPIO_HT16K33_DEVICE(16);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_17
+GPIO_HT16K33_DEVICE(17);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_18
+GPIO_HT16K33_DEVICE(18);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_19
+GPIO_HT16K33_DEVICE(19);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_20
+GPIO_HT16K33_DEVICE(20);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_21
+GPIO_HT16K33_DEVICE(21);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_22
+GPIO_HT16K33_DEVICE(22);
+#endif
+
+#ifdef DT_HOLTEK_HT16K33_KEYSCAN_23
+GPIO_HT16K33_DEVICE(23);
+#endif
+
diff --git a/drivers/led/Kconfig.ht16k33 b/drivers/led/Kconfig.ht16k33
index 32af881..c2a0da4 100644
--- a/drivers/led/Kconfig.ht16k33
+++ b/drivers/led/Kconfig.ht16k33
@@ -13,3 +13,47 @@
 	  The HT16K33 is a memory mapping, multifunction LED
 	  controller driver. The controller supports up to 128 LEDs
 	  (up to 16 rows and 8 commons).
+
+config HT16K33_KEYSCAN
+	bool "Enable keyscan support"
+	depends on (HT16K33 && GPIO)
+	select GPIO_HT16K33
+	help
+	  Enable keyscan child device support in the HT16K33 LED
+	  driver.
+
+	  The keyscan functionality itself is handled by the
+	  HT16K33 GPIO driver.
+
+if HT16K33_KEYSCAN
+
+config HT16K33_KEYSCAN_IRQ_THREAD_STACK_SIZE
+	int "Stack size for keyscan interrupt request handler thread"
+	default 400
+	help
+	  Size of the stack used for internal thread for keyscan
+	  interrupt processing.
+
+config HT16K33_KEYSCAN_IRQ_THREAD_PRIO
+	int "Priority for keyscan interrupt request handler thread"
+	default 2
+	help
+	  Priority level for internal thread for keyscan interrupt
+	  processing.
+
+config HT16K33_KEYSCAN_DEBOUNCE_MSEC
+	int "Keyscan debounce interval in milliseconds"
+	default 50
+	range 20 1000
+	help
+	  Keyscan debounce interval in milliseconds.
+
+config HT16K33_KEYSCAN_POLL_MSEC
+	int "Keyscan poll interval in milliseconds"
+	default 200
+	range 20 10000
+	help
+	  Keyscan poll interval in milliseconds. Polling is only used
+	  if no interrupt line is present.
+
+endif #HT16K33_KEYSCAN
diff --git a/drivers/led/ht16k33.c b/drivers/led/ht16k33.c
index 7728b60..9119eda 100644
--- a/drivers/led/ht16k33.c
+++ b/drivers/led/ht16k33.c
@@ -9,14 +9,19 @@
  * @brief LED driver for the HT16K33 I2C LED driver with keyscan
  */
 
+#include <gpio.h>
 #include <i2c.h>
+#include <kernel.h>
 #include <led.h>
+#include <misc/byteorder.h>
 #include <zephyr.h>
 
 #define LOG_LEVEL CONFIG_LED_LOG_LEVEL
 #include <logging/log.h>
 LOG_MODULE_REGISTER(ht16k33);
 
+#include <led/ht16k33.h>
+
 #include "led_context.h"
 
 /* HT16K33 commands and options */
@@ -53,10 +58,19 @@
 #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 {
 	char *i2c_dev_name;
 	u16_t i2c_addr;
+	bool irq_enabled;
+#ifdef CONFIG_HT16K33_KEYSCAN
+	char *irq_dev_name;
+	u32_t irq_pin;
+	int irq_flags;
+#endif /* CONFIG_HT16K33_KEYSCAN */
 };
 
 struct ht16k33_data {
@@ -64,6 +78,18 @@
 	struct led_data dev_data;
 	 /* Shadow buffer for the display data RAM */
 	u8_t buffer[HT16K33_DISP_DATA_SIZE];
+#ifdef CONFIG_HT16K33_KEYSCAN
+	struct k_mutex lock;
+	struct device *children[HT16K33_KEYSCAN_ROWS];
+	struct gpio_callback irq_cb;
+	struct k_thread irq_thread;
+	struct k_sem irq_sem;
+	struct k_timer timer;
+	u16_t key_state[HT16K33_KEYSCAN_ROWS];
+
+	K_THREAD_STACK_MEMBER(irq_thread_stack,
+			      CONFIG_HT16K33_KEYSCAN_IRQ_THREAD_STACK_SIZE);
+#endif /* CONFIG_HT16K33_KEYSCAN */
 };
 
 static int ht16k33_led_blink(struct device *dev, u32_t led,
@@ -175,6 +201,122 @@
 	return ht16k33_led_set_state(dev, led, false);
 }
 
+#ifdef CONFIG_HT16K33_KEYSCAN
+u32_t ht16k33_get_pending_int(struct device *dev)
+{
+	const struct ht16k33_cfg *config = dev->config->config_info;
+	struct ht16k33_data *data = dev->driver_data;
+	u8_t cmd;
+	u8_t flag;
+	int err;
+
+	cmd = HT16K33_CMD_INT_FLAG_ADDR;
+	err = i2c_write_read(data->i2c, config->i2c_addr, &cmd, sizeof(cmd),
+			     &flag, sizeof(flag));
+	if (err) {
+		LOG_ERR("Failed to to read HT16K33 IRQ flag");
+		return 0;
+	}
+
+	return (flag ? 1 : 0);
+}
+
+static bool ht16k33_process_keyscan_data(struct device *dev)
+{
+	const struct ht16k33_cfg *config = dev->config->config_info;
+	struct ht16k33_data *data = dev->driver_data;
+	u8_t keys[HT16K33_KEYSCAN_DATA_SIZE];
+	bool pressed;
+	u16_t row;
+	u16_t new;
+	int err;
+	int i;
+
+	err = i2c_burst_read(data->i2c, config->i2c_addr,
+			     HT16K33_CMD_KEY_DATA_ADDR, keys,
+			     sizeof(keys));
+	if (err) {
+		LOG_ERR("Failed to to read HT16K33 key data (err %d)", err);
+		return false;
+	}
+
+	k_mutex_lock(&data->lock, K_FOREVER);
+	for (i = 0; i < HT16K33_KEYSCAN_ROWS; i++) {
+		row = sys_get_le16(&keys[i * 2]);
+		if (row) {
+			pressed = true;
+			new = data->key_state[i] ^ row;
+			new &= row;
+			if (data->children[i] && new) {
+				ht16k33_process_keyscan_row_data(
+					data->children[i], new);
+			}
+		}
+		data->key_state[i] = row;
+	}
+	k_mutex_unlock(&data->lock);
+
+	return pressed;
+}
+
+static void ht16k33_irq_thread(struct device *dev)
+{
+	struct ht16k33_data *data = dev->driver_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(dev);
+			k_sleep(CONFIG_HT16K33_KEYSCAN_DEBOUNCE_MSEC);
+		} while (pressed);
+	}
+}
+
+static void ht16k33_irq_callback(struct device *gpiob,
+				 struct gpio_callback *cb, u32_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_device(struct device *parent,
+					   struct device *child,
+					   u8_t keyscan_idx)
+{
+	struct ht16k33_data *data = parent->driver_data;
+
+	k_mutex_lock(&data->lock, K_FOREVER);
+
+	if (data->children[keyscan_idx]) {
+		k_mutex_unlock(&data->lock);
+		LOG_ERR("HT16K33 keyscan device %d already registered",
+			keyscan_idx);
+		return -EINVAL;
+	}
+
+	data->children[keyscan_idx] = child;
+	k_mutex_unlock(&data->lock);
+
+	return 0;
+}
+#endif /* CONFIG_HT16K33_KEYSCAN */
+
 static int ht16k33_init(struct device *dev)
 {
 	const struct ht16k33_cfg *config = dev->config->config_info;
@@ -231,6 +373,86 @@
 		return -EIO;
 	}
 
+#ifdef CONFIG_HT16K33_KEYSCAN
+	memset(&data->children, 0, sizeof(data->children));
+	k_mutex_init(&data->lock);
+	k_sem_init(&data->irq_sem, 0, 1);
+
+	/* Configure interrupt */
+	if (config->irq_enabled) {
+		struct device *irq_dev;
+		u8_t keys[HT16K33_KEYSCAN_DATA_SIZE];
+
+		irq_dev = device_get_binding(config->irq_dev_name);
+		if (!irq_dev) {
+			LOG_ERR("IRQ device '%s' not found",
+				config->irq_dev_name);
+			return -EINVAL;
+		}
+
+		err = gpio_pin_configure(irq_dev, config->irq_pin,
+					 GPIO_DIR_IN | GPIO_INT |
+					 GPIO_INT_EDGE | config->irq_flags);
+		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(irq_dev, &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;
+		if (config->irq_flags & GPIO_INT_ACTIVE_HIGH) {
+			cmd[0] |= HT16K33_OPT_INT_HIGH;
+		} else {
+			cmd[0] |= HT16K33_OPT_INT_LOW;
+		}
+		if (i2c_write(data->i2c, cmd, 1, config->i2c_addr)) {
+			LOG_ERR("Enabling HT16K33 IRQ output failed");
+			return -EIO;
+		}
+
+		/* Flush key data before enabling interrupt */
+		err = i2c_burst_read(data->i2c, 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_enable_callback(irq_dev, config->irq_pin);
+		if (err) {
+			LOG_ERR("Failed to enable IRQ callback (err %d)", err);
+			return -EINVAL;
+		}
+	} else {
+		/* No interrupt pin, enable ROW15 */
+		cmd[0] = HT16K33_CMD_ROW_INT_SET | HT16K33_OPT_ROW;
+		if (i2c_write(data->i2c, 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, 0,
+			      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, dev, NULL, NULL,
+			K_PRIO_COOP(CONFIG_HT16K33_KEYSCAN_IRQ_THREAD_PRIO),
+			0, K_NO_WAIT);
+#endif /* CONFIG_HT16K33_KEYSCAN */
+
 	return 0;
 }
 
@@ -245,6 +467,7 @@
 	static const struct ht16k33_cfg ht16k33_##id##_cfg = {		\
 		.i2c_dev_name = DT_HOLTEK_HT16K33_##id##_BUS_NAME,	\
 		.i2c_addr     = DT_HOLTEK_HT16K33_##id##_BASE_ADDRESS,	\
+		.irq_enabled  = false,					\
 	};								\
 									\
 static struct ht16k33_data ht16k33_##id##_data;				\
@@ -254,36 +477,91 @@
 		    &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_name = DT_HOLTEK_HT16K33_##id##_BUS_NAME,	\
+		.i2c_addr     = DT_HOLTEK_HT16K33_##id##_BASE_ADDRESS,	\
+		.irq_enabled  = true,					\
+		.irq_dev_name =						\
+			DT_HOLTEK_HT16K33_##id##_IRQ_GPIOS_CONTROLLER,	\
+		.irq_pin      = DT_HOLTEK_HT16K33_##id##_IRQ_GPIOS_PIN,	\
+		.irq_flags    =						\
+			DT_HOLTEK_HT16K33_##id##_IRQ_GPIOS_FLAGS,	\
+	};								\
+									\
+static struct ht16k33_data ht16k33_##id##_data;				\
+									\
+DEVICE_AND_API_INIT(ht16k33_##id, DT_HOLTEK_HT16K33_##id##_LABEL,	\
+		    &ht16k33_init, &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 */
+
 /* Support up to eight HT16K33 devices */
 
 #ifdef DT_HOLTEK_HT16K33_0
+#ifdef DT_HOLTEK_HT16K33_0_IRQ_GPIOS_CONTROLLER
+HT16K33_DEVICE_WITH_IRQ(0);
+#else
 HT16K33_DEVICE(0);
 #endif
+#endif
 
 #ifdef DT_HOLTEK_HT16K33_1
+#ifdef DT_HOLTEK_HT16K33_1_IRQ_GPIOS_CONTROLLER
+HT16K33_DEVICE_WITH_IRQ(1);
+#else
 HT16K33_DEVICE(1);
 #endif
+#endif
 
 #ifdef DT_HOLTEK_HT16K33_2
+#ifdef DT_HOLTEK_HT16K33_2_IRQ_GPIOS_CONTROLLER
+HT16K33_DEVICE_WITH_IRQ(2);
+#else
 HT16K33_DEVICE(2);
 #endif
+#endif
 
 #ifdef DT_HOLTEK_HT16K33_3
+#ifdef DT_HOLTEK_HT16K33_3_IRQ_GPIOS_CONTROLLER
+HT16K33_DEVICE_WITH_IRQ(3);
+#else
 HT16K33_DEVICE(3);
 #endif
+#endif
 
 #ifdef DT_HOLTEK_HT16K33_4
+#ifdef DT_HOLTEK_HT16K33_4_IRQ_GPIOS_CONTROLLER
+HT16K33_DEVICE_WITH_IRQ(4);
+#else
 HT16K33_DEVICE(4);
 #endif
+#endif
 
 #ifdef DT_HOLTEK_HT16K33_5
+#ifdef DT_HOLTEK_HT16K33_5_IRQ_GPIOS_CONTROLLER
+HT16K33_DEVICE_WITH_IRQ(5);
+#else
 HT16K33_DEVICE(5);
 #endif
+#endif
 
 #ifdef DT_HOLTEK_HT16K33_6
+#ifdef DT_HOLTEK_HT16K33_6_IRQ_GPIOS_CONTROLLER
+HT16K33_DEVICE_WITH_IRQ(6);
+#else
 HT16K33_DEVICE(6);
 #endif
+#endif
 
 #ifdef DT_HOLTEK_HT16K33_7
+#ifdef DT_HOLTEK_HT16K33_7_IRQ_GPIOS_CONTROLLER
+HT16K33_DEVICE_WITH_IRQ(7);
+#else
 HT16K33_DEVICE(7);
 #endif
+#endif
diff --git a/dts/bindings/gpio/holtek,ht16k33-keyscan.yaml b/dts/bindings/gpio/holtek,ht16k33-keyscan.yaml
new file mode 100644
index 0000000..9d13499
--- /dev/null
+++ b/dts/bindings/gpio/holtek,ht16k33-keyscan.yaml
@@ -0,0 +1,34 @@
+---
+title: Holtek HT16K33 LED Driver With Keyscan
+version: 0.1
+
+description: Holtek HT16K33 Keyscan binding
+
+parent:
+    bus: ht16k33
+
+properties:
+    compatible:
+      type: string
+      category: required
+      description: compatible strings
+      constraint: "holtek,ht16k33-keyscan"
+      generation: define
+    reg:
+      type: array
+      description: Keyscan row on the HT16K33 (KSx)
+      generation: define
+      category: required
+    label:
+      type: string
+      category: required
+      description: Human readable string describing the device (used by Zephyr for API name)
+      generation: define
+
+cell_string: GPIO
+
+"#cells":
+  - pin
+  - flags
+
+...
diff --git a/dts/bindings/led/holtek,ht16k33.yaml b/dts/bindings/led/holtek,ht16k33.yaml
index f44abe8..475393b 100644
--- a/dts/bindings/led/holtek,ht16k33.yaml
+++ b/dts/bindings/led/holtek,ht16k33.yaml
@@ -7,6 +7,9 @@
 inherits:
     !include i2c-device.yaml
 
+child:
+    bus: ht16k33
+
 properties:
     compatible:
       type: string
@@ -14,10 +17,23 @@
       description: compatible strings
       constraint: "holtek,ht16k33"
       generation: define
-
+    "#address-cells":
+      type: int
+      category: required
+      description: should be 1.
+    "#size-cells":
+      type: int
+      category: required
+      description: should be 0.
     label:
       type: string
       category: required
       description: Human readable string describing the device (used by Zephyr for API name)
       generation: define
+    irq-gpios:
+      type: compound
+      category: optional
+      description: IRQ pin
+      generation: define, use-prop-name
+
 ...
diff --git a/include/drivers/led/ht16k33.h b/include/drivers/led/ht16k33.h
new file mode 100644
index 0000000..ca6144b
--- /dev/null
+++ b/include/drivers/led/ht16k33.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2019 Henrik Brix Andersen <henrik@brixandersen.dk>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+
+#ifndef ZEPHYR_INCLUDE_DRIVERS_LED_HT16K33_H_
+#define ZEPHYR_INCLUDE_DRIVERS_LED_HT16K33_H_
+
+#include <device.h>
+#include <zephyr/types.h>
+
+/**
+ * Register a HT16K33 keyscan device to be notified of relevant
+ * keyscan events by the keyscan interrupt thread in the HT16K33
+ * parent driver.
+ *
+ * @param parent      HT16K33 parent device.
+ * @param child       HT16K33 keyscan child device.
+ * @param keyscan_idx Index of the keyscan line handled by the keyscan
+ *		      child device (0, 1, or 2).
+ * @return 0 if successful, negative errne code on failure.
+ */
+int ht16k33_register_keyscan_device(struct device *parent,
+				    struct device *child,
+				    u8_t keyscan_idx);
+
+/**
+ * Check if a HT16K33 keyscan interrupt is pending.
+ *
+ * @param  parent HT16K33 parent device.
+ * @return status != 0 if an interrupt is pending.
+ */
+u32_t ht16k33_get_pending_int(struct device *parent);
+
+/**
+ * Dispatch keyscan row data from a keyscan event to be handled by a
+ * HT16K33 keyscan GPIO child device.
+ *
+ * @param child HT16K33 keyscan child device.
+ * @param keys  Bitmask of key state for the row.
+ */
+void ht16k33_process_keyscan_row_data(struct device *child,
+				      u32_t keys);
+
+#endif /* ZEPHYR_INCLUDE_DRIVERS_LED_HT16K33_H_ */