Index: sys/arm/conf/GENERIC =================================================================== --- sys/arm/conf/GENERIC +++ sys/arm/conf/GENERIC @@ -246,6 +246,10 @@ # DRM Video Controllers device drm +device aw_de2_drm + +# DRM Bridges +device dw_hdmi # Pinmux device fdt_pinctrl Index: sys/arm64/conf/GENERIC =================================================================== --- sys/arm64/conf/GENERIC +++ sys/arm64/conf/GENERIC @@ -306,6 +306,10 @@ # DRM Video Controllers device drm +device aw_de2_drm + +# DRM Bridges +device dw_hdmi # EVDEV support device evdev # input event device support Index: sys/conf/files.arm =================================================================== --- sys/conf/files.arm +++ sys/conf/files.arm @@ -175,6 +175,22 @@ no-depend \ compile-with "${CC} -c -o ${.TARGET} ${CFLAGS} -I$S/contrib/alpine-hal -I$S/contrib/alpine-hal/eth ${PROF} ${.IMPSRC}" +# Allwinner Video Controller +dev/drm/allwinner/aw_de2.c optional drm fdt aw_de2_drm +dev/drm/allwinner/aw_de2_drm.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_ui_plane.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_vi_plane.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_mixer.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_tcon.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_hdmi_phy.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_mixer_if.m optional drm fdt aw_de2_drm +dev/drm/allwinner/aw_de2_tcon_if.m optional drm fdt aw_de2_drm + +# Synopsis DesignWare HDMI Controller +dev/drm/bridges/dw_hdmi/aw_de2_dw_hdmi.c optional drm fdt dw_hdmi aw_de2_drm compile-with "${DRM_C}" +dev/drm/bridges/dw_hdmi/dw_hdmi_if.m optional drm fdt dw_hdmi aw_de2_drm +dev/drm/bridges/dw_hdmi/dw_hdmi_phy_if.m optional drm fdt dw_hdmi aw_de2_drm + # DRM dev/drm/core/drm_atomic.c optional drm compile-with "${DRM_C}" dev/drm/core/drm_atomic_helper.c optional drm compile-with "${DRM_C}" Index: sys/conf/files.arm64 =================================================================== --- sys/conf/files.arm64 +++ sys/conf/files.arm64 @@ -73,6 +73,22 @@ arm/allwinner/h6/h6_padconf.c optional soc_allwinner_h6 fdt arm/allwinner/h6/h6_r_padconf.c optional soc_allwinner_h6 fdt +# Allwinner Video Controller +dev/drm/allwinner/aw_de2.c optional drm fdt aw_de2_drm +dev/drm/allwinner/aw_de2_drm.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_ui_plane.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_vi_plane.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_mixer.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_tcon.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_hdmi_phy.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" +dev/drm/allwinner/aw_de2_mixer_if.m optional drm fdt aw_de2_drm +dev/drm/allwinner/aw_de2_tcon_if.m optional drm fdt aw_de2_drm + +# Synopsis DesignWare HDMI Controller +dev/drm/bridges/dw_hdmi/aw_de2_dw_hdmi.c optional drm fdt dw_hdmi aw_de2_drm compile-with "${DRM_C}" +dev/drm/bridges/dw_hdmi/dw_hdmi_if.m optional drm fdt dw_hdmi aw_de2_drm +dev/drm/bridges/dw_hdmi/dw_hdmi_phy_if.m optional drm fdt dw_hdmi aw_de2_drm + arm/annapurna/alpine/alpine_ccu.c optional al_ccu fdt arm/annapurna/alpine/alpine_nb_service.c optional al_nb_service fdt arm/annapurna/alpine/alpine_pci.c optional al_pci fdt Index: sys/dev/drm/allwinner/aw_de2.c =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2.c @@ -0,0 +1,152 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "syscon_if.h" + +#define SRAM_C_SYSCON_OFFSET 0x4 +#define SRAM_C_ASSIGN_TO_DE_VALUE 1 +#define SRAM_C_ASSIGN_TO_DE_SHIFT 24 + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun50i-a64-de2", 1 }, + { NULL, 0 } +}; + +struct aw_de2_softc { + struct simplebus_softc sc; + device_t dev; +}; + +static int aw_de2_probe(device_t dev); +static int aw_de2_attach(device_t dev); +static int aw_de2_detach(device_t dev); + +static int +aw_de2_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Allwinner DE2"); + return (BUS_PROBE_DEFAULT); +} + +static int +aw_de2_attach(device_t dev) +{ + struct aw_de2_softc *sc; + struct syscon *syscon; + device_t cdev; + phandle_t node, child; + phandle_t sram[2]; + uint32_t reg; + + sc = device_get_softc(dev); + sc->dev = dev; + node = ofw_bus_get_node(dev); + + /* Assign sram to DE */ + if (OF_getencprop(node, "allwinner,sram", sram, sizeof(sram)) <= 0) { + device_printf(dev, "Cannot get allwinner,sram property\n"); + return (ENXIO); + } + sram[0] = OF_parent(OF_parent(OF_node_from_xref(sram[0]))); + if (syscon_get_by_ofw_node(dev, sram[0], &syscon) != 0) { + device_printf(dev, "Cannot get syscon node\n"); + return (ENXIO); + } + reg = SYSCON_READ_4(syscon, SRAM_C_SYSCON_OFFSET); + reg &= ~(SRAM_C_ASSIGN_TO_DE_VALUE << SRAM_C_ASSIGN_TO_DE_SHIFT); + SYSCON_WRITE_4(syscon, SRAM_C_SYSCON_OFFSET, reg); + + simplebus_init(dev, node); + if (simplebus_fill_ranges(node, &sc->sc) < 0) { + device_printf(dev, "could not get ranges\n"); + return (ENXIO); + } + + for (child = OF_child(node); child > 0; child = OF_peer(child)) { + cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL); + if (cdev != NULL) + device_probe_and_attach(cdev); + } + + return (bus_generic_attach(dev)); +} + +static int +aw_de2_detach(device_t dev) +{ + + bus_generic_detach(dev); + return (0); +} + +static device_method_t aw_de2_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aw_de2_probe), + DEVMETHOD(device_attach, aw_de2_attach), + DEVMETHOD(device_detach, aw_de2_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_1(aw_de2, aw_de2_driver, aw_de2_methods, + sizeof(struct aw_de2_softc), simplebus_driver); + +static devclass_t aw_de2_devclass; + +EARLY_DRIVER_MODULE(aw_de2, simplebus, aw_de2_driver, + aw_de2_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_FIRST); +MODULE_VERSION(aw_de2, 1); Index: sys/dev/drm/allwinner/aw_de2_drm.c =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_drm.c @@ -0,0 +1,362 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fb_if.h" +#include "aw_de2_mixer_if.h" + +struct aw_de2_drm_softc { + device_t dev; + + struct drm_device drm_dev; + struct drm_fb_cma *fb; +}; + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun50i-a64-display-engine", 1 }, + { "allwinner,sun8i-h3-display-engine", 1 }, + { NULL, 0 } +}; + +static int aw_de2_drm_probe(device_t dev); +static int aw_de2_drm_attach(device_t dev); +static int aw_de2_drm_detach(device_t dev); + +/* DRM driver fops */ +static const struct file_operations aw_de2_drm_drv_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, + .compat_ioctl = drm_compat_ioctl, + .poll = drm_poll, + .read = drm_read, + .mmap = drm_gem_cma_mmap, + /* .llseek = noop_llseek, */ +}; + +static struct drm_driver aw_de2_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_PRIME, + + /* Generic Operations */ + .lastclose = drm_fb_helper_lastclose, + .fops = &aw_de2_drm_drv_fops, + + /* GEM Opeations */ + .dumb_create = drm_gem_cma_dumb_create, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + + .name = "aw-de2-drm", + .desc = "Allwinner DE2 Display Engine", + .date = "20190215", + .major = 1, + .minor = 0, +}; + +static void +aw_de2_drm_output_poll_changed(struct drm_device *drm_dev) +{ + struct aw_de2_drm_softc *sc; + + sc = container_of(drm_dev, struct aw_de2_drm_softc, drm_dev); + if (sc->fb != NULL) + drm_fb_helper_hotplug_event(&sc->fb->fb_helper); +} + +static const struct drm_mode_config_funcs aw_de2_drm_mode_config_funcs = { + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .output_poll_changed = aw_de2_drm_output_poll_changed, + .fb_create = drm_gem_fb_create, +}; + +static struct drm_mode_config_helper_funcs aw_de2_drm_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +static struct fb_info * +drm_fb_cma_helper_getinfo(device_t dev) +{ + struct aw_de2_drm_softc *sc; + + sc = device_get_softc(dev); + if (sc->fb == NULL) + return (NULL); + return (sc->fb->fb_helper.fbdev); +} + +static struct drm_fb_helper_funcs fb_helper_funcs = { + .fb_probe = drm_fb_cma_probe, +}; + +static int +aw_de2_drm_fb_preinit(struct drm_device *drm_dev) +{ + struct drm_fb_cma *fb; + struct aw_de2_drm_softc *sc; + + sc = container_of(drm_dev, struct aw_de2_drm_softc, drm_dev); + + fb = malloc(sizeof(*fb), DRM_MEM_DRIVER, M_WAITOK | M_ZERO); + drm_fb_helper_prepare(drm_dev, &fb->fb_helper, &fb_helper_funcs); + sc->fb = fb; + + return (0); +} + +static int +aw_de2_drm_fb_init(struct drm_device *drm_dev) +{ + struct aw_de2_drm_softc *sc; + int rv; + + sc = container_of(drm_dev, struct aw_de2_drm_softc, drm_dev); + + drm_dev->dev = sc->dev; + + rv = drm_fb_helper_init(drm_dev, &sc->fb->fb_helper, + drm_dev->mode_config.num_connector); + if (rv != 0) { + device_printf(drm_dev->dev, + "Cannot initialize frame buffer %d\n", rv); + return (rv); + } + + rv = drm_fb_helper_single_add_all_connectors(&sc->fb->fb_helper); + if (rv != 0) { + device_printf(drm_dev->dev, "Cannot add all connectors: %d\n", + rv); + goto err_fini; + } + + rv = drm_fb_helper_initial_config(&sc->fb->fb_helper, 32); + if (rv != 0) { + device_printf(drm_dev->dev, + "Cannot set initial config: %d\n", rv); + goto err_fini; + } + + return 0; + +err_fini: + drm_fb_helper_fini(&sc->fb->fb_helper); + return (rv); +} + +static void +aw_de2_drm_fb_destroy(struct drm_device *drm_dev) +{ + struct fb_info *info; + struct drm_fb_cma *fb; + struct aw_de2_drm_softc *sc; + + sc = container_of(drm_dev, struct aw_de2_drm_softc, drm_dev); + fb = sc->fb; + if (fb == NULL) + return; + info = fb->fb_helper.fbdev; + + drm_framebuffer_remove(&fb->drm_fb); + framebuffer_release(info); + drm_fb_helper_fini(&fb->fb_helper); + drm_framebuffer_cleanup(&fb->drm_fb); + + free(fb, DRM_MEM_DRIVER); + sc->fb = NULL; +} + +static void +aw_de2_drm_irq_hook(void *arg) +{ + struct aw_de2_drm_softc *sc; + phandle_t node; + device_t mixerdev; + uint32_t *mixers; + int rv, nmixers, i; + + sc = arg; + + node = ofw_bus_get_node(sc->dev); + + drm_mode_config_init(&sc->drm_dev); + + rv = drm_dev_init(&sc->drm_dev, &aw_de2_drm_driver, + sc->dev); + if (rv != 0) { + device_printf(sc->dev, "drm_dev_init(): %d\n", rv); + return; + } + + nmixers = OF_getencprop_alloc_multi(node, "allwinner,pipelines", + sizeof(*mixers), (void **)&mixers); + if (nmixers <= 0) { + device_printf(sc->dev, "Cannot find mixers in allwinner,pipelines property\n"); + goto fail; + } + + /* Attach the mixer(s) */ + for (i = 0; i < nmixers; i++) { + if (bootverbose) + device_printf(sc->dev, "Lookup mixer with phandle %x\n", + mixers[i]); + mixerdev = OF_device_from_xref(mixers[i]); + if (mixerdev != NULL) + AW_DE2_MIXER_CREATE_PIPELINE(mixerdev, + &sc->drm_dev); + else + device_printf(sc->dev, "Cannot find mixer with phandle %x\n", + mixers[i]); + } + + aw_de2_drm_fb_preinit(&sc->drm_dev); + + drm_vblank_init(&sc->drm_dev, sc->drm_dev.mode_config.num_crtc); + + drm_mode_config_reset(&sc->drm_dev); + sc->drm_dev.mode_config.max_width = 4096; + sc->drm_dev.mode_config.max_height = 4096; + sc->drm_dev.mode_config.funcs = &aw_de2_drm_mode_config_funcs; + sc->drm_dev.mode_config.helper_private = &aw_de2_drm_mode_config_helpers; + + aw_de2_drm_fb_init(&sc->drm_dev); + + drm_kms_helper_poll_init(&sc->drm_dev); + + /* Finally register our drm device */ + rv = drm_dev_register(&sc->drm_dev, 0); + if (rv < 0) + goto fail; + + sc->drm_dev.irq_enabled = true; + + return; +fail: + device_printf(sc->dev, "drm_dev_register(): %d\n", rv); +} + +static int +aw_de2_drm_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Allwinner DE2 Display Engine"); + return (BUS_PROBE_DEFAULT); +} + +static int +aw_de2_drm_attach(device_t dev) +{ + struct aw_de2_drm_softc *sc; + + sc = device_get_softc(dev); + sc->dev = dev; + + config_intrhook_oneshot(&aw_de2_drm_irq_hook, sc); + + return (0); +} + +static int +aw_de2_drm_detach(device_t dev) +{ + struct aw_de2_drm_softc *sc; + + sc = device_get_softc(dev); + + drm_dev_unregister(&sc->drm_dev); + drm_kms_helper_poll_fini(&sc->drm_dev); + drm_atomic_helper_shutdown(&sc->drm_dev); + drm_mode_config_cleanup(&sc->drm_dev); + + return (0); +} + +static device_method_t aw_de2_drm_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aw_de2_drm_probe), + DEVMETHOD(device_attach, aw_de2_drm_attach), + DEVMETHOD(device_detach, aw_de2_drm_detach), + + DEVMETHOD(fb_getinfo, drm_fb_cma_helper_getinfo), + + DEVMETHOD_END +}; + +static driver_t aw_de2_driver = { + "aw_de2_drm", + aw_de2_drm_methods, + sizeof(struct aw_de2_drm_softc), +}; + +static devclass_t aw_de2_drm_devclass; + +DRIVER_MODULE(aw_de2_drm, simplebus, aw_de2_driver, aw_de2_drm_devclass, 0, 0); +MODULE_DEPEND(aw_de2_drm, aw_de2, 1, 1, 1); +MODULE_DEPEND(aw_de2_drm, aw_de2_mixer, 1, 1, 1); +/* Bindings for fbd device. */ +extern devclass_t fbd_devclass; +extern driver_t fbd_driver; +DRIVER_MODULE(fbd, aw_de2_drm, fbd_driver, fbd_devclass, 0, 0); Index: sys/dev/drm/allwinner/aw_de2_hdmi_phy.c =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_hdmi_phy.c @@ -0,0 +1,557 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include "dw_hdmi_phy_if.h" + +#define ANA_CFG1 0x20 +#define ANA_CFG1_ENBI (1 << 0) +#define ANA_CFG1_ENVBS (1 << 1) +#define ANA_CFG1_LDOEN (1 << 2) +#define ANA_CFG1_CKEN (1 << 3) +#define ANA_CFG1_ENP2S_TMDS0 (1 << 4) +#define ANA_CFG1_ENP2S_TMDS1 (1 << 5) +#define ANA_CFG1_ENP2S_TMDS2 (1 << 6) +#define ANA_CFG1_ENP2S_TMDSCLK (1 << 7) +#define ANA_CFG1_BIASEN_TMDS0 (1 << 8) +#define ANA_CFG1_BIASEN_TMDS1 (1 << 9) +#define ANA_CFG1_BIASEN_TMDS2 (1 << 10) +#define ANA_CFG1_BIASEN_TMDSCLK (1 << 11) +#define ANA_CFG1_TXEN_TMDS0 (1 << 12) +#define ANA_CFG1_TXEN_TMDS1 (1 << 13) +#define ANA_CFG1_TXEN_TMDS2 (1 << 14) +#define ANA_CFG1_TXEN_TMDSCLK (1 << 15) +#define ANA_CFG1_TMDSCLK_EN (1 << 16) +#define ANA_CFG1_SCLKTMDS (1 << 17) +#define ANA_CFG1_ENCALOG (1 << 18) +#define ANA_CFG1_ENRCAL (1 << 19) +#define ANA_CFG1_EMPCK_OPT (1 << 20) +#define ANA_CFG1_AMPCK_OPT (1 << 21) +#define ANA_CFG1_EMP_OPT (1 << 22) +#define ANA_CFG1_AMP_OPT (1 << 23) +#define ANA_CFG1_SVBH(x) (x << 24) +#define ANA_CFG1_SVRCAL(x) (x << 26) +#define ANA_CFG1_PWENC (1 << 29) +#define ANA_CFG1_PWEND (1 << 30) +#define ANA_CFG1_SWI (1 << 31) + +#define ANA_CFG2 0x24 +#define ANA_CFG2_RESDI(x) (x << 0) +#define ANA_CFG2_BOOST(x) (x << 6) +#define ANA_CFG2_BOOSTCK(x) (x << 8) +#define ANA_CFG2_SLV(x) (x << 10) +#define ANA_CFG2_CSMPS(x) (x << 13) +#define ANA_CFG2_BIGSW (1 << 15) +#define ANA_CFG2_BIGSWCK (1 << 16) +#define ANA_CFG2_CKSS(x) (x << 17) +#define ANA_CFG2_CD(x) (x << 19) +#define ANA_CFG2_DEN (1 << 21) +#define ANA_CFG2_DENCK (1 << 22) +#define ANA_CFG2_PLR(x) (x << 23) +#define ANA_CFG2_PLRCK (1 << 26) +#define ANA_CFG2_HPDEN (1 << 27) +#define ANA_CFG2_HPDPD (1 << 28) +#define ANA_CFG2_SEN (1 << 29) +#define ANA_CFG2_PLLBEN (1 << 30) +#define ANA_CFG2_M_EN (1 << 31) + +#define ANA_CFG3 0x28 +#define ANA_CFG3_SCLEN (1 << 0) +#define ANA_CFG3_SCLPD (1 << 1) +#define ANA_CFG3_SDAEN (1 << 2) +#define ANA_CFG3_SDAPD (1 << 3) +#define ANA_CFG3_EMP(x) (x << 4) +#define ANA_CFG3_AMP(x) (x << 7) +#define ANA_CFG3_EMPCK(x) (x << 11) +#define ANA_CFG3_AMPCK(x) (x << 14) +#define ANA_CFG3_WIRE(x) (x << 18) +#define ANA_CFG3_SLOW(x) (x << 28) +#define ANA_CFG3_SLOWCK(x) (x << 30) + +#define PLL_CFG1 0x2C +#define PLL_CFG1_B_IN(x) (x << 0) +#define PLL_CFG1_BWS (1 << 6) +#define PLL_CFG1_CNT_INT(x) (x << 7) +#define PLL_CFG1_CP_S(x) (x << 13) +#define PLL_CFG1_CS (1 << 18) +#define PLL_CFG1_PLLDBEN (1 << 19) +#define PLL_CFG1_LDO_VSET(x) (x << 22) +#define PLL_CFG1_PLLEN (1 << 25) +#define PLL_CFG1_CKINSEL_SHIFT 26 +#define PLL_CFG1_HV_IS_33 (1 << 27) +#define PLL_CFG1_LDO1_EN (1 << 28) +#define PLL_CFG1_LDO2_EN (1 << 29) +#define PLL_CFG1_OD (1 << 30) +#define PLL_CFG1_OD1 (1 << 31) + +#define PLL_CFG2 0x30 +#define PLL_CFG2_PREDIV_MASK 0xF +#define PLL_CFG2_S5_7 (1 << 4) +#define PLL_CFG2_S6P25_7P5 (1 << 5) +#define PLL_CFG2_S(x) (x << 6) +#define PLL_CFG2_SDIV2 (1 << 9) +#define PLL_CFG2_SINT_FRAC (1 << 10) +#define PLL_CFG2_VCO_RST_IN (1 << 11) +#define PLL_CFG2_VCO_S(x) (x << 12) +#define PLL_CFG2_VCOGAIN(x) (x << 16) +#define PLL_CFG2_VCOGAIN_EN (1 << 19) +#define PLL_CFG2_VREG1_OUT_EN (1 << 20) +#define PLL_CFG2_VREG2_OUT_EN (1 << 21) +#define PLL_CFG2_AUTOSYNC_DIS (1 << 22) +#define PLL_CFG2_PCLK_SEL (1 << 23) +#define PLL_CFG2_PSET(x) (x << 24) +#define PLL_CFG2_CLKSTEP(x) (x << 27) +#define PLL_CFG2_PDCLKSEL(x) (x << 29) +#define PLL_CFG2_SV_H (1 << 31) + +#define PLL_CFG3 0x34 +#define PLL_CFG3_SOUT_DIV2 (1 << 0) + +#define ANA_STS 0x38 +#define ANA_STS_RESDO2D_MASK 0x3F +#define ANA_STS_COUT2D (1 << 6) +#define ANA_STS_RCALEND2D (1 << 7) +#define ANA_STS_PHYRXSENSE (1 << 8) +#define ANA_STS_LOCK_FLAG2 (1 << 9) +#define ANA_STS_LOCK_FLAG1 (1 << 10) +#define ANA_STS_B_OUT_MASK 0x1F800 +#define ANA_STS_B_OUT_SHIFT 11 +#define ANA_STS_ERROR_DET_SF (1 << 17) +#define ANA_STS_ERROR_SF (1 << 18) +#define ANA_STS_HPDO (1 << 19) + +#define READ_EN 0x10 +#define READ_EN_MAGIC 0x54524545 + +#define UNSCRAMBLE 0x14 +#define UNSCRAMBLE_MAGIC 0x42494E47 + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun50i-a64-hdmi-phy", 1 }, + { "allwinner,sun8i-h3-hdmi-phy", 1 }, + { NULL, 0 } +}; + +static struct resource_spec aw_de2_hdmi_phy_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { -1, 0 } +}; + +struct aw_de2_hdmi_phy_softc { + device_t dev; + struct resource *res[1]; + clk_t clk_bus; + clk_t clk_mod; + clk_t clk_pll; + hwreset_t reset; + + uint32_t rcal; +}; + +#define AW_DE2_HDMI_PHY_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) +#define AW_DE2_HDMI_PHY_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) + +static int aw_de2_hdmi_phy_probe(device_t dev); +static int aw_de2_hdmi_phy_attach(device_t dev); +static int aw_de2_hdmi_phy_detach(device_t dev); + +static inline void +PHY_UPDATE_BITS(struct aw_de2_hdmi_phy_softc *sc, uint32_t reg, uint32_t mask, uint32_t val) +{ + uint32_t tmp; + + tmp = AW_DE2_HDMI_PHY_READ_4(sc, reg); + tmp &= ~mask; + tmp |= val; + AW_DE2_HDMI_PHY_WRITE_4(sc, reg, tmp); +} + +static inline void +aw_de2_hdmi_phy_dump_regs(struct aw_de2_hdmi_phy_softc *sc) +{ + + DRM_DEBUG_DRIVER("%s: ANA_CFG1: %x\n", __func__, + AW_DE2_HDMI_PHY_READ_4(sc, ANA_CFG1)); + DRM_DEBUG_DRIVER("%s: ANA_CFG2: %x\n", __func__, + AW_DE2_HDMI_PHY_READ_4(sc, ANA_CFG2)); + DRM_DEBUG_DRIVER("%s: ANA_CFG3: %x\n", __func__, + AW_DE2_HDMI_PHY_READ_4(sc, ANA_CFG3)); + DRM_DEBUG_DRIVER("%s: PLL_CFG1: %x\n", __func__, + AW_DE2_HDMI_PHY_READ_4(sc, PLL_CFG1)); + DRM_DEBUG_DRIVER("%s: PLL_CFG2: %x\n", __func__, + AW_DE2_HDMI_PHY_READ_4(sc, PLL_CFG2)); + DRM_DEBUG_DRIVER("%s: PLL_CFG3: %x\n", __func__, + AW_DE2_HDMI_PHY_READ_4(sc, PLL_CFG3)); + DRM_DEBUG_DRIVER("%s: ANA_STS: %x\n", __func__, + AW_DE2_HDMI_PHY_READ_4(sc, ANA_STS)); + +} + +static int +aw_de2_hdmi_phy_init(device_t dev) +{ + struct aw_de2_hdmi_phy_softc *sc; + uint32_t reg; + int timeout = 1000; + + sc = device_get_softc(dev); + + aw_de2_hdmi_phy_dump_regs(sc); + + AW_DE2_HDMI_PHY_WRITE_4(sc, ANA_CFG1, 0); + PHY_UPDATE_BITS(sc, ANA_CFG1, ANA_CFG1_ENBI, ANA_CFG1_ENBI); + DELAY(5); + + /* Enable TMDS clock and voltage reference module */ + PHY_UPDATE_BITS(sc, ANA_CFG1, ANA_CFG1_TMDSCLK_EN, ANA_CFG1_TMDSCLK_EN); + PHY_UPDATE_BITS(sc, ANA_CFG1, ANA_CFG1_ENVBS, ANA_CFG1_ENVBS); + DELAY(20); + + /* Enable LDO */ + PHY_UPDATE_BITS(sc, ANA_CFG1, ANA_CFG1_LDOEN, ANA_CFG1_LDOEN); + DELAY(5); + + /* Enable clock */ + PHY_UPDATE_BITS(sc, ANA_CFG1, ANA_CFG1_CKEN, ANA_CFG1_CKEN); + DELAY(100); + + /* Enable calibration */ + PHY_UPDATE_BITS(sc, ANA_CFG1, ANA_CFG1_ENCALOG, ANA_CFG1_ENCALOG); + DELAY(100); + PHY_UPDATE_BITS(sc, ANA_CFG1, ANA_CFG1_ENRCAL, ANA_CFG1_ENRCAL); + + /* TMDS lanes */ + PHY_UPDATE_BITS(sc, ANA_CFG1, + ANA_CFG1_ENP2S_TMDS0 | + ANA_CFG1_ENP2S_TMDS1 | + ANA_CFG1_ENP2S_TMDS2, + ANA_CFG1_ENP2S_TMDS0 | + ANA_CFG1_ENP2S_TMDS1 | + ANA_CFG1_ENP2S_TMDS2); + + DRM_DEBUG_DRIVER("Waiting calibration\n"); + if (drm_debug & DRM_UT_DRIVER) + aw_de2_hdmi_phy_dump_regs(sc); + + /* Wait for calibration to finish */ + while (timeout > 0) { + reg = AW_DE2_HDMI_PHY_READ_4(sc, ANA_STS); + if (reg & ANA_STS_RCALEND2D) { + DRM_DEBUG_DRIVER("Calibration ok: %x\n", reg); + break; + } + timeout--; + DELAY(1000); + } + if (timeout == 0) { + DRM_DEBUG_DRIVER("Calibration failed\n"); + } + if (drm_debug & DRM_UT_DRIVER) + aw_de2_hdmi_phy_dump_regs(sc); + + PHY_UPDATE_BITS(sc, ANA_CFG1, + ANA_CFG1_BIASEN_TMDS0 | + ANA_CFG1_BIASEN_TMDS1 | + ANA_CFG1_BIASEN_TMDS2 | + ANA_CFG1_BIASEN_TMDSCLK, + ANA_CFG1_BIASEN_TMDS0 | + ANA_CFG1_BIASEN_TMDS1 | + ANA_CFG1_BIASEN_TMDS2 | + ANA_CFG1_BIASEN_TMDSCLK); + + PHY_UPDATE_BITS(sc, ANA_CFG1, + ANA_CFG1_ENP2S_TMDSCLK, + ANA_CFG1_ENP2S_TMDSCLK); + + AW_DE2_HDMI_PHY_WRITE_4(sc, PLL_CFG1, 0x39dc5040); + AW_DE2_HDMI_PHY_WRITE_4(sc, PLL_CFG2, 0x80084342); + DELAY(1000); + AW_DE2_HDMI_PHY_WRITE_4(sc, PLL_CFG3, 1); + + PHY_UPDATE_BITS(sc, PLL_CFG1, PLL_CFG1_PLLEN, PLL_CFG1_PLLEN); + DELAY(1000); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_hdmi_phy_dump_regs(sc); + + reg = (AW_DE2_HDMI_PHY_READ_4(sc, ANA_STS) & ANA_STS_B_OUT_MASK) >> ANA_STS_B_OUT_SHIFT; + DRM_DEBUG_DRIVER("B_OUT: %x\n", reg); + PHY_UPDATE_BITS(sc, PLL_CFG1, PLL_CFG1_OD | PLL_CFG1_OD1, PLL_CFG1_OD | PLL_CFG1_OD1); + PHY_UPDATE_BITS(sc, PLL_CFG1, PLL_CFG1_B_IN(reg), PLL_CFG1_B_IN(reg)); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_hdmi_phy_dump_regs(sc); + + /* Magic values from the datasheet for 297Mhz TMDS rate, will need to split those into known bits */ + DRM_DEBUG_DRIVER("Write ANA_CFG* values\n"); + AW_DE2_HDMI_PHY_WRITE_4(sc, ANA_CFG1, 0x01FFFF7F); + AW_DE2_HDMI_PHY_WRITE_4(sc, ANA_CFG2, 0x8063B000); + AW_DE2_HDMI_PHY_WRITE_4(sc, ANA_CFG3, 0x0F8246B5); + + /* Read calibration value */ + sc->rcal = AW_DE2_HDMI_PHY_READ_4(sc, ANA_STS) & ANA_STS_RESDO2D_MASK; + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_hdmi_phy_dump_regs(sc); + + /* unscrambler the hdmi registers */ + AW_DE2_HDMI_PHY_WRITE_4(sc, READ_EN, + READ_EN_MAGIC); + AW_DE2_HDMI_PHY_WRITE_4(sc, UNSCRAMBLE, + UNSCRAMBLE_MAGIC); + + return (0); +} + +static int +aw_de2_hdmi_phy_config(device_t dev, struct drm_display_mode *mode) +{ + struct aw_de2_hdmi_phy_softc *sc; + uint32_t ana_cfg1, ana_cfg2, ana_cfg3; + uint32_t pll_cfg1, pll_cfg2, pll_cfg3; + uint32_t reg; + + sc = device_get_softc(dev); + + DRM_DEBUG_DRIVER("Pixel clock: %d\n", mode->crtc_clock); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_hdmi_phy_dump_regs(sc); + + /* Pixel clock is in kHz */ + /* Magic value from the datasheet */ + if (mode->crtc_clock <= 27000) { + pll_cfg1 = 0x3DDC5040; + pll_cfg2 = 0x8008430A; + pll_cfg3 = 0x1; + ana_cfg1 = 0x11FFFF7F; + ana_cfg2 = 0x80623000 | (sc->rcal >> 2); + ana_cfg3 = 0x0F80C285; + } + else if (mode->crtc_clock <= 74250) { + pll_cfg1 = 0x3DDC5040; + pll_cfg2 = 0x80084342; + pll_cfg3 = 0x1; + ana_cfg1 = 0x11FFFF7F; + ana_cfg2 = 0x80623000 | (sc->rcal >> 2); + ana_cfg3 = 0x0F814385; + } + else if (mode->crtc_clock <= 148500) { + pll_cfg1 = 0x3DDC5040; + pll_cfg2 = 0x80084381; + pll_cfg3 = 0x1; + ana_cfg1 = 0x01FFFF7F; + ana_cfg2 = 0x8063a800; + ana_cfg3 = 0x0F81C485; + } + else { + /* mode->crtc_clock <= 297000 */ + pll_cfg1 = 0x35DC5FC0; + pll_cfg2 = 0x800863C0; + pll_cfg3 = 0x1; + ana_cfg1 = 0x01FFFF7F; + ana_cfg2 = 0x8063B000; + ana_cfg3 = 0x0F8246B5; + } + + /* We can only use pll-0 so select it */ + AW_DE2_HDMI_PHY_WRITE_4(sc, PLL_CFG1, pll_cfg1 & ~PLL_CFG1_CKINSEL_SHIFT); + AW_DE2_HDMI_PHY_WRITE_4(sc, PLL_CFG2, pll_cfg2); + DELAY(1000); + AW_DE2_HDMI_PHY_WRITE_4(sc, PLL_CFG3, pll_cfg3); + + PHY_UPDATE_BITS(sc, PLL_CFG1, PLL_CFG1_PLLEN, PLL_CFG1_PLLEN); + DELAY(1000); + + reg = (AW_DE2_HDMI_PHY_READ_4(sc, ANA_STS) & ANA_STS_B_OUT_MASK) >> ANA_STS_B_OUT_SHIFT; + DRM_DEBUG_DRIVER("B_OUT: %x\n", reg); + PHY_UPDATE_BITS(sc, PLL_CFG1, PLL_CFG1_OD | PLL_CFG1_OD1, PLL_CFG1_OD | PLL_CFG1_OD1); + PHY_UPDATE_BITS(sc, PLL_CFG1, PLL_CFG1_B_IN(reg), PLL_CFG1_B_IN(reg)); + + AW_DE2_HDMI_PHY_WRITE_4(sc, ANA_CFG1, ana_cfg1); + AW_DE2_HDMI_PHY_WRITE_4(sc, ANA_CFG2, ana_cfg2); + AW_DE2_HDMI_PHY_WRITE_4(sc, ANA_CFG3, ana_cfg3); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_hdmi_phy_dump_regs(sc); + + return (0); +} + +static bool +aw_de2_hdmi_phy_detect_hpd(device_t dev) +{ + struct aw_de2_hdmi_phy_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + + reg = AW_DE2_HDMI_PHY_READ_4(sc, ANA_STS); + DRM_DEBUG_DRIVER("%s: ANA_STS: %x\n", __func__, reg); + + return (reg & ANA_STS_HPDO); +} + +static int +aw_de2_hdmi_phy_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Allwinner SUN8I HDMI PHY"); + return (BUS_PROBE_DEFAULT); +} + +static int +aw_de2_hdmi_phy_attach(device_t dev) +{ + struct aw_de2_hdmi_phy_softc *sc; + phandle_t node; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + node = ofw_bus_get_node(dev); + + if (bus_alloc_resources(dev, aw_de2_hdmi_phy_spec, sc->res) != 0) { + device_printf(dev, "cannot allocate resources for device\n"); + error = ENXIO; + goto fail; + } + + if ((error = clk_get_by_ofw_name(dev, node, "bus", &sc->clk_bus)) != 0) { + device_printf(dev, "Cannot get bus clock\n"); + goto fail; + } + if (clk_enable(sc->clk_bus) != 0) { + device_printf(dev, "Cannot enable bus clock\n"); + goto fail; + } + if ((error = clk_get_by_ofw_name(dev, node, "mod", &sc->clk_mod)) != 0) { + device_printf(dev, "Cannot get mod clock\n"); + goto fail; + } + if (clk_enable(sc->clk_mod) != 0) { + device_printf(dev, "Cannot enable mod clock\n"); + goto fail; + } + if ((error = clk_get_by_ofw_name(dev, node, "pll-0", &sc->clk_pll)) != 0) { + device_printf(dev, "Cannot get pll-0 clock\n"); + goto fail; + } + if (clk_enable(sc->clk_pll) != 0) { + device_printf(dev, "Cannot enable pll-0 clock\n"); + goto fail; + } + if ((error = hwreset_get_by_ofw_name(dev, node, "phy", &sc->reset)) != 0) { + device_printf(dev, "Cannot get reset\n"); + goto fail; + } + if (hwreset_deassert(sc->reset) != 0) { + device_printf(dev, "Cannot deassert reset\n"); + goto fail; + } + + /* Register ourself */ + OF_device_register_xref(OF_xref_from_node(node), dev); + + return (0); + +fail: + aw_de2_hdmi_phy_detach(dev); + return (error); +} + +static int +aw_de2_hdmi_phy_detach(device_t dev) +{ + struct aw_de2_hdmi_phy_softc *sc; + sc = device_get_softc(dev); + + if (hwreset_assert(sc->reset) != 0) + device_printf(dev, "Cannot assert reset\n"); + if (clk_disable(sc->clk_pll) != 0) + device_printf(dev, "Cannot disable pll-0 clock\n"); + if (clk_disable(sc->clk_mod) != 0) + device_printf(dev, "Cannot disable mod clock\n"); + if (clk_disable(sc->clk_bus) != 0) + device_printf(dev, "Cannot disable bus clock\n"); + + bus_release_resources(dev, aw_de2_hdmi_phy_spec, sc->res); + + return (0); +} + +static device_method_t aw_de2_hdmi_phy_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aw_de2_hdmi_phy_probe), + DEVMETHOD(device_attach, aw_de2_hdmi_phy_attach), + DEVMETHOD(device_detach, aw_de2_hdmi_phy_detach), + + /* DW HDMI Phy interface */ + DEVMETHOD(dw_hdmi_phy_init, aw_de2_hdmi_phy_init), + DEVMETHOD(dw_hdmi_phy_config, aw_de2_hdmi_phy_config), + DEVMETHOD(dw_hdmi_phy_detect_hpd, aw_de2_hdmi_phy_detect_hpd), + + DEVMETHOD_END +}; + +static driver_t aw_de2_hdmi_phy_driver = { + "aw_de2_hdmi_phy", + aw_de2_hdmi_phy_methods, + sizeof(struct aw_de2_hdmi_phy_softc), +}; + +static devclass_t aw_de2_hdmi_phy_devclass; + +EARLY_DRIVER_MODULE(aw_de2_hdmi_phy, simplebus, aw_de2_hdmi_phy_driver, + aw_de2_hdmi_phy_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_FIRST); +MODULE_VERSION(aw_de2_hdmi_phy, 1); Index: sys/dev/drm/allwinner/aw_de2_mixer.h =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_mixer.h @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#ifndef _AW_DE2_MIXER_H_ +#define _AW_DE2_MIXER_H_ + +#include +#include + +/* Global Control register */ +#define GBL_CTL 0x00 +#define GBL_CTL_EN (1 << 0) +#define GBL_CTL_FINISH_IRQ_EN (1 << 4) +#define GBL_CTL_ERROR_IRQ_EN (1 << 5) + +#define GBL_STS 0x04 +#define GBL_STS_FINISH_IRQ (1 << 0) +#define GBL_STS_ERROR_IRQ (1 << 1) +#define GBL_STS_BUSY (1 << 4) +#define GBL_STS_ERROR (1 << 5) +#define GBL_STS_ODD_FIELD (1 << 8) + +#define GBL_DBUFFER 0x08 + +#define GBL_SIZE 0x0C +#define GBL_SIZE_HEIGHT(x) (((x & 0xFFFF0000) >> 16) + 1) +#define GBL_SIZE_WIDTH(x) ((x & 0xFFFF) + 1) + +/* Blender registers defines */ +#define BLD_BASE 0x1000 +#define BLD_PIPE_CTL (BLD_BASE + 0x0) +#define BLD_FILL_COLOR(pipe) (BLD_BASE + pipe * 0x10 + 0x4) +#define BLD_INSIZE(pipe) (BLD_BASE + pipe * 0x10 + 0x8) +#define BLD_COORD(pipe) (BLD_BASE + pipe * 0x10 + 0xC) +#define BLD_CH_ROUTING (BLD_BASE + 0x80) +#define BLD_OUTSIZE (BLD_BASE + 0x8C) + +#define AW_DE2_MIXER_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) +#define AW_DE2_MIXER_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) + +struct aw_de2_mixer_config { + char *name; + size_t vi_planes; + size_t ui_planes; + int dst_tcon; +}; + +struct aw_de2_mixer_plane { + struct drm_plane plane; + struct aw_de2_mixer_softc *sc; + int id; +}; + +struct aw_de2_mixer_softc { + device_t dev; + struct resource *res[1]; + struct aw_de2_mixer_config *conf; + + clk_t clk_bus; + clk_t clk_mod; + hwreset_t reset; + + struct aw_de2_mixer_plane *vi_planes; + struct aw_de2_mixer_plane *ui_planes; + + device_t tcon; +}; + +#endif /* _AW_DE2_MIXER_H_ */ Index: sys/dev/drm/allwinner/aw_de2_mixer.c =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_mixer.c @@ -0,0 +1,347 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "aw_de2_mixer_if.h" +#include "aw_de2_tcon_if.h" + +static const struct aw_de2_mixer_config a64_mixer0_config = { + .name = "Allwinner DE2 A64-Mixer 0", + .vi_planes = 1, + .ui_planes = 3, + .dst_tcon = 0, +}; + +static const struct aw_de2_mixer_config a64_mixer1_config = { + .name = "Allwinner DE2 A64-Mixer 1", + .vi_planes = 1, + .ui_planes = 1, + .dst_tcon = 1, +}; + +static const struct aw_de2_mixer_config h3_mixer0_config = { + .name = "Allwinner DE2 H3-Mixer 0", + .vi_planes = 1, + .ui_planes = 1, + .dst_tcon = 0, +}; + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun50i-a64-de2-mixer-0", (uintptr_t)&a64_mixer0_config }, + { "allwinner,sun50i-a64-de2-mixer-1", (uintptr_t)&a64_mixer1_config }, + { "allwinner,sun8i-h3-de2-mixer-0", (uintptr_t)&h3_mixer0_config }, + { NULL, 0 } +}; + +static struct resource_spec aw_de2_mixer_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { -1, 0 } +}; + +#define AW_DE2_MIXER_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) +#define AW_DE2_MIXER_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) + +static int aw_de2_mixer_attach(device_t dev); +static int aw_de2_mixer_detach(device_t dev); +static int aw_de2_mixer_commit(device_t dev); +static int aw_de2_mixer_create_pipeline(device_t dev, struct drm_device *drm); + +static inline void +aw_de2_mixer_dump_regs(struct aw_de2_mixer_softc *sc) +{ + uint32_t reg, pipe_ctl, pipe_routing; + int i; + + reg = AW_DE2_MIXER_READ_4(sc, GBL_CTL); + DRM_DEBUG_DRIVER("%s: Mixer %sabled\n", __func__, + reg & GBL_CTL_EN ? "En" : "Dis"); + + reg = AW_DE2_MIXER_READ_4(sc, GBL_SIZE); + DRM_DEBUG_DRIVER("%s: Mixer Global Size %dx%d\n", __func__, + GBL_SIZE_WIDTH(reg), GBL_SIZE_HEIGHT(reg)); + + for (i = 0; i < 4; i++) { + reg = AW_DE2_MIXER_READ_4(sc, BLD_FILL_COLOR(i)); + DRM_DEBUG_DRIVER("%s: BLD_FILL_COLOR(%d): %x\n", __func__, + i, reg); + } + for (i = 0; i < 4; i++) { + reg = AW_DE2_MIXER_READ_4(sc, BLD_INSIZE(i)); + DRM_DEBUG_DRIVER("%s: BLD_INSIZE(%d): %x (%dx%d)\n", __func__, + i, reg, + (reg & OVL_VI_SIZE_WIDTH_MASK) >> OVL_VI_SIZE_WIDTH_SHIFT, + (reg & OVL_VI_SIZE_HEIGHT_MASK) >> OVL_VI_SIZE_HEIGHT_SHIFT); + } + for (i = 0; i < 4; i++) { + reg = AW_DE2_MIXER_READ_4(sc, BLD_COORD(i)); + DRM_DEBUG_DRIVER("%s: BLD_COORD(%d): %x\n", __func__, i, reg); + } + reg = AW_DE2_MIXER_READ_4(sc, BLD_OUTSIZE); + DRM_DEBUG_DRIVER("%s: BLD_OUTSIZE: %x (%dx%d)\n", __func__, reg, + (reg & OVL_VI_SIZE_WIDTH_MASK) >> OVL_VI_SIZE_WIDTH_SHIFT, + (reg & OVL_VI_SIZE_HEIGHT_MASK) >> OVL_VI_SIZE_HEIGHT_SHIFT); + pipe_ctl = AW_DE2_MIXER_READ_4(sc, BLD_PIPE_CTL); + DRM_DEBUG_DRIVER("%s: BLD_PIPE_CTL: %x\n", __func__, pipe_ctl); + pipe_routing = AW_DE2_MIXER_READ_4(sc, BLD_CH_ROUTING); + DRM_DEBUG_DRIVER("%s: BLD_CH_ROUTING: %x\n", __func__, pipe_routing); + for (i = 0; i < 4; i++) { + DRM_DEBUG_DRIVER("%s: Pipe %d %sabled\n", __func__, + i, pipe_ctl & (1 << (i + 8)) ? "En" : "Dis"); + DRM_DEBUG_DRIVER("%s: Pipe %d Fill color %sabled\n", __func__, + i, pipe_ctl & (1 << i) ? "En" : "Dis"); + } + DRM_DEBUG_DRIVER("%s: Pipe0 routed from channel %d\n", __func__, + pipe_routing & 0xF); + DRM_DEBUG_DRIVER("%s: Pipe1 routed from channel %d\n", __func__, + (pipe_routing & 0xF0) >> 4); + DRM_DEBUG_DRIVER("%s: Pipe2 routed from channel %d\n", __func__, + (pipe_routing & 0xF00) >> 8); + DRM_DEBUG_DRIVER("%s: Pipe3 routed from channel %d\n", __func__, + (pipe_routing & 0xF000) >> 12); +} + +static int +aw_de2_mixer_probe(device_t dev) +{ + struct aw_de2_mixer_softc *sc; + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + sc = device_get_softc(dev); + sc->conf = (struct aw_de2_mixer_config *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + if (sc->conf == 0) + return (ENXIO); + + /* If we can't get the tcon now no need to attach */ + sc->tcon = ofw_graph_get_device_by_port_ep(ofw_bus_get_node(dev), + 1, sc->conf->dst_tcon); + if (sc->tcon == NULL) { + if (bootverbose) + device_printf(dev, "%s: Cannot find tcon, aborting\n", + sc->conf->name); + return (ENXIO); + } + + device_set_desc(dev, sc->conf->name); + return (BUS_PROBE_DEFAULT); +} + +static int +aw_de2_mixer_attach(device_t dev) +{ + struct aw_de2_mixer_softc *sc; + phandle_t node; + int error, i; + + node = ofw_bus_get_node(dev); + sc = device_get_softc(dev); + sc->dev = dev; + + sc->conf = (struct aw_de2_mixer_config *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + + if (bus_alloc_resources(dev, aw_de2_mixer_spec, sc->res) != 0) { + device_printf(dev, "cannot allocate resources for device\n"); + error = ENXIO; + goto fail; + } + + if ((error = clk_get_by_ofw_name(dev, node, "bus", &sc->clk_bus)) != 0) { + device_printf(dev, "Cannot get bus clock\n"); + error = ENXIO; + goto fail; + } + if (clk_enable(sc->clk_bus) != 0) { + device_printf(dev, "Cannot enable bus clock\n"); + error = ENXIO; + goto fail; + } + if ((error = clk_get_by_ofw_name(dev, node, "mod", &sc->clk_mod)) != 0) { + device_printf(dev, "Cannot get mod clock\n"); + error = ENXIO; + goto fail; + } + if (clk_enable(sc->clk_mod) != 0) { + device_printf(dev, "Cannot enable mod clock\n"); + error = ENXIO; + goto fail; + } + if ((error = hwreset_get_by_ofw_idx(dev, node, 0, &sc->reset)) != 0) { + device_printf(dev, "Cannot get reset\n"); + goto fail; + } + if ((error = hwreset_deassert(sc->reset)) != 0) { + device_printf(dev, "Cannot deassert reset\n"); + goto fail; + } + + sc->tcon = ofw_graph_get_device_by_port_ep(node, 1, sc->conf->dst_tcon); + if (sc->tcon == NULL) { + device_printf(dev, "Cannot get device from remote endpoint\n"); + error = ENXIO; + goto fail; + } + AW_DE2_TCON_SET_MIXER(sc->tcon, dev); + + /* Register ourself so aw_de can resolve who we are */ + OF_device_register_xref(OF_xref_from_node(node), dev); + + sc->vi_planes = malloc(sizeof(struct aw_de2_mixer_plane) * sc->conf->vi_planes, + DRM_MEM_DRIVER, M_WAITOK | M_ZERO); + sc->ui_planes = malloc(sizeof(struct aw_de2_mixer_plane) * sc->conf->ui_planes, + DRM_MEM_DRIVER, M_WAITOK | M_ZERO); + + /* Clear all regs */ + for (i = 0; i < 0x6000; i += 4) + AW_DE2_MIXER_WRITE_4(sc, i, 0x0); + + /* Set all pipes X to select from channel X */ + AW_DE2_MIXER_WRITE_4(sc, BLD_CH_ROUTING, 0x3210); + + /* Enable the mixer */ + AW_DE2_MIXER_WRITE_4(sc, GBL_CTL, GBL_CTL_EN); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_mixer_dump_regs(sc); + + return (0); + +fail: + aw_de2_mixer_detach(dev); + return (error); +} + +static int +aw_de2_mixer_detach(device_t dev) +{ + struct aw_de2_mixer_softc *sc; + phandle_t node; + + sc = device_get_softc(dev); + node = ofw_bus_get_node(dev); + + if (sc->vi_planes) + free(sc->vi_planes, DRM_MEM_DRIVER); + if (sc->ui_planes) + free(sc->ui_planes, DRM_MEM_DRIVER); + clk_release(sc->clk_mod); + clk_release(sc->clk_bus); + hwreset_release(sc->reset); + + bus_release_resources(dev, aw_de2_mixer_spec, sc->res); + + return (0); +} + +static device_method_t aw_de2_mixer_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aw_de2_mixer_probe), + DEVMETHOD(device_attach, aw_de2_mixer_attach), + DEVMETHOD(device_detach, aw_de2_mixer_detach), + + /* Mixer interface */ + DEVMETHOD(aw_de2_mixer_create_pipeline, aw_de2_mixer_create_pipeline), + DEVMETHOD(aw_de2_mixer_commit, aw_de2_mixer_commit), + DEVMETHOD_END +}; + +static driver_t aw_de2_mixer_driver = { + "aw_de2_mixer", + aw_de2_mixer_methods, + sizeof(struct aw_de2_mixer_softc), +}; + +static devclass_t aw_de2_mixer_devclass; + +EARLY_DRIVER_MODULE(aw_de2_mixer, simplebus, aw_de2_mixer_driver, + aw_de2_mixer_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_LATE); +MODULE_DEPEND(aw_de2_mixer, aw_de2_tcon, 1, 1, 1); +MODULE_VERSION(aw_de2_mixer, 1); + +static int +aw_de2_mixer_commit(device_t dev) +{ + struct aw_de2_mixer_softc *sc; + + sc = device_get_softc(dev); + + AW_DE2_MIXER_WRITE_4(sc, 0x08, 1); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_mixer_dump_regs(sc); + + return (0); +} + +static int +aw_de2_mixer_create_pipeline(device_t dev, struct drm_device *drm) +{ + struct aw_de2_mixer_softc *sc; + + sc = device_get_softc(dev); + + /* Create the different planes available */ + aw_de2_ui_plane_create(sc, drm); + aw_de2_vi_plane_create(sc, drm); + + /* + * Init the crtc + * UI 0 and VI are the only plane available in both mixers + */ + AW_DE2_TCON_CREATE_CRTC(sc->tcon, drm, + &sc->ui_planes[0].plane, &sc->vi_planes[0].plane); + + return (0); +} Index: sys/dev/drm/allwinner/aw_de2_mixer_if.m =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_mixer_if.m @@ -0,0 +1,48 @@ +#- +# Copyright (c) 2019 Emmanuel Vadot +# +# 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. +# +# $FreeBSD$ +# + +INTERFACE aw_de2_mixer; + +HEADER { + struct drm_device; + struct drm_plane; +} + +# +# Create and register the pipeline in the drm subsystem +# +METHOD int create_pipeline { + device_t dev; + struct drm_device *drm_dev; +}; + +# +# Commit changes to the mixer +# +METHOD int commit { + device_t dev; +}; Index: sys/dev/drm/allwinner/aw_de2_tcon.h =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_tcon.h @@ -0,0 +1,100 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#ifndef _AW_DE2_TCON_H_ +#define _AW_DE2_TCON_H_ + +#define TCON_GCTL 0x00 +#define TCON_GCTL_GAMMA_EN (1 << 30) +#define TCON_GCTL_EN (1 << 31) + +#define TCON_GINT0 0x04 +#define TCON_GINT0_LINE_FLAG (1 << 12) +#define TCON0_GINT0_VB_FLAG (1 << 14) +#define TCON1_GINT0_VB_FLAG (1 << 15) +#define TCON_GINT0_LINE_EN (1 << 28) +#define TCON0_GINT0_VB_EN (1 << 30) +#define TCON1_GINT0_VB_EN (1 << 31) + +#define TCON_GINT1 0x08 +#define TCON_GINT1_LINE_NUM_MASK 0xFFF + +#define TCON_CTL 0x90 +#define TCON_CTL_EN (1 << 31) +#define TCON_CTL_DELAY_MASK 0x1F0 +#define TCON_CTL_DELAY_SHIFT 4 + +#define TCON_TIMING0 0x94 +#define TCON_TIMING0_YI_MASK 0xFFF +#define TCON_TIMING0_YI_SHIFT 0 +#define TCON_TIMING0_YI(x) ((x - 1) & TCON_TIMING0_YI_MASK) +#define TCON_TIMING0_XI_MASK 0xFFF0000 +#define TCON_TIMING0_XI_SHIFT 16 +#define TCON_TIMING0_XI(x) (((x - 1) << TCON_TIMING0_XI_SHIFT) & TCON_TIMING0_XI_MASK) + +#define TCON_TIMING1 0x98 +#define TCON_TIMING1_LS_YO_MASK 0xFFF +#define TCON_TIMING1_LS_YO_SHIFT 0 +#define TCON_TIMING1_LS_YO(x) ((x - 1) & TCON_TIMING1_LS_YO_MASK) +#define TCON_TIMING1_LS_XO_MASK 0xFFF0000 +#define TCON_TIMING1_LS_XO_SHIFT 16 +#define TCON_TIMING1_LS_XO(x) (((x - 1) << TCON_TIMING1_LS_XO_SHIFT) & TCON_TIMING1_LS_XO_MASK) + +#define TCON_TIMING2 0x9C +#define TCON_TIMING2_YO_MASK 0xFFF +#define TCON_TIMING2_YO_SHIFT 0 +#define TCON_TIMING2_YO(x) ((x - 1) & TCON_TIMING2_YO_MASK) +#define TCON_TIMING2_XO_MASK 0xFFF0000 +#define TCON_TIMING2_XO_SHIFT 16 +#define TCON_TIMING2_XO(x) (((x - 1) << TCON_TIMING2_XO_SHIFT) & TCON_TIMING2_XO_MASK) + +#define TCON_TIMING3 0xA0 +#define TCON_TIMING3_HBP_MASK 0xFFF +#define TCON_TIMING3_HBP_SHIFT 0 +#define TCON_TIMING3_HBP(x) ((x - 1) & TCON_TIMING3_HBP_MASK) +#define TCON_TIMING3_HT_MASK 0xFFF0000 +#define TCON_TIMING3_HT_SHIFT 16 +#define TCON_TIMING3_HT(x) (((x - 1) << TCON_TIMING3_HT_SHIFT) & TCON_TIMING3_HT_MASK) + +#define TCON_TIMING4 0xA4 +#define TCON_TIMING4_VBP_MASK 0xFFF +#define TCON_TIMING4_VBP_SHIFT 0 +#define TCON_TIMING4_VBP(x) ((x - 1) & TCON_TIMING4_VBP_MASK) +#define TCON_TIMING4_VT_MASK 0xFFF0000 +#define TCON_TIMING4_VT_SHIFT 16 +#define TCON_TIMING4_VT(x) (((x * 2) << TCON_TIMING4_VT_SHIFT) & TCON_TIMING4_VT_MASK) + +#define TCON_TIMING5 0xA8 +#define TCON_TIMING5_VSPW_MASK 0x3FF +#define TCON_TIMING5_VSPW_SHIFT 0 +#define TCON_TIMING5_VSPW(x) ((x - 1) & TCON_TIMING5_VSPW_MASK) +#define TCON_TIMING5_HSPW_MASK 0x3FF0000 +#define TCON_TIMING5_HSPW_SHIFT 16 +#define TCON_TIMING5_HSPW(x) (((x - 1) << TCON_TIMING5_HSPW_SHIFT) & TCON_TIMING5_HSPW_MASK) + +#endif /* _AW_DE2_TCON_H_ */ + Index: sys/dev/drm/allwinner/aw_de2_tcon.c =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_tcon.c @@ -0,0 +1,703 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "aw_de2_tcon_if.h" +#include "aw_de2_mixer_if.h" +#include "dw_hdmi_if.h" +#include "drm_bridge_if.h" + +enum tcon_model { + A83T_TCON_LCD = 1, + A83T_TCON_TV, +}; + +struct tcon_config { + enum tcon_model model; + const char *name; + const char *clk_parent_name; +}; + +static struct tcon_config a83t_tcon_lcd = { + .model = A83T_TCON_LCD, + .name = "Allwinner DE2 LCD TCON", + .clk_parent_name = "pll_video0-2x", +}; + +static struct tcon_config a83t_tcon_tv = { + .model = A83T_TCON_TV, + .name = "Allwinner DE2 TV TCON", + .clk_parent_name = "pll_video1", +}; + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun8i-a83t-tcon-lcd", (uintptr_t)&a83t_tcon_lcd }, + { "allwinner,sun8i-a83t-tcon-tv", (uintptr_t)&a83t_tcon_tv }, + { NULL, 0 } +}; + +static struct resource_spec aw_de2_tcon_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { -1, 0 } +}; + +struct aw_de2_tcon_softc { + device_t dev; + struct resource *res[2]; + void * intrhand; + struct mtx mtx; + + struct tcon_config *conf; + clk_t clk_ahb; + clk_t clk_tcon; + hwreset_t rst_lcd; + hwreset_t rst_lvds; + + device_t mixer; + device_t outport; + + struct drm_pending_vblank_event *event; + struct drm_device *drm; + struct drm_crtc crtc; + struct drm_encoder encoder; + + uint32_t vbl_counter; + + int attach_done; +}; + +#define AW_DE2_TCON_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) +#define AW_DE2_TCON_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) + +#define AW_DE2_TCON_LOCK(sc) mtx_lock(&(sc)->mtx) +#define AW_DE2_TCON_UNLOCK(sc) mtx_unlock(&(sc)->mtx) + +static int aw_de2_tcon_probe(device_t dev); +static int aw_de2_tcon_attach(device_t dev); +static int aw_de2_tcon_detach(device_t dev); +static void aw_de2_tcon_intr(void *arg); + +static inline void +aw_de2_tcon_dump_regs(struct aw_de2_tcon_softc *sc) +{ + uint32_t reg; + + reg = AW_DE2_TCON_READ_4(sc, TCON_GCTL); + DRM_DEBUG_DRIVER("%s: GCTL: %x, TCON %sable, Gamma %sable\n", + __func__, + reg, + (reg & (1 << 31)) ? "En" : "Dis", + (reg & (1 << 30)) ? "En" : "Dis"); + + reg = AW_DE2_TCON_READ_4(sc, TCON_GINT0); + DRM_DEBUG_DRIVER("%s: GINT0: %x, VBlank %sable, Line %sable\n", + __func__, + reg, + (reg & (1 << 30)) ? "En" : "Dis", + (reg & (1 << 28)) ? "En" : "Dis"); + + reg = AW_DE2_TCON_READ_4(sc, TCON_CTL); + DRM_DEBUG_DRIVER("%s: CTL: %x, TCON %sable, delay: %d\n", + __func__, + reg, + (reg & (1 << 31)) ? "En" : "Dis", + (reg & TCON_CTL_DELAY_MASK) >> TCON_CTL_DELAY_SHIFT); + + reg = AW_DE2_TCON_READ_4(sc, TCON_TIMING0); + DRM_DEBUG_DRIVER("%s: TIMING0: %x, XI: %d, YI: %d\n", + __func__, + reg, + ((reg & TCON_TIMING0_XI_MASK) >> TCON_TIMING0_XI_SHIFT) + 1, + ((reg & TCON_TIMING0_YI_MASK) >> TCON_TIMING0_YI_SHIFT) + 1); + + reg = AW_DE2_TCON_READ_4(sc, TCON_TIMING1); + DRM_DEBUG_DRIVER("%s: TIMING1: %x, LS_XO: %d, LS_YO: %d\n", + __func__, + reg, + ((reg & TCON_TIMING1_LS_XO_MASK) >> TCON_TIMING1_LS_XO_SHIFT) + 1, + ((reg & TCON_TIMING1_LS_YO_MASK) >> TCON_TIMING1_LS_YO_SHIFT) + 1); + + reg = AW_DE2_TCON_READ_4(sc, TCON_TIMING2); + DRM_DEBUG_DRIVER("%s: TIMING2: %x, XO: %d, YO: %d\n", + __func__, + reg, + ((reg & TCON_TIMING2_XO_MASK) >> TCON_TIMING2_XO_SHIFT) + 1, + ((reg & TCON_TIMING2_YO_MASK) >> TCON_TIMING2_YO_SHIFT) + 1); + + reg = AW_DE2_TCON_READ_4(sc, TCON_TIMING3); + DRM_DEBUG_DRIVER("%s: TIMING3: %x, HT: %d, HBP: %d\n", + __func__, + reg, + (reg & TCON_TIMING3_HT_MASK) >> TCON_TIMING3_HT_SHIFT, + (reg & TCON_TIMING3_HBP_MASK) >> TCON_TIMING3_HBP_SHIFT); + + reg = AW_DE2_TCON_READ_4(sc, TCON_TIMING4); + DRM_DEBUG_DRIVER("%s: TIMING4: %x, VT: %d, VBP: %d\n", + __func__, + reg, + (reg & TCON_TIMING4_VT_MASK) >> TCON_TIMING4_VT_SHIFT, + (reg & TCON_TIMING4_VBP_MASK) >> TCON_TIMING4_VBP_SHIFT); + + reg = AW_DE2_TCON_READ_4(sc, TCON_TIMING5); + DRM_DEBUG_DRIVER("%s: TIMING5: %x, HSPW: %d, VSPW: %d\n", + __func__, + reg, + (reg & TCON_TIMING5_HSPW_MASK) >> TCON_TIMING5_HSPW_SHIFT, + (reg & TCON_TIMING5_VSPW_MASK) >> TCON_TIMING5_HSPW_SHIFT); +} + +/* + * VBLANK functions + */ +static int +aw_de2_tcon_enable_vblank(struct drm_crtc *crtc) +{ + struct aw_de2_tcon_softc *sc; + + sc = container_of(crtc, struct aw_de2_tcon_softc, crtc); + DRM_DEBUG_DRIVER("%s: Enabling VBLANK\n", __func__); + AW_DE2_TCON_WRITE_4(sc, TCON_GINT0, + TCON0_GINT0_VB_EN | TCON1_GINT0_VB_EN); + + return (0); +} + +static void +aw_de2_tcon_disable_vblank(struct drm_crtc *crtc) +{ + struct aw_de2_tcon_softc *sc; + + sc = container_of(crtc, struct aw_de2_tcon_softc, crtc); + DRM_DEBUG_DRIVER("%s: Disabling VBLANK\n", __func__); + AW_DE2_TCON_WRITE_4(sc, TCON_GINT0, 0x00); +} + +static uint32_t +aw_de2_tcon_get_vblank_counter(struct drm_crtc *crtc) +{ + struct aw_de2_tcon_softc *sc; + + sc = container_of(crtc, struct aw_de2_tcon_softc, crtc); + + return (sc->vbl_counter); +} + +static const struct drm_crtc_funcs aw_de2_tcon_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, + + .get_vblank_counter = aw_de2_tcon_get_vblank_counter, + .enable_vblank = aw_de2_tcon_enable_vblank, + .disable_vblank = aw_de2_tcon_disable_vblank, +}; + +static int +aw_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + + /* Not sure we need to something here, should replace with an helper */ + return (0); +} + +static void +aw_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct aw_de2_tcon_softc *sc; + unsigned long flags; + + sc = container_of(crtc, struct aw_de2_tcon_softc, crtc); + + if (crtc->state->event == NULL) + return; + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + + if (drm_crtc_vblank_get(crtc) != 0) + drm_crtc_send_vblank_event(crtc, crtc->state->event); + else + drm_crtc_arm_vblank_event(crtc, crtc->state->event); + + crtc->state->event = NULL; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); +} + +static void +aw_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct aw_de2_tcon_softc *sc; + struct drm_pending_vblank_event *event = crtc->state->event; + + sc = container_of(crtc, struct aw_de2_tcon_softc, crtc); + + AW_DE2_MIXER_COMMIT(sc->mixer); + + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&sc->drm->event_lock); + /* + * If not in page flip, arm it for later + * Else send it + */ + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&sc->drm->event_lock); + } +} + +static void +aw_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct aw_de2_tcon_softc *sc; + uint32_t reg, irqflags; + + sc = container_of(crtc, struct aw_de2_tcon_softc, crtc); + + /* Disable TCON */ + AW_DE2_TCON_LOCK(sc); + reg = AW_DE2_TCON_READ_4(sc, TCON_CTL); + reg &= ~TCON_CTL_EN; + AW_DE2_TCON_WRITE_4(sc, TCON_CTL, reg); + AW_DE2_TCON_UNLOCK(sc); + + /* Disable VBLANK events */ + drm_crtc_vblank_off(crtc); + + spin_lock_irqsave(&crtc->dev->event_lock, irqflags); + + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + + spin_unlock_irqrestore(&crtc->dev->event_lock, irqflags); + +} + +static void +aw_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct aw_de2_tcon_softc *sc; + uint32_t reg; + + sc = container_of(crtc, struct aw_de2_tcon_softc, crtc); + + /* Enable TCON */ + AW_DE2_TCON_LOCK(sc); + reg = AW_DE2_TCON_READ_4(sc, TCON_CTL); + reg |= TCON_CTL_EN; + AW_DE2_TCON_WRITE_4(sc, TCON_CTL, reg); + AW_DE2_TCON_UNLOCK(sc); + + /* Enable VBLANK events */ + drm_crtc_vblank_on(crtc); +} + +static void +aw_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct aw_de2_tcon_softc *sc; + struct drm_display_mode *mode; + clk_t parent; + uint64_t freq; + uint32_t reg; + + sc = container_of(crtc, struct aw_de2_tcon_softc, crtc); + mode = &crtc->state->adjusted_mode; + + clk_disable(sc->clk_tcon); + if (clk_get_by_name(sc->dev, sc->conf->clk_parent_name, &parent) != 0) { + DRM_ERROR("%s: Cannot get parent clock %s\n", __func__, + sc->conf->clk_parent_name); + return; + } + if (clk_set_parent_by_clk(sc->clk_tcon, parent) != 0) { + DRM_ERROR("%s: Cannot set clock parent to %s\n", __func__, + sc->conf->clk_parent_name); + return; + } + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_tcon_dump_regs(sc); + clk_get_freq(sc->clk_tcon, &freq); + DRM_DEBUG_DRIVER("%s: Current freq: %lu, Changing to %lu\n", + __func__, freq, mode->crtc_clock * 1000); + clk_set_freq(sc->clk_tcon, mode->crtc_clock * 1000, CLK_SET_ROUND_ANY); + clk_get_freq(sc->clk_tcon, &freq); + DRM_DEBUG_DRIVER("%s: New freq: %lu\n", __func__, freq); + clk_enable(sc->clk_tcon); + AW_DE2_TCON_LOCK(sc); + + /* Clock delay, writing what u-boot left, need to figure what it is */ + reg = AW_DE2_TCON_READ_4(sc, TCON_CTL); + reg &= ~TCON_CTL_DELAY_MASK; + reg |= 28 << TCON_CTL_DELAY_SHIFT; + AW_DE2_TCON_WRITE_4(sc, TCON_CTL, reg); + + /* Input resolution */ + AW_DE2_TCON_WRITE_4(sc, TCON_TIMING0, + TCON_TIMING0_YI(mode->crtc_vdisplay) | + TCON_TIMING0_XI(mode->crtc_hdisplay)); + + /* Upscale resolution */ + AW_DE2_TCON_WRITE_4(sc, TCON_TIMING1, + TCON_TIMING1_LS_YO(mode->crtc_vdisplay) | + TCON_TIMING1_LS_XO(mode->crtc_hdisplay)); + + /* Output resolution */ + AW_DE2_TCON_WRITE_4(sc, TCON_TIMING2, + TCON_TIMING2_YO(mode->crtc_vdisplay) | + TCON_TIMING2_XO(mode->crtc_hdisplay)); + + /* Horizontal total and backporch */ + AW_DE2_TCON_WRITE_4(sc, TCON_TIMING3, + TCON_TIMING3_HT(mode->crtc_htotal) | + TCON_TIMING3_HBP(mode->crtc_htotal - mode->crtc_hsync_start)); + + /* Vertical total and backporch */ + AW_DE2_TCON_WRITE_4(sc, TCON_TIMING4, + TCON_TIMING4_VT(mode->crtc_vtotal) | + TCON_TIMING4_VBP(mode->crtc_vtotal - mode->crtc_vsync_start)); + + /* Hsync and Vsync length */ + AW_DE2_TCON_WRITE_4(sc, TCON_TIMING5, + TCON_TIMING5_HSPW(mode->crtc_hsync_end - mode->crtc_hsync_start) | + TCON_TIMING5_VSPW(mode->crtc_vsync_end - mode->crtc_vsync_start)); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_tcon_dump_regs(sc); + + AW_DE2_TCON_UNLOCK(sc); +} + +static const struct drm_crtc_helper_funcs aw_crtc_helper_funcs = { + .atomic_check = aw_crtc_atomic_check, + .atomic_begin = aw_crtc_atomic_begin, + .atomic_flush = aw_crtc_atomic_flush, + .atomic_enable = aw_crtc_atomic_enable, + .atomic_disable = aw_crtc_atomic_disable, + .mode_set_nofb = aw_crtc_mode_set_nofb, +}; + +static void aw_de2_tcon_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) + +{ + +} + +static const struct drm_encoder_helper_funcs aw_de2_tcon_encoder_helper_funcs = { + .mode_set = aw_de2_tcon_encoder_mode_set, +}; + +static const struct drm_encoder_funcs aw_de2_tcon_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int +aw_de2_tcon_set_mixer(device_t dev, device_t mixer_dev) +{ + struct aw_de2_tcon_softc *sc; + + sc = device_get_softc(dev); + + sc->mixer = mixer_dev; + + return (0); +} + +static int +aw_de2_tcon_create_crtc(device_t dev, struct drm_device *drm, + struct drm_plane *main_plane, struct drm_plane *cursor_plane) +{ + struct aw_de2_tcon_softc *sc; + int ret; + + sc = device_get_softc(dev); + sc->drm = drm; + + ret = drm_crtc_init_with_planes(drm, &sc->crtc, + main_plane, + cursor_plane, + &aw_de2_tcon_funcs, + NULL); + if (ret != 0) { + device_printf(sc->dev, + "%s: drm_crtc_init_with_planes failed\n", __func__); + return (ret); + } + + drm_crtc_helper_add(&sc->crtc, &aw_crtc_helper_funcs); + + if (sc->conf->model == A83T_TCON_LCD) { + drm_encoder_helper_add(&sc->encoder, &aw_de2_tcon_encoder_helper_funcs); + sc->encoder.possible_crtcs = drm_crtc_mask(&sc->crtc); + drm_encoder_init(drm, &sc->encoder, &aw_de2_tcon_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + DRM_BRIDGE_ADD_BRIDGE(sc->outport, &sc->encoder, drm); + } else { + DW_HDMI_ADD_ENCODER(sc->outport, &sc->crtc, drm); + } + + return (0); +} + +static void +aw_de2_tcon_intr(void *arg) +{ + struct aw_de2_tcon_softc *sc; + uint32_t reg; + + sc = (struct aw_de2_tcon_softc *)arg; + + reg = AW_DE2_TCON_READ_4(sc, TCON_GINT0); + if (reg & (TCON0_GINT0_VB_FLAG | TCON1_GINT0_VB_FLAG)) { + /* Ack interrupts */ + AW_DE2_TCON_WRITE_4(sc, TCON_GINT0, + ~(TCON0_GINT0_VB_FLAG | TCON1_GINT0_VB_FLAG)); + + atomic_add_32(&sc->vbl_counter, 1); + drm_crtc_handle_vblank(&sc->crtc); + } +} + +static int +aw_de2_tcon_probe(device_t dev) +{ + struct aw_de2_tcon_softc *sc; + int endpoint; + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + sc = device_get_softc(dev); + sc->conf = (struct tcon_config *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + if (sc->conf == 0) + return (ENXIO); + + /* If we cannot get our endpoint now no point of trying to attach */ + endpoint = 0; + if (sc->conf->model == A83T_TCON_TV) + endpoint = 1; + sc->outport = ofw_graph_get_device_by_port_ep(ofw_bus_get_node(dev), + 1, endpoint); + if (sc->outport == NULL) { + if (bootverbose) + device_printf(dev, "%s: Cannot find endpoint, aborting\n", + sc->conf->name); + return (ENXIO); + } + + device_set_desc(dev, sc->conf->name); + + return (BUS_PROBE_DEFAULT); +} + +static int +aw_de2_tcon_attach(device_t dev) +{ + struct aw_de2_tcon_softc *sc; + phandle_t node; + int error, endpoint; + + node = ofw_bus_get_node(dev); + sc = device_get_softc(dev); + sc->dev = dev; + + if (bus_alloc_resources(dev, aw_de2_tcon_spec, sc->res) != 0) { + device_printf(dev, "cannot allocate resources for device\n"); + error = ENXIO; + goto fail; + } + if (bus_setup_intr(dev, sc->res[1], + INTR_TYPE_MISC | INTR_MPSAFE, NULL, aw_de2_tcon_intr, sc, + &sc->intrhand)) { + bus_release_resources(dev, aw_de2_tcon_spec, sc->res); + device_printf(dev, "cannot setup interrupt handler\n"); + return (ENXIO); + } + + mtx_init(&sc->mtx, device_get_nameunit(dev), "aw_de2_tcon", MTX_DEF); + + if ((error = clk_get_by_ofw_name(dev, node, "ahb", &sc->clk_ahb)) != 0) { + device_printf(dev, "Cannot get ahb clock\n"); + goto fail; + } + if ((error = clk_enable(sc->clk_ahb)) != 0) { + device_printf(dev, "Cannot enable ahb clock\n"); + goto fail; + } + + if (sc->conf->model == A83T_TCON_LCD) { + if ((error = clk_get_by_ofw_name(dev, node, "tcon-ch0", &sc->clk_tcon)) != 0) { + device_printf(dev, "Cannot get tcon clock\n"); + goto fail; + } + } else if (sc->conf->model == A83T_TCON_TV) { + if ((error = clk_get_by_ofw_name(dev, node, "tcon-ch1", &sc->clk_tcon)) != 0) { + device_printf(dev, "Cannot get tcon clock\n"); + goto fail; + } + } + if ((error = clk_enable(sc->clk_tcon)) != 0) { + device_printf(dev, "Cannot enable tcon clock\n"); + goto fail; + } + if ((error = hwreset_get_by_ofw_name(dev, node, "lcd", &sc->rst_lcd)) != 0) { + device_printf(dev, "Cannot get lcd reset\n"); + goto fail; + } + if ((error = hwreset_deassert(sc->rst_lcd)) != 0) { + device_printf(dev, "Cannot de-assert lcd reset\n"); + goto fail; + } + + if (sc->conf->model == A83T_TCON_LCD) { + if ((error = hwreset_get_by_ofw_name(dev, node, "lvds", &sc->rst_lvds)) != 0) { + device_printf(dev, "Cannot get lvds reset\n"); + goto fail; + } + if ((error = hwreset_deassert(sc->rst_lvds)) != 0) { + device_printf(dev, "Cannot de-assert lvds reset\n"); + goto fail; + } + } + + endpoint = 0; + if (sc->conf->model == A83T_TCON_TV) + endpoint = 1; + sc->outport = ofw_graph_get_device_by_port_ep(node, 1, endpoint); + if (sc->outport == NULL) { + device_printf(dev, "Cannot get remote endpoint device for port 1 and endpoint %d\n", endpoint); + error = ENXIO; + goto fail; + } + + /* Register ourself */ + OF_device_register_xref(OF_xref_from_node(node), dev); + + /* Clear and disable interrupts */ + AW_DE2_TCON_WRITE_4(sc, TCON_GINT0, 0x00); + + /* Enable module */ + AW_DE2_TCON_WRITE_4(sc, TCON_GCTL, TCON_GCTL_EN); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_tcon_dump_regs(sc); + + return (0); + +fail: + aw_de2_tcon_detach(dev); + return (error); +} + +static int +aw_de2_tcon_detach(device_t dev) +{ + struct aw_de2_tcon_softc *sc; + phandle_t node; + + sc = device_get_softc(dev); + node = ofw_bus_get_node(dev); + + clk_release(sc->clk_tcon); + clk_release(sc->clk_ahb); + hwreset_release(sc->rst_lcd); + if (sc->rst_lvds) + hwreset_release(sc->rst_lvds); + + bus_teardown_intr(dev, sc->res[1], sc->intrhand); + bus_release_resources(dev, aw_de2_tcon_spec, sc->res); + mtx_destroy(&sc->mtx); + + return (0); +} + +static device_method_t aw_de2_tcon_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aw_de2_tcon_probe), + DEVMETHOD(device_attach, aw_de2_tcon_attach), + DEVMETHOD(device_detach, aw_de2_tcon_detach), + + /* AW_DE2_TCON interface */ + DEVMETHOD(aw_de2_tcon_set_mixer, aw_de2_tcon_set_mixer), + DEVMETHOD(aw_de2_tcon_create_crtc, aw_de2_tcon_create_crtc), + DEVMETHOD_END +}; + +static driver_t aw_de2_tcon_driver = { + "aw_de2_tcon", + aw_de2_tcon_methods, + sizeof(struct aw_de2_tcon_softc), +}; + +static devclass_t aw_de2_tcon_devclass; + +EARLY_DRIVER_MODULE(aw_tcon, simplebus, aw_de2_tcon_driver, + aw_de2_tcon_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_MIDDLE); +MODULE_DEPEND(aw_de2_tcon, aw_de2_dw_hdmi, 1, 1, 1); +MODULE_VERSION(aw_de2_tcon, 1); Index: sys/dev/drm/allwinner/aw_de2_tcon_if.m =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_tcon_if.m @@ -0,0 +1,51 @@ +#- +# Copyright (c) 2019 Emmanuel Vadot +# +# 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. +# +# $FreeBSD$ +# + +INTERFACE aw_de2_tcon; + +HEADER { + struct drm_device; + struct drm_plane; +} + +# +# Set the corresponding mixer for this tcon +# +METHOD int set_mixer { + device_t dev; + device_t mixer_dev; +}; + +# +# Create and register the pipeline in the drm subsystem +# +METHOD int create_crtc { + device_t dev; + struct drm_device *drm_dev; + struct drm_plane *main_plane; + struct drm_plane *cursor_plane; +}; Index: sys/dev/drm/allwinner/aw_de2_ui_plane.h =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_ui_plane.h @@ -0,0 +1,74 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#ifndef _AW_DE2_UI_PLANE_H_ +#define _AW_DE2_UI_PLANE_H_ + +/* Overlay registers defines */ +#define OVL_UI_BASE 0x3000 +#define OVL_UI_CHANNEL_SIZE 0x1000 + +#define OVL_UI_CH_BASE(channel) (OVL_UI_BASE + (channel * OVL_UI_CHANNEL_SIZE)) + +#define OVL_UI_ATTR_CTL(channel) (OVL_UI_CH_BASE(channel)) +#define OVL_UI_ATTR_EN (1 << 0) +#define OVL_UI_ATTR_ALPHA_MASK 0x6 +#define OVL_UI_ATTR_ALPHA_SHIFT 1 +#define OVL_UI_ATTR_ALPHA_EN 2 +#define OVL_UI_ATTR_ALPHA_MIX 3 +#define OVL_UI_ATTR_FILLCOLOR_EN (1 << 4) +#define OVL_UI_PIX_FORMAT_MASK 0x1F00 +#define OVL_UI_PIX_FORMAT_SHIFT 8 + +#define OVL_UI_MBSIZE(channel) (OVL_UI_CH_BASE(channel) + 0x04) +#define OVL_UI_MBSIZE_WIDTH_MASK 0x1FFF +#define OVL_UI_MBSIZE_WIDTH_SHIFT 0 +#define OVL_UI_MBSIZE_HEIGHT_MASK 0x1FFF0000 +#define OVL_UI_MBSIZE_HEIGHT_SHIFT 16 + +#define OVL_UI_COORD(channel) (OVL_UI_CH_BASE(channel) + 0x08) +#define OVL_UI_COOR_X_MASK 0xFFFF +#define OVL_UI_COOR_X_SHIFT 0 +#define OVL_UI_COOR_Y_MASK 0xFFFF0000 +#define OVL_UI_COOR_Y_SHIFT 16 + +#define OVL_UI_PITCH(channel) (OVL_UI_CH_BASE(channel) + 0x0C) +#define OVL_UI_TOP_LADD(channel) (OVL_UI_CH_BASE(channel) + 0x10) +#define OVL_UI_BOT_LADD(channel) (OVL_UI_CH_BASE(channel) + 0x14) +#define OVL_UI_FILL_COLOR(channel) (OVL_UI_CH_BASE(channel) + 0x18) +#define OVL_UI_TOP_HADD(channel) (OVL_UI_CH_BASE(channel) + 0x80) +#define OVL_UI_BOT_HADD(channel) (OVL_UI_CH_BASE(channel) + 0x84) + +#define OVL_UI_SIZE(channel) (OVL_UI_CH_BASE(channel) + 0x88) +#define OVL_UI_SIZE_WIDTH_MASK 0x1FFF +#define OVL_UI_SIZE_WIDTH_SHIFT 0 +#define OVL_UI_SIZE_HEIGHT_MASK 0x1FFF0000 +#define OVL_UI_SIZE_HEIGHT_SHIFT 16 + +int aw_de2_ui_plane_create(struct aw_de2_mixer_softc *sc, struct drm_device *drm); + +#endif /* _AW_DE2_UI_PLANE_H_ */ Index: sys/dev/drm/allwinner/aw_de2_ui_plane.c =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_ui_plane.c @@ -0,0 +1,325 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +void aw_de2_ui_plane_dump_regs(struct aw_de2_mixer_softc *sc, int num); + +static const u32 aw_de2_ui_plane_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, +}; + +static int aw_de2_ui_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_crtc *crtc = state->crtc; + struct drm_crtc_state *crtc_state; + + if (crtc == NULL) + return (0); + + crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); + if (crtc_state == NULL) + return (-EINVAL); + + return drm_atomic_helper_check_plane_state(state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + true, true); +} + +static void aw_de2_ui_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct aw_de2_mixer_plane *mixer_plane; + struct aw_de2_mixer_softc *sc; + uint32_t reg; + + mixer_plane = container_of(plane, struct aw_de2_mixer_plane, plane); + sc = mixer_plane->sc; + + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_ATTR_CTL(mixer_plane->id)); + reg &= ~OVL_UI_ATTR_EN; + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_ATTR_CTL(mixer_plane->id), reg); +} + +static void aw_de2_ui_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct aw_de2_mixer_plane *mixer_plane; + struct aw_de2_mixer_softc *sc; + struct drm_plane_state *state = plane->state; + uint32_t src_w, src_h, dst_w, dst_h; + struct drm_fb_cma *fb; + struct drm_gem_cma_object *bo; + dma_addr_t paddr; + uint32_t reg; + int id, i; + + mixer_plane = container_of(plane, struct aw_de2_mixer_plane, plane); + fb = container_of(plane->state->fb, struct drm_fb_cma, drm_fb); + + sc = mixer_plane->sc; + id = mixer_plane->id; + + DRM_DEBUG_DRIVER("%s: plane=%p fb=%p\n", __func__, plane, fb); + + src_w = drm_rect_width(&state->src) >> 16; + src_h = drm_rect_height(&state->src) >> 16; + dst_w = drm_rect_width(&state->dst); + dst_h = drm_rect_height(&state->dst); + + if (!plane->state->visible) { + DRM_DEBUG_DRIVER("%s: Disabling UI layer %d\n", __func__, id); + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_ATTR_CTL(id)); + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_ATTR_CTL(id), + reg & ~OVL_UI_ATTR_EN); + return; + } + + DRM_DEBUG_DRIVER("%s: %d %d %d %d\n", __func__, + src_w, src_h, + dst_w, dst_h); + DRM_DEBUG_DRIVER("%s: Layer destination coordinates X: %d Y: %d\n", + __func__, state->dst.x1, state->dst.y1); + DRM_DEBUG_DRIVER("%s: Layer destination size W: %d H: %d\n", + __func__, + dst_w, dst_h); + + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_MBSIZE(id), + ((src_h - 1) << 16) | (src_w - 1)); + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_SIZE(id), + ((src_h - 1) << 16) | (src_w - 1)); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + AW_DE2_MIXER_WRITE_4(sc, GBL_SIZE, + ((dst_h - 1) << 16) | (dst_w - 1)); + AW_DE2_MIXER_WRITE_4(sc, BLD_OUTSIZE, + ((dst_h - 1) << 16) | (dst_w - 1)); + } + AW_DE2_MIXER_WRITE_4(sc, BLD_INSIZE(id), + ((dst_h - 1) << 16) | (dst_w - 1)); + AW_DE2_MIXER_WRITE_4(sc, BLD_COORD(id), + state->dst.y1 << 16 | state->dst.x1); + + /* Update addr and pitch */ + bo = drm_fb_cma_get_gem_obj(fb, 0); + + DRM_DEBUG_DRIVER("%s: gem: %p\n", __func__, bo); + DRM_DEBUG_DRIVER("%s: fb: %p\n", __func__, fb); + + paddr = bo->pbase + fb->drm_fb.offsets[0]; + paddr += (state->src.x1 >> 16) * fb->drm_fb.format->cpp[0]; + paddr += (state->src.y1 >> 16) * fb->drm_fb.pitches[0]; + + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_TOP_LADD(id), paddr & 0xFFFFFFFF); + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_BOT_LADD(id), 0); + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_TOP_HADD(id), 0); + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_BOT_HADD(id), 0); + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_PITCH(id), fb->drm_fb.pitches[0]); + + /* Update format */ + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_ATTR_CTL(id)); + reg &= ~OVL_UI_PIX_FORMAT_MASK; + for (i = 0; i < nitems(aw_de2_ui_plane_formats); i++) + if (aw_de2_ui_plane_formats[i] == state->fb->format->format) + break; + reg |= i << OVL_UI_PIX_FORMAT_SHIFT; + + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_ATTR_CTL(id), reg); + + /* Enable overlay */ + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_ATTR_CTL(id)); + AW_DE2_MIXER_WRITE_4(sc, OVL_UI_ATTR_CTL(id), + reg | OVL_UI_ATTR_EN); + + /* Enable pipe0 */ + reg = AW_DE2_MIXER_READ_4(sc, BLD_PIPE_CTL); + reg |= 0x101; + AW_DE2_MIXER_WRITE_4(sc, BLD_PIPE_CTL, + reg); + + reg = AW_DE2_MIXER_READ_4(sc, BLD_CH_ROUTING); + /* route channel 1 to pipe0 */ + reg |= 1 << 0; + AW_DE2_MIXER_WRITE_4(sc, BLD_CH_ROUTING, reg); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_ui_plane_dump_regs(sc, 0); +} + +static struct drm_plane_helper_funcs aw_de2_ui_plane_helper_funcs = { + .atomic_check = aw_de2_ui_plane_atomic_check, + .atomic_disable = aw_de2_ui_plane_atomic_disable, + .atomic_update = aw_de2_ui_plane_atomic_update, +}; + +static const struct drm_plane_funcs aw_de2_ui_plane_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .destroy = drm_plane_cleanup, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +int +aw_de2_ui_plane_create(struct aw_de2_mixer_softc *sc, struct drm_device *drm) +{ + enum drm_plane_type type = DRM_PLANE_TYPE_PRIMARY; + int i; + + for (i = 0; i < sc->conf->ui_planes; i++) { + if (i > 0) + type = DRM_PLANE_TYPE_OVERLAY; + drm_universal_plane_init(drm, + &sc->ui_planes[i].plane, + 0, + &aw_de2_ui_plane_funcs, + aw_de2_ui_plane_formats, + nitems(aw_de2_ui_plane_formats), + NULL, type, NULL); + + drm_plane_helper_add(&sc->ui_planes[i].plane, + &aw_de2_ui_plane_helper_funcs); + + sc->ui_planes[i].sc= sc; + sc->ui_planes[i].id = i; + } + + return (0); +} + +void +aw_de2_ui_plane_dump_regs(struct aw_de2_mixer_softc *sc, int num) +{ + uint32_t reg; + int i; + + DRM_DEBUG_DRIVER("%s: UI Plane %d\n", __func__, num); + + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_ATTR_CTL(num)); + DRM_DEBUG_DRIVER("%s: ATTR_CTL(%d): %x\n", __func__, num, reg); + + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_MBSIZE(num)); + DRM_DEBUG_DRIVER("%s: MBSIZE(%d): %x (%dx%d)\n", __func__, num, reg, + (reg & OVL_UI_MBSIZE_WIDTH_MASK) >> OVL_UI_MBSIZE_WIDTH_SHIFT, + (reg & OVL_UI_MBSIZE_HEIGHT_MASK) >> OVL_UI_MBSIZE_HEIGHT_SHIFT); + + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_COORD(num)); + DRM_DEBUG_DRIVER("%s: COOR(%d): %x (%d %d)\n", __func__, num, reg, + (reg & OVL_UI_COOR_X_MASK) >> OVL_UI_COOR_X_SHIFT, + (reg & OVL_UI_COOR_Y_MASK) >> OVL_UI_COOR_Y_SHIFT); + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_PITCH(num)); + DRM_DEBUG_DRIVER("%s: PITCH(%d): %d\n", __func__, num, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_TOP_LADD(num)); + DRM_DEBUG_DRIVER("%s: TOP_LADD(%d): %x\n", __func__, num, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_BOT_LADD(num)); + DRM_DEBUG_DRIVER("%s: BOT_LADD(%d): %x\n", __func__, num, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_FILL_COLOR(num)); + DRM_DEBUG_DRIVER("%s: FILL_COLOR(%d): %x\n", __func__, num, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_TOP_HADD(num)); + DRM_DEBUG_DRIVER("%s: TOP_HADD(%d): %x\n", __func__, num, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_BOT_HADD(num)); + DRM_DEBUG_DRIVER("%s: BOT_HADD(%d): %x\n", __func__, num, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_UI_SIZE(num)); + DRM_DEBUG_DRIVER("%s: SIZE(%d): %x (%dx%d)\n", __func__, num, reg, + (reg & OVL_UI_SIZE_WIDTH_MASK) >> OVL_UI_SIZE_WIDTH_SHIFT, + (reg & OVL_UI_SIZE_HEIGHT_MASK) >> OVL_UI_SIZE_HEIGHT_SHIFT); + reg = AW_DE2_MIXER_READ_4(sc, BLD_PIPE_CTL); + DRM_DEBUG_DRIVER("%s: BLD_PIPE_CTL: %x\n", __func__, reg); + for (i = 0; i < 4; i++) { + reg = AW_DE2_MIXER_READ_4(sc, BLD_FILL_COLOR(i)); + DRM_DEBUG_DRIVER("%s: BLD_FILL_COLOR(%d): %x\n", __func__, + i, reg); + } + reg = AW_DE2_MIXER_READ_4(sc, BLD_INSIZE(num)); + DRM_DEBUG_DRIVER("%s: BLD_INSIZE(%d): %x (%dx%d)\n", __func__, num, reg, + (reg & OVL_UI_SIZE_WIDTH_MASK) >> OVL_UI_SIZE_WIDTH_SHIFT, + (reg & OVL_UI_SIZE_HEIGHT_MASK) >> OVL_UI_SIZE_HEIGHT_SHIFT); + reg = AW_DE2_MIXER_READ_4(sc, BLD_COORD(num)); + DRM_DEBUG_DRIVER("%s: BLD_COORD(%d): %x (%dx%d)\n", __func__, num, reg, + (reg & OVL_UI_SIZE_WIDTH_MASK) >> OVL_UI_SIZE_WIDTH_SHIFT, + (reg & OVL_UI_SIZE_HEIGHT_MASK) >> OVL_UI_SIZE_HEIGHT_SHIFT); + reg = AW_DE2_MIXER_READ_4(sc, BLD_CH_ROUTING); + DRM_DEBUG_DRIVER("%s: BLD_CH_ROUTING: %x\n", __func__, reg); + reg = AW_DE2_MIXER_READ_4(sc, BLD_OUTSIZE); + DRM_DEBUG_DRIVER("%s: BLD_OUTSIZE: %x (%dx%d)\n", __func__, reg, + (reg & OVL_UI_SIZE_WIDTH_MASK) >> OVL_UI_SIZE_WIDTH_SHIFT, + (reg & OVL_UI_SIZE_HEIGHT_MASK) >> OVL_UI_SIZE_HEIGHT_SHIFT); +} Index: sys/dev/drm/allwinner/aw_de2_vi_plane.h =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_vi_plane.h @@ -0,0 +1,67 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#ifndef _AW_DE2_VI_PLANE_H_ +#define _AW_DE2_VI_PLANE_H_ + +/* Overlay registers defines */ +#define OVL_VI_BASE 0x2000 + +#define OVL_VI_ATTR_CTL OVL_VI_BASE +#define OVL_VI_ATTR_EN (1 << 0) +#define OVL_VI_PIX_FORMAT_MASK 0x1F00 +#define OVL_VI_PIX_FORMAT_SHIFT 8 +#define OVL_VI_PIX_FORMAT_SEL (1 << 15) + +#define OVL_VI_MBSIZE (OVL_VI_BASE + 0x04) +#define OVL_VI_MBSIZE_WIDTH_MASK 0x1FFF +#define OVL_VI_MBSIZE_WIDTH_SHIFT 0 +#define OVL_VI_MBSIZE_HEIGHT_MASK 0x1FFF0000 +#define OVL_VI_MBSIZE_HEIGHT_SHIFT 16 + +#define OVL_VI_COORD (OVL_VI_BASE + 0x08) +#define OVL_VI_COORD_X_MASK 0xFFFF +#define OVL_VI_COORD_X_SHIFT 0 +#define OVL_VI_COORD_Y_MASK 0xFFFF0000 +#define OVL_VI_COORD_Y_SHIFT 16 + +#define OVL_VI_Y_PITCH(x) (OVL_VI_BASE + 0x0C + (x * 0x4)) +#define OVL_VI_TOP_Y_LADD(x) (OVL_VI_BASE + 0x18 + (x * 0x4)) + +#define OVL_VI_SIZE (OVL_VI_BASE + 0xE8) +#define OVL_VI_SIZE_WIDTH_MASK 0x1FFF +#define OVL_VI_SIZE_WIDTH_SHIFT 0 +#define OVL_VI_SIZE_HEIGHT_MASK 0x1FFF0000 +#define OVL_VI_SIZE_HEIGHT_SHIFT 16 + +#define OVL_VI_FORMAT_YUV422 0x6 +#define OVL_VI_FORMAT_YUV420 0x9 +#define OVL_VI_FORMAT_YUV411 0xe + +int aw_de2_vi_plane_create(struct aw_de2_mixer_softc *sc, struct drm_device *drm); + +#endif /* _AW_DE2_VI_PLANE_H_ */ Index: sys/dev/drm/allwinner/aw_de2_vi_plane.c =================================================================== --- /dev/null +++ sys/dev/drm/allwinner/aw_de2_vi_plane.c @@ -0,0 +1,406 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +void aw_de2_vi_plane_dump_regs(struct aw_de2_mixer_softc *sc, int num); + +static const u32 aw_de2_vi_plane_formats[] = { + /* + * Do not set those format + * Even if they work xorg will try to use them + * for the cursor plane as VI is the only other plane + * available for that and since alpha isn't available + * for VI plane this will make a black square around the + * mouse cursor. + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + */ + + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_NV16, + DRM_FORMAT_NV61, + DRM_FORMAT_YUV422, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV411, +}; + +static int +aw_de2_vi_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_crtc *crtc = state->crtc; + struct drm_crtc_state *crtc_state; + + if (crtc == NULL) + return (0); + + crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); + if (crtc_state == NULL) + return (-EINVAL); + + return drm_atomic_helper_check_plane_state(state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, DRM_PLANE_HELPER_NO_SCALING, + true, true); +} + +static uint32_t +aw_de2_vi_plane_format(u32 drm_format, bool *is_rgb) { + + switch (drm_format) { + /* RGB format first */ + case DRM_FORMAT_ARGB8888: + *is_rgb = true; + return (0x00); + case DRM_FORMAT_ABGR8888: + *is_rgb = true; + return (0x01); + case DRM_FORMAT_RGBA8888: + *is_rgb = true; + return (0x02); + case DRM_FORMAT_BGRA8888: + *is_rgb = true; + return (0x03); + case DRM_FORMAT_XRGB8888: + *is_rgb = true; + return (0x04); + case DRM_FORMAT_XBGR8888: + *is_rgb = true; + return (0x05); + case DRM_FORMAT_RGBX8888: + *is_rgb = true; + return (0x06); + case DRM_FORMAT_BGRX8888: + *is_rgb = true; + return (0x07); + case DRM_FORMAT_RGB888: + *is_rgb = true; + return (0x08); + case DRM_FORMAT_BGR888: + *is_rgb = true; + return (0x09); + case DRM_FORMAT_RGB565: + *is_rgb = true; + return (0x0A); + case DRM_FORMAT_BGR565: + *is_rgb = true; + return (0x0B); + + /* YUV format, Interleaved format (0x00 to 0x03) not included yet */ + case DRM_FORMAT_NV16: + *is_rgb = false; + return (0x04); + break; + case DRM_FORMAT_NV61: + *is_rgb = false; + return (0x05); + break; + case DRM_FORMAT_YUV422: + *is_rgb = false; + return (0x06); + break; + case DRM_FORMAT_NV12: + *is_rgb = false; + return (0x08); + break; + case DRM_FORMAT_NV21: + *is_rgb = false; + return (0x09); + break; + case DRM_FORMAT_YUV420: + *is_rgb = false; + return (0x0A); + break; + case DRM_FORMAT_YUV411: + *is_rgb = false; + return (0x0E); + break; + } + + return (0); +} + +static void +aw_de2_vi_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct aw_de2_mixer_plane *mixer_plane; + struct aw_de2_mixer_softc *sc; + uint32_t reg; + + mixer_plane = container_of(plane, struct aw_de2_mixer_plane, plane); + sc = mixer_plane->sc; + + DRM_DEBUG_DRIVER("%s: Disabling VI plane\n", __func__); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_ATTR_CTL); + reg &= ~OVL_VI_ATTR_EN; + AW_DE2_MIXER_WRITE_4(sc, OVL_VI_ATTR_CTL, reg); + + /* HACK, Disable Pipe1 */ + reg = AW_DE2_MIXER_READ_4(sc, BLD_PIPE_CTL); + reg &= ~0x202; + AW_DE2_MIXER_WRITE_4(sc, BLD_PIPE_CTL, + reg); +} + +static void +aw_de2_vi_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct aw_de2_mixer_plane *mixer_plane; + struct aw_de2_mixer_softc *sc; + struct drm_plane_state *state = plane->state; + uint32_t src_w, src_h, dst_w, dst_h, src_x, src_y; + struct drm_fb_cma *fb; + struct drm_gem_cma_object *bo; + dma_addr_t paddr; + uint32_t reg; + uint32_t format; + int id, i; + bool is_rgb = false; + + mixer_plane = container_of(plane, struct aw_de2_mixer_plane, plane); + fb = container_of(plane->state->fb, struct drm_fb_cma, drm_fb); + sc = mixer_plane->sc; + id = mixer_plane->id; + + DRM_DEBUG_DRIVER("%s: plane=%p fb=%p\n", __func__, plane, fb); + + src_w = drm_rect_width(&state->src) >> 16; + src_h = drm_rect_height(&state->src) >> 16; + dst_w = drm_rect_width(&state->dst); + dst_h = drm_rect_height(&state->dst); + + if (!plane->state->visible) { + DRM_DEBUG_DRIVER("%s: Disabling VI layer %d\n", __func__, id); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_ATTR_CTL); + reg &= ~OVL_VI_ATTR_EN; + AW_DE2_MIXER_WRITE_4(sc, OVL_VI_ATTR_CTL, reg); + return; + } + + DRM_DEBUG_DRIVER("%s: %d %d %d %d\n", __func__, + src_w, src_h, + dst_w, dst_h); + DRM_DEBUG_DRIVER("%s: VI Layer destination coordinates X: %d Y: %d\n", + __func__, + state->dst.x1, state->dst.y1); + DRM_DEBUG_DRIVER("%s: VI Layer destination size W: %d H: %d\n", + __func__, + dst_w, dst_h); + + AW_DE2_MIXER_WRITE_4(sc, OVL_VI_MBSIZE, + ((src_h - 1) << 16) | (src_w - 1)); + AW_DE2_MIXER_WRITE_4(sc, OVL_VI_SIZE, + ((src_h - 1) << 16) | (src_w - 1)); + + AW_DE2_MIXER_WRITE_4(sc, BLD_INSIZE(id), + ((dst_h - 1) << 16) | (dst_w - 1)); + AW_DE2_MIXER_WRITE_4(sc, BLD_COORD(id), + state->dst.y1 << 16 | state->dst.x1); + + src_x = (state->src.x1 >> 16) & ~(fb->drm_fb.format->hsub - 1); + src_y = (state->src.y1 >> 16) & ~(fb->drm_fb.format->vsub - 1); + + DRM_DEBUG_DRIVER("%s: format->hsub: %d, format->vsub: %d\n", + __func__, + fb->drm_fb.format->hsub, fb->drm_fb.format->vsub); + + /* Update addr and pitch */ + for (i = 0; i < fb->drm_fb.format->num_planes; i++) { + bo = drm_fb_cma_get_gem_obj(fb, i); + + DRM_DEBUG_DRIVER("%s: gem: %p\n", __func__, bo); + DRM_DEBUG_DRIVER("%s: fb: %p\n", __func__, fb); + + if (i != 0) { + src_x /= fb->drm_fb.format->hsub; + src_y /= fb->drm_fb.format->vsub; + } + paddr = bo->pbase + fb->drm_fb.offsets[i]; + paddr += src_x * fb->drm_fb.format->cpp[i]; + paddr += src_y * fb->drm_fb.pitches[i]; + + AW_DE2_MIXER_WRITE_4(sc, OVL_VI_TOP_Y_LADD(i), paddr & 0xFFFFFFFF); + AW_DE2_MIXER_WRITE_4(sc, OVL_VI_Y_PITCH(i), fb->drm_fb.pitches[i]); + } + + /* Update format */ + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_ATTR_CTL); + reg &= ~OVL_VI_PIX_FORMAT_MASK; + format = aw_de2_vi_plane_format(state->fb->format->format, &is_rgb); + reg |= format << OVL_VI_PIX_FORMAT_SHIFT; + if (is_rgb) + reg |= OVL_VI_PIX_FORMAT_SEL; + DRM_DEBUG_DRIVER("%s: format reg:%x\n", __func__, reg); + AW_DE2_MIXER_WRITE_4(sc, OVL_VI_ATTR_CTL, reg); + + /* Enable overlay */ + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_ATTR_CTL); + AW_DE2_MIXER_WRITE_4(sc, OVL_VI_ATTR_CTL, + reg | OVL_VI_ATTR_EN); + + /* HACK, Enable Pipe1 */ + reg = AW_DE2_MIXER_READ_4(sc, BLD_PIPE_CTL); + reg |= 0x202; + AW_DE2_MIXER_WRITE_4(sc, BLD_PIPE_CTL, + reg); + + /* HACK, Route channel 0 to pipe 1 */ + reg = AW_DE2_MIXER_READ_4(sc, BLD_CH_ROUTING); + reg &= 0xFF0F; + AW_DE2_MIXER_WRITE_4(sc, BLD_CH_ROUTING, reg); + + if (drm_debug & DRM_UT_DRIVER) + aw_de2_vi_plane_dump_regs(sc, 1); +} + +static struct drm_plane_helper_funcs aw_de2_vi_plane_helper_funcs = { + .atomic_check = aw_de2_vi_plane_atomic_check, + .atomic_disable = aw_de2_vi_plane_atomic_disable, + .atomic_update = aw_de2_vi_plane_atomic_update, +}; + +static const struct drm_plane_funcs aw_de2_vi_plane_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .destroy = drm_plane_cleanup, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +int +aw_de2_vi_plane_create(struct aw_de2_mixer_softc *sc, struct drm_device *drm) +{ + int i; + + for (i = 0; i < sc->conf->vi_planes; i++) { + drm_universal_plane_init(drm, + &sc->vi_planes[i].plane, + 0, + &aw_de2_vi_plane_funcs, + aw_de2_vi_plane_formats, + nitems(aw_de2_vi_plane_formats), + NULL, DRM_PLANE_TYPE_OVERLAY, NULL); + + drm_plane_helper_add(&sc->vi_planes[i].plane, + &aw_de2_vi_plane_helper_funcs); + + sc->vi_planes[i].sc = sc; + sc->vi_planes[i].id = sc->conf->ui_planes + i; + } + + return (0); +} + +void +aw_de2_vi_plane_dump_regs(struct aw_de2_mixer_softc *sc, int num) +{ + uint32_t reg; + + DRM_DEBUG_DRIVER("%s: VI Plane\n", __func__); + + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_ATTR_CTL); + DRM_DEBUG_DRIVER("%s: ATTR_CTL: %x\n", __func__, reg); + + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_MBSIZE); + DRM_DEBUG_DRIVER("%s: MBSIZE: %x (%dx%d)\n", __func__, reg, + (reg & OVL_VI_MBSIZE_WIDTH_MASK) >> OVL_VI_MBSIZE_WIDTH_SHIFT, + (reg & OVL_VI_MBSIZE_HEIGHT_MASK) >> OVL_VI_MBSIZE_HEIGHT_SHIFT); + + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_COORD); + DRM_DEBUG_DRIVER("%s: COOR: %x (%d %d)\n", __func__, reg, + (reg & OVL_VI_COORD_X_MASK) >> OVL_VI_COORD_X_SHIFT, + (reg & OVL_VI_COORD_Y_MASK) >> OVL_VI_COORD_Y_SHIFT); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_Y_PITCH(0)); + DRM_DEBUG_DRIVER("%s: PITCH0: %d\n", __func__, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_Y_PITCH(1)); + DRM_DEBUG_DRIVER("%s: PITCH1: %d\n", __func__, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_Y_PITCH(2)); + DRM_DEBUG_DRIVER("%s: PITCH2: %d\n", __func__, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_TOP_Y_LADD(0)); + DRM_DEBUG_DRIVER("%s: TOP_LADD0: %x\n", __func__, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_TOP_Y_LADD(1)); + DRM_DEBUG_DRIVER("%s: TOP_LADD1: %x\n", __func__, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_TOP_Y_LADD(2)); + DRM_DEBUG_DRIVER("%s: TOP_LADD2: %x\n", __func__, reg); + reg = AW_DE2_MIXER_READ_4(sc, OVL_VI_SIZE); + DRM_DEBUG_DRIVER("%s: SIZE: %x (%dx%d)\n", __func__, reg, + (reg & OVL_VI_SIZE_WIDTH_MASK) >> OVL_VI_SIZE_WIDTH_SHIFT, + (reg & OVL_VI_SIZE_HEIGHT_MASK) >> OVL_VI_SIZE_HEIGHT_SHIFT); + reg = AW_DE2_MIXER_READ_4(sc, BLD_INSIZE(num)); + DRM_DEBUG_DRIVER("%s: BLD_INSIZE(%d): %x (%dx%d)\n", __func__, num, reg, + (reg & OVL_VI_SIZE_WIDTH_MASK) >> OVL_VI_SIZE_WIDTH_SHIFT, + (reg & OVL_VI_SIZE_HEIGHT_MASK) >> OVL_VI_SIZE_HEIGHT_SHIFT); + reg = AW_DE2_MIXER_READ_4(sc, BLD_COORD(num)); + DRM_DEBUG_DRIVER("%s: BLD_COORD(%d): %x (%dx%d)\n", __func__, num, reg, + (reg & OVL_VI_SIZE_WIDTH_MASK) >> OVL_VI_SIZE_WIDTH_SHIFT, + (reg & OVL_VI_SIZE_HEIGHT_MASK) >> OVL_VI_SIZE_HEIGHT_SHIFT); +} Index: sys/dev/drm/bridges/dw_hdmi/aw_de2_dw_hdmi.c =================================================================== --- /dev/null +++ sys/dev/drm/bridges/dw_hdmi/aw_de2_dw_hdmi.c @@ -0,0 +1,758 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dw_hdmireg.h" + +#include "dw_hdmi_if.h" +#include "dw_hdmi_phy_if.h" + +#include "iicbus_if.h" + +/* Redefine msleep because of linuxkpi */ +#undef msleep +#define msleep(chan, mtx, pri, wmesg, timo) \ + _sleep((chan), &(mtx)->lock_object, (pri), (wmesg), \ + tick_sbt * (timo), 0, C_HARDCLOCK) + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun50i-a64-dw-hdmi", 1 }, + { "allwinner,sun8i-h3-dw-hdmi", 1 }, + { NULL, 0 } +}; + +struct aw_de2_dw_hdmi_softc { + device_t dev; + device_t phydev; + struct resource *res[2]; + void * intrhand; + struct mtx mtx; + + clk_t clk_iahb; + clk_t clk_isfr; + clk_t clk_tmds; + hwreset_t reset_ctrl; + + + device_t iicbus; + struct i2c_adapter * ddc; + uint8_t i2cm_stat; + uint8_t i2cm_addr; + + struct drm_encoder encoder; + struct drm_connector connector; + struct drm_bridge bridge; + struct drm_display_mode mode; +}; + +static struct resource_spec aw_de2_dw_hdmi_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, + { -1, 0 } +}; + +#define AW_DE2_DW_HDMI_READ_1(sc, reg) bus_read_1((sc)->res[0], (reg)) +#define AW_DE2_DW_HDMI_WRITE_1(sc, reg, val) bus_write_1((sc)->res[0], (reg), (val)) +#define AW_DE2_DW_HDMI_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) +#define AW_DE2_DW_HDMI_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) + +#define AW_DE2_DW_HDMI_LOCK(sc) mtx_lock(&(sc)->mtx) +#define AW_DE2_DW_HDMI_UNLOCK(sc) mtx_unlock(&(sc)->mtx) + +#define DDC_SEGMENT_ADDR 0x30 + +static int aw_de2_dw_hdmi_probe(device_t dev); +static int aw_de2_dw_hdmi_attach(device_t dev); +static int aw_de2_dw_hdmi_detach(device_t dev); + +/* Encoder funcs, belongs here */ +static void aw_de2_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct aw_de2_dw_hdmi_softc *sc; + uint64_t freq; + + sc = container_of(encoder, struct aw_de2_dw_hdmi_softc, encoder); + + clk_get_freq(sc->clk_tmds, &freq); + DRM_DEBUG_DRIVER("%s: Setting clock %s from %ju to %ju\n", + __func__, + clk_get_name(sc->clk_tmds), + freq, + (uintmax_t)mode->crtc_clock * 1000); + clk_set_freq(sc->clk_tmds, mode->crtc_clock * 1000, CLK_SET_ROUND_ANY); + clk_get_freq(sc->clk_tmds, &freq); + DRM_DEBUG_DRIVER("%s: New clock %s is %ju\n", + __func__, + clk_get_name(sc->clk_tmds), + freq); +} + +static const struct drm_encoder_helper_funcs aw_de2_dw_hdmi_encoder_helper_funcs = { + .mode_set = aw_de2_dw_hdmi_encoder_mode_set, +}; + +static const struct drm_encoder_funcs aw_de2_dw_hdmi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +/* Connector funcs (Need to be in dw_hdmi) */ +static enum drm_connector_status +aw_de2_dw_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct aw_de2_dw_hdmi_softc *sc; + + sc = container_of(connector, struct aw_de2_dw_hdmi_softc, connector); + + if (DW_HDMI_PHY_DETECT_HPD(sc->phydev)) + return (connector_status_connected); + + return (connector_status_disconnected); +} + +static const struct drm_connector_funcs dw_hdmi_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = aw_de2_dw_hdmi_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/* I2CM functions, need to be in dw_hdmi */ +static void +dw_hdmi_i2cm_init(struct aw_de2_dw_hdmi_softc *sc) +{ + + /* I2CM Setup */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_PHY_I2CM_INT_ADDR, 0x08); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_PHY_I2CM_CTLINT_ADDR, 0x88); + + /* Soft reset */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_SOFTRSTZ, 0); + + /* standard speed mode */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_DIV, 0); + + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_INT, + DW_HDMI_I2CM_INT_DONE_POL); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_CLINT, + DW_HDMI_I2CM_CLINT_NACK_POL | DW_HDMI_I2CM_CLINT_ARB_POL); + + /* Clear interrupts */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_I2CM_STAT0, + DW_HDMI_IH_I2CM_STAT0_ERROR | + DW_HDMI_IH_I2CM_STAT0_DONE); +} + +static int +aw_de2_dw_hdmi_i2c_write(struct aw_de2_dw_hdmi_softc *sc, uint8_t *buf, uint16_t len) +{ + int i, err = 0; + + for (i = 0; i < len; i++) { + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_DATAO, buf[i]); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_ADDRESS, i); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_OP, DW_HDMI_I2CM_OP_WR); + + while (err == 0 && sc->i2cm_stat == 0) { + err = msleep(sc, &sc->mtx, 0, "dw_hdmi_ddc", 10 * hz); + } + if (err || sc->i2cm_stat & DW_HDMI_IH_I2CM_STAT0_ERROR) { + device_printf(sc->dev, "%s: error\n", __func__); + return (ENXIO); + } + } + return (0); +} + +static int +aw_de2_dw_hdmi_i2c_read(struct aw_de2_dw_hdmi_softc *sc, uint8_t *buf, uint16_t len) +{ + int i, err = 0; + + for (i = 0; i < len; i++) { + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_ADDRESS, sc->i2cm_addr++); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_OP, DW_HDMI_I2CM_OP_RD); + + while (err == 0 && sc->i2cm_stat == 0) { + err = msleep(sc, &sc->mtx, 0, "dw_hdmi_ddc", 10 * hz); + } + if (err || sc->i2cm_stat & DW_HDMI_IH_I2CM_STAT0_ERROR) { + device_printf(sc->dev, "%s: error\n", __func__); + return (ENXIO); + } + + buf[i] = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_I2CM_DATAI); + sc->i2cm_stat = 0; + } + + return (0); +} + +static int +aw_de2_dw_hdmi_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) +{ + struct aw_de2_dw_hdmi_softc *sc; + int i, ret; + + sc = device_get_softc(dev); + AW_DE2_DW_HDMI_LOCK(sc); + + sc->i2cm_addr = 0; + for (i = 0; i < nmsgs; i++) { + sc->i2cm_stat = 0; + /* Unmute done and error interrups */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_I2CM_STAT0, 0x00); + + /* Set DDC seg/addr */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_SLAVE, msgs[i].slave >> 1); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_I2CM_SEGADDR, DDC_SEGMENT_ADDR); + + if (msgs[i].flags & IIC_M_RD) + ret = aw_de2_dw_hdmi_i2c_read(sc, msgs[i].buf, msgs[i].len); + else { + if (msgs[i].len == 1) { + sc->i2cm_addr = msgs[i].buf[0]; + } else + ret =aw_de2_dw_hdmi_i2c_write(sc, msgs[i].buf, msgs[i].len); + } + + if (ret != 0) + break; + } + + /* mute done and error interrups */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_I2CM_STAT0, 0xFF); + + AW_DE2_DW_HDMI_UNLOCK(sc); + return (0); +} + +static int +aw_de2_dw_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct aw_de2_dw_hdmi_softc *sc; + struct edid *edid = NULL; + int ret = 0; + + sc = container_of(connector, struct aw_de2_dw_hdmi_softc, connector); + + edid = drm_get_edid(connector, sc->ddc); + drm_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + + return (ret); +} + +static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { + .get_modes = aw_de2_dw_hdmi_connector_get_modes, +}; + +/* bridge funcs, should be in dw_hdmi */ +static int +dw_hdmi_bridge_attach(struct drm_bridge *bridge) +{ + struct aw_de2_dw_hdmi_softc *sc; + + sc = container_of(bridge, struct aw_de2_dw_hdmi_softc, bridge); + + sc->connector.polled = DRM_CONNECTOR_POLL_HPD; + drm_connector_helper_add(&sc->connector, &dw_hdmi_connector_helper_funcs); + + drm_connector_init(bridge->dev, &sc->connector, &dw_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + + drm_connector_attach_encoder(&sc->connector, &sc->encoder); + + return (0); +} + +/* TODO: Is there some mode that we don't support ? */ +static enum drm_mode_status +dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode) +{ + struct aw_de2_dw_hdmi_softc *sc; + + sc = container_of(bridge, struct aw_de2_dw_hdmi_softc, bridge); + + return (MODE_OK); +} + +static void +dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct aw_de2_dw_hdmi_softc *sc; + + sc = container_of(bridge, struct aw_de2_dw_hdmi_softc, bridge); + + /* Copy the mode, this will be set in bridge_enable function */ + memcpy(&sc->mode, mode, sizeof(struct drm_display_mode)); +} + +static void +dw_hdmi_bridge_disable(struct drm_bridge *bridge) +{ + struct aw_de2_dw_hdmi_softc *sc; + + sc = container_of(bridge, struct aw_de2_dw_hdmi_softc, bridge); +} + +static inline void +dw_hdmi_dump_vp_regs(struct aw_de2_dw_hdmi_softc *sc) +{ + uint8_t reg; + + DRM_DEBUG_DRIVER("%s: DW_HDMI VP Registers\n", __func__); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_VP_STATUS); + DRM_DEBUG_DRIVER("%s: DW_HDMI_VP_STATUS: %x\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_VP_PR_CD); + DRM_DEBUG_DRIVER("%s: DW_HDMI_VP_PR_CD: %x\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_VP_STUFF); + DRM_DEBUG_DRIVER("%s: DW_HDMI_VP_STUFF: %x\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_VP_REMAP); + DRM_DEBUG_DRIVER("%s: DW_HDMI_VP_REMAP: %x\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_VP_CONF); + DRM_DEBUG_DRIVER("%s: DW_HDMI_VP_CONF: %x\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_VP_MASK); + DRM_DEBUG_DRIVER("%s: DW_HDMI_VP_MASK: %x\n", __func__, reg); +} + +static inline void +dw_hdmi_dump_fc_regs(struct aw_de2_dw_hdmi_softc *sc) +{ + uint8_t reg; + + DRM_DEBUG_DRIVER("%s: DW_HDMI FC Registers\n", __func__); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_INVIDCONF); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_INVIDCONF: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_INHACTIV0); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_INHACTIV0: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_INHACTIV1); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_INHACTIV1: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_INHBLANK0); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_INHBLANK0: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_INHBLANK1); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_INHBLANK1: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_INVACTIV0); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_INVACTIV1: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_HSYNCINDELAY0); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_HSYNCINDELAY0: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_HSYNCINDELAY1); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_HSYNCINDELAY1: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_HSYNCINWIDTH0); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_HSYNCINWIDTH0: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_HSYNCINWIDTH1); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_HSYNCINWIDTH1: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_VSYNCINDELAY); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_VSYNCINDELAY: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_VSYNCINWIDTH); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_VSYNCINWIDTH: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_CTRLDUR); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_CTRLDUR: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_EXCTRLDUR); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_EXCTRLDUR: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_EXCTRLSPAC); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_EXCTRLSPAC: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_CH0PREAM); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_CH0PREAM: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_CH1PREAM); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_CH1PREAM: %d\n", __func__, reg); + reg = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_FC_CH2PREAM); + DRM_DEBUG_DRIVER("%s: DW_HDMI_FC_CH2PREAM: %d\n", __func__, reg); +} + +static void +dw_hdmi_bridge_enable(struct drm_bridge *bridge) +{ + struct aw_de2_dw_hdmi_softc *sc; + uint8_t reg; + + sc = container_of(bridge, struct aw_de2_dw_hdmi_softc, bridge); + + DRM_DEBUG_DRIVER("%s: Mode information:\n" + "hdisplay: %d\n" + "vdisplay: %d\n" + "htotal: %d\n" + "hsync_start: %d\n" + "hsync_end: %d\n" + "vsync_start: %d\n" + "vsync_end: %d\n", + __func__, + sc->mode.hdisplay, + sc->mode.vdisplay, + sc->mode.htotal, + sc->mode.vtotal, + sc->mode.hsync_start, + sc->mode.hsync_end, + sc->mode.vsync_start, + sc->mode.vsync_end); + + /* VP stuff, need to find what's really needed */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_VP_STUFF, 0x27); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_VP_CONF, 0x47); + + /* AV composer setup */ + reg = (sc->mode.flags & DRM_MODE_FLAG_PVSYNC) ? + DW_HDMI_FC_INVIDCONF_VSYNC_POL_HIGH : 0; + reg |= (sc->mode.flags & DRM_MODE_FLAG_PHSYNC) ? + DW_HDMI_FC_INVIDCONF_HSYNC_POL_HIGH : 0; + reg |= DW_HDMI_FC_INVIDCONF_DATA_POL_HIGH; + + reg |= (sc->mode.flags & DRM_MODE_FLAG_INTERLACE) ? + DW_HDMI_FC_INVIDCONF_INTERLACED_MODE : 0; + + /* Will need to depend on drm_detect_hdmi_monitor return value */ + reg |= DW_HDMI_FC_INVIDCONF_HDMI_MODE; + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_INVIDCONF, reg); + + /* Frame composer setup */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_INHACTIV0, sc->mode.hdisplay & 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_INHACTIV1, sc->mode.hdisplay >> 8); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_INHBLANK0, (sc->mode.htotal - sc->mode.hdisplay) & 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_INHBLANK1, (sc->mode.htotal - sc->mode.hdisplay) >> 8); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_INVACTIV0, sc->mode.vdisplay & 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_INVACTIV1, sc->mode.vdisplay >> 8); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_INVBLANK, sc->mode.vtotal - sc->mode.vdisplay); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_HSYNCINDELAY0, (sc->mode.hsync_start - sc->mode.hdisplay) & 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_HSYNCINDELAY1, (sc->mode.hsync_start - sc->mode.hdisplay) >> 8); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_HSYNCINWIDTH0, (sc->mode.hsync_end - sc->mode.hsync_start) & 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_HSYNCINWIDTH1, (sc->mode.hsync_end - sc->mode.hsync_start) >> 8); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_VSYNCINDELAY, sc->mode.vsync_start - sc->mode.vdisplay); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_VSYNCINWIDTH, sc->mode.vsync_end - sc->mode.vsync_start); + + /* Configure the PHY */ + DW_HDMI_PHY_CONFIG(sc->phydev, &sc->mode); + + /* 12 pixel clock cycles */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_CTRLDUR, 12); + /* 32 pixel clock cycles */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_EXCTRLDUR, 32); + /* 1 50msec spacing */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_EXCTRLSPAC, 1); + + /* pream defaults */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_CH0PREAM, 11); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_CH1PREAM, 22); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_FC_CH2PREAM, 33); + + /* Enable pixel clock and TMDS clock */ + reg = DW_HDMI_MC_CLKDIS_PREPCLK | + DW_HDMI_MC_CLKDIS_AUDCLK | + DW_HDMI_MC_CLKDIS_CSCCLK | + DW_HDMI_MC_CLKDIS_CECCLK | + DW_HDMI_MC_CLKDIS_HDCPCLK; + reg &= ~DW_HDMI_MC_CLKDIS_PIXELCLK; + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_MC_CLKDIS, reg); + + reg &= ~DW_HDMI_MC_CLKDIS_TMDSCLK; + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_MC_CLKDIS, reg); + + if (drm_debug & DRM_UT_DRIVER) { + dw_hdmi_dump_vp_regs(sc); + dw_hdmi_dump_fc_regs(sc); + } +} + +static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { + .attach = dw_hdmi_bridge_attach, + .enable = dw_hdmi_bridge_enable, + .disable = dw_hdmi_bridge_disable, + .mode_set = dw_hdmi_bridge_mode_set, + .mode_valid = dw_hdmi_bridge_mode_valid, +}; + +static int +aw_de2_dw_hdmi_add_encoder(device_t dev, struct drm_crtc *crtc, struct drm_device *drm) +{ + struct aw_de2_dw_hdmi_softc *sc; + + sc = device_get_softc(dev); + + drm_encoder_helper_add(&sc->encoder, &aw_de2_dw_hdmi_encoder_helper_funcs); + sc->encoder.possible_crtcs = drm_crtc_mask(crtc); + drm_encoder_init(drm, &sc->encoder, &aw_de2_dw_hdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + + /* This part should be in dw_hdmi */ + sc->bridge.funcs = &dw_hdmi_bridge_funcs; + drm_bridge_attach(&sc->encoder, &sc->bridge, NULL); + + return (0); +} + +static void +aw_de2_dw_hdmi_intr(void *arg) +{ + struct aw_de2_dw_hdmi_softc *sc; + + sc = (struct aw_de2_dw_hdmi_softc *)arg; + + sc->i2cm_stat = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_IH_I2CM_STAT0); + if (sc->i2cm_stat != 0) { + /* Ack interrupts */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_I2CM_STAT0, sc->i2cm_stat); + } + + wakeup(sc); +} + +/* + * Driver routines + */ + +static int +aw_de2_dw_hdmi_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Allwinner SUN8I DW HDMI"); + return (BUS_PROBE_DEFAULT); +} + +static int +aw_de2_dw_hdmi_attach(device_t dev) +{ + struct aw_de2_dw_hdmi_softc *sc; + phandle_t node, phy; + int error; + uint16_t version; + + sc = device_get_softc(dev); + sc->dev = dev; + + if (bus_alloc_resources(dev, aw_de2_dw_hdmi_spec, sc->res) != 0) { + device_printf(dev, "cannot allocate resources for device\n"); + error = ENXIO; + goto fail; + } + if (bus_setup_intr(dev, sc->res[1], + INTR_TYPE_MISC | INTR_MPSAFE, NULL, aw_de2_dw_hdmi_intr, sc, + &sc->intrhand)) { + bus_release_resources(dev, aw_de2_dw_hdmi_spec, sc->res); + device_printf(dev, "cannot setup interrupt handler\n"); + return (ENXIO); + } + + mtx_init(&sc->mtx, device_get_nameunit(dev), "dw_hdmi", MTX_DEF); + + node = ofw_bus_get_node(dev); + + /* Clock and reset */ + if ((error = clk_get_by_ofw_name(dev, node, "iahb", &sc->clk_iahb)) != 0) { + device_printf(dev, "Cannot get iahb clock\n"); + goto fail; + } + if (clk_enable(sc->clk_iahb) != 0) { + device_printf(dev, "Cannot enable iahb clock\n"); + goto fail; + } + if ((error = clk_get_by_ofw_name(dev, node, "isfr", &sc->clk_isfr)) != 0) { + device_printf(dev, "Cannot get isfr clock\n"); + goto fail; + } + if (clk_enable(sc->clk_isfr) != 0) { + device_printf(dev, "Cannot enable isfr clock\n"); + goto fail; + } + if ((error = clk_get_by_ofw_name(dev, node, "tmds", &sc->clk_tmds)) != 0) { + device_printf(dev, "Cannot get tmds clock\n"); + goto fail; + } + if (clk_enable(sc->clk_tmds) != 0) { + device_printf(dev, "Cannot enable tmds clock\n"); + goto fail; + } + if ((error = hwreset_get_by_ofw_name(dev, node, "ctrl", &sc->reset_ctrl)) != 0) { + device_printf(dev, "Cannot get reset\n"); + goto fail; + } + if (hwreset_deassert(sc->reset_ctrl) != 0) { + device_printf(dev, "Cannot deassert reset\n"); + goto fail; + } + + /* Get and init the phy */ + if (!OF_hasprop(node, "phys")) { + device_printf(dev, "No phys property\n"); + error = ENXIO; + goto fail; + } + if (OF_getencprop(node, "phys", &phy, sizeof(phy)) == -1) { + device_printf(dev, "Cannot get the phys property\n"); + error = ENXIO; + goto fail; + } + sc->phydev = OF_device_from_xref(phy); + if (sc->phydev == NULL) { + device_printf(dev, "Cannot get the phy device\n"); + error = ENXIO; + goto fail; + } + DW_HDMI_PHY_INIT(sc->phydev); + + /* Register ourself */ + OF_device_register_xref(OF_xref_from_node(node), dev); + + if (bootverbose) { + version = AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_DESIGN_ID) << 8; + version |= AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_REVISION_ID); + if (bootverbose) { + device_printf(dev, "Version: %x\n", version); + device_printf(dev, "Product ID0: %x, Product ID1: %x\n", + AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_PRODUCT_ID0), + AW_DE2_DW_HDMI_READ_1(sc, DW_HDMI_PRODUCT_ID1)); + } + } + + dw_hdmi_i2cm_init(sc); + + /* Disable interrupts */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_FC_STAT0, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_FC_STAT1, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_FC_STAT2, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_AS_STAT0, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_PHY_STAT0, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_I2CM_STAT0, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_CEC_STAT0, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_VP_STAT0, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_I2CMPHY_STAT0, 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_AHBDMAAUD_STAT0, 0xFF); + + /* Mute interrupts*/ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_FC_STAT0, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_FC_STAT1, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_FC_STAT2, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_AS_STAT0, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_PHY_STAT0, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_I2CM_STAT0, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_CEC_STAT0, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_VP_STAT0, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_I2CMPHY_STAT0, + 0xFF); + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE_AHBDMAAUD_STAT0, + 0xFF); + + /* Unmute global interrupts */ + AW_DE2_DW_HDMI_WRITE_1(sc, DW_HDMI_IH_MUTE, + ~(DW_HDMI_IH_MUTE_ALL | + DW_HDMI_IH_MUTE_WAKEUP)); + + if ((sc->iicbus = device_add_child(dev, "iicbus", -1)) == NULL) { + device_printf(dev, "could not allocate iicbus instance\n"); + aw_de2_dw_hdmi_detach(dev); + return (ENXIO); + } + sc->ddc = i2c_bsd_adapter(sc->iicbus); + + return (0); + +fail: + aw_de2_dw_hdmi_detach(dev); + return (error); +} + +static int +aw_de2_dw_hdmi_detach(device_t dev) +{ + struct aw_de2_dw_hdmi_softc *sc; + + sc = device_get_softc(dev); + + bus_release_resources(dev, aw_de2_dw_hdmi_spec, sc->res); + mtx_destroy(&sc->mtx); + + return (0); +} + +static device_method_t aw_de2_dw_hdmi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aw_de2_dw_hdmi_probe), + DEVMETHOD(device_attach, aw_de2_dw_hdmi_attach), + DEVMETHOD(device_detach, aw_de2_dw_hdmi_detach), + + /* iicbus interface */ + DEVMETHOD(iicbus_transfer, aw_de2_dw_hdmi_transfer), + + /* DW_HDMI interface */ + DEVMETHOD(dw_hdmi_add_encoder, aw_de2_dw_hdmi_add_encoder), + + DEVMETHOD_END +}; + +static driver_t aw_de2_dw_hdmi_driver = { + "aw_de2_dw_hdmi", + aw_de2_dw_hdmi_methods, + sizeof(struct aw_de2_dw_hdmi_softc), +}; + +static devclass_t aw_de2_dw_hdmi_devclass; + +EARLY_DRIVER_MODULE(aw_de2_dw_hdmi, simplebus, aw_de2_dw_hdmi_driver, + aw_de2_dw_hdmi_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_EARLY); +MODULE_VERSION(aw_de2_dw_hdmi, 1); +MODULE_DEPEND(aw_de2_dw_hdmi, aw_de2_hdmi_phy, 1, 1, 1); Index: sys/dev/drm/bridges/dw_hdmi/dw_hdmi_if.m =================================================================== --- /dev/null +++ sys/dev/drm/bridges/dw_hdmi/dw_hdmi_if.m @@ -0,0 +1,42 @@ +#- +# Copyright (c) 2019 Emmanuel Vadot +# +# 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. +# +# $FreeBSD$ +# + +INTERFACE dw_hdmi; + +HEADER { + struct drm_crtc; + struct drm_device; +}; + +# +# Add the encoder to the drm pipeline +# +METHOD int add_encoder { + device_t dev; + struct drm_crtc *crtc; + struct drm_device *drm; +}; Index: sys/dev/drm/bridges/dw_hdmi/dw_hdmi_phy_if.m =================================================================== --- /dev/null +++ sys/dev/drm/bridges/dw_hdmi/dw_hdmi_phy_if.m @@ -0,0 +1,54 @@ +#- +# Copyright (c) 2019 Emmanuel Vadot +# +# 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. +# +# $FreeBSD$ +# + +INTERFACE dw_hdmi_phy; + +HEADER { + struct drm_display_mode; +}; + +# +# Init the phy +# +METHOD int init { + device_t dev; +}; + +# +# Configure the phy for the given mode +# +METHOD int config { + device_t dev; + struct drm_display_mode *mode; +}; + +# +# Detect hotplug cable +# +METHOD bool detect_hpd { + device_t dev; +}; Index: sys/dev/drm/bridges/dw_hdmi/dw_hdmireg.h =================================================================== --- /dev/null +++ sys/dev/drm/bridges/dw_hdmi/dw_hdmireg.h @@ -0,0 +1,184 @@ +/*- + * Copyright (c) 2019 Emmanuel Vadot + * + * 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$ + */ + +#ifndef _DW_HDMI_H_ +#define _DW_HDMI_H_ + +#define DW_HDMI_DESIGN_ID 0x00 +#define DW_HDMI_REVISION_ID 0x01 +#define DW_HDMI_PRODUCT_ID0 0x02 +#define DW_HDMI_PRODUCT_ID1 0x03 +#define DW_HDMI_CONFIG0_ID 0x04 +#define DW_HDMI_CONFIG1_ID 0x05 +#define DW_HDMI_CONFIG2_ID 0x06 +#define DW_HDMI_CONFIG3_ID 0x07 + +#define DW_HDMI_IH_FC_STAT0 0x0100 +#define DW_HDMI_IH_FC_STAT1 0x0101 +#define DW_HDMI_IH_FC_STAT2 0x0102 +#define DW_HDMI_IH_AS_STAT0 0x0103 +#define DW_HDMI_IH_PHY_STAT0 0x0104 + +#define DW_HDMI_IH_I2CM_STAT0 0x0105 +#define DW_HDMI_IH_I2CM_STAT0_ERROR (1 << 0) +#define DW_HDMI_IH_I2CM_STAT0_DONE (1 << 1) + +#define DW_HDMI_IH_CEC_STAT0 0x0106 +#define DW_HDMI_IH_VP_STAT0 0x0107 +#define DW_HDMI_IH_I2CMPHY_STAT0 0x0108 +#define DW_HDMI_IH_AHBDMAAUD_STAT0 0x0109 + +#define DW_HDMI_IH_MUTE_FC_STAT0 0x0180 +#define DW_HDMI_IH_MUTE_FC_STAT1 0x0181 +#define DW_HDMI_IH_MUTE_FC_STAT2 0x0182 +#define DW_HDMI_IH_MUTE_AS_STAT0 0x0183 +#define DW_HDMI_IH_MUTE_PHY_STAT0 0x0184 + +#define DW_HDMI_IH_MUTE_I2CM_STAT0 0x0185 +#define DW_HDMI_IH_MUTE_I2CM_STAT0_ERROR (1 << 0) +#define DW_HDMI_IH_MUTE_I2CM_STAT0_DONE (1 << 1) + +#define DW_HDMI_IH_MUTE_CEC_STAT0 0x0186 +#define DW_HDMI_IH_MUTE_VP_STAT0 0x0187 +#define DW_HDMI_IH_MUTE_I2CMPHY_STAT0 0x0188 +#define DW_HDMI_IH_MUTE_AHBDMAAUD_STAT0 0x0189 + +#define DW_HDMI_IH_MUTE 0x01FF +#define DW_HDMI_IH_MUTE_ALL (1 << 0) +#define DW_HDMI_IH_MUTE_WAKEUP (1 << 1) + +/* Video Packetizer */ +#define DW_HDMI_VP_STATUS 0x0800 +#define DW_HDMI_VP_PR_CD 0x0801 +#define DW_HDMI_VP_STUFF 0x0802 +#define DW_HDMI_VP_REMAP 0x0803 +#define DW_HDMI_VP_CONF 0x0804 +#define DW_HDMI_VP_MASK 0x0807 + +/* Frame Composer */ +#define DW_HDMI_FC_INVIDCONF 0x1000 +#define DW_HDMI_FC_INVIDCONF_HSYNC_POL_HIGH (1 << 5) +#define DW_HDMI_FC_INVIDCONF_VSYNC_POL_HIGH (1 << 6) +#define DW_HDMI_FC_INVIDCONF_DATA_POL_HIGH (1 << 4) +#define DW_HDMI_FC_INVIDCONF_HDMI_MODE (1 << 3) +#define DW_HDMI_FC_INVIDCONF_INTERLACED_MODE (1 << 0) +#define DW_HDMI_FC_INHACTIV0 0x1001 +#define DW_HDMI_FC_INHACTIV1 0x1002 +#define DW_HDMI_FC_INHBLANK0 0x1003 +#define DW_HDMI_FC_INHBLANK1 0x1004 +#define DW_HDMI_FC_INVACTIV0 0x1005 +#define DW_HDMI_FC_INVACTIV1 0x1006 +#define DW_HDMI_FC_INVBLANK 0x1007 +#define DW_HDMI_FC_HSYNCINDELAY0 0x1008 +#define DW_HDMI_FC_HSYNCINDELAY1 0x1009 +#define DW_HDMI_FC_HSYNCINWIDTH0 0x100A +#define DW_HDMI_FC_HSYNCINWIDTH1 0x100B +#define DW_HDMI_FC_VSYNCINDELAY 0x100C +#define DW_HDMI_FC_VSYNCINWIDTH 0x100D +#define DW_HDMI_FC_CTRLDUR 0x1011 +#define DW_HDMI_FC_EXCTRLDUR 0x1012 +#define DW_HDMI_FC_EXCTRLSPAC 0x1013 +#define DW_HDMI_FC_CH0PREAM 0x1014 +#define DW_HDMI_FC_CH1PREAM 0x1015 +#define DW_HDMI_FC_CH2PREAM 0x1016 +#define DW_HDMI_FC_AVICONF3 0x1017 +#define DW_HDMI_FC_GCP 0x1018 +#define DW_HDMI_FC_AVICONF0 0x1019 +#define DW_HDMI_FC_AVICONF1 0x101A +#define DW_HDMI_FC_AVICONF2 0x101B +#define DW_HDMI_FC_AVIDVID 0x101C +#define DW_HDMI_FC_AVIDETB0 0x101D +#define DW_HDMI_FC_AVIDETB1 0x101E +#define DW_HDMI_FC_AVISBB0 0x101F +#define DW_HDMI_FC_AVISBB1 0x1020 +#define DW_HDMI_FC_AVIELB0 0x1021 +#define DW_HDMI_FC_AVIELB1 0x1022 +#define DW_HDMI_FC_AVISRB0 0x1023 +#define DW_HDMI_FC_AVISRB1 0x1024 +#define DW_HDMI_FC_AUDICONF0 0x1025 +#define DW_HDMI_FC_AUDICONF1 0x1026 +#define DW_HDMI_FC_AUDICONF2 0x1027 +#define DW_HDMI_FC_AUDICONF3 0x1028 +#define DW_HDMI_FC_VSDIEEEID0 0x1029 +#define DW_HDMI_FC_VSDSIZE 0x102A + +/* Main Controller */ +#define DW_HDMI_MC_CLKDIS 0x4001 +#define DW_HDMI_MC_CLKDIS_PIXELCLK (1 << 0) +#define DW_HDMI_MC_CLKDIS_TMDSCLK (1 << 1) +#define DW_HDMI_MC_CLKDIS_PREPCLK (1 << 2) +#define DW_HDMI_MC_CLKDIS_AUDCLK (1 << 3) +#define DW_HDMI_MC_CLKDIS_CSCCLK (1 << 4) +#define DW_HDMI_MC_CLKDIS_CECCLK (1 << 5) +#define DW_HDMI_MC_CLKDIS_HDCPCLK (1 << 6) +#define DW_HDMI_MC_SWRSTZREQ 0x4002 +#define DW_HDMI_MC_FLOWCTRL 0x4004 +#define DW_HDMI_MC_PHYRSTZ 0x4005 +#define DW_HDMI_MC_LOCKONCLOCK 0x4006 +#define DW_HDMI_MC_HEACPHY_RST 0x4007 + +/* PHY I2CM */ +#define DW_HDMI_PHY_I2CM_INT_ADDR 0x3027 +#define DW_HDMI_PHY_I2CM_CTLINT_ADDR 0x3028 + +/* I2CM */ +#define DW_HDMI_I2CM_SLAVE 0x7E00 +#define DW_HDMI_I2CM_ADDRESS 0x7E01 +#define DW_HDMI_I2CM_DATAO 0x7E02 +#define DW_HDMI_I2CM_DATAI 0x7E03 +#define DW_HDMI_I2CM_OP 0x7E04 +#define DW_HDMI_I2CM_OP_RD (1 << 0) +#define DW_HDMI_I2CM_OP_RDEXT (1 << 1) +#define DW_HDMI_I2CM_OP_WR (1 << 4) + +#define DW_HDMI_I2CM_INT 0x7E05 +#define DW_HDMI_I2CM_INT_DONE_STATUS (1 << 0) +#define DW_HDMI_I2CM_INT_DONE_INT (1 << 1) +#define DW_HDMI_I2CM_INT_DONE_MASK (1 << 2) +#define DW_HDMI_I2CM_INT_DONE_POL (1 << 3) + +#define DW_HDMI_I2CM_CLINT 0x7E06 +#define DW_HDMI_I2CM_CLINT_ARB_STATUS (1 << 0) +#define DW_HDMI_I2CM_CLINT_ARB_INT (1 << 1) +#define DW_HDMI_I2CM_CLINT_ARB_MASK (1 << 2) +#define DW_HDMI_I2CM_CLINT_ARB_POL (1 << 3) +#define DW_HDMI_I2CM_CLINT_NACK_STATUS (1 << 4) +#define DW_HDMI_I2CM_CLINT_NACK_INT (1 << 5) +#define DW_HDMI_I2CM_CLINT_NACK_MASK (1 << 6) +#define DW_HDMI_I2CM_CLINT_NACK_POL (1 << 7) + +#define DW_HDMI_I2CM_DIV 0x7E07 +#define DW_HDMI_I2CM_DIV_FAST_MODE (1 << 3) + +#define DW_HDMI_I2CM_SEGADDR 0x7E08 + +#define DW_HDMI_I2CM_SOFTRSTZ 0x7E09 +#define DW_HDMI_I2CM_SOFTRSTZ_RST (1 << 0) + +#define DW_HDMI_I2CM_SEGPTR 0x7E0A + +#endif /* _DW_HDMI_H_ */