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 ,
} ;
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 = {
/* R8A7791 has one RGB output, one LVDS output and one
* ( currently unsupported ) TCON output .
*/
[ RCAR_DU_OUTPUT_DPAD0 ] = {
. possible_crtcs = BIT ( 1 ) ,
. 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 ,
} ;
static const struct platform_device_id rcar_du_id_table [ ] = {
{ " rcar-du-r8a7779 " , ( kernel_ulong_t ) & rcar_du_r8a7779_info } ,
{ " rcar-du-r8a7790 " , ( kernel_ulong_t ) & rcar_du_r8a7790_info } ,
{ " rcar-du-r8a7791 " , ( kernel_ulong_t ) & rcar_du_r8a7791_info } ,
{ }
} ;
MODULE_DEVICE_TABLE ( platform , rcar_du_id_table ) ;
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 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , rcar_du_of_table ) ;
2013-06-19 13:54:11 +02:00
/* -----------------------------------------------------------------------------
* DRM operations
*/
static int rcar_du_unload ( struct drm_device * dev )
{
2013-03-14 22:45:22 +01:00
struct rcar_du_device * rcdu = dev - > dev_private ;
if ( rcdu - > fbdev )
drm_fbdev_cma_fini ( rcdu - > fbdev ) ;
2013-06-19 13:54:11 +02:00
drm_kms_helper_poll_fini ( dev ) ;
drm_mode_config_cleanup ( dev ) ;
drm_vblank_cleanup ( dev ) ;
2013-06-14 14:15:01 +02:00
dev - > irq_enabled = 0 ;
2013-06-19 13:54:11 +02:00
dev - > dev_private = NULL ;
return 0 ;
}
static int rcar_du_load ( struct drm_device * dev , unsigned long flags )
{
struct platform_device * pdev = dev - > platformdev ;
2014-01-21 15:57:26 +01:00
struct device_node * np = pdev - > dev . of_node ;
2013-06-19 13:54:11 +02:00
struct rcar_du_device * rcdu ;
struct resource * mem ;
int ret ;
2014-09-17 02:07:52 +03:00
if ( np = = NULL ) {
2013-06-19 13:54:11 +02:00
dev_err ( dev - > dev , " no platform data \n " ) ;
return - ENODEV ;
}
rcdu = devm_kzalloc ( & pdev - > dev , sizeof ( * rcdu ) , GFP_KERNEL ) ;
if ( rcdu = = NULL ) {
dev_err ( dev - > dev , " failed to allocate private data \n " ) ;
return - ENOMEM ;
}
2015-02-23 01:02:15 +02:00
init_waitqueue_head ( & rcdu - > commit . wait ) ;
2013-06-19 13:54:11 +02:00
rcdu - > dev = & pdev - > dev ;
2014-01-21 15:57:26 +01:00
rcdu - > info = np ? of_match_device ( rcar_du_of_table , rcdu - > dev ) - > data
: ( void * ) platform_get_device_id ( pdev ) - > driver_data ;
2013-06-19 13:54:11 +02:00
rcdu - > ddev = dev ;
dev - > dev_private = rcdu ;
2013-06-14 14:15:01 +02:00
/* I/O resources */
2013-06-19 13:54:11 +02:00
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-06-17 02:29:07 +02:00
rcdu - > mmio = devm_ioremap_resource ( & pdev - > dev , mem ) ;
if ( IS_ERR ( rcdu - > mmio ) )
return PTR_ERR ( rcdu - > mmio ) ;
2013-06-19 13:54:11 +02:00
2015-02-18 13:14:46 +02:00
/* Initialize vertical blanking interrupts handling. Start with vblank
* disabled for all CRTCs .
*/
ret = drm_vblank_init ( dev , ( 1 < < rcdu - > info - > num_crtcs ) - 1 ) ;
2013-06-19 13:54:11 +02:00
if ( ret < 0 ) {
2015-02-18 13:14:46 +02:00
dev_err ( & pdev - > dev , " failed to initialize vblank \n " ) ;
2013-06-19 13:54:11 +02:00
goto done ;
}
2015-02-18 13:14:46 +02:00
/* DRM/KMS objects */
ret = rcar_du_modeset_init ( rcdu ) ;
2013-06-19 13:54:11 +02:00
if ( ret < 0 ) {
2015-05-14 01:08:34 +03:00
dev_err ( & pdev - > dev , " failed to initialize DRM/KMS (%d) \n " , ret ) ;
2013-06-19 13:54:11 +02:00
goto done ;
}
2013-06-14 14:15:01 +02:00
dev - > irq_enabled = 1 ;
2013-06-19 13:54:11 +02:00
platform_set_drvdata ( pdev , rcdu ) ;
done :
if ( ret )
rcar_du_unload ( dev ) ;
return ret ;
}
static void rcar_du_preclose ( struct drm_device * dev , struct drm_file * file )
{
struct rcar_du_device * rcdu = dev - > dev_private ;
unsigned int i ;
2013-06-16 22:22:23 +02:00
for ( i = 0 ; i < rcdu - > num_crtcs ; + + i )
2013-06-19 13:54:11 +02:00
rcar_du_crtc_cancel_page_flip ( & rcdu - > crtcs [ i ] , file ) ;
}
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
}
static int rcar_du_enable_vblank ( struct drm_device * dev , int crtc )
{
struct rcar_du_device * rcdu = dev - > dev_private ;
rcar_du_crtc_enable_vblank ( & rcdu - > crtcs [ crtc ] , true ) ;
return 0 ;
}
static void rcar_du_disable_vblank ( struct drm_device * dev , int crtc )
{
struct rcar_du_device * rcdu = dev - > dev_private ;
rcar_du_crtc_enable_vblank ( & rcdu - > crtcs [ crtc ] , false ) ;
}
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-06-19 13:54:11 +02:00
. load = rcar_du_load ,
. unload = rcar_du_unload ,
. preclose = rcar_du_preclose ,
2013-03-14 22:45:22 +01:00
. lastclose = rcar_du_lastclose ,
2014-08-29 12:12:43 +02:00
. set_busid = drm_platform_set_busid ,
2013-06-19 13:54:11 +02:00
. get_vblank_counter = drm_vblank_count ,
. 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
*/
static int rcar_du_probe ( struct platform_device * pdev )
{
return drm_platform_init ( & rcar_du_driver , pdev ) ;
}
static int rcar_du_remove ( struct platform_device * pdev )
{
2013-12-11 11:34:22 +01:00
struct rcar_du_device * rcdu = platform_get_drvdata ( pdev ) ;
drm_put_dev ( rcdu - > ddev ) ;
2013-06-19 13:54:11 +02:00
return 0 ;
}
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
} ,
2013-06-14 13:38:33 +02:00
. id_table = rcar_du_id_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 " ) ;