2019-06-01 10:08:57 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2017-08-20 12:05:55 +02:00
/*
* 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 <linux/version.h>
# include <drm/drm_atomic_helper.h>
2019-01-17 22:03:34 +01:00
# include <drm/drm_bridge.h>
2019-06-30 08:18:53 +02:00
# include <drm/drm_drv.h>
2019-01-17 22:03:34 +01:00
# include <drm/drm_fb_cma_helper.h>
# include <drm/drm_fb_helper.h>
2017-08-20 12:05:55 +02:00
# include <drm/drm_gem_cma_helper.h>
2017-09-24 14:26:24 +02:00
# include <drm/drm_gem_framebuffer_helper.h>
2017-09-02 22:07:11 +02:00
# include <drm/drm_of.h>
2019-01-17 22:03:34 +01:00
# include <drm/drm_panel.h>
# include <drm/drm_probe_helper.h>
2019-06-30 08:18:53 +02:00
# include <drm/drm_vblank.h>
2017-08-20 12:05:55 +02:00
# include "tve200_drm.h"
# define DRIVER_DESC "DRM module for Faraday TVE200"
static const struct drm_mode_config_funcs mode_config_funcs = {
2017-09-24 14:26:24 +02:00
. fb_create = drm_gem_fb_create ,
2017-08-20 12:05:55 +02:00
. 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 ;
2017-09-02 22:07:11 +02:00
struct drm_panel * panel ;
struct drm_bridge * bridge ;
2017-08-20 12:05:55 +02:00
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 ;
2017-09-02 22:07:11 +02:00
ret = drm_of_find_panel_or_bridge ( dev - > dev - > of_node ,
0 , 0 , & panel , & bridge ) ;
if ( ret & & ret ! = - ENODEV )
return ret ;
if ( panel ) {
drm/bridge: panel: Infer connector type from panel by default
The drm panel bridge creates a connector using a connector type
explicitly passed by the display controller or bridge driver that
instantiates the panel bridge. Now that drm_panel reports its connector
type, we can use it to avoid passing an explicit (and often incorrect)
connector type to drm_panel_bridge_add() and
devm_drm_panel_bridge_add().
Several drivers report incorrect or unknown connector types to
userspace. Reporting a different type may result in a breakage. For that
reason, rename (devm_)drm_panel_bridge_add() to
(devm_)drm_panel_bridge_add_typed(), and add new
(devm_)drm_panel_bridge_add() functions that use the panel connector
type. Update all callers of (devm_)drm_panel_bridge_add() to the _typed
function, they will be converted one by one after testing.
The panel drivers have been updated with the following Coccinelle
semantic patch, with manual inspection and fixes to indentation.
@@
expression bridge;
expression dev;
expression panel;
identifier type;
@@
(
-bridge = drm_panel_bridge_add(panel, type);
+bridge = drm_panel_bridge_add_typed(panel, type);
|
-bridge = devm_drm_panel_bridge_add(dev, panel, type);
+bridge = devm_drm_panel_bridge_add_typed(dev, panel, type);
)
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20190904132804.29680-3-laurent.pinchart@ideasonboard.com
2019-09-04 16:28:04 +03:00
bridge = drm_panel_bridge_add_typed ( panel ,
DRM_MODE_CONNECTOR_Unknown ) ;
2017-09-02 22:07:11 +02:00
if ( IS_ERR ( bridge ) ) {
ret = PTR_ERR ( bridge ) ;
goto out_bridge ;
}
2017-09-11 00:08:01 +02:00
} 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 ;
2017-09-02 22:07:11 +02:00
}
ret = tve200_display_init ( dev ) ;
2017-08-20 12:05:55 +02:00
if ( ret ) {
2017-09-02 22:07:11 +02:00
dev_err ( dev - > dev , " failed to init display \n " ) ;
goto out_bridge ;
}
2017-09-11 00:08:01 +02:00
ret = drm_simple_display_pipe_attach_bridge ( & priv - > pipe ,
bridge ) ;
if ( ret ) {
dev_err ( dev - > dev , " failed to attach bridge \n " ) ;
2017-09-02 22:07:11 +02:00
goto out_bridge ;
2017-08-20 12:05:55 +02:00
}
2017-09-11 00:08:01 +02:00
2017-09-02 22:07:11 +02:00
priv - > panel = panel ;
2019-12-07 15:03:32 +01:00
priv - > connector = drm_panel_bridge_connector ( bridge ) ;
2017-09-02 22:07:11 +02:00
priv - > bridge = bridge ;
2017-08-20 12:05:55 +02:00
2017-09-02 22:07:11 +02:00
dev_info ( dev - > dev , " attached to panel %s \n " ,
dev_name ( panel - > dev ) ) ;
2017-08-20 12:05:55 +02:00
ret = drm_vblank_init ( dev , 1 ) ;
if ( ret ) {
dev_err ( dev - > dev , " failed to init vblank \n " ) ;
2017-09-02 22:07:11 +02:00
goto out_bridge ;
2017-08-20 12:05:55 +02:00
}
drm_mode_config_reset ( dev ) ;
drm_kms_helper_poll_init ( dev ) ;
goto finish ;
2017-09-02 22:07:11 +02:00
out_bridge :
if ( panel )
drm_panel_bridge_remove ( bridge ) ;
2017-08-20 12:05:55 +02:00
drm_mode_config_cleanup ( dev ) ;
finish :
return ret ;
}
DEFINE_DRM_GEM_CMA_FOPS ( drm_fops ) ;
static struct drm_driver tve200_drm_driver = {
2019-06-17 17:39:24 +02:00
. driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC ,
2017-08-20 12:05:55 +02:00
. ioctls = NULL ,
. fops = & drm_fops ,
. name = " tve200 " ,
. desc = DRIVER_DESC ,
. date = " 20170703 " ,
. major = 1 ,
. minor = 0 ,
. patchlevel = 0 ,
2020-06-05 09:32:44 +02:00
DRM_GEM_CMA_DRIVER_OPS ,
2017-08-20 12:05:55 +02:00
} ;
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 ) ;
2017-09-25 13:25:20 +03:00
if ( IS_ERR ( priv - > regs ) ) {
2017-08-20 12:05:55 +02:00
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 ;
2018-09-08 15:46:45 +02:00
/*
* Passing in 16 here will make the RGB565 mode the default
* Passing in 32 will use XRGB8888 mode
*/
drm_fbdev_generic_setup ( drm , 16 ) ;
2017-08-20 12:05:55 +02:00
return 0 ;
clk_disable :
clk_disable_unprepare ( priv - > pclk ) ;
dev_unref :
2018-11-15 23:16:23 +01:00
drm_dev_put ( drm ) ;
2017-08-20 12:05:55 +02:00
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 ) ;
2017-09-02 22:07:11 +02:00
if ( priv - > panel )
drm_panel_bridge_remove ( priv - > bridge ) ;
2017-08-20 12:05:55 +02:00
drm_mode_config_cleanup ( drm ) ;
clk_disable_unprepare ( priv - > pclk ) ;
2018-11-15 23:16:23 +01:00
drm_dev_put ( drm ) ;
2017-08-20 12:05:55 +02:00
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@linaro.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;