2013-06-19 13:54:11 +02:00
/*
* rcar_du_drv . c - - R - Car Display Unit DRM driver
*
2014-02-06 18:13:52 +01:00
* Copyright ( C ) 2013 - 2014 Renesas Electronics Corporation
2013-06-19 13:54:11 +02:00
*
* Contact : Laurent Pinchart ( laurent . pinchart @ ideasonboard . com )
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*/
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/mm.h>
# include <linux/module.h>
2014-01-21 15:57:26 +01:00
# include <linux/of_device.h>
2013-06-19 13:54:11 +02:00
# include <linux/platform_device.h>
# include <linux/pm.h>
# include <linux/slab.h>
2015-02-23 01:02:15 +02:00
# include <linux/wait.h>
2013-06-19 13:54:11 +02:00
# include <drm/drmP.h>
# include <drm/drm_crtc_helper.h>
2013-03-14 22:45:22 +01:00
# include <drm/drm_fb_cma_helper.h>
2013-06-19 13:54:11 +02:00
# include <drm/drm_gem_cma_helper.h>
# include "rcar_du_crtc.h"
# include "rcar_du_drv.h"
# include "rcar_du_kms.h"
# include "rcar_du_regs.h"
2014-01-21 15:57:26 +01:00
/* -----------------------------------------------------------------------------
* Device Information
*/
static const struct rcar_du_device_info rcar_du_r8a7779_info = {
. features = 0 ,
. num_crtcs = 2 ,
. routes = {
/* R8A7779 has two RGB outputs and one (currently unsupported)
* TCON output .
*/
[ RCAR_DU_OUTPUT_DPAD0 ] = {
. possible_crtcs = BIT ( 0 ) ,
. encoder_type = DRM_MODE_ENCODER_NONE ,
. port = 0 ,
} ,
[ RCAR_DU_OUTPUT_DPAD1 ] = {
. possible_crtcs = BIT ( 1 ) | BIT ( 0 ) ,
. encoder_type = DRM_MODE_ENCODER_NONE ,
. port = 1 ,
} ,
} ,
. num_lvds = 0 ,
} ;
static const struct rcar_du_device_info rcar_du_r8a7790_info = {
2014-12-09 00:21:12 +02:00
. features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS ,
2014-01-21 15:57:26 +01:00
. quirks = RCAR_DU_QUIRK_ALIGN_128B | RCAR_DU_QUIRK_LVDS_LANES ,
. num_crtcs = 3 ,
. routes = {
/* R8A7790 has one RGB output, two LVDS outputs and one
* ( currently unsupported ) TCON output .
*/
[ RCAR_DU_OUTPUT_DPAD0 ] = {
. possible_crtcs = BIT ( 2 ) | BIT ( 1 ) | BIT ( 0 ) ,
. encoder_type = DRM_MODE_ENCODER_NONE ,
. port = 0 ,
} ,
[ RCAR_DU_OUTPUT_LVDS0 ] = {
. possible_crtcs = BIT ( 0 ) ,
. encoder_type = DRM_MODE_ENCODER_LVDS ,
. port = 1 ,
} ,
[ RCAR_DU_OUTPUT_LVDS1 ] = {
. possible_crtcs = BIT ( 2 ) | BIT ( 1 ) ,
. encoder_type = DRM_MODE_ENCODER_LVDS ,
. port = 2 ,
} ,
} ,
. num_lvds = 2 ,
} ;
2015-07-17 10:44:33 +03:00
/* M2-W (r8a7791) and M2-N (r8a7793) are identical */
2014-01-21 15:57:26 +01:00
static const struct rcar_du_device_info rcar_du_r8a7791_info = {
2014-12-09 00:21:12 +02:00
. features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS ,
2014-01-21 15:57:26 +01:00
. num_crtcs = 2 ,
. routes = {
2015-07-17 10:44:33 +03:00
/* R8A779[13] has one RGB output, one LVDS output and one
2014-01-21 15:57:26 +01:00
* ( currently unsupported ) TCON output .
*/
[ RCAR_DU_OUTPUT_DPAD0 ] = {
2015-04-28 15:26:33 +03:00
. possible_crtcs = BIT ( 1 ) | BIT ( 0 ) ,
2014-01-21 15:57:26 +01:00
. encoder_type = DRM_MODE_ENCODER_NONE ,
. port = 0 ,
} ,
[ RCAR_DU_OUTPUT_LVDS0 ] = {
. possible_crtcs = BIT ( 0 ) ,
. encoder_type = DRM_MODE_ENCODER_LVDS ,
. port = 1 ,
} ,
} ,
. num_lvds = 1 ,
} ;
2015-07-17 10:44:33 +03:00
static const struct rcar_du_device_info rcar_du_r8a7794_info = {
. features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS ,
. num_crtcs = 2 ,
. routes = {
/* R8A7794 has two RGB outputs and one (currently unsupported)
* TCON output .
*/
[ RCAR_DU_OUTPUT_DPAD0 ] = {
. possible_crtcs = BIT ( 0 ) ,
. encoder_type = DRM_MODE_ENCODER_NONE ,
. port = 0 ,
} ,
[ RCAR_DU_OUTPUT_DPAD1 ] = {
. possible_crtcs = BIT ( 1 ) ,
. encoder_type = DRM_MODE_ENCODER_NONE ,
. port = 1 ,
} ,
} ,
. num_lvds = 0 ,
} ;
2014-01-21 15:57:26 +01:00
static const struct of_device_id rcar_du_of_table [ ] = {
{ . compatible = " renesas,du-r8a7779 " , . data = & rcar_du_r8a7779_info } ,
{ . compatible = " renesas,du-r8a7790 " , . data = & rcar_du_r8a7790_info } ,
{ . compatible = " renesas,du-r8a7791 " , . data = & rcar_du_r8a7791_info } ,
2015-07-17 10:44:33 +03:00
{ . compatible = " renesas,du-r8a7793 " , . data = & rcar_du_r8a7791_info } ,
2015-07-17 10:44:33 +03:00
{ . compatible = " renesas,du-r8a7794 " , . data = & rcar_du_r8a7794_info } ,
2014-01-21 15:57:26 +01:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , rcar_du_of_table ) ;
2013-06-19 13:54:11 +02:00
/* -----------------------------------------------------------------------------
* DRM operations
*/
2013-03-14 22:45:22 +01:00
static void rcar_du_lastclose ( struct drm_device * dev )
2013-06-19 13:54:11 +02:00
{
struct rcar_du_device * rcdu = dev - > dev_private ;
2013-03-14 22:45:22 +01:00
drm_fbdev_cma_restore_mode ( rcdu - > fbdev ) ;
2013-06-19 13:54:11 +02:00
}
2015-09-24 18:35:31 +02:00
static int rcar_du_enable_vblank ( struct drm_device * dev , unsigned int pipe )
2013-06-19 13:54:11 +02:00
{
struct rcar_du_device * rcdu = dev - > dev_private ;
2015-09-24 18:35:31 +02:00
rcar_du_crtc_enable_vblank ( & rcdu - > crtcs [ pipe ] , true ) ;
2013-06-19 13:54:11 +02:00
return 0 ;
}
2015-09-24 18:35:31 +02:00
static void rcar_du_disable_vblank ( struct drm_device * dev , unsigned int pipe )
2013-06-19 13:54:11 +02:00
{
struct rcar_du_device * rcdu = dev - > dev_private ;
2015-09-24 18:35:31 +02:00
rcar_du_crtc_enable_vblank ( & rcdu - > crtcs [ pipe ] , false ) ;
2013-06-19 13:54:11 +02:00
}
static const struct file_operations rcar_du_fops = {
. owner = THIS_MODULE ,
. open = drm_open ,
. release = drm_release ,
. unlocked_ioctl = drm_ioctl ,
# ifdef CONFIG_COMPAT
. compat_ioctl = drm_compat_ioctl ,
# endif
. poll = drm_poll ,
. read = drm_read ,
. llseek = no_llseek ,
. mmap = drm_gem_cma_mmap ,
} ;
static struct drm_driver rcar_du_driver = {
2015-02-26 21:22:10 +02:00
. driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME
| DRIVER_ATOMIC ,
2013-03-14 22:45:22 +01:00
. lastclose = rcar_du_lastclose ,
2015-09-30 16:46:48 +03:00
. get_vblank_counter = drm_vblank_no_hw_counter ,
2013-06-19 13:54:11 +02:00
. enable_vblank = rcar_du_enable_vblank ,
. disable_vblank = rcar_du_disable_vblank ,
. gem_free_object = drm_gem_cma_free_object ,
. gem_vm_ops = & drm_gem_cma_vm_ops ,
. prime_handle_to_fd = drm_gem_prime_handle_to_fd ,
. prime_fd_to_handle = drm_gem_prime_fd_to_handle ,
2013-07-10 15:23:35 +02:00
. 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 ,
2013-07-04 20:05:51 +02:00
. dumb_create = rcar_du_dumb_create ,
2013-06-19 13:54:11 +02:00
. dumb_map_offset = drm_gem_cma_dumb_map_offset ,
2013-07-16 09:12:04 +02:00
. dumb_destroy = drm_gem_dumb_destroy ,
2013-06-19 13:54:11 +02:00
. fops = & rcar_du_fops ,
. name = " rcar-du " ,
. desc = " Renesas R-Car Display Unit " ,
. date = " 20130110 " ,
. major = 1 ,
. minor = 0 ,
} ;
/* -----------------------------------------------------------------------------
* Power management
*/
2014-07-13 12:18:58 +01:00
# ifdef CONFIG_PM_SLEEP
2013-06-19 13:54:11 +02:00
static int rcar_du_pm_suspend ( struct device * dev )
{
struct rcar_du_device * rcdu = dev_get_drvdata ( dev ) ;
drm_kms_helper_poll_disable ( rcdu - > ddev ) ;
/* TODO Suspend the CRTC */
return 0 ;
}
static int rcar_du_pm_resume ( struct device * dev )
{
struct rcar_du_device * rcdu = dev_get_drvdata ( dev ) ;
/* TODO Resume the CRTC */
drm_kms_helper_poll_enable ( rcdu - > ddev ) ;
return 0 ;
}
# endif
static const struct dev_pm_ops rcar_du_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( rcar_du_pm_suspend , rcar_du_pm_resume )
} ;
/* -----------------------------------------------------------------------------
* Platform driver
*/
2015-09-28 18:39:53 +03:00
static int rcar_du_remove ( struct platform_device * pdev )
2013-06-19 13:54:11 +02:00
{
2015-09-28 18:39:53 +03:00
struct rcar_du_device * rcdu = platform_get_drvdata ( pdev ) ;
struct drm_device * ddev = rcdu - > ddev ;
mutex_lock ( & ddev - > mode_config . mutex ) ;
drm_connector_unplug_all ( ddev ) ;
mutex_unlock ( & ddev - > mode_config . mutex ) ;
drm_dev_unregister ( ddev ) ;
if ( rcdu - > fbdev )
drm_fbdev_cma_fini ( rcdu - > fbdev ) ;
drm_kms_helper_poll_fini ( ddev ) ;
drm_mode_config_cleanup ( ddev ) ;
drm_vblank_cleanup ( ddev ) ;
drm_dev_unref ( ddev ) ;
return 0 ;
2013-06-19 13:54:11 +02:00
}
2015-09-28 18:39:53 +03:00
static int rcar_du_probe ( struct platform_device * pdev )
2013-06-19 13:54:11 +02:00
{
2015-09-28 18:39:53 +03:00
struct device_node * np = pdev - > dev . of_node ;
struct rcar_du_device * rcdu ;
struct drm_connector * connector ;
struct drm_device * ddev ;
struct resource * mem ;
int ret ;
if ( np = = NULL ) {
dev_err ( & pdev - > dev , " no device tree node \n " ) ;
return - ENODEV ;
}
/* Allocate and initialize the DRM and R-Car device structures. */
rcdu = devm_kzalloc ( & pdev - > dev , sizeof ( * rcdu ) , GFP_KERNEL ) ;
if ( rcdu = = NULL )
return - ENOMEM ;
init_waitqueue_head ( & rcdu - > commit . wait ) ;
rcdu - > dev = & pdev - > dev ;
rcdu - > info = of_match_device ( rcar_du_of_table , rcdu - > dev ) - > data ;
ddev = drm_dev_alloc ( & rcar_du_driver , & pdev - > dev ) ;
if ( ! ddev )
return - ENOMEM ;
drm_dev_set_unique ( ddev , dev_name ( & pdev - > dev ) ) ;
rcdu - > ddev = ddev ;
ddev - > dev_private = rcdu ;
2013-12-11 11:34:22 +01:00
2015-09-28 18:39:53 +03:00
platform_set_drvdata ( pdev , rcdu ) ;
/* I/O resources */
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
rcdu - > mmio = devm_ioremap_resource ( & pdev - > dev , mem ) ;
if ( IS_ERR ( rcdu - > mmio ) ) {
ret = PTR_ERR ( rcdu - > mmio ) ;
goto error ;
}
/* Initialize vertical blanking interrupts handling. Start with vblank
* disabled for all CRTCs .
*/
ret = drm_vblank_init ( ddev , ( 1 < < rcdu - > info - > num_crtcs ) - 1 ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to initialize vblank \n " ) ;
goto error ;
}
/* DRM/KMS objects */
ret = rcar_du_modeset_init ( rcdu ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to initialize DRM/KMS (%d) \n " , ret ) ;
goto error ;
}
ddev - > irq_enabled = 1 ;
/* Register the DRM device with the core and the connectors with
* sysfs .
*/
ret = drm_dev_register ( ddev , 0 ) ;
if ( ret )
goto error ;
mutex_lock ( & ddev - > mode_config . mutex ) ;
drm_for_each_connector ( connector , ddev ) {
ret = drm_connector_register ( connector ) ;
if ( ret < 0 )
break ;
}
mutex_unlock ( & ddev - > mode_config . mutex ) ;
if ( ret < 0 )
goto error ;
DRM_INFO ( " Device %s probed \n " , dev_name ( & pdev - > dev ) ) ;
2013-06-19 13:54:11 +02:00
return 0 ;
2015-09-28 18:39:53 +03:00
error :
rcar_du_remove ( pdev ) ;
return ret ;
2013-06-19 13:54:11 +02:00
}
static struct platform_driver rcar_du_platform_driver = {
. probe = rcar_du_probe ,
. remove = rcar_du_remove ,
. driver = {
. name = " rcar-du " ,
. pm = & rcar_du_pm_ops ,
2014-01-21 15:57:26 +01:00
. of_match_table = rcar_du_of_table ,
2013-06-19 13:54:11 +02:00
} ,
} ;
module_platform_driver ( rcar_du_platform_driver ) ;
MODULE_AUTHOR ( " Laurent Pinchart <laurent.pinchart@ideasonboard.com> " ) ;
MODULE_DESCRIPTION ( " Renesas R-Car Display Unit DRM Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;