incubator-nuttx/drivers/lcd/tda19988.c

1795 lines
50 KiB
C

/****************************************************************************
* drivers/lcd/tda19988.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdbool.h>
#include <string.h>
#include <poll.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.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 */
mutex_t lock; /* 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 g_tda19988_fops =
{
tda19988_open, /* open */
tda19988_close, /* close */
tda19988_read, /* read */
tda19988_write, /* write */
tda19988_seek, /* seek */
tda19988_ioctl, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
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, &regval, 1);
if (ret < 0)
{
lcderr("ERROR: tda19988_getregs failed: %d\n", ret);
return ret;
}
/* Modify the register content */
regval &= ~clrbits;
regval |= setbits;
/* Write the modified register content */
ret = tda19988_putreg(dev, regaddr, regval);
if (ret < 0)
{
lcderr("ERROR: tda19988_putreg failed: %d\n", ret);
return ret;
}
return OK;
}
/****************************************************************************
* Name: tda19988_cec_getregs
*
* Description:
* Read the value from one or more TDA19988 CEC registers
*
* Returned Value:
* Zero (OK) is returned on success; otherwise a negated errno value is
* returned.
*
****************************************************************************/
static inline int tda19988_cec_getregs(FAR struct tda1988_dev_s *priv,
uint8_t regaddr, FAR uint8_t *regval,
int nregs)
{
return tda19988_getregs(&priv->lower->cec, regaddr, regval, nregs);
}
/****************************************************************************
* Name: tda19988_cec_putreg
*
* Description:
* Write a value to one TDA19988 CEC register
*
* Returned Value:
* Zero (OK) is returned on success; otherwise a negated errno value is
* returned.
*
****************************************************************************/
static inline int tda19988_cec_putreg(FAR struct tda1988_dev_s *priv,
uint8_t regaddr, uint8_t regval)
{
return tda19988_putreg(&priv->lower->cec, regaddr, regval);
}
/****************************************************************************
* Name: tda19988_cec_modifyreg
*
* Description:
* Modify bits in one TDA19988 CEC register
*
* Returned Value:
* Zero (OK) is returned on success; otherwise a negated errno value is
* returned.
*
****************************************************************************/
static inline int tda19988_cec_modifyreg(FAR struct tda1988_dev_s *priv,
uint8_t regaddr, uint8_t clrbits,
uint8_t setbits)
{
return tda19988_modifyreg(&priv->lower->cec, regaddr, clrbits, setbits);
}
/****************************************************************************
* Name: tda19988_select_page
*
* Description:
* Select the HDMI page (if not already selected)
*
* Returned Value:
* Zero (OK) is returned on success; otherwise a negated errno value is
* returned.
*
****************************************************************************/
static int tda19988_select_page(FAR struct tda1988_dev_s *priv, uint8_t page)
{
int ret = OK;
/* Check if we need to select a new page for this transfer */
if (page != HDMI_NO_PAGE && page != priv->page)
{
ret = tda19988_putreg(&priv->lower->hdmi,
REGADDR(HDMI_PAGE_SELECT_REG), page);
}
return ret;
}
/****************************************************************************
* Name: tda19988_hdmi_getregs
*
* Description:
* Read the value from one or more TDA19988 HDMI registers
*
* Returned Value:
* Zero (OK) is returned on success; otherwise a negated errno value is
* returned.
*
****************************************************************************/
static int tda19988_hdmi_getregs(FAR struct tda1988_dev_s *priv,
uint16_t reginfo, FAR uint8_t *regval,
int nregs)
{
uint8_t page = REGPAGE(reginfo);
uint8_t regaddr = REGADDR(reginfo);
int ret;
DEBUGASSERT(priv != NULL && regval != NULL && nregs > 0);
/* Select the HDMI page */
ret = tda19988_select_page(priv, page);
if (ret < 0)
{
lcderr("ERROR: Failed to select page %02x: %d\n", page, ret);
return ret;
}
/* Write the register address and read the register value */
ret = tda19988_getregs(&priv->lower->hdmi, regaddr, regval, nregs);
if (ret < 0)
{
lcderr("ERROR: tda19988_getregs() failed: %d\n", ret);
return -1;
}
lcdinfo("Read: %02x:%02x->%02x\n", page, regaddr, *regval);
return OK;
}
/****************************************************************************
* Name: tda19988_hdmi_putreg
*
* Description:
* Write an 8-bit value to one TDA19988 HDMI register
*
* Returned Value:
* Zero (OK) is returned on success; otherwise a negated errno value is
* returned.
*
****************************************************************************/
static int tda19988_hdmi_putreg(FAR struct tda1988_dev_s *priv,
uint16_t reginfo, uint8_t regval)
{
uint8_t page = REGPAGE(reginfo);
uint8_t regaddr = REGADDR(reginfo);
int ret;
/* Select the HDMI page */
ret = tda19988_select_page(priv, page);
if (ret < 0)
{
lcderr("ERROR: tda19988_select_page failed page %02x: %d\n",
page, ret);
return ret;
}
/* Write the register address and the register value */
ret = tda19988_putreg(&priv->lower->hdmi, regaddr, regval);
if (ret < 0)
{
lcderr("ERROR: tda19988_putreg() failed: %d\n", ret);
return ret;
}
lcdinfo("Read: %02x:%02x<-%02x\n", page, regaddr, regval);
return OK;
}
/****************************************************************************
* Name: tda19988_hdmi_putreg16
*
* Description:
* Write a 16-bit value to one TDA19988 HDMI register
*
* Returned Value:
* Zero (OK) is returned on success; otherwise a negated errno value is
* returned.
*
****************************************************************************/
static int tda19988_hdmi_putreg16(FAR struct tda1988_dev_s *priv,
uint16_t reginfo, uint16_t regval)
{
uint8_t page = REGPAGE(reginfo);
uint8_t regaddr = REGADDR(reginfo);
int ret;
/* Select the HDMI page */
ret = tda19988_select_page(priv, page);
if (ret < 0)
{
lcderr("ERROR: tda19988_select_page failed page %02x: %d\n",
page, ret);
return ret;
}
/* Write the register address and the register value */
ret = tda19988_putreg16(&priv->lower->hdmi, regaddr, regval);
if (ret < 0)
{
lcderr("ERROR: tda19988_putreg16() failed: %d\n", ret);
return ret;
}
lcdinfo("Read: %02x:%02x<-%04x\n", page, regaddr, regval);
return OK;
}
/****************************************************************************
* Name: tda19988_hdmi_modifyreg
*
* Description:
* Modify bits in one TDA19988 HDMI register
*
* Returned Value:
* Zero (OK) is returned on success; otherwise a negated errno value is
* returned.
*
****************************************************************************/
static int tda19988_hdmi_modifyreg(FAR struct tda1988_dev_s *priv,
uint16_t reginfo, uint8_t clrbits,
uint8_t setbits)
{
uint8_t page = REGPAGE(reginfo);
uint8_t regaddr = REGADDR(reginfo);
int ret;
/* Select the HDMI page */
ret = tda19988_select_page(priv, page);
if (ret < 0)
{
lcderr("ERROR: Failed to select page %02x: %d\n", page, ret);
return ret;
}
/* Read-modify-write the register contents */
ret = tda19988_modifyreg(&priv->lower->hdmi, regaddr, clrbits, setbits);
if (ret < 0)
{
lcderr("ERROR: tda19988_modifyreg failed: %d\n", ret);
return ret;
}
return OK;
}
/****************************************************************************
* Name: tda19988_connected
*
* Description:
* Check if a display is connected.
*
* Returned Values:
* DISPLAY_CONNECTED - A display is connected
* DISPLAY_DETACHED - No display is connected
* A negated errno value is returned on any failure.
*
****************************************************************************/
#if 0 /* Not used */
static int tda19988_connected(FAR struct tda1988_dev_s *priv)
{
uint8_t regval;
int ret;
ret = tda19988_cec_getregs(priv, CEC_STATUS_REG, &regval, 1);
if (ret < 0)
{
lcderr("ERROR: tda19988_cec_getregs failed: %d\n", ret);
return ret;
}
if ((regval & CEC_STATUS_CONNECTED) == 0)
{
lcdwarn("WARNING: Display not connected\n");
return DISPLAY_DETACHED;
}
else
{
lcdinfo("Display connect\n");
return DISPLAY_CONNECTED;
}
}
#endif
/****************************************************************************
* Name: tda19988_fetch_edid_block
*
* Description:
* Fetch one EDID block from the DSD.
*
* Returned Value:
* Zero (OK) is returned on success; A negated errno value is returned on
* any failure.
*
****************************************************************************/
static int tda19988_fetch_edid_block(FAR struct tda1988_dev_s *priv,
FAR uint8_t *buf, int block)
{
uint8_t data;
int attempt;
int ret;
ret = OK;
tda19988_hdmi_modifyreg(priv, HDMI_CTRL_INT_REG, 0, HDMI_CTRL_INT_EDID);
/* Block 0 */
tda19988_hdmi_putreg(priv, HDMI_EDID_DEV_ADDR_REG, 0xa0);
tda19988_hdmi_putreg(priv, HDMI_EDID_OFFSET_REG,
(block & 1) != 0 ? 128 : 0);
tda19988_hdmi_putreg(priv, HDMI_EDID_SEGM_ADDR_REG, 0x60);
tda19988_hdmi_putreg(priv, HDMI_EDID_DDC_SEGM_REG, block >> 1);
tda19988_hdmi_putreg(priv, HDMI_EDID_REQ_REG, HDMI_EDID_REQ_READ);
tda19988_hdmi_putreg(priv, HDMI_EDID_REQ_REG, 0);
data = 0;
for (attempt = 0; attempt < MAX_READ_ATTEMPTS; attempt++)
{
tda19988_hdmi_getregs(priv, HDMI_CTRL_INT_REG, &data, 1);
if ((data & HDMI_CTRL_INT_EDID) != 0)
{
break;
}
}
if (attempt == MAX_READ_ATTEMPTS)
{
ret = -ETIMEDOUT;
goto done;
}
if (tda19988_hdmi_getregs(priv, HDMI_EDID_DATA_REG, buf, EDID_LENGTH) != 0)
{
ret = -EIO;
goto done;
}
done:
tda19988_hdmi_modifyreg(priv, HDMI_CTRL_INT_REG, HDMI_CTRL_INT_EDID, 0);
return ret;
}
/****************************************************************************
* Name: tda19988_fetch_edid
*
* Description:
* Fetch the EDID block from the DSD.
*
* Returned Value:
* Zero (OK) is returned on success; A negated errno value is returned on
* any failure.
*
****************************************************************************/
static int tda19988_fetch_edid(struct tda1988_dev_s *priv)
{
int blocks;
int ret;
ret = 0;
if (priv->version == HDMI_CTRL_REV_TDA19988)
{
tda19988_hdmi_modifyreg(priv, HDMI_HDCPOTP_TX4_REG,
HDMI_HDCPOTP_TX4_PDRAM, 0);
}
ret = tda19988_fetch_edid_block(priv, priv->edid, 0);
if (ret < 0)
{
goto done;
}
blocks = priv->edid[EDID_TRAILER_NEXTENSIONS_OFFSET];
if (blocks > 0)
{
FAR uint8_t *edid;
unsigned int edid_len;
int i;
edid_len = EDID_LENGTH * (blocks + 1);
edid = kmm_realloc(priv->edid, edid_len);
if (edid == NULL)
{
lcderr("ERROR: Failed to kmm_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 */
inode = filep->f_inode;
priv = inode->i_private;
DEBUGASSERT(priv != NULL);
/* Get exclusive access to the driver instance */
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
return ret;
}
/* Increment the reference count on the driver instance */
DEBUGASSERT(priv->crefs != UINT8_MAX);
priv->crefs++;
nxmutex_unlock(&priv->lock);
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 */
inode = filep->f_inode;
priv = inode->i_private;
DEBUGASSERT(priv != NULL);
/* Get exclusive access to the driver */
ret = nxmutex_lock(&priv->lock);
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
nxmutex_unlock(&priv->lock);
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 */
inode = filep->f_inode;
priv = inode->i_private;
DEBUGASSERT(priv != NULL);
/* Get exclusive access to the driver */
ret = nxmutex_lock(&priv->lock);
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;
}
nxmutex_unlock(&priv->lock);
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 */
inode = filep->f_inode;
priv = inode->i_private;
DEBUGASSERT(priv != NULL);
/* Get exclusive access to the driver */
ret = nxmutex_lock(&priv->lock);
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;
}
nxmutex_unlock(&priv->lock);
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 */
inode = filep->f_inode;
priv = inode->i_private;
DEBUGASSERT(priv != NULL);
/* Get exclusive access to the driver */
ret = nxmutex_lock(&priv->lock);
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;
}
nxmutex_unlock(&priv->lock);
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 */
inode = filep->f_inode;
priv = inode->i_private;
DEBUGASSERT(priv != NULL);
/* Get exclusive access to the driver */
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
return ret;
}
if (setup)
{
poll_notify(&fds, 1, POLLIN | POLLOUT);
}
nxmutex_unlock(&priv->lock);
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->i_private != NULL);
priv = inode->i_private;
/* Get exclusive access to the driver */
ret = nxmutex_lock(&priv->lock);
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;
nxmutex_unlock(&priv->lock);
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 interpreter */
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 */
nxmutex_destroy(&priv->lock);
/* 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 = 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 = 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;
nxmutex_init(&priv->lock);
/* 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, &g_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 = nxmutex_lock(&priv->lock);
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);
}
nxmutex_unlock(&priv->lock);
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 = nxmutex_lock(&priv->lock);
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);
}
nxmutex_unlock(&priv->lock);
return nread;
}