Index: head/sys/mips/conf/JZ4780 =================================================================== --- head/sys/mips/conf/JZ4780 +++ head/sys/mips/conf/JZ4780 @@ -86,6 +86,16 @@ device dme +device iic +device iicbus + +# Framebuffer console support +device vt +device kbdmux +device hdmi +device videomode +device pty + # USB support options USB_DEBUG # enable debug msgs options USB_HOST_ALIGN=128 # L2 cache line size @@ -95,6 +105,7 @@ device usb # USB Bus (required) #device udbp # USB Double Bulk Pipe devices device uhid # "Human Interface Devices" +device ukbd # Allow keyboard like HIDs to control console #device ulpt # Printer device umass # Disks/Mass storage - Requires scbus and da device ums # Mouse Index: head/sys/mips/ingenic/files.jz4780 =================================================================== --- head/sys/mips/ingenic/files.jz4780 +++ head/sys/mips/ingenic/files.jz4780 @@ -6,6 +6,9 @@ mips/ingenic/jz4780_ohci.c optional ohci mips/ingenic/jz4780_smb.c optional iicbus mips/ingenic/jz4780_uart.c optional uart +mips/ingenic/jz4780_lcd.c optional vt +dev/hdmi/dwc_hdmi.c optional hdmi iicbus +dev/hdmi/dwc_hdmi_fdt.c optional hdmi iicbus mips/ingenic/jz4780_clock.c standard mips/ingenic/jz4780_clk_gen.c standard @@ -25,3 +28,6 @@ # Custom interface between pinctrl and gpio mips/ingenic/jz4780_gpio_if.m standard + +# HDMI interface +dev/hdmi/hdmi_if.m standard Index: head/sys/mips/ingenic/jz4780_lcd.h =================================================================== --- head/sys/mips/ingenic/jz4780_lcd.h +++ head/sys/mips/ingenic/jz4780_lcd.h @@ -0,0 +1,204 @@ +/*- + * Copyright (c) 2016 Jared McNeill + * 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 ``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 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. + * + * $FreeBSD$ + */ + +/* + * Ingenic JZ4780 LCD Controller + */ + +#ifndef __JZ4780_LCD_H__ +#define __JZ4780_LCD_H__ + +#define LCDCFG 0x0000 +#define LCDCFG_LCDPIN (1 << 31) +#define LCDCFG_TVEPEH (1 << 30) +#define LCDCFG_NEWDES (1 << 28) +#define LCDCFG_PALBP (1 << 27) +#define LCDCFG_TVEN (1 << 26) +#define LCDCFG_RECOVER (1 << 25) +#define LCDCFG_PSM (1 << 23) +#define LCDCFG_CLSM (1 << 22) +#define LCDCFG_SPLM (1 << 21) +#define LCDCFG_REVM (1 << 20) +#define LCDCFG_HSYNM (1 << 19) +#define LCDCFG_VSYNM (1 << 18) +#define LCDCFG_INVDAT (1 << 17) +#define LCDCFG_SYNDIR (1 << 16) +#define LCDCFG_PSP (1 << 15) +#define LCDCFG_CLSP (1 << 14) +#define LCDCFG_SPLP (1 << 13) +#define LCDCFG_REVP (1 << 12) +#define LCDCFG_HSP (1 << 11) +#define LCDCFG_PCP (1 << 10) +#define LCDCFG_DEP (1 << 9) +#define LCDCFG_VSP (1 << 8) +#define LCDCFG_18_16 (1 << 7) +#define LCDCFG_24 (1 << 6) +#define LCDCFG_MODE (0xf << 0) +#define LCDCTRL 0x0030 +#define LCDCTRL_PINMD (1 << 31) +#define LCDCTRL_BST (0x7 << 28) +#define LCDCTRL_BST_4 (0 << 28) +#define LCDCTRL_BST_8 (1 << 28) +#define LCDCTRL_BST_16 (2 << 28) +#define LCDCTRL_BST_32 (3 << 28) +#define LCDCTRL_BST_64 (4 << 28) +#define LCDCTRL_OUTRGB (1 << 27) +#define LCDCTRL_OFUP (1 << 26) +#define LCDCTRL_DACTE (1 << 14) +#define LCDCTRL_EOFM (1 << 13) +#define LCDCTRL_SOFM (1 << 12) +#define LCDCTRL_OFUM (1 << 11) +#define LCDCTRL_IFUM0 (1 << 10) +#define LCDCTRL_IFUM1 (1 << 9) +#define LCDCTRL_LDDM (1 << 8) +#define LCDCTRL_QDM (1 << 7) +#define LCDCTRL_BEDN (1 << 6) +#define LCDCTRL_PEDN (1 << 5) +#define LCDCTRL_DIS (1 << 4) +#define LCDCTRL_ENA (1 << 3) +#define LCDCTRL_BPP0 (0x7 << 0) +#define LCDCTRL_BPP0_1 (0 << 0) +#define LCDCTRL_BPP0_2 (1 << 0) +#define LCDCTRL_BPP0_4 (2 << 0) +#define LCDCTRL_BPP0_8 (3 << 0) +#define LCDCTRL_BPP0_15_16 (4 << 0) +#define LCDCTRL_BPP0_18_24 (5 << 0) +#define LCDCTRL_BPP0_24_COMP (6 << 0) +#define LCDCTRL_BPP0_30 (7 << 0) +#define LCDCTR +#define LCDSTATE 0x0034 +#define LCDSTATE_QD (1 << 7) +#define LCDSTATE_EOF (1 << 5) +#define LCDSTATE_SOF (1 << 4) +#define LCDSTATE_OUT (1 << 3) +#define LCDSTATE_IFU0 (1 << 2) +#define LCDSTATE_IFU1 (1 << 1) +#define LCDSTATE_LDD (1 << 0) +#define LCDOSDC 0x0100 +#define LCDOSDCTRL 0x0104 +#define LCDOSDS 0x0108 +#define LCDBGC0 0x010c +#define LCDBGC1 0x02c4 +#define LCDKEY0 0x0110 +#define LCDKEY1 0x0114 +#define LCDALPHA 0x0118 +#define LCDIPUR 0x011c +#define LCDRGBC 0x0090 +#define LCDRGBC_RGBDM (1 << 15) +#define LCDRGBC_DMM (1 << 14) +#define LCDRGBC_422 (1 << 8) +#define LCDRGBC_RGBFMT (1 << 7) +#define LCDRGBC_ODDRGB (0x7 << 4) +#define LCDRGBC_EVENRGB (0x7 << 0) +#define LCDVAT 0x000c +#define LCDVAT_HT_SHIFT 16 +#define LCDVAT_VT_SHIFT 0 +#define LCDDAH 0x0010 +#define LCDDAH_HDS_SHIFT 16 +#define LCDDAH_HDE_SHIFT 0 +#define LCDDAV 0x0014 +#define LCDDAV_VDS_SHIFT 16 +#define LCDDAV_VDE_SHIFT 0 +#define LCDXYP0 0x0120 +#define LCDXYP1 0x0124 +#define LCDSIZE0 0x0128 +#define LCDSIZE1 0x012c +#define LCDVSYNC 0x0004 +#define LCDHSYNC 0x0008 +#define LCDPS 0x0018 +#define LCDCLS 0x001c +#define LCDSPL 0x0020 +#define LCDREV 0x0024 +#define LCDIID 0x0038 +#define LCDDA0 0x0040 +#define LCDSA0 0x0044 +#define LCDFID0 0x0048 +#define LCDCMD0 0x004c +#define LCDCMD_SOFINT (1 << 31) +#define LCDCMD_EOFINT (1 << 30) +#define LCDCMD_CMD (1 << 29) +#define LCDCMD_COMPE (1 << 27) +#define LCDCMD_FRM_EN (1 << 26) +#define LCDCMD_FIELD_SEL (1 << 25) +#define LCDCMD_16X16BLOCK (1 << 24) +#define LCDCMD_LEN (0xffffff << 0) +#define LCDOFFS0 0x0060 +#define LCDPW0 0x0064 +#define LCDCNUM0 0x0068 +#define LCDPOS0 LCDCNUM0 +#define LCDPOS_ALPHAMD1 (1 << 31) +#define LCDPOS_RGB01 (1 << 30) +#define LCDPOS_BPP01 (0x7 << 27) +#define LCDPOS_BPP01_15_16 (4 << 27) +#define LCDPOS_BPP01_18_24 (5 << 27) +#define LCDPOS_BPP01_24_COMP (6 << 27) +#define LCDPOS_BPP01_30 (7 << 27) +#define LCDPOS_PREMULTI01 (1 << 26) +#define LCDPOS_COEF_SLE01 (0x3 << 24) +#define LCDPOS_COEF_BLE01_1 (1 << 24) +#define LCDPOS_YPOS01 (0xfff << 12) +#define LCDPOS_XPOS01 (0xfff << 0) +#define LCDDESSIZE0 0x006c +#define LCDDESSIZE_ALPHA (0xff << 24) +#define LCDDESSIZE_HEIGHT (0xfff << 12) +#define LCDDESSIZE_HEIGHT_SHIFT 12 +#define LCDDESSIZE_WIDTH (0xfff << 0) +#define LCDDESSIZE_WIDTH_SHIFT 0 +#define LCDDA1 0x0050 +#define LCDSA1 0x0054 +#define LCDFID1 0x0058 +#define LCDCMD1 0x005c +#define LCDOFFS1 0x0070 +#define LCDPW1 0x0074 +#define LCDCNUM1 0x0078 +#define LCDPOS1 LCDCNUM1 +#define LCDDESSIZE1 0x007c +#define LCDPCFG 0x02c0 +#define LCDDUALCTRL 0x02c8 +#define LCDENH_CFG 0x0400 +#define LCDENH_CSCCFG 0x0404 +#define LCDENH_LUMACFG 0x0408 +#define LCDENH_CHROCFG0 0x040c +#define LCDENH_CHROCFG1 0x0410 +#define LCDENH_DITHERCFG 0x0414 +#define LCDENH_STATUS 0x0418 +#define LCDENH_GAMMA 0x0800 /* base */ +#define LCDENH_VEE 0x1000 /* base */ + +struct lcd_frame_descriptor { + uint32_t next; + uint32_t physaddr; + uint32_t id; + uint32_t cmd; + uint32_t offs; + uint32_t pw; + uint32_t cnum_pos; + uint32_t dessize; +} __packed; + +#endif /* !__JZ4780_LCD_H__ */ Index: head/sys/mips/ingenic/jz4780_lcd.c =================================================================== --- head/sys/mips/ingenic/jz4780_lcd.c +++ head/sys/mips/ingenic/jz4780_lcd.c @@ -0,0 +1,572 @@ +/*- + * Copyright (c) 2016 Jared McNeill + * 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 ``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 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. + * + * $FreeBSD$ + */ + +/* + * Ingenic JZ4780 LCD Controller + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include + +#include + +#include "fb_if.h" +#include "hdmi_if.h" + +#define FB_DEFAULT_W 800 +#define FB_DEFAULT_H 600 +#define FB_DEFAULT_REF 60 +#define FB_BPP 32 +#define FB_ALIGN (16 * 4) +#define FB_MAX_BW (1920 * 1080 * 60) +#define FB_MAX_W 2048 +#define FB_MAX_H 2048 +#define FB_DIVIDE(x, y) (((x) + ((y) / 2)) / (y)) + +#define PCFG_MAGIC 0xc7ff2100 + +#define DOT_CLOCK_TO_HZ(c) ((c) * 1000) + +#ifndef VM_MEMATTR_WRITE_COMBINING +#define VM_MEMATTR_WRITE_COMBINING VM_MEMATTR_UNCACHEABLE +#endif + +struct jzlcd_softc { + device_t dev; + device_t fbdev; + struct resource *res[1]; + + /* Clocks */ + clk_t clk; + clk_t clk_pix; + + /* Framebuffer */ + struct fb_info info; + size_t fbsize; + bus_addr_t paddr; + vm_offset_t vaddr; + + /* HDMI */ + eventhandler_tag hdmi_evh; + + /* Frame descriptor DMA */ + bus_dma_tag_t fdesc_tag; + bus_dmamap_t fdesc_map; + bus_addr_t fdesc_paddr; + struct lcd_frame_descriptor *fdesc; +}; + +static struct resource_spec jzlcd_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { -1, 0 } +}; + +#define LCD_READ(sc, reg) bus_read_4((sc)->res[0], (reg)) +#define LCD_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) + +static int +jzlcd_allocfb(struct jzlcd_softc *sc) +{ + sc->vaddr = kmem_alloc_contig(kernel_arena, sc->fbsize, + M_NOWAIT | M_ZERO, 0, ~0, FB_ALIGN, 0, VM_MEMATTR_WRITE_COMBINING); + if (sc->vaddr == 0) { + device_printf(sc->dev, "failed to allocate FB memory\n"); + return (ENOMEM); + } + sc->paddr = pmap_kextract(sc->vaddr); + + return (0); +} + +static void +jzlcd_freefb(struct jzlcd_softc *sc) +{ + kmem_free(kernel_arena, sc->vaddr, sc->fbsize); +} + +static void +jzlcd_start(struct jzlcd_softc *sc) +{ + uint32_t ctrl; + + /* Clear status registers */ + LCD_WRITE(sc, LCDSTATE, 0); + LCD_WRITE(sc, LCDOSDS, 0); + /* Enable the controller */ + ctrl = LCD_READ(sc, LCDCTRL); + ctrl |= LCDCTRL_ENA; + ctrl &= ~LCDCTRL_DIS; + LCD_WRITE(sc, LCDCTRL, ctrl); +} + +static void +jzlcd_stop(struct jzlcd_softc *sc) +{ + uint32_t ctrl; + + ctrl = LCD_READ(sc, LCDCTRL); + if ((ctrl & LCDCTRL_ENA) != 0) { + /* Disable the controller and wait for it to stop */ + ctrl |= LCDCTRL_DIS; + LCD_WRITE(sc, LCDCTRL, ctrl); + while ((LCD_READ(sc, LCDSTATE) & LCDSTATE_LDD) == 0) + DELAY(100); + } + /* Clear all status except for disable */ + LCD_WRITE(sc, LCDSTATE, LCD_READ(sc, LCDSTATE) & ~LCDSTATE_LDD); +} + +static void +jzlcd_setup_descriptor(struct jzlcd_softc *sc, const struct videomode *mode, + u_int desno) +{ + struct lcd_frame_descriptor *fdesc; + int line_sz; + + /* Frame size is specified in # words */ + line_sz = (mode->hdisplay * FB_BPP) >> 3; + line_sz = ((line_sz + 3) & ~3) / 4; + + fdesc = sc->fdesc + desno; + + if (desno == 0) + fdesc->next = sc->fdesc_paddr + + sizeof(struct lcd_frame_descriptor); + else + fdesc->next = sc->fdesc_paddr; + fdesc->physaddr = sc->paddr; + fdesc->id = desno; + fdesc->cmd = LCDCMD_FRM_EN | (line_sz * mode->vdisplay); + fdesc->offs = 0; + fdesc->pw = 0; + fdesc->cnum_pos = LCDPOS_BPP01_18_24 | + LCDPOS_PREMULTI01 | + (desno == 0 ? LCDPOS_COEF_BLE01_1 : LCDPOS_COEF_SLE01); + fdesc->dessize = LCDDESSIZE_ALPHA | + ((mode->vdisplay - 1) << LCDDESSIZE_HEIGHT_SHIFT) | + ((mode->hdisplay - 1) << LCDDESSIZE_WIDTH_SHIFT); +} + +static int +jzlcd_set_videomode(struct jzlcd_softc *sc, const struct videomode *mode) +{ + u_int hbp, hfp, hsw, vbp, vfp, vsw; + u_int hds, hde, ht, vds, vde, vt; + uint32_t ctrl; + int error; + + hbp = mode->htotal - mode->hsync_end; + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + vbp = mode->vtotal - mode->vsync_end; + vfp = mode->vsync_start - mode->vdisplay; + vsw = mode->vsync_end - mode->vsync_start; + + hds = hsw + hbp; + hde = hds + mode->hdisplay; + ht = hde + hfp; + + vds = vsw + vbp; + vde = vds + mode->vdisplay; + vt = vde + vfp; + + /* Setup timings */ + LCD_WRITE(sc, LCDVAT, + (ht << LCDVAT_HT_SHIFT) | (vt << LCDVAT_VT_SHIFT)); + LCD_WRITE(sc, LCDDAH, + (hds << LCDDAH_HDS_SHIFT) | (hde << LCDDAH_HDE_SHIFT)); + LCD_WRITE(sc, LCDDAV, + (vds << LCDDAV_VDS_SHIFT) | (vde << LCDDAV_VDE_SHIFT)); + LCD_WRITE(sc, LCDHSYNC, hsw); + LCD_WRITE(sc, LCDVSYNC, vsw); + + /* Set configuration */ + LCD_WRITE(sc, LCDCFG, LCDCFG_NEWDES | LCDCFG_RECOVER | LCDCFG_24 | + LCDCFG_PSM | LCDCFG_CLSM | LCDCFG_SPLM | LCDCFG_REVM | LCDCFG_PCP); + ctrl = LCD_READ(sc, LCDCTRL); + ctrl &= ~LCDCTRL_BST; + ctrl |= LCDCTRL_BST_64 | LCDCTRL_OFUM; + LCD_WRITE(sc, LCDCTRL, ctrl); + LCD_WRITE(sc, LCDPCFG, PCFG_MAGIC); + LCD_WRITE(sc, LCDRGBC, LCDRGBC_RGBFMT); + + /* Update registers */ + LCD_WRITE(sc, LCDSTATE, 0); + + /* Setup frame descriptors */ + jzlcd_setup_descriptor(sc, mode, 0); + jzlcd_setup_descriptor(sc, mode, 1); + bus_dmamap_sync(sc->fdesc_tag, sc->fdesc_map, BUS_DMASYNC_PREWRITE); + + /* Setup DMA channels */ + LCD_WRITE(sc, LCDDA0, sc->fdesc_paddr + + sizeof(struct lcd_frame_descriptor)); + LCD_WRITE(sc, LCDDA1, sc->fdesc_paddr); + + /* Set display clock */ + error = clk_set_freq(sc->clk_pix, DOT_CLOCK_TO_HZ(mode->dot_clock), 0); + if (error != 0) { + device_printf(sc->dev, "failed to set pixel clock to %u Hz\n", + DOT_CLOCK_TO_HZ(mode->dot_clock)); + return (error); + } + + return (0); +} + +static int +jzlcd_configure(struct jzlcd_softc *sc, const struct videomode *mode) +{ + size_t fbsize; + int error; + + fbsize = round_page(mode->hdisplay * mode->vdisplay * (FB_BPP / NBBY)); + + /* Detach the old FB device */ + if (sc->fbdev != NULL) { + device_delete_child(sc->dev, sc->fbdev); + sc->fbdev = NULL; + } + + /* If the FB size has changed, free the old FB memory */ + if (sc->fbsize > 0 && sc->fbsize != fbsize) { + jzlcd_freefb(sc); + sc->vaddr = 0; + } + + /* Allocate the FB if necessary */ + sc->fbsize = fbsize; + if (sc->vaddr == 0) { + error = jzlcd_allocfb(sc); + if (error != 0) { + device_printf(sc->dev, "failed to allocate FB memory\n"); + return (ENXIO); + } + } + + /* Setup video mode */ + error = jzlcd_set_videomode(sc, mode); + if (error != 0) + return (error); + + /* Attach framebuffer device */ + sc->info.fb_name = device_get_nameunit(sc->dev); + sc->info.fb_vbase = (intptr_t)sc->vaddr; + sc->info.fb_pbase = sc->paddr; + sc->info.fb_size = sc->fbsize; + sc->info.fb_bpp = sc->info.fb_depth = FB_BPP; + sc->info.fb_stride = mode->hdisplay * (FB_BPP / NBBY); + sc->info.fb_width = mode->hdisplay; + sc->info.fb_height = mode->vdisplay; + + sc->fbdev = device_add_child(sc->dev, "fbd", device_get_unit(sc->dev)); + if (sc->fbdev == NULL) { + device_printf(sc->dev, "failed to add fbd child\n"); + return (ENOENT); + } + + error = device_probe_and_attach(sc->fbdev); + if (error != 0) { + device_printf(sc->dev, "failed to attach fbd device\n"); + return (error); + } + + return (0); +} + +static int +jzlcd_get_bandwidth(const struct videomode *mode) +{ + int refresh; + + refresh = FB_DIVIDE(FB_DIVIDE(DOT_CLOCK_TO_HZ(mode->dot_clock), + mode->htotal), mode->vtotal); + + return mode->hdisplay * mode->vdisplay * refresh; +} + +static int +jzlcd_mode_supported(const struct videomode *mode) +{ + /* Width and height must be less than 2048 */ + if (mode->hdisplay > FB_MAX_W || mode->vdisplay > FB_MAX_H) + return (0); + + /* Bandwidth check */ + if (jzlcd_get_bandwidth(mode) > FB_MAX_BW) + return (0); + + /* Interlace modes not yet supported by the driver */ + if ((mode->flags & VID_INTERLACE) != 0) + return (0); + + return (1); +} + +static const struct videomode * +jzlcd_find_mode(struct edid_info *ei) +{ + const struct videomode *best; + int n, bw, best_bw; + + /* If the preferred mode is OK, just use it */ + if (jzlcd_mode_supported(ei->edid_preferred_mode) != 0) + return ei->edid_preferred_mode; + + /* Pick the mode with the highest bandwidth requirements */ + best = NULL; + best_bw = 0; + for (n = 0; n < ei->edid_nmodes; n++) { + if (jzlcd_mode_supported(&ei->edid_modes[n]) == 0) + continue; + bw = jzlcd_get_bandwidth(&ei->edid_modes[n]); + if (bw > FB_MAX_BW) + continue; + if (best == NULL || bw > best_bw) { + best = &ei->edid_modes[n]; + best_bw = bw; + } + } + + return best; +} + +static void +jzlcd_hdmi_event(void *arg, device_t hdmi_dev) +{ + const struct videomode *mode; + struct videomode hdmi_mode; + struct jzlcd_softc *sc; + struct edid_info ei; + uint8_t *edid; + uint32_t edid_len; + int error; + + sc = arg; + edid = NULL; + edid_len = 0; + mode = NULL; + + error = HDMI_GET_EDID(hdmi_dev, &edid, &edid_len); + if (error != 0) { + device_printf(sc->dev, "failed to get EDID: %d\n", error); + } else { + error = edid_parse(edid, &ei); + if (error != 0) { + device_printf(sc->dev, "failed to parse EDID: %d\n", + error); + } else { + if (bootverbose) + edid_print(&ei); + + mode = jzlcd_find_mode(&ei); + } + } + + /* If a suitable mode could not be found, try the default */ + if (mode == NULL) + mode = pick_mode_by_ref(FB_DEFAULT_W, FB_DEFAULT_H, + FB_DEFAULT_REF); + + if (mode == NULL) { + device_printf(sc->dev, "failed to find usable video mode\n"); + return; + } + + if (bootverbose) + device_printf(sc->dev, "using %dx%d\n", + mode->hdisplay, mode->vdisplay); + + /* Stop the controller */ + jzlcd_stop(sc); + + /* Configure LCD controller */ + error = jzlcd_configure(sc, mode); + if (error != 0) { + device_printf(sc->dev, "failed to configure FB: %d\n", error); + return; + } + + /* Enable HDMI TX */ + hdmi_mode = *mode; + HDMI_SET_VIDEOMODE(hdmi_dev, &hdmi_mode); + + /* Start the controller! */ + jzlcd_start(sc); +} + +static void +jzlcd_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + if (error != 0) + return; + *(bus_addr_t *)arg = segs[0].ds_addr; +} + +static int +jzlcd_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_is_compatible(dev, "ingenic,jz4780-lcd")) + return (ENXIO); + + device_set_desc(dev, "Ingenic JZ4780 LCD Controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +jzlcd_attach(device_t dev) +{ + struct jzlcd_softc *sc; + int error; + + sc = device_get_softc(dev); + + sc->dev = dev; + + if (bus_alloc_resources(dev, jzlcd_spec, sc->res)) { + device_printf(dev, "cannot allocate resources for device\n"); + goto failed; + } + + if (clk_get_by_ofw_name(dev, 0, "lcd_clk", &sc->clk) != 0 || + clk_get_by_ofw_name(dev, 0, "lcd_pixclk", &sc->clk_pix) != 0) { + device_printf(dev, "cannot get clocks\n"); + goto failed; + } + if (clk_enable(sc->clk) != 0 || clk_enable(sc->clk_pix) != 0) { + device_printf(dev, "cannot enable clocks\n"); + goto failed; + } + + error = bus_dma_tag_create( + bus_get_dma_tag(dev), + sizeof(struct lcd_frame_descriptor), 0, + BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, + NULL, NULL, + sizeof(struct lcd_frame_descriptor) * 2, 1, + sizeof(struct lcd_frame_descriptor) * 2, + 0, + NULL, NULL, + &sc->fdesc_tag); + if (error != 0) { + device_printf(dev, "cannot create bus dma tag\n"); + goto failed; + } + + error = bus_dmamem_alloc(sc->fdesc_tag, (void **)&sc->fdesc, + BUS_DMA_NOCACHE | BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->fdesc_map); + if (error != 0) { + device_printf(dev, "cannot allocate dma descriptor\n"); + goto dmaalloc_failed; + } + + error = bus_dmamap_load(sc->fdesc_tag, sc->fdesc_map, sc->fdesc, + sizeof(struct lcd_frame_descriptor) * 2, jzlcd_dmamap_cb, + &sc->fdesc_paddr, 0); + if (error != 0) { + device_printf(dev, "cannot load dma map\n"); + goto dmaload_failed; + } + + sc->hdmi_evh = EVENTHANDLER_REGISTER(hdmi_event, + jzlcd_hdmi_event, sc, 0); + + return (0); + +dmaload_failed: + bus_dmamem_free(sc->fdesc_tag, sc->fdesc, sc->fdesc_map); +dmaalloc_failed: + bus_dma_tag_destroy(sc->fdesc_tag); +failed: + if (sc->clk_pix != NULL) + clk_release(sc->clk); + if (sc->clk != NULL) + clk_release(sc->clk); + if (sc->res != NULL) + bus_release_resources(dev, jzlcd_spec, sc->res); + + return (ENXIO); +} + +static struct fb_info * +jzlcd_fb_getinfo(device_t dev) +{ + struct jzlcd_softc *sc; + + sc = device_get_softc(dev); + + return (&sc->info); +} + +static device_method_t jzlcd_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, jzlcd_probe), + DEVMETHOD(device_attach, jzlcd_attach), + + /* FB interface */ + DEVMETHOD(fb_getinfo, jzlcd_fb_getinfo), + + DEVMETHOD_END +}; + +static driver_t jzlcd_driver = { + "fb", + jzlcd_methods, + sizeof(struct jzlcd_softc), +}; + +static devclass_t jzlcd_devclass; + +DRIVER_MODULE(fb, simplebus, jzlcd_driver, jzlcd_devclass, 0, 0);