Index: arm/conf/BEAGLEBONE =================================================================== --- arm/conf/BEAGLEBONE (revision 262679) +++ arm/conf/BEAGLEBONE (working copy) @@ -25,6 +25,7 @@ include "../ti/am335x/std.beaglebone" makeoptions WITHOUT_MODULES="ahc" +makeoptions MODULES_OVERRIDE="" options HZ=100 options SCHED_4BSD #4BSD scheduler @@ -57,10 +58,10 @@ #options VERBOSE_SYSINIT #Enable verbose sysinit messages options KDB options DDB #Enable the kernel debugger -options INVARIANTS #Enable calls of extra sanity checking -options INVARIANT_SUPPORT #Extra sanity checks of internal structures, required by INVARIANTS -options WITNESS #Enable checks to detect deadlocks and cycles -options WITNESS_SKIPSPIN #Don't run witness on spinlocks for speed +# options INVARIANTS #Enable calls of extra sanity checking +# options INVARIANT_SUPPORT #Extra sanity checks of internal structures, required by INVARIANTS +# options WITNESS #Enable checks to detect deadlocks and cycles +# options WITNESS_SKIPSPIN #Don't run witness on spinlocks for speed #options DIAGNOSTIC # NFS support @@ -69,12 +70,12 @@ options NFSLOCKD # Uncomment this for NFS root -#options NFS_ROOT #NFS usable as /, requires NFSCL -#options BOOTP_NFSROOT -#options BOOTP_COMPAT -#options BOOTP -#options BOOTP_NFSV3 -#options BOOTP_WIRED_TO=cpsw0 +options NFS_ROOT #NFS usable as /, requires NFSCL +options BOOTP_NFSROOT +options BOOTP_COMPAT +options BOOTP +options BOOTP_NFSV3 +options BOOTP_WIRED_TO=cpsw0 # MMC/SD/SDIO card slot support @@ -130,7 +131,18 @@ device usb_template # Control of the gadget device usfs +device tda19988 + # Flattened Device Tree options FDT options FDT_DTB_STATIC -makeoptions FDT_DTS_FILE=beaglebone.dts +makeoptions FDT_DTS_FILE=beaglebone-black.dts + +# Comment following lines for boot console on serial port +device sc +device kbdmux +options SC_DFLT_FONT # compile font in +makeoptions SC_DFLT_FONT=cp437 +device ukbd + + Index: arm/ti/ti_i2c.c =================================================================== --- arm/ti/ti_i2c.c (revision 262679) +++ arm/ti/ti_i2c.c (working copy) @@ -89,7 +89,13 @@ struct mtx sc_mtx; - volatile uint16_t sc_stat_flags; /* contains the status flags last IRQ */ + // volatile uint16_t sc_stat_flags; /* contains the status flags last IRQ */ + int sc_waiting_for_bus; + uint8_t *sc_buffer; + uint32_t sc_buffer_direction; + uint32_t sc_buffer_len; + uint32_t sc_buffer_pos; + int sc_error; uint16_t sc_i2c_addr; uint16_t sc_rev; @@ -326,6 +332,115 @@ return 0; } +static uint16_t xstatus = 0; + +static int +ti_i2c_transfer_intr(struct ti_i2c_softc* sc, uint16_t status) +{ + uint32_t amount = 0; + uint32_t i; + int done; + done = 0; + + /* check for the error conditions */ + if (status & I2C_STAT_NACK) { + /* no ACK from slave */ + ti_i2c_dbg(sc, "NACK\n"); + sc->sc_error = ENXIO; + xstatus &= ~(I2C_STAT_NACK); + done = 1; + } + else if (status & I2C_STAT_AL) { + /* arbitration lost */ + ti_i2c_dbg(sc, "Arbitration lost\n"); + sc->sc_error = ENXIO; + xstatus &= ~(I2C_STAT_AL); + done = 1; + } + /* check if we have finished */ + else { + if (status & I2C_STAT_ARDY) { + /* register access ready - transaction complete basically */ + ti_i2c_dbg(sc, "ARDY transaction complete\n"); + sc->sc_error = 0; + xstatus &= ~(I2C_STAT_ARDY); + done = 1; + } + if (sc->sc_buffer_direction & IIC_M_RD) { + /* read some data */ + if (status & I2C_STAT_RDR) { + /* Receive draining interrupt - last data received */ + ti_i2c_dbg(sc, "Receive draining interrupt\n"); + + /* get the number of bytes in the FIFO */ + amount = ti_i2c_read_reg(sc, I2C_REG_BUFSTAT); + amount >>= 8; + amount &= 0x3f; + xstatus &= ~(I2C_STAT_RDR); + } + else if (status & I2C_STAT_RRDY) { + /* Receive data ready interrupt - enough data received */ + ti_i2c_dbg(sc, "Receive data ready interrupt\n"); + xstatus &= ~(I2C_STAT_RRDY); + + /* get the number of bytes in the FIFO */ + amount = ti_i2c_read_reg(sc, I2C_REG_BUF); + amount >>= 8; + amount &= 0x3f; + amount += 1; + } + + /* sanity check we haven't overwritten the array */ + if ((sc->sc_buffer_pos + amount) > sc->sc_buffer_len) { + ti_i2c_dbg(sc, "too many bytes to read (%d + %d, %d)\n", sc->sc_buffer_pos, amount, sc->sc_buffer_len); + amount = (sc->sc_buffer_len - sc->sc_buffer_pos); + } + + /* read the bytes from the fifo */ + for (i = 0; i < amount; i++) { + sc->sc_buffer[sc->sc_buffer_pos++] = + (uint8_t)(ti_i2c_read_reg(sc, I2C_REG_DATA) & 0xff); + } + } + else { + /* write some data */ + if (status & I2C_STAT_XDR) { + /* Receive draining interrupt - last data received */ + ti_i2c_dbg(sc, "Transmit draining interrupt\n"); + + /* get the number of bytes in the FIFO */ + amount = ti_i2c_read_reg(sc, I2C_REG_BUFSTAT); + xstatus &= ~(I2C_STAT_XDR); + amount &= 0x3f; + } + else if (status & I2C_STAT_XRDY) { + /* Receive data ready interrupt - enough data received */ + ti_i2c_dbg(sc, "Transmit data ready interrupt\n"); + + /* get the number of bytes in the FIFO */ + amount = ti_i2c_read_reg(sc, I2C_REG_BUF); + amount &= 0x3f; + xstatus &= ~(I2C_STAT_XRDY); + amount += 1; + } + + /* sanity check we haven't overwritten the array */ + if ((sc->sc_buffer_pos + amount) > sc->sc_buffer_len) { + ti_i2c_dbg(sc, "too many bytes to write\n"); + amount = (sc->sc_buffer_len - sc->sc_buffer_pos); + } + + + /* write the bytes from the fifo */ + for (i = 0; i < amount; i++) + ti_i2c_write_reg(sc, I2C_REG_DATA, + sc->sc_buffer[sc->sc_buffer_pos++]); + } + } + + return (done); +} + /** * ti_i2c_intr - interrupt handler for the I2C module * @dev: i2c device handle @@ -343,95 +458,66 @@ { struct ti_i2c_softc *sc = (struct ti_i2c_softc*) arg; uint16_t status; + uint16_t events; + int done; status = ti_i2c_read_reg(sc, I2C_REG_STAT); - if (status == 0) + xstatus = status; + // printf("status == %08x\n", status); + if (status == 0) { + printf("STRAY!\n"); return; + } TI_I2C_LOCK(sc); - /* save the flags */ - sc->sc_stat_flags |= status; + events = ti_i2c_read_2(sc, I2C_REG_IRQENABLE_SET); + ti_i2c_write_2(sc, I2C_REG_IRQENABLE_CLR, 0xffff); - /* clear the status flags */ - ti_i2c_write_reg(sc, I2C_REG_STAT, status); + done = 0; - /* wakeup the process the started the transaction */ - wakeup(sc); + if (sc->sc_waiting_for_bus) { + if (status & I2C_IE_BF) { + /* bus is free */ + ti_i2c_dbg(sc, "Bus is free (%04x/%04x)\n", status, events); + done = 1; + sc->sc_waiting_for_bus = 0; + xstatus &= ~(I2C_IE_BF); + } + } + else { + if (sc->sc_buffer != NULL) + done = ti_i2c_transfer_intr(sc, status); + else { + ti_i2c_dbg(sc, "Transfer interrupt without buffer\n"); + sc->sc_error = EINVAL; + done = 1; + } + } - TI_I2C_UNLOCK(sc); + #if 0 + if (xstatus & I2C_STAT_ROVR) + printf("overrrun/ %04x/%04x/%d\n", xstatus, status, sc->sc_buffer_pos); - return; -} + xstatus &= events; + if (xstatus) + printf("Unhandled: %04x/%04x\n", xstatus, events); + #endif -/** - * ti_i2c_wait - waits for the specific event to occur - * @sc: i2c driver context - * @flags: the event(s) to wait on, this is a bitmask of the I2C_STAT_??? flags - * @statp: if not null will contain the status flags upon return - * @timo: the number of ticks to wait - * - * - * - * LOCKING: - * The driver context must be locked before calling this function. Internally - * the function sleeps, releasing the lock as it does so, however the lock is - * always retaken before this function returns. - * - * RETURNS: - * 0 if the event(s) were tripped within timeout period - * EBUSY if timedout waiting for the events - * ENXIO if a NACK event was received - */ -static int -ti_i2c_wait(struct ti_i2c_softc *sc, uint16_t flags, uint16_t *statp, int timo) -{ - int waittime = timo; - int start_ticks = ticks; - int rc; + /* clear the status flags */ + ti_i2c_write_reg(sc, I2C_REG_STAT, status); - TI_I2C_ASSERT_LOCKED(sc); - /* check if the condition has already occured, the interrupt routine will - * clear the status flags. - */ - if ((sc->sc_stat_flags & flags) == 0) { - /* condition(s) haven't occured so sleep on the IRQ */ - while (waittime > 0) { + if (done) { + /* wakeup the process the started the transaction */ + wakeup(sc); + } else + ti_i2c_write_2(sc, I2C_REG_IRQENABLE_SET, events); - rc = mtx_sleep(sc, &sc->sc_mtx, 0, "I2Cwait", waittime); - if (rc == EWOULDBLOCK) { - /* timed-out, simply break out of the loop */ - break; - } else { - /* IRQ has been tripped, but need to sanity check we have the - * right events in the status flag. - */ - if ((sc->sc_stat_flags & flags) != 0) - break; + TI_I2C_UNLOCK(sc); - /* event hasn't been tripped so wait some more */ - waittime -= (ticks - start_ticks); - start_ticks = ticks; - } - } - } - - /* copy the actual status bits */ - if (statp != NULL) - *statp = sc->sc_stat_flags; - - /* return the status found */ - if ((sc->sc_stat_flags & flags) != 0) - rc = 0; - else - rc = EBUSY; - - /* clear the flags set by the interrupt handler */ - sc->sc_stat_flags = 0; - - return (rc); + return; } /** @@ -462,7 +548,16 @@ ti_i2c_set_intr_enable(sc, I2C_IE_BF); /* wait for the bus free interrupt to be tripped */ - return ti_i2c_wait(sc, I2C_STAT_BF, NULL, timo); + sc->sc_waiting_for_bus = 1; + mtx_sleep(sc, &sc->sc_mtx, 0, "I2Cbfwait", timo); + + /* disable bus free interrupt */ + ti_i2c_set_intr_enable(sc, 0x0000); + + if ((ti_i2c_read_reg(sc, I2C_REG_STAT) & I2C_STAT_BB) == 0) + return 0; + else + return (EBUSY); } /** @@ -487,10 +582,6 @@ int err = 0; uint16_t con_reg; uint16_t events; - uint16_t status; - uint32_t amount = 0; - uint32_t sofar = 0; - uint32_t i; /* wait for the bus to become free */ err = ti_i2c_wait_for_free_bus(sc, timo); @@ -509,6 +600,11 @@ /* enable interrupts for the events we want */ ti_i2c_set_intr_enable(sc, events); + sc->sc_buffer = buf; + sc->sc_buffer_len = len; + sc->sc_buffer_pos = 0; + sc->sc_error = 0; + /* write the number of bytes to read */ ti_i2c_write_reg(sc, I2C_REG_CNT, len); @@ -520,75 +616,15 @@ con_reg |= I2C_CON_MST | I2C_CON_STT | I2C_CON_STP; ti_i2c_write_reg(sc, I2C_REG_CON, con_reg); - /* reading loop */ - while (1) { + /* wait for an event */ + err = mtx_sleep(sc, &sc->sc_mtx, 0, "I2Crdwait", timo); + if (err == 0) + err = sc->sc_error; - /* wait for an event */ - err = ti_i2c_wait(sc, events, &status, timo); - if (err != 0) { - break; - } - - /* check for the error conditions */ - if (status & I2C_STAT_NACK) { - /* no ACK from slave */ - ti_i2c_dbg(sc, "NACK\n"); - err = ENXIO; - break; - } - if (status & I2C_STAT_AL) { - /* arbitration lost */ - ti_i2c_dbg(sc, "Arbitration lost\n"); - err = ENXIO; - break; - } - - /* check if we have finished */ - if (status & I2C_STAT_ARDY) { - /* register access ready - transaction complete basically */ - ti_i2c_dbg(sc, "ARDY transaction complete\n"); - err = 0; - break; - } - - /* read some data */ - if (status & I2C_STAT_RDR) { - /* Receive draining interrupt - last data received */ - ti_i2c_dbg(sc, "Receive draining interrupt\n"); - - /* get the number of bytes in the FIFO */ - amount = ti_i2c_read_reg(sc, I2C_REG_BUFSTAT); - amount >>= 8; - amount &= 0x3f; - } - else if (status & I2C_STAT_RRDY) { - /* Receive data ready interrupt - enough data received */ - ti_i2c_dbg(sc, "Receive data ready interrupt\n"); - - /* get the number of bytes in the FIFO */ - amount = ti_i2c_read_reg(sc, I2C_REG_BUF); - amount >>= 8; - amount &= 0x3f; - amount += 1; - } - - /* sanity check we haven't overwritten the array */ - if ((sofar + amount) > len) { - ti_i2c_dbg(sc, "to many bytes to read\n"); - amount = (len - sofar); - } - - /* read the bytes from the fifo */ - for (i = 0; i < amount; i++) { - buf[sofar++] = (uint8_t)(ti_i2c_read_reg(sc, I2C_REG_DATA) & 0xff); - } - - /* attempt to clear the receive ready bits */ - ti_i2c_write_reg(sc, I2C_REG_STAT, I2C_STAT_RDR | I2C_STAT_RRDY); - } - /* reset the registers regardless if there was an error or not */ ti_i2c_set_intr_enable(sc, 0x0000); + sc->sc_buffer = NULL; + ti_i2c_write_reg(sc, I2C_REG_CON, I2C_CON_I2C_EN | I2C_CON_MST | I2C_CON_STP); return (err); @@ -610,16 +646,12 @@ * EINVAL if invalid message is passed as an arg */ static int -ti_i2c_write_bytes(struct ti_i2c_softc *sc, const uint8_t *buf, uint16_t len) +ti_i2c_write_bytes(struct ti_i2c_softc *sc, uint8_t *buf, uint16_t len) { int timo = (hz / 4); int err = 0; uint16_t con_reg; uint16_t events; - uint16_t status; - uint32_t amount = 0; - uint32_t sofar = 0; - uint32_t i; /* wait for the bus to become free */ err = ti_i2c_wait_for_free_bus(sc, timo); @@ -636,6 +668,11 @@ /* enable interrupts for the events we want*/ ti_i2c_set_intr_enable(sc, events); + sc->sc_buffer = buf; + sc->sc_buffer_len = len; + sc->sc_buffer_pos = 0; + sc->sc_error = 0; + /* write the number of bytes to write */ ti_i2c_write_reg(sc, I2C_REG_CNT, len); @@ -646,73 +683,14 @@ con_reg |= I2C_CON_TRX | I2C_CON_MST | I2C_CON_STT | I2C_CON_STP; ti_i2c_write_reg(sc, I2C_REG_CON, con_reg); - /* writing loop */ - while (1) { + /* wait for an event */ + err = mtx_sleep(sc, &sc->sc_mtx, 0, "I2Cwrwait", timo); + if (err == 0) + err = sc->sc_error; - /* wait for an event */ - err = ti_i2c_wait(sc, events, &status, timo); - if (err != 0) { - break; - } - - /* check for the error conditions */ - if (status & I2C_STAT_NACK) { - /* no ACK from slave */ - ti_i2c_dbg(sc, "NACK\n"); - err = ENXIO; - break; - } - if (status & I2C_STAT_AL) { - /* arbitration lost */ - ti_i2c_dbg(sc, "Arbitration lost\n"); - err = ENXIO; - break; - } - - /* check if we have finished */ - if (status & I2C_STAT_ARDY) { - /* register access ready - transaction complete basically */ - ti_i2c_dbg(sc, "ARDY transaction complete\n"); - err = 0; - break; - } - - /* read some data */ - if (status & I2C_STAT_XDR) { - /* Receive draining interrupt - last data received */ - ti_i2c_dbg(sc, "Transmit draining interrupt\n"); - - /* get the number of bytes in the FIFO */ - amount = ti_i2c_read_reg(sc, I2C_REG_BUFSTAT); - amount &= 0x3f; - } - else if (status & I2C_STAT_XRDY) { - /* Receive data ready interrupt - enough data received */ - ti_i2c_dbg(sc, "Transmit data ready interrupt\n"); - - /* get the number of bytes in the FIFO */ - amount = ti_i2c_read_reg(sc, I2C_REG_BUF); - amount &= 0x3f; - amount += 1; - } - - /* sanity check we haven't overwritten the array */ - if ((sofar + amount) > len) { - ti_i2c_dbg(sc, "to many bytes to write\n"); - amount = (len - sofar); - } - - /* write the bytes from the fifo */ - for (i = 0; i < amount; i++) { - ti_i2c_write_reg(sc, I2C_REG_DATA, buf[sofar++]); - } - - /* attempt to clear the transmit ready bits */ - ti_i2c_write_reg(sc, I2C_REG_STAT, I2C_STAT_XDR | I2C_STAT_XRDY); - } - /* reset the registers regardless if there was an error or not */ ti_i2c_set_intr_enable(sc, 0x0000); + sc->sc_buffer = NULL; ti_i2c_write_reg(sc, I2C_REG_CON, I2C_CON_I2C_EN | I2C_CON_MST | I2C_CON_STP); return (err); @@ -758,12 +736,11 @@ ti_i2c_write_reg(sc, I2C_REG_SA, msgs[i].slave); /* perform the read or write */ - if (msgs[i].flags & IIC_M_RD) { + sc->sc_buffer_direction = msgs[i].flags; + if (msgs[i].flags & IIC_M_RD) err = ti_i2c_read_bytes(sc, buf, len); - } else { + else err = ti_i2c_write_bytes(sc, buf, len); - } - } out: Index: boot/fdt/dts/arm/beaglebone-black.dts =================================================================== --- boot/fdt/dts/arm/beaglebone-black.dts (revision 262682) +++ boot/fdt/dts/arm/beaglebone-black.dts (working copy) @@ -110,18 +110,22 @@ "GPMC_BEn1", "gpio1_28", "input_pulldown", "GPMC_CSn0", "gpio1_29", "input_pulldown", "GPMC_CLK", "gpio2_1", "input_pulldown", - "LCD_DATA0", "gpio2_6", "input_pulldown", - "LCD_DATA1", "gpio2_7", "input_pulldown", - "LCD_DATA2", "gpio2_8", "input_pulldown", - "LCD_DATA3", "gpio2_9", "input_pulldown", - "LCD_DATA4", "gpio2_10", "input_pulldown", - "LCD_DATA5", "gpio2_11", "input_pulldown", - "LCD_DATA6", "gpio2_12", "input_pulldown", - "LCD_DATA7", "gpio2_13", "input_pulldown", - "LCD_VSYNC", "gpio2_22", "input_pulldown", - "LCD_HSYNC", "gpio2_23", "input_pulldown", - "LCD_PCLK", "gpio2_24", "input_pulldown", - "LCD_AC_BIAS_EN", "gpio2_25", "input_pulldown", + + /* HDMI */ + "LCD_DATA0", "gpio2_6", "output", + "LCD_DATA1", "gpio2_7", "output", + "LCD_DATA2", "gpio2_8", "output", + "LCD_DATA3", "gpio2_9", "output", + "LCD_DATA4", "gpio2_10", "output", + "LCD_DATA5", "gpio2_11", "output", + "LCD_DATA6", "gpio2_12", "output", + "LCD_DATA7", "gpio2_13", "output", + "LCD_VSYNC", "gpio2_22", "output", + "LCD_HSYNC", "gpio2_23", "output", + "LCD_PCLK", "gpio2_24", "output", + "LCD_AC_BIAS_EN", "gpio2_25", "output", + "XDMA_EVENT_INTR0", "clkout1", "output", + "MCASP0_FSR", "gpio3_19", "input_pulldown", "MCASP0_AHCLKX", "gpio3_21", "input_pulldown", /* TIMERs */ @@ -139,9 +143,40 @@ "GPMC_AD9", "ehrpwm2B", "output"; }; + lcd@4830e000 { + panel_name = "HDMI_1280x720at60Hz"; + panel_width = <1280>; + panel_height = <720>; + panel_hfp = <109>; + panel_hbp = <219>; + panel_hsw = <39>; + panel_vfp = <5>; + panel_vbp = <19>; + panel_vsw = <5>; + panel_pxl_clk = <74250>; + panel_invert_pxl_clk = <0>; + panel_type = <1>; /* Active or passive, compatibility */ + panel_max_bpp = <32>; /* compatibility */ + panel_min_bpp = <32>; /* compatibility */ + panel_shade = <1>; /* compatibility */ + ac_bias = <255>; + ac_bias_intrpt = <0>; + dma_burst_sz = <16>; + bpp = <32>; + fdd = <128>; + tft_alt_mode = <0>; /* compatiblity */ + stn_565_mode = <0>; /* compatibility */ + mono_8bit_mode = <0>; /* compatibilty */ + invert_line_clock = <1>; + invert_frm_clock = <1>; + sync_edge = <0>; + sync_ctrl = <1>; + raster_order = <0>; /* compatibity */ + }; + mmchs1@481D8000 { bus-width = <8>; - status = "okay"; + status = "disabled"; }; @@ -150,6 +185,11 @@ compatible = "ti,am335x-pmic"; reg = <0x24>; }; + + tda19988@50 { + compatible = "nxp,tda19988"; + reg = <0x70>; + }; }; }; Index: conf/files =================================================================== --- conf/files (revision 262679) +++ conf/files (working copy) @@ -1432,6 +1432,7 @@ dev/iicbus/ds133x.c optional ds133x dev/iicbus/ds1374.c optional ds1374 dev/iicbus/ds1672.c optional ds1672 +dev/iicbus/tda19988.c optional tda19988 dev/iicbus/icee.c optional icee dev/iicbus/if_ic.c optional ic dev/iicbus/iic.c optional iic Index: dev/iicbus/tda19988.c =================================================================== --- dev/iicbus/tda19988.c (revision 0) +++ dev/iicbus/tda19988.c (working copy) @@ -0,0 +1,860 @@ +/*- + * Copyright (c) 2013 Oleksandr Tymoshenko + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +#include +__FBSDID("$FreeBSD: head/sys/arm/ti/am335x/tda19988.c 239281 2012-08-15 06:31:32Z gonzo $"); +/* +* NXP TDA19988 HDMI encoder +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "iicbus_if.h" + +#define MAX_IIC_DATA_SIZE 2 + +#define MKREG(page, addr) (((page) << 8) | (addr)) + +#define REGPAGE(reg) (((reg) >> 8) & 0xff) +#define REGADDR(reg) ((reg) & 0xff) + +#define TDA_VERSION MKREG(0, 0x00) +#define TDA_CTRL MKREG(0, 0x01) +#define CTRL_SOFTRESET (1 << 0) +#define TDA_VERSION_MSB MKREG(0, 0x02) +#define TDA_SOFTRESET MKREG(0, 0x0a) +#define SOFTRESET_I2C (1 << 1) +#define SOFTRESET_AUDIO (1 << 0) +#define TDA_DDC_CTRL MKREG(0, 0x0b) +#define DDC_ENABLE 0 +#define TDA_CCLK MKREG(0, 0x0c) +#define CCLK_ENABLE 1 +#define TDA_INT_FLAGS2 MKREG(0, 0x11) +#define INT_FLAGS2_EDID_BLK_RD (1 << 1) + +#define TDA_EDID_DATA0 MKREG(0x09, 0x00) +#define TDA_EDID_CTRL MKREG(0x09, 0xfa) +#define TDA_DDC_ADDR MKREG(0x09, 0xfb) +#define TDA_DDC_OFFS MKREG(0x09, 0xfc) +#define TDA_DDC_SEGM_ADDR MKREG(0x09, 0xfd) +#define TDA_DDC_SEGM MKREG(0x09, 0xfe) + +#define TDA_TX3 MKREG(0x12, 0x9a) +#define TDA_TX4 MKREG(0x12, 0x9b) +#define TX4_PD_RAM (1 << 1) + +#define TDA_CURPAGE_ADDR 0xff + +#define TDA_CEC_ENAMODS 0xff +#define ENAMODS_RXSENS (1 << 2) +#define ENAMODS_HDMI (1 << 1) + +#define EDID_LENGTH 0x80 +#define MAX_READ_ATTEMPTS 100 + + + +/************ LINUX DEFINED **********************/ +/* Page 00h: General Control */ +#define REG_VERSION_LSB MKREG(0x00, 0x00) /* read */ +#define REG_MAIN_CNTRL0 MKREG(0x00, 0x01) /* read/write */ +# define MAIN_CNTRL0_SR (1 << 0) +# define MAIN_CNTRL0_DECS (1 << 1) +# define MAIN_CNTRL0_DEHS (1 << 2) +# define MAIN_CNTRL0_CECS (1 << 3) +# define MAIN_CNTRL0_CEHS (1 << 4) +# define MAIN_CNTRL0_SCALER (1 << 7) +#define REG_VERSION_MSB MKREG(0x00, 0x02) /* read */ +#define REG_SOFTRESET MKREG(0x00, 0x0a) /* write */ +# define SOFTRESET_AUDIO (1 << 0) +# define SOFTRESET_I2C_MASTER (1 << 1) +#define REG_DDC_DISABLE MKREG(0x00, 0x0b) /* read/write */ +#define REG_CCLK_ON MKREG(0x00, 0x0c) /* read/write */ +#define REG_I2C_MASTER MKREG(0x00, 0x0d) /* read/write */ +# define I2C_MASTER_DIS_MM (1 << 0) +# define I2C_MASTER_DIS_FILT (1 << 1) +# define I2C_MASTER_APP_STRT_LAT (1 << 2) +#define REG_INT_FLAGS_0 MKREG(0x00, 0x0f) /* read/write */ +#define REG_INT_FLAGS_1 MKREG(0x00, 0x10) /* read/write */ +#define REG_INT_FLAGS_2 MKREG(0x00, 0x11) /* read/write */ +# define INT_FLAGS_2_EDID_BLK_RD (1 << 1) +#define REG_ENA_VP_0 MKREG(0x00, 0x18) /* read/write */ +#define REG_ENA_VP_1 MKREG(0x00, 0x19) /* read/write */ +#define REG_ENA_VP_2 MKREG(0x00, 0x1a) /* read/write */ +#define REG_ENA_AP MKREG(0x00, 0x1e) /* read/write */ +#define REG_VIP_CNTRL_0 MKREG(0x00, 0x20) /* write */ +# define VIP_CNTRL_0_MIRR_A (1 << 7) +# define VIP_CNTRL_0_SWAP_A(x) (((x) & 7) << 4) +# define VIP_CNTRL_0_MIRR_B (1 << 3) +# define VIP_CNTRL_0_SWAP_B(x) (((x) & 7) << 0) +#define REG_VIP_CNTRL_1 MKREG(0x00, 0x21) /* write */ +# define VIP_CNTRL_1_MIRR_C (1 << 7) +# define VIP_CNTRL_1_SWAP_C(x) (((x) & 7) << 4) +# define VIP_CNTRL_1_MIRR_D (1 << 3) +# define VIP_CNTRL_1_SWAP_D(x) (((x) & 7) << 0) +#define REG_VIP_CNTRL_2 MKREG(0x00, 0x22) /* write */ +# define VIP_CNTRL_2_MIRR_E (1 << 7) +# define VIP_CNTRL_2_SWAP_E(x) (((x) & 7) << 4) +# define VIP_CNTRL_2_MIRR_F (1 << 3) +# define VIP_CNTRL_2_SWAP_F(x) (((x) & 7) << 0) +#define REG_VIP_CNTRL_3 MKREG(0x00, 0x23) /* write */ +# define VIP_CNTRL_3_X_TGL (1 << 0) +# define VIP_CNTRL_3_H_TGL (1 << 1) +# define VIP_CNTRL_3_V_TGL (1 << 2) +# define VIP_CNTRL_3_EMB (1 << 3) +# define VIP_CNTRL_3_SYNC_DE (1 << 4) +# define VIP_CNTRL_3_SYNC_HS (1 << 5) +# define VIP_CNTRL_3_DE_INT (1 << 6) +# define VIP_CNTRL_3_EDGE (1 << 7) +#define REG_VIP_CNTRL_4 MKREG(0x00, 0x24) /* write */ +# define VIP_CNTRL_4_BLC(x) (((x) & 3) << 0) +# define VIP_CNTRL_4_BLANKIT(x) (((x) & 3) << 2) +# define VIP_CNTRL_4_CCIR656 (1 << 4) +# define VIP_CNTRL_4_656_ALT (1 << 5) +# define VIP_CNTRL_4_TST_656 (1 << 6) +# define VIP_CNTRL_4_TST_PAT (1 << 7) +#define REG_VIP_CNTRL_5 MKREG(0x00, 0x25) /* write */ +# define VIP_CNTRL_5_CKCASE (1 << 0) +# define VIP_CNTRL_5_SP_CNT(x) (((x) & 3) << 1) +#define REG_MUX_AP MKREG(0x00, 0x26) +#define MUX_AP_SELECT_I2S (0x64) +#define REG_MAT_CONTRL MKREG(0x00, 0x80) /* write */ +# define MAT_CONTRL_MAT_SC(x) (((x) & 3) << 0) +# define MAT_CONTRL_MAT_BP (1 << 2) +#define REG_VIDFORMAT MKREG(0x00, 0xa0) /* write */ +#define REG_REFPIX_MSB MKREG(0x00, 0xa1) /* write */ +#define REG_REFPIX_LSB MKREG(0x00, 0xa2) /* write */ +#define REG_REFLINE_MSB MKREG(0x00, 0xa3) /* write */ +#define REG_REFLINE_LSB MKREG(0x00, 0xa4) /* write */ +#define REG_NPIX_MSB MKREG(0x00, 0xa5) /* write */ +#define REG_NPIX_LSB MKREG(0x00, 0xa6) /* write */ +#define REG_NLINE_MSB MKREG(0x00, 0xa7) /* write */ +#define REG_NLINE_LSB MKREG(0x00, 0xa8) /* write */ +#define REG_VS_LINE_STRT_1_MSB MKREG(0x00, 0xa9) /* write */ +#define REG_VS_LINE_STRT_1_LSB MKREG(0x00, 0xaa) /* write */ +#define REG_VS_PIX_STRT_1_MSB MKREG(0x00, 0xab) /* write */ +#define REG_VS_PIX_STRT_1_LSB MKREG(0x00, 0xac) /* write */ +#define REG_VS_LINE_END_1_MSB MKREG(0x00, 0xad) /* write */ +#define REG_VS_LINE_END_1_LSB MKREG(0x00, 0xae) /* write */ +#define REG_VS_PIX_END_1_MSB MKREG(0x00, 0xaf) /* write */ +#define REG_VS_PIX_END_1_LSB MKREG(0x00, 0xb0) /* write */ +#define REG_VS_PIX_STRT_2_MSB MKREG(0x00, 0xb3) /* write */ +#define REG_VS_PIX_STRT_2_LSB MKREG(0x00, 0xb4) /* write */ +#define REG_VS_PIX_END_2_MSB MKREG(0x00, 0xb7) /* write */ +#define REG_VS_PIX_END_2_LSB MKREG(0x00, 0xb8) /* write */ +#define REG_HS_PIX_START_MSB MKREG(0x00, 0xb9) /* write */ +#define REG_HS_PIX_START_LSB MKREG(0x00, 0xba) /* write */ +#define REG_HS_PIX_STOP_MSB MKREG(0x00, 0xbb) /* write */ +#define REG_HS_PIX_STOP_LSB MKREG(0x00, 0xbc) /* write */ +#define REG_VWIN_START_1_MSB MKREG(0x00, 0xbd) /* write */ +#define REG_VWIN_START_1_LSB MKREG(0x00, 0xbe) /* write */ +#define REG_VWIN_END_1_MSB MKREG(0x00, 0xbf) /* write */ +#define REG_VWIN_END_1_LSB MKREG(0x00, 0xc0) /* write */ +#define REG_DE_START_MSB MKREG(0x00, 0xc5) /* write */ +#define REG_DE_START_LSB MKREG(0x00, 0xc6) /* write */ +#define REG_DE_STOP_MSB MKREG(0x00, 0xc7) /* write */ +#define REG_DE_STOP_LSB MKREG(0x00, 0xc8) /* write */ +#define REG_TBG_CNTRL_0 MKREG(0x00, 0xca) /* write */ +# define TBG_CNTRL_0_FRAME_DIS (1 << 5) +# define TBG_CNTRL_0_SYNC_MTHD (1 << 6) +# define TBG_CNTRL_0_SYNC_ONCE (1 << 7) +#define REG_TBG_CNTRL_1 MKREG(0x00, 0xcb) /* write */ +# define TBG_CNTRL_1_VH_TGL_0 (1 << 0) +# define TBG_CNTRL_1_VH_TGL_1 (1 << 1) +# define TBG_CNTRL_1_VH_TGL_2 (1 << 2) +# define TBG_CNTRL_1_VHX_EXT_DE (1 << 3) +# define TBG_CNTRL_1_VHX_EXT_HS (1 << 4) +# define TBG_CNTRL_1_VHX_EXT_VS (1 << 5) +# define TBG_CNTRL_1_DWIN_DIS (1 << 6) +#define REG_ENABLE_SPACE MKREG(0x00, 0xd6) /* write */ +#define REG_HVF_CNTRL_0 MKREG(0x00, 0xe4) /* write */ +# define HVF_CNTRL_0_SM (1 << 7) +# define HVF_CNTRL_0_RWB (1 << 6) +# define HVF_CNTRL_0_PREFIL(x) (((x) & 3) << 2) +# define HVF_CNTRL_0_INTPOL(x) (((x) & 3) << 0) +#define REG_HVF_CNTRL_1 MKREG(0x00, 0xe5) /* write */ +# define HVF_CNTRL_1_FOR (1 << 0) +# define HVF_CNTRL_1_YUVBLK (1 << 1) +# define HVF_CNTRL_1_VQR(x) (((x) & 3) << 2) +# define HVF_CNTRL_1_PAD(x) (((x) & 3) << 4) +# define HVF_CNTRL_1_SEMI_PLANAR (1 << 6) +#define REG_RPT_CNTRL MKREG(0x00, 0xf0) /* write */ +#define REG_I2S_FORMAT MKREG(0x00, 0xfc) + +#define REG_AIP_CLKSEL MKREG(0x00, 0xfd) +# define SEL_AIP_I2S (1 << 3) /* I2S Clk */ + + +/* Page 02h: PLL settings */ +#define REG_PLL_SERIAL_1 MKREG(0x02, 0x00) /* read/write */ +# define PLL_SERIAL_1_SRL_FDN (1 << 0) +# define PLL_SERIAL_1_SRL_IZ(x) (((x) & 3) << 1) +# define PLL_SERIAL_1_SRL_MAN_IZ (1 << 6) +#define REG_PLL_SERIAL_2 MKREG(0x02, 0x01) /* read/write */ +# define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 3) << 0) +# define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4) +#define REG_PLL_SERIAL_3 MKREG(0x02, 0x02) /* read/write */ +# define PLL_SERIAL_3_SRL_CCIR (1 << 0) +# define PLL_SERIAL_3_SRL_DE (1 << 2) +# define PLL_SERIAL_3_SRL_PXIN_SEL (1 << 4) +#define REG_SERIALIZER MKREG(0x02, 0x03) /* read/write */ +#define REG_BUFFER_OUT MKREG(0x02, 0x04) /* read/write */ +#define REG_PLL_SCG1 MKREG(0x02, 0x05) /* read/write */ +#define REG_PLL_SCG2 MKREG(0x02, 0x06) /* read/write */ +#define REG_PLL_SCGN1 MKREG(0x02, 0x07) /* read/write */ +#define REG_PLL_SCGN2 MKREG(0x02, 0x08) /* read/write */ +#define REG_PLL_SCGR1 MKREG(0x02, 0x09) /* read/write */ +#define REG_PLL_SCGR2 MKREG(0x02, 0x0a) /* read/write */ +#define REG_AUDIO_DIV MKREG(0x02, 0x0e) /* read/write */ +#define REG_SEL_CLK MKREG(0x02, 0x11) /* read/write */ +# define SEL_CLK_SEL_CLK1 (1 << 0) +# define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1) +# define SEL_CLK_ENA_SC_CLK (1 << 3) +#define REG_ANA_GENERAL MKREG(0x02, 0x12) /* read/write */ + + +/* Page 09h: EDID Control */ +#define REG_EDID_DATA_0 MKREG(0x09, 0x00) /* read */ +/* next 127 successive registers are the EDID block */ +#define REG_EDID_CTRL MKREG(0x09, 0xfa) /* read/write */ +#define REG_DDC_ADDR MKREG(0x09, 0xfb) /* read/write */ +#define REG_DDC_OFFS MKREG(0x09, 0xfc) /* read/write */ +#define REG_DDC_SEGM_ADDR MKREG(0x09, 0xfd) /* read/write */ +#define REG_DDC_SEGM MKREG(0x09, 0xfe) /* read/write */ + + +/* Page 10h: information frames and packets */ + +#define REG_AVI_IF MKREG(0x10, 0x40) /* AVI Infoframe packet */ +#define REG_AUDIO_IF MKREG(0x10, 0x80) /* AVI Infoframe packet */ + + +/* Page 11h: audio settings and content info packets */ +#define REG_AIP_CNTRL_0 MKREG(0x11, 0x00) /* read/write */ +# define AIP_CNTRL_0_RST_FIFO (1 << 0) +# define AIP_CNTRL_0_SWAP (1 << 1) +# define AIP_CNTRL_0_LAYOUT (1 << 2) +# define AIP_CNTRL_0_ACR_MAN (1 << 5) +# define AIP_CNTRL_0_RST_CTS (1 << 6) +#define REG_ACR_CTS_0 MKREG(0x11, 0x05) +#define REG_ACR_CTS_1 MKREG(0x11, 0x06) +#define REG_ACR_CTS_2 MKREG(0x11, 0x07) +#define REG_ACR_N_0 MKREG(0x11, 0x08) +#define REG_ACR_N_1 MKREG(0x11, 0x09) +#define REG_ACR_N_2 MKREG(0x11, 0x0a) +#define REG_GC_AVMUTE MKREG(0x11, 0x0b) +# define GC_AVMUTE_CLRMUTE (1 << 0) +# define GC_AVMUTE_SETMUTE (1 << 1) +#define REG_CTS_N MKREG(0x11, 0x0c) + +#define REG_ENC_CNTRL MKREG(0x11, 0x0d) /* read/write */ +# define ENC_CNTRL_RST_ENC (1 << 0) +# define ENC_CNTRL_RST_SEL (1 << 1) +# define ENC_CNTRL_CTL_CODE(x) (((x) & 3) << 2) + + +#define REG_DIP_FLAGS MKREG(0x11, 0x0e) +# define DIP_FLAGS_ACR (1 << 0) +#define REG_DIP_IF_FLAGS MKREG(0x11, 0x0f) /* read/write */ +#define DIP_IF_FLAGS_IF1 (1 << 1) +#define DIP_IF_FLAGS_IF2 (1 << 2) +#define DIP_IF_FLAGS_IF3 (1 << 3) +#define DIP_IF_FLAGS_IF4 (1 << 4) +#define DIP_IF_FLAGS_IF5 (1 << 5) + + +/* Page 12h: HDCP and OTP */ +#define REG_TX3 MKREG(0x12, 0x9a) /* read/write */ +#define REG_TX33 MKREG(0x12, 0xb8) /* read/write */ +# define TX33_HDMI (1 << 1) + + +struct tda19988_softc { + device_t sc_dev; + uint32_t sc_addr; + uint32_t sc_cec_addr; + struct intr_config_hook enum_hook; + int sc_current_page; + char sc_edid[EDID_LENGTH]; +}; + +static int +tda19988_set_page(struct tda19988_softc *sc, uint8_t page) +{ + uint8_t addr = TDA_CURPAGE_ADDR; + uint8_t cmd[2]; + int result; + struct iic_msg msg[] = { + { sc->sc_addr, IIC_M_WR, 2, cmd }, + }; + + cmd[0] = addr; + cmd[1] = page; + + result = (iicbus_transfer(sc->sc_dev, msg, 1)); + if (result) + printf("tda19988_set_page failed: %d\n", result); + else + sc->sc_current_page = page; + + return (result); +} + +static int +tda19988_cec_read(struct tda19988_softc *sc, uint8_t addr, uint8_t *data) +{ + struct iic_msg msg[] = { + { sc->sc_cec_addr, IIC_M_WR, 1, &addr }, + { sc->sc_cec_addr, IIC_M_RD, 1, data }, + }; + + return (iicbus_transfer(sc->sc_dev, msg, 2)); +} + +static int +tda19988_cec_write(struct tda19988_softc *sc, uint8_t address, uint8_t data) +{ + uint8_t cmd[2]; + struct iic_msg msg[] = { + { sc->sc_cec_addr, IIC_M_WR, 2, cmd }, + }; + + cmd[0] = address; + cmd[1] = data; + + return (iicbus_transfer(sc->sc_dev, msg, 1)); +} + +static int +tda19988_block_read(struct tda19988_softc *sc, uint16_t addr, uint8_t *data, int len) +{ + uint8_t reg; + int result; + struct iic_msg msg[] = { + { sc->sc_addr, IIC_M_WR, 1, ® }, + { sc->sc_addr, IIC_M_RD, len, data }, + }; + + reg = REGADDR(addr); + + if (sc->sc_current_page != REGPAGE(addr)) + tda19988_set_page(sc, REGPAGE(addr)); + + result = (iicbus_transfer(sc->sc_dev, msg, 2)); + if (result) + device_printf(sc->sc_dev, "tda19988_block_read failed: %d\n", result); + return (result); +} + + +static int +tda19988_reg_read(struct tda19988_softc *sc, uint16_t addr, uint8_t *data) +{ + uint8_t reg; + int result; + struct iic_msg msg[] = { + { sc->sc_addr, IIC_M_WR, 1, ® }, + { sc->sc_addr, IIC_M_RD, 1, data }, + }; + + reg = REGADDR(addr); + + if (sc->sc_current_page != REGPAGE(addr)) + tda19988_set_page(sc, REGPAGE(addr)); + + result = (iicbus_transfer(sc->sc_dev, msg, 2)); + if (result) + device_printf(sc->sc_dev, "tda19988_reg_read failed: %d\n", result); + return (result); +} + +static int +tda19988_reg_write(struct tda19988_softc *sc, uint16_t address, uint8_t data) +{ + uint8_t cmd[2]; + int result; + struct iic_msg msg[] = { + { sc->sc_addr, IIC_M_WR, 2, cmd }, + }; + + cmd[0] = REGADDR(address); + cmd[1] = data; + + if (sc->sc_current_page != REGPAGE(address)) + tda19988_set_page(sc, REGPAGE(address)); + + result = iicbus_transfer(sc->sc_dev, msg, 1); + if (result) + device_printf(sc->sc_dev, "tda19988_reg_write failed: %d\n", result); + + return (result); +} + +static int +tda19988_reg_write2(struct tda19988_softc *sc, uint16_t address, uint16_t data) +{ + uint8_t cmd[3]; + int result; + struct iic_msg msg[] = { + { sc->sc_addr, IIC_M_WR, 3, cmd }, + }; + + cmd[0] = REGADDR(address); + cmd[1] = (data >> 8); + cmd[2] = (data & 0xff); + + if (sc->sc_current_page != REGPAGE(address)) + tda19988_set_page(sc, REGPAGE(address)); + + result = iicbus_transfer(sc->sc_dev, msg, 1); + if (result) + device_printf(sc->sc_dev, "tda19988_reg_write2 failed: %d\n", result); + + return (result); +} + + + +static void +tda19988_reg_set(struct tda19988_softc *sc, uint16_t addr, uint8_t flags) +{ + uint8_t data; + + tda19988_reg_read(sc, addr, &data); + data |= flags; + tda19988_reg_write(sc, addr, data); +} + +static void +tda19988_reg_clear(struct tda19988_softc *sc, uint16_t addr, uint8_t flags) +{ + uint8_t data; + + tda19988_reg_read(sc, addr, &data); + data &= ~flags; + tda19988_reg_write(sc, addr, data); +} + +static int +tda19988_probe(device_t dev) +{ + if (!ofw_bus_is_compatible(dev, "nxp,tda19988")) + return (ENXIO); + + return (BUS_PROBE_DEFAULT); +} + +/* +struct drm_display_mode display_mode_640_480 + = { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25175, 640, 656, + 752, 800, 0, 480, 490, 492, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }; + +struct drm_display_mode display_mode_1280_720 + = { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; +*/ + +static void +tda19988_init_encoder(struct tda19988_softc *sc) +{ + uint16_t hs_start, hs_end, line_start, line_end; + uint16_t vwin_start, vwin_end, de_start, de_end; + uint16_t ref_pix, ref_line, pix_start2; + uint8_t reg, div; + + hs_start = 110; + hs_end = 150; + line_start = 1; + line_end = 1 + 5; + vwin_start = 25; + vwin_end = vwin_start + 720; + de_start = 750 - 720; + de_end = 750; + + pix_start2 = 0; + ref_line = 2; + + /* this might changes for other color formats from the CRTC: */ + ref_pix = 0x2a + hs_start; + + div = 148500 / 74250; + + /* set HDMI HDCP mode off: */ + tda19988_reg_set(sc, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS); + tda19988_reg_clear(sc, REG_TX33, TX33_HDMI); + + tda19988_reg_write(sc, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0)); + /* no pre-filter or interpolator: */ + tda19988_reg_write(sc, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) | + HVF_CNTRL_0_INTPOL(0)); + tda19988_reg_write(sc, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0)); + tda19988_reg_write(sc, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) | + VIP_CNTRL_4_BLC(0)); + tda19988_reg_clear(sc, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR); + + tda19988_reg_clear(sc, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ); + tda19988_reg_clear(sc, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE); + tda19988_reg_write(sc, REG_SERIALIZER, 0); + tda19988_reg_write(sc, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(1)); + + /* TODO enable pixel repeat for pixel rates less than 25Msamp/s */ + tda19988_reg_write(sc, REG_RPT_CNTRL, 0); + tda19988_reg_write(sc, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) | + SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); + tda19988_reg_write(sc, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) | + PLL_SERIAL_2_SRL_PR(0)); + tda19988_reg_write(sc, REG_SEL_CLK, 0x00); + tda19988_reg_write(sc, REG_PLL_SERIAL_2, 0x01); + + + tda19988_reg_write2(sc, REG_VS_PIX_STRT_2_MSB, pix_start2); + tda19988_reg_write2(sc, REG_VS_PIX_END_2_MSB, pix_start2); + + /* set color matrix bypass flag: */ + tda19988_reg_set(sc, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP); + + /* set BIAS tmds value: */ + tda19988_reg_write(sc, REG_ANA_GENERAL, 0x09); + + tda19988_reg_clear(sc, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD); + + tda19988_reg_write(sc, REG_VIP_CNTRL_3, 0); + tda19988_reg_set(sc, REG_VIP_CNTRL_3, VIP_CNTRL_3_SYNC_HS); + /* XXX: should be flagged */ + tda19988_reg_set(sc, REG_VIP_CNTRL_3, VIP_CNTRL_3_V_TGL); + tda19988_reg_set(sc, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL); + +#if 1 + /* this is an HDMI monitor, so set things up a bit differently */ + // if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC)) + // tda19988_reg_set(sc, REG_TBG_CNTRL_1, TBG_CNTRL_1_VH_TGL_0); + // else + tda19988_reg_write(sc, REG_TBG_CNTRL_1, 0); + + uint8_t vidformat = 2; + tda19988_reg_write(sc, REG_VIDFORMAT, vidformat); + + /* set up audio registers */ + tda19988_reg_write(sc, REG_ACR_CTS_0, 0x0); + tda19988_reg_write(sc, REG_ACR_CTS_1, 0x0); + tda19988_reg_write(sc, REG_ACR_CTS_2, 0x0); + + tda19988_reg_write(sc, REG_ACR_N_0, 0x0); + tda19988_reg_write(sc, REG_ACR_N_1, 0x18); + tda19988_reg_write(sc, REG_ACR_N_2, 0x0); + + tda19988_reg_set(sc, REG_DIP_FLAGS, DIP_FLAGS_ACR); + + tda19988_reg_write(sc, REG_ENC_CNTRL, 0x04); + tda19988_reg_write(sc, REG_CTS_N, 0x33); + /* Set 2 channel I2S mode */ + tda19988_reg_write(sc, REG_ENA_AP, 0x3); + + /* set audio divider in pll settings */ + tda19988_reg_write(sc, REG_AUDIO_DIV, 0x2); + + /* select the audio input port clock */ + tda19988_reg_write(sc, REG_AIP_CLKSEL, SEL_AIP_I2S); + tda19988_reg_write(sc, REG_MUX_AP, MUX_AP_SELECT_I2S); + + /* select I2S format, and datasize */ + tda19988_reg_write(sc, REG_I2S_FORMAT, 0x0a); + + /* enable the audio FIFO: */ + tda19988_reg_clear(sc, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO); + + /* mute and then unmute, to get audio going */ + tda19988_reg_write(sc, REG_GC_AVMUTE, GC_AVMUTE_SETMUTE); + tda19988_reg_write(sc, REG_GC_AVMUTE, GC_AVMUTE_CLRMUTE); + +#endif + tda19988_reg_write2(sc, REG_NPIX_MSB, 1280 - 1); + tda19988_reg_write2(sc, REG_NLINE_MSB, 720 - 1); + tda19988_reg_write2(sc, REG_VS_LINE_STRT_1_MSB, line_start); + tda19988_reg_write2(sc, REG_VS_LINE_END_1_MSB, line_end); + tda19988_reg_write2(sc, REG_VS_PIX_STRT_1_MSB, hs_start); + tda19988_reg_write2(sc, REG_VS_PIX_END_1_MSB, hs_start); + tda19988_reg_write2(sc, REG_HS_PIX_START_MSB, hs_start); + tda19988_reg_write2(sc, REG_HS_PIX_STOP_MSB, hs_end); + tda19988_reg_write2(sc, REG_VWIN_START_1_MSB, vwin_start); + tda19988_reg_write2(sc, REG_VWIN_END_1_MSB, vwin_end); + tda19988_reg_write2(sc, REG_DE_START_MSB, de_start); + tda19988_reg_write2(sc, REG_DE_STOP_MSB, de_end); + + /* let incoming pixels fill the active space (if any) */ + tda19988_reg_write(sc, REG_ENABLE_SPACE, 0x01); + + tda19988_reg_write2(sc, REG_REFPIX_MSB, ref_pix); + tda19988_reg_write2(sc, REG_REFLINE_MSB, ref_line); + + reg = TBG_CNTRL_1_VHX_EXT_DE | + TBG_CNTRL_1_VHX_EXT_HS | + TBG_CNTRL_1_VHX_EXT_VS | + TBG_CNTRL_1_DWIN_DIS | /* HDCP off */ + TBG_CNTRL_1_VH_TGL_2; + // reg |= TBG_CNTRL_1_VH_TGL_0; + tda19988_reg_set(sc, REG_TBG_CNTRL_1, reg); + + /* must be last register set: */ + tda19988_reg_clear(sc, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE); +} + +static void +tda19988_parse_edid(struct tda19988_softc *sc) +{ + int i; + char edid_header[] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}; + char *modes0[] = { + "800x600 @ 60 Hz", + "800x600 @ 56 Hz", + "640x480 @ 75 Hz", + "640x480 @ 72 Hz", + "640x480 @ 67 Hz", + "640x480 @ 60 Hz", + "720x400 @ 88 Hz", + "720x400 @ 70 Hz" + }; + + char *modes1[] = { + "1280x1024 @ 75 Hz", + "1024x768 @ 75 Hz", + "1024x768 @ 72 Hz", + "1024x768 @ 60 Hz", + "1024x768i @ 87 Hz", + "832x624 @ 75 Hz", + "800x600 @ 75 Hz", + "800x600 @ 72 Hz" + }; + + if (memcmp(sc->sc_edid, edid_header, 8) != 0) { + device_printf(sc->sc_dev, "invalid EDID header\n"); + return; + } + + device_printf(sc->sc_dev, "EDID v%d.%d\n", sc->sc_edid[18], sc->sc_edid[19]); + device_printf(sc->sc_dev, "Supported modes:\n"); + for (i = 0; i < 8; i++) + if (sc->sc_edid[35] & (1 << i)) + device_printf(sc->sc_dev, "%s\n", modes0[i]); + for (i = 0; i < 8; i++) + if (sc->sc_edid[36] & (1 << i)) + device_printf(sc->sc_dev, "%s\n", modes1[i]); + for (i = 38; i < 54; i += 2) { + int x, y, f; + x = y = f = 0; + if ((sc->sc_edid[i] == 1) && (sc->sc_edid[i+1] == 1)) + continue; + x = (sc->sc_edid[i]+31)*8; + switch(sc->sc_edid[i+1] >> 6) { + case 0: + y = x*10/16; + break; + case 1: + y = x*3/4; + break; + case 2: + y = x*4/5; + break; + case 3: + y = x*9/16; + break; + } + + f = sc->sc_edid[i+1] & 0x3f + 60; + device_printf(sc->sc_dev, "%dx%d @ %dHz\n", x, y, f); + } +} + +static void +tda19988_start(void *xdev) +{ + struct tda19988_softc *sc; + device_t dev = (device_t)xdev; + uint8_t data; + uint16_t version; + int attempt; + + sc = device_get_softc(dev); + + + tda19988_cec_write(sc, TDA_CEC_ENAMODS, 0x6 /*ENAMODS_RXSENS | ENAMODS_HDMI*/); + DELAY(1000); + tda19988_cec_read(sc, 0xfe, &data); + + /* Reset core */ + data = 0; + tda19988_reg_set(sc, TDA_SOFTRESET, SOFTRESET_I2C); + DELAY(100); + tda19988_reg_clear(sc, TDA_SOFTRESET, SOFTRESET_I2C); + DELAY(100); + + + version = 0; + tda19988_reg_read(sc, TDA_VERSION, &data); + version |= data; + tda19988_reg_read(sc, TDA_VERSION_MSB, &data); + version |= (data << 8); + printf("TDA revision: 0x%04x\n", version); + + tda19988_reg_write(sc, TDA_DDC_CTRL, DDC_ENABLE); + tda19988_reg_write(sc, TDA_CCLK, CCLK_ENABLE); + tda19988_reg_write(sc, TDA_TX3, 0x27); + tda19988_cec_write(sc, 0xfb, (1 << 7) | (1 << 1)); + + tda19988_reg_clear(sc, TDA_TX4, TX4_PD_RAM); + tda19988_reg_set(sc, TDA_INT_FLAGS2, INT_FLAGS2_EDID_BLK_RD); + tda19988_reg_write(sc, 0x0f, 0x04); + + /* Block 0 */ + tda19988_reg_write(sc, TDA_DDC_ADDR, 0xa0); + tda19988_reg_write(sc, TDA_DDC_OFFS, 0); + tda19988_reg_write(sc, TDA_DDC_SEGM_ADDR, 0x60); + tda19988_reg_write(sc, TDA_DDC_SEGM, 0); + + tda19988_reg_write(sc, TDA_EDID_CTRL, 1); + tda19988_reg_write(sc, TDA_EDID_CTRL, 0); + + data = 0; + for (attempt = 0; attempt < MAX_READ_ATTEMPTS; attempt++) { + tda19988_reg_read(sc, TDA_INT_FLAGS2, &data); + if (data & INT_FLAGS2_EDID_BLK_RD) + break; + DELAY(1000); + } + + if (attempt == MAX_READ_ATTEMPTS) + goto done; + + if (tda19988_block_read(sc, TDA_EDID_DATA0, sc->sc_edid, EDID_LENGTH) != 0) + goto done; + +#if 0 + tda19988_cec_write(sc, 0xff, 0x87); + tda19988_reg_write(sc, 0xff, 0); + tda19988_reg_write(sc, 0xa0, 2); + tda19988_reg_write(sc, 0xe4, 0xc0); + tda19988_reg_write(sc, 0xf0, 0x00); + config_intrhook_disestablish(&sc->enum_hook); + return; +#endif + + + + tda19988_reg_clear(sc, TDA_INT_FLAGS2, INT_FLAGS2_EDID_BLK_RD); + + tda19988_parse_edid(sc); +#if 1 + + tda19988_reg_write(sc, REG_DDC_DISABLE, 0x00); + + /* set clock on DDC channel: */ + tda19988_reg_write(sc, REG_TX3, 39); + +#define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */ +#define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7) +#define CEC_FRO_IM_CLK_CTRL_ENA_OTP (1 << 6) +#define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1) +#define CEC_FRO_IM_CLK_CTRL_FRO_DIV (1 << 0) + + tda19988_cec_write(sc, REG_CEC_FRO_IM_CLK_CTRL, + CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL); + + tda19988_init_encoder(sc); + + tda19988_reg_write(sc, REG_ENA_AP, 0x03); + tda19988_reg_write(sc, REG_ENA_VP_0, 0xff); + tda19988_reg_write(sc, REG_ENA_VP_1, 0xff); + tda19988_reg_write(sc, REG_ENA_VP_2, 0xff); + + /* set muxing after enabling ports: */ + tda19988_reg_write(sc, REG_VIP_CNTRL_0, + VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3)); + tda19988_reg_write(sc, REG_VIP_CNTRL_1, + VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1)); + tda19988_reg_write(sc, REG_VIP_CNTRL_2, + VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5)); +#endif + + +done: + config_intrhook_disestablish(&sc->enum_hook); +} + +static int +tda19988_attach(device_t dev) +{ + struct tda19988_softc *sc; + + sc = device_get_softc(dev); + + sc->sc_dev = dev; + sc->sc_addr = iicbus_get_addr(dev); + sc->sc_cec_addr = 0x34; /* hardcoded */ + + device_set_desc(dev, "NXP TDA19988 HDMI transmitter"); + + sc->enum_hook.ich_func = tda19988_start; + sc->enum_hook.ich_arg = dev; + + if (config_intrhook_establish(&sc->enum_hook) != 0) + return (ENOMEM); + + return (0); +} + +static device_method_t tda_methods[] = { + DEVMETHOD(device_probe, tda19988_probe), + DEVMETHOD(device_attach, tda19988_attach), + {0, 0}, +}; + +static driver_t tda_driver = { + "tda", + tda_methods, + sizeof(struct tda19988_softc), +}; + +static devclass_t tda_devclass; + +DRIVER_MODULE(tda, iicbus, tda_driver, tda_devclass, 0, 0); +MODULE_VERSION(tda, 1); +MODULE_DEPEND(tda, iicbus, 1, 1, 1);