From 179c02fe90a4104d32e92a46b9ff4ecc32bf3647 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 20 Aug 2017 12:05:55 +0200 Subject: drm/tve200: Add new driver for TVE200 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. Acked-by: Daniel Vetter Reviewed-by: Eric Anholt Signed-off-by: Linus Walleij Link: https://patchwork.freedesktop.org/patch/msgid/20170820100557.24991-2-linus.walleij@linaro.org --- drivers/gpu/drm/tve200/tve200_drv.c | 278 ++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 drivers/gpu/drm/tve200/tve200_drv.c (limited to 'drivers/gpu/drm/tve200/tve200_drv.c') diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c new file mode 100644 index 000000000000..fe742106db4e --- /dev/null +++ b/drivers/gpu/drm/tve200/tve200_drv.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2017 Linus Walleij + * Parts of this file were based on sources as follows: + * + * Copyright (C) 2006-2008 Intel Corporation + * Copyright (C) 2007 Amos Lee + * Copyright (C) 2007 Dave Airlie + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "tve200_drm.h" + +#define DRIVER_DESC "DRM module for Faraday TVE200" + +static const 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, + .gem_free_object_unlocked = 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_export = drm_gem_prime_export, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, +}; + +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 clk_disable; + } + + irq = platform_get_irq(pdev, 0); + if (!irq) { + ret = -EINVAL; + goto clk_disable; + } + + /* 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); + goto clk_disable; + } + + ret = tve200_modeset_init(drm); + if (ret) + goto clk_disable; + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto clk_disable; + + 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 "); +MODULE_LICENSE("GPL"); -- cgit From 14b469f9c02d458a4388479fb393a625f15af488 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 2 Sep 2017 22:07:11 +0200 Subject: drm/tve200: Replace custom connector with panel bridge This replaces the custom connector in the TVE200 with the panel bridge helper. As long as we're just using panels and no other bridges, this works just fine. Reviewed-by: Eric Anholt Signed-off-by: Linus Walleij Link: https://patchwork.freedesktop.org/patch/msgid/20170902200711.29298-1-linus.walleij@linaro.org --- drivers/gpu/drm/tve200/tve200_drv.c | 66 ++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 19 deletions(-) (limited to 'drivers/gpu/drm/tve200/tve200_drv.c') diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c index fe742106db4e..c22644692a88 100644 --- a/drivers/gpu/drm/tve200/tve200_drv.c +++ b/drivers/gpu/drm/tve200/tve200_drv.c @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include "tve200_drm.h" @@ -62,6 +64,8 @@ static int tve200_modeset_init(struct drm_device *dev) { struct drm_mode_config *mode_config; struct tve200_drm_dev_private *priv = dev->dev_private; + struct drm_panel *panel; + struct drm_bridge *bridge; int ret = 0; drm_mode_config_init(dev); @@ -72,35 +76,51 @@ static int tve200_modeset_init(struct drm_device *dev) mode_config->min_height = 240; mode_config->max_height = 576; - ret = tve200_connector_init(dev); + ret = drm_of_find_panel_or_bridge(dev->dev->of_node, + 0, 0, &panel, &bridge); + if (ret && ret != -ENODEV) + return ret; + if (panel) { + bridge = drm_panel_bridge_add(panel, + DRM_MODE_CONNECTOR_Unknown); + if (IS_ERR(bridge)) { + ret = PTR_ERR(bridge); + goto out_bridge; + } + } + + ret = tve200_display_init(dev); if (ret) { - dev_err(dev->dev, "Failed to create tve200_drm_connector\n"); - goto out_config; + dev_err(dev->dev, "failed to init display\n"); + goto out_bridge; + } + + if (bridge) { + ret = drm_bridge_attach(priv->encoder, bridge, NULL); + if (ret) + goto out_bridge; } /* - * Don't actually attach if we didn't find a drm_panel - * attached to us. + * TODO: when we are using a different bridge than a panel + * (such as a dumb VGA connector) we need to devise a different + * method to get the connector out of the bridge. */ - if (!priv->connector.panel) { - dev_info(dev->dev, - "deferring due to lack of DRM panel device\n"); - ret = -EPROBE_DEFER; - goto out_config; + if (!panel) { + dev_err(dev->dev, "the bridge is not a panel\n"); + goto out_bridge; } - dev_info(dev->dev, "attached to panel %s\n", - dev_name(priv->connector.panel->dev)); + priv->panel = panel; + priv->connector = panel->connector; + priv->bridge = bridge; - ret = tve200_display_init(dev); - if (ret) { - dev_err(dev->dev, "failed to init display\n"); - goto out_config; - } + dev_info(dev->dev, "attached to panel %s\n", + dev_name(panel->dev)); ret = drm_vblank_init(dev, 1); if (ret) { dev_err(dev->dev, "failed to init vblank\n"); - goto out_config; + goto out_bridge; } drm_mode_config_reset(dev); @@ -115,7 +135,11 @@ static int tve200_modeset_init(struct drm_device *dev) goto finish; -out_config: +out_bridge: + if (panel) + drm_panel_bridge_remove(bridge); + else + drm_bridge_remove(bridge); drm_mode_config_cleanup(dev); finish: return ret; @@ -249,6 +273,10 @@ static int tve200_remove(struct platform_device *pdev) drm_dev_unregister(drm); if (priv->fbdev) drm_fbdev_cma_fini(priv->fbdev); + if (priv->panel) + drm_panel_bridge_remove(priv->bridge); + else + drm_bridge_remove(priv->bridge); drm_mode_config_cleanup(drm); clk_disable_unprepare(priv->pclk); drm_dev_unref(drm); -- cgit From 9ab12e88a0b46b4e6fa32bb0a2875c813a928e73 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 11 Sep 2017 00:08:01 +0200 Subject: drm/tve200: Clean up panel bridging This makes use of the drm_simple_display_pipe_attach_bridge() call and removes the two calls removing the bridge, which were erroneous: they unregister the bridge which is not what we want, we just want to unreference it and that is already handled by the core. Reviewed-by: Eric Anholt Acked-by: Daniel Vetter Signed-off-by: Linus Walleij Link: https://patchwork.freedesktop.org/patch/msgid/20170910220801.28588-1-linus.walleij@linaro.org --- drivers/gpu/drm/tve200/tve200_drv.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) (limited to 'drivers/gpu/drm/tve200/tve200_drv.c') diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c index c22644692a88..eae38b669f0a 100644 --- a/drivers/gpu/drm/tve200/tve200_drv.c +++ b/drivers/gpu/drm/tve200/tve200_drv.c @@ -87,6 +87,14 @@ static int tve200_modeset_init(struct drm_device *dev) ret = PTR_ERR(bridge); goto out_bridge; } + } else { + /* + * TODO: when we are using a different bridge than a panel + * (such as a dumb VGA connector) we need to devise a different + * method to get the connector out of the bridge. + */ + dev_err(dev->dev, "the bridge is not a panel\n"); + goto out_bridge; } ret = tve200_display_init(dev); @@ -95,21 +103,13 @@ static int tve200_modeset_init(struct drm_device *dev) goto out_bridge; } - if (bridge) { - ret = drm_bridge_attach(priv->encoder, bridge, NULL); - if (ret) - goto out_bridge; - } - - /* - * TODO: when we are using a different bridge than a panel - * (such as a dumb VGA connector) we need to devise a different - * method to get the connector out of the bridge. - */ - if (!panel) { - dev_err(dev->dev, "the bridge is not a panel\n"); + ret = drm_simple_display_pipe_attach_bridge(&priv->pipe, + bridge); + if (ret) { + dev_err(dev->dev, "failed to attach bridge\n"); goto out_bridge; } + priv->panel = panel; priv->connector = panel->connector; priv->bridge = bridge; @@ -138,8 +138,6 @@ static int tve200_modeset_init(struct drm_device *dev) out_bridge: if (panel) drm_panel_bridge_remove(bridge); - else - drm_bridge_remove(bridge); drm_mode_config_cleanup(dev); finish: return ret; @@ -275,8 +273,6 @@ static int tve200_remove(struct platform_device *pdev) drm_fbdev_cma_fini(priv->fbdev); if (priv->panel) drm_panel_bridge_remove(priv->bridge); - else - drm_bridge_remove(priv->bridge); drm_mode_config_cleanup(drm); clk_disable_unprepare(priv->pclk); drm_dev_unref(drm); -- cgit From 44390ef519006b13c8fab9eb13abac909342a585 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 25 Sep 2017 13:25:20 +0300 Subject: drm/tve200: Check for IS_ERR instead of NULL in probe devm_ioremap_resource() returns error pointer, it never returns NULL on error. Fixes: 179c02fe90a4 ("drm/tve200: Add new driver for TVE200") Signed-off-by: Dan Carpenter Signed-off-by: Linus Walleij Link: https://patchwork.freedesktop.org/patch/msgid/20170925102520.a7spymwqqbsczzz2@mwanda --- drivers/gpu/drm/tve200/tve200_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpu/drm/tve200/tve200_drv.c') diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c index eae38b669f0a..6939f7455a2d 100644 --- a/drivers/gpu/drm/tve200/tve200_drv.c +++ b/drivers/gpu/drm/tve200/tve200_drv.c @@ -225,7 +225,7 @@ static int tve200_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->regs = devm_ioremap_resource(dev, res); - if (!priv->regs) { + if (IS_ERR(priv->regs)) { dev_err(dev, "%s failed mmio\n", __func__); ret = -EINVAL; goto clk_disable; -- cgit From 57b8a4bf87806cbb64ed91078f32fbf37f556c5e Mon Sep 17 00:00:00 2001 From: Noralf Trønnes Date: Sun, 24 Sep 2017 14:26:24 +0200 Subject: drm/tve200: Use drm_gem_fb_create() and drm_gem_fb_prepare_fb() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_fb_cma_create() and drm_fb_cma_prepare_fb() are just wrappers now, use drm_gem_fb_create() and drm_gem_fb_prepare_fb() directly. Cc: Linus Walleij Signed-off-by: Noralf Trønnes Reviewed-by: Linus Walleij Reviewed-by: Eric Anholt Link: https://patchwork.freedesktop.org/patch/msgid/1506255985-61113-10-git-send-email-noralf@tronnes.org --- drivers/gpu/drm/tve200/tve200_drv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/gpu/drm/tve200/tve200_drv.c') diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c index 6939f7455a2d..bd6c9454d767 100644 --- a/drivers/gpu/drm/tve200/tve200_drv.c +++ b/drivers/gpu/drm/tve200/tve200_drv.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -55,7 +56,7 @@ #define DRIVER_DESC "DRM module for Faraday TVE200" static const struct drm_mode_config_funcs mode_config_funcs = { - .fb_create = drm_fb_cma_create, + .fb_create = drm_gem_fb_create, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, }; -- cgit