1296064Sjmcneill/*- 2296064Sjmcneill * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca> 3296064Sjmcneill * All rights reserved. 4296064Sjmcneill * 5296064Sjmcneill * Redistribution and use in source and binary forms, with or without 6296064Sjmcneill * modification, are permitted provided that the following conditions 7296064Sjmcneill * are met: 8296064Sjmcneill * 1. Redistributions of source code must retain the above copyright 9296064Sjmcneill * notice, this list of conditions and the following disclaimer. 10296064Sjmcneill * 2. Redistributions in binary form must reproduce the above copyright 11296064Sjmcneill * notice, this list of conditions and the following disclaimer in the 12296064Sjmcneill * documentation and/or other materials provided with the distribution. 13296064Sjmcneill * 14296064Sjmcneill * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15296064Sjmcneill * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16296064Sjmcneill * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17296064Sjmcneill * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18296064Sjmcneill * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19296064Sjmcneill * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20296064Sjmcneill * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 21296064Sjmcneill * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22296064Sjmcneill * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23296064Sjmcneill * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24296064Sjmcneill * SUCH DAMAGE. 25296064Sjmcneill * 26296064Sjmcneill * $FreeBSD: stable/11/sys/arm/allwinner/a10_hdmi.c 308324 2016-11-05 04:17:32Z mmel $ 27296064Sjmcneill */ 28296064Sjmcneill 29296064Sjmcneill/* 30296064Sjmcneill * Allwinner A10/A20 HDMI TX 31296064Sjmcneill */ 32296064Sjmcneill 33296064Sjmcneill#include <sys/cdefs.h> 34296064Sjmcneill__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/a10_hdmi.c 308324 2016-11-05 04:17:32Z mmel $"); 35296064Sjmcneill 36296064Sjmcneill#include <sys/param.h> 37296064Sjmcneill#include <sys/systm.h> 38296064Sjmcneill#include <sys/bus.h> 39296064Sjmcneill#include <sys/rman.h> 40296064Sjmcneill#include <sys/condvar.h> 41296064Sjmcneill#include <sys/kernel.h> 42296064Sjmcneill#include <sys/module.h> 43296064Sjmcneill 44296064Sjmcneill#include <machine/bus.h> 45296064Sjmcneill 46296064Sjmcneill#include <dev/ofw/ofw_bus.h> 47296064Sjmcneill#include <dev/ofw/ofw_bus_subr.h> 48296064Sjmcneill 49296064Sjmcneill#include <dev/videomode/videomode.h> 50296064Sjmcneill#include <dev/videomode/edidvar.h> 51296064Sjmcneill 52297627Sjmcneill#include <dev/extres/clk/clk.h> 53296064Sjmcneill 54296064Sjmcneill#include "hdmi_if.h" 55296064Sjmcneill 56296064Sjmcneill#define HDMI_CTRL 0x004 57296064Sjmcneill#define CTRL_MODULE_EN (1 << 31) 58296064Sjmcneill#define HDMI_INT_STATUS 0x008 59296064Sjmcneill#define HDMI_HPD 0x00c 60296064Sjmcneill#define HPD_DET (1 << 0) 61296064Sjmcneill#define HDMI_VID_CTRL 0x010 62296064Sjmcneill#define VID_CTRL_VIDEO_EN (1 << 31) 63296064Sjmcneill#define VID_CTRL_HDMI_MODE (1 << 30) 64296064Sjmcneill#define VID_CTRL_INTERLACE (1 << 4) 65296064Sjmcneill#define VID_CTRL_REPEATER_2X (1 << 0) 66296064Sjmcneill#define HDMI_VID_TIMING0 0x014 67296064Sjmcneill#define VID_ACT_V(v) (((v) - 1) << 16) 68296064Sjmcneill#define VID_ACT_H(h) (((h) - 1) << 0) 69296064Sjmcneill#define HDMI_VID_TIMING1 0x018 70296064Sjmcneill#define VID_VBP(vbp) (((vbp) - 1) << 16) 71296064Sjmcneill#define VID_HBP(hbp) (((hbp) - 1) << 0) 72296064Sjmcneill#define HDMI_VID_TIMING2 0x01c 73296064Sjmcneill#define VID_VFP(vfp) (((vfp) - 1) << 16) 74296064Sjmcneill#define VID_HFP(hfp) (((hfp) - 1) << 0) 75296064Sjmcneill#define HDMI_VID_TIMING3 0x020 76296064Sjmcneill#define VID_VSPW(vspw) (((vspw) - 1) << 16) 77296064Sjmcneill#define VID_HSPW(hspw) (((hspw) - 1) << 0) 78296064Sjmcneill#define HDMI_VID_TIMING4 0x024 79296064Sjmcneill#define TX_CLOCK_NORMAL 0x03e00000 80296064Sjmcneill#define VID_VSYNC_ACTSEL (1 << 1) 81296064Sjmcneill#define VID_HSYNC_ACTSEL (1 << 0) 82296064Sjmcneill#define HDMI_AUD_CTRL 0x040 83296064Sjmcneill#define AUD_CTRL_EN (1 << 31) 84296064Sjmcneill#define AUD_CTRL_RST (1 << 30) 85296064Sjmcneill#define HDMI_ADMA_CTRL 0x044 86296064Sjmcneill#define HDMI_ADMA_MODE (1 << 31) 87296064Sjmcneill#define HDMI_ADMA_MODE_DDMA (0 << 31) 88296064Sjmcneill#define HDMI_ADMA_MODE_NDMA (1 << 31) 89296064Sjmcneill#define HDMI_AUD_FMT 0x048 90296064Sjmcneill#define AUD_FMT_CH(n) ((n) - 1) 91296064Sjmcneill#define HDMI_PCM_CTRL 0x04c 92296064Sjmcneill#define HDMI_AUD_CTS 0x050 93296064Sjmcneill#define HDMI_AUD_N 0x054 94296064Sjmcneill#define HDMI_AUD_CH_STATUS0 0x058 95296064Sjmcneill#define CH_STATUS0_FS_FREQ (0xf << 24) 96296064Sjmcneill#define CH_STATUS0_FS_FREQ_48 (2 << 24) 97296064Sjmcneill#define HDMI_AUD_CH_STATUS1 0x05c 98296064Sjmcneill#define CH_STATUS1_WORD_LEN (0x7 << 1) 99296064Sjmcneill#define CH_STATUS1_WORD_LEN_16 (1 << 1) 100296064Sjmcneill#define HDMI_AUDIO_RESET_RETRY 1000 101296064Sjmcneill#define HDMI_AUDIO_CHANNELS 2 102296064Sjmcneill#define HDMI_AUDIO_CHANNELMAP 0x76543210 103296064Sjmcneill#define HDMI_AUDIO_N 6144 /* 48 kHz */ 104296064Sjmcneill#define HDMI_AUDIO_CTS(r, n) ((((r) * 10) * ((n) / 128)) / 480) 105296064Sjmcneill#define HDMI_PADCTRL0 0x200 106296064Sjmcneill#define PADCTRL0_BIASEN (1 << 31) 107296064Sjmcneill#define PADCTRL0_LDOCEN (1 << 30) 108296064Sjmcneill#define PADCTRL0_LDODEN (1 << 29) 109296064Sjmcneill#define PADCTRL0_PWENC (1 << 28) 110296064Sjmcneill#define PADCTRL0_PWEND (1 << 27) 111296064Sjmcneill#define PADCTRL0_PWENG (1 << 26) 112296064Sjmcneill#define PADCTRL0_CKEN (1 << 25) 113296064Sjmcneill#define PADCTRL0_SEN (1 << 24) 114296064Sjmcneill#define PADCTRL0_TXEN (1 << 23) 115296064Sjmcneill#define HDMI_PADCTRL1 0x204 116296064Sjmcneill#define PADCTRL1_AMP_OPT (1 << 23) 117296064Sjmcneill#define PADCTRL1_AMPCK_OPT (1 << 22) 118296064Sjmcneill#define PADCTRL1_DMP_OPT (1 << 21) 119296064Sjmcneill#define PADCTRL1_EMP_OPT (1 << 20) 120296064Sjmcneill#define PADCTRL1_EMPCK_OPT (1 << 19) 121296064Sjmcneill#define PADCTRL1_PWSCK (1 << 18) 122296064Sjmcneill#define PADCTRL1_PWSDT (1 << 17) 123296064Sjmcneill#define PADCTRL1_REG_CSMPS (1 << 16) 124296064Sjmcneill#define PADCTRL1_REG_DEN (1 << 15) 125296064Sjmcneill#define PADCTRL1_REG_DENCK (1 << 14) 126296064Sjmcneill#define PADCTRL1_REG_PLRCK (1 << 13) 127296064Sjmcneill#define PADCTRL1_REG_EMP (0x7 << 10) 128296064Sjmcneill#define PADCTRL1_REG_EMP_EN (0x2 << 10) 129296064Sjmcneill#define PADCTRL1_REG_CD (0x3 << 8) 130296064Sjmcneill#define PADCTRL1_REG_CKSS (0x3 << 6) 131296064Sjmcneill#define PADCTRL1_REG_CKSS_1X (0x1 << 6) 132296064Sjmcneill#define PADCTRL1_REG_CKSS_2X (0x0 << 6) 133296064Sjmcneill#define PADCTRL1_REG_AMP (0x7 << 3) 134296064Sjmcneill#define PADCTRL1_REG_AMP_EN (0x6 << 3) 135296064Sjmcneill#define PADCTRL1_REG_PLR (0x7 << 0) 136296064Sjmcneill#define HDMI_PLLCTRL0 0x208 137296064Sjmcneill#define PLLCTRL0_PLL_EN (1 << 31) 138296064Sjmcneill#define PLLCTRL0_BWS (1 << 30) 139296064Sjmcneill#define PLLCTRL0_HV_IS_33 (1 << 29) 140296064Sjmcneill#define PLLCTRL0_LDO1_EN (1 << 28) 141296064Sjmcneill#define PLLCTRL0_LDO2_EN (1 << 27) 142296064Sjmcneill#define PLLCTRL0_SDIV2 (1 << 25) 143296064Sjmcneill#define PLLCTRL0_VCO_GAIN (0x1 << 22) 144296064Sjmcneill#define PLLCTRL0_S (0x7 << 17) 145296064Sjmcneill#define PLLCTRL0_CP_S (0xf << 12) 146296064Sjmcneill#define PLLCTRL0_CS (0x7 << 8) 147296064Sjmcneill#define PLLCTRL0_PREDIV(x) ((x) << 4) 148296064Sjmcneill#define PLLCTRL0_VCO_S (0x8 << 0) 149296064Sjmcneill#define HDMI_PLLDBG0 0x20c 150296064Sjmcneill#define PLLDBG0_CKIN_SEL (1 << 21) 151296064Sjmcneill#define PLLDBG0_CKIN_SEL_PLL3 (0 << 21) 152296064Sjmcneill#define PLLDBG0_CKIN_SEL_PLL7 (1 << 21) 153296064Sjmcneill#define HDMI_PKTCTRL0 0x2f0 154296064Sjmcneill#define HDMI_PKTCTRL1 0x2f4 155296064Sjmcneill#define PKTCTRL_PACKET(n,t) ((t) << ((n) << 2)) 156296064Sjmcneill#define PKT_NULL 0 157296064Sjmcneill#define PKT_GC 1 158296064Sjmcneill#define PKT_AVI 2 159296064Sjmcneill#define PKT_AI 3 160296064Sjmcneill#define PKT_SPD 5 161296064Sjmcneill#define PKT_END 15 162296064Sjmcneill#define DDC_CTRL 0x500 163296064Sjmcneill#define CTRL_DDC_EN (1 << 31) 164296064Sjmcneill#define CTRL_DDC_ACMD_START (1 << 30) 165296064Sjmcneill#define CTRL_DDC_FIFO_DIR (1 << 8) 166296064Sjmcneill#define CTRL_DDC_FIFO_DIR_READ (0 << 8) 167296064Sjmcneill#define CTRL_DDC_FIFO_DIR_WRITE (1 << 8) 168296064Sjmcneill#define CTRL_DDC_SWRST (1 << 0) 169296064Sjmcneill#define DDC_SLAVE_ADDR 0x504 170296064Sjmcneill#define SLAVE_ADDR_SEG_SHIFT 24 171296064Sjmcneill#define SLAVE_ADDR_EDDC_SHIFT 16 172296064Sjmcneill#define SLAVE_ADDR_OFFSET_SHIFT 8 173296064Sjmcneill#define SLAVE_ADDR_SHIFT 0 174296064Sjmcneill#define DDC_INT_STATUS 0x50c 175296064Sjmcneill#define INT_STATUS_XFER_DONE (1 << 0) 176296064Sjmcneill#define DDC_FIFO_CTRL 0x510 177296064Sjmcneill#define FIFO_CTRL_CLEAR (1 << 31) 178296064Sjmcneill#define DDC_BYTE_COUNTER 0x51c 179296064Sjmcneill#define DDC_COMMAND 0x520 180296064Sjmcneill#define COMMAND_EOREAD (4 << 0) 181296064Sjmcneill#define DDC_CLOCK 0x528 182296064Sjmcneill#define DDC_CLOCK_M (1 << 3) 183296064Sjmcneill#define DDC_CLOCK_N (5 << 0) 184296064Sjmcneill#define DDC_FIFO 0x518 185296064Sjmcneill#define SWRST_DELAY 1000 186296064Sjmcneill#define DDC_DELAY 1000 187296064Sjmcneill#define DDC_RETRY 1000 188296064Sjmcneill#define DDC_BLKLEN 16 189296064Sjmcneill#define DDC_ADDR 0x50 190296064Sjmcneill#define EDDC_ADDR 0x60 191296064Sjmcneill#define EDID_LENGTH 128 192296064Sjmcneill#define HDMI_ENABLE_DELAY 50000 193296064Sjmcneill#define DDC_READ_RETRY 4 194296064Sjmcneill#define EXT_TAG 0x00 195296064Sjmcneill#define CEA_TAG_ID 0x02 196296064Sjmcneill#define CEA_DTD 0x03 197296064Sjmcneill#define DTD_BASIC_AUDIO (1 << 6) 198297514Sjmcneill#define CEA_REV 0x02 199297514Sjmcneill#define CEA_DATA_OFF 0x03 200297514Sjmcneill#define CEA_DATA_START 4 201297514Sjmcneill#define BLOCK_TAG(x) (((x) >> 5) & 0x7) 202297514Sjmcneill#define BLOCK_TAG_VSDB 3 203297514Sjmcneill#define BLOCK_LEN(x) ((x) & 0x1f) 204297514Sjmcneill#define HDMI_VSDB_MINLEN 5 205297514Sjmcneill#define HDMI_OUI "\x03\x0c\x00" 206297514Sjmcneill#define HDMI_OUI_LEN 3 207297627Sjmcneill#define HDMI_DEFAULT_FREQ 297000000 208296064Sjmcneill 209296064Sjmcneillstruct a10hdmi_softc { 210296064Sjmcneill struct resource *res; 211296064Sjmcneill 212296064Sjmcneill struct intr_config_hook mode_hook; 213296064Sjmcneill 214296064Sjmcneill uint8_t edid[EDID_LENGTH]; 215296064Sjmcneill 216296789Sjmcneill int has_hdmi; 217296064Sjmcneill int has_audio; 218297627Sjmcneill 219297627Sjmcneill clk_t clk_ahb; 220297627Sjmcneill clk_t clk_hdmi; 221297627Sjmcneill clk_t clk_lcd; 222296064Sjmcneill}; 223296064Sjmcneill 224296064Sjmcneillstatic struct resource_spec a10hdmi_spec[] = { 225296064Sjmcneill { SYS_RES_MEMORY, 0, RF_ACTIVE }, 226296064Sjmcneill { -1, 0 } 227296064Sjmcneill}; 228296064Sjmcneill 229296064Sjmcneill#define HDMI_READ(sc, reg) bus_read_4((sc)->res, (reg)) 230296064Sjmcneill#define HDMI_WRITE(sc, reg, val) bus_write_4((sc)->res, (reg), (val)) 231296064Sjmcneill 232296064Sjmcneillstatic void 233296064Sjmcneilla10hdmi_init(struct a10hdmi_softc *sc) 234296064Sjmcneill{ 235296064Sjmcneill /* Enable the HDMI module */ 236296064Sjmcneill HDMI_WRITE(sc, HDMI_CTRL, CTRL_MODULE_EN); 237296064Sjmcneill 238296064Sjmcneill /* Configure PLL/DRV settings */ 239296064Sjmcneill HDMI_WRITE(sc, HDMI_PADCTRL0, PADCTRL0_BIASEN | PADCTRL0_LDOCEN | 240296064Sjmcneill PADCTRL0_LDODEN | PADCTRL0_PWENC | PADCTRL0_PWEND | 241296064Sjmcneill PADCTRL0_PWENG | PADCTRL0_CKEN | PADCTRL0_TXEN); 242296064Sjmcneill HDMI_WRITE(sc, HDMI_PADCTRL1, PADCTRL1_AMP_OPT | PADCTRL1_AMPCK_OPT | 243296064Sjmcneill PADCTRL1_EMP_OPT | PADCTRL1_EMPCK_OPT | PADCTRL1_REG_DEN | 244296064Sjmcneill PADCTRL1_REG_DENCK | PADCTRL1_REG_EMP_EN | PADCTRL1_REG_AMP_EN); 245296064Sjmcneill 246296064Sjmcneill /* Select PLL3 as input clock */ 247296064Sjmcneill HDMI_WRITE(sc, HDMI_PLLDBG0, PLLDBG0_CKIN_SEL_PLL3); 248296064Sjmcneill 249296064Sjmcneill DELAY(HDMI_ENABLE_DELAY); 250296064Sjmcneill} 251296064Sjmcneill 252296064Sjmcneillstatic void 253296064Sjmcneilla10hdmi_hpd(void *arg) 254296064Sjmcneill{ 255296064Sjmcneill struct a10hdmi_softc *sc; 256296064Sjmcneill device_t dev; 257296064Sjmcneill uint32_t hpd; 258296064Sjmcneill 259296064Sjmcneill dev = arg; 260296064Sjmcneill sc = device_get_softc(dev); 261296064Sjmcneill 262296064Sjmcneill hpd = HDMI_READ(sc, HDMI_HPD); 263296064Sjmcneill if ((hpd & HPD_DET) == HPD_DET) 264296064Sjmcneill EVENTHANDLER_INVOKE(hdmi_event, dev, HDMI_EVENT_CONNECTED); 265296064Sjmcneill 266296064Sjmcneill config_intrhook_disestablish(&sc->mode_hook); 267296064Sjmcneill} 268296064Sjmcneill 269296064Sjmcneillstatic int 270296064Sjmcneilla10hdmi_probe(device_t dev) 271296064Sjmcneill{ 272296064Sjmcneill if (!ofw_bus_status_okay(dev)) 273296064Sjmcneill return (ENXIO); 274296064Sjmcneill 275296064Sjmcneill if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-hdmi")) 276296064Sjmcneill return (ENXIO); 277296064Sjmcneill 278296064Sjmcneill device_set_desc(dev, "Allwinner HDMI TX"); 279296064Sjmcneill return (BUS_PROBE_DEFAULT); 280296064Sjmcneill} 281296064Sjmcneill 282296064Sjmcneillstatic int 283296064Sjmcneilla10hdmi_attach(device_t dev) 284296064Sjmcneill{ 285296064Sjmcneill struct a10hdmi_softc *sc; 286296064Sjmcneill int error; 287296064Sjmcneill 288296064Sjmcneill sc = device_get_softc(dev); 289296064Sjmcneill 290296064Sjmcneill if (bus_alloc_resources(dev, a10hdmi_spec, &sc->res)) { 291296064Sjmcneill device_printf(dev, "cannot allocate resources for device\n"); 292296064Sjmcneill return (ENXIO); 293296064Sjmcneill } 294296064Sjmcneill 295297627Sjmcneill /* Setup clocks */ 296308324Smmel error = clk_get_by_ofw_name(dev, 0, "ahb", &sc->clk_ahb); 297297627Sjmcneill if (error != 0) { 298297627Sjmcneill device_printf(dev, "cannot find ahb clock\n"); 299297627Sjmcneill return (error); 300297627Sjmcneill } 301308324Smmel error = clk_get_by_ofw_name(dev, 0, "hdmi", &sc->clk_hdmi); 302297627Sjmcneill if (error != 0) { 303297627Sjmcneill device_printf(dev, "cannot find hdmi clock\n"); 304297627Sjmcneill return (error); 305297627Sjmcneill } 306308324Smmel error = clk_get_by_ofw_name(dev, 0, "lcd", &sc->clk_lcd); 307297627Sjmcneill if (error != 0) { 308297627Sjmcneill device_printf(dev, "cannot find lcd clock\n"); 309297627Sjmcneill } 310297627Sjmcneill /* Enable HDMI clock */ 311297627Sjmcneill error = clk_enable(sc->clk_hdmi); 312297627Sjmcneill if (error != 0) { 313297627Sjmcneill device_printf(dev, "cannot enable hdmi clock\n"); 314297627Sjmcneill return (error); 315297627Sjmcneill } 316297627Sjmcneill /* Gating AHB clock for HDMI */ 317297627Sjmcneill error = clk_enable(sc->clk_ahb); 318297627Sjmcneill if (error != 0) { 319297627Sjmcneill device_printf(dev, "cannot enable ahb gate\n"); 320297627Sjmcneill return (error); 321297627Sjmcneill } 322296064Sjmcneill 323296064Sjmcneill a10hdmi_init(sc); 324296064Sjmcneill 325296064Sjmcneill sc->mode_hook.ich_func = a10hdmi_hpd; 326296064Sjmcneill sc->mode_hook.ich_arg = dev; 327296064Sjmcneill 328296064Sjmcneill error = config_intrhook_establish(&sc->mode_hook); 329296064Sjmcneill if (error != 0) 330296064Sjmcneill return (error); 331296064Sjmcneill 332296064Sjmcneill return (0); 333296064Sjmcneill} 334296064Sjmcneill 335296064Sjmcneillstatic int 336296064Sjmcneilla10hdmi_ddc_xfer(struct a10hdmi_softc *sc, uint16_t addr, uint8_t seg, 337296064Sjmcneill uint8_t off, int len) 338296064Sjmcneill{ 339296064Sjmcneill uint32_t val; 340296064Sjmcneill int retry; 341296064Sjmcneill 342296064Sjmcneill /* Set FIFO direction to read */ 343296064Sjmcneill val = HDMI_READ(sc, DDC_CTRL); 344296064Sjmcneill val &= ~CTRL_DDC_FIFO_DIR; 345296064Sjmcneill val |= CTRL_DDC_FIFO_DIR_READ; 346296064Sjmcneill HDMI_WRITE(sc, DDC_CTRL, val); 347296064Sjmcneill 348296064Sjmcneill /* Setup DDC slave address */ 349296064Sjmcneill val = (addr << SLAVE_ADDR_SHIFT) | (seg << SLAVE_ADDR_SEG_SHIFT) | 350296064Sjmcneill (EDDC_ADDR << SLAVE_ADDR_EDDC_SHIFT) | 351296064Sjmcneill (off << SLAVE_ADDR_OFFSET_SHIFT); 352296064Sjmcneill HDMI_WRITE(sc, DDC_SLAVE_ADDR, val); 353296064Sjmcneill 354296064Sjmcneill /* Clear FIFO */ 355296064Sjmcneill val = HDMI_READ(sc, DDC_FIFO_CTRL); 356296064Sjmcneill val |= FIFO_CTRL_CLEAR; 357296064Sjmcneill HDMI_WRITE(sc, DDC_FIFO_CTRL, val); 358296064Sjmcneill 359296064Sjmcneill /* Set transfer length */ 360296064Sjmcneill HDMI_WRITE(sc, DDC_BYTE_COUNTER, len); 361296064Sjmcneill 362296064Sjmcneill /* Set command to "Explicit Offset Address Read" */ 363296064Sjmcneill HDMI_WRITE(sc, DDC_COMMAND, COMMAND_EOREAD); 364296064Sjmcneill 365296064Sjmcneill /* Start transfer */ 366296064Sjmcneill val = HDMI_READ(sc, DDC_CTRL); 367296064Sjmcneill val |= CTRL_DDC_ACMD_START; 368296064Sjmcneill HDMI_WRITE(sc, DDC_CTRL, val); 369296064Sjmcneill 370296064Sjmcneill /* Wait for command to start */ 371296064Sjmcneill retry = DDC_RETRY; 372296064Sjmcneill while (--retry > 0) { 373296064Sjmcneill val = HDMI_READ(sc, DDC_CTRL); 374296064Sjmcneill if ((val & CTRL_DDC_ACMD_START) == 0) 375296064Sjmcneill break; 376296064Sjmcneill DELAY(DDC_DELAY); 377296064Sjmcneill } 378296064Sjmcneill if (retry == 0) 379296064Sjmcneill return (ETIMEDOUT); 380296064Sjmcneill 381296064Sjmcneill /* Ensure that the transfer completed */ 382296064Sjmcneill val = HDMI_READ(sc, DDC_INT_STATUS); 383296064Sjmcneill if ((val & INT_STATUS_XFER_DONE) == 0) 384296064Sjmcneill return (EIO); 385296064Sjmcneill 386296064Sjmcneill return (0); 387296064Sjmcneill} 388296064Sjmcneill 389296064Sjmcneillstatic int 390296064Sjmcneilla10hdmi_ddc_read(struct a10hdmi_softc *sc, int block, uint8_t *edid) 391296064Sjmcneill{ 392296064Sjmcneill int resid, off, len, error; 393296064Sjmcneill uint8_t *pbuf; 394296064Sjmcneill 395296064Sjmcneill pbuf = edid; 396296064Sjmcneill resid = EDID_LENGTH; 397296064Sjmcneill off = (block & 1) ? EDID_LENGTH : 0; 398296064Sjmcneill 399296064Sjmcneill while (resid > 0) { 400296064Sjmcneill len = min(resid, DDC_BLKLEN); 401296064Sjmcneill error = a10hdmi_ddc_xfer(sc, DDC_ADDR, block >> 1, off, len); 402296064Sjmcneill if (error != 0) 403296064Sjmcneill return (error); 404296064Sjmcneill 405296064Sjmcneill bus_read_multi_1(sc->res, DDC_FIFO, pbuf, len); 406296064Sjmcneill 407296064Sjmcneill pbuf += len; 408296064Sjmcneill off += len; 409296064Sjmcneill resid -= len; 410296064Sjmcneill } 411296064Sjmcneill 412296064Sjmcneill return (0); 413296064Sjmcneill} 414296064Sjmcneill 415297514Sjmcneillstatic int 416297514Sjmcneilla10hdmi_detect_hdmi_vsdb(uint8_t *edid) 417297514Sjmcneill{ 418297514Sjmcneill int off, p, btag, blen; 419297514Sjmcneill 420297514Sjmcneill if (edid[EXT_TAG] != CEA_TAG_ID) 421297514Sjmcneill return (0); 422297514Sjmcneill 423297514Sjmcneill off = edid[CEA_DATA_OFF]; 424297514Sjmcneill 425297514Sjmcneill /* CEA data block collection starts at byte 4 */ 426297514Sjmcneill if (off <= CEA_DATA_START) 427297514Sjmcneill return (0); 428297514Sjmcneill 429297514Sjmcneill /* Parse the CEA data blocks */ 430297514Sjmcneill for (p = CEA_DATA_START; p < off;) { 431297514Sjmcneill btag = BLOCK_TAG(edid[p]); 432297514Sjmcneill blen = BLOCK_LEN(edid[p]); 433297514Sjmcneill 434297514Sjmcneill /* Make sure the length is sane */ 435297514Sjmcneill if (p + blen + 1 > off) 436297514Sjmcneill break; 437297514Sjmcneill 438297514Sjmcneill /* Look for a VSDB with the HDMI 24-bit IEEE registration ID */ 439297514Sjmcneill if (btag == BLOCK_TAG_VSDB && blen >= HDMI_VSDB_MINLEN && 440297514Sjmcneill memcmp(&edid[p + 1], HDMI_OUI, HDMI_OUI_LEN) == 0) 441297514Sjmcneill return (1); 442297514Sjmcneill 443297514Sjmcneill /* Next data block */ 444297514Sjmcneill p += (1 + blen); 445297514Sjmcneill } 446297514Sjmcneill 447297514Sjmcneill return (0); 448297514Sjmcneill} 449297514Sjmcneill 450296789Sjmcneillstatic void 451296789Sjmcneilla10hdmi_detect_hdmi(struct a10hdmi_softc *sc, int *phdmi, int *paudio) 452296064Sjmcneill{ 453296064Sjmcneill struct edid_info ei; 454296064Sjmcneill uint8_t edid[EDID_LENGTH]; 455296064Sjmcneill int block; 456296064Sjmcneill 457296789Sjmcneill *phdmi = *paudio = 0; 458296789Sjmcneill 459296064Sjmcneill if (edid_parse(sc->edid, &ei) != 0) 460296789Sjmcneill return; 461296064Sjmcneill 462296064Sjmcneill /* Scan through extension blocks, looking for a CEA-861 block. */ 463296064Sjmcneill for (block = 1; block <= ei.edid_ext_block_count; block++) { 464296064Sjmcneill if (a10hdmi_ddc_read(sc, block, edid) != 0) 465296789Sjmcneill return; 466296064Sjmcneill 467297514Sjmcneill if (a10hdmi_detect_hdmi_vsdb(edid) != 0) { 468296789Sjmcneill *phdmi = 1; 469296789Sjmcneill *paudio = ((edid[CEA_DTD] & DTD_BASIC_AUDIO) != 0); 470296789Sjmcneill return; 471296789Sjmcneill } 472296064Sjmcneill } 473296064Sjmcneill} 474296064Sjmcneill 475296064Sjmcneillstatic int 476296064Sjmcneilla10hdmi_get_edid(device_t dev, uint8_t **edid, uint32_t *edid_len) 477296064Sjmcneill{ 478296064Sjmcneill struct a10hdmi_softc *sc; 479296064Sjmcneill int error, retry; 480296064Sjmcneill 481296064Sjmcneill sc = device_get_softc(dev); 482296064Sjmcneill retry = DDC_READ_RETRY; 483296064Sjmcneill 484296064Sjmcneill while (--retry > 0) { 485296064Sjmcneill /* I2C software reset */ 486296064Sjmcneill HDMI_WRITE(sc, DDC_FIFO_CTRL, 0); 487296064Sjmcneill HDMI_WRITE(sc, DDC_CTRL, CTRL_DDC_EN | CTRL_DDC_SWRST); 488296064Sjmcneill DELAY(SWRST_DELAY); 489296064Sjmcneill if (HDMI_READ(sc, DDC_CTRL) & CTRL_DDC_SWRST) { 490296064Sjmcneill device_printf(dev, "DDC software reset failed\n"); 491296064Sjmcneill return (ENXIO); 492296064Sjmcneill } 493296064Sjmcneill 494296064Sjmcneill /* Configure DDC clock */ 495296064Sjmcneill HDMI_WRITE(sc, DDC_CLOCK, DDC_CLOCK_M | DDC_CLOCK_N); 496296064Sjmcneill 497296064Sjmcneill /* Read EDID block */ 498296064Sjmcneill error = a10hdmi_ddc_read(sc, 0, sc->edid); 499296064Sjmcneill if (error == 0) { 500296064Sjmcneill *edid = sc->edid; 501296064Sjmcneill *edid_len = sizeof(sc->edid); 502296064Sjmcneill break; 503296064Sjmcneill } 504296064Sjmcneill } 505296064Sjmcneill 506296064Sjmcneill if (error == 0) 507296789Sjmcneill a10hdmi_detect_hdmi(sc, &sc->has_hdmi, &sc->has_audio); 508296064Sjmcneill else 509296789Sjmcneill sc->has_hdmi = sc->has_audio = 0; 510296064Sjmcneill 511296064Sjmcneill return (error); 512296064Sjmcneill} 513296064Sjmcneill 514296064Sjmcneillstatic void 515296064Sjmcneilla10hdmi_set_audiomode(device_t dev, const struct videomode *mode) 516296064Sjmcneill{ 517296064Sjmcneill struct a10hdmi_softc *sc; 518296064Sjmcneill uint32_t val; 519296064Sjmcneill int retry; 520296064Sjmcneill 521296064Sjmcneill sc = device_get_softc(dev); 522296064Sjmcneill 523296064Sjmcneill /* Disable and reset audio module and wait for reset bit to clear */ 524296064Sjmcneill HDMI_WRITE(sc, HDMI_AUD_CTRL, AUD_CTRL_RST); 525296064Sjmcneill for (retry = HDMI_AUDIO_RESET_RETRY; retry > 0; retry--) { 526296064Sjmcneill val = HDMI_READ(sc, HDMI_AUD_CTRL); 527296064Sjmcneill if ((val & AUD_CTRL_RST) == 0) 528296064Sjmcneill break; 529296064Sjmcneill } 530296064Sjmcneill if (retry == 0) { 531296064Sjmcneill device_printf(dev, "timeout waiting for audio module\n"); 532296064Sjmcneill return; 533296064Sjmcneill } 534296064Sjmcneill 535296064Sjmcneill if (!sc->has_audio) 536296064Sjmcneill return; 537296064Sjmcneill 538296064Sjmcneill /* DMA and FIFO control */ 539296064Sjmcneill HDMI_WRITE(sc, HDMI_ADMA_CTRL, HDMI_ADMA_MODE_DDMA); 540296064Sjmcneill 541296064Sjmcneill /* Audio format control (LPCM, S16LE, stereo) */ 542296064Sjmcneill HDMI_WRITE(sc, HDMI_AUD_FMT, AUD_FMT_CH(HDMI_AUDIO_CHANNELS)); 543296064Sjmcneill 544296064Sjmcneill /* Channel mappings */ 545296064Sjmcneill HDMI_WRITE(sc, HDMI_PCM_CTRL, HDMI_AUDIO_CHANNELMAP); 546296064Sjmcneill 547296064Sjmcneill /* Clocks */ 548296064Sjmcneill HDMI_WRITE(sc, HDMI_AUD_CTS, 549296064Sjmcneill HDMI_AUDIO_CTS(mode->dot_clock, HDMI_AUDIO_N)); 550296064Sjmcneill HDMI_WRITE(sc, HDMI_AUD_N, HDMI_AUDIO_N); 551296064Sjmcneill 552296064Sjmcneill /* Set sampling frequency to 48 kHz, word length to 16-bit */ 553296064Sjmcneill HDMI_WRITE(sc, HDMI_AUD_CH_STATUS0, CH_STATUS0_FS_FREQ_48); 554296064Sjmcneill HDMI_WRITE(sc, HDMI_AUD_CH_STATUS1, CH_STATUS1_WORD_LEN_16); 555296064Sjmcneill 556296064Sjmcneill /* Enable */ 557296064Sjmcneill HDMI_WRITE(sc, HDMI_AUD_CTRL, AUD_CTRL_EN); 558296064Sjmcneill} 559296064Sjmcneill 560296064Sjmcneillstatic int 561297627Sjmcneilla10hdmi_get_tcon_config(struct a10hdmi_softc *sc, int *div, int *dbl) 562297627Sjmcneill{ 563297627Sjmcneill uint64_t lcd_fin, lcd_fout; 564297627Sjmcneill clk_t clk_lcd_parent; 565297627Sjmcneill const char *pname; 566297627Sjmcneill int error; 567297627Sjmcneill 568297627Sjmcneill error = clk_get_parent(sc->clk_lcd, &clk_lcd_parent); 569297627Sjmcneill if (error != 0) 570297627Sjmcneill return (error); 571297627Sjmcneill 572297627Sjmcneill /* Get the LCD CH1 special clock 2 divider */ 573297627Sjmcneill error = clk_get_freq(sc->clk_lcd, &lcd_fout); 574297627Sjmcneill if (error != 0) 575297627Sjmcneill return (error); 576297627Sjmcneill error = clk_get_freq(clk_lcd_parent, &lcd_fin); 577297627Sjmcneill if (error != 0) 578297627Sjmcneill return (error); 579297627Sjmcneill *div = lcd_fin / lcd_fout; 580297627Sjmcneill 581297627Sjmcneill /* Detect LCD CH1 special clock using a 1X or 2X source */ 582297627Sjmcneill /* XXX */ 583297627Sjmcneill pname = clk_get_name(clk_lcd_parent); 584297627Sjmcneill if (strcmp(pname, "pll3-1x") == 0 || strcmp(pname, "pll7-1x") == 0) 585297627Sjmcneill *dbl = 0; 586297627Sjmcneill else 587297627Sjmcneill *dbl = 1; 588297627Sjmcneill 589297627Sjmcneill return (0); 590297627Sjmcneill} 591297627Sjmcneill 592297627Sjmcneillstatic int 593296064Sjmcneilla10hdmi_set_videomode(device_t dev, const struct videomode *mode) 594296064Sjmcneill{ 595296064Sjmcneill struct a10hdmi_softc *sc; 596296064Sjmcneill int error, clk_div, clk_dbl; 597296064Sjmcneill int dblscan, hfp, hspw, hbp, vfp, vspw, vbp; 598296064Sjmcneill uint32_t val; 599296064Sjmcneill 600296064Sjmcneill sc = device_get_softc(dev); 601296064Sjmcneill dblscan = !!(mode->flags & VID_DBLSCAN); 602296064Sjmcneill hfp = mode->hsync_start - mode->hdisplay; 603296064Sjmcneill hspw = mode->hsync_end - mode->hsync_start; 604296064Sjmcneill hbp = mode->htotal - mode->hsync_start; 605296064Sjmcneill vfp = mode->vsync_start - mode->vdisplay; 606296064Sjmcneill vspw = mode->vsync_end - mode->vsync_start; 607296064Sjmcneill vbp = mode->vtotal - mode->vsync_start; 608296064Sjmcneill 609297627Sjmcneill error = a10hdmi_get_tcon_config(sc, &clk_div, &clk_dbl); 610297627Sjmcneill if (error != 0) { 611297627Sjmcneill device_printf(dev, "couldn't get tcon config: %d\n", error); 612296064Sjmcneill return (error); 613297627Sjmcneill } 614296064Sjmcneill 615296064Sjmcneill /* Clear interrupt status */ 616296064Sjmcneill HDMI_WRITE(sc, HDMI_INT_STATUS, HDMI_READ(sc, HDMI_INT_STATUS)); 617296064Sjmcneill 618296064Sjmcneill /* Clock setup */ 619296064Sjmcneill val = HDMI_READ(sc, HDMI_PADCTRL1); 620296064Sjmcneill val &= ~PADCTRL1_REG_CKSS; 621296064Sjmcneill val |= (clk_dbl ? PADCTRL1_REG_CKSS_2X : PADCTRL1_REG_CKSS_1X); 622296064Sjmcneill HDMI_WRITE(sc, HDMI_PADCTRL1, val); 623296064Sjmcneill HDMI_WRITE(sc, HDMI_PLLCTRL0, PLLCTRL0_PLL_EN | PLLCTRL0_BWS | 624296064Sjmcneill PLLCTRL0_HV_IS_33 | PLLCTRL0_LDO1_EN | PLLCTRL0_LDO2_EN | 625296064Sjmcneill PLLCTRL0_SDIV2 | PLLCTRL0_VCO_GAIN | PLLCTRL0_S | 626296064Sjmcneill PLLCTRL0_CP_S | PLLCTRL0_CS | PLLCTRL0_PREDIV(clk_div) | 627296064Sjmcneill PLLCTRL0_VCO_S); 628296064Sjmcneill 629296064Sjmcneill /* Setup display settings */ 630297514Sjmcneill if (bootverbose) 631297514Sjmcneill device_printf(dev, "HDMI: %s, Audio: %s\n", 632297514Sjmcneill sc->has_hdmi ? "yes" : "no", sc->has_audio ? "yes" : "no"); 633296789Sjmcneill val = 0; 634296789Sjmcneill if (sc->has_hdmi) 635296789Sjmcneill val |= VID_CTRL_HDMI_MODE; 636296064Sjmcneill if (mode->flags & VID_INTERLACE) 637296064Sjmcneill val |= VID_CTRL_INTERLACE; 638296064Sjmcneill if (mode->flags & VID_DBLSCAN) 639296064Sjmcneill val |= VID_CTRL_REPEATER_2X; 640296064Sjmcneill HDMI_WRITE(sc, HDMI_VID_CTRL, val); 641296064Sjmcneill 642296064Sjmcneill /* Setup display timings */ 643296064Sjmcneill HDMI_WRITE(sc, HDMI_VID_TIMING0, 644296064Sjmcneill VID_ACT_V(mode->vdisplay) | VID_ACT_H(mode->hdisplay << dblscan)); 645296064Sjmcneill HDMI_WRITE(sc, HDMI_VID_TIMING1, 646296064Sjmcneill VID_VBP(vbp) | VID_HBP(hbp << dblscan)); 647296064Sjmcneill HDMI_WRITE(sc, HDMI_VID_TIMING2, 648296064Sjmcneill VID_VFP(vfp) | VID_HFP(hfp << dblscan)); 649296064Sjmcneill HDMI_WRITE(sc, HDMI_VID_TIMING3, 650296064Sjmcneill VID_VSPW(vspw) | VID_HSPW(hspw << dblscan)); 651296064Sjmcneill val = TX_CLOCK_NORMAL; 652296064Sjmcneill if (mode->flags & VID_PVSYNC) 653296064Sjmcneill val |= VID_VSYNC_ACTSEL; 654296064Sjmcneill if (mode->flags & VID_PHSYNC) 655296064Sjmcneill val |= VID_HSYNC_ACTSEL; 656296064Sjmcneill HDMI_WRITE(sc, HDMI_VID_TIMING4, val); 657296064Sjmcneill 658296064Sjmcneill /* This is an ordered list of infoframe packets that the HDMI 659296064Sjmcneill * transmitter will send. Transmit packets in the following order: 660296064Sjmcneill * 1. General control packet 661296064Sjmcneill * 2. AVI infoframe 662296064Sjmcneill * 3. Audio infoframe 663296064Sjmcneill * There are 2 registers with 4 slots each. The list is terminated 664296064Sjmcneill * with the special PKT_END marker. 665296064Sjmcneill */ 666296064Sjmcneill HDMI_WRITE(sc, HDMI_PKTCTRL0, 667296064Sjmcneill PKTCTRL_PACKET(0, PKT_GC) | PKTCTRL_PACKET(1, PKT_AVI) | 668296064Sjmcneill PKTCTRL_PACKET(2, PKT_AI) | PKTCTRL_PACKET(3, PKT_END)); 669296064Sjmcneill HDMI_WRITE(sc, HDMI_PKTCTRL1, 0); 670296064Sjmcneill 671296064Sjmcneill /* Setup audio */ 672296064Sjmcneill a10hdmi_set_audiomode(dev, mode); 673296064Sjmcneill 674296064Sjmcneill return (0); 675296064Sjmcneill} 676296064Sjmcneill 677296064Sjmcneillstatic int 678296064Sjmcneilla10hdmi_enable(device_t dev, int onoff) 679296064Sjmcneill{ 680296064Sjmcneill struct a10hdmi_softc *sc; 681296064Sjmcneill uint32_t val; 682296064Sjmcneill 683296064Sjmcneill sc = device_get_softc(dev); 684296064Sjmcneill 685296064Sjmcneill /* Enable or disable video output */ 686296064Sjmcneill val = HDMI_READ(sc, HDMI_VID_CTRL); 687296064Sjmcneill if (onoff) 688296064Sjmcneill val |= VID_CTRL_VIDEO_EN; 689296064Sjmcneill else 690296064Sjmcneill val &= ~VID_CTRL_VIDEO_EN; 691296064Sjmcneill HDMI_WRITE(sc, HDMI_VID_CTRL, val); 692296064Sjmcneill 693296064Sjmcneill return (0); 694296064Sjmcneill} 695296064Sjmcneill 696296064Sjmcneillstatic device_method_t a10hdmi_methods[] = { 697296064Sjmcneill /* Device interface */ 698296064Sjmcneill DEVMETHOD(device_probe, a10hdmi_probe), 699296064Sjmcneill DEVMETHOD(device_attach, a10hdmi_attach), 700296064Sjmcneill 701296064Sjmcneill /* HDMI interface */ 702296064Sjmcneill DEVMETHOD(hdmi_get_edid, a10hdmi_get_edid), 703296064Sjmcneill DEVMETHOD(hdmi_set_videomode, a10hdmi_set_videomode), 704296064Sjmcneill DEVMETHOD(hdmi_enable, a10hdmi_enable), 705296064Sjmcneill 706296064Sjmcneill DEVMETHOD_END 707296064Sjmcneill}; 708296064Sjmcneill 709296064Sjmcneillstatic driver_t a10hdmi_driver = { 710296064Sjmcneill "a10hdmi", 711296064Sjmcneill a10hdmi_methods, 712296064Sjmcneill sizeof(struct a10hdmi_softc), 713296064Sjmcneill}; 714296064Sjmcneill 715296064Sjmcneillstatic devclass_t a10hdmi_devclass; 716296064Sjmcneill 717296064SjmcneillDRIVER_MODULE(a10hdmi, simplebus, a10hdmi_driver, a10hdmi_devclass, 0, 0); 718296064SjmcneillMODULE_VERSION(a10hdmi, 1); 719