diff --git a/sys/arm/conf/BEAGLEBONE b/sys/arm/conf/BEAGLEBONE index 38c6b4a..8b06d20 100644 --- a/sys/arm/conf/BEAGLEBONE +++ b/sys/arm/conf/BEAGLEBONE @@ -138,3 +138,11 @@ device fdt_pinctrl # Flattened Device Tree options FDT # Configure using FDT/DTB data + +# Comment following lines for boot console on serial port +device vt +device videomode +device hdmi +device ums +device ukbd +device kbdmux diff --git a/sys/arm/ti/am335x/am335x_lcd.c b/sys/arm/ti/am335x/am335x_lcd.c index 501042a..1680e84 100644 --- a/sys/arm/ti/am335x/am335x_lcd.c +++ b/sys/arm/ti/am335x/am335x_lcd.c @@ -52,6 +52,10 @@ __FBSDID("$FreeBSD$"); #include #include +#include +#include +#include + #include #ifdef DEV_SC #include @@ -66,6 +70,7 @@ __FBSDID("$FreeBSD$"); #include "am335x_pwm.h" #include "fb_if.h" +#include "hdmi_if.h" #define LCD_PID 0x00 #define LCD_CTRL 0x04 @@ -176,11 +181,20 @@ __FBSDID("$FreeBSD$"); #define LCD_WRITE4(_sc, reg, value) \ bus_write_4((_sc)->sc_mem_res, reg, value); - /* Backlight is controlled by eCAS interface on PWM unit 0 */ #define PWM_UNIT 0 #define PWM_PERIOD 100 +#define MODE_HBP(mode) ((mode)->htotal - (mode)->hsync_end) +#define MODE_HFP(mode) ((mode)->hsync_start - (mode)->hdisplay) +#define MODE_HSW(mode) ((mode)->hsync_end - (mode)->hsync_start) +#define MODE_VBP(mode) ((mode)->vtotal - (mode)->vsync_end) +#define MODE_VFP(mode) ((mode)->vsync_start - (mode)->vdisplay) +#define MODE_VSW(mode) ((mode)->vsync_end - (mode)->vsync_start) + +#define MAX_PIXEL_CLOCK 126000 +#define MAX_BANDWIDTH (1280*1024*60) + struct am335x_lcd_softc { device_t sc_dev; struct fb_info sc_fb_info; @@ -191,12 +205,18 @@ struct am335x_lcd_softc { int sc_backlight; struct sysctl_oid *sc_oid; + struct panel_info sc_panel; + /* Framebuffer */ bus_dma_tag_t sc_dma_tag; bus_dmamap_t sc_dma_map; size_t sc_fb_size; bus_addr_t sc_fb_phys; uint8_t *sc_fb_base; + + /* HDMI framer */ + phandle_t sc_hdmi_framer; + eventhandler_tag sc_hdmi_evh; }; static void @@ -214,13 +234,22 @@ am335x_fb_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err) static uint32_t am335x_lcd_calc_divisor(uint32_t reference, uint32_t freq) { - uint32_t div; + uint32_t div, i; + uint32_t delta, min_delta; + + min_delta = freq; + div = 255; + /* Raster mode case: divisors are in range from 2 to 255 */ - for (div = 2; div < 255; div++) - if (reference/div <= freq) - return (div); + for (i = 2; i < 255; i++) { + delta = abs(reference/i - freq); + if (delta < min_delta) { + div = i; + min_delta = delta; + } + } - return (255); + return (div); } static int @@ -251,6 +280,81 @@ am335x_lcd_sysctl_backlight(SYSCTL_HANDLER_ARGS) return (error); } +static uint32_t +am335x_mode_vrefresh(const struct videomode *mode) +{ + uint32_t refresh; + + /* Calculate vertical refresh rate */ + refresh = (mode->dot_clock * 1000 / mode->htotal); + refresh = (refresh + mode->vtotal / 2) / mode->vtotal; + + if (mode->flags & VID_INTERLACE) + refresh *= 2; + if (mode->flags & VID_DBLSCAN) + refresh /= 2; + + return refresh; +} + +static int +am335x_mode_is_valid(const struct videomode *mode) +{ + uint32_t hbp, hfp, hsw; + uint32_t vbp, vfp, vsw; + + if (mode->dot_clock > MAX_PIXEL_CLOCK) + return (0); + + if (mode->hdisplay & 0xf) + return (0); + + if (mode->vdisplay > 2048) + return (0); + + /* Check ranges for timing parameters */ + hbp = MODE_HBP(mode) - 1; + hfp = MODE_HFP(mode) - 1; + hsw = MODE_HSW(mode) - 1; + vbp = MODE_VBP(mode); + vfp = MODE_VFP(mode); + vsw = MODE_VSW(mode) - 1; + + if (hbp > 0x3ff) + return (0); + if (hfp > 0x3ff) + return (0); + if (hsw > 0x3ff) + return (0); + + if (vbp > 0xff) + return (0); + if (vfp > 0xff) + return (0); + if (vsw > 0x3f) + return (0); + if (mode->vdisplay*mode->hdisplay*am335x_mode_vrefresh(mode) + > MAX_BANDWIDTH) + return (0); + + return (1); +} + +static void +am335x_read_hdmi_property(device_t dev) +{ + phandle_t node; + phandle_t hdmi_xref; + struct am335x_lcd_softc *sc; + + sc = device_get_softc(dev); + node = ofw_bus_get_node(dev); + if (OF_getencprop(node, "hdmi", &hdmi_xref, sizeof(hdmi_xref)) == -1) + sc->sc_hdmi_framer = 0; + else + sc->sc_hdmi_framer = hdmi_xref; +} + static int am335x_read_property(device_t dev, phandle_t node, const char *name, uint32_t *val) { @@ -343,46 +447,37 @@ out: static int am335x_read_panel_info(device_t dev, phandle_t node, struct panel_info *panel) { - int error; phandle_t panel_info_node; panel_info_node = ofw_bus_find_child(node, "panel-info"); if (panel_info_node == 0) return (-1); - error = 0; + am335x_read_property(dev, panel_info_node, + "ac-bias", &panel->ac_bias); - if ((error = am335x_read_property(dev, panel_info_node, - "ac-bias", &panel->ac_bias))) - goto out; + am335x_read_property(dev, panel_info_node, + "ac-bias-intrpt", &panel->ac_bias_intrpt); - if ((error = am335x_read_property(dev, panel_info_node, - "ac-bias-intrpt", &panel->ac_bias_intrpt))) - goto out; + am335x_read_property(dev, panel_info_node, + "dma-burst-sz", &panel->dma_burst_sz); - if ((error = am335x_read_property(dev, panel_info_node, - "dma-burst-sz", &panel->dma_burst_sz))) - goto out; + am335x_read_property(dev, panel_info_node, + "bpp", &panel->bpp); - if ((error = am335x_read_property(dev, panel_info_node, - "bpp", &panel->bpp))) - goto out; + am335x_read_property(dev, panel_info_node, + "fdd", &panel->fdd); - if ((error = am335x_read_property(dev, panel_info_node, - "fdd", &panel->fdd))) - goto out; - - if ((error = am335x_read_property(dev, panel_info_node, - "sync-edge", &panel->sync_edge))) - goto out; + am335x_read_property(dev, panel_info_node, + "sync-edge", &panel->sync_edge); - error = am335x_read_property(dev, panel_info_node, + am335x_read_property(dev, panel_info_node, "sync-ctrl", &panel->sync_ctrl); -out: - return (error); + return (0); } + static void am335x_lcd_intr(void *arg) { @@ -442,119 +537,75 @@ done: reg = LCD_READ4(sc, LCD_END_OF_INT_IND); } -static int -am335x_lcd_probe(device_t dev) +static const struct videomode * +am335x_lcd_pick_mode(struct edid_info *ei) { -#ifdef DEV_SC - int err; -#endif - - if (!ofw_bus_status_okay(dev)) - return (ENXIO); + const struct videomode *videomode; + const struct videomode *m; + int n; - if (!ofw_bus_is_compatible(dev, "ti,am33xx-tilcdc")) - return (ENXIO); + /* Get standard VGA as default */ + videomode = NULL; - device_set_desc(dev, "AM335x LCD controller"); + /* + * Pick a mode. + */ + if (ei->edid_preferred_mode != NULL) { + if (am335x_mode_is_valid(ei->edid_preferred_mode)) + videomode = ei->edid_preferred_mode; + } -#ifdef DEV_SC - err = sc_probe_unit(device_get_unit(dev), - device_get_flags(dev) | SC_AUTODETECT_KBD); - if (err != 0) - return (err); -#endif + if (videomode == NULL) { + m = ei->edid_modes; + + sort_modes(ei->edid_modes, + &ei->edid_preferred_mode, + ei->edid_nmodes); + for (n = 0; n < ei->edid_nmodes; n++) + if (am335x_mode_is_valid(&m[n])) { + videomode = &m[n]; + break; + } + } - return (BUS_PROBE_DEFAULT); + return videomode; } static int -am335x_lcd_attach(device_t dev) +am335x_lcd_configure(struct am335x_lcd_softc *sc) { - struct am335x_lcd_softc *sc; - int rid; int div; - struct panel_info panel; uint32_t reg, timing0, timing1, timing2; - struct sysctl_ctx_list *ctx; - struct sysctl_oid *tree; uint32_t burst_log; - int err; size_t dma_size; uint32_t hbp, hfp, hsw; uint32_t vbp, vfp, vsw; uint32_t width, height; - phandle_t root, panel_node; - - sc = device_get_softc(dev); - sc->sc_dev = dev; - - root = OF_finddevice("/"); - if (root == 0) { - device_printf(dev, "failed to get FDT root node\n"); - return (ENXIO); - } - - panel_node = fdt_find_compatible(root, "ti,tilcdc,panel", 1); - if (panel_node == 0) { - device_printf(dev, "failed to find compatible panel in FDT blob\n"); - return (ENXIO); - } - - if (am335x_read_panel_info(dev, panel_node, &panel)) { - device_printf(dev, "failed to read panel info\n"); - return (ENXIO); - } + unsigned int ref_freq; + int err; - if (am335x_read_timing(dev, panel_node, &panel)) { - device_printf(dev, "failed to read timings\n"); + /* + * try to adjust clock to get double of requested frequency + * HDMI/DVI displays are very sensitive to error in frequncy value + */ + if (ti_prcm_clk_set_source_freq(LCDC_CLK, sc->sc_panel.panel_pxl_clk*2)) { + device_printf(sc->sc_dev, "can't set source frequency\n"); return (ENXIO); } - int ref_freq = 0; - ti_prcm_clk_enable(LCDC_CLK); if (ti_prcm_clk_get_source_freq(LCDC_CLK, &ref_freq)) { - device_printf(dev, "Can't get reference frequency\n"); + device_printf(sc->sc_dev, "can't get reference frequency\n"); return (ENXIO); } - rid = 0; - sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, - RF_ACTIVE); - if (!sc->sc_mem_res) { - device_printf(dev, "cannot allocate memory window\n"); - return (ENXIO); - } - - rid = 0; - sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, - RF_ACTIVE); - if (!sc->sc_irq_res) { - bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); - device_printf(dev, "cannot allocate interrupt\n"); - return (ENXIO); - } - - if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, - NULL, am335x_lcd_intr, sc, - &sc->sc_intr_hl) != 0) { - bus_release_resource(dev, SYS_RES_IRQ, rid, - sc->sc_irq_res); - bus_release_resource(dev, SYS_RES_MEMORY, rid, - sc->sc_mem_res); - device_printf(dev, "Unable to setup the irq handler.\n"); - return (ENXIO); - } - - LCD_LOCK_INIT(sc); - /* Panle initialization */ - dma_size = round_page(panel.panel_width*panel.panel_height*panel.bpp/8); + dma_size = round_page(sc->sc_panel.panel_width*sc->sc_panel.panel_height*sc->sc_panel.bpp/8); /* * Now allocate framebuffer memory */ err = bus_dma_tag_create( - bus_get_dma_tag(dev), + bus_get_dma_tag(sc->sc_dev), 4, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ @@ -564,49 +615,49 @@ am335x_lcd_attach(device_t dev) NULL, NULL, /* lockfunc, lockarg */ &sc->sc_dma_tag); if (err) - goto fail; + goto done; err = bus_dmamem_alloc(sc->sc_dma_tag, (void **)&sc->sc_fb_base, BUS_DMA_COHERENT, &sc->sc_dma_map); if (err) { - device_printf(dev, "cannot allocate framebuffer\n"); - goto fail; + device_printf(sc->sc_dev, "cannot allocate framebuffer\n"); + goto done; } err = bus_dmamap_load(sc->sc_dma_tag, sc->sc_dma_map, sc->sc_fb_base, dma_size, am335x_fb_dmamap_cb, &sc->sc_fb_phys, BUS_DMA_NOWAIT); if (err) { - device_printf(dev, "cannot load DMA map\n"); - goto fail; + device_printf(sc->sc_dev, "cannot load DMA map\n"); + goto done; } /* Make sure it's blank */ - memset(sc->sc_fb_base, 0x00, dma_size); + memset(sc->sc_fb_base, 0x0, dma_size); /* Calculate actual FB Size */ - sc->sc_fb_size = panel.panel_width*panel.panel_height*panel.bpp/8; + sc->sc_fb_size = sc->sc_panel.panel_width*sc->sc_panel.panel_height*sc->sc_panel.bpp/8; /* Only raster mode is supported */ reg = CTRL_RASTER_MODE; - div = am335x_lcd_calc_divisor(ref_freq, panel.panel_pxl_clk); + div = am335x_lcd_calc_divisor(ref_freq, sc->sc_panel.panel_pxl_clk); reg |= (div << CTRL_DIV_SHIFT); LCD_WRITE4(sc, LCD_CTRL, reg); /* Set timing */ timing0 = timing1 = timing2 = 0; - hbp = panel.panel_hbp - 1; - hfp = panel.panel_hfp - 1; - hsw = panel.panel_hsw - 1; + hbp = sc->sc_panel.panel_hbp - 1; + hfp = sc->sc_panel.panel_hfp - 1; + hsw = sc->sc_panel.panel_hsw - 1; - vbp = panel.panel_vbp; - vfp = panel.panel_vfp; - vsw = panel.panel_vsw - 1; + vbp = sc->sc_panel.panel_vbp; + vfp = sc->sc_panel.panel_vfp; + vsw = sc->sc_panel.panel_vsw - 1; - height = panel.panel_height - 1; - width = panel.panel_width - 1; + height = sc->sc_panel.panel_height - 1; + width = sc->sc_panel.panel_width - 1; /* Horizontal back porch */ timing0 |= (hbp & 0xff) << RASTER_TIMING_0_HBP_SHIFT; @@ -636,22 +687,22 @@ am335x_lcd_attach(device_t dev) << RASTER_TIMING_2_LPP_B10_SHIFT; /* clock signal settings */ - if (panel.sync_ctrl) + if (sc->sc_panel.sync_ctrl) timing2 |= RASTER_TIMING_2_PHSVS; - if (panel.sync_edge) + if (sc->sc_panel.sync_edge) timing2 |= RASTER_TIMING_2_PHSVS_RISE; else timing2 |= RASTER_TIMING_2_PHSVS_FALL; - if (panel.hsync_active == 0) + if (sc->sc_panel.hsync_active == 0) timing2 |= RASTER_TIMING_2_IHS; - if (panel.vsync_active == 0) + if (sc->sc_panel.vsync_active == 0) timing2 |= RASTER_TIMING_2_IVS; - if (panel.pixelclk_active == 0) + if (sc->sc_panel.pixelclk_active == 0) timing2 |= RASTER_TIMING_2_IPC; /* AC bias */ - timing2 |= (panel.ac_bias << RASTER_TIMING_2_ACB_SHIFT); - timing2 |= (panel.ac_bias_intrpt << RASTER_TIMING_2_ACBI_SHIFT); + timing2 |= (sc->sc_panel.ac_bias << RASTER_TIMING_2_ACB_SHIFT); + timing2 |= (sc->sc_panel.ac_bias_intrpt << RASTER_TIMING_2_ACBI_SHIFT); LCD_WRITE4(sc, LCD_RASTER_TIMING_0, timing0); LCD_WRITE4(sc, LCD_RASTER_TIMING_1, timing1); @@ -660,7 +711,7 @@ am335x_lcd_attach(device_t dev) /* DMA settings */ reg = LCDDMA_CTRL_FB0_FB1; /* Find power of 2 for current burst size */ - switch (panel.dma_burst_sz) { + switch (sc->sc_panel.dma_burst_sz) { case 1: burst_log = 0; break; @@ -690,11 +741,11 @@ am335x_lcd_attach(device_t dev) /* Enable LCD */ reg = RASTER_CTRL_LCDTFT; - reg |= (panel.fdd << RASTER_CTRL_REQDLY_SHIFT); + reg |= (sc->sc_panel.fdd << RASTER_CTRL_REQDLY_SHIFT); reg |= (PALETTE_DATA_ONLY << RASTER_CTRL_PALMODE_SHIFT); - if (panel.bpp >= 24) + if (sc->sc_panel.bpp >= 24) reg |= RASTER_CTRL_TFT24; - if (panel.bpp == 32) + if (sc->sc_panel.bpp == 32) reg |= RASTER_CTRL_TFT24_UNPACKED; LCD_WRITE4(sc, LCD_RASTER_CTRL, reg); @@ -717,54 +768,255 @@ am335x_lcd_attach(device_t dev) LCD_WRITE4(sc, LCD_SYSCONFIG, SYSCONFIG_STANDBY_SMART | SYSCONFIG_IDLE_SMART); - /* Init backlight interface */ - ctx = device_get_sysctl_ctx(sc->sc_dev); - tree = device_get_sysctl_tree(sc->sc_dev); - sc->sc_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, - "backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0, - am335x_lcd_sysctl_backlight, "I", "LCD backlight"); - sc->sc_backlight = 0; - /* Check if eCAS interface is available at this point */ - if (am335x_pwm_config_ecap(PWM_UNIT, - PWM_PERIOD, PWM_PERIOD) == 0) - sc->sc_backlight = 100; - sc->sc_fb_info.fb_name = device_get_nameunit(sc->sc_dev); sc->sc_fb_info.fb_vbase = (intptr_t)sc->sc_fb_base; sc->sc_fb_info.fb_pbase = sc->sc_fb_phys; sc->sc_fb_info.fb_size = sc->sc_fb_size; - sc->sc_fb_info.fb_bpp = sc->sc_fb_info.fb_depth = panel.bpp; - sc->sc_fb_info.fb_stride = panel.panel_width*panel.bpp / 8; - sc->sc_fb_info.fb_width = panel.panel_width; - sc->sc_fb_info.fb_height = panel.panel_height; + sc->sc_fb_info.fb_bpp = sc->sc_fb_info.fb_depth = sc->sc_panel.bpp; + sc->sc_fb_info.fb_stride = sc->sc_panel.panel_width*sc->sc_panel.bpp / 8; + sc->sc_fb_info.fb_width = sc->sc_panel.panel_width; + sc->sc_fb_info.fb_height = sc->sc_panel.panel_height; #ifdef DEV_SC - err = (sc_attach_unit(device_get_unit(dev), - device_get_flags(dev) | SC_AUTODETECT_KBD)); + err = (sc_attach_unit(device_get_unit(sc->sc_dev), + device_get_flags(sc->sc_dev) | SC_AUTODETECT_KBD)); if (err) { - device_printf(dev, "failed to attach syscons\n"); + device_printf(sc->sc_dev, "failed to attach syscons\n"); goto fail; } am335x_lcd_syscons_setup((vm_offset_t)sc->sc_fb_base, sc->sc_fb_phys, &panel); #else /* VT */ - device_t fbd = device_add_child(dev, "fbd", - device_get_unit(dev)); - if (fbd == NULL) { - device_printf(dev, "Failed to add fbd child\n"); - goto fail; + device_t fbd = device_add_child(sc->sc_dev, "fbd", + device_get_unit(sc->sc_dev)); + if (fbd != NULL) { + if (device_probe_and_attach(fbd) != 0) + device_printf(sc->sc_dev, "failed to attach fbd device\n"); + } else + device_printf(sc->sc_dev, "failed to add fbd child\n"); +#endif + +done: + return (err); +} + + + +static void +am335x_lcd_hdmi_event(void *arg) +{ + struct am335x_lcd_softc *sc; + const struct videomode *videomode; + struct videomode hdmi_mode; + device_t hdmi_dev; + uint8_t *edid; + uint32_t edid_len; + struct edid_info ei; + + sc = arg; + + /* Nothing to work with */ + if (!sc->sc_hdmi_framer) { + device_printf(sc->sc_dev, "HDMI event without HDMI framer set\n"); + return; } - if (device_probe_and_attach(fbd) != 0) { - device_printf(dev, "Failed to attach fbd device\n"); - goto fail; + + hdmi_dev = OF_device_from_xref(sc->sc_hdmi_framer); + if (!hdmi_dev) { + device_printf(sc->sc_dev, "no actual device for \"hdmi\" property\n"); + return; + } + + edid = NULL; + edid_len = 0; + if (HDMI_GET_EDID(hdmi_dev, &edid, &edid_len) != 0) { + device_printf(sc->sc_dev, "failed to get EDID info from HDMI framer\n"); + return; + } + + videomode = NULL; + + if (edid_parse(edid, &ei) == 0) { + edid_print(&ei); + videomode = am335x_lcd_pick_mode(&ei); + } else + device_printf(sc->sc_dev, "failed to parse EDID\n"); + + /* Use standard VGA as fallback */ + if (videomode == NULL) + videomode = pick_mode_by_ref(640, 480, 60); + + if (videomode == NULL) { + device_printf(sc->sc_dev, "failed to find usable videomode"); + return; + } + + + device_printf(sc->sc_dev, "detected videomode: %dx%d @ %dKHz\n", videomode->hdisplay, + videomode->vdisplay, am335x_mode_vrefresh(videomode)); + + sc->sc_panel.panel_width = videomode->hdisplay; + sc->sc_panel.panel_height = videomode->vdisplay; + sc->sc_panel.panel_hfp = videomode->hsync_start - videomode->hdisplay; + sc->sc_panel.panel_hbp = videomode->htotal - videomode->hsync_end; + sc->sc_panel.panel_hsw = videomode->hsync_end - videomode->hsync_start; + sc->sc_panel.panel_vfp = videomode->vsync_start - videomode->vdisplay; + sc->sc_panel.panel_vbp = videomode->vtotal - videomode->vsync_end; + sc->sc_panel.panel_vsw = videomode->vsync_end - videomode->vsync_start; + sc->sc_panel.pixelclk_active = 1; + + /* logic for HSYNC should be reversed */ + if (videomode->flags & VID_NHSYNC) + sc->sc_panel.hsync_active = 1; + else + sc->sc_panel.hsync_active = 0; + + if (videomode->flags & VID_NVSYNC) + sc->sc_panel.vsync_active = 0; + else + sc->sc_panel.vsync_active = 1; + + sc->sc_panel.panel_pxl_clk = videomode->dot_clock * 1000; + + am335x_lcd_configure(sc); + + memcpy(&hdmi_mode, videomode, sizeof(hdmi_mode)); + hdmi_mode.hskew = videomode->hsync_end - videomode->hsync_start; + hdmi_mode.flags |= VID_HSKEW; +#if 0 + if (videomode->flags & VID_NHSYNC) { + hdmi_mode.flags &= ~(VID_NHSYNC); + hdmi_mode.flags |= VID_PHSYNC; + } + else { + hdmi_mode.flags &= ~(VID_PHSYNC); + hdmi_mode.flags |= VID_NHSYNC; } #endif - return (0); + HDMI_SET_VIDEOMODE(hdmi_dev, &hdmi_mode); +} -fail: - return (err); +static int +am335x_lcd_probe(device_t dev) +{ +#ifdef DEV_SC + int err; +#endif + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_is_compatible(dev, "ti,am33xx-tilcdc")) + return (ENXIO); + + device_set_desc(dev, "AM335x LCD controller"); + +#ifdef DEV_SC + err = sc_probe_unit(device_get_unit(dev), + device_get_flags(dev) | SC_AUTODETECT_KBD); + if (err != 0) + return (err); +#endif + + return (BUS_PROBE_DEFAULT); +} + +static int +am335x_lcd_attach(device_t dev) +{ + struct am335x_lcd_softc *sc; + + int err; + int rid; + struct sysctl_ctx_list *ctx; + struct sysctl_oid *tree; + phandle_t root, panel_node; + + err = 0; + sc = device_get_softc(dev); + sc->sc_dev = dev; + + am335x_read_hdmi_property(dev); + + root = OF_finddevice("/"); + if (root == 0) { + device_printf(dev, "failed to get FDT root node\n"); + return (ENXIO); + } + + sc->sc_panel.ac_bias = 255; + sc->sc_panel.ac_bias_intrpt = 0; + sc->sc_panel.dma_burst_sz = 16; + sc->sc_panel.bpp = 16; + sc->sc_panel.fdd = 128; + sc->sc_panel.sync_edge = 0; + sc->sc_panel.sync_ctrl = 1; + + panel_node = fdt_find_compatible(root, "ti,tilcdc,panel", 1); + if (panel_node != 0) { + device_printf(dev, "using static panel info\n"); + if (am335x_read_panel_info(dev, panel_node, &sc->sc_panel)) { + device_printf(dev, "failed to read panel info\n"); + return (ENXIO); + } + + if (am335x_read_timing(dev, panel_node, &sc->sc_panel)) { + device_printf(dev, "failed to read timings\n"); + return (ENXIO); + } + } + + ti_prcm_clk_enable(LCDC_CLK); + + rid = 0; + sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->sc_mem_res) { + device_printf(dev, "cannot allocate memory window\n"); + return (ENXIO); + } + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (!sc->sc_irq_res) { + bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); + device_printf(dev, "cannot allocate interrupt\n"); + return (ENXIO); + } + + if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, am335x_lcd_intr, sc, + &sc->sc_intr_hl) != 0) { + bus_release_resource(dev, SYS_RES_IRQ, rid, + sc->sc_irq_res); + bus_release_resource(dev, SYS_RES_MEMORY, rid, + sc->sc_mem_res); + device_printf(dev, "Unable to setup the irq handler.\n"); + return (ENXIO); + } + + LCD_LOCK_INIT(sc); + + /* Init backlight interface */ + ctx = device_get_sysctl_ctx(sc->sc_dev); + tree = device_get_sysctl_tree(sc->sc_dev); + sc->sc_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0, + am335x_lcd_sysctl_backlight, "I", "LCD backlight"); + sc->sc_backlight = 0; + /* Check if eCAS interface is available at this point */ + if (am335x_pwm_config_ecap(PWM_UNIT, + PWM_PERIOD, PWM_PERIOD) == 0) + sc->sc_backlight = 100; + + + sc->sc_hdmi_evh = EVENTHANDLER_REGISTER(hdmi_event, + am335x_lcd_hdmi_event, sc, 0); + + return (0); } static int diff --git a/sys/arm/ti/am335x/am335x_lcd.h b/sys/arm/ti/am335x/am335x_lcd.h index e3c82db..ed0779f 100644 --- a/sys/arm/ti/am335x/am335x_lcd.h +++ b/sys/arm/ti/am335x/am335x_lcd.h @@ -29,6 +29,7 @@ #define __AM335X_LCD_H__ struct panel_info { + /* Timing part */ uint32_t panel_width; uint32_t panel_height; uint32_t panel_hfp; @@ -37,16 +38,17 @@ struct panel_info { uint32_t panel_vfp; uint32_t panel_vbp; uint32_t panel_vsw; + uint32_t hsync_active; + uint32_t vsync_active; + uint32_t panel_pxl_clk; + uint32_t ac_bias; uint32_t ac_bias_intrpt; uint32_t dma_burst_sz; uint32_t bpp; uint32_t fdd; - uint32_t hsync_active; - uint32_t vsync_active; uint32_t sync_edge; uint32_t sync_ctrl; - uint32_t panel_pxl_clk; uint32_t pixelclk_active; }; diff --git a/sys/arm/ti/am335x/am335x_prcm.c b/sys/arm/ti/am335x/am335x_prcm.c index fa47f63..69fa6e2 100644 --- a/sys/arm/ti/am335x/am335x_prcm.c +++ b/sys/arm/ti/am335x/am335x_prcm.c @@ -151,6 +151,7 @@ static int am335x_clk_hsmmc_get_source_freq(struct ti_clock_dev *clkdev, unsign static int am335x_clk_get_sysclk_freq(struct ti_clock_dev *clkdev, unsigned int *freq); static int am335x_clk_get_arm_fclk_freq(struct ti_clock_dev *clkdev, unsigned int *freq); static int am335x_clk_get_arm_disp_freq(struct ti_clock_dev *clkdev, unsigned int *freq); +static int am335x_clk_set_arm_disp_freq(struct ti_clock_dev *clkdev, unsigned int freq); static void am335x_prcm_reset(void); static int am335x_clk_cpsw_activate(struct ti_clock_dev *clkdev); static int am335x_clk_musb0_activate(struct ti_clock_dev *clkdev); @@ -163,7 +164,8 @@ static int am335x_clk_pruss_activate(struct ti_clock_dev *clkdev); .clk_deactivate = am335x_clk_noop_deactivate, \ .clk_set_source = am335x_clk_noop_set_source, \ .clk_accessible = NULL, \ - .clk_get_source_freq = NULL \ + .clk_get_source_freq = NULL, \ + .clk_set_source_freq = NULL \ } #define AM335X_GENERIC_CLOCK_DEV(i) \ @@ -172,7 +174,8 @@ static int am335x_clk_pruss_activate(struct ti_clock_dev *clkdev); .clk_deactivate = am335x_clk_generic_deactivate, \ .clk_set_source = am335x_clk_generic_set_source, \ .clk_accessible = NULL, \ - .clk_get_source_freq = NULL \ + .clk_get_source_freq = NULL, \ + .clk_set_source_freq = NULL \ } #define AM335X_GPIO_CLOCK_DEV(i) \ @@ -181,7 +184,8 @@ static int am335x_clk_pruss_activate(struct ti_clock_dev *clkdev); .clk_deactivate = am335x_clk_generic_deactivate, \ .clk_set_source = am335x_clk_generic_set_source, \ .clk_accessible = NULL, \ - .clk_get_source_freq = NULL \ + .clk_get_source_freq = NULL, \ + .clk_set_source_freq = NULL \ } #define AM335X_MMCHS_CLOCK_DEV(i) \ @@ -190,7 +194,8 @@ static int am335x_clk_pruss_activate(struct ti_clock_dev *clkdev); .clk_deactivate = am335x_clk_generic_deactivate, \ .clk_set_source = am335x_clk_generic_set_source, \ .clk_accessible = NULL, \ - .clk_get_source_freq = am335x_clk_hsmmc_get_source_freq \ + .clk_get_source_freq = am335x_clk_hsmmc_get_source_freq, \ + .clk_set_source_freq = NULL \ } struct ti_clock_dev ti_am335x_clk_devmap[] = { @@ -201,6 +206,7 @@ struct ti_clock_dev ti_am335x_clk_devmap[] = { .clk_set_source = NULL, .clk_accessible = NULL, .clk_get_source_freq = am335x_clk_get_sysclk_freq, + .clk_set_source_freq = NULL, }, /* MPU (ARM) core clocks */ { .id = MPU_CLK, @@ -209,6 +215,7 @@ struct ti_clock_dev ti_am335x_clk_devmap[] = { .clk_set_source = NULL, .clk_accessible = NULL, .clk_get_source_freq = am335x_clk_get_arm_fclk_freq, + .clk_set_source_freq = NULL, }, /* CPSW Ethernet Switch core clocks */ { .id = CPSW_CLK, @@ -217,6 +224,7 @@ struct ti_clock_dev ti_am335x_clk_devmap[] = { .clk_set_source = NULL, .clk_accessible = NULL, .clk_get_source_freq = NULL, + .clk_set_source_freq = NULL, }, /* Mentor USB HS controller core clocks */ @@ -226,6 +234,7 @@ struct ti_clock_dev ti_am335x_clk_devmap[] = { .clk_set_source = NULL, .clk_accessible = NULL, .clk_get_source_freq = NULL, + .clk_set_source_freq = NULL, }, /* LCD controller clocks */ @@ -235,6 +244,7 @@ struct ti_clock_dev ti_am335x_clk_devmap[] = { .clk_set_source = NULL, .clk_accessible = NULL, .clk_get_source_freq = am335x_clk_get_arm_disp_freq, + .clk_set_source_freq = am335x_clk_set_arm_disp_freq, }, /* UART */ @@ -296,6 +306,7 @@ struct ti_clock_dev ti_am335x_clk_devmap[] = { .clk_set_source = NULL, .clk_accessible = NULL, .clk_get_source_freq = NULL, + .clk_set_source_freq = NULL, }, /* RTC */ @@ -627,6 +638,8 @@ am335x_clk_get_sysclk_freq(struct ti_clock_dev *clkdev, unsigned int *freq) #define DPLL_BYP_CLKSEL(reg) ((reg>>23) & 1) #define DPLL_DIV(reg) ((reg & 0x7f)+1) #define DPLL_MULT(reg) ((reg>>8) & 0x7FF) +#define DPLL_MAX_MUL 0x800 +#define DPLL_MAX_DIV 0x80 static int am335x_clk_get_arm_fclk_freq(struct ti_clock_dev *clkdev, unsigned int *freq) @@ -662,6 +675,52 @@ am335x_clk_get_arm_disp_freq(struct ti_clock_dev *clkdev, unsigned int *freq) return(0); } +static int +am335x_clk_set_arm_disp_freq(struct ti_clock_dev *clkdev, unsigned int freq) +{ + uint32_t sysclk; + uint32_t mul, div; + uint32_t i, j; + unsigned int delta, min_delta; + + am335x_clk_get_sysclk_freq(NULL, &sysclk); + + /* Bypass mode */ + prcm_write_4(CM_WKUP_CM_CLKMODE_DPLL_DISP, 0x4); + + /* Make sure it's in bypass mode */ + while (!(prcm_read_4(CM_WKUP_CM_IDLEST_DPLL_DISP) + & (1 << 8))) + DELAY(10); + + /* Dumb and non-optimal implementation */ + min_delta = freq; + for (i = 1; i < DPLL_MAX_MUL; i++) { + for (j = 1; j < DPLL_MAX_DIV; j++) { + delta = abs(freq - i*(sysclk/j)); + if (delta < min_delta) { + mul = i; + div = j; + min_delta = delta; + } + if (min_delta == 0) + break; + } + } + + prcm_write_4(CM_WKUP_CM_CLKSEL_DPLL_DISP, (mul << 8) | (div - 1)); + + /* Locked mode */ + prcm_write_4(CM_WKUP_CM_CLKMODE_DPLL_DISP, 0x7); + + int timeout = 10000; + while ((!(prcm_read_4(CM_WKUP_CM_IDLEST_DPLL_DISP) + & (1 << 0))) && timeout--) + DELAY(10); + + return(0); +} + static void am335x_prcm_reset(void) { @@ -724,27 +783,10 @@ am335x_clk_lcdc_activate(struct ti_clock_dev *clkdev) if (sc == NULL) return (ENXIO); - /* Bypass mode */ - prcm_write_4(CM_WKUP_CM_CLKMODE_DPLL_DISP, 0x4); - - /* Make sure it's in bypass mode */ - while (!(prcm_read_4(CM_WKUP_CM_IDLEST_DPLL_DISP) - & (1 << 8))) - DELAY(10); - /* - * For now set frequency to 99*SYSFREQ/8 which is twice as - * HDMI 1080p pixel clock (minimum LCDC freq divisor is 2) + * For now set frequency to 2*VGA_PIXEL_CLOCK */ - prcm_write_4(CM_WKUP_CM_CLKSEL_DPLL_DISP, (99 << 8) | 8); - - /* Locked mode */ - prcm_write_4(CM_WKUP_CM_CLKMODE_DPLL_DISP, 0x7); - - int timeout = 10000; - while ((!(prcm_read_4(CM_WKUP_CM_IDLEST_DPLL_DISP) - & (1 << 0))) && timeout--) - DELAY(10); + am335x_clk_set_arm_disp_freq(clkdev, 25175000*2); /*set MODULEMODE to ENABLE(2) */ prcm_write_4(CM_PER_LCDC_CLKCTRL, 2); diff --git a/sys/arm/ti/am335x/files.am335x b/sys/arm/ti/am335x/files.am335x index 277ee52..7293fd0 100644 --- a/sys/arm/ti/am335x/files.am335x +++ b/sys/arm/ti/am335x/files.am335x @@ -16,5 +16,8 @@ arm/ti/am335x/am335x_scm_padconf.c standard arm/ti/am335x/am335x_usbss.c optional musb fdt arm/ti/am335x/am335x_musb.c optional musb fdt +arm/ti/am335x/hdmi_if.m optional hdmi +arm/ti/am335x/tda19988.c optional hdmi + arm/ti/ti_edma3.c standard arm/ti/cpsw/if_cpsw.c optional cpsw diff --git a/sys/arm/ti/am335x/tda19988.c b/sys/arm/ti/am335x/tda19988.c new file mode 100644 index 0000000..837961d --- /dev/null +++ b/sys/arm/ti/am335x/tda19988.c @@ -0,0 +1,1016 @@ +/*- + * Copyright (c) 2015 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$"); +/* +* NXP TDA19988 HDMI encoder +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "iicbus_if.h" +#include "hdmi_if.h" + +#define MKREG(page, addr) (((page) << 8) | (addr)) + +#define REGPAGE(reg) (((reg) >> 8) & 0xff) +#define REGADDR(reg) ((reg) & 0xff) + +#define TDA_VERSION MKREG(0x00, 0x00) +#define TDA_MAIN_CNTRL0 MKREG(0x00, 0x01) +#define MAIN_CNTRL0_SR (1 << 0) +#define TDA_VERSION_MSB MKREG(0x00, 0x02) +#define TDA_SOFTRESET MKREG(0x00, 0x0a) +#define SOFTRESET_I2C (1 << 1) +#define SOFTRESET_AUDIO (1 << 0) +#define TDA_DDC_CTRL MKREG(0x00, 0x0b) +#define DDC_ENABLE 0 +#define TDA_CCLK MKREG(0x00, 0x0c) +#define CCLK_ENABLE 1 +#define TDA_INT_FLAGS_2 MKREG(0x00, 0x11) +#define INT_FLAGS_2_EDID_BLK_RD (1 << 1) + +#define TDA_VIP_CNTRL_0 MKREG(0x00, 0x20) +#define TDA_VIP_CNTRL_1 MKREG(0x00, 0x21) +#define TDA_VIP_CNTRL_2 MKREG(0x00, 0x22) +#define TDA_VIP_CNTRL_3 MKREG(0x00, 0x23) +#define VIP_CNTRL_3_SYNC_HS (2 << 4) +#define VIP_CNTRL_3_V_TGL (1 << 2) +#define VIP_CNTRL_3_H_TGL (1 << 1) + +#define TDA_VIP_CNTRL_4 MKREG(0x00, 0x24) +#define VIP_CNTRL_4_BLANKIT_NDE (0 << 2) +#define VIP_CNTRL_4_BLANKIT_HS_VS (1 << 2) +#define VIP_CNTRL_4_BLANKIT_NHS_VS (2 << 2) +#define VIP_CNTRL_4_BLANKIT_HE_VE (3 << 2) +#define VIP_CNTRL_4_BLC_NONE (0 << 0) +#define VIP_CNTRL_4_BLC_RGB444 (1 << 0) +#define VIP_CNTRL_4_BLC_YUV444 (2 << 0) +#define VIP_CNTRL_4_BLC_YUV422 (3 << 0) +#define TDA_VIP_CNTRL_5 MKREG(0x00, 0x25) +#define VIP_CNTRL_5_SP_CNT(n) (((n) & 3) << 1) +#define TDA_MUX_VP_VIP_OUT MKREG(0x00, 0x27) +#define TDA_MAT_CONTRL MKREG(0x00, 0x80) +#define MAT_CONTRL_MAT_BP (1 << 2) +#define TDA_VIDFORMAT MKREG(0x00, 0xa0) +#define TDA_REFPIX_MSB MKREG(0x00, 0xa1) +#define TDA_REFPIX_LSB MKREG(0x00, 0xa2) +#define TDA_REFLINE_MSB MKREG(0x00, 0xa3) +#define TDA_REFLINE_LSB MKREG(0x00, 0xa4) +#define TDA_NPIX_MSB MKREG(0x00, 0xa5) +#define TDA_NPIX_LSB MKREG(0x00, 0xa6) +#define TDA_NLINE_MSB MKREG(0x00, 0xa7) +#define TDA_NLINE_LSB MKREG(0x00, 0xa8) +#define TDA_VS_LINE_STRT_1_MSB MKREG(0x00, 0xa9) +#define TDA_VS_LINE_STRT_1_LSB MKREG(0x00, 0xaa) +#define TDA_VS_PIX_STRT_1_MSB MKREG(0x00, 0xab) +#define TDA_VS_PIX_STRT_1_LSB MKREG(0x00, 0xac) +#define TDA_VS_LINE_END_1_MSB MKREG(0x00, 0xad) +#define TDA_VS_LINE_END_1_LSB MKREG(0x00, 0xae) +#define TDA_VS_PIX_END_1_MSB MKREG(0x00, 0xaf) +#define TDA_VS_PIX_END_1_LSB MKREG(0x00, 0xb0) +#define TDA_VS_LINE_STRT_2_MSB MKREG(0x00, 0xb1) +#define TDA_VS_LINE_STRT_2_LSB MKREG(0x00, 0xb2) +#define TDA_VS_PIX_STRT_2_MSB MKREG(0x00, 0xb3) +#define TDA_VS_PIX_STRT_2_LSB MKREG(0x00, 0xb4) +#define TDA_VS_LINE_END_2_MSB MKREG(0x00, 0xb5) +#define TDA_VS_LINE_END_2_LSB MKREG(0x00, 0xb6) +#define TDA_VS_PIX_END_2_MSB MKREG(0x00, 0xb7) +#define TDA_VS_PIX_END_2_LSB MKREG(0x00, 0xb8) +#define TDA_HS_PIX_START_MSB MKREG(0x00, 0xb9) +#define TDA_HS_PIX_START_LSB MKREG(0x00, 0xba) +#define TDA_HS_PIX_STOP_MSB MKREG(0x00, 0xbb) +#define TDA_HS_PIX_STOP_LSB MKREG(0x00, 0xbc) +#define TDA_VWIN_START_1_MSB MKREG(0x00, 0xbd) +#define TDA_VWIN_START_1_LSB MKREG(0x00, 0xbe) +#define TDA_VWIN_END_1_MSB MKREG(0x00, 0xbf) +#define TDA_VWIN_END_1_LSB MKREG(0x00, 0xc0) +#define TDA_VWIN_START_2_MSB MKREG(0x00, 0xc1) +#define TDA_VWIN_START_2_LSB MKREG(0x00, 0xc2) +#define TDA_VWIN_END_2_MSB MKREG(0x00, 0xc3) +#define TDA_VWIN_END_2_LSB MKREG(0x00, 0xc4) +#define TDA_DE_START_MSB MKREG(0x00, 0xc5) +#define TDA_DE_START_LSB MKREG(0x00, 0xc6) +#define TDA_DE_STOP_MSB MKREG(0x00, 0xc7) +#define TDA_DE_STOP_LSB MKREG(0x00, 0xc8) + +#define TDA_TBG_CNTRL_0 MKREG(0x00, 0xca) +#define TBG_CNTRL_0_SYNC_ONCE (1 << 7) +#define TBG_CNTRL_0_SYNC_MTHD (1 << 6) + +#define TDA_TBG_CNTRL_1 MKREG(0x00, 0xcb) +#define TBG_CNTRL_1_DWIN_DIS (1 << 6) +#define TBG_CNTRL_1_TGL_EN (1 << 2) +#define TBG_CNTRL_1_V_TGL (1 << 1) +#define TBG_CNTRL_1_H_TGL (1 << 0) + +#define TDA_HVF_CNTRL_0 MKREG(0x00, 0xe4) +#define HVF_CNTRL_0_PREFIL_NONE (0 << 2) +#define HVF_CNTRL_0_INTPOL_BYPASS (0 << 0) +#define TDA_HVF_CNTRL_1 MKREG(0x00, 0xe5) +#define HVF_CNTRL_1_VQR(x) (((x) & 3) << 2) +#define HVF_CNTRL_1_VQR_FULL HVF_CNTRL_1_VQR(0) +#define TDA_ENABLE_SPACE MKREG(0x00, 0xd6) +#define TDA_RPT_CNTRL MKREG(0x00, 0xf0) + +#define TDA_PLL_SERIAL_1 MKREG(0x02, 0x00) +#define PLL_SERIAL_1_SRL_MAN_IP (1 << 6) +#define TDA_PLL_SERIAL_2 MKREG(0x02, 0x01) +#define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4) +#define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 0x3) << 0) +#define TDA_PLL_SERIAL_3 MKREG(0x02, 0x02) +#define PLL_SERIAL_3_SRL_PXIN_SEL (1 << 4) +#define PLL_SERIAL_3_SRL_DE (1 << 2) +#define PLL_SERIAL_3_SRL_CCIR (1 << 0) +#define TDA_SERIALIZER MKREG(0x02, 0x03) +#define TDA_BUFFER_OUT MKREG(0x02, 0x04) +#define TDA_PLL_SCG1 MKREG(0x02, 0x05) +#define TDA_PLL_SCG2 MKREG(0x02, 0x06) +#define TDA_PLL_SCGN1 MKREG(0x02, 0x07) +#define TDA_PLL_SCGN2 MKREG(0x02, 0x08) +#define TDA_PLL_SCGR1 MKREG(0x02, 0x09) +#define TDA_PLL_SCGR2 MKREG(0x02, 0x0a) + +#define TDA_SEL_CLK MKREG(0x02, 0x11) +#define SEL_CLK_ENA_SC_CLK (1 << 3) +#define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1) +#define SEL_CLK_SEL_CLK1 (1 << 0) +#define TDA_ANA_GENERAL MKREG(0x02, 0x12) + +#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_IF_VSP MKREG(0x10, 0x20) +#define TDA_IF_AVI MKREG(0x10, 0x40) +#define TDA_IF_SPD MKREG(0x10, 0x60) +#define TDA_IF_AUD MKREG(0x10, 0x80) +#define TDA_IF_MPS MKREG(0x10, 0xa0) + +#define TDA_ENC_CNTRL MKREG(0x11, 0x0d) +#define ENC_CNTRL_DVI_MODE (0 << 2) +#define ENC_CNTRL_HDMI_MODE (1 << 2) +#define TDA_DIP_IF_FLAGS MKREG(0x11, 0x0f) +#define DIP_IF_FLAGS_IF5 (1 << 5) +#define DIP_IF_FLAGS_IF4 (1 << 4) +#define DIP_IF_FLAGS_IF3 (1 << 3) +#define DIP_IF_FLAGS_IF2 (1 << 2) /* AVI IF on page 10h */ +#define DIP_IF_FLAGS_IF1 (1 << 1) + + +#define TDA_TX3 MKREG(0x12, 0x9a) +#define TDA_TX4 MKREG(0x12, 0x9b) +#define TX4_PD_RAM (1 << 1) +#define TDA_HDCP_TX33 MKREG(0x12, 0xb8) +#define HDCP_TX33_HDMI (1 << 1) + +#define TDA_CURPAGE_ADDR 0xff + +#define TDA_CEC_ENAMODS 0xff +#define ENAMODS_RXSENS (1 << 2) +#define ENAMODS_HDMI (1 << 1) +#define TDA_CEC_FRO_IM_CLK_CTRL 0xfb +#define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7) +#define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1) + +/* EDID reading */ +#define EDID_LENGTH 0x80 +#define MAX_READ_ATTEMPTS 100 + +/* EDID fields */ +#define EDID_MODES0 35 +#define EDID_MODES1 36 +#define EDID_TIMING_START 38 +#define EDID_TIMING_END 54 +#define EDID_TIMING_X(v) (((v) + 31) * 8) +#define EDID_FREQ(v) (((v) & 0x3f) + 60) +#define EDID_RATIO(v) (((v) >> 6) & 0x3) +#define EDID_RATIO_10x16 0 +#define EDID_RATIO_3x4 1 +#define EDID_RATIO_4x5 2 +#define EDID_RATIO_9x16 3 + +struct drm_display_mode { + char name[32]; + + unsigned int type; + + /* Proposed mode values */ + int clock; /* in kHz */ + int hdisplay; + int hsync_start; + int hsync_end; + int htotal; + int hskew; + int vdisplay; + int vsync_start; + int vsync_end; + int vtotal; + int vscan; + unsigned int flags; +}; + +#define DRM_MODE_FLAG_NHSYNC (1 << 0) +#define DRM_MODE_FLAG_NVSYNC (1 << 1) +#define DRM_MODE_FLAG_INTERLACE (1 << 2) +#define DRM_MODE_FLAG_HSKEW (1 << 3) + +#define DRM_MODE(nm, t, c, hd, hss, hse, ht, hsk, vd, vss, vse, vt, vs, f) \ + .name = nm, .type = (t), .clock = (c), \ + .hdisplay = (hd), .hsync_start = (hss), .hsync_end = (hse), \ + .htotal = (ht), .hskew = (hsk), .vdisplay = (vd), \ + .vsync_start = (vss), .vsync_end = (vse), .vtotal = (vt), \ + .vscan = (vs), .flags = (f) + +/* + * This is standard 640x480@60Hz mode timing with HSKEW to + * compensate for inability of TI LCDC to generate VESA timing: TDA19988 + * detects frame start by deecting rising VSYNC and rising HSYNC signals but + * LCDC can not provide it + */ +static struct drm_display_mode mode_std640x480 = + { DRM_MODE("640x480", 0, 25175, 640, 656, + 752, 800, (752 - 656), 480, 490, 492, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_HSKEW) }; + +#define TDA19988 0x0301 + +struct tda19988_softc { + device_t sc_dev; + uint32_t sc_addr; + uint32_t sc_cec_addr; + uint16_t sc_version; + struct intr_config_hook enum_hook; + int sc_current_page; + uint8_t *sc_edid; + uint32_t sc_edid_len; + // struct edid_info *sc_edid_info; +}; + +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) +{ + int result; + struct iic_msg msg[] = { + { sc->sc_cec_addr, IIC_M_WR, 1, &addr }, + { sc->sc_cec_addr, IIC_M_RD, 1, data }, + }; + + result = iicbus_transfer(sc->sc_dev, msg, 2); + if (result) + printf("tda19988_cec_read failed: %d\n", result); + return (result); +} + +static int +tda19988_cec_write(struct tda19988_softc *sc, uint8_t address, uint8_t data) +{ + uint8_t cmd[2]; + int result; + struct iic_msg msg[] = { + { sc->sc_cec_addr, IIC_M_WR, 2, cmd }, + }; + + cmd[0] = address; + cmd[1] = data; + + result = iicbus_transfer(sc->sc_dev, msg, 1); + if (result) + printf("tda19988_cec_write failed: %d\n", result); + return (result); +} + +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,tda998x")) + return (ENXIO); + + return (BUS_PROBE_DEFAULT); +} + +static void +tda19988_init_encoder(struct tda19988_softc *sc, struct drm_display_mode *mode) +{ + uint16_t ref_pix, ref_line, n_pix, n_line; + uint16_t hs_pix_start, hs_pix_stop; + uint16_t vs1_pix_start, vs1_pix_stop; + uint16_t vs1_line_start, vs1_line_end; + uint16_t vs2_pix_start, vs2_pix_stop; + uint16_t vs2_line_start, vs2_line_end; + uint16_t vwin1_line_start, vwin1_line_end; + uint16_t vwin2_line_start, vwin2_line_end; + uint16_t de_start, de_stop; + uint8_t reg, div; + + 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 & DRM_MODE_FLAG_HSKEW) + ref_pix += mode->hskew; + + if ((mode->flags & DRM_MODE_FLAG_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->clock; + if (div != 0) { + div--; + if (div > 3) + div = 3; + } + + /* set HDMI HDCP mode off */ + tda19988_reg_set(sc, TDA_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS); + tda19988_reg_clear(sc, TDA_HDCP_TX33, HDCP_TX33_HDMI); + tda19988_reg_write(sc, TDA_ENC_CNTRL, ENC_CNTRL_DVI_MODE); + + /* no pre-filter or interpolator */ + tda19988_reg_write(sc, TDA_HVF_CNTRL_0, + HVF_CNTRL_0_INTPOL_BYPASS | HVF_CNTRL_0_PREFIL_NONE); + tda19988_reg_write(sc, TDA_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0)); + tda19988_reg_write(sc, TDA_VIP_CNTRL_4, + VIP_CNTRL_4_BLANKIT_NDE | VIP_CNTRL_4_BLC_NONE); + + tda19988_reg_clear(sc, TDA_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR); + tda19988_reg_clear(sc, TDA_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IP); + tda19988_reg_clear(sc, TDA_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE); + tda19988_reg_write(sc, TDA_SERIALIZER, 0); + tda19988_reg_write(sc, TDA_HVF_CNTRL_1, HVF_CNTRL_1_VQR_FULL); + + tda19988_reg_write(sc, TDA_RPT_CNTRL, 0); + tda19988_reg_write(sc, TDA_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) | + SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); + + tda19988_reg_write(sc, TDA_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) | + PLL_SERIAL_2_SRL_PR(0)); + + tda19988_reg_set(sc, TDA_MAT_CONTRL, MAT_CONTRL_MAT_BP); + + tda19988_reg_write(sc, TDA_ANA_GENERAL, 0x09); + + tda19988_reg_clear(sc, TDA_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD); + + /* + * Sync on rising HSYNC/VSYNC + */ + reg = VIP_CNTRL_3_SYNC_HS; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + reg |= VIP_CNTRL_3_H_TGL; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + reg |= VIP_CNTRL_3_V_TGL; + tda19988_reg_write(sc, TDA_VIP_CNTRL_3, reg); + + reg = TBG_CNTRL_1_TGL_EN; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + reg |= TBG_CNTRL_1_H_TGL; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + reg |= TBG_CNTRL_1_V_TGL; + tda19988_reg_write(sc, TDA_TBG_CNTRL_1, reg); + + /* Program timing */ + tda19988_reg_write(sc, TDA_VIDFORMAT, 0x00); + + tda19988_reg_write2(sc, TDA_REFPIX_MSB, ref_pix); + tda19988_reg_write2(sc, TDA_REFLINE_MSB, ref_line); + tda19988_reg_write2(sc, TDA_NPIX_MSB, n_pix); + tda19988_reg_write2(sc, TDA_NLINE_MSB, n_line); + + tda19988_reg_write2(sc, TDA_VS_LINE_STRT_1_MSB, vs1_line_start); + tda19988_reg_write2(sc, TDA_VS_PIX_STRT_1_MSB, vs1_pix_start); + tda19988_reg_write2(sc, TDA_VS_LINE_END_1_MSB, vs1_line_end); + tda19988_reg_write2(sc, TDA_VS_PIX_END_1_MSB, vs1_pix_stop); + tda19988_reg_write2(sc, TDA_VS_LINE_STRT_2_MSB, vs2_line_start); + tda19988_reg_write2(sc, TDA_VS_PIX_STRT_2_MSB, vs2_pix_start); + tda19988_reg_write2(sc, TDA_VS_LINE_END_2_MSB, vs2_line_end); + tda19988_reg_write2(sc, TDA_VS_PIX_END_2_MSB, vs2_pix_stop); + tda19988_reg_write2(sc, TDA_HS_PIX_START_MSB, hs_pix_start); + tda19988_reg_write2(sc, TDA_HS_PIX_STOP_MSB, hs_pix_stop); + tda19988_reg_write2(sc, TDA_VWIN_START_1_MSB, vwin1_line_start); + tda19988_reg_write2(sc, TDA_VWIN_END_1_MSB, vwin1_line_end); + tda19988_reg_write2(sc, TDA_VWIN_START_2_MSB, vwin2_line_start); + tda19988_reg_write2(sc, TDA_VWIN_END_2_MSB, vwin2_line_end); + tda19988_reg_write2(sc, TDA_DE_START_MSB, de_start); + tda19988_reg_write2(sc, TDA_DE_STOP_MSB, de_stop); + + if (sc->sc_version == TDA19988) + tda19988_reg_write(sc, TDA_ENABLE_SPACE, 0x00); + + /* must be last register set */ + tda19988_reg_clear(sc, TDA_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE); +} + + +static void +tda19988_init_encoder_videomode(struct tda19988_softc *sc, const struct videomode *mode) +{ + uint16_t ref_pix, ref_line, n_pix, n_line; + uint16_t hs_pix_start, hs_pix_stop; + uint16_t vs1_pix_start, vs1_pix_stop; + uint16_t vs1_line_start, vs1_line_end; + uint16_t vs2_pix_start, vs2_pix_stop; + uint16_t vs2_line_start, vs2_line_end; + uint16_t vwin1_line_start, vwin1_line_end; + uint16_t vwin2_line_start, vwin2_line_end; + uint16_t de_start, de_stop; + uint8_t reg, div; + + n_pix = mode->htotal; + n_line = mode->vtotal; + + hs_pix_stop = mode->hsync_end - mode->hdisplay; + hs_pix_start = mode->hsync_start - mode->hdisplay; + + de_stop = mode->htotal; + de_start = mode->htotal - mode->hdisplay; + ref_pix = hs_pix_start + 3; + + if (mode->flags & VID_HSKEW) + ref_pix += mode->hskew; + + if ((mode->flags & VID_INTERLACE) == 0) { + ref_line = 1 + mode->vsync_start - mode->vdisplay; + vwin1_line_start = mode->vtotal - mode->vdisplay - 1; + vwin1_line_end = vwin1_line_start + mode->vdisplay; + + vs1_pix_start = vs1_pix_stop = hs_pix_start; + vs1_line_start = mode->vsync_start - mode->vdisplay; + vs1_line_end = vs1_line_start + mode->vsync_end - mode->vsync_start; + + vwin2_line_start = vwin2_line_end = 0; + vs2_pix_start = vs2_pix_stop = 0; + vs2_line_start = vs2_line_end = 0; + } else { + ref_line = 1 + (mode->vsync_start - mode->vdisplay)/2; + vwin1_line_start = (mode->vtotal - mode->vdisplay)/2; + vwin1_line_end = vwin1_line_start + mode->vdisplay/2; + + vs1_pix_start = vs1_pix_stop = hs_pix_start; + vs1_line_start = (mode->vsync_start - mode->vdisplay)/2; + vs1_line_end = vs1_line_start + (mode->vsync_end - mode->vsync_start)/2; + + vwin2_line_start = vwin1_line_start + mode->vtotal/2; + vwin2_line_end = vwin2_line_start + mode->vdisplay/2; + + vs2_pix_start = vs2_pix_stop = hs_pix_start + mode->htotal/2; + vs2_line_start = vs1_line_start + mode->vtotal/2 ; + vs2_line_end = vs2_line_start + (mode->vsync_end - mode->vsync_start)/2; + } + + div = 148500 / mode->dot_clock; + if (div != 0) { + div--; + if (div > 3) + div = 3; + } + + /* set HDMI HDCP mode off */ + tda19988_reg_set(sc, TDA_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS); + tda19988_reg_clear(sc, TDA_HDCP_TX33, HDCP_TX33_HDMI); + tda19988_reg_write(sc, TDA_ENC_CNTRL, ENC_CNTRL_DVI_MODE); + + /* no pre-filter or interpolator */ + tda19988_reg_write(sc, TDA_HVF_CNTRL_0, + HVF_CNTRL_0_INTPOL_BYPASS | HVF_CNTRL_0_PREFIL_NONE); + tda19988_reg_write(sc, TDA_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0)); + tda19988_reg_write(sc, TDA_VIP_CNTRL_4, + VIP_CNTRL_4_BLANKIT_NDE | VIP_CNTRL_4_BLC_NONE); + + tda19988_reg_clear(sc, TDA_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR); + tda19988_reg_clear(sc, TDA_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IP); + tda19988_reg_clear(sc, TDA_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE); + tda19988_reg_write(sc, TDA_SERIALIZER, 0); + tda19988_reg_write(sc, TDA_HVF_CNTRL_1, HVF_CNTRL_1_VQR_FULL); + + tda19988_reg_write(sc, TDA_RPT_CNTRL, 0); + tda19988_reg_write(sc, TDA_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) | + SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); + + tda19988_reg_write(sc, TDA_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) | + PLL_SERIAL_2_SRL_PR(0)); + + tda19988_reg_set(sc, TDA_MAT_CONTRL, MAT_CONTRL_MAT_BP); + + tda19988_reg_write(sc, TDA_ANA_GENERAL, 0x09); + + tda19988_reg_clear(sc, TDA_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD); + + /* + * Sync on rising HSYNC/VSYNC + */ + reg = VIP_CNTRL_3_SYNC_HS; + if (mode->flags & VID_NHSYNC) + reg |= VIP_CNTRL_3_H_TGL; + if (mode->flags & VID_NVSYNC) + reg |= VIP_CNTRL_3_V_TGL; + tda19988_reg_write(sc, TDA_VIP_CNTRL_3, reg); + + reg = TBG_CNTRL_1_TGL_EN; + if (mode->flags & VID_NHSYNC) + reg |= TBG_CNTRL_1_H_TGL; + if (mode->flags & VID_NVSYNC) + reg |= TBG_CNTRL_1_V_TGL; + tda19988_reg_write(sc, TDA_TBG_CNTRL_1, reg); + + /* Program timing */ + tda19988_reg_write(sc, TDA_VIDFORMAT, 0x00); + + tda19988_reg_write2(sc, TDA_REFPIX_MSB, ref_pix); + tda19988_reg_write2(sc, TDA_REFLINE_MSB, ref_line); + tda19988_reg_write2(sc, TDA_NPIX_MSB, n_pix); + tda19988_reg_write2(sc, TDA_NLINE_MSB, n_line); + + tda19988_reg_write2(sc, TDA_VS_LINE_STRT_1_MSB, vs1_line_start); + tda19988_reg_write2(sc, TDA_VS_PIX_STRT_1_MSB, vs1_pix_start); + tda19988_reg_write2(sc, TDA_VS_LINE_END_1_MSB, vs1_line_end); + tda19988_reg_write2(sc, TDA_VS_PIX_END_1_MSB, vs1_pix_stop); + tda19988_reg_write2(sc, TDA_VS_LINE_STRT_2_MSB, vs2_line_start); + tda19988_reg_write2(sc, TDA_VS_PIX_STRT_2_MSB, vs2_pix_start); + tda19988_reg_write2(sc, TDA_VS_LINE_END_2_MSB, vs2_line_end); + tda19988_reg_write2(sc, TDA_VS_PIX_END_2_MSB, vs2_pix_stop); + tda19988_reg_write2(sc, TDA_HS_PIX_START_MSB, hs_pix_start); + tda19988_reg_write2(sc, TDA_HS_PIX_STOP_MSB, hs_pix_stop); + tda19988_reg_write2(sc, TDA_VWIN_START_1_MSB, vwin1_line_start); + tda19988_reg_write2(sc, TDA_VWIN_END_1_MSB, vwin1_line_end); + tda19988_reg_write2(sc, TDA_VWIN_START_2_MSB, vwin2_line_start); + tda19988_reg_write2(sc, TDA_VWIN_END_2_MSB, vwin2_line_end); + tda19988_reg_write2(sc, TDA_DE_START_MSB, de_start); + tda19988_reg_write2(sc, TDA_DE_STOP_MSB, de_stop); + + if (sc->sc_version == TDA19988) + tda19988_reg_write(sc, TDA_ENABLE_SPACE, 0x00); + + /* must be last register set */ + tda19988_reg_clear(sc, TDA_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE); +} + + + +static int +tda19988_read_edid_block(struct tda19988_softc *sc, uint8_t *buf, int block) +{ + int attempt, err; + uint8_t data; + + err = 0; + + tda19988_reg_set(sc, TDA_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + + /* Block 0 */ + tda19988_reg_write(sc, TDA_DDC_ADDR, 0xa0); + tda19988_reg_write(sc, TDA_DDC_OFFS, (block % 2) ? 128 : 0); + tda19988_reg_write(sc, TDA_DDC_SEGM_ADDR, 0x60); + tda19988_reg_write(sc, TDA_DDC_SEGM, block / 2); + + 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_FLAGS_2, &data); + if (data & INT_FLAGS_2_EDID_BLK_RD) + break; + pause("EDID", 1); + } + + if (attempt == MAX_READ_ATTEMPTS) { + err = -1; + goto done; + } + + if (tda19988_block_read(sc, TDA_EDID_DATA0, buf, EDID_LENGTH) != 0) { + err = -1; + goto done; + } + +done: + tda19988_reg_clear(sc, TDA_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + + return (err); +} + +static int +tda19988_read_edid(struct tda19988_softc *sc) +{ + int err; + int blocks, i; + uint8_t *buf; + + err = 0; + if (sc->sc_version == TDA19988) + tda19988_reg_clear(sc, TDA_TX4, TX4_PD_RAM); + + err = tda19988_read_edid_block(sc, sc->sc_edid, 0); + if (err) + goto done; + + blocks = sc->sc_edid[0x7e]; + if (blocks > 0) { + sc->sc_edid = realloc(sc->sc_edid, + EDID_LENGTH*(blocks+1), M_DEVBUF, M_WAITOK); + sc->sc_edid_len = EDID_LENGTH*(blocks+1); + for (i = 0; i < blocks; i++) { + /* TODO: check validity */ + buf = sc->sc_edid + EDID_LENGTH*(i+1); + err = tda19988_read_edid_block(sc, buf, i); + if (err) + goto done; + } + } + +#if 0 + for (i = 0; i < EDID_LENGTH; i++) { + printf("%02x ", sc->sc_edid[i]); + if ((i % 16) == 15) { + printf("\n"); + } + } +#endif + + EVENTHANDLER_INVOKE(hdmi_event, 0); + +done: + if (sc->sc_version == TDA19988) + tda19988_reg_set(sc, TDA_TX4, TX4_PD_RAM); + + return (err); +} + +static void +tda19988_start(void *xdev) +{ + struct tda19988_softc *sc; + device_t dev = (device_t)xdev; + uint8_t data; + uint16_t version; + + sc = device_get_softc(dev); + + tda19988_cec_write(sc, TDA_CEC_ENAMODS, ENAMODS_RXSENS | ENAMODS_HDMI); + DELAY(1000); + tda19988_cec_read(sc, 0xfe, &data); + + /* Reset core */ + tda19988_reg_set(sc, TDA_SOFTRESET, 3); + DELAY(100); + tda19988_reg_clear(sc, TDA_SOFTRESET, 3); + DELAY(100); + + /* reset transmitter: */ + tda19988_reg_set(sc, TDA_MAIN_CNTRL0, MAIN_CNTRL0_SR); + tda19988_reg_clear(sc, TDA_MAIN_CNTRL0, MAIN_CNTRL0_SR); + + /* PLL registers common configuration */ + tda19988_reg_write(sc, TDA_PLL_SERIAL_1, 0x00); + tda19988_reg_write(sc, TDA_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1)); + tda19988_reg_write(sc, TDA_PLL_SERIAL_3, 0x00); + tda19988_reg_write(sc, TDA_SERIALIZER, 0x00); + tda19988_reg_write(sc, TDA_BUFFER_OUT, 0x00); + tda19988_reg_write(sc, TDA_PLL_SCG1, 0x00); + tda19988_reg_write(sc, TDA_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); + tda19988_reg_write(sc, TDA_PLL_SCGN1, 0xfa); + tda19988_reg_write(sc, TDA_PLL_SCGN2, 0x00); + tda19988_reg_write(sc, TDA_PLL_SCGR1, 0x5b); + tda19988_reg_write(sc, TDA_PLL_SCGR2, 0x00); + tda19988_reg_write(sc, TDA_PLL_SCG2, 0x10); + + /* Write the default value MUX register */ + tda19988_reg_write(sc, TDA_MUX_VP_VIP_OUT, 0x24); + + version = 0; + tda19988_reg_read(sc, TDA_VERSION, &data); + version |= data; + tda19988_reg_read(sc, TDA_VERSION_MSB, &data); + version |= (data << 8); + + /* Clear feature bits */ + sc->sc_version = version & ~0x30; + switch (sc->sc_version) { + case TDA19988: + device_printf(dev, "TDA19988\n"); + break; + default: + device_printf(dev, "Unknown device: %04x\n", sc->sc_version); + goto done; + } + + tda19988_reg_write(sc, TDA_DDC_CTRL, DDC_ENABLE); + tda19988_reg_write(sc, TDA_TX3, 39); + + tda19988_cec_write(sc, TDA_CEC_FRO_IM_CLK_CTRL, + CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL); + + if (tda19988_read_edid(sc) < 0) { + device_printf(dev, "failed to read EDID\n"); + goto done; + } + + if (0) + tda19988_init_encoder(sc, &mode_std640x480); + + /* Default values for RGB 4:4:4 mapping */ + tda19988_reg_write(sc, TDA_VIP_CNTRL_0, 0x23); + tda19988_reg_write(sc, TDA_VIP_CNTRL_1, 0x45); + tda19988_reg_write(sc, TDA_VIP_CNTRL_2, 0x01); + +done: + config_intrhook_disestablish(&sc->enum_hook); +} + +static int +tda19988_attach(device_t dev) +{ + struct tda19988_softc *sc; + phandle_t node; + + sc = device_get_softc(dev); + + sc->sc_dev = dev; + sc->sc_addr = iicbus_get_addr(dev) << 1; + sc->sc_cec_addr = (0x34 << 1); /* hardcoded */ + sc->sc_edid = malloc(EDID_LENGTH, M_DEVBUF, M_WAITOK | M_ZERO); + sc->sc_edid_len = EDID_LENGTH; + + 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); + + node = ofw_bus_get_node(dev); + OF_device_register_xref(OF_xref_from_node(node), dev); + + return (0); +} + +static int +tda19988_detach(device_t dev) +{ + + /* XXX: Do not let unload drive */ + return (EBUSY); +} + +static int +tda19988_get_edid(device_t dev, uint8_t **edid, uint32_t *edid_len) +{ + struct tda19988_softc *sc; + + sc = device_get_softc(dev); + + if (sc->sc_edid) { + *edid = sc->sc_edid; + *edid_len = sc->sc_edid_len; + } else + return (ENXIO); + + return (0); +} + + +static int +tda19988_set_videomode(device_t dev, const struct videomode *mode) +{ + struct tda19988_softc *sc; + + sc = device_get_softc(dev); + + tda19988_init_encoder_videomode(sc, mode); + + return (0); +} + +static device_method_t tda_methods[] = { + DEVMETHOD(device_probe, tda19988_probe), + DEVMETHOD(device_attach, tda19988_attach), + DEVMETHOD(device_detach, tda19988_detach), + + /* HDMI methods */ + DEVMETHOD(hdmi_get_edid, tda19988_get_edid), + DEVMETHOD(hdmi_set_videomode, tda19988_set_videomode), + {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); diff --git a/sys/arm/ti/ti_prcm.c b/sys/arm/ti/ti_prcm.c index 3c073f9..c5ab9c7 100644 --- a/sys/arm/ti/ti_prcm.c +++ b/sys/arm/ti/ti_prcm.c @@ -285,7 +285,7 @@ ti_prcm_clk_set_source(clk_ident_t clk, clk_src_t clksrc) * @clk: identifier for the module to enable, see ti_prcm.h for a list * of possible modules. * @freq: pointer to an integer that upon return will contain the src freq - * + * * This function returns the frequency of the source clock. * * The real work done to enable the clock is really done in the callback @@ -319,6 +319,39 @@ ti_prcm_clk_get_source_freq(clk_ident_t clk, unsigned int *freq) ret = clk_dev->clk_get_source_freq(clk_dev, freq); else ret = EINVAL; - + + return (ret); +} + +/** + * ti_prcm_clk_set_source_freq - sets the source clock frequency as close to freq as possible + * @clk: identifier for the module to enable, see ti_prcm.h for a list + * of possible modules. + * @freq: requested freq + * + * LOCKING: + * Internally locks the driver context. + * + * RETURNS: + * Returns 0 on success or positive error code on failure. + */ +int +ti_prcm_clk_set_source_freq(clk_ident_t clk, unsigned int freq) +{ + struct ti_clock_dev *clk_dev; + int ret; + + clk_dev = ti_prcm_clk_dev(clk); + + /* Sanity check we managed to find the clock */ + if (clk_dev == NULL) + return (EINVAL); + + /* Get the source frequency of the clock */ + if (clk_dev->clk_set_source_freq) + ret = clk_dev->clk_set_source_freq(clk_dev, freq); + else + ret = EINVAL; + return (ret); } diff --git a/sys/arm/ti/ti_prcm.h b/sys/arm/ti/ti_prcm.h index eaea990..c40439a 100644 --- a/sys/arm/ti/ti_prcm.h +++ b/sys/arm/ti/ti_prcm.h @@ -184,6 +184,8 @@ struct ti_clock_dev { int (*clk_set_source)(struct ti_clock_dev *clkdev, clk_src_t clksrc); int (*clk_accessible)(struct ti_clock_dev *clkdev); + int (*clk_set_source_freq)(struct ti_clock_dev *clkdev, + unsigned int freq); int (*clk_get_source_freq)(struct ti_clock_dev *clkdev, unsigned int *freq); }; @@ -194,6 +196,7 @@ int ti_prcm_clk_disable(clk_ident_t clk); int ti_prcm_clk_accessible(clk_ident_t clk); int ti_prcm_clk_disable_autoidle(clk_ident_t clk); int ti_prcm_clk_set_source(clk_ident_t clk, clk_src_t clksrc); +int ti_prcm_clk_set_source_freq(clk_ident_t clk, unsigned int freq); int ti_prcm_clk_get_source_freq(clk_ident_t clk, unsigned int *freq); void ti_prcm_reset(void); diff --git a/sys/boot/fdt/dts/arm/beaglebone-black.dts b/sys/boot/fdt/dts/arm/beaglebone-black.dts index f5144dd..21cbf83 100644 --- a/sys/boot/fdt/dts/arm/beaglebone-black.dts +++ b/sys/boot/fdt/dts/arm/beaglebone-black.dts @@ -29,3 +29,25 @@ #include "am335x-boneblack.dts" #include "beaglebone-common.dtsi" + +&i2c0 { + tda998x: hdmi-encoder { + compatible = "nxp,tda998x"; + reg = <0x70>; + + pinctrl-names = "default", "off"; + pinctrl-0 = <&nxp_hdmi_bonelt_pins>; + pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>; + status = "okay"; + }; +}; + +&lcdc { + hdmi = <&tda998x>; +}; + +/ { + hdmi { + status = "disabled"; + }; +}; diff --git a/sys/dev/videomode/videomode.h b/sys/dev/videomode/videomode.h index b223da8..3e64444 100644 --- a/sys/dev/videomode/videomode.h +++ b/sys/dev/videomode/videomode.h @@ -43,6 +43,7 @@ struct videomode { int vtotal; int flags; /* Video mode flags; see below. */ const char *name; + int hskew; }; /*