diff options
Diffstat (limited to 'drivers/gpu/drm/tve200/tve200_drv.c')
-rw-r--r-- | drivers/gpu/drm/tve200/tve200_drv.c | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c new file mode 100644 index 000000000..04db72e3f --- /dev/null +++ b/drivers/gpu/drm/tve200/tve200_drv.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> + * Parts of this file were based on sources as follows: + * + * Copyright (C) 2006-2008 Intel Corporation + * Copyright (C) 2007 Amos Lee <amos_lee@storlinksemi.com> + * Copyright (C) 2007 Dave Airlie <airlied@linux.ie> + * Copyright (C) 2011 Texas Instruments + * Copyright (C) 2017 Eric Anholt + */ + +/** + * 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 <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_module.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#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_gem_fb_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; + struct drm_panel *panel; + struct drm_bridge *bridge; + int ret; + + 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 = 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_typed(panel, + DRM_MODE_CONNECTOR_Unknown); + if (IS_ERR(bridge)) { + 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"); + ret = -EINVAL; + goto out_bridge; + } + + ret = tve200_display_init(dev); + if (ret) { + dev_err(dev->dev, "failed to init display\n"); + goto out_bridge; + } + + 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 = drm_panel_bridge_connector(bridge); + priv->bridge = bridge; + + 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_bridge; + } + + drm_mode_config_reset(dev); + drm_kms_helper_poll_init(dev); + + goto finish; + +out_bridge: + if (panel) + drm_panel_bridge_remove(bridge); + drm_mode_config_cleanup(dev); +finish: + return ret; +} + +DEFINE_DRM_GEM_DMA_FOPS(drm_fops); + +static const struct drm_driver tve200_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .ioctls = NULL, + .fops = &drm_fops, + .name = "tve200", + .desc = DRIVER_DESC, + .date = "20170703", + .major = 1, + .minor = 0, + .patchlevel = 0, + DRM_GEM_DMA_DRIVER_OPS, +}; + +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 (IS_ERR(priv->regs)) { + dev_err(dev, "%s failed mmio\n", __func__); + ret = -EINVAL; + goto clk_disable; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + 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; + + /* + * Passing in 16 here will make the RGB565 mode the default + * Passing in 32 will use XRGB8888 mode + */ + drm_fbdev_generic_setup(drm, 16); + + return 0; + +clk_disable: + clk_disable_unprepare(priv->pclk); +dev_unref: + drm_dev_put(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->panel) + drm_panel_bridge_remove(priv->bridge); + drm_mode_config_cleanup(drm); + clk_disable_unprepare(priv->pclk); + drm_dev_put(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, +}; +drm_module_platform_driver(tve200_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_LICENSE("GPL"); |