]> Zhao Yanbai Git Server - minix.git/commitdiff
tda19988: driver for the TDA19988 HDMI Transmitter 89/689/1
authorThomas Cort <tcort@minix3.org>
Mon, 29 Jul 2013 16:21:33 +0000 (12:21 -0400)
committerThomas Cort <tcort@minix3.org>
Mon, 29 Jul 2013 16:38:01 +0000 (12:38 -0400)
Change-Id: Ia7750df3dd4ec4bd68624c800a0241c70eea7ca4

distrib/sets/lists/minix/md.evbarm
drivers/Makefile
drivers/tda19988/Makefile [new file with mode: 0644]
drivers/tda19988/README.txt [new file with mode: 0644]
drivers/tda19988/tda19988.c [new file with mode: 0644]
etc/system.conf
etc/usr/rc

index e671d2f73112f263dbd7ca103ba1265cc9de13a6..70d70eaa9f193c514c6eeeadb4a1148d5285f219 100644 (file)
 ./usr/sbin/gpio                                minix-sys
 ./usr/sbin/i2c                         minix-sys
 ./usr/sbin/random                      minix-sys
+./usr/sbin/tda19988                    minix-sys
 ./usr/tests/minix-posix/mod            minix-sys
 ./usr/tests/minix-posix/test63         minix-sys
index 99fe499c296047443b91ec44d8f08bd5f25f04d6..3a5e7c3530af0f4db6aa8140aa47997665e5eca5 100644 (file)
@@ -23,7 +23,7 @@ SUBDIR= ahci amddev atl2 at_wini audio dec21140A dp8390 dpeth \
 .endif
 
 .if ${MACHINE_ARCH} == "earm"
-SUBDIR=  cat24c256 fb gpio i2c mmc log tty random
+SUBDIR=  cat24c256 fb gpio i2c mmc log tda19988 tty random
 .endif
 
 .endif # ${MKIMAGEONLY} != "yes"
diff --git a/drivers/tda19988/Makefile b/drivers/tda19988/Makefile
new file mode 100644 (file)
index 0000000..f9605d9
--- /dev/null
@@ -0,0 +1,14 @@
+# Makefile for the tda19988 HDMI framer found on the BeagleBone Black.
+PROG=  tda19988
+SRCS=  tda19988.c
+
+DPADD+=        ${LIBI2CDRIVER} ${LIBBLOCKDRIVER} ${LIBSYS} ${LIBTIMERS}
+LDADD+=        -li2cdriver -lblockdriver -lsys -ltimers
+
+MAN=
+
+BINDIR?= /usr/sbin
+
+CPPFLAGS+=     -I${NETBSDSRCDIR}
+
+.include <minix.service.mk>
diff --git a/drivers/tda19988/README.txt b/drivers/tda19988/README.txt
new file mode 100644 (file)
index 0000000..7b47e66
--- /dev/null
@@ -0,0 +1,79 @@
+TDA19988 Driver (HDMI Framer)
+=============================
+
+Overview
+--------
+
+This is the driver for the HDMI Framer chip commonly found on the BeagleBone
+Black.
+
+Interface
+---------
+
+To make things easy for the frame buffer driver, a block device driver
+interface is provided. Read requests cause the TDA19988 driver to read
+the EDID and return the data.
+
+Documentation
+-------------
+
+The documentation available is rather thin. NXP doesn't provide a
+datasheet on their website and they did not respond to my request
+for a datasheet. There are a few other sources of information:
+
+ * TDA9983B.pdf - this chip is similar but not the same. Full
+ register descriptions are provided. This was suggested to me by
+ the author of the Linux driver and someone at BeagleBoard.org as
+ neither of them were able to get a full datasheet for the TDA19988.
+
+ * TDA19988.pdf - you can probably find this on the net. It's a
+ pre-production draft of the datasheet for the TDA19988. It has
+ some information about how things work, but it doesn't give details
+ on the registers.
+
+ * LPC4350_FPU_TFT_HDMI-v2.0/Driver/tda19988.c - there's some
+ example code from NXP for a Hitex LPC4350 REV A5 which includes
+ a BSD licensed driver from NXP. It isn't complete as it only does
+ test mode, put-through mode, and EDID reading. Some of the comments
+ in the code make it seem like it isn't totally working/tested.
+
+ * linux/drivers/gpu/drm/i2c/tda998x_drv.c - there is a Linux driver
+ which implements a lot of functionality. As is always the case,
+ great care has to be taken to only study the code to learn the
+ functionality of the chip and not reproduce/copy the code.
+
+Limitations
+-----------
+
+Currently, only the EDID reading functionality is implemented.
+
+Testing the Code
+----------------
+
+Starting up an instance:
+
+/bin/service up /usr/sbin/tda19988 -label tda19988.1.3470 \
+       -args 'cec_bus=1 cec_address=0x34 hdmi_bus=1 hdmi_address=0x70'
+
+Killing an instance:
+
+/bin/service down tda19988.1.3470
+
+The driver is meant to be accessed from other drivers using the block
+device protocol, so it doesn't have a reserved major number and device file.
+However, if you want a simple test from user space, you can create a temporary
+device file to read the EDID like this:
+
+cd /dev
+mknod tda19988 b 32 0
+chmod 600 tda19988
+/bin/service up /usr/sbin/tda19988 -label tda19988.1.3470 \
+       -dev /dev/tda19988 \
+       -args 'cec_bus=1 cec_address=0x34 hdmi_bus=1 hdmi_address=0x70'
+dd if=/dev/tda19988 of=/root/edid.dat count=1 bs=128
+/bin/service down tda19988.1.3470
+hexdump -C /root/edid.dat
+rm tda19988
+
+The hexdump should begin with the EDID magic number: 00 ff ff ff ff ff ff 00
+
diff --git a/drivers/tda19988/tda19988.c b/drivers/tda19988/tda19988.c
new file mode 100644 (file)
index 0000000..0d19422
--- /dev/null
@@ -0,0 +1,1086 @@
+#include <minix/blockdriver.h>
+#include <minix/drivers.h>
+#include <minix/ds.h>
+#include <minix/i2c.h>
+#include <minix/i2cdriver.h>
+#include <minix/log.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* constants */
+#define NR_DEVS 1              /* number of devices this driver handles */
+#define TDA19988_DEV 0         /* index of TDA19988 device */
+#define EDID_LEN 128           /* length of standard EDID block */
+
+/* When passing data over a grant one needs to pass
+ * a buffer to sys_safecopy copybuff is used for that*/
+#define COPYBUF_SIZE 0x1000    /* 4k buf */
+static unsigned char copybuf[COPYBUF_SIZE];
+
+/* The device has two I2C interfaces CEC (0x34) and HDMI (0x70). This driver
+ * needs access to both.
+ */
+
+/*
+ * CEC - Register and Bit Definitions
+ */
+
+#define CEC_STATUS_REG 0xfe
+#define CEC_STATUS_CONNECTED_MASK 0x02
+
+#define CEC_ENABLE_REG 0xff
+#define CEC_ENABLE_ALL_MASK 0x87
+
+/*
+ * HDMI - Pages
+ *
+ * The HDMI part is much bigger than the CEC part. Memory is accessed according
+ * to page and address. Once the page is set, only the address needs to be
+ * sent if accessing memory locations within the same page (you don't need to
+ * send the page number every time).
+ */
+
+#define HDMI_CTRL_PAGE 0x00
+#define HDMI_PPL_PAGE 0x02
+#define HDMI_EDID_PAGE 0x09
+#define HDMI_INFO_PAGE 0x10
+#define HDMI_AUDIO_PAGE 0x11
+#define HDMI_HDCP_OTP_PAGE 0x12
+#define HDMI_GAMUT_PAGE 0x13
+
+/* 
+ * The page select register isn't part of a page. A dummy value of 0xff is
+ * used to signfiy this in the code.
+ */
+#define HDMI_PAGELESS 0xff
+
+/*
+ * Control Page Registers and Bit Definitions
+ */
+
+#define HDMI_CTRL_REV_LO_REG 0x00
+#define HDMI_CTRL_REV_HI_REG 0x02
+
+#define HDMI_CTRL_RESET_REG 0x0a
+#define HDMI_CTRL_RESET_DDC_MASK 0x02
+
+#define HDMI_CTRL_DDC_CTRL_REG 0x0b
+#define HDMI_CTRL_DDC_EN_MASK 0x00
+
+#define HDMI_CTRL_DDC_CLK_REG 0x0c
+#define HDMI_CTRL_DDC_CLK_EN_MASK 0x01
+
+#define HDMI_CTRL_INTR_CTRL_REG 0x0f
+#define HDMI_CTRL_INTR_EN_GLO_MASK 0x04
+
+#define HDMI_CTRL_INT_REG 0x11
+#define HDMI_CTRL_INT_EDID_MASK 0x02
+
+/*
+ * EDID Page Registers and Bit Definitions
+ */
+
+#define HDMI_EDID_DATA_REG 0x00
+
+#define HDMI_EDID_DEV_ADDR_REG 0xfb
+#define HDMI_EDID_DEV_ADDR 0xa0
+
+#define HDMI_EDID_OFFSET_REG 0xfc
+#define HDMI_EDID_OFFSET 0x00
+
+#define HDMI_EDID_SEG_PTR_ADDR_REG 0xfc
+#define HDMI_EDID_SEG_PTR_ADDR 0x00
+
+#define HDMI_EDID_SEG_ADDR_REG 0xfe
+#define HDMI_EDID_SEG_ADDR 0x00
+
+#define HDMI_EDID_REQ_REG 0xfa
+#define HDMI_EDID_REQ_READ_MASK 0x01
+
+/*
+ * HDCP and OTP
+ */
+#define HDMI_HDCP_OTP_DDC_CLK_REG 0x9a
+#define HDMI_HDCP_OTP_DDC_CLK_MASK 0x27
+
+/* this register/mask isn't documented but it has to be cleared/set */
+#define HDMI_HDCP_OTP_SOME_REG 0x9b
+#define HDMI_HDCP_OTP_SOME_MASK 0x02
+
+/*
+ * Pageless Registers
+ */
+
+#define HDMI_PAGE_SELECT_REG 0xff
+
+/*
+ * Constants
+ */
+
+/* Revision of the TDA19988. */
+#define HDMI_REV_TDA19988 0x0331
+
+/* the bus that this device is on (counting starting at 1) */
+static uint32_t cec_bus;
+static uint32_t hdmi_bus;
+
+/* slave address of the device */
+static i2c_addr_t cec_address;
+static i2c_addr_t hdmi_address;
+
+/* endpoint for the driver for the bus itself. */
+static endpoint_t cec_bus_endpoint;
+static endpoint_t hdmi_bus_endpoint;
+
+/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
+static struct log log = {
+       .name = "tda19988",
+       .log_level = LEVEL_INFO,
+       .log_func = default_log
+};
+
+static void sef_local_startup(void);
+static int sef_cb_lu_state_save(int);
+static int lu_state_restore(void);
+static int sef_cb_init(int type, sef_init_info_t * info);
+
+/* CEC Module */
+static int is_display_connected(void);
+static int enable_hdmi_module(void);
+
+/* HDMI Module */
+static int set_page(uint8_t page);
+static int hdmi_read(uint8_t page, uint8_t reg, uint8_t * val);
+static int hdmi_write(uint8_t page, uint8_t reg, uint8_t val);
+static int hdmi_set(uint8_t page, uint8_t reg, uint8_t mask);
+static int hdmi_clear(uint8_t page, uint8_t reg, uint8_t mask);
+
+static int hdmi_ddc_enable(void);
+static int hdmi_init(void);
+static int check_revision(void);
+static int read_edid(uint8_t * data, size_t count);
+
+/* libblockdriver callbacks */
+static int tda19988_blk_open(dev_t minor, int access);
+static int tda19988_blk_close(dev_t minor);
+static ssize_t tda19988_blk_transfer(dev_t minor, int do_write, u64_t pos,
+    endpoint_t endpt, iovec_t * iov, unsigned count, int flags);
+static int tda19988_blk_ioctl(dev_t minor, unsigned int request,
+    endpoint_t endpt, cp_grant_id_t grant);
+static struct device *tda19988_blk_part(dev_t minor);
+static int tda19988_blk_other(message * m);
+
+/* Entry points into the device dependent code of block drivers. */
+struct blockdriver tda19988_tab = {
+       .bdr_type = BLOCKDRIVER_TYPE_OTHER,
+       .bdr_open = tda19988_blk_open,
+       .bdr_close = tda19988_blk_close,
+       .bdr_transfer = tda19988_blk_transfer,
+       .bdr_ioctl = tda19988_blk_ioctl,        /* nop -- always returns EINVAL */
+       .bdr_cleanup = NULL,    /* nothing allocated -- nothing to clean up */
+       .bdr_part = tda19988_blk_part,
+       .bdr_geometry = NULL,   /* no geometry (cylinders, heads, sectors, etc) */
+       .bdr_intr = NULL,       /* i2c devices don't generate interrupts */
+       .bdr_alarm = NULL,      /* alarm not needed */
+       .bdr_other = tda19988_blk_other,        /* to recv notify events from DS */
+       .bdr_device = NULL      /* 1 insance per device, threads not needed */
+};
+
+/* counts the number of times a device file is open */
+static int openct[NR_DEVS];
+
+/* base and size of each device */
+static struct device geom[NR_DEVS];
+
+static int
+tda19988_blk_open(dev_t minor, int access)
+{
+       log_trace(&log, "tda19988_blk_open(%d,%d)\n", minor, access);
+       if (tda19988_blk_part(minor) == NULL) {
+               return ENXIO;
+       }
+
+       openct[minor]++;
+
+       return OK;
+}
+
+static int
+tda19988_blk_close(dev_t minor)
+{
+       log_trace(&log, "tda19988_blk_close(%d)\n", minor);
+       if (tda19988_blk_part(minor) == NULL) {
+               return ENXIO;
+       }
+
+       if (openct[minor] < 1) {
+               log_warn(&log, "closing unopened device %d\n", minor);
+               return EINVAL;
+       }
+       openct[minor]--;
+
+       return OK;
+}
+
+static ssize_t
+tda19988_blk_transfer(dev_t minor, int do_write, u64_t pos64,
+    endpoint_t endpt, iovec_t * iov, unsigned nr_req, int flags)
+{
+       unsigned count;
+       struct device *dv;
+       u64_t dv_size;
+       int r;
+       u64_t position;
+       cp_grant_id_t grant;
+
+       log_trace(&log, "tda19988_blk_transfer()\n");
+
+       /* Get minor device information. */
+       dv = tda19988_blk_part(minor);
+       if (dv == NULL) {
+               return ENXIO;
+       }
+
+       if (nr_req > NR_IOREQS) {
+               return EINVAL;
+       }
+
+       dv_size = dv->dv_size;
+       if (pos64 >= dv_size) {
+               return OK;      /* Beyond EOF */
+       }
+
+       if (nr_req > 0) {
+
+               /* How much to transfer and where to / from. */
+               count = iov->iov_size;
+               grant = (cp_grant_id_t) iov->iov_addr;
+
+               /* check for EOF */
+               if (pos64 >= dv_size) {
+                       return 0;
+               }
+
+               /* don't go past the end of the device */
+               if (pos64 + count > dv_size) {
+                       count = dv_size - position;
+               }
+
+               /* don't overflow copybuf */
+               if (count > COPYBUF_SIZE) {
+                       count = COPYBUF_SIZE;
+               }
+
+               log_debug(&log, "transfering 0x%x bytes\n", count);
+
+               if (do_write) {
+
+                       log_warn(&log, "Error: writing to read-only device\n");
+                       return EACCES;
+
+               } else {
+
+                       if (is_display_connected() == 1) {
+
+                               r = hdmi_init();
+                               if (r != OK) {
+                                       log_warn(&log,
+                                           "Failed to enable HDMI module\n");
+                                       return EIO;
+                               }
+
+                               memset(copybuf, '\0', COPYBUF_SIZE);
+                               r = read_edid(copybuf, count);
+                               if (r != OK) {
+                                       log_warn(&log,
+                                           "read_edid() failed (r=%d)\n", r);
+                                       return r;
+                               }
+
+                               r = sys_safecopyto(endpt, grant, (vir_bytes)
+                                   0, (vir_bytes) copybuf, count);
+                               if (r != OK) {
+                                       log_warn(&log, "safecopyto failed\n");
+                                       return EINVAL;
+                               }
+
+                               return iov->iov_size;
+                       } else {
+                               log_warn(&log, "Display not connected.\n");
+                               return ENODEV;
+                       }
+               }
+       } else {
+
+               /* empty request */
+               return 0;
+       }
+}
+
+static int
+tda19988_blk_ioctl(dev_t minor, unsigned int request, endpoint_t endpt,
+    cp_grant_id_t grant)
+{
+       log_trace(&log, "tda19988_blk_ioctl(%d)\n", minor);
+       /* no supported ioctls for this device */
+       return EINVAL;
+}
+
+static struct device *
+tda19988_blk_part(dev_t minor)
+{
+       log_trace(&log, "tda19988_blk_part(%d)\n", minor);
+
+       if (minor >= NR_DEVS) {
+               return NULL;
+       }
+
+       return &geom[minor];
+}
+
+static int
+tda19988_blk_other(message * m)
+{
+       int r;
+
+       log_trace(&log, "tda19988_blk_other(0x%x)\n", m->m_type);
+
+       switch (m->m_type) {
+       case NOTIFY_MESSAGE:
+               if (m->m_source == DS_PROC_NR) {
+                       log_debug(&log,
+                           "bus driver changed state, update endpoint\n");
+                       i2cdriver_handle_bus_update(&cec_bus_endpoint, cec_bus,
+                           cec_address);
+                       i2cdriver_handle_bus_update(&hdmi_bus_endpoint,
+                           hdmi_bus, hdmi_address);
+               }
+               r = OK;
+               break;
+       default:
+               log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
+               r = EINVAL;
+               break;
+       }
+
+       return r;
+}
+
+/*
+ * Check to see if a display is connected.
+ * Returns 1 for yes, 0 for no, -1 for error.
+ */
+static int
+is_display_connected(void)
+{
+       int r;
+       minix_i2c_ioctl_exec_t ioctl_exec;
+
+       memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
+
+       /* Read from CEC */
+       ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
+       ioctl_exec.iie_addr = cec_address;
+
+       /* write the register address */
+       ioctl_exec.iie_cmd[0] = CEC_STATUS_REG;
+       ioctl_exec.iie_cmdlen = 1;
+
+       /* read 1 byte */
+       ioctl_exec.iie_buflen = 1;
+
+       r = i2cdriver_exec(cec_bus_endpoint, &ioctl_exec);
+       if (r != OK) {
+               log_warn(&log, "Reading connection status failed (r=%d)\n", r);
+               return -1;
+       }
+
+       if ((CEC_STATUS_CONNECTED_MASK & ioctl_exec.iie_buf[0]) == 0) {
+               log_debug(&log, "No Display Detected\n");
+               return 0;
+       } else {
+               log_debug(&log, "Display Detected\n");
+               return 1;
+       }
+}
+
+/*
+ * Enable all the modules and clocks.
+ */
+static int
+enable_hdmi_module(void)
+{
+       int r;
+       minix_i2c_ioctl_exec_t ioctl_exec;
+
+       memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
+
+       /* Write to CEC */
+       ioctl_exec.iie_op = I2C_OP_WRITE_WITH_STOP;
+       ioctl_exec.iie_addr = cec_address;
+
+       /* write the register address and value */
+       ioctl_exec.iie_buf[0] = CEC_ENABLE_REG;
+       ioctl_exec.iie_buf[1] = CEC_ENABLE_ALL_MASK;
+       ioctl_exec.iie_buflen = 2;
+
+       r = i2cdriver_exec(cec_bus_endpoint, &ioctl_exec);
+       if (r != OK) {
+               log_warn(&log, "Writing enable bits failed (r=%d)\n", r);
+               return -1;
+       }
+
+       log_debug(&log, "HDMI module enabled\n");
+
+       return OK;
+}
+
+static int
+set_page(uint8_t page)
+{
+       int r;
+       static int current_page = HDMI_PAGELESS;
+
+       if (page != current_page) {
+
+               r = hdmi_write(HDMI_PAGELESS, HDMI_PAGE_SELECT_REG, page);
+               if (r != OK) {
+                       return r;
+               }
+
+               current_page = page;
+       }
+
+       return OK;
+}
+
+static int
+hdmi_read_block(uint8_t page, uint8_t reg, uint8_t * buf, size_t buflen)
+{
+
+       int r;
+       minix_i2c_ioctl_exec_t ioctl_exec;
+
+       if (buf == NULL || buflen > I2C_EXEC_MAX_BUFLEN) {
+               log_warn(&log,
+                   "Read block called with NULL pointer or invalid buflen.\n");
+               return EINVAL;
+       }
+
+       if (page != HDMI_PAGELESS) {
+               r = set_page(page);
+               if (r != OK) {
+                       log_warn(&log, "Unable to set page to 0x%x\n", page);
+                       return r;
+               }
+       }
+
+       memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
+
+       /* Read from HDMI */
+       ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
+       ioctl_exec.iie_addr = hdmi_address;
+
+       /* write the register address */
+       ioctl_exec.iie_cmd[0] = reg;
+       ioctl_exec.iie_cmdlen = 1;
+
+       /* read bytes */
+       ioctl_exec.iie_buflen = buflen;
+
+       r = i2cdriver_exec(hdmi_bus_endpoint, &ioctl_exec);
+       if (r != OK) {
+               log_warn(&log, "hdmi_read() failed (r=%d)\n", r);
+               return -1;
+       }
+
+       memcpy(buf, ioctl_exec.iie_buf, buflen);
+
+       log_trace(&log, "Read %d bytes from reg 0x%x in page 0x%x\n", buflen,
+           reg, page);
+
+       return OK;
+}
+
+static int
+hdmi_read(uint8_t page, uint8_t reg, uint8_t * val)
+{
+
+       int r;
+       minix_i2c_ioctl_exec_t ioctl_exec;
+
+       if (val == NULL) {
+               log_warn(&log, "Read called with NULL pointer\n");
+               return EINVAL;
+       }
+
+       if (page != HDMI_PAGELESS) {
+               r = set_page(page);
+               if (r != OK) {
+                       log_warn(&log, "Unable to set page to 0x%x\n", page);
+                       return r;
+               }
+       }
+
+       memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
+
+       /* Read from HDMI */
+       ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
+       ioctl_exec.iie_addr = hdmi_address;
+
+       /* write the register address */
+       ioctl_exec.iie_cmd[0] = reg;
+       ioctl_exec.iie_cmdlen = 1;
+
+       /* read 1 byte */
+       ioctl_exec.iie_buflen = 1;
+
+       r = i2cdriver_exec(hdmi_bus_endpoint, &ioctl_exec);
+       if (r != OK) {
+               log_warn(&log, "hdmi_read() failed (r=%d)\n", r);
+               return -1;
+       }
+
+       *val = ioctl_exec.iie_buf[0];
+
+       log_trace(&log, "Read 0x%x from reg 0x%x in page 0x%x\n", *val, reg,
+           page);
+
+       return OK;
+}
+
+static int
+hdmi_write(uint8_t page, uint8_t reg, uint8_t val)
+{
+
+       int r;
+       minix_i2c_ioctl_exec_t ioctl_exec;
+
+       if (page != HDMI_PAGELESS) {
+               r = set_page(page);
+               if (r != OK) {
+                       log_warn(&log, "Unable to set page to 0x%x\n", page);
+                       return r;
+               }
+       }
+
+       memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
+
+       /* Write to HDMI */
+       ioctl_exec.iie_op = I2C_OP_WRITE_WITH_STOP;
+       ioctl_exec.iie_addr = hdmi_address;
+
+       /* write the register address and value */
+       ioctl_exec.iie_buf[0] = reg;
+       ioctl_exec.iie_buf[1] = val;
+       ioctl_exec.iie_buflen = 2;
+
+       r = i2cdriver_exec(hdmi_bus_endpoint, &ioctl_exec);
+       if (r != OK) {
+               log_warn(&log, "hdmi_write() failed (r=%d)\n", r);
+               return -1;
+       }
+
+       log_trace(&log, "Successfully wrote 0x%x to reg 0x%x in page 0x%x\n",
+           val, reg, page);
+
+       return OK;
+}
+
+static int
+hdmi_set(uint8_t page, uint8_t reg, uint8_t mask)
+{
+
+       int r;
+       uint8_t val;
+
+       val = 0x00;
+
+       r = hdmi_read(page, reg, &val);
+       if (r != OK) {
+               return r;
+       }
+
+       val |= mask;
+
+       r = hdmi_write(page, reg, val);
+       if (r != OK) {
+               return r;
+       }
+
+       return OK;
+}
+
+static int
+hdmi_clear(uint8_t page, uint8_t reg, uint8_t mask)
+{
+
+       int r;
+       uint8_t val;
+
+       val = 0x00;
+
+       r = hdmi_read(page, reg, &val);
+       if (r != OK) {
+               return r;
+       }
+
+       val &= ~mask;
+
+       r = hdmi_write(page, reg, val);
+       if (r != OK) {
+               return r;
+       }
+
+       return OK;
+}
+
+static int
+check_revision(void)
+{
+       int r;
+       uint8_t rev_lo;
+       uint8_t rev_hi;
+       uint16_t revision;
+
+       r = hdmi_read(HDMI_CTRL_PAGE, HDMI_CTRL_REV_LO_REG, &rev_lo);
+       if (r != OK) {
+               log_warn(&log, "Failed to read rev_lo (r=%d)\n", r);
+               return -1;
+       }
+
+       r = hdmi_read(HDMI_CTRL_PAGE, HDMI_CTRL_REV_HI_REG, &rev_hi);
+       if (r != OK) {
+               log_warn(&log, "Failed to read rev_hi (r=%d)\n", r);
+               return -1;
+       }
+
+       revision = ((rev_hi << 8) | rev_lo);
+       if (revision != HDMI_REV_TDA19988) {
+
+               log_warn(&log, "Unrecognized value in revision registers.\n");
+               log_warn(&log, "Read: 0x%x | Expected: 0x%x\n", revision,
+                   HDMI_REV_TDA19988);
+               return -1;
+       }
+
+       log_debug(&log, "Device Revision: 0x%x\n", revision);
+
+       return OK;
+}
+
+static int
+hdmi_ddc_enable(void)
+{
+       int r;
+
+       /* Soft Reset DDC Bus */
+       r = hdmi_set(HDMI_CTRL_PAGE, HDMI_CTRL_RESET_REG,
+           HDMI_CTRL_RESET_DDC_MASK);
+       if (r != OK) {
+               return r;
+       }
+       micro_delay(100000);
+       r = hdmi_clear(HDMI_CTRL_PAGE, HDMI_CTRL_RESET_REG,
+           HDMI_CTRL_RESET_DDC_MASK);
+       if (r != OK) {
+               return r;
+       }
+       micro_delay(100000);
+
+       /* Enable DDC */
+       r = hdmi_write(HDMI_CTRL_PAGE, HDMI_CTRL_DDC_CTRL_REG,
+           HDMI_CTRL_DDC_EN_MASK);
+       if (r != OK) {
+               return r;
+       }
+
+       /* Setup the clock (I think) */
+       r = hdmi_write(HDMI_CTRL_PAGE, HDMI_CTRL_DDC_CLK_REG,
+           HDMI_CTRL_DDC_CLK_EN_MASK);
+       if (r != OK) {
+               return r;
+       }
+
+       r = hdmi_write(HDMI_HDCP_OTP_PAGE, HDMI_HDCP_OTP_DDC_CLK_REG,
+           HDMI_HDCP_OTP_DDC_CLK_MASK);
+       if (r != OK) {
+               return r;
+       }
+       log_debug(&log, "DDC Enabled\n");
+
+       return OK;
+}
+
+static int
+hdmi_init(void)
+{
+       int r;
+
+       /* Turn on HDMI module (slave 0x70) */
+       r = enable_hdmi_module();
+       if (r != OK) {
+               log_warn(&log, "HDMI Module Init Failed\n");
+               return -1;
+       }
+
+       /* Read chip version to ensure compatibility */
+       r = check_revision();
+       if (r != OK) {
+               log_warn(&log, "Couldn't find expected TDA19988 revision\n");
+               return -1;
+       }
+
+       /* Turn on DDC interface between TDA19988 and display */
+       r = hdmi_ddc_enable();
+       if (r != OK) {
+               log_warn(&log, "Failed to enable DDC\n");
+               return -1;
+       }
+
+       return OK;
+}
+
+static int
+read_edid(uint8_t * buf, size_t count)
+{
+       int r;
+       int i, j;
+       int tries;
+       int edid_ready;
+       uint8_t val;
+
+       log_debug(&log, "Reading edid...\n");
+
+       if (buf == NULL || count < EDID_LEN) {
+               log_warn(&log, "Expected 128 byte data buffer\n");
+               return -1;
+       }
+
+       r = hdmi_clear(HDMI_HDCP_OTP_PAGE, HDMI_HDCP_OTP_SOME_REG,
+           HDMI_HDCP_OTP_SOME_MASK);
+       if (r != OK) {
+               log_warn(&log, "Failed to clear bit in HDCP OTP reg\n");
+               return -1;
+       }
+
+       /* Enable EDID Block Read Interrupt */
+       r = hdmi_set(HDMI_CTRL_PAGE, HDMI_CTRL_INT_REG,
+           HDMI_CTRL_INT_EDID_MASK);
+       if (r != OK) {
+               log_warn(&log, "Failed to enable EDID Block Read interrupt\n");
+               return -1;
+       }
+
+       /* enable global interrupts */
+       r = hdmi_write(HDMI_CTRL_PAGE, HDMI_CTRL_INTR_CTRL_REG,
+           HDMI_CTRL_INTR_EN_GLO_MASK);
+       if (r != OK) {
+               log_warn(&log, "Failed to enable interrupts\n");
+               return -1;
+       }
+
+       /* Set Device Address */
+       r = hdmi_write(HDMI_EDID_PAGE, HDMI_EDID_DEV_ADDR_REG,
+           HDMI_EDID_DEV_ADDR);
+       if (r != OK) {
+               log_warn(&log, "Couldn't set device address\n");
+               return -1;
+       }
+
+       /* Set Offset */
+       r = hdmi_write(HDMI_EDID_PAGE, HDMI_EDID_OFFSET_REG, HDMI_EDID_OFFSET);
+       if (r != OK) {
+               log_warn(&log, "Couldn't set offset\n");
+               return -1;
+       }
+
+       /* Set Segment Pointer Address */
+       r = hdmi_write(HDMI_EDID_PAGE, HDMI_EDID_SEG_PTR_ADDR_REG,
+           HDMI_EDID_SEG_PTR_ADDR);
+       if (r != OK) {
+               log_warn(&log, "Couldn't set segment pointer address\n");
+               return -1;
+       }
+
+       /* Set Segment Address */
+       r = hdmi_write(HDMI_EDID_PAGE, HDMI_EDID_SEG_ADDR_REG,
+           HDMI_EDID_SEG_ADDR);
+       if (r != OK) {
+               log_warn(&log, "Couldn't set segment address\n");
+               return -1;
+       }
+
+       /* 
+        * Toggle EDID Read Request Bit to request a read.
+        */
+
+       r = hdmi_write(HDMI_EDID_PAGE, HDMI_EDID_REQ_REG,
+           HDMI_EDID_REQ_READ_MASK);
+       if (r != OK) {
+               log_warn(&log, "Couldn't set Read Request bit\n");
+               return -1;
+       }
+
+       r = hdmi_write(HDMI_EDID_PAGE, HDMI_EDID_REQ_REG, 0x00);
+       if (r != OK) {
+               log_warn(&log, "Couldn't clear Read Request bit\n");
+               return -1;
+       }
+
+       log_debug(&log, "Starting polling\n");
+
+       /* poll interrupt status flag */
+       edid_ready = 0;
+       for (tries = 0; tries < 100; tries++) {
+
+               r = hdmi_read(HDMI_CTRL_PAGE, HDMI_CTRL_INT_REG, &val);
+               if (r != OK) {
+                       log_warn(&log, "Read failed while polling int flag\n");
+                       return -1;
+               }
+
+               if (val & HDMI_CTRL_INT_EDID_MASK) {
+                       log_debug(&log, "Mask Set\n");
+                       edid_ready = 1;
+                       break;
+               }
+
+               micro_delay(1000);
+       }
+
+       if (!edid_ready) {
+               log_warn(&log, "Data Ready interrupt never fired.\n");
+               return EBUSY;
+       }
+
+       log_debug(&log, "Ready to read\n");
+
+       /* Finally, perform the read. */
+       memset(buf, '\0', count);
+       r = hdmi_read_block(HDMI_EDID_PAGE, HDMI_EDID_DATA_REG, buf, EDID_LEN);
+       if (r != OK) {
+               log_warn(&log, "Failed to read EDID data\n");
+               return -1;
+       }
+
+       /* Disable EDID Block Read Interrupt */
+       r = hdmi_clear(HDMI_CTRL_PAGE, HDMI_CTRL_INT_REG,
+           HDMI_CTRL_INT_EDID_MASK);
+       if (r != OK) {
+               log_warn(&log,
+                   "Failed to disable EDID Block Read interrupt\n");
+               return -1;
+       }
+
+       r = hdmi_set(HDMI_HDCP_OTP_PAGE, HDMI_HDCP_OTP_SOME_REG,
+           HDMI_HDCP_OTP_SOME_MASK);
+       if (r != OK) {
+               log_warn(&log, "Failed to set bit in HDCP/OTP reg\n");
+               return -1;
+       }
+
+       log_debug(&log, "Done EDID Reading\n");
+
+       return OK;
+}
+
+static int
+sef_cb_lu_state_save(int UNUSED(state))
+{
+       ds_publish_u32("cec_bus", cec_bus, DSF_OVERWRITE);
+       ds_publish_u32("hdmi_bus", hdmi_bus, DSF_OVERWRITE);
+       ds_publish_u32("cec_address", cec_address, DSF_OVERWRITE);
+       ds_publish_u32("hdmi_address", hdmi_address, DSF_OVERWRITE);
+       return OK;
+}
+
+static int
+lu_state_restore(void)
+{
+       /* Restore the state. */
+       u32_t value;
+
+       ds_retrieve_u32("cec_bus", &value);
+       ds_delete_u32("cec_bus");
+       cec_bus = (int) value;
+
+       ds_retrieve_u32("hdmi_bus", &value);
+       ds_delete_u32("hdmi_bus");
+       hdmi_bus = (int) value;
+
+       ds_retrieve_u32("cec_address", &value);
+       ds_delete_u32("cec_address");
+       cec_address = (int) value;
+
+       ds_retrieve_u32("hdmi_address", &value);
+       ds_delete_u32("hdmi_address");
+       hdmi_address = (int) value;
+
+       return OK;
+}
+
+static int
+sef_cb_init(int type, sef_init_info_t * UNUSED(info))
+{
+       int r;
+
+       if (type == SEF_INIT_LU) {
+               /* Restore the state. */
+               lu_state_restore();
+       }
+
+       geom[TDA19988_DEV].dv_base = ((u64_t) (0));
+       geom[TDA19988_DEV].dv_size = ((u64_t) (128));
+
+       /*
+        * CEC Module
+        */
+
+       /* look-up the endpoint for the bus driver */
+       cec_bus_endpoint = i2cdriver_bus_endpoint(cec_bus);
+       if (cec_bus_endpoint == 0) {
+               log_warn(&log, "Couldn't find bus driver.\n");
+               return EXIT_FAILURE;
+       }
+
+       /* claim the device */
+       r = i2cdriver_reserve_device(cec_bus_endpoint, cec_address);
+       if (r != OK) {
+               log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
+                   cec_address, r);
+               return EXIT_FAILURE;
+       }
+
+       /*
+        * HDMI Module
+        */
+
+       /* look-up the endpoint for the bus driver */
+       hdmi_bus_endpoint = i2cdriver_bus_endpoint(hdmi_bus);
+       if (hdmi_bus_endpoint == 0) {
+               log_warn(&log, "Couldn't find bus driver.\n");
+               return EXIT_FAILURE;
+       }
+
+       /* claim the device */
+       r = i2cdriver_reserve_device(hdmi_bus_endpoint, hdmi_address);
+       if (r != OK) {
+               log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
+                   hdmi_address, r);
+               return EXIT_FAILURE;
+       }
+
+       if (type != SEF_INIT_LU) {
+
+               /* sign up for updates about the i2c bus going down/up */
+               r = i2cdriver_subscribe_bus_updates(cec_bus);
+               if (r != OK) {
+                       log_warn(&log, "Couldn't subscribe to bus updates\n");
+                       return EXIT_FAILURE;
+               }
+
+               /* sign up for updates about the i2c bus going down/up */
+               r = i2cdriver_subscribe_bus_updates(hdmi_bus);
+               if (r != OK) {
+                       log_warn(&log, "Couldn't subscribe to bus updates\n");
+                       return EXIT_FAILURE;
+               }
+
+               i2cdriver_announce(cec_bus);
+               if (cec_bus != hdmi_bus) {
+                       i2cdriver_announce(hdmi_bus);
+               }
+
+               log_trace(&log, "announced\n");
+       }
+
+       return OK;
+}
+
+static void
+sef_local_startup(void)
+{
+       /*
+        * Register init callbacks. Use the same function for all event types
+        */
+       sef_setcb_init_fresh(sef_cb_init);
+       sef_setcb_init_lu(sef_cb_init);
+       sef_setcb_init_restart(sef_cb_init);
+
+       /*
+        * Register live update callbacks.
+        */
+       /* Agree to update immediately when LU is requested in a valid state. */
+       sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
+       /* Support live update starting from any standard state. */
+       sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
+       /* Register a custom routine to save the state. */
+       sef_setcb_lu_state_save(sef_cb_lu_state_save);
+
+       /* Let SEF perform startup. */
+       sef_startup();
+}
+
+static int
+tda19988_env_parse()
+{
+       int r;
+       long int cec_busl;
+       long int cec_addressl;
+       long int hdmi_busl;
+       long int hdmi_addressl;
+
+       r = env_parse("cec_bus", "d", 0, &cec_busl, 1, 3);
+       if (r != EP_SET) {
+               return -1;
+       }
+       cec_bus = (uint32_t) cec_busl;
+
+       r = env_parse("cec_address", "x", 0, &cec_addressl, 0x34, 0x37);
+       if (r != EP_SET) {
+               return -1;
+       }
+       cec_address = (i2c_addr_t) cec_addressl;
+
+       r = env_parse("hdmi_bus", "d", 0, &hdmi_busl, 1, 3);
+       if (r != EP_SET) {
+               return -1;
+       }
+       hdmi_bus = (uint32_t) hdmi_busl;
+
+       r = env_parse("hdmi_address", "x", 0, &hdmi_addressl, 0x70, 0x73);
+       if (r != EP_SET) {
+               return -1;
+       }
+       hdmi_address = (i2c_addr_t) hdmi_addressl;
+
+       return OK;
+}
+
+int
+main(int argc, char *argv[])
+{
+       int r;
+
+       env_setargs(argc, argv);
+
+       r = tda19988_env_parse();
+       if (r < 0) {
+               log_warn(&log,
+                   "Expecting -args 'cec_bus=X cec_address=0xAA hdmi_bus=Y hdmi_address=0xBB'\n");
+               log_warn(&log,
+                   "Example -args 'cec_bus=1 cec_address=0x34 hdmi_bus=1 hdmi_address=0x70'\n");
+               return EXIT_FAILURE;
+       }
+
+       sef_local_startup();
+
+       log_debug(&log, "Startup Complete\n");
+       blockdriver_task(&tda19988_tab);
+       log_debug(&log, "Shutting down\n");
+
+       return OK;
+}
index 209a45435d3f121cb009645c4f6716cab4237fea..9f1857c1c88db50b29f4d2b3ad4d1ac4d69df85d 100644 (file)
@@ -568,7 +568,7 @@ service fb
                PRIVCTL         #  4
         ;
        ipc
-                SYSTEM pm rs ds vm vfs
+                SYSTEM pm rs ds vm vfs tda19988
        ;
 };
 
@@ -613,6 +613,11 @@ service cat24c256
        ipc SYSTEM RS DS i2c;
 };
 
+service tda19988
+{
+       ipc SYSTEM RS DS i2c;
+};
+
 service vbox
 {
        system
index ab8595c8f6447c10f63459cbfb1747e194eb1437..999d4aac2978728070aad6514dbec6205accd9a7 100644 (file)
@@ -210,6 +210,10 @@ start)
                        test -e /dev/eepromb1s50 || (cd /dev && MAKEDEV eepromb1s50)
                        up cat24c256 -dev /dev/eepromb1s50 \
                                -label cat24c256.1.50 -args 'bus=1 address=0x50'
+
+                       # Start TDA19988 driver for EDID reading.
+                       up tda19988 -label tda19988.1.3470 -args \
+                               'cec_bus=1 cec_address=0x34 hdmi_bus=1 hdmi_address=0x70'
                        ;;
                UNKNOWN)
                        echo "Unable to detect board -- assuming BeagleBoard-xM"