6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
1815 lines
51 KiB
C
1815 lines
51 KiB
C
/****************************************************************************
|
|
* 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/video/edid.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);
|
|
static int tda19988_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup);
|
|
#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 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 */
|
|
tda19988_poll /* poll */
|
|
#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("Write: %02x<-%02x\n", regaddr, *regval);
|
|
lcderrdumpbuffer("Read:", regval, nregs);
|
|
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\n", 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<-%04x\n", 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[EDID_TRAILER_NEXTENSIONS_OFFSET];
|
|
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)
|
|
{
|
|
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 videomode_s structure
|
|
* instance.
|
|
* Returns: None
|
|
*/
|
|
|
|
case TDA19988_IOC_VIDEOMODE:
|
|
{
|
|
FAR const struct videomode_s *mode =
|
|
(FAR const struct 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
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;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 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->dotclock;
|
|
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);
|
|
|
|
priv->lower->attach(priv->lower, NULL, NULL);
|
|
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. The
|
|
* standard location would be a path like /dev/hdmi0.
|
|
* 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;
|
|
|
|
nxsem_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 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;
|
|
}
|