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, ®val, 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, ®val, 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 */