2012-04-26 13:53:59 +02:00
/*
* shmob_drm_drv . c - - SH Mobile DRM driver
*
2014-02-06 18:13:52 +01:00
* Copyright ( C ) 2012 Renesas Electronics Corporation
2012-04-26 13:53:59 +02:00
*
* 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>
# include <linux/platform_device.h>
# include <linux/pm.h>
# include <linux/slab.h>
# include <drm/drmP.h>
# include <drm/drm_crtc_helper.h>
# include <drm/drm_gem_cma_helper.h>
# include "shmob_drm_crtc.h"
# include "shmob_drm_drv.h"
# include "shmob_drm_kms.h"
# include "shmob_drm_plane.h"
# include "shmob_drm_regs.h"
/* -----------------------------------------------------------------------------
* Hardware initialization
*/
2012-12-21 15:09:25 -08:00
static int shmob_drm_init_interface ( struct shmob_drm_device * sdev )
2012-04-26 13:53:59 +02:00
{
static const u32 ldmt1r [ ] = {
[ SHMOB_DRM_IFACE_RGB8 ] = LDMT1R_MIFTYP_RGB8 ,
[ SHMOB_DRM_IFACE_RGB9 ] = LDMT1R_MIFTYP_RGB9 ,
[ SHMOB_DRM_IFACE_RGB12A ] = LDMT1R_MIFTYP_RGB12A ,
[ SHMOB_DRM_IFACE_RGB12B ] = LDMT1R_MIFTYP_RGB12B ,
[ SHMOB_DRM_IFACE_RGB16 ] = LDMT1R_MIFTYP_RGB16 ,
[ SHMOB_DRM_IFACE_RGB18 ] = LDMT1R_MIFTYP_RGB18 ,
[ SHMOB_DRM_IFACE_RGB24 ] = LDMT1R_MIFTYP_RGB24 ,
[ SHMOB_DRM_IFACE_YUV422 ] = LDMT1R_MIFTYP_YCBCR ,
[ SHMOB_DRM_IFACE_SYS8A ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8A ,
[ SHMOB_DRM_IFACE_SYS8B ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8B ,
[ SHMOB_DRM_IFACE_SYS8C ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8C ,
[ SHMOB_DRM_IFACE_SYS8D ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8D ,
[ SHMOB_DRM_IFACE_SYS9 ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS9 ,
[ SHMOB_DRM_IFACE_SYS12 ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS12 ,
[ SHMOB_DRM_IFACE_SYS16A ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16A ,
[ SHMOB_DRM_IFACE_SYS16B ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16B ,
[ SHMOB_DRM_IFACE_SYS16C ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16C ,
[ SHMOB_DRM_IFACE_SYS18 ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS18 ,
[ SHMOB_DRM_IFACE_SYS24 ] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS24 ,
} ;
if ( sdev - > pdata - > iface . interface > = ARRAY_SIZE ( ldmt1r ) ) {
dev_err ( sdev - > dev , " invalid interface type %u \n " ,
sdev - > pdata - > iface . interface ) ;
return - EINVAL ;
}
sdev - > ldmt1r = ldmt1r [ sdev - > pdata - > iface . interface ] ;
return 0 ;
}
2012-12-21 15:09:25 -08:00
static int shmob_drm_setup_clocks ( struct shmob_drm_device * sdev ,
2012-04-26 13:53:59 +02:00
enum shmob_drm_clk_source clksrc )
{
struct clk * clk ;
char * clkname ;
switch ( clksrc ) {
case SHMOB_DRM_CLK_BUS :
clkname = " bus_clk " ;
sdev - > lddckr = LDDCKR_ICKSEL_BUS ;
break ;
case SHMOB_DRM_CLK_PERIPHERAL :
clkname = " peripheral_clk " ;
sdev - > lddckr = LDDCKR_ICKSEL_MIPI ;
break ;
case SHMOB_DRM_CLK_EXTERNAL :
clkname = NULL ;
sdev - > lddckr = LDDCKR_ICKSEL_HDMI ;
break ;
default :
return - EINVAL ;
}
2013-04-25 12:12:33 +02:00
clk = devm_clk_get ( sdev - > dev , clkname ) ;
2012-04-26 13:53:59 +02:00
if ( IS_ERR ( clk ) ) {
dev_err ( sdev - > dev , " cannot get dot clock %s \n " , clkname ) ;
return PTR_ERR ( clk ) ;
}
sdev - > clock = clk ;
return 0 ;
}
/* -----------------------------------------------------------------------------
* DRM operations
*/
static int shmob_drm_unload ( struct drm_device * dev )
{
drm_kms_helper_poll_fini ( dev ) ;
drm_mode_config_cleanup ( dev ) ;
drm_vblank_cleanup ( dev ) ;
drm_irq_uninstall ( dev ) ;
dev - > dev_private = NULL ;
return 0 ;
}
static int shmob_drm_load ( struct drm_device * dev , unsigned long flags )
{
struct shmob_drm_platform_data * pdata = dev - > dev - > platform_data ;
struct platform_device * pdev = dev - > platformdev ;
struct shmob_drm_device * sdev ;
struct resource * res ;
unsigned int i ;
int ret ;
if ( pdata = = NULL ) {
dev_err ( dev - > dev , " no platform data \n " ) ;
return - EINVAL ;
}
2013-04-25 12:12:33 +02:00
sdev = devm_kzalloc ( & pdev - > dev , sizeof ( * sdev ) , GFP_KERNEL ) ;
2012-04-26 13:53:59 +02:00
if ( sdev = = NULL ) {
dev_err ( dev - > dev , " failed to allocate private data \n " ) ;
return - ENOMEM ;
}
sdev - > dev = & pdev - > dev ;
sdev - > pdata = pdata ;
spin_lock_init ( & sdev - > irq_lock ) ;
sdev - > ddev = dev ;
dev - > dev_private = sdev ;
/* I/O resources and clocks */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( res = = NULL ) {
dev_err ( & pdev - > dev , " failed to get memory resource \n " ) ;
2013-04-25 12:12:33 +02:00
return - EINVAL ;
2012-04-26 13:53:59 +02:00
}
2013-04-25 12:12:33 +02:00
sdev - > mmio = devm_ioremap_nocache ( & pdev - > dev , res - > start ,
resource_size ( res ) ) ;
2012-04-26 13:53:59 +02:00
if ( sdev - > mmio = = NULL ) {
dev_err ( & pdev - > dev , " failed to remap memory resource \n " ) ;
2013-04-25 12:12:33 +02:00
return - ENOMEM ;
2012-04-26 13:53:59 +02:00
}
ret = shmob_drm_setup_clocks ( sdev , pdata - > clk_source ) ;
if ( ret < 0 )
2013-04-25 12:12:33 +02:00
return ret ;
2012-04-26 13:53:59 +02:00
ret = shmob_drm_init_interface ( sdev ) ;
if ( ret < 0 )
2013-04-25 12:12:33 +02:00
return ret ;
2012-04-26 13:53:59 +02:00
ret = shmob_drm_modeset_init ( sdev ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to initialize mode setting \n " ) ;
2013-04-25 12:12:33 +02:00
return ret ;
2012-04-26 13:53:59 +02:00
}
for ( i = 0 ; i < 4 ; + + i ) {
ret = shmob_drm_plane_create ( sdev , i ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to create plane %u \n " , i ) ;
goto done ;
}
}
ret = drm_vblank_init ( dev , 1 ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to initialize vblank \n " ) ;
goto done ;
}
2013-11-03 21:09:27 +01:00
ret = drm_irq_install ( dev , platform_get_irq ( dev - > platformdev , 0 ) ) ;
2012-04-26 13:53:59 +02:00
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to install IRQ handler \n " ) ;
goto done ;
}
2012-10-15 18:03:42 +00:00
platform_set_drvdata ( pdev , sdev ) ;
2012-04-26 13:53:59 +02:00
done :
if ( ret )
shmob_drm_unload ( dev ) ;
return ret ;
}
static irqreturn_t shmob_drm_irq ( int irq , void * arg )
{
struct drm_device * dev = arg ;
struct shmob_drm_device * sdev = dev - > dev_private ;
unsigned long flags ;
u32 status ;
/* Acknowledge interrupts. Putting interrupt enable and interrupt flag
* bits in the same register is really brain - dead design and requires
* taking a spinlock .
*/
spin_lock_irqsave ( & sdev - > irq_lock , flags ) ;
status = lcdc_read ( sdev , LDINTR ) ;
lcdc_write ( sdev , LDINTR , status ^ LDINTR_STATUS_MASK ) ;
spin_unlock_irqrestore ( & sdev - > irq_lock , flags ) ;
if ( status & LDINTR_VES ) {
drm_handle_vblank ( dev , 0 ) ;
shmob_drm_crtc_finish_page_flip ( & sdev - > crtc ) ;
}
return IRQ_HANDLED ;
}
2015-09-24 18:35:31 +02:00
static int shmob_drm_enable_vblank ( struct drm_device * dev , unsigned int pipe )
2012-04-26 13:53:59 +02:00
{
struct shmob_drm_device * sdev = dev - > dev_private ;
shmob_drm_crtc_enable_vblank ( sdev , true ) ;
return 0 ;
}
2015-09-24 18:35:31 +02:00
static void shmob_drm_disable_vblank ( struct drm_device * dev , unsigned int pipe )
2012-04-26 13:53:59 +02:00
{
struct shmob_drm_device * sdev = dev - > dev_private ;
shmob_drm_crtc_enable_vblank ( sdev , false ) ;
}
static const struct file_operations shmob_drm_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 shmob_drm_driver = {
2013-06-08 09:33:27 +02:00
. driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET
| DRIVER_PRIME ,
2012-04-26 13:53:59 +02:00
. load = shmob_drm_load ,
. unload = shmob_drm_unload ,
. irq_handler = shmob_drm_irq ,
2015-09-30 16:46:48 +03:00
. get_vblank_counter = drm_vblank_no_hw_counter ,
2012-04-26 13:53:59 +02:00
. enable_vblank = shmob_drm_enable_vblank ,
. disable_vblank = shmob_drm_disable_vblank ,
2016-05-30 19:53:04 +02:00
. gem_free_object_unlocked = drm_gem_cma_free_object ,
2012-04-26 13:53:59 +02:00
. gem_vm_ops = & drm_gem_cma_vm_ops ,
2013-06-08 09:33:27 +02:00
. prime_handle_to_fd = drm_gem_prime_handle_to_fd ,
. prime_fd_to_handle = drm_gem_prime_fd_to_handle ,
2013-07-10 15:28:15 +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 ,
2012-04-26 13:53:59 +02:00
. dumb_create = drm_gem_cma_dumb_create ,
. dumb_map_offset = drm_gem_cma_dumb_map_offset ,
2013-07-16 09:12:04 +02:00
. dumb_destroy = drm_gem_dumb_destroy ,
2012-04-26 13:53:59 +02:00
. fops = & shmob_drm_fops ,
. name = " shmob-drm " ,
. desc = " Renesas SH Mobile DRM " ,
. date = " 20120424 " ,
. major = 1 ,
. minor = 0 ,
} ;
/* -----------------------------------------------------------------------------
* Power management
*/
2014-07-13 12:19:03 +01:00
# ifdef CONFIG_PM_SLEEP
2012-04-26 13:53:59 +02:00
static int shmob_drm_pm_suspend ( struct device * dev )
{
2012-10-15 18:03:42 +00:00
struct shmob_drm_device * sdev = dev_get_drvdata ( dev ) ;
2012-04-26 13:53:59 +02:00
2012-10-15 18:03:42 +00:00
drm_kms_helper_poll_disable ( sdev - > ddev ) ;
2012-04-26 13:53:59 +02:00
shmob_drm_crtc_suspend ( & sdev - > crtc ) ;
return 0 ;
}
static int shmob_drm_pm_resume ( struct device * dev )
{
2012-10-15 18:03:42 +00:00
struct shmob_drm_device * sdev = dev_get_drvdata ( dev ) ;
2012-04-26 13:53:59 +02:00
2012-12-02 01:36:59 +01:00
drm_modeset_lock_all ( sdev - > ddev ) ;
2012-04-26 13:53:59 +02:00
shmob_drm_crtc_resume ( & sdev - > crtc ) ;
2012-12-02 01:36:59 +01:00
drm_modeset_unlock_all ( sdev - > ddev ) ;
2012-04-26 13:53:59 +02:00
drm_kms_helper_poll_enable ( sdev - > ddev ) ;
return 0 ;
}
# endif
static const struct dev_pm_ops shmob_drm_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( shmob_drm_pm_suspend , shmob_drm_pm_resume )
} ;
/* -----------------------------------------------------------------------------
* Platform driver
*/
2012-12-21 15:09:25 -08:00
static int shmob_drm_probe ( struct platform_device * pdev )
2012-04-26 13:53:59 +02:00
{
return drm_platform_init ( & shmob_drm_driver , pdev ) ;
}
2012-12-21 15:09:25 -08:00
static int shmob_drm_remove ( struct platform_device * pdev )
2012-04-26 13:53:59 +02:00
{
2013-12-11 11:34:27 +01:00
struct shmob_drm_device * sdev = platform_get_drvdata ( pdev ) ;
drm_put_dev ( sdev - > ddev ) ;
2012-04-26 13:53:59 +02:00
return 0 ;
}
static struct platform_driver shmob_drm_platform_driver = {
. probe = shmob_drm_probe ,
2012-12-21 15:09:25 -08:00
. remove = shmob_drm_remove ,
2012-04-26 13:53:59 +02:00
. driver = {
. name = " shmob-drm " ,
. pm = & shmob_drm_pm_ops ,
} ,
} ;
module_platform_driver ( shmob_drm_platform_driver ) ;
MODULE_AUTHOR ( " Laurent Pinchart <laurent.pinchart@ideasonboard.com> " ) ;
MODULE_DESCRIPTION ( " Renesas SH Mobile DRM Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;