From a830d59da252e3ee8b559f09cb1473e7c78c9ce9 Mon Sep 17 00:00:00 2001
From: Gregory Nutt <gnutt@nuttx.org>
Date: Tue, 15 Jan 2019 08:55:53 -0600
Subject: [PATCH] This commit brings in a complete, but untested,
 implementation of a driver for the TI TDA19988 HDMI Encoder.  This encoder is
 used on the Beaglebone Black board.  Since this driver is untested, it
 requires CONFIG_EXPERIMENTAL in the configuration.

Squashed commit of the following:

    drivers/lcd/tda19988.c:  Numerous changes for a clean compilation.

    drivers/lcd/tda19988:  Bring in the final pieces from the FreeBSD TDA19988 driver.  The driver is now code complete.

    drivers/lcd/tda19988.c:  Flesh on EDID read logic (still not complete).  Add unlink shutdown logic.

    drivers/lcd/tda19988.c:  Add logic to read one EDID block.

    drivers/lcd/tda19988.c:  Add tda19988_read_edid() interface.  Flesh read() and seek() methods.

    drivers/lcd/st7032.c:  Fix another error in seek method:  Does not account for negative offsets!

    drivers/lcd/tda19988.c:  Add basic IOCTL infrastructure to support configuratin the video mode.  Mostly just a framework for now.

    drivers/lcd/tda19988.c:  Add more register definitions.  Encode page+address in the same was as FreeBSD.

    drivers/lcd/tda19988.c:  Add support for CEC messaging.

    drivers/lcd/tda19988.c:  Implement the unlink method.  No IOCTLs defined yet.

    drivers/lcd/tda19988.c:  Add placeholders for missing character driver methods.

    drivers/lcd/tda19988.c:  Trivial interface improvement to permit reading multiple TDA19988 registers.

    drivers/lcd/tda19988.c:  Feeble start of TCA19988 drivers.  Consists of lower half interface definition and some I2C helper functions.  Nothing more.
---
 drivers/lcd/Kconfig           |   23 +
 drivers/lcd/Make.defs         |   10 +
 drivers/lcd/tda19988.c        | 1817 +++++++++++++++++++++++++++++++++
 drivers/lcd/tda19988.h        |  313 ++++++
 include/nuttx/lcd/lcd_ioctl.h |   11 +-
 include/nuttx/lcd/tda19988.h  |  237 +++++
 6 files changed, 2408 insertions(+), 3 deletions(-)
 create mode 100644 drivers/lcd/tda19988.c
 create mode 100644 drivers/lcd/tda19988.h
 create mode 100644 include/nuttx/lcd/tda19988.h

diff --git a/drivers/lcd/Kconfig b/drivers/lcd/Kconfig
index c045ec21dc..bf18d09e74 100644
--- a/drivers/lcd/Kconfig
+++ b/drivers/lcd/Kconfig
@@ -1147,6 +1147,8 @@ endif # LCD_FT80X
 endmenu # LCD Driver selection
 endif # LCD
 
+comment "Character/Segment LCD Devices"
+
 menuconfig SLCD
 	bool "Alphanumeric/Segment LCD Devices"
 	default n
@@ -1212,4 +1214,25 @@ config LCD_ST7032
 		the ground. So only I2C pins SDA and SCL are used by NuttX.
 
 endif # SLCD
+
+comment "Other LCD-related Devices"
+
+config LCD_OTHER
+	bool
+	default n
+
+config LCD_TDA19988
+	bool "TDA19988 HDMI Encoder"
+	default n
+	select LCD_OTHER
+	depends on I2C && EXPERIMENTAL
+	---help---
+		Enable support for the TI TDA19988 HDMI Encoder.  The TDA19988 is a
+		very low power and very small size High-Definition Multimedia
+		Interface (HDMI) 1.4a transmitter.  This device is primarily
+		intended for mobile applications like Digital Video Camera (DVC),
+		Digital Still Camera (DSC), Portable Multimedia Player (PMP), Mobile
+		Phone and Ultra-Mobile Personal Computer (UM PC), new PC tablet and
+		MID where size and power are key for battery autonomy.
+
 endmenu # LCD Driver Support
diff --git a/drivers/lcd/Make.defs b/drivers/lcd/Make.defs
index 5c35e7ae0a..21535fcba7 100644
--- a/drivers/lcd/Make.defs
+++ b/drivers/lcd/Make.defs
@@ -134,6 +134,12 @@ ifeq ($(CONFIG_LCD_ST7032),y)
 endif
 endif # CONFIG_SLCD
 
+# Other LCD-related devices
+
+ifeq ($(CONFIG_LCD_TDA19988),y)
+  CSRCS += tda19988.c
+endif
+
 # Include LCD driver build support (the nested if-then-else implements an OR)
 
 ifeq ($(CONFIG_LCD),y)
@@ -144,4 +150,8 @@ else ifeq ($(CONFIG_SLCD),y)
   DEPPATH += --dep-path lcd
   VPATH += :lcd
   CFLAGS += ${shell $(INCDIR) $(INCDIROPT) "$(CC)" $(TOPDIR)$(DELIM)drivers$(DELIM)lcd}
+else ifeq ($(CONFIG_LCD_OTHER),y)
+  DEPPATH += --dep-path lcd
+  VPATH += :lcd
+  CFLAGS += ${shell $(INCDIR) $(INCDIROPT) "$(CC)" $(TOPDIR)$(DELIM)drivers$(DELIM)lcd}
 endif
diff --git a/drivers/lcd/tda19988.c b/drivers/lcd/tda19988.c
new file mode 100644
index 0000000000..f38bcc9e66
--- /dev/null
+++ b/drivers/lcd/tda19988.c
@@ -0,0 +1,1817 @@
+/****************************************************************************
+ * drivers/lcd/tda19988.c
+ *
+ *   Copyright (C) 2019 Gregory Nutt. All rights reserved.
+ *   Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * Derives rather loosely from the FreeBSD driver which has a compatible
+ * two-clause BSD license:
+ *
+ *   Copyright (c) 2015 Oleksandr Tymoshenko <gonzo@freebsd.org>
+ *   All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ *    used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <string.h>
+#include <poll.h>
+#include <semaphore.h>
+#include <errno.h>
+
+#include <nuttx/kmalloc.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/fs/fs.h>
+#include <nuttx/drivers/drivers.h>
+#include <nuttx/lcd/tda19988.h>
+
+#include "tda19988.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Returned values from tda19988_connected() */
+
+#define DISPLAY_CONNECTED 0
+#define DISPLAY_DETACHED  1
+
+/* Number of times to try reading EDID */
+
+#define MAX_READ_ATTEMPTS  100
+
+#define HDMI_CTRL_CEC_ENAMODS            0xff
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* This structure represents the state of one TDA19988 driver instance */
+
+struct tda1988_dev_s
+{
+  /* The contained lower half driver instance */
+
+  FAR const struct tda19988_lower_s *lower;
+
+  /* Upper half driver state */
+
+  sem_t exclsem;              /* Assures exclusive access to the driver */
+  uint8_t page;               /* Currently selected page */
+  uint8_t crefs;              /* Number of open references */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  bool unlinked;              /* True, driver has been unlinked */
+#endif
+  uint16_t version;           /* TDA19988 version */
+  FAR uint8_t *edid;          /* Extended Display Identification Data */
+  uint32_t edid_len;          /* Size of EDID */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* General I2C Helpers */
+
+static int     tda19988_getregs(FAR const struct tda19988_i2c_s *dev,
+                 uint8_t regaddr, FAR uint8_t *regval, int nregs);
+static int     tda19988_putreg(FAR const struct tda19988_i2c_s *dev,
+                 uint8_t regaddr, uint8_t regval);
+static int     tda19988_putreg16(FAR const struct tda19988_i2c_s *dev,
+                 uint8_t regaddr, uint16_t regval);
+static int     tda19988_modifyreg(FAR const struct tda19988_i2c_s *dev,
+                 uint8_t regaddr, uint8_t clrbits, uint8_t setbits);
+
+/* CEC I2C Helpers */
+
+static inline int tda19988_cec_getregs(FAR struct tda1988_dev_s *priv,
+                 uint8_t regaddr, FAR uint8_t *regval, int nregs);
+static inline int tda19988_cec_putreg(FAR struct tda1988_dev_s *priv,
+                 uint8_t regaddr, uint8_t regval);
+static inline int tda19988_cec_modifyreg(FAR struct tda1988_dev_s *priv,
+                 uint8_t regaddr, uint8_t clrbits, uint8_t setbits);
+
+/* HDMI I2C Helpers */
+
+static int     tda19988_select_page(FAR struct tda1988_dev_s *priv,
+                 uint8_t page);
+static int     tda19988_hdmi_getregs(FAR struct tda1988_dev_s *priv,
+                 uint16_t reginfo, FAR uint8_t *regval, int nregs);
+static int     tda19988_hdmi_putreg(FAR struct tda1988_dev_s *priv,
+                 uint16_t reginfo, uint8_t regval);
+static int     tda19988_hdmi_putreg16(FAR struct tda1988_dev_s *priv,
+                 uint16_t reginfo, uint16_t regval);
+static int     tda19988_hdmi_modifyreg(FAR struct tda1988_dev_s *priv,
+                 uint16_t reginfo, uint8_t clrbits, uint8_t setbits);
+
+/* CEC Module Helpers */
+
+#if 0 /* Not used */
+static int     tda19988_connected(FAR struct tda1988_dev_s *priv);
+#endif
+
+/* HDMI Module Helpers */
+
+static int     tda19988_fetch_edid_block(FAR struct tda1988_dev_s *priv,
+                 FAR uint8_t *buf, int block);
+static int     tda19988_fetch_edid(struct tda1988_dev_s *priv);
+static ssize_t tda19988_read_internal(FAR struct tda1988_dev_s *priv,
+                 off_t offset, FAR uint8_t *buffer, size_t buflen);
+
+/* Character driver methods */
+
+static int     tda19988_open(FAR struct file *filep);
+static int     tda19988_close(FAR struct file *filep);
+static ssize_t tda19988_read(FAR struct file *filep, FAR char *buffer,
+                 size_t buflen);
+static ssize_t tda19988_write(FAR struct file *filep, FAR const char *buffer,
+                 size_t buflen);
+static off_t   tda19988_seek(FAR struct file *filep, off_t offset, int whence);
+static int     tda19988_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
+#ifndef CONFIG_DISABLE_POLL
+static int     tda19988_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                 bool setup);
+#endif
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+static int     tda19988_unlink(FAR struct inode *inode);
+#endif
+
+/* Initialization */
+
+static int     tda19988_hwinitialize(FAR struct tda1988_dev_s *priv);
+static int     tda19988_videomode_internal(FAR struct tda1988_dev_s *priv,
+                 FAR const struct tda19988_videomode_s *mode);
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+static void    tda19988_shutdown(FAR struct tda1988_dev_s *priv);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations tda19988_fops =
+{
+  tda19988_open,     /* open */
+  tda19988_close,    /* close */
+  tda19988_read,     /* read */
+  tda19988_write,    /* write */
+  tda19988_seek,     /* seek */
+  tda19988_ioctl     /* ioctl */
+#ifndef CONFIG_DISABLE_POLL
+  , tda19988_poll    /* poll */
+#endif
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , tda19988_unlink  /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: tda19988_getregs
+ *
+ * Description:
+ *   Read the value from one or more TDA19988 CEC or HDMI registers
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_getregs(FAR const struct tda19988_i2c_s *dev,
+                            uint8_t regaddr, FAR uint8_t *regval, int nregs)
+{
+  uint8_t buffer[1];
+  int ret;
+
+  DEBUGASSERT(dev != NULL && regval != NULL && nregs > 0);
+
+  /* Write the register address and read the register value */
+
+  buffer[0] = regaddr;
+  ret = i2c_writeread(dev->i2c, &dev->config, buffer, 1, regval, nregs);
+  if (ret < 0)
+    {
+      lcderr("ERROR: i2c_writeread() failed: %d\n", ret);
+      return -1;
+    }
+
+  lcdinfo("Read: %02x:%02x->%02x\n", page, regaddr, *regval);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_putreg
+ *
+ * Description:
+ *   Write an 8-bit value to one TDA19988 CEC or HDMI register
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_putreg(FAR const struct tda19988_i2c_s *dev,
+                           uint8_t regaddr, uint8_t regval)
+{
+  uint8_t buffer[2];
+  int ret;
+
+  /* Write the register address and the register value */
+
+  buffer[0] = regaddr;
+  buffer[1] = regval;
+
+  ret = i2c_write(dev->i2c, &dev->config, buffer, 2);
+  if (ret < 0)
+    {
+      lcderr("ERROR: i2c_write() failed: %d\n", ret);
+      return ret;
+    }
+
+  lcdinfo("Wrote: %02x:%02x<-%02x\n", page, regaddr, regval);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_putreg16
+ *
+ * Description:
+ *   Write a 16-bit value to one TDA19988 CEC or HDMI register
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_putreg16(FAR const struct tda19988_i2c_s *dev,
+                             uint8_t regaddr, uint16_t regval)
+{
+  uint8_t buffer[3];
+  int ret;
+
+  /* Write the register address and the register value */
+
+  buffer[0] = regaddr;
+  buffer[1] = (regval >> 8);
+  buffer[2] = (regval & 0xff);
+
+  ret = i2c_write(dev->i2c, &dev->config, buffer, 3);
+  if (ret < 0)
+    {
+      lcderr("ERROR: i2c_write() failed: %d\n", ret);
+      return ret;
+    }
+
+  lcdinfo("Wrote: %02x:%02x<-%02x\n", page, regaddr, regval);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_modifyreg
+ *
+ * Description:
+ *   Modify bits in one TDA19988 CEC or HDMI register
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_modifyreg(FAR const struct tda19988_i2c_s *dev,
+                              uint8_t regaddr, uint8_t clrbits,
+                              uint8_t setbits)
+{
+  uint8_t regval;
+  int ret;
+
+  /* Read the register contents */
+
+  ret = tda19988_getregs(dev, regaddr, &regval, 1);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_getregs failed: %d\n", ret);
+      return ret;
+    }
+
+  /* Modify the register content */
+
+  regval &= ~clrbits;
+  regval |= setbits;
+
+  /* Write the modified register content */
+
+  ret = tda19988_putreg(dev, regaddr, regval);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_putreg failed: %d\n", ret);
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_cec_getregs
+ *
+ * Description:
+ *   Read the value from one or more TDA19988 CEC registers
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static inline int tda19988_cec_getregs(FAR struct tda1988_dev_s *priv,
+                                       uint8_t regaddr, FAR uint8_t *regval,
+                                       int nregs)
+{
+  return tda19988_getregs(&priv->lower->cec, regaddr, regval, nregs);
+}
+
+/****************************************************************************
+ * Name: tda19988_cec_putreg
+ *
+ * Description:
+ *   Write a value to one TDA19988 CEC register
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static inline int tda19988_cec_putreg(FAR struct tda1988_dev_s *priv,
+                                      uint8_t regaddr, uint8_t regval)
+{
+  return tda19988_putreg(&priv->lower->cec, regaddr, regval);
+}
+
+/****************************************************************************
+ * Name: tda19988_cec_modifyreg
+ *
+ * Description:
+ *   Modify bits in one TDA19988 CEC register
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static inline int tda19988_cec_modifyreg(FAR struct tda1988_dev_s *priv,
+                                         uint8_t regaddr, uint8_t clrbits,
+                                         uint8_t setbits)
+{
+  return tda19988_modifyreg(&priv->lower->cec, regaddr, clrbits, setbits);
+}
+
+/****************************************************************************
+ * Name: tda19988_select_page
+ *
+ * Description:
+ *   Select the HDMI page (if not already selected)
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_select_page(FAR struct tda1988_dev_s *priv, uint8_t page)
+{
+  int ret = OK;
+
+  /* Check if we need to select a new page for this transfer */
+
+  if (page != HDMI_NO_PAGE && page != priv->page)
+    {
+      ret = tda19988_putreg(&priv->lower->hdmi,
+                            REGADDR(HDMI_PAGE_SELECT_REG), page);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: tda19988_hdmi_getregs
+ *
+ * Description:
+ *   Read the value from one or more TDA19988 HDMI registers
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_hdmi_getregs(FAR struct tda1988_dev_s *priv,
+                                 uint16_t reginfo, FAR uint8_t *regval,
+                                 int nregs)
+{
+  uint8_t page    = REGPAGE(reginfo);
+  uint8_t regaddr = REGADDR(reginfo);
+  int ret;
+
+  DEBUGASSERT(priv != NULL && regval != NULL && nregs > 0);
+
+  /* Select the HDMI page */
+
+  ret = tda19988_select_page(priv, page);
+  if (ret < 0)
+    {
+      lcderr("ERROR: Failed to select page %02x: %d\n", page, ret);
+      return ret;
+    }
+
+  /* Write the register address and read the register value */
+
+  ret = tda19988_getregs(&priv->lower->hdmi, regaddr, regval, nregs);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_getregs() failed: %d\n", ret);
+      return -1;
+    }
+
+  lcdinfo("Read: %02x:%02x->%02x\n", page, regaddr, *regval);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_hdmi_putreg
+ *
+ * Description:
+ *   Write an 8-bit value to one TDA19988 HDMI register
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_hdmi_putreg(FAR struct tda1988_dev_s *priv,
+                                uint16_t reginfo, uint8_t regval)
+{
+  uint8_t page    = REGPAGE(reginfo);
+  uint8_t regaddr = REGADDR(reginfo);
+  int ret;
+
+  /* Select the HDMI page */
+
+  ret = tda19988_select_page(priv, page);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_select_page failed page %02x: %d\n",
+             page, ret);
+      return ret;
+    }
+
+  /* Write the register address and the register value */
+
+  ret = tda19988_putreg(&priv->lower->hdmi, regaddr, regval);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_putreg() failed: %d\n", ret);
+      return ret;
+    }
+
+  lcdinfo("Read: %02x:%02x<-%02x\n", page, regaddr, regval);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_hdmi_putreg16
+ *
+ * Description:
+ *   Write a 16-bit value to one TDA19988 HDMI register
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_hdmi_putreg16(FAR struct tda1988_dev_s *priv,
+                                  uint16_t reginfo, uint16_t regval)
+{
+  uint8_t page    = REGPAGE(reginfo);
+  uint8_t regaddr = REGADDR(reginfo);
+  int ret;
+
+  /* Select the HDMI page */
+
+  ret = tda19988_select_page(priv, page);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_select_page failed page %02x: %d\n",
+             page, ret);
+      return ret;
+    }
+
+  /* Write the register address and the register value */
+
+  ret = tda19988_putreg16(&priv->lower->hdmi, regaddr, regval);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_putreg16() failed: %d\n", ret);
+      return ret;
+    }
+
+  lcdinfo("Read: %02x:%02x<-%04x\n", page, regaddr, regval);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_hdmi_modifyreg
+ *
+ * Description:
+ *   Modify bits in one TDA19988 HDMI register
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; otherwise a negated errno value is
+ *   returned.
+ *
+ ****************************************************************************/
+
+static int tda19988_hdmi_modifyreg(FAR struct tda1988_dev_s *priv,
+                                   uint16_t reginfo, uint8_t clrbits,
+                                   uint8_t setbits)
+{
+  uint8_t page    = REGPAGE(reginfo);
+  uint8_t regaddr = REGADDR(reginfo);
+  int ret;
+
+  /* Select the HDMI page */
+
+  ret = tda19988_select_page(priv, page);
+  if (ret < 0)
+    {
+      lcderr("ERROR: Failed to select page %02x: %d\n", page, ret);
+      return ret;
+    }
+
+  /* Read-modify-write the register contents */
+
+  ret = tda19988_modifyreg(&priv->lower->hdmi, regaddr, clrbits, setbits);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_modifyreg failed: %d\n", ret);
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_connected
+ *
+ * Description:
+ *   Check if a display is connected.
+ *
+ * Returned Values:
+ *   DISPLAY_CONNECTED - A display is connected
+ *   DISPLAY_DETACHED  - No display is connected
+ *   A negated errno value is returned on any failure.
+ *
+ ****************************************************************************/
+
+#if 0 /* Not used */
+static int tda19988_connected(FAR struct tda1988_dev_s *priv)
+{
+  uint8_t regval;
+  int ret;
+
+  ret = tda19988_cec_getregs(priv, CEC_STATUS_REG, &regval, 1);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_cec_getregs failed: %d\n", ret);
+      return ret;
+    }
+
+  if ((regval & CEC_STATUS_CONNECTED) == 0)
+    {
+      lcdwarn("WARNING:  Display not connected\n");
+      return DISPLAY_DETACHED;
+    }
+  else
+    {
+      lcdinfo("Display connect\n");
+      return DISPLAY_CONNECTED;
+    }
+}
+#endif
+
+/****************************************************************************
+ * Name: tda19988_fetch_edid_block
+ *
+ * Description:
+ *   Fetch one EDID block from the DSD.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+static int tda19988_fetch_edid_block(FAR struct tda1988_dev_s *priv,
+                                     FAR uint8_t *buf, int block)
+{
+  uint8_t data;
+  int attempt;
+  int ret;
+
+  ret = OK;
+
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_INT_REG, 0, HDMI_CTRL_INT_EDID);
+
+  /* Block 0 */
+
+  tda19988_hdmi_putreg(priv, HDMI_EDID_DEV_ADDR_REG, 0xa0);
+  tda19988_hdmi_putreg(priv, HDMI_EDID_OFFSET_REG, (block & 1) != 0 ? 128 : 0);
+  tda19988_hdmi_putreg(priv, HDMI_EDID_SEGM_ADDR_REG, 0x60);
+  tda19988_hdmi_putreg(priv, HDMI_EDID_DDC_SEGM_REG, block >> 1);
+
+  tda19988_hdmi_putreg(priv, HDMI_EDID_REQ_REG, HDMI_EDID_REQ_READ);
+  tda19988_hdmi_putreg(priv, HDMI_EDID_REQ_REG, 0);
+
+  data = 0;
+  for (attempt = 0; attempt < MAX_READ_ATTEMPTS; attempt++)
+    {
+      tda19988_hdmi_getregs(priv, HDMI_CTRL_INT_REG, &data, 1);
+      if ((data & HDMI_CTRL_INT_EDID) != 0)
+        {
+          break;
+        }
+    }
+
+  if (attempt == MAX_READ_ATTEMPTS)
+    {
+      ret = -ETIMEDOUT;
+      goto done;
+    }
+
+  if (tda19988_hdmi_getregs(priv, HDMI_EDID_DATA_REG, buf, EDID_LENGTH) != 0)
+    {
+      ret = -EIO;
+      goto done;
+    }
+
+done:
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_INT_REG, HDMI_CTRL_INT_EDID, 0);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: tda19988_fetch_edid
+ *
+ * Description:
+ *   Fetch the EDID block from the DSD.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+static int tda19988_fetch_edid(struct tda1988_dev_s *priv)
+{
+  int blocks;
+  int ret;
+
+  ret = 0;
+
+  if (priv->version == HDMI_CTRL_REV_TDA19988)
+    {
+      tda19988_hdmi_modifyreg(priv, HDMI_HDCPOTP_TX4_REG,
+                              HDMI_HDCPOTP_TX4_PDRAM, 0);
+    }
+
+  ret = tda19988_fetch_edid_block(priv, priv->edid, 0);
+  if (ret < 0)
+    {
+      goto done;
+    }
+
+  blocks = priv->edid[0x7e];
+  if (blocks > 0)
+    {
+      FAR uint8_t *edid;
+      unsigned int edid_len;
+      int i;
+
+      edid_len =  EDID_LENGTH * (blocks + 1);
+      edid     = (FAR void *)kmm_realloc(priv->edid, edid_len);
+
+      if (edid == NULL)
+        {
+          lcderr("ERROR:  Failed to realloc EDID\n");
+          ret = -ENOMEM;
+          goto done;
+        }
+
+      priv->edid     = edid;
+      priv->edid_len = edid_len;
+
+      for (i = 0; i < blocks; i++)
+        {
+          FAR uint8_t *buf;
+
+          /* TODO: check validity */
+
+          buf = priv->edid + EDID_LENGTH * (i + 1);
+          ret = tda19988_fetch_edid_block(priv, buf, i);
+          if (ret < 0)
+            {
+              lcderr("ERROR: tda19988_fetch_edid_block failed: %d\n", ret);
+              goto done;
+            }
+        }
+    }
+
+done:
+  if (priv->version == HDMI_CTRL_REV_TDA19988)
+    {
+      (void)tda19988_hdmi_modifyreg(priv, HDMI_HDCPOTP_TX4_REG,
+                                    0, HDMI_HDCPOTP_TX4_PDRAM);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: tda19988_read_internal
+ *
+ * Description:
+ *   Return the previously read EDID data.
+ *
+ * Returned Value:
+ *   The number of bytes actually read is returned on success; A negated
+ *   errno value is returned on any failure.
+ *
+ ****************************************************************************/
+
+
+static ssize_t tda19988_read_internal(FAR struct tda1988_dev_s *priv,
+                                      off_t offset, FAR uint8_t *buffer,
+                                      size_t buflen)
+{
+  DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0);
+
+  /* Check if the offset lies outside of the EDID buffer */
+
+  DEBUGASSERT(priv->edid != NULL && priv->edid_len > 0);
+  if (offset < 0)
+    {
+      offset = 0;
+    }
+  else if (offset >= priv->edid_len)
+    {
+      return 0;  /* End-of-file */
+    }
+
+  /* Clip the number of bytes so that the read region is wholly
+   * within the EDID buffer.
+   */
+
+  if (offset + buflen > priv->edid_len)
+    {
+      buflen = priv->edid_len - offset;
+    }
+
+  memcpy(buffer, &priv->edid[offset], buflen);
+  return (ssize_t)buflen;
+}
+
+/****************************************************************************
+ * Name: tda19988_open
+ *
+ * Description:
+ *   Standard character driver open method.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+static int tda19988_open(FAR struct file *filep)
+{
+  FAR struct inode *inode;
+  FAR struct tda1988_dev_s *priv;
+  int ret;
+
+  /* Get the private driver state instance */
+
+  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
+  inode = filep->f_inode;
+
+  priv = (FAR struct tda1988_dev_s *)inode->i_private;
+  DEBUGASSERT(priv != NULL);
+
+  /* Get exclusive access to the driver instance */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Increment the reference count on the driver instance */
+
+  DEBUGASSERT(priv->crefs != UINT8_MAX);
+  priv->crefs++;
+
+  nxsem_post(&priv->exclsem);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_close
+ *
+ * Description:
+ *   Standard character driver cl;ose method.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+static int tda19988_close(FAR struct file *filep)
+{
+  FAR struct inode *inode;
+  FAR struct tda1988_dev_s *priv;
+  int ret;
+
+  /* Get the private driver state instance */
+
+  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
+  inode = filep->f_inode;
+
+  priv = (FAR struct tda1988_dev_s *)inode->i_private;
+  DEBUGASSERT(priv != NULL);
+
+  /* Get exclusive access to the driver */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Decrement the count of open references on the driver */
+
+  DEBUGASSERT(priv->crefs > 0);
+  priv->crefs--;
+
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  /* If the count has decremented to zero and the driver has been unlinked,
+   * then self-destruct now.
+   */
+
+  if (priv->crefs == 0 && priv->unlinked)
+    {
+      tda19988_shutdown(priv);
+      return OK;
+    }
+#endif
+
+  nxsem_post(&priv->exclsem);
+  return -ENOSYS;
+}
+
+/****************************************************************************
+ * Name: tda19988_read
+ *
+ * Description:
+ *   Standard character driver read method.
+ *
+ * Returned Value:
+ *   The number of bytes read is returned on success; A negated errno value
+ *   is returned on any failure.  End-of-file (zero) is never returned.
+ *
+ ****************************************************************************/
+
+static ssize_t tda19988_read(FAR struct file *filep, FAR char *buffer,
+                             size_t len)
+{
+  FAR struct inode *inode;
+  FAR struct tda1988_dev_s *priv;
+  ssize_t nread;
+  int ret;
+
+  /* Get the private driver state instance */
+
+  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
+  inode = filep->f_inode;
+
+  priv = (FAR struct tda1988_dev_s *)inode->i_private;
+  DEBUGASSERT(priv != NULL);
+
+  /* Get exclusive access to the driver */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Return the previously read EDID data */
+
+  nread = tda19988_read_internal(priv, filep->f_pos, (FAR uint8_t *)buffer,
+                                 len);
+  if (nread > 0)
+    {
+      filep->f_pos += nread;
+    }
+
+  nxsem_post(&priv->exclsem);
+  return nread;
+}
+
+/****************************************************************************
+ * Name: tda19988_write
+ *
+ * Description:
+ *   Standard character driver write method.
+ *
+ * Returned Value:
+ *   The number of bytes written is returned on success; A negated errno value
+ *   is returned on any failure.
+ *
+ ****************************************************************************/
+
+static ssize_t tda19988_write(FAR struct file *filep, FAR const char *buffer,
+                              size_t len)
+{
+  /* Driver may be opened for write access.  Writing, however, is not
+   * supported.
+   */
+
+  return -ENOSYS;
+}
+
+/****************************************************************************
+ * Name: tda19988_seek
+ *
+ * Description:
+ *   Standard character driver poll method.
+ *
+ * Returned Value:
+ *   The current file position is returned on success; A negated errno value
+ *   is returned on any failure.
+ *
+ ****************************************************************************/
+
+static off_t tda19988_seek(FAR struct file *filep, off_t offset, int whence)
+{
+  FAR struct inode *inode;
+  FAR struct tda1988_dev_s *priv;
+  off_t pos;
+  int ret;
+
+  /* Get the private driver state instance */
+
+  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
+  inode = filep->f_inode;
+
+  priv = (FAR struct tda1988_dev_s *)inode->i_private;
+  DEBUGASSERT(priv != NULL);
+
+  /* Get exclusive access to the driver */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Perform the seek operation */
+
+  pos = filep->f_pos;
+
+  switch (whence)
+    {
+      case SEEK_CUR:
+        pos += offset;
+        if (pos > EDID_LENGTH)
+          {
+            pos = EDID_LENGTH;
+          }
+        else if (pos < 0)
+          {
+            pos = 0;
+          }
+
+        filep->f_pos = pos;
+        break;
+
+      case SEEK_SET:
+        pos = offset;
+        if (pos > EDID_LENGTH)
+          {
+            pos = EDID_LENGTH;
+          }
+        else if (pos < 0)
+          {
+            pos = 0;
+          }
+
+        filep->f_pos = pos;
+        break;
+
+      case SEEK_END:
+        pos = EDID_LENGTH + offset;
+        if (pos > EDID_LENGTH)
+          {
+            pos = EDID_LENGTH;
+          }
+        else if (pos < 0)
+          {
+            pos = 0;
+          }
+
+        filep->f_pos = pos;
+        break;
+
+      default:
+        /* Return EINVAL if the whence argument is invalid */
+
+        pos = (off_t)-EINVAL;
+        break;
+    }
+
+  nxsem_post(&priv->exclsem);
+  return pos;
+}
+
+/****************************************************************************
+ * Name: tda19988_ioctl
+ *
+ * Description:
+ *   Standard character driver poll method.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+static int tda19988_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
+{
+  FAR struct inode *inode;
+  FAR struct tda1988_dev_s *priv;
+  int ret;
+
+  /* Get the private driver state instance */
+
+  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
+  inode = filep->f_inode;
+
+  priv = (FAR struct tda1988_dev_s *)inode->i_private;
+  DEBUGASSERT(priv != NULL);
+
+  /* Get exclusive access to the driver */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Handle the IOCTL command */
+
+  switch (cmd)
+    {
+      /* TDA19988_IOC_VIDEOMODE:
+       *   Description:  Select the video mode.  This must be done as part
+       *                 of the initialization of the driver.  This is
+       *                 equivalent to calling tda18899_videomode() within
+       *                 the OS.
+       *   Argument:     A reference to a tda19988_videomode_s structure
+       *                 instance.
+       *   Returns:      None
+       */
+
+      case TDA19988_IOC_VIDEOMODE:
+        {
+          FAR const struct tda19988_videomode_s *mode =
+            (FAR const struct tda19988_videomode_s *)((uintptr_t)arg);
+
+          if (mode == NULL)
+            {
+              ret = -EINVAL;
+            }
+          else
+            {
+              ret = tda19988_videomode_internal(priv, mode);
+              if (ret < 0)
+                {
+                  lcderr("ERROR: tda19988_videomode_internal failed: %d\n",
+                         ret);
+                }
+            }
+        }
+        break;
+
+      default:
+        ret = -ENOTTY;
+        break;
+    }
+
+  nxsem_post(&priv->exclsem);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: tda19988_poll
+ *
+ * Description:
+ *   Standard character driver poll method.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+#ifndef CONFIG_DISABLE_POLL
+static int tda19988_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                         bool setup)
+{
+  FAR struct inode *inode;
+  FAR struct tda1988_dev_s *priv;
+  int ret;
+
+  /* Get the private driver state instance */
+
+  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
+  inode = filep->f_inode;
+
+  priv = (FAR struct tda1988_dev_s *)inode->i_private;
+  DEBUGASSERT(priv != NULL);
+
+  /* Get exclusive access to the driver */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (setup)
+    {
+      fds->revents |= (fds->events & (POLLIN | POLLOUT));
+      if (fds->revents != 0)
+        {
+          nxsem_post(fds->sem);
+        }
+    }
+
+  nxsem_post(&priv->exclsem);
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: tda19988_unlink
+ *
+ * Description:
+ *   Standard character driver unlink method.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+static int tda19988_unlink(FAR struct inode *inode)
+{
+  FAR struct tda1988_dev_s *priv;
+  int ret;
+
+  /* Get the private driver state instance */
+
+  DEBUGASSERT(inode != NULL && inode->i_private != NULL);
+  priv = (FAR struct tda1988_dev_s *)inode->i_private;
+
+  /* Get exclusive access to the driver */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Are there open references to the driver data structure? */
+
+  if (priv->crefs <= 0)
+    {
+      tda19988_shutdown(priv);
+      return OK;
+    }
+
+  /* No... just mark the driver as unlinked and free the resources when the
+   * last client closes their reference to the driver.
+   */
+
+  priv->unlinked = true;
+  nxsem_post(&priv->exclsem);
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: tda19988_hwinitialize
+ *
+ * Description:
+ *   Initialize the TDA19988 hardware.
+ *
+ * Input Parameters:
+ *   priv - TDA19988 driver state
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; a negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+static int tda19988_hwinitialize(FAR struct tda1988_dev_s *priv)
+{
+  uint16_t version;
+  uint8_t data;
+  int ret;
+
+  tda19988_cec_putreg(priv, CEC_ENAMODS_REG,
+                      CEC_ENAMODS_RXSENS | CEC_ENAMODS_HDMI);
+  up_udelay(1000);
+  tda19988_cec_getregs(priv, CEC_STATUS_REG, &data, 1);
+
+  /* Reset core */
+
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_RESET_REG, 0, 3);
+  up_udelay(100);
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_RESET_REG, 3, 0);
+  up_udelay(100);
+
+  /* Reset transmitter: */
+
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_MAIN_CNTRL0_REG, 0,
+                          HDMI_CTRL_MAIN_CNTRL0_SR);
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_MAIN_CNTRL0_REG,
+                          HDMI_CTRL_MAIN_CNTRL0_SR, 0);
+
+  /* PLL registers common configuration */
+
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SERIAL_1_REG, 0x00);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SERIAL_2_REG,
+                       HDMI_PLL_SERIAL_2_SRL_NOSC(1));
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SERIAL_3_REG, 0x00);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SERIALIZER_REG, 0x00);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_BUFFER_OUT_REG, 0x00);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SCG1_REG, 0x00);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SEL_CLK_REG,
+                       HDMI_PLL_SEL_CLK_SEL_CLK1 |
+                       HDMI_PLL_SEL_CLK_ENA_SC_CLK);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SCGN1_REG, 0xfa);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SCGN2_REG, 0x00);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SCGR1_REG, 0x5b);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SCGR2_REG, 0x00);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SCG2_REG, 0x10);
+
+  /* Write the default value MUX register */
+
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_MUX_VP_VIP_OUT_REG, 0x24);
+
+  tda19988_hdmi_getregs(priv, HDMI_CTRL_REV_LO_REG, &data, 1);
+  version  = (uint16_t)data;
+  tda19988_hdmi_getregs(priv, HDMI_CTRL_REV_HI_REG, &data, 1);
+  version |= ((uint16_t)data << 8);
+
+  /* Clear feature bits */
+
+  priv->version = version & ~0x30;
+  switch (priv->version)
+    {
+    case HDMI_CTRL_REV_TDA19988:
+      lcdinfo("TDA19988\n");
+      break;
+
+    default:
+      lcderr("ERROR: Unknown device: %04x\n", priv->version);
+      ret = -ENODEV;
+      goto done;
+    }
+
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_DDC_CTRL_REG, HDMI_CTRL_DDC_EN);
+  tda19988_hdmi_putreg(priv, HDMI_HDCPOTP_TX3_REG, 39);
+
+  tda19988_cec_putreg(priv, CEC_FRO_IM_CLK_CTRL_REG,
+                      CEC_FRO_IM_CLK_CTRL_GHOST_DIS |
+                      CEC_FRO_IM_CLK_CTRL_IMCLK_SEL);
+
+  ret = tda19988_fetch_edid(priv);
+  if (ret < 0)
+    {
+      lcderr("ERROR:  tda19988_fetch_edid failed: %d\n", ret);
+      goto done;
+    }
+
+  /* Default values for RGB 4:4:4 mapping */
+
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_VIPCTRL_0_REG, 0x23);
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_VIPCTRL_1_REG, 0x01);
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_VIPCTRL_2_REG, 0x45);
+
+  ret = OK;
+
+done:
+  return ret;
+}
+
+/****************************************************************************
+ * Name: tda19988_videomode_internal
+ *
+ * Description:
+ *   Initialize the TDA19988 driver to a specified video mode.  This is a
+ *   necessary part of the TDA19988 initialization:  A video mode  must be
+ *   configured before the driver is usable.
+ *
+ * Input Parameters:
+ *   priv - TDA19988 driver state
+ *   mode - The new video mode.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; a negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+static int
+  tda19988_videomode_internal(FAR struct tda1988_dev_s *priv,
+                              FAR const struct tda19988_videomode_s *mode)
+{
+  uint16_t ref_pix;
+  uint16_t ref_line;
+  uint16_t n_pix;
+  uint16_t n_line;
+  uint16_t hs_pix_start;
+  uint16_t hs_pix_stop;
+  uint16_t vs1_pix_start;
+  uint16_t vs1_pix_stop;
+  uint16_t vs1_line_start;
+  uint16_t vs1_line_end;
+  uint16_t vs2_pix_start;
+  uint16_t vs2_pix_stop;
+  uint16_t vs2_line_start;
+  uint16_t vs2_line_end;
+  uint16_t vwin1_line_start;
+  uint16_t vwin1_line_end;
+  uint16_t vwin2_line_start;
+  uint16_t vwin2_line_end;
+  uint16_t de_start;
+  uint16_t de_stop;
+  uint8_t regval;
+  uint8_t div;
+
+  DEBUGASSERT(priv != NULL && mode != NULL);
+
+  n_pix        = mode->htotal;
+  n_line       = mode->vtotal;
+
+  hs_pix_stop  = mode->hsync_end - mode->hdisplay;
+  hs_pix_start = mode->hsync_start - mode->hdisplay;
+
+  de_stop      = mode->htotal;
+  de_start     = mode->htotal - mode->hdisplay;
+  ref_pix      = hs_pix_start + 3;
+
+  if (mode->flags & VID_HSKEW)
+    {
+      ref_pix += mode->hskew;
+    }
+
+  if ((mode->flags & VID_INTERLACE) == 0)
+    {
+      ref_line         = 1 + mode->vsync_start - mode->vdisplay;
+      vwin1_line_start = mode->vtotal - mode->vdisplay - 1;
+      vwin1_line_end   = vwin1_line_start + mode->vdisplay;
+
+      vs1_pix_start    = vs1_pix_stop = hs_pix_start;
+      vs1_line_start   = mode->vsync_start - mode->vdisplay;
+      vs1_line_end     = vs1_line_start + mode->vsync_end -
+                         mode->vsync_start;
+
+      vwin2_line_start = vwin2_line_end = 0;
+      vs2_pix_start    = vs2_pix_stop = 0;
+      vs2_line_start   = vs2_line_end = 0;
+    }
+  else
+    {
+      ref_line         = 1 + (mode->vsync_start - mode->vdisplay) / 2;
+      vwin1_line_start = (mode->vtotal - mode->vdisplay) / 2;
+      vwin1_line_end   = vwin1_line_start + mode->vdisplay / 2;
+
+      vs1_pix_start    = vs1_pix_stop = hs_pix_start;
+      vs1_line_start   = (mode->vsync_start - mode->vdisplay) / 2;
+      vs1_line_end     = vs1_line_start +
+                         (mode->vsync_end - mode->vsync_start) / 2;
+
+      vwin2_line_start = vwin1_line_start + mode->vtotal / 2;
+      vwin2_line_end   = vwin2_line_start + mode->vdisplay / 2;
+
+      vs2_pix_start    = vs2_pix_stop = hs_pix_start + mode->htotal / 2;
+      vs2_line_start   = vs1_line_start + mode->vtotal / 2;
+      vs2_line_end     = vs2_line_start +
+                         (mode->vsync_end - mode->vsync_start) / 2;
+    }
+
+  div = 148500 / mode->dot_clock;
+  if (div != 0)
+    {
+      if (--div > 3)
+        {
+          div = 3;
+        }
+    }
+
+  /* Set HDMI HDCP mode off */
+
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_TBG_CNTRL_1_REG, 0,
+                          HDMI_CTRL_TBG_CNTRL_1_DWIN_DIS);
+  tda19988_hdmi_modifyreg(priv, HDMI_HDCPOTP_TX33_REG,
+                          HDMI_HDCPOTP_TX33_HDMI, 0);
+  tda19988_hdmi_putreg(priv, HDMI_AUDIO_ENC_CTRL_REG,
+                       HDMI_AUDIO_ENC_CNTRL_DVI_MODE);
+
+  /* No pre-filter or interpretor */
+
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_HVF_CNTRL_0_REG,
+                       HDMI_CTRL_HVF_CNTRL_0_INTPOL_BYPASS |
+                       HDMI_CTRL_HVF_CNTRL_0_PREFIL_NONE);
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_VIPCTRL_5_REG,
+                       HDMI_CTRL_VIPCTRL_5_SP_CNT(0));
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_VIPCTRL_4_REG,
+                       HDMI_CTRL_VIPCTRL_4_BLANKIT_NDE |
+                       HDMI_CTRL_VIPCTRL_4_BLC_NONE);
+
+  tda19988_hdmi_modifyreg(priv, HDMI_PLL_SERIAL_3_REG,
+                          HDMI_PLL_SERIAL_3_SRL_CCIR, 0);
+  tda19988_hdmi_modifyreg(priv, HDMI_PLL_SERIAL_1_REG,
+                          HDMI_PLL_SERIAL_1_SRL_MAN_IP, 0);
+  tda19988_hdmi_modifyreg(priv, HDMI_PLL_SERIAL_3_REG,
+                          HDMI_PLL_SERIAL_3_SRL_DE, 0);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SERIALIZER_REG, 0);
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_HVF_CNTRL_1_REG,
+                       HDMI_CTRL_HVF_CNTRL_1_VQR_FULL);
+
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_RPT_CNTRL_REG, 0);
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SEL_CLK_REG,
+                       HDMI_PLL_SEL_CLK_SEL_VRF_CLK(0) |
+                       HDMI_PLL_SEL_CLK_SEL_CLK1 |
+                       HDMI_PLL_SEL_CLK_ENA_SC_CLK);
+
+  tda19988_hdmi_putreg(priv, HDMI_PLL_SERIAL_2_REG,
+                       HDMI_PLL_SERIAL_2_SRL_NOSC(div) |
+                       HDMI_PLL_SERIAL_2_SRL_PR(0));
+
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_MATCTRL_REG, 0,
+                          HDMI_CTRL_MAT_CONTRL_MAT_BP);
+
+  tda19988_hdmi_putreg(priv, HDMI_PLL_ANA_GENERAL_REG, 0x09);
+
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_TBG_CNTRL_0_REG,
+                          HDMI_CTRL_TBG_CNTRL_0_SYNC_MTHD, 0);
+
+  /* Sync on rising HSYNC/VSYNC */
+
+  regval = HDMI_CTRL_VIPCTRL_3_SYNC_HS;
+  if (mode->flags & VID_NHSYNC)
+    {
+      regval |= HDMI_CTRL_VIPCTRL_3_H_TGL;
+    }
+
+  if (mode->flags & VID_NVSYNC)
+    {
+      regval |= HDMI_CTRL_VIPCTRL_3_V_TGL;
+    }
+
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_VIPCTRL_3_REG, regval);
+
+  regval = HDMI_CTRL_TBG_CNTRL_1_TGL_EN;
+  if (mode->flags & VID_NHSYNC)
+    {
+      regval |= HDMI_CTRL_TBG_CNTRL_1_H_TGL;
+    }
+
+  if (mode->flags & VID_NVSYNC)
+    {
+      regval |= HDMI_CTRL_TBG_CNTRL_1_V_TGL;
+    }
+
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_TBG_CNTRL_1_REG, regval);
+
+  /* Program timing */
+
+  tda19988_hdmi_putreg(priv, HDMI_CTRL_MUX_VIDFORMAT_REG, 0x00);
+
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_MUX_REFPIX_MSB_REG, ref_pix);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_MUX_REFLINE_MSB_REG, ref_line);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_MUX_NPIX_MSB_REG, n_pix);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_MUX_NLINE_MSB_REG, n_line);
+
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_MUX_VS_LINE_STRT_1_MSB_REG,
+                         vs1_line_start);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_MUX_VS_PIX_STRT_1_MSB_REG, vs1_pix_start);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VS_LINE_END_1_MSB_REG, vs1_line_end);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VS_PIX_END_1_MSB_REG, vs1_pix_stop);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VS_LINE_STRT_2_MSB_REG,
+                         vs2_line_start);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VS_PIX_STRT_2_MSB_REG, vs2_pix_start);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VS_LINE_END_2_MSB_REG, vs2_line_end);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VS_PIX_END_2_MSB_REG, vs2_pix_stop);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_HS_PIX_START_MSB_REG, hs_pix_start);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_HS_PIX_STOP_MSB_REG, hs_pix_stop);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VWIN_START_1_MSB_REG, vwin1_line_start);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VWIN_END_1_MSB_REG, vwin1_line_end);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VWIN_START_2_MSB_REG,
+                         vwin2_line_start);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_VWIN_END_2_MSB_REG, vwin2_line_end);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_DE_START_MSB_REG, de_start);
+  tda19988_hdmi_putreg16(priv, HDMI_CTRL_DE_STOP_MSB_REG, de_stop);
+
+  if (priv->version == HDMI_CTRL_REV_TDA19988)
+    {
+      tda19988_hdmi_putreg(priv, HDMI_CTRL_ENABLE_SPACE_REG, 0x00);
+    }
+
+  /* Must be last register set */
+
+  tda19988_hdmi_modifyreg(priv, HDMI_CTRL_TBG_CNTRL_0_REG,
+                          HDMI_CTRL_TBG_CNTRL_0_SYNC_ONCE, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tda19988_shutdown
+ *
+ * Description:
+ *   Free resources used by the driver when it has been unlinked.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+static void tda19988_shutdown(FAR struct tda1988_dev_s *priv)
+{
+  /* Detach and disable interrupts */
+
+  if (priv->lower != NULL)  /* May be called before fully initialized */
+    {
+      DEBUGASSERT(priv->lower->attach != NULL &&
+                  priv->lower->enable != NULL);
+
+      (void)priv->lower->attach(priv->lower, NULL, NULL);
+      (void)priv->lower->enable(priv->lower, false);
+    }
+
+  /* Release resources */
+
+  nxsem_destroy(&priv->exclsem);
+
+  /* Free memory */
+
+  if (priv->edid != NULL)
+    {
+      kmm_free(priv->edid);
+    }
+
+  kmm_free(priv);
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: tda19988_register
+ *
+ * Description:
+ *   Create and register the the TDA19988 driver at 'devpath'
+ *
+ * Input Parameters:
+ *   devpath - The location to register the TDA19988 driver instance
+ *   lower   - The interface to the the TDA19988 lower half driver.
+ *
+ * Returned Value:
+ *   On success, non-NULL handle is returned that may be subsequently used
+ *   with tda19988_videomode().  NULL is returned on failure.
+ *
+ ****************************************************************************/
+
+TDA19988_HANDLE tda19988_register(FAR const char *devpath,
+                                  FAR const struct tda19988_lower_s *lower)
+{
+  FAR struct tda1988_dev_s *priv;
+  int ret;
+
+  DEBUGASSERT(devpath != NULL && lower != NULL);
+
+  /* Allocate an instance of the TDA19988 driver */
+
+  priv = (FAR struct tda1988_dev_s *)kmm_zalloc(sizeof(struct tda1988_dev_s));
+  if (priv == NULL)
+    {
+      lcderr("ERROR: Failed to allocate device structure\n");
+      return NULL;
+    }
+
+  /* Assume a single block in EDID */
+
+  priv->edid = (FAR uint8_t *)kmm_malloc(EDID_LENGTH);
+  if (priv->edid == NULL)
+    {
+      lcderr("ERROR: Failed to allocate EDID\n");
+      tda19988_shutdown(priv);
+      return NULL;
+    }
+
+  priv->edid_len = EDID_LENGTH;
+
+  /* Initialize the driver structure */
+
+  priv->lower = lower;
+  priv->page  = HDMI_NO_PAGE;
+
+  sem_init(&priv->exclsem, 0, 1);
+
+  /* Initialize the TDA19988 */
+
+  ret = tda19988_hwinitialize(priv);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_hwinitialize failed: %d\n", ret);
+      tda19988_shutdown(priv);
+      return NULL;
+    }
+
+  /* Register the driver */
+
+  ret = register_driver(devpath, &tda19988_fops, 0666, NULL);
+  if (ret < 0)
+    {
+      lcderr("ERROR: register_driver() failed: %d\n", ret);
+      tda19988_shutdown(priv);
+      return NULL;
+    }
+
+  return (TDA19988_HANDLE)priv;
+}
+
+/****************************************************************************
+ * Name: tda19988_videomode
+ *
+ * Description:
+ *   Initialize the TDA19988 driver to a specified video mode.  This is a
+ *   necessary part of the TDA19988 initialization:  A video mode  must be
+ *   configured before the driver is usable.
+ *
+ *   NOTE:  This may be done in two ways:  (1) via a call to
+ *   tda19988_videomode() from board-specific logic within the OS, or
+ *   equivalently (2) using the TDA19988_IOC_VIDEOMODE from application
+ *   logic outside of the OS.
+ *
+ * Input Parameters:
+ *   handle - The handle previously returned by tda19988_register().
+ *   mode   - The new video mode.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; a negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+int tda19988_videomode(TDA19988_HANDLE handle,
+                       FAR const struct tda19988_videomode_s *mode)
+{
+  FAR struct tda1988_dev_s *priv = (FAR struct tda1988_dev_s *)handle;
+  int ret;
+
+  DEBUGASSERT(priv != NULL && mode != NULL);
+
+  /* Get exclusive access to the driver */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Defer the heavy lifting to tda19988_videomode_internal() */
+
+  ret = tda19988_videomode_internal(priv, mode);
+  if (ret < 0)
+    {
+      lcderr("ERROR: tda19988_videomode_internal failed: %d\n", ret);
+    }
+
+  nxsem_post(&priv->exclsem);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: tda19988_read_edid
+ *
+ * Description:
+ *   Read the EDID (Extended Display Identification Data).
+ *
+ *   NOTE:  This may be done in two ways:  (1) via a call to
+ *   tda19988_read_edid() from board-specific logic within the OS, or
+ *   equivalently (2) using a standard read() to read the EDID from
+ *   application logic outside of the OS.
+ *
+ * Input Parameters:
+ *   handle - The handle previously returned by tda19988_register().
+ *   offset - The offset into the EDID to begin reading (0..127)
+ *   buffer - Location in which to return the EDID data
+ *   buflen - Size of buffer in bytes
+ *
+ * Returned Value:
+ *   On success, the number of bytes read is returned; a negated errno value
+ *   is returned on any failure.
+ *
+ ****************************************************************************/
+
+ssize_t tda19988_read_edid(TDA19988_HANDLE handle, off_t offset,
+                           FAR uint8_t *buffer, size_t buflen)
+{
+  FAR struct tda1988_dev_s *priv = (FAR struct tda1988_dev_s *)handle;
+  size_t nread;
+  int ret;
+
+  DEBUGASSERT(priv != NULL);
+
+  /* Get exclusive access to the driver */
+
+  ret = nxsem_wait(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Defer the heavy lifting to tda19988_read_internal() */
+
+  nread = tda19988_read_internal(priv, offset, buffer, buflen);
+  if (nread < 0)
+    {
+      lcderr("ERROR: tda19988_read_internal failed: %d\n",
+             (int)nread);
+    }
+
+  nxsem_post(&priv->exclsem);
+  return nread;
+}
diff --git a/drivers/lcd/tda19988.h b/drivers/lcd/tda19988.h
new file mode 100644
index 0000000000..ef59dccd9f
--- /dev/null
+++ b/drivers/lcd/tda19988.h
@@ -0,0 +1,313 @@
+/******************************************************************************************************
+ * drivers/lcd/tda19988.h
+ * Definitions for the TDA19988.  The TDA19988 is a very low power and very small
+ * size High-Definition Multimedia Interface (HDMI) 1.4a transmitter
+ *
+ *   Copyright (C) 2018 Gregory Nutt. All rights reserved.
+ *   Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ *    used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ******************************************************************************************************/
+
+#ifndef __DRIVERS_LCD_TDA19988_H
+#define __DRIVERS_LCD_TDA19988_H
+
+/******************************************************************************************************
+ * Included Files
+ ******************************************************************************************************/
+
+/******************************************************************************************************
+ * Pre-processor Definitions
+ ******************************************************************************************************/
+
+/* CEC Registers **************************************************************************************/
+/* The device has two I2C interfaces CEC (0x34) and HDMI (0x70). */
+
+#define CEC_FRO_IM_CLK_CTRL_REG                  0xfb
+#  define CEC_FRO_IM_CLK_CTRL_GHOST_DIS          (1 << 7)
+#  define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL          (1 << 1)
+
+#define CEC_STATUS_REG                           0xfe
+#  define CEC_STATUS_CONNECTED                   (1 << 1)
+
+#define CEC_ENAMODS_REG                          0xff
+#  define CEC_ENAMODS_HDMI                       (1 << 1)
+#  define CEC_ENAMODS_RXSENS                     (1 << 2)
+#  define CEC_ENABLE_ALL                         0x87
+
+/* HDMI Memory Pages **********************************************************************************/
+/* HDMI Memory is accessed via page and address.  The page must first be selected, then
+ * only the address is sent in order accessing memory locations within the selected
+ * page.
+ */
+
+#define HDMI_CTRL_PAGE                           0x00  /* General control page */
+#define HDMI_PLL_PAGE                            0x02  /* PLL settings page */
+#define HDMI_EDID_PAGE                           0x09  /* EDID control page */
+#define HDMI_INFO_PAGE                           0x10  /* Information frames and packets page */
+#define HDMI_AUDIO_PAGE                          0x11  /* Audio settings and content info packets page */
+#define HDMI_HDCPOTP_PAGE                        0x12  /* HDCP (TDA19988AHN and TDA19988AET only) and OTP */
+#define HDMI_GAMUT_PAGE                          0x13  /* Gamut-related metadata packets page */
+
+/* The page select register does not lie within the above pages. The value of 0xff is
+ * used for this access.
+ */
+
+#define HDMI_NO_PAGE                             0xff
+
+/* Page-related macros */
+
+#define MKREG(page, addr)                        (((page) << 8) | (addr))
+#define REGPAGE(reg)                             (((reg) >> 8) & 0xff)
+#define REGADDR(reg)                             ((reg) & 0xff)
+
+/* General Control Page Registers and Bit Definitions */
+
+#define HDMI_CTRL_REV_LO_REG                     MKREG(HDMI_CTRL_PAGE, 0x00)
+
+#define HDMI_CTRL_MAIN_CNTRL0_REG                MKREG(HDMI_CTRL_PAGE, 0x01)
+#  define HDMI_CTRL_MAIN_CNTRL0_SR               (1 << 0)
+
+#define HDMI_CTRL_REV_HI_REG                     MKREG(HDMI_CTRL_PAGE, 0x02)
+#  define HDMI_CTRL_REV_TDA9989N2                0x0101  /* Masking out bits 4-5 */
+#  define HDMI_CTRL_REV_TDA19989                 0x0201
+#  define HDMI_CTRL_REV_TDA19989N2               0x0202
+#  define HDMI_CTRL_REV_TDA19988                 0x0301
+
+#define HDMI_CTRL_RESET_REG                      MKREG(HDMI_CTRL_PAGE, 0x0a)
+#  define HDMI_CTRL_RESET_AUDIO                  (1 << 0)
+#  define HDMI_CTRL_RESET_I2C                    (1 << 1)
+
+#define HDMI_CTRL_DDC_CTRL_REG                   MKREG(HDMI_CTRL_PAGE, 0x0b)
+#  define HDMI_CTRL_DDC_EN                       0x00
+
+#define HDMI_CTRL_DDC_CLK_REG                    MKREG(HDMI_CTRL_PAGE, 0x0c)
+#  define HDMI_CTRL_DDC_CLK_EN                   (1 << 0)
+
+#define HDMI_CTRL_INTR_CTRL_REG                  MKREG(HDMI_CTRL_PAGE, 0x0f)
+#  define HDMI_CTRL_INTR_EN_GLO                  (1 << 2)
+
+#define HDMI_CTRL_INT_REG                        MKREG(HDMI_CTRL_PAGE, 0x11)
+#  define HDMI_CTRL_INT_EDID                     (1 << 1)
+
+#define HDMI_CTRL_VIPCTRL_0_REG                  MKREG(HDMI_CTRL_PAGE, 0x20)
+#define HDMI_CTRL_VIPCTRL_1_REG                  MKREG(HDMI_CTRL_PAGE, 0x21)
+#define HDMI_CTRL_VIPCTRL_2_REG                  MKREG(HDMI_CTRL_PAGE, 0x22)
+
+#define HDMI_CTRL_VIPCTRL_3_REG                  MKREG(HDMI_CTRL_PAGE, 0x23)
+#  define HDMI_CTRL_VIPCTRL_3_SYNC_HS            (2 << 4)
+#  define HDMI_CTRL_VIPCTRL_3_V_TGL              (1 << 2)
+#  define HDMI_CTRL_VIPCTRL_3_H_TGL              (1 << 1)
+
+#define HDMI_CTRL_VIPCTRL_4_REG                  MKREG(HDMI_CTRL_PAGE, 0x24)
+#  define HDMI_CTRL_VIPCTRL_4_BLANKIT_NDE        (0 << 2)
+#  define HDMI_CTRL_VIPCTRL_4_BLANKIT_HS_VS      (1 << 2)
+#  define HDMI_CTRL_VIPCTRL_4_BLANKIT_NHS_VS     (2 << 2)
+#  define HDMI_CTRL_VIPCTRL_4_BLANKIT_HE_VE      (3 << 2)
+#  define HDMI_CTRL_VIPCTRL_4_BLC_NONE           (0 << 0)
+#  define HDMI_CTRL_VIPCTRL_4_BLC_RGB444         (1 << 0)
+#  define HDMI_CTRL_VIPCTRL_4_BLC_YUV444         (2 << 0)
+#  define HDMI_CTRL_VIPCTRL_4_BLC_YUV422         (3 << 0)
+
+#define HDMI_CTRL_VIPCTRL_5_REG                  MKREG(HDMI_CTRL_PAGE, 0x25)
+#  define HDMI_CTRL_VIPCTRL_5_SP_CNT(n)          (((n) & 3) << 1)
+
+#define HDMI_CTRL_MUX_VP_VIP_OUT_REG             MKREG(HDMI_CTRL_PAGE, 0x27)
+
+#define HDMI_CTRL_MATCTRL_REG                    MKREG(HDMI_CTRL_PAGE, 0x80)
+#  define HDMI_CTRL_MAT_CONTRL_MAT_BP            (1 << 2)
+
+#define HDMI_CTRL_MUX_VIDFORMAT_REG              MKREG(HDMI_CTRL_PAGE, 0xa0)
+#define HDMI_CTRL_MUX_REFPIX_MSB_REG             MKREG(HDMI_CTRL_PAGE, 0xa1)
+#define HDMI_CTRL_MUX_REFPIX_LSB_REG             MKREG(HDMI_CTRL_PAGE, 0xa2)
+#define HDMI_CTRL_MUX_REFLINE_MSB_REG            MKREG(HDMI_CTRL_PAGE, 0xa3)
+#define HDMI_CTRL_MUX_REFLINE_LSB_REG            MKREG(HDMI_CTRL_PAGE, 0xa4)
+#define HDMI_CTRL_MUX_NPIX_MSB_REG               MKREG(HDMI_CTRL_PAGE, 0xa5)
+#define HDMI_CTRL_MUX_NPIX_LSB_REG               MKREG(HDMI_CTRL_PAGE, 0xa6)
+#define HDMI_CTRL_MUX_NLINE_MSB_REG              MKREG(HDMI_CTRL_PAGE, 0xa7)
+#define HDMI_CTRL_MUX_NLINE_LSB_REG              MKREG(HDMI_CTRL_PAGE, 0xa8)
+#define HDMI_CTRL_MUX_VS_LINE_STRT_1_MSB_REG     MKREG(HDMI_CTRL_PAGE, 0xa9)
+#define HDMI_CTRL_MUX_VS_LINE_STRT_1_LSB_REG     MKREG(HDMI_CTRL_PAGE, 0xaa)
+#define HDMI_CTRL_MUX_VS_PIX_STRT_1_MSB_REG      MKREG(HDMI_CTRL_PAGE, 0xab)
+#define HDMI_CTRL_VS_PIX_STRT_1_LSB_REG          MKREG(HDMI_CTRL_PAGE, 0xac)
+#define HDMI_CTRL_VS_LINE_END_1_MSB_REG          MKREG(HDMI_CTRL_PAGE, 0xad)
+#define HDMI_CTRL_VS_LINE_END_1_LSB_REG          MKREG(HDMI_CTRL_PAGE, 0xae)
+#define HDMI_CTRL_VS_PIX_END_1_MSB_REG           MKREG(HDMI_CTRL_PAGE, 0xaf)
+#define HDMI_CTRL_VS_PIX_END_1_LSB_REG           MKREG(HDMI_CTRL_PAGE, 0xb0)
+#define HDMI_CTRL_VS_LINE_STRT_2_MSB_REG         MKREG(HDMI_CTRL_PAGE, 0xb1)
+#define HDMI_CTRL_VS_LINE_STRT_2_LSB_REG         MKREG(HDMI_CTRL_PAGE, 0xb2)
+#define HDMI_CTRL_VS_PIX_STRT_2_MSB_REG          MKREG(HDMI_CTRL_PAGE, 0xb3)
+#define HDMI_CTRL_VS_PIX_STRT_2_LSB_REG          MKREG(HDMI_CTRL_PAGE, 0xb4)
+#define HDMI_CTRL_VS_LINE_END_2_MSB_REG          MKREG(HDMI_CTRL_PAGE, 0xb5)
+#define HDMI_CTRL_VS_LINE_END_2_LSB_REG          MKREG(HDMI_CTRL_PAGE, 0xb6)
+#define HDMI_CTRL_VS_PIX_END_2_MSB_REG           MKREG(HDMI_CTRL_PAGE, 0xb7)
+#define HDMI_CTRL_VS_PIX_END_2_LSB_REG           MKREG(HDMI_CTRL_PAGE, 0xb8)
+#define HDMI_CTRL_HS_PIX_START_MSB_REG           MKREG(HDMI_CTRL_PAGE, 0xb9)
+#define HDMI_CTRL_HS_PIX_START_LSB_REG           MKREG(HDMI_CTRL_PAGE, 0xba)
+#define HDMI_CTRL_HS_PIX_STOP_MSB_REG            MKREG(HDMI_CTRL_PAGE, 0xbb)
+#define HDMI_CTRL_HS_PIX_STOP_LSB_REG            MKREG(HDMI_CTRL_PAGE, 0xbc)
+#define HDMI_CTRL_VWIN_START_1_MSB_REG           MKREG(HDMI_CTRL_PAGE, 0xbd)
+#define HDMI_CTRL_VWIN_START_1_LSB_REG           MKREG(HDMI_CTRL_PAGE, 0xbe)
+#define HDMI_CTRL_VWIN_END_1_MSB_REG             MKREG(HDMI_CTRL_PAGE, 0xbf)
+#define HDMI_CTRL_VWIN_END_1_LSB_REG             MKREG(HDMI_CTRL_PAGE, 0xc0)
+#define HDMI_CTRL_VWIN_START_2_MSB_REG           MKREG(HDMI_CTRL_PAGE, 0xc1)
+#define HDMI_CTRL_VWIN_START_2_LSB_REG           MKREG(HDMI_CTRL_PAGE, 0xc2)
+#define HDMI_CTRL_VWIN_END_2_MSB_REG             MKREG(HDMI_CTRL_PAGE, 0xc3)
+#define HDMI_CTRL_VWIN_END_2_LSB_REG             MKREG(HDMI_CTRL_PAGE, 0xc4)
+#define HDMI_CTRL_DE_START_MSB_REG               MKREG(HDMI_CTRL_PAGE, 0xc5)
+#define HDMI_CTRL_DE_START_LSB_REG               MKREG(HDMI_CTRL_PAGE, 0xc6)
+#define HDMI_CTRL_DE_STOP_MSB_REG                MKREG(HDMI_CTRL_PAGE, 0xc7)
+#define HDMI_CTRL_DE_STOP_LSB_REG                MKREG(HDMI_CTRL_PAGE, 0xc8)
+
+#define HDMI_CTRL_TBG_CNTRL_0_REG                MKREG(HDMI_CTRL_PAGE, 0xca)
+#  define HDMI_CTRL_TBG_CNTRL_0_SYNC_MTHD        (1 << 6)
+#  define HDMI_CTRL_TBG_CNTRL_0_SYNC_ONCE        (1 << 7)
+
+#define HDMI_CTRL_TBG_CNTRL_1_REG                MKREG(HDMI_CTRL_PAGE, 0xcb)
+#  define HDMI_CTRL_TBG_CNTRL_1_H_TGL            (1 << 0)
+#  define HDMI_CTRL_TBG_CNTRL_1_V_TGL            (1 << 1)
+#  define HDMI_CTRL_TBG_CNTRL_1_TGL_EN           (1 << 2)
+#  define HDMI_CTRL_TBG_CNTRL_1_DWIN_DIS         (1 << 6)
+
+#define HDMI_CTRL_HVF_CNTRL_0_REG                MKREG(HDMI_CTRL_PAGE, 0xe4)
+#  define HDMI_CTRL_HVF_CNTRL_0_INTPOL_BYPASS    (0 << 0)
+#  define HDMI_CTRL_HVF_CNTRL_0_PREFIL_NONE      (0 << 2)
+
+#define HDMI_CTRL_HVF_CNTRL_1_REG                MKREG(HDMI_CTRL_PAGE, 0xe5)
+#  define HDMI_CTRL_HVF_CNTRL_1_VQR(x)           (((x) & 3) << 2)
+#  define HDMI_CTRL_HVF_CNTRL_1_VQR_FULL         HDMI_CTRL_HVF_CNTRL_1_VQR(0)
+
+#define HDMI_CTRL_ENABLE_SPACE_REG               MKREG(HDMI_CTRL_PAGE, 0xd6)
+#define HDMI_CTRL_RPT_CNTRL_REG                  MKREG(HDMI_CTRL_PAGE, 0xf0)
+
+/* PLL Page Registers */
+
+#define HDMI_PLL_SERIAL_1_REG                    MKREG(HDMI_PLL_PAGE, 0x00)
+#  define HDMI_PLL_SERIAL_1_SRL_MAN_IP           (1 << 6)
+
+#define HDMI_PLL_SERIAL_2_REG                    MKREG(HDMI_PLL_PAGE, 0x01)
+#  define HDMI_PLL_SERIAL_2_SRL_NOSC(x)          (((x) & 0x3) << 0)
+#  define HDMI_PLL_SERIAL_2_SRL_PR(x)            (((x) & 0xf) << 4)
+
+#define HDMI_PLL_SERIAL_3_REG                    MKREG(HDMI_PLL_PAGE, 0x02)
+#  define HDMI_PLL_SERIAL_3_SRL_CCIR             (1 << 0)
+#  define HDMI_PLL_SERIAL_3_SRL_DE               (1 << 2)
+#  define HDMI_PLL_SERIAL_3_SRL_PXIN_SEL         (1 << 4)
+
+#define HDMI_PLL_SERIALIZER_REG                  MKREG(HDMI_PLL_PAGE, 0x03)
+#define HDMI_PLL_BUFFER_OUT_REG                  MKREG(HDMI_PLL_PAGE, 0x04)
+#define HDMI_PLL_SCG1_REG                        MKREG(HDMI_PLL_PAGE, 0x05)
+#define HDMI_PLL_SCG2_REG                        MKREG(HDMI_PLL_PAGE, 0x06)
+#define HDMI_PLL_SCGN1_REG                       MKREG(HDMI_PLL_PAGE, 0x07)
+#define HDMI_PLL_SCGN2_REG                       MKREG(HDMI_PLL_PAGE, 0x08)
+#define HDMI_PLL_SCGR1_REG                       MKREG(HDMI_PLL_PAGE, 0x09)
+#define HDMI_PLL_SCGR2_REG                       MKREG(HDMI_PLL_PAGE, 0x0a)
+
+#define HDMI_PLL_SEL_CLK_REG                     MKREG(HDMI_PLL_PAGE, 0x11)
+#  define HDMI_PLL_SEL_CLK_ENA_SC_CLK            (1 << 3)
+#  define HDMI_PLL_SEL_CLK_SEL_VRF_CLK(x)        (((x) & 3) << 1)
+#  define HDMI_PLL_SEL_CLK_SEL_CLK1              (1 << 0)
+
+#define HDMI_PLL_ANA_GENERAL_REG                 MKREG(HDMI_PLL_PAGE, 0x12)
+
+/* EDID Control Page Registers and Bit Definitions */
+
+#define HDMI_EDID_DATA_REG                       MKREG(HDMI_EDID_PAGE, 0x00)
+
+#define HDMI_EDID_REQ_REG                        MKREG(HDMI_EDID_PAGE, 0xfa)
+#  define HDMI_EDID_REQ_READ                     (1 << 0)
+
+#define HDMI_EDID_DEV_ADDR_REG                   MKREG(HDMI_EDID_PAGE, 0xfb)
+#  define HDMI_EDID_DEV_ADDR                     0xa0
+
+#define HDMI_EDID_OFFSET_REG                     MKREG(HDMI_EDID_PAGE, 0xfc)
+#  define HDMI_EDID_OFFSET                       0x00
+
+#define HDMI_EDID_SEGM_ADDR_REG                  MKREG(HDMI_EDID_PAGE, 0xfd)
+#define HDMI_EDID_SEGM_ADDR                      0x00
+
+#define HDMI_EDID_DDC_SEGM_REG                   MKREG(HDMI_EDID_PAGE, 0xfe)
+#  define HDMI_EDID_SEG_ADDR                     0x00
+
+/* HDCP (TDA19988AHN and TDA19988AET only) and OTP Page Registers and Bit
+ * Definitions.
+ */
+
+#define HDMI_HDCPOTP_TX3_REG                     MKREG(HDMI_HDCPOTP_PAGE, 0x9a)
+
+#define HDMI_HDCPOTP_TX4_REG                     MKREG(HDMI_HDCPOTP_PAGE, 0x9b)
+#  define HDMI_HDCPOTP_TX4_PDRAM                 (1 << 1)
+
+#define HDMI_HDCPOTP_TX33_REG                    MKREG(HDMI_HDCPOTP_PAGE, 0x9b)
+#  define HDMI_HDCPOTP_TX33_HDMI                 (1 << 1)
+
+/* Information Frames and Packets Page Registers and Bit Definitions */
+
+#define HDMI_INFO_VSP                            MKREG(HDMI_INFO_PAGE, 0x20)
+#define HDMI_INFO_AVI                            MKREG(HDMI_INFO_PAGE, 0x40)
+#define HDMI_INFO_SPD                            MKREG(HDMI_INFO_PAGE, 0x60)
+#define HDMI_INFO_AUD                            MKREG(HDMI_INFO_PAGE, 0x80)
+#define HDMI_INFO_MPS                            MKREG(HDMI_INFO_PAGE, 0xa0)
+
+/* Audio settings and content info packets page Registers and Bit Definitions */
+
+#define HDMI_AUDIO_ENC_CTRL_REG                  MKREG(HDMI_AUDIO_PAGE, 0x0d)
+#  define HDMI_AUDIO_ENC_CNTRL_DVI_MODE          (0 << 2)
+#  define HDMI_AUDIO_ENC_CNTRL_HDMI_MODE         (1 << 2)
+
+#define HDMI_AUDIO_DIP_IF_FLAGS_REG              MKREG(HDMI_AUDIO_PAGE, 0x0f)
+#  define HDMI_AUDIO_DIP_IF_FLAGS_IF1            (1 << 1)
+#  define HDMI_AUDIO_DIP_IF_FLAGS_IF2            (1 << 2) /* AVI IF on page 10h */
+#  define HDMI_AUDIO_DIP_IF_FLAGS_IF3            (1 << 3)
+#  define HDMI_AUDIO_DIP_IF_FLAGS_IF4            (1 << 4)
+#  define HDMI_AUDIO_DIP_IF_FLAGS_IF5            (1 << 5)
+
+/* Page Select Register (no page) */
+
+#define HDMI_PAGE_SELECT_REG                     MKREG(HDMI_NO_PAGE, 0xff)
+
+/* EDID Definitions */
+
+#define EDID_LENGTH                              128
+
+/* EDID fields */
+
+#define EDID_MODES0                              35
+#define EDID_MODES1                              36
+#define EDID_TIMING_START                        38
+#define EDID_TIMING_END                          54
+#define EDID_TIMING_X(v)                         (((v) + 31) * 8)
+#define EDID_FREQ(v)                             (((v) & 0x3f) + 60)
+#define EDID_RATIO(v)                            (((v) >> 6) & 0x3)
+#define EDID_RATIO_10x16                         0
+#define EDID_RATIO_3x4                           1
+#define EDID_RATIO_4x5                           2
+#define EDID_RATIO_9x16                          3
+
+#endif /* __DRIVERS_LCD_TDA19988_H */
diff --git a/include/nuttx/lcd/lcd_ioctl.h b/include/nuttx/lcd/lcd_ioctl.h
index 054edbea90..e80fcdb4bc 100644
--- a/include/nuttx/lcd/lcd_ioctl.h
+++ b/include/nuttx/lcd/lcd_ioctl.h
@@ -2,7 +2,7 @@
  * include/nuttx/input/slcd_ioctl.h
  * IOCTL commands for segment LCDs
  *
- *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
+ *   Copyright (C) 2013, 2019 Gregory Nutt. All rights reserved.
  *   Author: Gregory Nutt <gnutt@nuttx.org>
  *
  * Redistribution and use in source and binary forms, with or without
@@ -50,7 +50,12 @@
 
 /* IOCTL commands set aside for FT80x character driver */
 
-#define FT80X_NIOCTL_CMDS  16
-#define FT80X_NIOCTL_BASE  0x0001
+#define FT80X_NIOCTL_CMDS     16
+#define FT80X_NIOCTL_BASE     0x0001
+
+/* IOCTL commands set aside for TDA19988 HDMI encoder */
+
+#define TDA19988_NIOCTL_CMDS  1
+#define TDA19988_NIOCTL_BASE  (FT80X_NIOCTL_BASE + FT80X_NIOCTL_CMDS)
 
 #endif /* __INCLUDE_NUTTX_INPUT_LCD_IOCTL_H */
diff --git a/include/nuttx/lcd/tda19988.h b/include/nuttx/lcd/tda19988.h
new file mode 100644
index 0000000000..1e35b6d3f3
--- /dev/null
+++ b/include/nuttx/lcd/tda19988.h
@@ -0,0 +1,237 @@
+/****************************************************************************
+ * include/nuttx/lcd/tca19988.h
+ *
+ *   Copyright (C) 2019 Gregory Nutt. All rights reserved.
+ *   Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ *    used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_LCD_TDA19988_H
+#define __INCLUDE_NUTTX_LCD_TDA19988_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <nuttx/irq.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/lcd/lcd_ioctl.h>
+
+#ifdef CONFIG_LCD_TDA19988
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* TDA19988 IOCTL commands **************************************************
+ *
+ * TDA19988_IOC_VIDEOMODE:
+ *   Description:  Select the video mode.  This must be done as part of the
+ *                 initialization of the driver.  This is equivalent to
+ *                 calling tda18899_videomode() within the OS.
+ *   Argument:     A reference to a tda19988_videomode_s structure instance.
+ *                 See struct tda19988_videomode_s below.
+ *   Returns:      None
+ */
+
+#define TDA19988_IOC_VIDEOMODE _LCDIOC(TDA19988_NIOCTL_BASE + 0)
+
+/* Values for video mode flags */
+
+#define VID_PHSYNC             0x0001
+#define VID_NHSYNC             0x0002
+#define VID_PVSYNC             0x0004
+#define VID_NVSYNC             0x0008
+#define VID_INTERLACE          0x0010
+#define VID_DBLSCAN            0x0020
+#define VID_CSYNC              0x0040
+#define VID_PCSYNC             0x0080
+#define VID_NCSYNC             0x0100
+#define VID_HSKEW              0x0200
+#define VID_BCAST              0x0400
+#define VID_PIXMUX             0x1000
+#define VID_DBLCLK             0x2000
+#define VID_CLKDIV2            0x4000
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* Opaque handle returned by tda19988_register() */
+
+typedef FAR void *TDA19988_HANDLE;
+
+/* Structure that provides the TDA19988 video mode */
+
+struct tda19988_videomode_s
+{
+  int dot_clock;          /* Dot clock frequency in kHz. */
+
+  int hdisplay;
+  int hsync_start;
+  int hsync_end;
+  int htotal;
+
+  int vdisplay;
+  int vsync_start;
+  int vsync_end;
+  int vtotal;
+
+  int flags;              /* Video mode flags; see above. */
+  int hskew;
+};
+
+/* This structure defines the I2C interface.
+ * REVISIT:  This could be simplified because the CEC and HDMI reside on
+ * the same I2C bus (pins CSCL and CSCA).
+ */
+
+struct tda19988_i2c_s
+{
+  struct i2c_config_s config;   /* Frequency, address, address length */
+  FAR struct i2c_master_s *i2c; /* I2C bus interface */
+};
+
+/* This structure provides the TDA19988 lower half interface */
+
+struct i2c_master_s;     /* Forward reference */
+
+struct tda19988_lower_s
+{
+  /* I2C bus interfaces (CEC and HDMI lie on same I2C bus) */
+
+  struct tda19988_i2c_s cec;
+  struct tda19988_i2c_s hdmi;
+
+  /* Interrupt controls */
+
+  CODE int (*attach)(FAR const struct tda19988_lower_s *lower,
+                     xcpt_t handler, FAR void *arg);
+  CODE int (*enable)(FAR const struct tda19988_lower_s *lower,
+                     bool enable);
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: tda19988_register
+ *
+ * Description:
+ *   Create and register the the TDA19988 driver at 'devpath'
+ *
+ * Input Parameters:
+ *   devpath - The location to register the TDA19988 driver instance
+ *   lower   - The interface to the the TDA19988 lower half driver.
+ *
+ * Returned Value:
+ *   On success, non-NULL handle is returned that may be subsequently used
+ *   with tda19988_videomode().  NULL is returned on failure.
+ *
+ ****************************************************************************/
+
+TDA19988_HANDLE tda19988_register(FAR const char *devpath,
+                                  FAR const struct tda19988_lower_s *lower);
+
+/****************************************************************************
+ * Name: tda19988_videomode
+ *
+ * Description:
+ *   Initialize the TDA19988 driver to a specified video mode.  This is a
+ *   necessary part of the TDA19988 initialization:  A video mode  must be
+ *   configured before the driver is usable.
+ *
+ *   NOTE:  This may be done in two ways:  (1) via a call to
+ *   tda19988_videomode() from board-specific logic within the OS, or
+ *   equivalently (2) using the TDA19988_IOC_VIDEOMODE from application
+ *   logic outside of the OS.
+ *
+ * Input Parameters:
+ *   handle - The handle previously returned by tda19988_register().
+ *   mode   - The new video mode.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; a negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+int tda19988_videomode(TDA19988_HANDLE handle,
+                       FAR const struct tda19988_videomode_s *mode);
+
+/****************************************************************************
+ * Name: tda19988_read_edid
+ *
+ * Description:
+ *   Read the EDID (Extended Display Identification Data).
+ *
+ *   NOTE:  This may be done in two ways:  (1) via a call to
+ *   tda19988_read_edid() from board-specific logic within the OS, or
+ *   equivalently (2) using a standard read() to read the EDID from
+ *   application logic outside of the OS.
+ *
+ * Input Parameters:
+ *   handle - The handle previously returned by tda19988_register().
+ *   offset - The offset into the EDID to begin reading (0..127)
+ *   buffer - Location in which to return the EDID data
+ *   buflen - Size of buffer in bytes
+ *
+ * Returned Value:
+ *   On success, the number of bytes read is returned; a negated errno value
+ *   is returned on any failure.
+ *
+ ****************************************************************************/
+
+ssize_t tda19988_read_edid(TDA19988_HANDLE handle, off_t offset,
+                           FAR uint8_t *buffer, size_t buflen);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONFIG_LCD_TDA19988 */
+#endif /* __INCLUDE_NUTTX_LCD_TDA19988_H */