Add support for Allwinner A64's display pipeline.

This commit is contained in:
jmcneill 2019-01-30 01:24:00 +00:00
parent 314f9af4b4
commit a9d036463e
19 changed files with 2581 additions and 17 deletions

View File

@ -1,4 +1,4 @@
# $NetBSD: files.sunxi,v 1.60 2019/01/22 20:17:36 jmcneill Exp $
# $NetBSD: files.sunxi,v 1.61 2019/01/30 01:24:00 jmcneill Exp $
#
# Configuration info for Allwinner sunxi family SoCs
#
@ -307,6 +307,36 @@ device sunxide2ccu: sunxi_ccu
attach sunxide2ccu at fdt with sunxi_de2ccu
file arch/arm/sunxi/sunxi_de2_ccu.c sunxi_de2ccu
# DE2 mixer
device sunximixer: drmkms
attach sunximixer at fdt with sunxi_mixer
file arch/arm/sunxi/sunxi_mixer.c sunxi_mixer
# DE2 timing controller
device sunxilcdc: drmkms
attach sunxilcdc at fdt with sunxi_lcdc
file arch/arm/sunxi/sunxi_lcdc.c sunxi_lcdc
# Display Pipeline
define sunxifbbus { }
device sunxidrm: drmkms, ddc_read_edid, sunxifbbus
attach sunxidrm at fdt with sunxi_drm
file arch/arm/sunxi/sunxi_drm.c sunxi_drm
# DRM framebuffer console
device sunxifb: sunxifbbus, drmfb, wsemuldisplaydev
attach sunxifb at sunxifbbus with sunxi_fb
file arch/arm/sunxi/sunxi_fb.c sunxi_fb
# Allwinner HDMI (Designware based)
attach dwhdmi at fdt with sunxi_dwhdmi
file arch/arm/sunxi/sunxi_dwhdmi.c sunxi_dwhdmi
# Allwinner HDMI TX PHY
device sunxihdmiphy: drmkms
attach sunxihdmiphy at fdt with sunxi_hdmiphy
file arch/arm/sunxi/sunxi_hdmiphy.c sunxi_hdmiphy | sunxi_dwhdmi
# SOC parameters
defflag opt_soc.h SOC_SUNXI
defflag opt_soc.h SOC_SUNXI_MC

View File

@ -1,4 +1,4 @@
/* $NetBSD: sun50i_a64_ccu.c,v 1.10 2019/01/22 23:06:49 jmcneill Exp $ */
/* $NetBSD: sun50i_a64_ccu.c,v 1.11 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
@ -28,7 +28,7 @@
#include <sys/cdefs.h>
__KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.10 2019/01/22 23:06:49 jmcneill Exp $");
__KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.11 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
@ -42,8 +42,10 @@ __KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.10 2019/01/22 23:06:49 jmcneill
#define PLL_CPUX_CTRL_REG 0x000
#define PLL_AUDIO_CTRL_REG 0x008
#define PLL_VIDEO0_CTRL_REG 0x010
#define PLL_PERIPH0_CTRL_REG 0x028
#define PLL_PERIPH1_CTRL_REG 0x02c
#define PLL_VIDEO1_CTRL_REG 0x030
#define PLL_DE_CTRL_REG 0x048
#define AHB1_APB1_CFG_REG 0x054
#define APB2_CFG_REG 0x058
@ -61,7 +63,10 @@ __KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.10 2019/01/22 23:06:49 jmcneill
#define DRAM_CFG_REG 0x0f4
#define MBUS_RST_REG 0x0fc
#define DE_CLK_REG 0x104
#define TCON1_CLK_REG 0x11c
#define AC_DIG_CLK_REG 0x140
#define HDMI_CLK_REG 0x150
#define HDMI_SLOW_CLK_REG 0x154
#define BUS_SOFT_RST_REG0 0x2c0
#define BUS_SOFT_RST_REG1 0x2c4
#define BUS_SOFT_RST_REG2 0x2c8
@ -146,6 +151,8 @@ static const char *apb2_parents[] = { "losc", "hosc", "pll_periph0" };
static const char *mmc_parents[] = { "hosc", "pll_periph0_2x", "pll_periph1_2x" };
static const char *ths_parents[] = { "hosc", NULL, NULL, NULL };
static const char *de_parents[] = { "pll_periph0_2x", "pll_de" };
static const char *hdmi_parents[] = { "pll_video0", "pll_video1" };
static const char *tcon1_parents[] = { "pll_video0", NULL, "pll_video1", NULL };
static const struct sunxi_ccu_nkmp_tbl sun50i_a64_cpux_table[] = {
{ 60000000, 9, 0, 0, 2 },
@ -262,8 +269,36 @@ static struct sunxi_ccu_clk sun50i_a64_ccu_clks[] = {
SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_AUDIO_4X, "pll_audio_4x", "pll_audio_base", 1, 4),
SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_AUDIO_8X, "pll_audio_8x", "pll_audio_base", 1, 8),
SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_VIDEO0, "pll_video0", "hosc",
PLL_VIDEO0_CTRL_REG, /* reg */
__BITS(14,8), /* m */
16, /* m_min */
50, /* m_max */
__BIT(24), /* div_en */
__BIT(25), /* frac_sel */
270000000, 297000000, /* frac values */
__BITS(3,0), /* prediv */
4, /* prediv_val */
__BIT(31), /* enable */
SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE),
SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_VIDEO0_2X, "pll_video0_2x", "pll_video0", 1, 2),
SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_VIDEO1, "pll_video1", "hosc",
PLL_VIDEO1_CTRL_REG, /* reg */
__BITS(14,8), /* m */
16, /* m_min */
50, /* m_max */
__BIT(24), /* div_en */
__BIT(25), /* frac_sel */
270000000, 297000000, /* frac values */
__BITS(3,0), /* prediv */
4, /* prediv_val */
__BIT(31), /* enable */
SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE),
SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_DE, "pll_de", "hosc",
DE_CLK_REG, /* reg */
PLL_DE_CTRL_REG, /* reg */
__BITS(14,8), /* m */
16, /* m_min */
50, /* m_max */
@ -273,7 +308,7 @@ static struct sunxi_ccu_clk sun50i_a64_ccu_clks[] = {
__BITS(3,0), /* prediv */
2, /* prediv_val */
__BIT(31), /* enable */
SUNXI_CCU_FRACTIONAL_PLUSONE),
SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE),
SUNXI_CCU_PREDIV(A64_CLK_AHB1, "ahb1", ahb1_parents,
AHB1_APB1_CFG_REG, /* reg */
@ -346,6 +381,23 @@ static struct sunxi_ccu_clk sun50i_a64_ccu_clks[] = {
SUNXI_CCU_GATE(A64_CLK_AC_DIG_4X, "ac-dig-4x", "pll_audio_4x",
AC_DIG_CLK_REG, 30),
SUNXI_CCU_DIV_GATE(A64_CLK_HDMI, "hdmi", hdmi_parents,
HDMI_CLK_REG, /* reg */
__BITS(3,0), /* div */
__BITS(25,24), /* sel */
__BIT(31), /* enable */
0),
SUNXI_CCU_GATE(A64_CLK_HDMI_DDC, "hdmi-ddc", "hosc",
HDMI_SLOW_CLK_REG, 31),
SUNXI_CCU_DIV_GATE(A64_CLK_TCON1, "tcon1", tcon1_parents,
TCON1_CLK_REG, /* reg */
__BITS(3,0), /* div */
__BITS(25,24), /* sel */
__BIT(31), /* enable */
0),
SUNXI_CCU_GATE(A64_CLK_BUS_MIPI_DSI, "bus-mipi-dsi", "ahb1",
BUS_CLK_GATING_REG0, 1),
SUNXI_CCU_GATE(A64_CLK_BUS_CE, "bus-ce", "ahb1",
@ -482,5 +534,9 @@ sun50i_a64_ccu_attach(device_t parent, device_t self, void *aux)
aprint_naive("\n");
aprint_normal(": A64 CCU\n");
/* Set DE parent to PLL_DE */
clk_set_parent(&sc->sc_clks[A64_CLK_DE].base, &sc->sc_clks[A64_CLK_PLL_DE].base);
clk_set_rate(&sc->sc_clks[A64_CLK_PLL_DE].base, 420000000);
sunxi_ccu_print(sc);
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: sunxi_ccu.h,v 1.20 2019/01/22 23:06:49 jmcneill Exp $ */
/* $NetBSD: sunxi_ccu.h,v 1.21 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
@ -373,6 +373,7 @@ struct sunxi_ccu_fractional {
uint32_t enable;
uint32_t flags;
#define SUNXI_CCU_FRACTIONAL_PLUSONE __BIT(0)
#define SUNXI_CCU_FRACTIONAL_SET_ENABLE __BIT(1)
};
int sunxi_ccu_fractional_enable(struct sunxi_ccu_softc *,

View File

@ -1,4 +1,4 @@
/* $NetBSD: sunxi_ccu_fractional.c,v 1.3 2019/01/22 23:06:49 jmcneill Exp $ */
/* $NetBSD: sunxi_ccu_fractional.c,v 1.4 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_fractional.c,v 1.3 2019/01/22 23:06:49 jmcneill Exp $");
__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_fractional.c,v 1.4 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
@ -169,8 +169,11 @@ sunxi_ccu_fractional_set_rate(struct sunxi_ccu_softc *sc,
val &= ~fractional->m;
val |= __SHIFTIN(best_m, fractional->m);
if (fractional->flags & SUNXI_CCU_FRACTIONAL_SET_ENABLE)
val |= fractional->enable;
CCU_WRITE(sc, fractional->reg, val);
return 0;
}

View File

@ -0,0 +1,407 @@
/* $NetBSD: sunxi_drm.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sunxi_drm.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <uvm/uvm_extern.h>
#include <uvm/uvm_object.h>
#include <uvm/uvm_device.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
#include <dev/fdt/fdtvar.h>
#include <dev/fdt/fdt_port.h>
#include <arm/sunxi/sunxi_drm.h>
static TAILQ_HEAD(, sunxi_drm_endpoint) sunxi_drm_endpoints =
TAILQ_HEAD_INITIALIZER(sunxi_drm_endpoints);
static const char * const compatible[] = {
"allwinner,sun50i-a64-display-engine",
NULL
};
static const char * fb_compatible[] = {
"allwinner,simple-framebuffer",
NULL
};
static int sunxi_drm_match(device_t, cfdata_t, void *);
static void sunxi_drm_attach(device_t, device_t, void *);
static void sunxi_drm_init(device_t);
static int sunxi_drm_set_busid(struct drm_device *, struct drm_master *);
static int sunxi_drm_load(struct drm_device *, unsigned long);
static int sunxi_drm_unload(struct drm_device *);
static struct drm_driver sunxi_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
.dev_priv_size = 0,
.load = sunxi_drm_load,
.unload = sunxi_drm_unload,
.gem_free_object = drm_gem_cma_free_object,
.mmap_object = drm_gem_or_legacy_mmap_object,
.gem_uvm_ops = &drm_gem_cma_uvm_ops,
.dumb_create = drm_gem_cma_dumb_create,
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
.dumb_destroy = drm_gem_dumb_destroy,
#if notyet
.get_vblank_counter = sunxi_drm_get_vblank_counter,
.enable_vblank = sunxi_drm_enable_vblank,
.disable_vblank = sunxi_drm_disable_vblank,
#endif
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
.set_busid = sunxi_drm_set_busid,
};
CFATTACH_DECL_NEW(sunxi_drm, sizeof(struct sunxi_drm_softc),
sunxi_drm_match, sunxi_drm_attach, NULL, NULL);
static int
sunxi_drm_match(device_t parent, cfdata_t cf, void *aux)
{
struct fdt_attach_args * const faa = aux;
return of_match_compatible(faa->faa_phandle, compatible);
}
static void
sunxi_drm_attach(device_t parent, device_t self, void *aux)
{
struct sunxi_drm_softc * const sc = device_private(self);
struct fdt_attach_args * const faa = aux;
struct drm_driver * const driver = &sunxi_drm_driver;
sc->sc_dev = self;
sc->sc_dmat = faa->faa_dmat;
sc->sc_bst = faa->faa_bst;
sc->sc_phandle = faa->faa_phandle;
aprint_naive("\n");
aprint_normal(": Display Engine Pipeline\n");
sc->sc_ddev = drm_dev_alloc(driver, sc->sc_dev);
if (sc->sc_ddev == NULL) {
aprint_error_dev(self, "couldn't allocate DRM device\n");
return;
}
sc->sc_ddev->dev_private = sc;
sc->sc_ddev->bst = sc->sc_bst;
sc->sc_ddev->bus_dmat = sc->sc_dmat;
sc->sc_ddev->dmat = sc->sc_ddev->bus_dmat;
sc->sc_ddev->dmat_subregion_p = false;
fdt_remove_bycompat(fb_compatible);
config_defer(self, sunxi_drm_init);
}
static void
sunxi_drm_init(device_t dev)
{
struct sunxi_drm_softc * const sc = device_private(dev);
struct drm_driver * const driver = &sunxi_drm_driver;
int error;
error = -drm_dev_register(sc->sc_ddev, 0);
if (error) {
drm_dev_unref(sc->sc_ddev);
aprint_error_dev(dev, "couldn't register DRM device: %d\n",
error);
return;
}
aprint_normal_dev(dev, "initialized %s %d.%d.%d %s on minor %d\n",
driver->name, driver->major, driver->minor, driver->patchlevel,
driver->date, sc->sc_ddev->primary->index);
}
static int
sunxi_drm_set_busid(struct drm_device *ddev, struct drm_master *master)
{
struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev);
char id[32];
snprintf(id, sizeof(id), "platform:sunxi:%u", device_unit(sc->sc_dev));
master->unique = kzalloc(strlen(id) + 1, GFP_KERNEL);
if (master->unique == NULL)
return -ENOMEM;
strcpy(master->unique, id);
master->unique_len = strlen(master->unique);
return 0;
}
static int
sunxi_drm_fb_create_handle(struct drm_framebuffer *fb,
struct drm_file *file, unsigned int *handle)
{
struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(fb);
return drm_gem_handle_create(file, &sfb->obj->base, handle);
}
static void
sunxi_drm_fb_destroy(struct drm_framebuffer *fb)
{
struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(fb);
drm_framebuffer_cleanup(fb);
drm_gem_object_unreference_unlocked(&sfb->obj->base);
kmem_free(sfb, sizeof(*sfb));
}
static const struct drm_framebuffer_funcs sunxi_drm_framebuffer_funcs = {
.create_handle = sunxi_drm_fb_create_handle,
.destroy = sunxi_drm_fb_destroy,
};
static struct drm_framebuffer *
sunxi_drm_fb_create(struct drm_device *ddev, struct drm_file *file,
struct drm_mode_fb_cmd2 *cmd)
{
struct sunxi_drm_framebuffer *fb;
struct drm_gem_object *gem_obj;
int error;
if (cmd->flags)
return NULL;
gem_obj = drm_gem_object_lookup(ddev, file, cmd->handles[0]);
if (gem_obj == NULL)
return NULL;
fb = kmem_zalloc(sizeof(*fb), KM_SLEEP);
fb->obj = to_drm_gem_cma_obj(gem_obj);
fb->base.pitches[0] = cmd->pitches[0];
fb->base.offsets[0] = cmd->offsets[0];
fb->base.width = cmd->width;
fb->base.height = cmd->height;
fb->base.pixel_format = cmd->pixel_format;
drm_fb_get_bpp_depth(cmd->pixel_format, &fb->base.depth,
&fb->base.bits_per_pixel);
error = drm_framebuffer_init(ddev, &fb->base, &sunxi_drm_framebuffer_funcs);
if (error != 0)
goto dealloc;
return &fb->base;
dealloc:
drm_framebuffer_cleanup(&fb->base);
kmem_free(fb, sizeof(*fb));
drm_gem_object_unreference_unlocked(gem_obj);
return NULL;
}
static struct drm_mode_config_funcs sunxi_drm_mode_config_funcs = {
.fb_create = sunxi_drm_fb_create,
};
static int
sunxi_drm_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes)
{
struct sunxi_drm_softc * const sc = sunxi_drm_private(helper->dev);
struct drm_device *ddev = helper->dev;
struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(helper->fb);
struct drm_framebuffer *fb = helper->fb;
struct sunxi_drmfb_attach_args sfa;
int error;
const u_int width = sizes->surface_width;
const u_int height = sizes->surface_height;
const u_int pitch = width * (32 / 8);
const size_t size = roundup(height * pitch, PAGE_SIZE);
sfb->obj = drm_gem_cma_create(ddev, size);
if (sfb->obj == NULL) {
DRM_ERROR("failed to allocate memory for framebuffer\n");
return -ENOMEM;
}
fb->pitches[0] = pitch;
fb->offsets[0] = 0;
fb->width = width;
fb->height = height;
fb->pixel_format = DRM_FORMAT_XRGB8888;
drm_fb_get_bpp_depth(fb->pixel_format, &fb->depth, &fb->bits_per_pixel);
error = drm_framebuffer_init(ddev, fb, &sunxi_drm_framebuffer_funcs);
if (error != 0) {
DRM_ERROR("failed to initialize framebuffer\n");
return error;
}
memset(&sfa, 0, sizeof(sfa));
sfa.sfa_drm_dev = ddev;
sfa.sfa_fb_helper = helper;
sfa.sfa_fb_sizes = *sizes;
sfa.sfa_fb_bst = sc->sc_bst;
sfa.sfa_fb_dmat = sc->sc_dmat;
sfa.sfa_fb_linebytes = helper->fb->pitches[0];
helper->fbdev = config_found_ia(ddev->dev, "sunxifbbus", &sfa, NULL);
if (helper->fbdev == NULL) {
DRM_ERROR("unable to attach framebuffer\n");
return -ENXIO;
}
return 0;
}
static struct drm_fb_helper_funcs sunxi_drm_fb_helper_funcs = {
.fb_probe = sunxi_drm_fb_probe,
};
static int
sunxi_drm_load(struct drm_device *ddev, unsigned long flags)
{
struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev);
struct sunxi_drm_endpoint *sep;
struct sunxi_drm_fbdev *fbdev;
const u_int *data;
int datalen, error, num_crtc;
drm_mode_config_init(ddev);
ddev->mode_config.min_width = 0;
ddev->mode_config.min_height = 0;
ddev->mode_config.max_width = 3840;
ddev->mode_config.max_height = 2160;
ddev->mode_config.funcs = &sunxi_drm_mode_config_funcs;
num_crtc = 0;
data = fdtbus_get_prop(sc->sc_phandle, "allwinner,pipelines", &datalen);
while (datalen >= 4) {
const int crtc_phandle = fdtbus_get_phandle_from_native(be32dec(data));
TAILQ_FOREACH(sep, &sunxi_drm_endpoints, entries)
if (sep->phandle == crtc_phandle && sep->ddev == NULL) {
sep->ddev = ddev;
error = fdt_endpoint_activate_direct(sep->ep, true);
if (error != 0) {
aprint_error_dev(sc->sc_dev, "failed to activate endpoint: %d\n",
error);
}
if (fdt_endpoint_type(sep->ep) == EP_DRM_CRTC)
num_crtc++;
}
datalen -= 4;
data++;
}
if (num_crtc == 0) {
aprint_error_dev(sc->sc_dev, "no pipelines configured\n");
return ENXIO;
}
fbdev = kmem_zalloc(sizeof(*fbdev), KM_SLEEP);
drm_fb_helper_prepare(ddev, &fbdev->helper, &sunxi_drm_fb_helper_funcs);
error = drm_fb_helper_init(ddev, &fbdev->helper, num_crtc, num_crtc);
if (error)
goto drmerr;
fbdev->helper.fb = kmem_zalloc(sizeof(struct sunxi_drm_framebuffer), KM_SLEEP);
drm_fb_helper_single_add_all_connectors(&fbdev->helper);
drm_helper_disable_unused_functions(ddev);
drm_fb_helper_initial_config(&fbdev->helper, 32);
return 0;
drmerr:
drm_mode_config_cleanup(ddev);
kmem_free(fbdev, sizeof(*fbdev));
return error;
}
static int
sunxi_drm_unload(struct drm_device *ddev)
{
drm_mode_config_cleanup(ddev);
return 0;
}
int
sunxi_drm_register_endpoint(int phandle, struct fdt_endpoint *ep)
{
struct sunxi_drm_endpoint *sep;
sep = kmem_zalloc(sizeof(*sep), KM_SLEEP);
sep->phandle = phandle;
sep->ep = ep;
sep->ddev = NULL;
TAILQ_INSERT_TAIL(&sunxi_drm_endpoints, sep, entries);
return 0;
}
struct drm_device *
sunxi_drm_endpoint_device(struct fdt_endpoint *ep)
{
struct sunxi_drm_endpoint *sep;
TAILQ_FOREACH(sep, &sunxi_drm_endpoints, entries)
if (sep->ep == ep)
return sep->ddev;
return NULL;
}

View File

@ -0,0 +1,88 @@
/* $NetBSD: sunxi_drm.h,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _ARM_SUNXI_DRM_H
#define _ARM_SUNXI_DRM_H
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#define DRIVER_AUTHOR "Jared McNeill"
#define DRIVER_NAME "sunxi"
#define DRIVER_DESC "Allwinner Display Engine"
#define DRIVER_DATE "20190123"
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 0
#define DRIVER_PATCHLEVEL 0
struct sunxi_framebuffer;
struct sunxi_drm_softc {
device_t sc_dev;
struct drm_device *sc_ddev;
bus_space_tag_t sc_bst;
bus_dma_tag_t sc_dmat;
int sc_phandle;
};
struct sunxi_drm_framebuffer {
struct drm_framebuffer base;
struct drm_gem_cma_object *obj;
};
struct sunxi_drm_endpoint {
int phandle;
struct fdt_endpoint *ep;
struct drm_device *ddev;
TAILQ_ENTRY(sunxi_drm_endpoint) entries;
};
struct sunxi_drm_fbdev {
struct drm_fb_helper helper;
};
struct sunxi_drmfb_attach_args {
struct drm_device *sfa_drm_dev;
struct drm_fb_helper *sfa_fb_helper;
struct drm_fb_helper_surface_size sfa_fb_sizes;
bus_space_tag_t sfa_fb_bst;
bus_dma_tag_t sfa_fb_dmat;
uint32_t sfa_fb_linebytes;
};
#define sunxi_drm_private(ddev) (ddev)->dev_private
#define to_sunxi_drm_framebuffer(x) container_of(x, struct sunxi_drm_framebuffer, base)
int sunxi_drm_register_endpoint(int, struct fdt_endpoint *);
struct drm_device *sunxi_drm_endpoint_device(struct fdt_endpoint *);
#endif /* _ARM_SUNXI_DRM_H */

View File

@ -0,0 +1,258 @@
/* $NetBSD: sunxi_dwhdmi.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sunxi_dwhdmi.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <drm/drmP.h>
#include <dev/fdt/fdtvar.h>
#include <dev/fdt/fdt_port.h>
#include <dev/ic/dw_hdmi.h>
#include <arm/sunxi/sunxi_hdmiphy.h>
enum {
DWHDMI_PORT_INPUT = 0,
DWHDMI_PORT_OUTPUT = 1,
};
static const char * const compatible[] = {
"allwinner,sun50i-a64-dw-hdmi",
NULL
};
struct sunxi_dwhdmi_softc {
struct dwhdmi_softc sc_base;
int sc_phandle;
struct fdtbus_phy *sc_phy;
struct fdt_device_ports sc_ports;
struct drm_display_mode sc_curmode;
};
#define to_sunxi_dwhdmi_softc(x) container_of(x, struct sunxi_dwhdmi_softc, sc_base)
static int
sunxi_dwhdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
{
struct sunxi_dwhdmi_softc * const sc = device_private(dev);
struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep);
struct fdt_endpoint *out_ep, *out_rep;
struct drm_encoder *encoder;
struct drm_bridge *bridge;
int error;
if (!activate)
return EINVAL;
if (fdt_endpoint_port_index(ep) != DWHDMI_PORT_INPUT)
return EINVAL;
switch (fdt_endpoint_type(in_ep)) {
case EP_DRM_ENCODER:
encoder = fdt_endpoint_get_data(in_ep);
break;
case EP_DRM_BRIDGE:
bridge = fdt_endpoint_get_data(in_ep);
encoder = bridge->encoder;
break;
default:
encoder = NULL;
break;
}
if (encoder == NULL)
return EINVAL;
sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "hdmi-phy");
if (sc->sc_phy == NULL) {
device_printf(dev, "couldn't find hdmi-phy\n");
return ENXIO;
}
error = dwhdmi_bind(&sc->sc_base, encoder);
if (error != 0)
return error;
out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, DWHDMI_PORT_OUTPUT, 0);
if (out_ep != NULL) {
/* Ignore downstream connectors, we have our own. */
out_rep = fdt_endpoint_remote(out_ep);
if (out_rep != NULL && fdt_endpoint_type(out_rep) == EP_DRM_CONNECTOR)
return 0;
error = fdt_endpoint_activate(out_ep, activate);
if (error != 0)
return error;
}
return 0;
}
static void *
sunxi_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
{
struct sunxi_dwhdmi_softc * const sc = device_private(dev);
return &sc->sc_base.sc_bridge;
}
static enum drm_connector_status
sunxi_dwhdmi_detect(struct dwhdmi_softc *dsc, bool force)
{
struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
KASSERT(sc->sc_phy != NULL);
if (sunxi_hdmiphy_detect(sc->sc_phy, force))
return connector_status_connected;
else
return connector_status_disconnected;
}
static void
sunxi_dwhdmi_enable(struct dwhdmi_softc *dsc)
{
struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
int error;
KASSERT(sc->sc_phy != NULL);
error = fdtbus_phy_enable(sc->sc_phy, true);
if (error != 0) {
device_printf(dsc->sc_dev, "failed to enable phy: %d\n", error);
return;
}
error = sunxi_hdmiphy_config(sc->sc_phy, &sc->sc_curmode);
if (error != 0)
device_printf(dsc->sc_dev, "failed to configure phy: %d\n", error);
}
static void
sunxi_dwhdmi_disable(struct dwhdmi_softc *dsc)
{
struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
int error;
KASSERT(sc->sc_phy != NULL);
error = fdtbus_phy_enable(sc->sc_phy, false);
if (error != 0)
device_printf(dsc->sc_dev, "failed to disable phy\n");
}
static void
sunxi_dwhdmi_mode_set(struct dwhdmi_softc *dsc, struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
sc->sc_curmode = *adjusted_mode;
}
static int
sunxi_dwhdmi_match(device_t parent, cfdata_t cf, void *aux)
{
struct fdt_attach_args * const faa = aux;
return of_match_compatible(faa->faa_phandle, compatible);
}
static void
sunxi_dwhdmi_attach(device_t parent, device_t self, void *aux)
{
struct sunxi_dwhdmi_softc * const sc = device_private(self);
struct fdt_attach_args * const faa = aux;
const int phandle = faa->faa_phandle;
struct clk *clk_iahb, *clk_isfr;
struct fdtbus_reset *rst;
bus_addr_t addr;
bus_size_t size;
if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
aprint_error(": couldn't get registers\n");
return;
}
rst = fdtbus_reset_get(phandle, "ctrl");
if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
aprint_error(": couldn't de-assert reset\n");
return;
}
clk_iahb = fdtbus_clock_get(phandle, "iahb");
if (clk_iahb == NULL || clk_enable(clk_iahb) != 0) {
aprint_error(": couldn't enable iahb clock\n");
return;
}
clk_isfr = fdtbus_clock_get(phandle, "isfr");
if (clk_isfr == NULL || clk_enable(clk_isfr) != 0) {
aprint_error(": couldn't enable isfr clock\n");
return;
}
sc->sc_base.sc_dev = self;
sc->sc_base.sc_reg_width = 1;
sc->sc_base.sc_bst = faa->faa_bst;
if (bus_space_map(sc->sc_base.sc_bst, addr, size, 0, &sc->sc_base.sc_bsh) != 0) {
aprint_error(": couldn't map registers\n");
return;
}
sc->sc_base.sc_detect = sunxi_dwhdmi_detect;
sc->sc_base.sc_enable = sunxi_dwhdmi_enable;
sc->sc_base.sc_disable = sunxi_dwhdmi_disable;
sc->sc_base.sc_mode_set = sunxi_dwhdmi_mode_set;
sc->sc_phandle = faa->faa_phandle;
aprint_naive("\n");
aprint_normal(": HDMI TX\n");
if (dwhdmi_attach(&sc->sc_base) != 0) {
aprint_error_dev(self, "failed to attach driver\n");
return;
}
sc->sc_ports.dp_ep_activate = sunxi_dwhdmi_ep_activate;
sc->sc_ports.dp_ep_get_data = sunxi_dwhdmi_ep_get_data;
fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE);
}
CFATTACH_DECL_NEW(sunxi_dwhdmi, sizeof(struct sunxi_dwhdmi_softc),
sunxi_dwhdmi_match, sunxi_dwhdmi_attach, NULL, NULL);

View File

@ -0,0 +1,163 @@
/* $NetBSD: sunxi_fb.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2015-2019 Jared McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "opt_wsdisplay_compat.h"
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sunxi_fb.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <dev/fdt/fdtvar.h>
#include <drm/drmP.h>
#include <drm/drmfb.h>
#include <arm/sunxi/sunxi_drm.h>
static int sunxi_fb_match(device_t, cfdata_t, void *);
static void sunxi_fb_attach(device_t, device_t, void *);
static bool sunxi_fb_shutdown(device_t, int);
struct sunxi_fb_softc {
struct drmfb_softc sc_drmfb;
device_t sc_dev;
struct sunxi_drm_softc *sc_drm;
struct sunxi_drm_framebuffer *sc_fb;
struct sunxi_drmfb_attach_args sc_sfa;
};
static paddr_t sunxi_fb_mmapfb(struct drmfb_softc *, off_t, int);
static int sunxi_fb_ioctl(struct drmfb_softc *, u_long, void *, int,
lwp_t *);
static const struct drmfb_params sunxifb_drmfb_params = {
.dp_mmapfb = sunxi_fb_mmapfb,
.dp_ioctl = sunxi_fb_ioctl,
};
CFATTACH_DECL_NEW(sunxi_fb, sizeof(struct sunxi_fb_softc),
sunxi_fb_match, sunxi_fb_attach, NULL, NULL);
static int
sunxi_fb_match(device_t parent, cfdata_t cf, void *aux)
{
return 1;
}
static void
sunxi_fb_attach(device_t parent, device_t self, void *aux)
{
struct sunxi_fb_softc * const sc = device_private(self);
struct sunxi_drm_softc * const drmsc = device_private(parent);
struct sunxi_drmfb_attach_args * const sfa = aux;
int error;
sc->sc_dev = self;
sc->sc_drm = drmsc;
sc->sc_sfa = *sfa;
sc->sc_fb = to_sunxi_drm_framebuffer(sfa->sfa_fb_helper->fb);
aprint_naive("\n");
aprint_normal("\n");
#ifdef WSDISPLAY_MULTICONS
prop_dictionary_t dict = device_properties(self);
const bool is_console = true;
prop_dictionary_set_bool(dict, "is_console", is_console);
#endif
const struct drmfb_attach_args da = {
.da_dev = self,
.da_fb_helper = sfa->sfa_fb_helper,
.da_fb_sizes = &sfa->sfa_fb_sizes,
.da_fb_vaddr = sc->sc_fb->obj->vaddr,
.da_fb_linebytes = sfa->sfa_fb_linebytes,
.da_params = &sunxifb_drmfb_params,
};
error = drmfb_attach(&sc->sc_drmfb, &da);
if (error) {
aprint_error_dev(self, "failed to attach drmfb: %d\n", error);
return;
}
pmf_device_register1(self, NULL, NULL, sunxi_fb_shutdown);
}
static bool
sunxi_fb_shutdown(device_t self, int flags)
{
struct sunxi_fb_softc * const sc = device_private(self);
return drmfb_shutdown(&sc->sc_drmfb, flags);
}
static paddr_t
sunxi_fb_mmapfb(struct drmfb_softc *sc, off_t off, int prot)
{
struct sunxi_fb_softc * const tfb_sc = (struct sunxi_fb_softc *)sc;
struct drm_gem_cma_object *obj = tfb_sc->sc_fb->obj;
KASSERT(off >= 0);
KASSERT(off < obj->dmasize);
return bus_dmamem_mmap(obj->dmat, obj->dmasegs, 1, off, prot,
BUS_DMA_PREFETCHABLE);
}
static int
sunxi_fb_ioctl(struct drmfb_softc *sc, u_long cmd, void *data, int flag,
lwp_t *l)
{
struct wsdisplayio_bus_id *busid;
struct wsdisplayio_fbinfo *fbi;
struct rasops_info *ri = &sc->sc_genfb.vd.active->scr_ri;
int error;
switch (cmd) {
case WSDISPLAYIO_GET_BUSID:
busid = data;
busid->bus_type = WSDISPLAYIO_BUS_SOC;
return 0;
case WSDISPLAYIO_GTYPE:
*(u_int *)data = WSDISPLAY_TYPE_GENFB;
return 0;
case WSDISPLAYIO_GET_FBINFO:
fbi = data;
error = wsdisplayio_get_fbinfo(ri, fbi);
fbi->fbi_flags |= WSFB_VRAM_IS_RAM;
return error;
default:
return EPASSTHROUGH;
}
}

View File

@ -0,0 +1,501 @@
/* $NetBSD: sunxi_hdmiphy.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2019 Jared McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sunxi_hdmiphy.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <dev/fdt/fdtvar.h>
#include <arm/sunxi/sunxi_hdmiphy.h>
#define DBG_CTRL 0x000
#define DBG_CTRL_POL __BITS(15,8)
#define DBG_CTRL_POL_NVSYNC 1
#define DBG_CTRL_POL_NHSYNC 2
#define READ_EN 0x010
#define READ_EN_MAGIC 0x54524545 /* "TREE" */
#define UNSCRAMBLE 0x014
#define UNSCRAMBLE_MAGIC 0x42494E47 /* "BING" */
#define ANA_CFG1 0x020
#define ANA_CFG1_ENRCAL __BIT(19)
#define ANA_CFG1_ENCALOG __BIT(18)
#define ANA_CFG1_TMDSCLK_EN __BIT(16)
#define ANA_CFG1_TXEN __BITS(15,12)
#define ANA_CFG1_BIASEN __BITS(11,8)
#define ANA_CFG1_ENP2S __BITS(7,4)
#define ANA_CFG1_CKEN __BIT(3)
#define ANA_CFG1_LDOEN __BIT(2)
#define ANA_CFG1_ENVBS __BIT(1)
#define ANA_CFG1_ENBI __BIT(0)
#define ANA_CFG2 0x024
#define ANA_CFG2_REG_RESDI __BITS(5,0)
#define ANA_CFG3 0x028
#define ANA_CFG3_REG_SDAEN __BIT(2)
#define ANA_CFG3_REG_SCLEN __BIT(0)
#define PLL_CFG1 0x02c
#define PLL_CFG1_REG_OD1 __BIT(31)
#define PLL_CFG1_REG_OD0 __BIT(30)
#define PLL_CFG1_CKIN_SEL __BIT(26)
#define PLL_CFG1_PLLEN __BIT(25)
#define PLL_CFG1_B_IN __BITS(5,0)
#define PLL_CFG2 0x030
#define PLL_CFG2_PREDIV __BITS(3,0)
#define PLL_CFG3 0x034
#define ANA_STS 0x038
#define ANA_STS_HPDO __BIT(19)
#define ANA_STS_B_OUT __BITS(16,11)
#define ANA_STS_RCALEND2D __BIT(7)
#define ANA_STS_RESDO2D __BITS(5,0)
#define CEC 0x03c
#define CEC_CONTROL_SEL __BIT(7)
#define CEC_INPUT_DATA __BIT(1)
#define CEC_OUTPUT_DATA __BIT(0)
#define CONTROLLER_VER 0xff8
#define PHY_VER 0xffc
struct sunxi_hdmiphy_softc;
static int sunxi_hdmiphy_match(device_t, cfdata_t, void *);
static void sunxi_hdmiphy_attach(device_t, device_t, void *);
static void sun50i_a64_hdmiphy_init(struct sunxi_hdmiphy_softc *);
static int sun50i_a64_hdmiphy_config(struct sunxi_hdmiphy_softc *, u_int);
struct sunxi_hdmiphy_data {
void (*init)(struct sunxi_hdmiphy_softc *);
int (*config)(struct sunxi_hdmiphy_softc *, u_int);
};
static const struct sunxi_hdmiphy_data sun50i_a64_hdmiphy_data = {
.init = sun50i_a64_hdmiphy_init,
.config = sun50i_a64_hdmiphy_config,
};
static const struct of_compat_data compat_data[] = {
{ "allwinner,sun50i-a64-hdmi-phy", (uintptr_t)&sun50i_a64_hdmiphy_data },
{ NULL }
};
struct sunxi_hdmiphy_softc {
device_t sc_dev;
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
const struct sunxi_hdmiphy_data *sc_data;
struct clk *sc_clk_pll0;
u_int sc_rcalib;
};
#define PHY_READ(sc, reg) \
bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
#define PHY_WRITE(sc, reg, val) \
bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
#define PHY_SET_CLEAR(sc, reg, set, clr) \
do { \
uint32_t _tval = PHY_READ((sc), (reg)); \
_tval &= ~(clr); \
_tval |= (set); \
PHY_WRITE((sc), (reg), _tval); \
} while (0)
#define PHY_SET(sc, reg, set) \
PHY_SET_CLEAR(sc, reg, set, 0)
#define PHY_CLEAR(sc, reg, clr) \
PHY_SET_CLEAR(sc, reg, 0, clr)
CFATTACH_DECL_NEW(sunxi_hdmiphy, sizeof(struct sunxi_hdmiphy_softc),
sunxi_hdmiphy_match, sunxi_hdmiphy_attach, NULL, NULL);
static void *
sunxi_hdmiphy_acquire(device_t dev, const void *data, size_t len)
{
struct sunxi_hdmiphy_softc * const sc = device_private(dev);
if (len != 0)
return NULL;
return sc;
}
static void
sunxi_hdmiphy_release(device_t dev, void *priv)
{
}
static int
sunxi_hdmiphy_enable(device_t dev, void *priv, bool enable)
{
struct sunxi_hdmiphy_softc * const sc = priv;
if (enable) {
sc->sc_data->init(sc);
} else {
sc->sc_data->config(sc, 0);
}
return 0;
}
static const struct fdtbus_phy_controller_func sunxi_hdmiphy_funcs = {
.acquire = sunxi_hdmiphy_acquire,
.release = sunxi_hdmiphy_release,
.enable = sunxi_hdmiphy_enable,
};
#ifdef SUNXI_HDMIPHY_DEBUG
static void
sunxi_hdmiphy_dump(struct sunxi_hdmiphy_softc *sc)
{
device_printf(sc->sc_dev, "ANA_CFG1: %#x\tANA_CFG2: %#x\tANA_CFG3: %#x\n",
PHY_READ(sc, ANA_CFG1), PHY_READ(sc, ANA_CFG2), PHY_READ(sc, ANA_CFG3));
device_printf(sc->sc_dev, "PLL_CFG1: %#x\tPLL_CFG2: %#x\tPLL_CFG3: %#x\n",
PHY_READ(sc, PLL_CFG1), PHY_READ(sc, PLL_CFG2), PHY_READ(sc, PLL_CFG3));
device_printf(sc->sc_dev, "DBG_CTRL: %#x\tANA_STS: %#x\n",
PHY_READ(sc, DBG_CTRL), PHY_READ(sc, ANA_STS));
}
#endif
static void
sun50i_a64_hdmiphy_init(struct sunxi_hdmiphy_softc *sc)
{
uint32_t val;
int retry;
PHY_WRITE(sc, ANA_CFG1, 0);
PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENBI);
delay(5);
/* Enable TMDS clock */
PHY_SET(sc, ANA_CFG1, ANA_CFG1_TMDSCLK_EN);
/* Enable common voltage reference bias module */
PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENVBS);
delay(20);
/* Enable internal LDO */
PHY_SET(sc, ANA_CFG1, ANA_CFG1_LDOEN);
delay(5);
/* Enable common clock module */
PHY_SET(sc, ANA_CFG1, ANA_CFG1_CKEN);
delay(100);
/* Enable resistance calibration analog and digital modules */
PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENRCAL);
delay(200);
PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENCALOG);
/* P2S module enable for TMDS data lane */
PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0x7, ANA_CFG1_ENP2S), ANA_CFG1_ENP2S);
/* Wait for resistance calibration to finish */
for (retry = 2000; retry > 0; retry--) {
if ((PHY_READ(sc, ANA_STS) & ANA_STS_RCALEND2D) != 0)
break;
delay(1);
}
if (retry == 0)
aprint_error_dev(sc->sc_dev, "HDMI PHY resistance calibration timed out\n");
/* Enable current and voltage module */
PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0xf, ANA_CFG1_BIASEN), ANA_CFG1_BIASEN);
/* P2S module enable for TMDS clock lane */
PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0xf, ANA_CFG1_ENP2S), ANA_CFG1_ENP2S);
/* Enable DDC */
PHY_SET(sc, ANA_CFG3, ANA_CFG3_REG_SDAEN | ANA_CFG3_REG_SCLEN);
/* Set parent clock to videopll0 */
PHY_CLEAR(sc, PLL_CFG1, PLL_CFG1_CKIN_SEL);
/* Clear software control of CEC pins */
PHY_CLEAR(sc, CEC, CEC_CONTROL_SEL);
/* Read calibration value for source termination resistors */
val = PHY_READ(sc, ANA_STS);
sc->sc_rcalib = __SHIFTOUT(val, ANA_STS_RESDO2D);
}
/*
* The following table is based on data from the "HDMI TX PHY S40 Specification".
*/
static const struct sun50i_a64_hdmiphy_init {
/* PLL Recommended Configuration */
uint32_t pll_cfg1;
uint32_t pll_cfg2;
uint32_t pll_cfg3;
/* TMDS Characteristics Recommended Configuration */
uint32_t ana_cfg1;
uint32_t ana_cfg2;
uint32_t ana_cfg3;
bool ana_cfg2_rcal_200;
u_int b_offset;
} sun50i_a64_hdmiphy_inittab[] = {
/* 27 MHz */
[0] = {
.pll_cfg1 = 0x3ddc5040, .pll_cfg2 = 0x8008430a, .pll_cfg3 = 0x1,
.ana_cfg1 = 0x11ffff7f, .ana_cfg2 = 0x80623000, .ana_cfg3 = 0x0f80c285,
.ana_cfg2_rcal_200 = true,
},
/* 74.25 MHz */
[1] = {
.pll_cfg1 = 0x3ddc5040, .pll_cfg2 = 0x80084343, .pll_cfg3 = 0x1,
.ana_cfg1 = 0x11ffff7f, .ana_cfg2 = 0x80623000, .ana_cfg3 = 0x0f814385,
.ana_cfg2_rcal_200 = true,
},
/* 148.5 MHz */
[2] = {
.pll_cfg1 = 0x3ddc5040, .pll_cfg2 = 0x80084381, .pll_cfg3 = 0x1,
.ana_cfg1 = 0x01ffff7f, .ana_cfg2 = 0x8063a800, .ana_cfg3 = 0x0f81c485,
},
/* 297 MHz */
[3] = {
.pll_cfg1 = 0x35dc5fc0, .pll_cfg2 = 0x800863c0, .pll_cfg3 = 0x1,
.ana_cfg1 = 0x01ffff7f, .ana_cfg2 = 0x8063b000, .ana_cfg3 = 0x0f8246b5,
.b_offset = 2,
},
};
static int
sun50i_a64_hdmiphy_config(struct sunxi_hdmiphy_softc *sc, u_int rate)
{
const struct sun50i_a64_hdmiphy_init *inittab;
u_int init_index, b_out, prediv;
uint32_t val, rcalib;
if (rate == 0) {
/* Disable the PHY */
PHY_WRITE(sc, ANA_CFG1, ANA_CFG1_LDOEN | ANA_CFG1_ENVBS | ANA_CFG1_ENBI);
PHY_WRITE(sc, PLL_CFG1, 0);
return 0;
}
init_index = 0;
if (rate > 27000000)
init_index++;
if (rate > 74250000)
init_index++;
if (rate > 148500000)
init_index++;
inittab = &sun50i_a64_hdmiphy_inittab[init_index];
val = PHY_READ(sc, PLL_CFG2);
prediv = val & PLL_CFG2_PREDIV;
/* Config PLL */
PHY_WRITE(sc, PLL_CFG1, inittab->pll_cfg1 & ~PLL_CFG1_CKIN_SEL);
PHY_WRITE(sc, PLL_CFG2, (inittab->pll_cfg2 & ~PLL_CFG2_PREDIV) | prediv);
delay(15000);
PHY_WRITE(sc, PLL_CFG3, inittab->pll_cfg3);
/* Enable PLL */
PHY_SET(sc, PLL_CFG1, PLL_CFG1_PLLEN);
delay(100000);
/* Config PLL */
val = PHY_READ(sc, ANA_STS);
b_out = __SHIFTOUT(val, ANA_STS_B_OUT);
b_out = MIN(b_out + inittab->b_offset, __SHIFTOUT_MASK(ANA_STS_B_OUT));
PHY_SET(sc, PLL_CFG1, PLL_CFG1_REG_OD1 | PLL_CFG1_REG_OD0);
PHY_SET(sc, PLL_CFG1, __SHIFTIN(b_out, PLL_CFG1_B_IN));
delay(100000);
/* Config TMDS characteristics */
if (inittab->ana_cfg2_rcal_200)
rcalib = sc->sc_rcalib >> 2;
else
rcalib = 0;
PHY_WRITE(sc, ANA_CFG1, inittab->ana_cfg1);
PHY_WRITE(sc, ANA_CFG2, inittab->ana_cfg2 | rcalib);
PHY_WRITE(sc, ANA_CFG3, inittab->ana_cfg3);
#ifdef SUNXI_HDMIPHY_DEBUG
sunxi_hdmiphy_dump(sc);
#endif
return 0;
}
static int
sunxi_hdmiphy_set_rate(struct sunxi_hdmiphy_softc *sc, u_int new_rate)
{
u_int prediv, best_prediv, best_rate;
if (sc->sc_clk_pll0 == NULL)
return 0;
const u_int parent_rate = clk_get_rate(sc->sc_clk_pll0);
best_rate = 0;
for (prediv = 0; prediv <= __SHIFTOUT_MASK(PLL_CFG2_PREDIV); prediv++) {
const u_int tmp_rate = parent_rate / (prediv + 1);
const int diff = new_rate - tmp_rate;
if (diff >= 0 && tmp_rate > best_rate) {
best_rate = tmp_rate;
best_prediv = prediv;
}
}
if (best_rate == 0)
return ERANGE;
PHY_SET_CLEAR(sc, PLL_CFG2, __SHIFTIN(best_prediv, PLL_CFG2_PREDIV), PLL_CFG2_PREDIV);
return 0;
}
static int
sunxi_hdmiphy_match(device_t parent, cfdata_t cf, void *aux)
{
struct fdt_attach_args * const faa = aux;
return of_match_compat_data(faa->faa_phandle, compat_data);
}
static void
sunxi_hdmiphy_attach(device_t parent, device_t self, void *aux)
{
struct sunxi_hdmiphy_softc * const sc = device_private(self);
struct fdt_attach_args * const faa = aux;
const int phandle = faa->faa_phandle;
struct clk *clk_bus, *clk_mod, *clk_pll0;
struct fdtbus_reset *rst;
bus_addr_t addr;
bus_size_t size;
if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
aprint_error(": couldn't get registers\n");
return;
}
rst = fdtbus_reset_get(phandle, "phy");
if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
aprint_error(": couldn't de-assert reset\n");
return;
}
clk_bus = fdtbus_clock_get(phandle, "bus");
if (clk_bus == NULL || clk_enable(clk_bus) != 0) {
aprint_error(": couldn't enable bus clock\n");
return;
}
clk_mod = fdtbus_clock_get(phandle, "mod");
if (clk_mod == NULL || clk_enable(clk_mod) != 0) {
aprint_error(": couldn't enable mod clock\n");
return;
}
clk_pll0 = fdtbus_clock_get(phandle, "pll-0");
if (clk_pll0 == NULL || clk_enable(clk_pll0) != 0) {
aprint_error(": couldn't enable pll-0 clock\n");
return;
}
sc->sc_dev = self;
sc->sc_bst = faa->faa_bst;
sc->sc_data = (void *)of_search_compatible(phandle, compat_data)->data;
if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
aprint_error(": couldn't map registers\n");
return;
}
sc->sc_clk_pll0 = clk_pll0;
aprint_naive("\n");
aprint_normal(": HDMI PHY\n");
fdtbus_register_phy_controller(self, phandle, &sunxi_hdmiphy_funcs);
PHY_WRITE(sc, READ_EN, READ_EN_MAGIC);
PHY_WRITE(sc, UNSCRAMBLE, UNSCRAMBLE_MAGIC);
#ifdef SUNXI_HDMIPHY_DEBUG
sunxi_hdmiphy_dump(sc);
#endif
}
int
sunxi_hdmiphy_config(struct fdtbus_phy *phy, struct drm_display_mode *mode)
{
device_t dev = fdtbus_phy_device(phy);
struct sunxi_hdmiphy_softc * const sc = device_private(dev);
u_int pol;
int error;
pol = 0;
if ((mode->flags & DRM_MODE_FLAG_NHSYNC) != 0)
pol |= __SHIFTIN(DBG_CTRL_POL_NHSYNC, DBG_CTRL_POL);
if ((mode->flags & DRM_MODE_FLAG_NVSYNC) != 0)
pol |= __SHIFTIN(DBG_CTRL_POL_NVSYNC, DBG_CTRL_POL);
PHY_SET_CLEAR(sc, DBG_CTRL, pol, DBG_CTRL_POL);
error = sunxi_hdmiphy_set_rate(sc, mode->crtc_clock * 1000);
if (error != 0) {
aprint_error_dev(dev, "failed to set HDMI PHY clock: %d\n", error);
return error;
}
return sc->sc_data->config(sc, mode->crtc_clock * 1000);
}
bool
sunxi_hdmiphy_detect(struct fdtbus_phy *phy, bool force)
{
device_t dev = fdtbus_phy_device(phy);
struct sunxi_hdmiphy_softc * const sc = device_private(dev);
uint32_t val;
val = PHY_READ(sc, ANA_STS);
return ISSET(val, ANA_STS_HPDO);
}

View File

@ -0,0 +1,38 @@
/* $NetBSD: sunxi_hdmiphy.h,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2019 Jared McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _ARM_SUNXI_HDMIPHY_H
#define _ARM_SUNXI_HDMIPHY_H
#include <drm/drmP.h>
#include <drm/drm_modes.h>
int sunxi_hdmiphy_config(struct fdtbus_phy *, struct drm_display_mode *);
bool sunxi_hdmiphy_detect(struct fdtbus_phy *, bool);
#endif /* !_ARM_SUNXI_HDMIPHY_H */

View File

@ -0,0 +1,317 @@
/* $NetBSD: sunxi_lcdc.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sunxi_lcdc.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <dev/fdt/fdtvar.h>
#include <dev/fdt/fdt_port.h>
#define TCON_GCTL_REG 0x000
#define TCON_GCTL_TCON_EN __BIT(31)
#define TCON_GCTL_GAMMA_EN __BIT(30)
#define TCON_GCTL_IO_MAP_SEL __BIT(0)
#define TCON_GINT0_REG 0x004
#define TCON_GINT1_REG 0x008
#define TCON_GINT1_TCON1_LINE_INT_NUM __BITS(11,0)
#define TCON1_CTL_REG 0x090
#define TCON1_CTL_TCON1_EN __BIT(31)
#define TCON1_CTL_START_DELAY __BITS(8,4)
#define TCON1_CTL_TCON1_SRC_SEL __BIT(1)
#define TCON1_BASIC0_REG 0x094
#define TCON1_BASIC1_REG 0x098
#define TCON1_BASIC2_REG 0x09c
#define TCON1_BASIC3_REG 0x0a0
#define TCON1_BASIC4_REG 0x0a4
#define TCON1_BASIC5_REG 0x0a8
#define TCON1_IO_POL_REG 0x0f0
#define TCON1_IO_POL_IO3_INV __BIT(27)
#define TCON1_IO_POL_IO2_INV __BIT(26)
#define TCON1_IO_POL_IO1_INV __BIT(25)
#define TCON1_IO_POL_IO0_INV __BIT(24)
#define TCON1_IO_POL_DATA_INV __BITS(23,0)
#define TCON1_IO_TRI_REG 0x0f4
enum {
MIXER_PORT_INPUT = 0,
MIXER_PORT_OUTPUT = 1,
};
static const char * const compatible[] = {
"allwinner,sun50i-a64-tcon-lcd",
"allwinner,sun50i-a64-tcon-tv",
NULL
};
struct sunxi_lcdc_softc;
struct sunxi_lcdc_encoder {
struct drm_encoder base;
struct sunxi_lcdc_softc *sc;
struct drm_display_mode curmode;
};
struct sunxi_lcdc_softc {
device_t sc_dev;
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
int sc_phandle;
struct clk *sc_clk_ch[2];
struct sunxi_lcdc_encoder sc_encoder;
struct drm_connector sc_connector;
struct fdt_device_ports sc_ports;
};
#define to_sunxi_lcdc_encoder(x) container_of(x, struct sunxi_lcdc_encoder, base)
#define TCON_READ(sc, reg) \
bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
#define TCON_WRITE(sc, reg, val) \
bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
static void
sunxi_lcdc_destroy(struct drm_encoder *encoder)
{
}
static const struct drm_encoder_funcs sunxi_lcdc_funcs = {
.destroy = sunxi_lcdc_destroy,
};
static void
sunxi_lcdc_tcon1_dpms(struct drm_encoder *encoder, int mode)
{
}
static bool
sunxi_lcdc_tcon1_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
{
return true;
}
static void
sunxi_lcdc_tcon1_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
{
struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
lcdc_encoder->curmode = *adjusted_mode;
}
static void
sunxi_lcdc_tcon1_prepare(struct drm_encoder *encoder)
{
struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
struct sunxi_lcdc_softc * const sc = lcdc_encoder->sc;
uint32_t val;
val = TCON_READ(sc, TCON_GCTL_REG);
val |= TCON_GCTL_TCON_EN;
TCON_WRITE(sc, TCON_GCTL_REG, val);
TCON_WRITE(sc, TCON1_IO_POL_REG, 0);
TCON_WRITE(sc, TCON1_IO_TRI_REG, 0xffffffff);
}
static void
sunxi_lcdc_tcon1_commit(struct drm_encoder *encoder)
{
struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
struct sunxi_lcdc_softc * const sc = lcdc_encoder->sc;
struct drm_display_mode *mode = &lcdc_encoder->curmode;
uint32_t val;
int error;
const u_int interlace_p = (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0;
const u_int hspw = mode->hsync_end - mode->hsync_start;
const u_int hbp = mode->htotal - mode->hsync_start;
const u_int vspw = mode->vsync_end - mode->vsync_start;
const u_int vbp = mode->vtotal - mode->vsync_start;
const u_int vblank_len =
((mode->vtotal << interlace_p) >> 1) - mode->vdisplay - 2;
const u_int start_delay =
vblank_len >= 32 ? 30 : vblank_len - 2;
val = TCON1_CTL_TCON1_EN |
__SHIFTIN(start_delay, TCON1_CTL_START_DELAY);
TCON_WRITE(sc, TCON1_CTL_REG, val);
TCON_WRITE(sc, TCON1_BASIC0_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
TCON_WRITE(sc, TCON1_BASIC1_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
TCON_WRITE(sc, TCON1_BASIC2_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
TCON_WRITE(sc, TCON1_BASIC3_REG, ((mode->htotal - 1) << 16) | (hbp - 1));
TCON_WRITE(sc, TCON1_BASIC4_REG, ((mode->vtotal * 2) << 16) | (vbp - 1));
TCON_WRITE(sc, TCON1_BASIC5_REG, ((hspw - 1) << 16) | (vspw - 1));
TCON_WRITE(sc, TCON_GINT1_REG,
__SHIFTIN(start_delay + 2, TCON_GINT1_TCON1_LINE_INT_NUM));
if (sc->sc_clk_ch[1] != NULL) {
error = clk_set_rate(sc->sc_clk_ch[1], mode->crtc_clock * 1000);
if (error != 0) {
device_printf(sc->sc_dev, "failed to set CH1 PLL rate to %u Hz: %d\n",
mode->crtc_clock * 1000, error);
return;
}
error = clk_enable(sc->sc_clk_ch[1]);
if (error != 0) {
device_printf(sc->sc_dev, "failed to enable CH1 PLL: %d\n", error);
return;
}
} else {
device_printf(sc->sc_dev, "no CH1 PLL configured\n");
}
}
static const struct drm_encoder_helper_funcs sunxi_lcdc_tcon1_helper_funcs = {
.dpms = sunxi_lcdc_tcon1_dpms,
.mode_fixup = sunxi_lcdc_tcon1_mode_fixup,
.prepare = sunxi_lcdc_tcon1_prepare,
.commit = sunxi_lcdc_tcon1_commit,
.mode_set = sunxi_lcdc_tcon1_mode_set,
};
static int
sunxi_lcdc_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
{
struct sunxi_lcdc_softc * const sc = device_private(dev);
struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep);
struct fdt_endpoint *out_ep;
struct drm_crtc *crtc;
int error;
if (!activate)
return EINVAL;
if (fdt_endpoint_port_index(ep) != MIXER_PORT_INPUT)
return EINVAL;
if (fdt_endpoint_type(in_ep) != EP_DRM_CRTC)
return EINVAL;
crtc = fdt_endpoint_get_data(in_ep);
sc->sc_encoder.sc = sc;
out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, MIXER_PORT_OUTPUT, 1);
if (out_ep != NULL) {
drm_encoder_init(crtc->dev, &sc->sc_encoder.base, &sunxi_lcdc_funcs,
DRM_MODE_ENCODER_TMDS);
drm_encoder_helper_add(&sc->sc_encoder.base, &sunxi_lcdc_tcon1_helper_funcs);
error = fdt_endpoint_activate(out_ep, activate);
if (error != 0)
return error;
sc->sc_encoder.base.possible_crtcs = 1 << drm_crtc_index(crtc);
}
return 0;
}
static void *
sunxi_lcdc_ep_get_data(device_t dev, struct fdt_endpoint *ep)
{
struct sunxi_lcdc_softc * const sc = device_private(dev);
return &sc->sc_encoder;
}
static int
sunxi_lcdc_match(device_t parent, cfdata_t cf, void *aux)
{
struct fdt_attach_args * const faa = aux;
return of_match_compatible(faa->faa_phandle, compatible);
}
static void
sunxi_lcdc_attach(device_t parent, device_t self, void *aux)
{
struct sunxi_lcdc_softc * const sc = device_private(self);
struct fdt_attach_args * const faa = aux;
const int phandle = faa->faa_phandle;
struct fdtbus_reset *rst;
struct clk *clk;
bus_addr_t addr;
bus_size_t size;
if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
aprint_error(": couldn't get registers\n");
return;
}
rst = fdtbus_reset_get(phandle, "lcd");
if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
aprint_error(": couldn't de-assert reset\n");
return;
}
clk = fdtbus_clock_get(phandle, "ahb");
if (clk == NULL || clk_enable(clk) != 0) {
aprint_error(": couldn't enable bus clock\n");
return;
}
sc->sc_dev = self;
sc->sc_bst = faa->faa_bst;
if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
aprint_error(": couldn't map registers\n");
return;
}
sc->sc_phandle = faa->faa_phandle;
sc->sc_clk_ch[0] = fdtbus_clock_get(phandle, "tcon-ch0");
sc->sc_clk_ch[1] = fdtbus_clock_get(phandle, "tcon-ch1");
aprint_naive("\n");
aprint_normal(": Timing Controller\n");
sc->sc_ports.dp_ep_activate = sunxi_lcdc_ep_activate;
sc->sc_ports.dp_ep_get_data = sunxi_lcdc_ep_get_data;
fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_ENCODER);
}
CFATTACH_DECL_NEW(sunxi_lcdc, sizeof(struct sunxi_lcdc_softc),
sunxi_lcdc_match, sunxi_lcdc_attach, NULL, NULL);

View File

@ -0,0 +1,387 @@
/* $NetBSD: sunxi_mixer.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sunxi_mixer.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_plane_helper.h>
#include <dev/fdt/fdtvar.h>
#include <dev/fdt/fdt_port.h>
#include <arm/sunxi/sunxi_drm.h>
#define SUNXI_MIXER_FREQ 432000000
#define GLB_BASE 0x00000
#define BLD_BASE 0x01000
#define OVL_BASE(n) (0x02000 + (n) * 0x1000)
#define OVL_UI_BASE OVL_BASE(1)
/* GLB registers */
#define GLB_CTL 0x000
#define GLB_CTL_EN __BIT(0)
#define GLB_STS 0x004
#define GLB_DBUFFER 0x008
#define GLB_DBUFFER_DOUBLE_BUFFER_RDY __BIT(0)
#define GLB_SIZE 0x00c
/* BLD registers */
#define BLD_FILL_COLOR_CTL 0x000
#define BLD_FILL_COLOR_CTL_P0_EN __BIT(8)
#define BLD_CH_ISIZE(n) (0x008 + (n) * 0x10)
#define BLD_CH_OFFSET(n) (0x00c + (n) * 0x10)
#define BLD_CH_RTCTL 0x080
#define BLD_CH_RTCTL_P0 __BITS(3,0)
#define BLD_SIZE 0x08c
#define BLD_CTL(n) (0x090 + (n) * 0x04)
/* OVL_UI registers */
#define OVL_UI_ATTR_CTL(n) (0x000 + (n) * 0x20)
#define OVL_UI_ATTR_CTL_LAY_FBFMT __BITS(12,8)
#define OVL_UI_ATTR_CTL_LAY_FBFMT_XRGB_8888 0x04
#define OVL_UI_ATTR_CTL_LAY_EN __BIT(0)
#define OVL_UI_MBSIZE(n) (0x004 + (n) * 0x20)
#define OVL_UI_COOR(n) (0x008 + (n) * 0x20)
#define OVL_UI_PITCH(n) (0x00c + (n) * 0x20)
#define OVL_UI_TOP_LADD(n) (0x010 + (n) * 0x20)
#define OVL_UI_TOP_HADD 0x080
#define OVL_UI_TOP_HADD_LAYER0 __BITS(7,0)
#define OVL_UI_SIZE 0x088
enum {
MIXER_PORT_OUTPUT = 1,
};
static const char * const compatible[] = {
"allwinner,sun50i-a64-de2-mixer-0",
"allwinner,sun50i-a64-de2-mixer-1",
NULL
};
struct sunxi_mixer_softc;
struct sunxi_mixer_crtc {
struct drm_crtc base;
struct sunxi_mixer_softc *sc;
};
struct sunxi_mixer_softc {
device_t sc_dev;
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
int sc_phandle;
struct sunxi_mixer_crtc sc_crtc;
struct fdt_device_ports sc_ports;
};
#define GLB_READ(sc, reg) \
bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, GLB_BASE + (reg))
#define GLB_WRITE(sc, reg, val) \
bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, GLB_BASE + (reg), (val))
#define BLD_READ(sc, reg) \
bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, BLD_BASE + (reg))
#define BLD_WRITE(sc, reg, val) \
bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, BLD_BASE + (reg), (val))
#define OVL_UI_READ(sc, reg) \
bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, OVL_UI_BASE + (reg))
#define OVL_UI_WRITE(sc, reg, val) \
bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, OVL_UI_BASE + (reg), (val))
#define to_sunxi_mixer_crtc(x) container_of(x, struct sunxi_mixer_crtc, base)
static void
sunxi_mixer_destroy(struct drm_crtc *crtc)
{
drm_crtc_cleanup(crtc);
}
static const struct drm_crtc_funcs sunxi_mixer_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.destroy = sunxi_mixer_destroy,
};
static void
sunxi_mixer_dpms(struct drm_crtc *crtc, int mode)
{
}
static bool
sunxi_mixer_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
{
return true;
}
static int
sunxi_mixer_mode_do_set_base(struct drm_crtc *crtc, struct drm_framebuffer *fb,
int x, int y, int atomic)
{
struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
struct sunxi_drm_framebuffer *sfb = atomic?
to_sunxi_drm_framebuffer(fb) :
to_sunxi_drm_framebuffer(crtc->primary->fb);
uint64_t paddr = (uint64_t)sfb->obj->dmamap->dm_segs[0].ds_addr;
uint32_t haddr = (paddr >> 32) & OVL_UI_TOP_HADD_LAYER0;
uint32_t laddr = paddr & 0xffffffff;
/* Framebuffer start address */
OVL_UI_WRITE(sc, OVL_UI_TOP_HADD, haddr);
OVL_UI_WRITE(sc, OVL_UI_TOP_LADD(0), laddr);
return 0;
}
static int
sunxi_mixer_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode, int x, int y,
struct drm_framebuffer *old_fb)
{
struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
uint32_t val;
const uint32_t size = ((adjusted_mode->vdisplay - 1) << 16) |
(adjusted_mode->hdisplay - 1);
const uint32_t offset = (y << 16) | x;
/* Set global size */
GLB_WRITE(sc, GLB_SIZE, size);
/* Enable pipe 0 */
BLD_WRITE(sc, BLD_FILL_COLOR_CTL, BLD_FILL_COLOR_CTL_P0_EN);
/* Set blender 0 input size */
BLD_WRITE(sc, BLD_CH_ISIZE(0), size);
/* Set blender 0 offset */
BLD_WRITE(sc, BLD_CH_OFFSET(0), offset);
/* Route channel 1 to pipe 0 */
BLD_WRITE(sc, BLD_CH_RTCTL, __SHIFTIN(1, BLD_CH_RTCTL_P0));
/* Set blender output size */
BLD_WRITE(sc, BLD_SIZE, size);
/* Enable UI overlay in XRGB8888 mode */
val = OVL_UI_ATTR_CTL_LAY_EN |
__SHIFTIN(OVL_UI_ATTR_CTL_LAY_FBFMT_XRGB_8888, OVL_UI_ATTR_CTL_LAY_FBFMT);
OVL_UI_WRITE(sc, OVL_UI_ATTR_CTL(0), val);
/* Set UI overlay layer size */
OVL_UI_WRITE(sc, OVL_UI_MBSIZE(0), size);
/* Set UI overlay offset */
OVL_UI_WRITE(sc, OVL_UI_COOR(0), offset);
/* Set UI overlay line size */
OVL_UI_WRITE(sc, OVL_UI_PITCH(0), adjusted_mode->hdisplay * 4);
/* Set UI overlay window size */
OVL_UI_WRITE(sc, OVL_UI_SIZE, size);
sunxi_mixer_mode_do_set_base(crtc, old_fb, x, y, 0);
return 0;
}
static int
sunxi_mixer_mode_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_framebuffer *old_fb)
{
struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
sunxi_mixer_mode_do_set_base(crtc, old_fb, x, y, 0);
/* Commit settings */
GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY);
return 0;
}
static int
sunxi_mixer_mode_set_base_atomic(struct drm_crtc *crtc, struct drm_framebuffer *fb,
int x, int y, enum mode_set_atomic state)
{
struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
sunxi_mixer_mode_do_set_base(crtc, fb, x, y, 1);
/* Commit settings */
GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY);
return 0;
}
static void
sunxi_mixer_disable(struct drm_crtc *crtc)
{
}
static void
sunxi_mixer_prepare(struct drm_crtc *crtc)
{
struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
/* RT enable */
GLB_WRITE(sc, GLB_CTL, GLB_CTL_EN);
}
static void
sunxi_mixer_commit(struct drm_crtc *crtc)
{
struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
/* Commit settings */
GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY);
}
static const struct drm_crtc_helper_funcs sunxi_mixer_crtc_helper_funcs = {
.dpms = sunxi_mixer_dpms,
.mode_fixup = sunxi_mixer_mode_fixup,
.mode_set = sunxi_mixer_mode_set,
.mode_set_base = sunxi_mixer_mode_set_base,
.mode_set_base_atomic = sunxi_mixer_mode_set_base_atomic,
.disable = sunxi_mixer_disable,
.prepare = sunxi_mixer_prepare,
.commit = sunxi_mixer_commit,
};
static int
sunxi_mixer_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
{
struct sunxi_mixer_softc * const sc = device_private(dev);
struct drm_device *ddev;
if (!activate)
return EINVAL;
ddev = sunxi_drm_endpoint_device(ep);
if (ddev == NULL) {
DRM_ERROR("couldn't find DRM device\n");
return ENXIO;
}
sc->sc_crtc.sc = sc;
drm_crtc_init(ddev, &sc->sc_crtc.base, &sunxi_mixer_crtc_funcs);
drm_crtc_helper_add(&sc->sc_crtc.base, &sunxi_mixer_crtc_helper_funcs);
return fdt_endpoint_activate(ep, activate);
}
static void *
sunxi_mixer_ep_get_data(device_t dev, struct fdt_endpoint *ep)
{
struct sunxi_mixer_softc * const sc = device_private(dev);
return &sc->sc_crtc;
}
static int
sunxi_mixer_match(device_t parent, cfdata_t cf, void *aux)
{
struct fdt_attach_args * const faa = aux;
return of_match_compatible(faa->faa_phandle, compatible);
}
static void
sunxi_mixer_attach(device_t parent, device_t self, void *aux)
{
struct sunxi_mixer_softc * const sc = device_private(self);
struct fdt_attach_args * const faa = aux;
struct fdt_endpoint *out_ep;
const int phandle = faa->faa_phandle;
struct clk *clk_bus, *clk_mod;
struct fdtbus_reset *rst;
bus_addr_t addr;
bus_size_t size;
if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
aprint_error(": couldn't get registers\n");
return;
}
rst = fdtbus_reset_get_index(phandle, 0);
if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
aprint_error(": couldn't de-assert reset\n");
return;
}
clk_bus = fdtbus_clock_get(phandle, "bus");
if (clk_bus == NULL || clk_enable(clk_bus) != 0) {
aprint_error(": couldn't enable bus clock\n");
return;
}
clk_mod = fdtbus_clock_get(phandle, "mod");
if (clk_mod == NULL ||
clk_set_rate(clk_mod, SUNXI_MIXER_FREQ) != 0 ||
clk_enable(clk_mod) != 0) {
aprint_error(": couldn't enable mod clock\n");
return;
}
sc->sc_dev = self;
sc->sc_bst = faa->faa_bst;
if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
aprint_error(": couldn't map registers\n");
return;
}
sc->sc_phandle = faa->faa_phandle;
aprint_naive("\n");
aprint_normal(": Display Engine Mixer\n");
sc->sc_ports.dp_ep_activate = sunxi_mixer_ep_activate;
sc->sc_ports.dp_ep_get_data = sunxi_mixer_ep_get_data;
fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_CRTC);
out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, MIXER_PORT_OUTPUT, 0);
if (out_ep != NULL)
sunxi_drm_register_endpoint(phandle, out_ep);
}
CFATTACH_DECL_NEW(sunxi_mixer, sizeof(struct sunxi_mixer_softc),
sunxi_mixer_match, sunxi_mixer_attach, NULL, NULL);

View File

@ -1,4 +1,4 @@
/* $NetBSD: fdt_i2c.c,v 1.5 2018/09/26 20:03:36 jakllsch Exp $ */
/* $NetBSD: fdt_i2c.c,v 1.6 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2015 Jared D. McNeill <jmcneill@invisible.ca>
@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fdt_i2c.c,v 1.5 2018/09/26 20:03:36 jakllsch Exp $");
__KERNEL_RCSID(0, "$NetBSD: fdt_i2c.c,v 1.6 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
@ -89,6 +89,18 @@ fdtbus_get_i2c_tag(int phandle)
return i2c->i2c_funcs->get_tag(i2c->i2c_dev);
}
i2c_tag_t
fdtbus_i2c_acquire(int phandle, const char *prop)
{
int i2c_phandle;
i2c_phandle = fdtbus_get_phandle(phandle, prop);
if (i2c_phandle == -1)
return NULL;
return fdtbus_get_i2c_tag(i2c_phandle);
}
device_t
fdtbus_attach_i2cbus(device_t dev, int phandle, i2c_tag_t tag, cfprint_t print)
{

View File

@ -1,4 +1,4 @@
/* $NetBSD: fdt_phy.c,v 1.2 2018/06/30 20:34:43 jmcneill Exp $ */
/* $NetBSD: fdt_phy.c,v 1.3 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2015-2017 Jared McNeill <jmcneill@invisible.ca>
@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fdt_phy.c,v 1.2 2018/06/30 20:34:43 jmcneill Exp $");
__KERNEL_RCSID(0, "$NetBSD: fdt_phy.c,v 1.3 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
@ -172,6 +172,12 @@ fdtbus_phy_put(struct fdtbus_phy *phy)
kmem_free(phy, sizeof(*phy));
}
device_t
fdtbus_phy_device(struct fdtbus_phy *phy)
{
return phy->phy_pc->pc_dev;
}
int
fdtbus_phy_enable(struct fdtbus_phy *phy, bool enable)
{

View File

@ -1,4 +1,4 @@
/* $NetBSD: fdt_port.c,v 1.1 2018/04/03 12:40:20 bouyer Exp $ */
/* $NetBSD: fdt_port.c,v 1.2 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@ -38,7 +38,7 @@
#include <sys/cdefs.h>
__KERNEL_RCSID(1, "$NetBSD: fdt_port.c,v 1.1 2018/04/03 12:40:20 bouyer Exp $");
__KERNEL_RCSID(1, "$NetBSD: fdt_port.c,v 1.2 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@ -118,6 +118,20 @@ fdt_endpoint_get_from_index(struct fdt_device_ports *device_ports,
return NULL;
}
struct fdt_endpoint *
fdt_endpoint_remote_from_index(struct fdt_device_ports *device_ports,
int port_index, int ep_index)
{
struct fdt_endpoint *ep;
ep = fdt_endpoint_get_from_index(device_ports, port_index,
ep_index);
if (ep == NULL)
return NULL;
return fdt_endpoint_remote(ep);
}
struct fdt_endpoint *
fdt_endpoint_remote(struct fdt_endpoint *ep)
{
@ -154,6 +168,12 @@ fdt_endpoint_is_enabled(struct fdt_endpoint *ep)
return ep->ep_enabled;
}
enum endpoint_type
fdt_endpoint_type(struct fdt_endpoint *ep)
{
return ep->ep_type;
}
int
fdt_endpoint_activate(struct fdt_endpoint *ep, bool activate)
{
@ -170,6 +190,8 @@ fdt_endpoint_activate(struct fdt_endpoint *ep, bool activate)
return EBUSY;
rdp = rep->ep_port->port_dp;
device_printf(rdp->dp_dev, "activating port %d endpoint %d\n",
fdt_endpoint_port_index(rep), fdt_endpoint_index(rep));
if (rdp->dp_ep_activate)
error = rdp->dp_ep_activate(rdp->dp_dev, rep, activate);
@ -178,6 +200,21 @@ fdt_endpoint_activate(struct fdt_endpoint *ep, bool activate)
return error;
}
int
fdt_endpoint_activate_direct(struct fdt_endpoint *ep, bool activate)
{
struct fdt_device_ports *dp;
int error = 0;
dp = ep->ep_port->port_dp;
device_printf(dp->dp_dev, "activating port %d endpoint %d (direct)\n",
fdt_endpoint_port_index(ep), fdt_endpoint_index(ep));
if (dp->dp_ep_activate)
error = dp->dp_ep_activate(dp->dp_dev, ep, activate);
return error;
}
int
fdt_endpoint_enable(struct fdt_endpoint *ep, bool enable)
{

View File

@ -1,4 +1,4 @@
/* $NetBSD: fdt_port.h,v 1.1 2018/04/03 12:40:20 bouyer Exp $ */
/* $NetBSD: fdt_port.h,v 1.2 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@ -44,6 +44,8 @@
#ifndef _DEV_FDT_FDT_PORT_H_
#define _DEV_FDT_FDT_PORT_H_
struct drm_device;
struct fdt_port;
struct fdt_endpoint;
@ -63,6 +65,11 @@ enum endpoint_type {
EP_OTHER = 0,
EP_CONNECTOR,
EP_PANEL,
EP_DRM_BRIDGE, /* struct drm_bridge */
EP_DRM_CONNECTOR, /* struct drm_connector */
EP_DRM_CRTC, /* struct drm_crtc */
EP_DRM_ENCODER, /* struct drm_encoder */
};
@ -79,15 +86,19 @@ struct fdt_endpoint *fdt_endpoint_get_from_phandle(int);
struct fdt_endpoint *fdt_endpoint_get_from_index(struct fdt_device_ports *,
int, int);
struct fdt_endpoint *fdt_endpoint_remote(struct fdt_endpoint *);
struct fdt_endpoint *fdt_endpoint_remote_from_index(struct fdt_device_ports *,
int, int);
/*
* get informations/data for a given endpoint
*/
int fdt_endpoint_port_index(struct fdt_endpoint *);
int fdt_endpoint_index(struct fdt_endpoint *);
int fdt_endpoint_phandle(struct fdt_endpoint *);
device_t fdt_endpoint_device(struct fdt_endpoint *);
bool fdt_endpoint_is_active(struct fdt_endpoint *);
bool fdt_endpoint_is_enabled(struct fdt_endpoint *);
enum endpoint_type fdt_endpoint_type(struct fdt_endpoint *);
/*
* call dp_ep_get_data() for the endpoint. The returned pointer is
* type of driver-specific.
@ -99,6 +110,12 @@ void * fdt_endpoint_get_data(struct fdt_endpoint *);
* called for the remote endpoint
*/
int fdt_endpoint_activate(struct fdt_endpoint *, bool);
/*
* Activate/deactive an endpoint by direct reference.
*/
int fdt_endpoint_activate_direct(struct fdt_endpoint *, bool);
/*
* Enable/disable an endpoint. This causes dp_ep_enable() to be called for
* the remote endpoint

View File

@ -1,4 +1,4 @@
/* $NetBSD: fdtvar.h,v 1.47 2019/01/26 14:38:30 thorpej Exp $ */
/* $NetBSD: fdtvar.h,v 1.48 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2015 Jared D. McNeill <jmcneill@invisible.ca>
@ -280,6 +280,7 @@ int fdtbus_get_reg64(int, u_int, uint64_t *, uint64_t *);
int fdtbus_get_phandle(int, const char *);
int fdtbus_get_phandle_from_native(int);
i2c_tag_t fdtbus_get_i2c_tag(int);
i2c_tag_t fdtbus_i2c_acquire(int, const char *);
void * fdtbus_intr_establish(int, u_int, int, int,
int (*func)(void *), void *arg);
void * fdtbus_intr_establish_raw(int, const u_int *, int, int,
@ -344,6 +345,7 @@ int fdtbus_reset_deassert(struct fdtbus_reset *);
struct fdtbus_phy *fdtbus_phy_get(int, const char *);
struct fdtbus_phy *fdtbus_phy_get_index(int, u_int);
void fdtbus_phy_put(struct fdtbus_phy *);
device_t fdtbus_phy_device(struct fdtbus_phy *);
int fdtbus_phy_enable(struct fdtbus_phy *, bool);
struct fdtbus_mmc_pwrseq *fdtbus_mmc_pwrseq_get(int);

View File

@ -1,4 +1,4 @@
# $NetBSD: files.fdt,v 1.41 2018/10/19 21:09:10 jakllsch Exp $
# $NetBSD: files.fdt,v 1.42 2019/01/30 01:24:00 jmcneill Exp $
include "external/bsd/libfdt/conf/files.libfdt"
@ -45,6 +45,10 @@ device panel: fdt_port
attach panel at fdt with fdt_panel
file dev/fdt/panel_fdt.c fdt_panel
device dispcon: fdt_port, drmkms, ddc_read_edid
attach dispcon at fdt with dispcon_hdmi
file dev/fdt/hdmi_connector.c dispcon_hdmi
file dev/fdt/fdt_openfirm.c fdtbase
file dev/fdt/fdt_subr.c fdtbase
file dev/fdt/fdt_clock.c fdt

View File

@ -0,0 +1,237 @@
/* $NetBSD: hdmi_connector.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
/*-
* Copyright (c) 2019 Jared McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hdmi_connector.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/gpio.h>
#include <dev/fdt/fdtvar.h>
#include <dev/fdt/fdt_port.h>
#include <dev/i2c/ddcvar.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
static const char * const compatible[] = {
"hdmi-connector",
NULL
};
struct dispcon_hdmi_connector {
struct drm_connector base;
struct fdtbus_gpio_pin *hpd;
i2c_tag_t ddc;
int type; /* DRM_MODE_CONNECTOR_* */
};
struct dispcon_hdmi_softc {
struct fdt_device_ports sc_ports;
struct dispcon_hdmi_connector sc_connector;
};
#define to_dispcon_hdmi_connector(x) container_of(x, struct dispcon_hdmi_connector, base)
static enum drm_connector_status
dispcon_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector);
bool con;
if (hdmi_connector->hpd == NULL) {
/*
* No hotplug detect pin available. Assume that we are connected.
*/
return connector_status_connected;
}
/*
* Read connect status from hotplug detect pin.
*/
con = fdtbus_gpio_read(hdmi_connector->hpd);
if (con) {
return connector_status_connected;
} else {
return connector_status_disconnected;
}
}
static void
dispcon_hdmi_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs dispcon_hdmi_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = dispcon_hdmi_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = dispcon_hdmi_connector_destroy,
};
static int
dispcon_hdmi_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode)
{
return MODE_OK;
}
static int
dispcon_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector);
char edid[EDID_LENGTH * 4];
struct edid *pedid = NULL;
int error, block;
if (hdmi_connector->ddc != NULL) {
memset(edid, 0, sizeof(edid));
for (block = 0; block < 4; block++) {
error = ddc_read_edid_block(hdmi_connector->ddc,
&edid[block * EDID_LENGTH], EDID_LENGTH, block);
if (error)
break;
if (block == 0) {
pedid = (struct edid *)edid;
if (edid[0x7e] == 0)
break;
}
}
}
drm_mode_connector_update_edid_property(connector, pedid);
if (pedid == NULL)
return 0;
error = drm_add_edid_modes(connector, pedid);
drm_edid_to_eld(connector, pedid);
return error;
}
static struct drm_encoder *
dispcon_hdmi_connector_best_encoder(struct drm_connector *connector)
{
int enc_id = connector->encoder_ids[0];
struct drm_mode_object *obj;
struct drm_encoder *encoder = NULL;
if (enc_id) {
obj = drm_mode_object_find(connector->dev, enc_id,
DRM_MODE_OBJECT_ENCODER);
if (obj == NULL)
return NULL;
encoder = obj_to_encoder(obj);
}
return encoder;
}
static const struct drm_connector_helper_funcs dispcon_hdmi_connector_helper_funcs = {
.mode_valid = dispcon_hdmi_connector_mode_valid,
.get_modes = dispcon_hdmi_connector_get_modes,
.best_encoder = dispcon_hdmi_connector_best_encoder,
};
static int
dispcon_hdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
{
struct drm_connector *connector = fdt_endpoint_get_data(ep);
struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector);
struct fdt_endpoint *rep = fdt_endpoint_remote(ep);
struct drm_encoder *encoder;
if (fdt_endpoint_port_index(ep) != 0)
return EINVAL;
if (fdt_endpoint_type(rep) != EP_DRM_ENCODER)
return EINVAL;
if (activate) {
encoder = fdt_endpoint_get_data(rep);
connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
connector->interlace_allowed = 0;
connector->doublescan_allowed = 0;
drm_connector_init(encoder->dev, connector, &dispcon_hdmi_connector_funcs,
hdmi_connector->type);
drm_connector_helper_add(connector, &dispcon_hdmi_connector_helper_funcs);
drm_connector_register(connector);
drm_mode_connector_attach_encoder(connector, encoder);
}
return 0;
}
static void *
dispcon_hdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
{
struct dispcon_hdmi_softc * const sc = device_private(dev);
return &sc->sc_connector.base;
}
static int
dispcon_hdmi_match(device_t parent, cfdata_t cf, void *aux)
{
struct fdt_attach_args * const faa = aux;
return of_match_compatible(faa->faa_phandle, compatible);
}
static void
dispcon_hdmi_attach(device_t parent, device_t self, void *aux)
{
struct dispcon_hdmi_softc * const sc = device_private(self);
struct dispcon_hdmi_connector * const hdmi_connector = &sc->sc_connector;
struct fdt_attach_args * const faa = aux;
const int phandle = faa->faa_phandle;
aprint_naive("\n");
aprint_normal(": HDMI connector\n");
hdmi_connector->type = DRM_MODE_CONNECTOR_HDMIA;
hdmi_connector->hpd = fdtbus_gpio_acquire(phandle, "hpd-gpios", GPIO_PIN_INPUT);
hdmi_connector->ddc = fdtbus_i2c_acquire(phandle, "ddc-i2c-bus");
sc->sc_ports.dp_ep_activate = dispcon_hdmi_ep_activate;
sc->sc_ports.dp_ep_get_data = dispcon_hdmi_ep_get_data;
fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_CONNECTOR);
}
CFATTACH_DECL_NEW(dispcon_hdmi, sizeof(struct dispcon_hdmi_softc),
dispcon_hdmi_match, dispcon_hdmi_attach, NULL, NULL);