On Sun, Aug 13, 2017 at 05:11:30PM +0200, Linus Walleij wrote: > This adds a new DRM driver for the Faraday Technology TVE200 > block. This "TV Encoder" encodes a ITU-T BT.656 stream and can > be found in the StorLink SL3516 (later Cortina Systems CS3516) > as well as the Grain Media GM8180. > > I do not have definitive word from anyone at Faraday that this > IP block is theirs, but it bears the hallmark of their 3-digit > version code (200) and is used in two SoCs from completely > different companies. (Grain Media was fully owned by Faraday > until it was transferred to NovoTek this january, and > Faraday did lots of work on the StorLink SoCs.) > > The D-Link DIR-685 uses this in connection with the Ilitek > ILI9322 panel driver that supports BT.656 input, while the > GM8180 apparently has been used with the Cirrus Logic CS4954 > digital video encoder. The oldest user seems to be > something called Techwall 2835. > > This driver is heavily inspired by Eric Anholt's PL111 > driver and therefore I have mentioned all the ancestor authors > in the header file. > > Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxx> > --- > Documentation/gpu/index.rst | 1 + > Documentation/gpu/tve200.rst | 6 + > MAINTAINERS | 6 + > drivers/gpu/drm/Kconfig | 2 + > drivers/gpu/drm/Makefile | 1 + > drivers/gpu/drm/tve200/Kconfig | 15 ++ > drivers/gpu/drm/tve200/Makefile | 5 + > drivers/gpu/drm/tve200/tve200_connector.c | 126 +++++++++++ > drivers/gpu/drm/tve200/tve200_display.c | 346 ++++++++++++++++++++++++++++++ > drivers/gpu/drm/tve200/tve200_drm.h | 129 +++++++++++ > drivers/gpu/drm/tve200/tve200_drv.c | 277 ++++++++++++++++++++++++ > 11 files changed, 914 insertions(+) > create mode 100644 Documentation/gpu/tve200.rst > create mode 100644 drivers/gpu/drm/tve200/Kconfig > create mode 100644 drivers/gpu/drm/tve200/Makefile > create mode 100644 drivers/gpu/drm/tve200/tve200_connector.c > create mode 100644 drivers/gpu/drm/tve200/tve200_display.c > create mode 100644 drivers/gpu/drm/tve200/tve200_drm.h > create mode 100644 drivers/gpu/drm/tve200/tve200_drv.c > > diff --git a/Documentation/gpu/index.rst b/Documentation/gpu/index.rst > index 35d673bf9b56..c36586dad29d 100644 > --- a/Documentation/gpu/index.rst > +++ b/Documentation/gpu/index.rst > @@ -15,6 +15,7 @@ Linux GPU Driver Developer's Guide > pl111 > tegra > tinydrm > + tve200 > vc4 > vga-switcheroo > vgaarbiter > diff --git a/Documentation/gpu/tve200.rst b/Documentation/gpu/tve200.rst > new file mode 100644 > index 000000000000..69b17b324e12 > --- /dev/null > +++ b/Documentation/gpu/tve200.rst > @@ -0,0 +1,6 @@ > +================================== > + drm/tve200 Faraday TV Encoder 200 > +================================== > + > +.. kernel-doc:: drivers/gpu/drm/tve200/tve200_drv.c > + :doc: Faraday TV Encoder 200 > diff --git a/MAINTAINERS b/MAINTAINERS > index e87cba115ea4..c3d42d68253a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -4305,6 +4305,12 @@ T: git git://anongit.freedesktop.org/drm/drm-misc > S: Maintained > F: drivers/gpu/drm/bochs/ > > +DRM DRIVER FOR FARADAY TVE200 TV ENCODER > +M: Linus Walleij <linus.walleij@xxxxxxxxxx> > +T: git git://anongit.freedesktop.org/drm/drm-misc > +S: Maintained > +F: drivers/gpu/drm/tve200/ > + > DRM DRIVER FOR INTEL I810 VIDEO CARDS > S: Orphan / Obsolete > F: drivers/gpu/drm/i810/ > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig > index 83cb2a88c204..c5e1a8409285 100644 > --- a/drivers/gpu/drm/Kconfig > +++ b/drivers/gpu/drm/Kconfig > @@ -278,6 +278,8 @@ source "drivers/gpu/drm/tinydrm/Kconfig" > > source "drivers/gpu/drm/pl111/Kconfig" > > +source "drivers/gpu/drm/tve200/Kconfig" > + > # Keep legacy drivers last > > menuconfig DRM_LEGACY > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile > index 24a066e1841c..cc81813e2238 100644 > --- a/drivers/gpu/drm/Makefile > +++ b/drivers/gpu/drm/Makefile > @@ -100,3 +100,4 @@ obj-$(CONFIG_DRM_ZTE) += zte/ > obj-$(CONFIG_DRM_MXSFB) += mxsfb/ > obj-$(CONFIG_DRM_TINYDRM) += tinydrm/ > obj-$(CONFIG_DRM_PL111) += pl111/ > +obj-$(CONFIG_DRM_TVE200) += tve200/ > diff --git a/drivers/gpu/drm/tve200/Kconfig b/drivers/gpu/drm/tve200/Kconfig > new file mode 100644 > index 000000000000..21d9841ddb88 > --- /dev/null > +++ b/drivers/gpu/drm/tve200/Kconfig > @@ -0,0 +1,15 @@ > +config DRM_TVE200 > + tristate "DRM Support for Faraday TV Encoder TVE200" > + depends on DRM > + depends on CMA > + depends on ARM || COMPILE_TEST > + depends on OF > + select DRM_PANEL > + select DRM_KMS_HELPER > + select DRM_KMS_CMA_HELPER > + select DRM_GEM_CMA_HELPER > + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE > + help > + Choose this option for DRM support for the Faraday TV Encoder > + TVE200 Controller. > + If M is selected the module will be called tve200_drm. > diff --git a/drivers/gpu/drm/tve200/Makefile b/drivers/gpu/drm/tve200/Makefile > new file mode 100644 > index 000000000000..a9dba54f7ee5 > --- /dev/null > +++ b/drivers/gpu/drm/tve200/Makefile > @@ -0,0 +1,5 @@ > +tve200_drm-y += tve200_connector.o \ > + tve200_display.o \ > + tve200_drv.o > + > +obj-$(CONFIG_DRM_TVE200) += tve200_drm.o > diff --git a/drivers/gpu/drm/tve200/tve200_connector.c b/drivers/gpu/drm/tve200/tve200_connector.c > new file mode 100644 > index 000000000000..93e99156d375 > --- /dev/null > +++ b/drivers/gpu/drm/tve200/tve200_connector.c > @@ -0,0 +1,126 @@ > +/* > + * Copyright (C) 2017 Linus Walleij <linus.walleij@xxxxxxxxxx> > + * Parts of this file were based on sources as follows: > + * > + * Copyright (C) 2006-2008 Intel Corporation > + * Copyright (C) 2007 Amos Lee <amos_lee@xxxxxxxxxxxxxxxx> > + * Copyright (C) 2007 Dave Airlie <airlied@xxxxxxxx> > + * Copyright (C) 2011 Texas Instruments > + * Copyright (C) 2017 Eric Anholt > + * > + * This program is free software and is provided to you under the terms of the > + * GNU General Public License version 2 as published by the Free Software > + * Foundation, and any use by you of this program is subject to the terms of > + * such GNU licence. > + */ > + > +/** > + * tve200_drm_connector.c > + * Implementation of the connector functions for the Faraday TV Encoder > + */ > +#include <linux/version.h> > +#include <linux/shmem_fs.h> > +#include <linux/dma-buf.h> > + > +#include <drm/drmP.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_of.h> > +#include <drm/drm_panel.h> > + > +#include "tve200_drm.h" > + > +static void tve200_connector_destroy(struct drm_connector *connector) > +{ > + struct tve200_drm_connector *tve200con = > + to_tve200_connector(connector); > + > + if (tve200con->panel) > + drm_panel_detach(tve200con->panel); > + > + drm_connector_unregister(connector); > + drm_connector_cleanup(connector); > +} > + > +static enum drm_connector_status tve200_connector_detect(struct drm_connector > + *connector, bool force) > +{ > + struct tve200_drm_connector *tve200con = > + to_tve200_connector(connector); > + > + return (tve200con->panel ? > + connector_status_connected : > + connector_status_disconnected); > +} > + > +static int tve200_connector_helper_get_modes(struct drm_connector *connector) > +{ > + struct tve200_drm_connector *tve200con = > + to_tve200_connector(connector); > + > + if (!tve200con->panel) > + return 0; > + > + return drm_panel_get_modes(tve200con->panel); > +} > + > +static const struct drm_connector_funcs connector_funcs = { > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = tve200_connector_destroy, > + .detect = tve200_connector_detect, > + .dpms = drm_atomic_helper_connector_dpms, > + .reset = drm_atomic_helper_connector_reset, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > +}; > + > +static const struct drm_connector_helper_funcs connector_helper_funcs = { > + .get_modes = tve200_connector_helper_get_modes, > +}; The new "wrap panel as a bridge endpoint" helper we have in drm/bridge/panel.c is meant to remove all the need for the above boilerplate. Even comes with devm_ built-in :-) Please check it out. You're using the simple helpers, but I think they should fully mesh with the bridge stuff. Otherwise looks all neat and tidy. Acked-by: Daniel Vetter <daniel.vetter@xxxxxxxx> Wrt merging/maintaining: Want to include it in the drm-misc pile? We'll happily throw commit rights at every driver submission (and honestly expect that, since it helps tremendously with balance maintainer loads for the oddball trivial patch). Thanks, Daniel > + > +/* > + * Walks the OF graph to find the panel node and then asks DRM to look > + * up the panel. > + */ > +static struct drm_panel *tve200_get_panel(struct device *dev) > +{ > + struct device_node *endpoint, *panel_node; > + struct device_node *np = dev->of_node; > + struct drm_panel *panel; > + > + endpoint = of_graph_get_next_endpoint(np, NULL); > + if (!endpoint) { > + dev_err(dev, "no endpoint to fetch panel\n"); > + return NULL; > + } > + > + /* Don't proceed if we have an endpoint but no panel_node tied to it */ > + panel_node = of_graph_get_remote_port_parent(endpoint); > + of_node_put(endpoint); > + if (!panel_node) { > + dev_err(dev, "no valid panel node\n"); > + return NULL; > + } > + > + panel = of_drm_find_panel(panel_node); > + of_node_put(panel_node); > + > + return panel; > +} > + > +int tve200_connector_init(struct drm_device *dev) > +{ > + struct tve200_drm_dev_private *priv = dev->dev_private; > + struct tve200_drm_connector *tve200con = &priv->connector; > + struct drm_connector *connector = &tve200con->connector; > + > + drm_connector_init(dev, connector, &connector_funcs, > + DRM_MODE_CONNECTOR_DPI); > + drm_connector_helper_add(connector, &connector_helper_funcs); > + > + tve200con->panel = tve200_get_panel(dev->dev); > + if (tve200con->panel) > + drm_panel_attach(tve200con->panel, connector); > + > + return 0; > +} > diff --git a/drivers/gpu/drm/tve200/tve200_display.c b/drivers/gpu/drm/tve200/tve200_display.c > new file mode 100644 > index 000000000000..027553aacb33 > --- /dev/null > +++ b/drivers/gpu/drm/tve200/tve200_display.c > @@ -0,0 +1,346 @@ > +/* > + * Copyright (C) 2017 Linus Walleij <linus.walleij@xxxxxxxxxx> > + * Parts of this file were based on sources as follows: > + * > + * Copyright (C) 2006-2008 Intel Corporation > + * Copyright (C) 2007 Amos Lee <amos_lee@xxxxxxxxxxxxxxxx> > + * Copyright (C) 2007 Dave Airlie <airlied@xxxxxxxx> > + * Copyright (C) 2011 Texas Instruments > + * Copyright (C) 2017 Eric Anholt > + * > + * This program is free software and is provided to you under the terms of the > + * GNU General Public License version 2 as published by the Free Software > + * Foundation, and any use by you of this program is subject to the terms of > + * such GNU licence. > + */ > +#include <linux/clk.h> > +#include <linux/version.h> > +#include <linux/dma-buf.h> > +#include <linux/of_graph.h> > + > +#include <drm/drmP.h> > +#include <drm/drm_panel.h> > +#include <drm/drm_gem_cma_helper.h> > +#include <drm/drm_fb_cma_helper.h> > + > +#include "tve200_drm.h" > + > +irqreturn_t tve200_irq(int irq, void *data) > +{ > + struct tve200_drm_dev_private *priv = data; > + u32 stat; > + u32 val; > + > + stat = readl(priv->regs + TVE200_INT_STAT); > + > + if (!stat) > + return IRQ_NONE; > + > + /* > + * Vblank IRQ > + * > + * The hardware is a bit tilted: the line stays high after clearing > + * the vblank IRQ, fireing many more interrupts. We counter this > + * by toggling the IRQ back and forth from fireing at vblank and > + * fireing at start of active image, which works around the problem > + * since those occur strictly in sequence, and we get two IRQs for each > + * frame, one at start of Vblank (that we make call into the CRTC) and > + * another one at the start of the image (that we discard). > + */ > + if (stat & TVE200_INT_V_STATUS) { > + val = readl(priv->regs + TVE200_CTRL); > + /* We have an actual start of vsync */ > + if (!(val & TVE200_VSTSTYPE_BITS)) { > + drm_crtc_handle_vblank(&priv->pipe.crtc); > + /* Toggle trigger to start of active image */ > + val |= TVE200_VSTSTYPE_VAI; > + } else { > + /* Toggle trigger back to start of vsync */ > + val &= ~TVE200_VSTSTYPE_BITS; > + } > + writel(val, priv->regs + TVE200_CTRL); > + } else > + dev_err(priv->drm->dev, "stray IRQ %08x\n", stat); > + > + /* Clear the interrupt once done */ > + writel(stat, priv->regs + TVE200_INT_CLR); > + > + return IRQ_HANDLED; > +} > + > +static int tve200_display_check(struct drm_simple_display_pipe *pipe, > + struct drm_plane_state *pstate, > + struct drm_crtc_state *cstate) > +{ > + const struct drm_display_mode *mode = &cstate->mode; > + struct drm_framebuffer *old_fb = pipe->plane.state->fb; > + struct drm_framebuffer *fb = pstate->fb; > + struct drm_connector *connector = pipe->connector; > + struct drm_device *drm = connector->dev; > + > + /* > + * We support these specific resolutions and nothing else. > + */ > + if (!(mode->hdisplay == 352 && mode->vdisplay == 240) && /* SIF(525) */ > + !(mode->hdisplay == 352 && mode->vdisplay == 288) && /* CIF(625) */ > + !(mode->hdisplay == 640 && mode->vdisplay == 480) && /* VGA */ > + !(mode->hdisplay == 720 && mode->vdisplay == 480) && /* D1 */ > + !(mode->hdisplay == 720 && mode->vdisplay == 576)) { /* D1 */ > + dev_err(drm->dev, "unsupported display mode (%u x %u)\n", > + mode->hdisplay, mode->vdisplay); > + return -EINVAL; > + } > + > + if (fb) { > + u32 offset = drm_fb_cma_get_gem_addr(fb, pstate, 0); > + > + /* FB base address must be dword aligned. */ > + if (offset & 3) { > + dev_err(drm->dev, "FB not 32-bit aligned\n"); > + return -EINVAL; > + } > + > + /* > + * There's no pitch register, the mode's hdisplay > + * controls this. > + */ > + if (fb->pitches[0] != mode->hdisplay * fb->format->cpp[0]) { > + dev_err(drm->dev, "can't handle pitches\n"); > + return -EINVAL; > + } > + > + /* > + * We can't change the FB format in a flicker-free > + * manner (and only update it during CRTC enable). > + */ > + if (old_fb && old_fb->format != fb->format) > + cstate->mode_changed = true; > + } > + > + return 0; > +} > + > +static void tve200_display_enable(struct drm_simple_display_pipe *pipe, > + struct drm_crtc_state *cstate) > +{ > + struct drm_crtc *crtc = &pipe->crtc; > + struct drm_plane *plane = &pipe->plane; > + struct drm_device *drm = crtc->dev; > + struct tve200_drm_dev_private *priv = drm->dev_private; > + const struct drm_display_mode *mode = &cstate->mode; > + struct drm_framebuffer *fb = plane->state->fb; > + struct drm_connector *connector = &priv->connector.connector; > + u32 format = fb->format->format; > + u32 ctrl1 = 0; > + > + clk_prepare_enable(priv->clk); > + > + /* Function 1 */ > + ctrl1 |= TVE200_CTRL_CSMODE; > + /* Interlace mode for CCIR656: parameterize? */ > + ctrl1 |= TVE200_CTRL_NONINTERLACE; > + /* 32 words per burst */ > + ctrl1 |= TVE200_CTRL_BURST_32_WORDS; > + /* 16 retries */ > + ctrl1 |= TVE200_CTRL_RETRYCNT_16; > + /* NTSC mode: parametrize? */ > + ctrl1 |= TVE200_CTRL_NTSC; > + > + /* Vsync IRQ at start of Vsync at first */ > + ctrl1 |= TVE200_VSTSTYPE_VSYNC; > + > + if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE) > + ctrl1 |= TVE200_CTRL_TVCLKP; > + > + if ((mode->hdisplay == 352 && mode->vdisplay == 240) || /* SIF(525) */ > + (mode->hdisplay == 352 && mode->vdisplay == 288)) { /* CIF(625) */ > + ctrl1 |= TVE200_CTRL_IPRESOL_CIF; > + dev_info(drm->dev, "CIF mode\n"); > + } else if (mode->hdisplay == 640 && mode->vdisplay == 480) { > + ctrl1 |= TVE200_CTRL_IPRESOL_VGA; > + dev_info(drm->dev, "VGA mode\n"); > + } else if ((mode->hdisplay == 720 && mode->vdisplay == 480) || > + (mode->hdisplay == 720 && mode->vdisplay == 576)) { > + ctrl1 |= TVE200_CTRL_IPRESOL_D1; > + dev_info(drm->dev, "D1 mode\n"); > + } > + > + if (format & DRM_FORMAT_BIG_ENDIAN) { > + ctrl1 |= TVE200_CTRL_BBBP; > + format &= ~DRM_FORMAT_BIG_ENDIAN; > + } > + > + switch (format) { > + case DRM_FORMAT_XRGB8888: > + ctrl1 |= TVE200_IPDMOD_RGB888; > + break; > + case DRM_FORMAT_RGB565: > + ctrl1 |= TVE200_IPDMOD_RGB565; > + break; > + case DRM_FORMAT_XRGB1555: > + ctrl1 |= TVE200_IPDMOD_RGB555; > + break; > + case DRM_FORMAT_XBGR8888: > + ctrl1 |= TVE200_IPDMOD_RGB888 | TVE200_BGR; > + break; > + case DRM_FORMAT_BGR565: > + ctrl1 |= TVE200_IPDMOD_RGB565 | TVE200_BGR; > + break; > + case DRM_FORMAT_XBGR1555: > + ctrl1 |= TVE200_IPDMOD_RGB555 | TVE200_BGR; > + break; > + case DRM_FORMAT_YUYV: > + ctrl1 |= TVE200_IPDMOD_YUV422; > + ctrl1 |= TVE200_CTRL_YCBCRODR_CR0Y1CB0Y0; > + break; > + case DRM_FORMAT_YVYU: > + ctrl1 |= TVE200_IPDMOD_YUV422; > + ctrl1 |= TVE200_CTRL_YCBCRODR_CB0Y1CR0Y0; > + break; > + case DRM_FORMAT_UYVY: > + ctrl1 |= TVE200_IPDMOD_YUV422; > + ctrl1 |= TVE200_CTRL_YCBCRODR_Y1CR0Y0CB0; > + break; > + case DRM_FORMAT_VYUY: > + ctrl1 |= TVE200_IPDMOD_YUV422; > + ctrl1 |= TVE200_CTRL_YCBCRODR_Y1CB0Y0CR0; > + break; > + case DRM_FORMAT_YUV420: > + ctrl1 |= TVE200_CTRL_YUV420; > + ctrl1 |= TVE200_IPDMOD_YUV420; > + break; > + default: > + dev_err(drm->dev, "Unknown FB format 0x%08x\n", > + fb->format->format); > + break; > + } > + > + ctrl1 |= TVE200_TVEEN; > + > + drm_panel_prepare(priv->connector.panel); > + > + /* Turn it on */ > + writel(ctrl1, priv->regs + TVE200_CTRL); > + > + drm_panel_enable(priv->connector.panel); > + > + drm_crtc_vblank_on(crtc); > +} > + > +void tve200_display_disable(struct drm_simple_display_pipe *pipe) > +{ > + struct drm_crtc *crtc = &pipe->crtc; > + struct drm_device *drm = crtc->dev; > + struct tve200_drm_dev_private *priv = drm->dev_private; > + > + drm_crtc_vblank_off(crtc); > + > + drm_panel_disable(priv->connector.panel); > + > + /* Disable and Power Down */ > + writel(0, priv->regs + TVE200_CTRL); > + > + drm_panel_unprepare(priv->connector.panel); > + > + clk_disable_unprepare(priv->clk); > +} > + > +static void tve200_display_update(struct drm_simple_display_pipe *pipe, > + struct drm_plane_state *old_pstate) > +{ > + struct drm_crtc *crtc = &pipe->crtc; > + struct drm_device *drm = crtc->dev; > + struct tve200_drm_dev_private *priv = drm->dev_private; > + struct drm_pending_vblank_event *event = crtc->state->event; > + struct drm_plane *plane = &pipe->plane; > + struct drm_plane_state *pstate = plane->state; > + struct drm_framebuffer *fb = pstate->fb; > + > + if (fb) { > + /* For RGB, the Y component is used as base address */ > + writel(drm_fb_cma_get_gem_addr(fb, pstate, 0), > + priv->regs + TVE200_Y_FRAME_BASE_ADDR); > + > + /* For three plane YUV we need two more addresses */ > + if (fb->format->format == DRM_FORMAT_YUV420) { > + writel(drm_fb_cma_get_gem_addr(fb, pstate, 1), > + priv->regs + TVE200_U_FRAME_BASE_ADDR); > + writel(drm_fb_cma_get_gem_addr(fb, pstate, 2), > + priv->regs + TVE200_V_FRAME_BASE_ADDR); > + } > + } > + > + if (event) { > + crtc->state->event = NULL; > + > + spin_lock_irq(&crtc->dev->event_lock); > + if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0) > + drm_crtc_arm_vblank_event(crtc, event); > + else > + drm_crtc_send_vblank_event(crtc, event); > + spin_unlock_irq(&crtc->dev->event_lock); > + } > +} > + > +int tve200_enable_vblank(struct drm_device *drm, unsigned int crtc) > +{ > + struct tve200_drm_dev_private *priv = drm->dev_private; > + > + writel(TVE200_INT_V_STATUS, priv->regs + TVE200_INT_EN); > + return 0; > +} > + > +void tve200_disable_vblank(struct drm_device *drm, unsigned int crtc) > +{ > + struct tve200_drm_dev_private *priv = drm->dev_private; > + > + writel(0, priv->regs + TVE200_INT_EN); > +} > + > +static int tve200_display_prepare_fb(struct drm_simple_display_pipe *pipe, > + struct drm_plane_state *plane_state) > +{ > + return drm_fb_cma_prepare_fb(&pipe->plane, plane_state); > +} > + > +const struct drm_simple_display_pipe_funcs tve200_display_funcs = { > + .check = tve200_display_check, > + .enable = tve200_display_enable, > + .disable = tve200_display_disable, > + .update = tve200_display_update, > + .prepare_fb = tve200_display_prepare_fb, > +}; > + > +int tve200_display_init(struct drm_device *drm) > +{ > + struct tve200_drm_dev_private *priv = drm->dev_private; > + int ret; > + static const u32 formats[] = { > + DRM_FORMAT_XRGB8888, > + DRM_FORMAT_XBGR8888, > + DRM_FORMAT_RGB565, > + DRM_FORMAT_BGR565, > + DRM_FORMAT_XRGB1555, > + DRM_FORMAT_XBGR1555, > + /* > + * The controller actually supports any YCbCr ordering, > + * for packed YCbCr. This just lists the orderings that > + * DRM supports. > + */ > + DRM_FORMAT_YUYV, > + DRM_FORMAT_YVYU, > + DRM_FORMAT_UYVY, > + DRM_FORMAT_VYUY, > + /* This uses three planes */ > + DRM_FORMAT_YUV420, > + }; > + > + ret = drm_simple_display_pipe_init(drm, &priv->pipe, > + &tve200_display_funcs, > + formats, ARRAY_SIZE(formats), > + &priv->connector.connector); > + if (ret) > + return ret; > + > + return 0; > +} > diff --git a/drivers/gpu/drm/tve200/tve200_drm.h b/drivers/gpu/drm/tve200/tve200_drm.h > new file mode 100644 > index 000000000000..f00fc47a6bd1 > --- /dev/null > +++ b/drivers/gpu/drm/tve200/tve200_drm.h > @@ -0,0 +1,129 @@ > +/* > + * Copyright (C) 2017 Linus Walleij <linus.walleij@xxxxxxxxxx> > + * Parts of this file were based on sources as follows: > + * > + * Copyright (C) 2006-2008 Intel Corporation > + * Copyright (C) 2007 Amos Lee <amos_lee@xxxxxxxxxxxxxxxx> > + * Copyright (C) 2007 Dave Airlie <airlied@xxxxxxxx> > + * Copyright (C) 2011 Texas Instruments > + * Copyright (C) 2017 Eric Anholt > + * > + * This program is free software and is provided to you under the terms of the > + * GNU General Public License version 2 as published by the Free Software > + * Foundation, and any use by you of this program is subject to the terms of > + * such GNU licence. > + */ > + > +#ifndef _TVE200_DRM_H_ > +#define _TVE200_DRM_H_ > + > +/* Bits 2-31 are valid physical base addresses */ > +#define TVE200_Y_FRAME_BASE_ADDR 0x00 > +#define TVE200_U_FRAME_BASE_ADDR 0x04 > +#define TVE200_V_FRAME_BASE_ADDR 0x08 > + > +#define TVE200_INT_EN 0x0C > +#define TVE200_INT_CLR 0x10 > +#define TVE200_INT_STAT 0x14 > +#define TVE200_INT_BUS_ERR BIT(7) > +#define TVE200_INT_V_STATUS BIT(6) /* vertical blank */ > +#define TVE200_INT_V_NEXT_FRAME BIT(5) > +#define TVE200_INT_U_NEXT_FRAME BIT(4) > +#define TVE200_INT_Y_NEXT_FRAME BIT(3) > +#define TVE200_INT_V_FIFO_UNDERRUN BIT(2) > +#define TVE200_INT_U_FIFO_UNDERRUN BIT(1) > +#define TVE200_INT_Y_FIFO_UNDERRUN BIT(0) > +#define TVE200_FIFO_UNDERRUNS (TVE200_INT_V_FIFO_UNDERRUN | \ > + TVE200_INT_U_FIFO_UNDERRUN | \ > + TVE200_INT_Y_FIFO_UNDERRUN) > + > +#define TVE200_CTRL 0x18 > +#define TVE200_CTRL_YUV420 BIT(31) > +#define TVE200_CTRL_CSMODE BIT(30) > +#define TVE200_CTRL_NONINTERLACE BIT(28) /* 0 = non-interlace CCIR656 */ > +#define TVE200_CTRL_TVCLKP BIT(27) /* Inverted clock phase */ > +/* Bits 24..26 define the burst size after arbitration on the bus */ > +#define TVE200_CTRL_BURST_4_WORDS (0 << 24) > +#define TVE200_CTRL_BURST_8_WORDS (1 << 24) > +#define TVE200_CTRL_BURST_16_WORDS (2 << 24) > +#define TVE200_CTRL_BURST_32_WORDS (3 << 24) > +#define TVE200_CTRL_BURST_64_WORDS (4 << 24) > +#define TVE200_CTRL_BURST_128_WORDS (5 << 24) > +#define TVE200_CTRL_BURST_256_WORDS (6 << 24) > +#define TVE200_CTRL_BURST_0_WORDS (7 << 24) /* ? */ > +/* > + * Bits 16..23 is the retry count*16 before issueing a new AHB transfer > + * on the AHB bus. > + */ > +#define TVE200_CTRL_RETRYCNT_MASK GENMASK(23, 16) > +#define TVE200_CTRL_RETRYCNT_16 (1 << 16) > +#define TVE200_CTRL_BBBP BIT(15) /* 0 = little-endian */ > +/* Bits 12..14 define the YCbCr ordering */ > +#define TVE200_CTRL_YCBCRODR_CB0Y0CR0Y1 (0 << 12) > +#define TVE200_CTRL_YCBCRODR_Y0CB0Y1CR0 (1 << 12) > +#define TVE200_CTRL_YCBCRODR_CR0Y0CB0Y1 (2 << 12) > +#define TVE200_CTRL_YCBCRODR_Y1CB0Y0CR0 (3 << 12) > +#define TVE200_CTRL_YCBCRODR_CR0Y1CB0Y0 (4 << 12) > +#define TVE200_CTRL_YCBCRODR_Y1CR0Y0CB0 (5 << 12) > +#define TVE200_CTRL_YCBCRODR_CB0Y1CR0Y0 (6 << 12) > +#define TVE200_CTRL_YCBCRODR_Y0CR0Y1CB0 (7 << 12) > +/* Bits 10..11 define the input resolution (framebuffer size) */ > +#define TVE200_CTRL_IPRESOL_CIF (0 << 10) > +#define TVE200_CTRL_IPRESOL_VGA (1 << 10) > +#define TVE200_CTRL_IPRESOL_D1 (2 << 10) > +#define TVE200_CTRL_NTSC BIT(9) /* 0 = PAL, 1 = NTSC */ > +#define TVE200_CTRL_INTERLACE BIT(8) /* 1 = interlace, only for D1 */ > +#define TVE200_IPDMOD_RGB555 (0 << 6) /* TVE200_CTRL_YUV420 = 0 */ > +#define TVE200_IPDMOD_RGB565 (1 << 6) > +#define TVE200_IPDMOD_RGB888 (2 << 6) > +#define TVE200_IPDMOD_YUV420 (2 << 6) /* TVE200_CTRL_YUV420 = 1 */ > +#define TVE200_IPDMOD_YUV422 (3 << 6) > +/* Bits 4 & 5 define when to fire the vblank IRQ */ > +#define TVE200_VSTSTYPE_VSYNC (0 << 4) /* start of vsync */ > +#define TVE200_VSTSTYPE_VBP (1 << 4) /* start of v back porch */ > +#define TVE200_VSTSTYPE_VAI (2 << 4) /* start of v active image */ > +#define TVE200_VSTSTYPE_VFP (3 << 4) /* start of v front porch */ > +#define TVE200_VSTSTYPE_BITS (BIT(4) | BIT(5)) > +#define TVE200_BGR BIT(1) /* 0 = RGB, 1 = BGR */ > +#define TVE200_TVEEN BIT(0) /* Enable TVE block */ > + > +#define TVE200_CTRL_2 0x1c > +#define TVE200_CTRL_3 0x20 > + > +#define TVE200_CTRL_4 0x24 > +#define TVE200_CTRL_4_RESET BIT(0) /* triggers reset of TVE200 */ > + > +#include <drm/drm_gem.h> > +#include <drm/drm_simple_kms_helper.h> > + > +struct tve200_drm_connector { > + struct drm_connector connector; > + struct drm_panel *panel; > +}; > + > +struct tve200_drm_dev_private { > + struct drm_device *drm; > + > + struct tve200_drm_connector connector; > + struct drm_simple_display_pipe pipe; > + struct drm_fbdev_cma *fbdev; > + > + void *regs; > + struct clk *pclk; > + struct clk *clk; > +}; > + > +#define to_tve200_connector(x) \ > + container_of(x, struct tve200_drm_connector, connector) > + > +int tve200_display_init(struct drm_device *dev); > +int tve200_enable_vblank(struct drm_device *drm, unsigned int crtc); > +void tve200_disable_vblank(struct drm_device *drm, unsigned int crtc); > +irqreturn_t tve200_irq(int irq, void *data); > +int tve200_connector_init(struct drm_device *dev); > +int tve200_encoder_init(struct drm_device *dev); > +int tve200_dumb_create(struct drm_file *file_priv, > + struct drm_device *dev, > + struct drm_mode_create_dumb *args); > + > +#endif /* _TVE200_DRM_H_ */ > diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c > new file mode 100644 > index 000000000000..9e34147e2617 > --- /dev/null > +++ b/drivers/gpu/drm/tve200/tve200_drv.c > @@ -0,0 +1,277 @@ > +/* > + * Copyright (C) 2017 Linus Walleij <linus.walleij@xxxxxxxxxx> > + * Parts of this file were based on sources as follows: > + * > + * Copyright (C) 2006-2008 Intel Corporation > + * Copyright (C) 2007 Amos Lee <amos_lee@xxxxxxxxxxxxxxxx> > + * Copyright (C) 2007 Dave Airlie <airlied@xxxxxxxx> > + * Copyright (C) 2011 Texas Instruments > + * Copyright (C) 2017 Eric Anholt > + * > + * This program is free software and is provided to you under the terms of the > + * GNU General Public License version 2 as published by the Free Software > + * Foundation, and any use by you of this program is subject to the terms of > + * such GNU licence. > + */ > + > +/** > + * DOC: Faraday TV Encoder TVE200 DRM Driver > + * > + * The Faraday TV Encoder TVE200 is also known as the Gemini TV Interface > + * Controller (TVC) and is found in the Gemini Chipset from Storlink > + * Semiconductor (later Storm Semiconductor, later Cortina Systems) > + * but also in the Grain Media GM8180 chipset. On the Gemini the module > + * is connected to 8 data lines and a single clock line, comprising an > + * 8-bit BT.656 interface. > + * > + * This is a very basic YUV display driver. The datasheet specifies that > + * it supports the ITU BT.656 standard. It requires a 27 MHz clock which is > + * the hallmark of any TV encoder supporting both PAL and NTSC. > + * > + * This driver exposes a standard KMS interface for this TV encoder. > + */ > + > +#include <linux/clk.h> > +#include <linux/dma-buf.h> > +#include <linux/irq.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/shmem_fs.h> > +#include <linux/slab.h> > +#include <linux/version.h> > + > +#include <drm/drmP.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_gem_cma_helper.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_panel.h> > + > +#include "tve200_drm.h" > + > +#define DRIVER_DESC "DRM module for Faraday TVE200" > + > +struct drm_mode_config_funcs mode_config_funcs = { > + .fb_create = drm_fb_cma_create, > + .atomic_check = drm_atomic_helper_check, > + .atomic_commit = drm_atomic_helper_commit, > +}; > + > +static int tve200_modeset_init(struct drm_device *dev) > +{ > + struct drm_mode_config *mode_config; > + struct tve200_drm_dev_private *priv = dev->dev_private; > + int ret = 0; > + > + drm_mode_config_init(dev); > + mode_config = &dev->mode_config; > + mode_config->funcs = &mode_config_funcs; > + mode_config->min_width = 352; > + mode_config->max_width = 720; > + mode_config->min_height = 240; > + mode_config->max_height = 576; > + > + ret = tve200_connector_init(dev); > + if (ret) { > + dev_err(dev->dev, "Failed to create tve200_drm_connector\n"); > + goto out_config; > + } > + > + /* > + * Don't actually attach if we didn't find a drm_panel > + * attached to us. > + */ > + if (!priv->connector.panel) { > + dev_info(dev->dev, > + "deferring due to lack of DRM panel device\n"); > + ret = -EPROBE_DEFER; > + goto out_config; > + } > + dev_info(dev->dev, "attached to panel %s\n", > + dev_name(priv->connector.panel->dev)); > + > + ret = tve200_display_init(dev); > + if (ret) { > + dev_err(dev->dev, "failed to init display\n"); > + goto out_config; > + } > + > + ret = drm_vblank_init(dev, 1); > + if (ret) { > + dev_err(dev->dev, "failed to init vblank\n"); > + goto out_config; > + } > + > + drm_mode_config_reset(dev); > + > + /* > + * Passing in 16 here will make the RGB656 mode the default > + * Passing in 32 will use XRGB8888 mode > + */ > + priv->fbdev = drm_fbdev_cma_init(dev, 16, > + dev->mode_config.num_connector); > + drm_kms_helper_poll_init(dev); > + > + goto finish; > + > +out_config: > + drm_mode_config_cleanup(dev); > +finish: > + return ret; > +} > + > +DEFINE_DRM_GEM_CMA_FOPS(drm_fops); > + > +static void tve200_lastclose(struct drm_device *dev) > +{ > + struct tve200_drm_dev_private *priv = dev->dev_private; > + > + drm_fbdev_cma_restore_mode(priv->fbdev); > +} > + > +static struct drm_driver tve200_drm_driver = { > + .driver_features = > + DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC, > + .lastclose = tve200_lastclose, > + .ioctls = NULL, > + .fops = &drm_fops, > + .name = "tve200", > + .desc = DRIVER_DESC, > + .date = "20170703", > + .major = 1, > + .minor = 0, > + .patchlevel = 0, > + .dumb_create = drm_gem_cma_dumb_create, > + .dumb_destroy = drm_gem_dumb_destroy, > + .dumb_map_offset = drm_gem_cma_dumb_map_offset, > + .gem_free_object = drm_gem_cma_free_object, > + .gem_vm_ops = &drm_gem_cma_vm_ops, > + > + .enable_vblank = tve200_enable_vblank, > + .disable_vblank = tve200_disable_vblank, > + > + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, > + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, > + .gem_prime_import = drm_gem_prime_import, > + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, > + .gem_prime_export = drm_gem_prime_export, > + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, > +}; > + > +static int tve200_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct tve200_drm_dev_private *priv; > + struct drm_device *drm; > + struct resource *res; > + int irq; > + int ret; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + drm = drm_dev_alloc(&tve200_drm_driver, dev); > + if (IS_ERR(drm)) > + return PTR_ERR(drm); > + platform_set_drvdata(pdev, drm); > + priv->drm = drm; > + drm->dev_private = priv; > + > + /* Clock the silicon so we can access the registers */ > + priv->pclk = devm_clk_get(dev, "PCLK"); > + if (IS_ERR(priv->pclk)) { > + dev_err(dev, "unable to get PCLK\n"); > + ret = PTR_ERR(priv->pclk); > + goto dev_unref; > + } > + ret = clk_prepare_enable(priv->pclk); > + if (ret) { > + dev_err(dev, "failed to enable PCLK\n"); > + goto dev_unref; > + } > + > + /* This clock is for the pixels (27MHz) */ > + priv->clk = devm_clk_get(dev, "TVE"); > + if (IS_ERR(priv->clk)) { > + dev_err(dev, "unable to get TVE clock\n"); > + ret = PTR_ERR(priv->clk); > + goto clk_disable; > + } > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + priv->regs = devm_ioremap_resource(dev, res); > + if (!priv->regs) { > + dev_err(dev, "%s failed mmio\n", __func__); > + ret = -EINVAL; > + goto dev_unref; > + } > + > + irq = platform_get_irq(pdev, 0); > + if (!irq) { > + ret = -EINVAL; > + goto dev_unref; > + } > + > + /* turn off interrupts before requesting the irq */ > + writel(0, priv->regs + TVE200_INT_EN); > + > + ret = devm_request_irq(dev, irq, tve200_irq, 0, "tve200", priv); > + if (ret) { > + dev_err(dev, "failed to request irq %d\n", ret); > + return ret; > + } > + > + ret = tve200_modeset_init(drm); > + if (ret) > + goto dev_unref; > + > + ret = drm_dev_register(drm, 0); > + if (ret < 0) > + goto dev_unref; > + > + return 0; > + > +clk_disable: > + clk_disable_unprepare(priv->pclk); > +dev_unref: > + drm_dev_unref(drm); > + return ret; > +} > + > +static int tve200_remove(struct platform_device *pdev) > +{ > + struct drm_device *drm = platform_get_drvdata(pdev); > + struct tve200_drm_dev_private *priv = drm->dev_private; > + > + drm_dev_unregister(drm); > + if (priv->fbdev) > + drm_fbdev_cma_fini(priv->fbdev); > + drm_mode_config_cleanup(drm); > + clk_disable_unprepare(priv->pclk); > + drm_dev_unref(drm); > + > + return 0; > +} > + > +static const struct of_device_id tve200_of_match[] = { > + { > + .compatible = "faraday,tve200", > + }, > + {}, > +}; > + > +static struct platform_driver tve200_driver = { > + .driver = { > + .name = "tve200", > + .of_match_table = of_match_ptr(tve200_of_match), > + }, > + .probe = tve200_probe, > + .remove = tve200_remove, > +}; > +module_platform_driver(tve200_driver); > + > +MODULE_DESCRIPTION(DRIVER_DESC); > +MODULE_AUTHOR("Linus Walleij <linus.walleij@xxxxxxxxxx>"); > +MODULE_LICENSE("GPL"); > -- > 2.13.4 > > _______________________________________________ > dri-devel mailing list > dri-devel@xxxxxxxxxxxxxxxxxxxxx > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel