2015-08-19 22:19:49 -04:00
/*
* Copyright 2015 Freescale Semiconductor , Inc .
*
* Freescale DCU drm device driver
*
* 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/clk-provider.h>
2016-02-11 17:31:51 -08:00
# include <linux/console.h>
2015-08-19 22:19:49 -04:00
# include <linux/io.h>
# include <linux/mfd/syscon.h>
# include <linux/mm.h>
# include <linux/module.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
# include <linux/pm.h>
# include <linux/pm_runtime.h>
# include <linux/regmap.h>
# include <drm/drmP.h>
2016-02-11 17:31:51 -08:00
# include <drm/drm_atomic_helper.h>
2015-08-19 22:19:49 -04:00
# include <drm/drm_crtc_helper.h>
2016-04-16 22:02:49 -07:00
# include <drm/drm_fb_cma_helper.h>
2015-08-19 22:19:49 -04:00
# include <drm/drm_gem_cma_helper.h>
# include "fsl_dcu_drm_crtc.h"
# include "fsl_dcu_drm_drv.h"
2015-12-02 14:39:40 -08:00
# include "fsl_tcon.h"
2015-08-19 22:19:49 -04:00
2016-11-16 17:23:27 -08:00
static int legacyfb_depth = 24 ;
module_param ( legacyfb_depth , int , 0444 ) ;
2015-11-17 18:05:25 -08:00
static bool fsl_dcu_drm_is_volatile_reg ( struct device * dev , unsigned int reg )
{
if ( reg = = DCU_INT_STATUS | | reg = = DCU_UPDATE_MODE )
return true ;
return false ;
}
2015-08-19 22:19:49 -04:00
static const struct regmap_config fsl_dcu_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
2015-11-17 18:05:25 -08:00
. volatile_reg = fsl_dcu_drm_is_volatile_reg ,
2015-08-19 22:19:49 -04:00
} ;
static int fsl_dcu_drm_irq_init ( struct drm_device * dev )
{
struct fsl_dcu_drm_device * fsl_dev = dev - > dev_private ;
int ret ;
ret = drm_irq_install ( dev , fsl_dev - > irq ) ;
if ( ret < 0 )
dev_err ( dev - > dev , " failed to install IRQ handler \n " ) ;
2015-11-18 16:50:55 -08:00
regmap_write ( fsl_dev - > regmap , DCU_INT_STATUS , 0 ) ;
2015-11-18 17:27:04 -08:00
regmap_write ( fsl_dev - > regmap , DCU_INT_MASK , ~ 0 ) ;
2015-08-19 22:19:49 -04:00
return ret ;
}
2016-04-13 00:14:18 -07:00
static int fsl_dcu_load ( struct drm_device * dev , unsigned long flags )
2015-08-19 22:19:49 -04:00
{
2016-04-13 00:14:18 -07:00
struct fsl_dcu_drm_device * fsl_dev = dev - > dev_private ;
2015-08-19 22:19:49 -04:00
int ret ;
ret = fsl_dcu_drm_modeset_init ( fsl_dev ) ;
if ( ret < 0 ) {
2016-04-13 00:14:18 -07:00
dev_err ( dev - > dev , " failed to initialize mode setting \n " ) ;
2015-08-19 22:19:49 -04:00
return ret ;
}
2016-04-13 00:14:18 -07:00
ret = drm_vblank_init ( dev , dev - > mode_config . num_crtc ) ;
2015-08-19 22:19:49 -04:00
if ( ret < 0 ) {
2016-04-13 00:14:18 -07:00
dev_err ( dev - > dev , " failed to initialize vblank \n " ) ;
2015-08-19 22:19:49 -04:00
goto done ;
}
2016-04-13 00:14:18 -07:00
ret = fsl_dcu_drm_irq_init ( dev ) ;
2015-08-19 22:19:49 -04:00
if ( ret < 0 )
goto done ;
2016-04-13 00:14:18 -07:00
dev - > irq_enabled = true ;
2015-08-19 22:19:49 -04:00
2016-11-16 17:23:27 -08:00
if ( legacyfb_depth ! = 16 & & legacyfb_depth ! = 24 & &
legacyfb_depth ! = 32 ) {
dev_warn ( dev - > dev ,
" Invalid legacyfb_depth. Defaulting to 24bpp \n " ) ;
legacyfb_depth = 24 ;
}
fsl_dev - > fbdev = drm_fbdev_cma_init ( dev , legacyfb_depth , 1 , 1 ) ;
if ( IS_ERR ( fsl_dev - > fbdev ) ) {
ret = PTR_ERR ( fsl_dev - > fbdev ) ;
fsl_dev - > fbdev = NULL ;
goto done ;
}
2015-08-19 22:19:49 -04:00
return 0 ;
done :
2016-04-16 21:17:12 -07:00
drm_kms_helper_poll_fini ( dev ) ;
2016-04-16 22:02:49 -07:00
if ( fsl_dev - > fbdev )
drm_fbdev_cma_fini ( fsl_dev - > fbdev ) ;
2016-04-13 00:14:18 -07:00
drm_mode_config_cleanup ( dev ) ;
drm_vblank_cleanup ( dev ) ;
drm_irq_uninstall ( dev ) ;
dev - > dev_private = NULL ;
2015-08-19 22:19:49 -04:00
return ret ;
}
2017-01-06 15:57:31 -02:00
static void fsl_dcu_unload ( struct drm_device * dev )
2015-08-19 22:19:49 -04:00
{
2016-04-16 22:02:49 -07:00
struct fsl_dcu_drm_device * fsl_dev = dev - > dev_private ;
2016-10-19 17:32:21 -07:00
drm_crtc_force_disable_all ( dev ) ;
2016-04-16 21:17:12 -07:00
drm_kms_helper_poll_fini ( dev ) ;
2016-04-16 22:02:49 -07:00
if ( fsl_dev - > fbdev )
drm_fbdev_cma_fini ( fsl_dev - > fbdev ) ;
2015-08-19 22:19:49 -04:00
drm_mode_config_cleanup ( dev ) ;
drm_vblank_cleanup ( dev ) ;
drm_irq_uninstall ( dev ) ;
dev - > dev_private = NULL ;
}
static irqreturn_t fsl_dcu_drm_irq ( int irq , void * arg )
{
struct drm_device * dev = arg ;
struct fsl_dcu_drm_device * fsl_dev = dev - > dev_private ;
unsigned int int_status ;
int ret ;
ret = regmap_read ( fsl_dev - > regmap , DCU_INT_STATUS , & int_status ) ;
2015-11-18 16:50:55 -08:00
if ( ret ) {
dev_err ( dev - > dev , " read DCU_INT_STATUS failed \n " ) ;
return IRQ_NONE ;
}
2015-08-19 22:19:49 -04:00
if ( int_status & DCU_INT_STATUS_VBLANK )
drm_handle_vblank ( dev , 0 ) ;
2015-11-18 16:50:55 -08:00
regmap_write ( fsl_dev - > regmap , DCU_INT_STATUS , int_status ) ;
2015-08-19 22:19:49 -04:00
return IRQ_HANDLED ;
}
2015-09-24 18:35:31 +02:00
static int fsl_dcu_drm_enable_vblank ( struct drm_device * dev , unsigned int pipe )
2015-08-19 22:19:49 -04:00
{
struct fsl_dcu_drm_device * fsl_dev = dev - > dev_private ;
unsigned int value ;
2015-11-18 16:50:55 -08:00
regmap_read ( fsl_dev - > regmap , DCU_INT_MASK , & value ) ;
2015-08-19 22:19:49 -04:00
value & = ~ DCU_INT_MASK_VBLANK ;
2015-11-18 16:50:55 -08:00
regmap_write ( fsl_dev - > regmap , DCU_INT_MASK , value ) ;
2015-08-19 22:19:49 -04:00
return 0 ;
}
2015-09-24 18:35:31 +02:00
static void fsl_dcu_drm_disable_vblank ( struct drm_device * dev ,
unsigned int pipe )
2015-08-19 22:19:49 -04:00
{
struct fsl_dcu_drm_device * fsl_dev = dev - > dev_private ;
unsigned int value ;
2015-11-18 16:50:55 -08:00
regmap_read ( fsl_dev - > regmap , DCU_INT_MASK , & value ) ;
2015-08-19 22:19:49 -04:00
value | = DCU_INT_MASK_VBLANK ;
2015-11-18 16:50:55 -08:00
regmap_write ( fsl_dev - > regmap , DCU_INT_MASK , value ) ;
2015-08-19 22:19:49 -04:00
}
2016-04-16 21:18:54 -07:00
static void fsl_dcu_drm_lastclose ( struct drm_device * dev )
{
struct fsl_dcu_drm_device * fsl_dev = dev - > dev_private ;
drm_fbdev_cma_restore_mode ( fsl_dev - > fbdev ) ;
}
2015-08-19 22:19:49 -04:00
static const struct file_operations fsl_dcu_drm_fops = {
. owner = THIS_MODULE ,
. open = drm_open ,
. release = drm_release ,
. unlocked_ioctl = drm_ioctl ,
. compat_ioctl = drm_compat_ioctl ,
. poll = drm_poll ,
. read = drm_read ,
. llseek = no_llseek ,
. mmap = drm_gem_cma_mmap ,
} ;
static struct drm_driver fsl_dcu_drm_driver = {
. driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET
| DRIVER_PRIME | DRIVER_ATOMIC ,
2016-04-16 21:18:54 -07:00
. lastclose = fsl_dcu_drm_lastclose ,
2015-08-19 22:19:49 -04:00
. load = fsl_dcu_load ,
. unload = fsl_dcu_unload ,
. irq_handler = fsl_dcu_drm_irq ,
2015-09-30 16:46:48 +03:00
. get_vblank_counter = drm_vblank_no_hw_counter ,
2015-08-19 22:19:49 -04:00
. enable_vblank = fsl_dcu_drm_enable_vblank ,
. disable_vblank = fsl_dcu_drm_disable_vblank ,
2016-05-30 19:52:58 +02:00
. gem_free_object_unlocked = drm_gem_cma_free_object ,
2015-08-19 22:19:49 -04:00
. 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 ,
. 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 ,
. dumb_create = drm_gem_cma_dumb_create ,
. dumb_map_offset = drm_gem_cma_dumb_map_offset ,
. dumb_destroy = drm_gem_dumb_destroy ,
. fops = & fsl_dcu_drm_fops ,
. name = " fsl-dcu-drm " ,
. desc = " Freescale DCU DRM " ,
2016-04-25 20:47:57 -07:00
. date = " 20160425 " ,
2015-08-19 22:19:49 -04:00
. major = 1 ,
2016-04-25 20:47:57 -07:00
. minor = 1 ,
2015-08-19 22:19:49 -04:00
} ;
# ifdef CONFIG_PM_SLEEP
static int fsl_dcu_drm_pm_suspend ( struct device * dev )
{
struct fsl_dcu_drm_device * fsl_dev = dev_get_drvdata ( dev ) ;
if ( ! fsl_dev )
return 0 ;
2016-02-11 17:31:51 -08:00
disable_irq ( fsl_dev - > irq ) ;
2015-08-19 22:19:49 -04:00
drm_kms_helper_poll_disable ( fsl_dev - > drm ) ;
2016-02-11 17:31:51 -08:00
console_lock ( ) ;
drm_fbdev_cma_set_suspend ( fsl_dev - > fbdev , 1 ) ;
console_unlock ( ) ;
fsl_dev - > state = drm_atomic_helper_suspend ( fsl_dev - > drm ) ;
if ( IS_ERR ( fsl_dev - > state ) ) {
console_lock ( ) ;
drm_fbdev_cma_set_suspend ( fsl_dev - > fbdev , 0 ) ;
console_unlock ( ) ;
drm_kms_helper_poll_enable ( fsl_dev - > drm ) ;
enable_irq ( fsl_dev - > irq ) ;
return PTR_ERR ( fsl_dev - > state ) ;
}
clk_disable_unprepare ( fsl_dev - > pix_clk ) ;
2016-02-11 16:56:30 -08:00
clk_disable_unprepare ( fsl_dev - > clk ) ;
2015-08-19 22:19:49 -04:00
return 0 ;
}
static int fsl_dcu_drm_pm_resume ( struct device * dev )
{
struct fsl_dcu_drm_device * fsl_dev = dev_get_drvdata ( dev ) ;
int ret ;
if ( ! fsl_dev )
return 0 ;
2016-02-11 16:56:30 -08:00
ret = clk_prepare_enable ( fsl_dev - > clk ) ;
2015-08-19 22:19:49 -04:00
if ( ret < 0 ) {
dev_err ( dev , " failed to enable dcu clk \n " ) ;
return ret ;
}
2016-10-04 16:40:14 -07:00
if ( fsl_dev - > tcon )
fsl_tcon_bypass_enable ( fsl_dev - > tcon ) ;
2016-02-11 17:31:51 -08:00
fsl_dcu_drm_init_planes ( fsl_dev - > drm ) ;
drm_atomic_helper_resume ( fsl_dev - > drm , fsl_dev - > state ) ;
console_lock ( ) ;
drm_fbdev_cma_set_suspend ( fsl_dev - > fbdev , 0 ) ;
console_unlock ( ) ;
2015-08-19 22:19:49 -04:00
drm_kms_helper_poll_enable ( fsl_dev - > drm ) ;
2016-02-11 17:31:51 -08:00
enable_irq ( fsl_dev - > irq ) ;
2015-08-19 22:19:49 -04:00
return 0 ;
}
# endif
static const struct dev_pm_ops fsl_dcu_drm_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( fsl_dcu_drm_pm_suspend , fsl_dcu_drm_pm_resume )
} ;
static const struct fsl_dcu_soc_data fsl_dcu_ls1021a_data = {
. name = " ls1021a " ,
. total_layer = 16 ,
. max_layer = 4 ,
2016-02-11 16:41:18 -08:00
. layer_regs = LS1021A_LAYER_REG_NUM ,
2015-08-19 22:19:49 -04:00
} ;
static const struct fsl_dcu_soc_data fsl_dcu_vf610_data = {
. name = " vf610 " ,
. total_layer = 64 ,
. max_layer = 6 ,
2016-02-11 16:41:18 -08:00
. layer_regs = VF610_LAYER_REG_NUM ,
2015-08-19 22:19:49 -04:00
} ;
static const struct of_device_id fsl_dcu_of_match [ ] = {
{
. compatible = " fsl,ls1021a-dcu " ,
. data = & fsl_dcu_ls1021a_data ,
} , {
. compatible = " fsl,vf610-dcu " ,
. data = & fsl_dcu_vf610_data ,
} , {
} ,
} ;
MODULE_DEVICE_TABLE ( of , fsl_dcu_of_match ) ;
static int fsl_dcu_drm_probe ( struct platform_device * pdev )
{
struct fsl_dcu_drm_device * fsl_dev ;
struct drm_device * drm ;
struct device * dev = & pdev - > dev ;
struct resource * res ;
void __iomem * base ;
struct drm_driver * driver = & fsl_dcu_drm_driver ;
2016-03-22 18:06:08 -07:00
struct clk * pix_clk_in ;
char pix_clk_name [ 32 ] ;
const char * pix_clk_in_name ;
2015-08-19 22:19:49 -04:00
const struct of_device_id * id ;
int ret ;
2016-09-02 11:23:37 -07:00
u8 div_ratio_shift = 0 ;
2015-08-19 22:19:49 -04:00
fsl_dev = devm_kzalloc ( dev , sizeof ( * fsl_dev ) , GFP_KERNEL ) ;
if ( ! fsl_dev )
return - ENOMEM ;
2016-03-22 15:13:05 -07:00
id = of_match_node ( fsl_dcu_of_match , pdev - > dev . of_node ) ;
if ( ! id )
return - ENODEV ;
fsl_dev - > soc = id - > data ;
2015-08-19 22:19:49 -04:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( base ) ) {
ret = PTR_ERR ( base ) ;
return ret ;
}
fsl_dev - > irq = platform_get_irq ( pdev , 0 ) ;
if ( fsl_dev - > irq < 0 ) {
dev_err ( dev , " failed to get irq \n " ) ;
2016-11-16 13:38:33 -02:00
return fsl_dev - > irq ;
2015-08-19 22:19:49 -04:00
}
2016-03-22 15:13:05 -07:00
fsl_dev - > regmap = devm_regmap_init_mmio ( dev , base ,
& fsl_dcu_regmap_config ) ;
if ( IS_ERR ( fsl_dev - > regmap ) ) {
dev_err ( dev , " regmap init failed \n " ) ;
return PTR_ERR ( fsl_dev - > regmap ) ;
}
2015-08-19 22:19:49 -04:00
fsl_dev - > clk = devm_clk_get ( dev , " dcu " ) ;
if ( IS_ERR ( fsl_dev - > clk ) ) {
dev_err ( dev , " failed to get dcu clock \n " ) ;
2016-03-22 15:13:05 -07:00
return PTR_ERR ( fsl_dev - > clk ) ;
2015-08-19 22:19:49 -04:00
}
2016-03-22 15:13:05 -07:00
ret = clk_prepare_enable ( fsl_dev - > clk ) ;
2015-08-19 22:19:49 -04:00
if ( ret < 0 ) {
dev_err ( dev , " failed to enable dcu clk \n " ) ;
return ret ;
}
2016-03-22 18:06:08 -07:00
pix_clk_in = devm_clk_get ( dev , " pix " ) ;
if ( IS_ERR ( pix_clk_in ) ) {
/* legancy binding, use dcu clock as pixel clock input */
pix_clk_in = fsl_dev - > clk ;
}
2016-09-02 11:23:37 -07:00
if ( of_property_read_bool ( dev - > of_node , " big-endian " ) )
div_ratio_shift = 24 ;
2016-03-22 18:06:08 -07:00
pix_clk_in_name = __clk_get_name ( pix_clk_in ) ;
snprintf ( pix_clk_name , sizeof ( pix_clk_name ) , " %s_pix " , pix_clk_in_name ) ;
fsl_dev - > pix_clk = clk_register_divider ( dev , pix_clk_name ,
pix_clk_in_name , 0 , base + DCU_DIV_RATIO ,
2016-09-02 11:23:37 -07:00
div_ratio_shift , 8 , CLK_DIVIDER_ROUND_CLOSEST , NULL ) ;
2016-03-22 15:45:29 -07:00
if ( IS_ERR ( fsl_dev - > pix_clk ) ) {
2016-03-22 18:06:08 -07:00
dev_err ( dev , " failed to register pix clk \n " ) ;
ret = PTR_ERR ( fsl_dev - > pix_clk ) ;
goto disable_clk ;
2016-03-22 15:45:29 -07:00
}
2016-03-22 18:06:08 -07:00
2015-12-02 14:39:40 -08:00
fsl_dev - > tcon = fsl_tcon_init ( dev ) ;
2015-08-19 22:19:49 -04:00
drm = drm_dev_alloc ( driver , dev ) ;
2016-09-21 16:59:19 +02:00
if ( IS_ERR ( drm ) ) {
ret = PTR_ERR ( drm ) ;
2016-10-04 17:40:29 -07:00
goto unregister_pix_clk ;
2016-03-22 15:13:05 -07:00
}
2015-08-19 22:19:49 -04:00
fsl_dev - > dev = dev ;
fsl_dev - > drm = drm ;
fsl_dev - > np = dev - > of_node ;
drm - > dev_private = fsl_dev ;
dev_set_drvdata ( dev , fsl_dev ) ;
ret = drm_dev_register ( drm , 0 ) ;
if ( ret < 0 )
goto unref ;
return 0 ;
unref :
drm_dev_unref ( drm ) ;
2016-03-22 18:06:08 -07:00
unregister_pix_clk :
clk_unregister ( fsl_dev - > pix_clk ) ;
2016-03-22 15:13:05 -07:00
disable_clk :
clk_disable_unprepare ( fsl_dev - > clk ) ;
2015-08-19 22:19:49 -04:00
return ret ;
}
static int fsl_dcu_drm_remove ( struct platform_device * pdev )
{
struct fsl_dcu_drm_device * fsl_dev = platform_get_drvdata ( pdev ) ;
2016-12-13 18:54:49 +01:00
drm_dev_unregister ( fsl_dev - > drm ) ;
drm_dev_unref ( fsl_dev - > drm ) ;
2016-03-22 15:13:05 -07:00
clk_disable_unprepare ( fsl_dev - > clk ) ;
2016-03-22 18:06:08 -07:00
clk_unregister ( fsl_dev - > pix_clk ) ;
2015-08-19 22:19:49 -04:00
return 0 ;
}
static struct platform_driver fsl_dcu_drm_platform_driver = {
. probe = fsl_dcu_drm_probe ,
. remove = fsl_dcu_drm_remove ,
. driver = {
. name = " fsl-dcu " ,
. pm = & fsl_dcu_drm_pm_ops ,
. of_match_table = fsl_dcu_of_match ,
} ,
} ;
module_platform_driver ( fsl_dcu_drm_platform_driver ) ;
MODULE_DESCRIPTION ( " Freescale DCU DRM Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;