2015-04-02 19:48:39 +01:00
/*
* Copyright ( C ) 2013 - 2015 ARM Limited
* Author : Liviu Dudau < Liviu . Dudau @ arm . com >
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file COPYING in the main directory of this archive
* for more details .
*
* ARM HDLCD Driver
*/
# include <linux/module.h>
# include <linux/spinlock.h>
# include <linux/clk.h>
# include <linux/component.h>
2017-03-27 16:20:57 +01:00
# include <linux/console.h>
2019-08-04 11:41:32 +02:00
# include <linux/dma-mapping.h>
2015-04-02 19:48:39 +01:00
# include <linux/list.h>
# include <linux/of_graph.h>
# include <linux/of_reserved_mem.h>
2019-08-04 11:41:32 +02:00
# include <linux/platform_device.h>
2015-04-02 19:48:39 +01:00
# include <linux/pm_runtime.h>
# include <drm/drm_atomic_helper.h>
# include <drm/drm_crtc.h>
2019-08-04 11:41:32 +02:00
# include <drm/drm_debugfs.h>
# include <drm/drm_drv.h>
2015-04-02 19:48:39 +01:00
# include <drm/drm_fb_cma_helper.h>
2019-01-17 22:03:34 +01:00
# include <drm/drm_fb_helper.h>
2015-04-02 19:48:39 +01:00
# include <drm/drm_gem_cma_helper.h>
2017-08-13 15:31:48 +02:00
# include <drm/drm_gem_framebuffer_helper.h>
2019-08-04 11:41:32 +02:00
# include <drm/drm_irq.h>
2017-12-08 20:37:34 +01:00
# include <drm/drm_modeset_helper.h>
2015-04-02 19:48:39 +01:00
# include <drm/drm_of.h>
2019-01-17 22:03:34 +01:00
# include <drm/drm_probe_helper.h>
2019-08-04 11:41:32 +02:00
# include <drm/drm_vblank.h>
2015-04-02 19:48:39 +01:00
# include "hdlcd_drv.h"
# include "hdlcd_regs.h"
static int hdlcd_load ( struct drm_device * drm , unsigned long flags )
{
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
struct platform_device * pdev = to_platform_device ( drm - > dev ) ;
struct resource * res ;
u32 version ;
int ret ;
hdlcd - > clk = devm_clk_get ( drm - > dev , " pxlclk " ) ;
if ( IS_ERR ( hdlcd - > clk ) )
return PTR_ERR ( hdlcd - > clk ) ;
# ifdef CONFIG_DEBUG_FS
atomic_set ( & hdlcd - > buffer_underrun_count , 0 ) ;
atomic_set ( & hdlcd - > bus_error_count , 0 ) ;
atomic_set ( & hdlcd - > vsync_count , 0 ) ;
atomic_set ( & hdlcd - > dma_end_count , 0 ) ;
# endif
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
hdlcd - > mmio = devm_ioremap_resource ( drm - > dev , res ) ;
if ( IS_ERR ( hdlcd - > mmio ) ) {
DRM_ERROR ( " failed to map control registers area \n " ) ;
2016-04-02 08:42:24 +03:00
ret = PTR_ERR ( hdlcd - > mmio ) ;
2015-04-02 19:48:39 +01:00
hdlcd - > mmio = NULL ;
2016-04-02 08:42:24 +03:00
return ret ;
2015-04-02 19:48:39 +01:00
}
version = hdlcd_read ( hdlcd , HDLCD_REG_VERSION ) ;
if ( ( version & HDLCD_PRODUCT_MASK ) ! = HDLCD_PRODUCT_ID ) {
DRM_ERROR ( " unknown product id: 0x%x \n " , version ) ;
2016-02-19 11:15:01 +03:00
return - EINVAL ;
2015-04-02 19:48:39 +01:00
}
DRM_INFO ( " found ARM HDLCD version r%dp%d \n " ,
( version & HDLCD_VERSION_MAJOR_MASK ) > > 8 ,
version & HDLCD_VERSION_MINOR_MASK ) ;
/* Get the optional framebuffer memory resource */
ret = of_reserved_mem_device_init ( drm - > dev ) ;
if ( ret & & ret ! = - ENODEV )
2016-02-19 11:15:01 +03:00
return ret ;
2015-04-02 19:48:39 +01:00
ret = dma_set_mask_and_coherent ( drm - > dev , DMA_BIT_MASK ( 32 ) ) ;
if ( ret )
goto setup_fail ;
ret = hdlcd_setup_crtc ( drm ) ;
if ( ret < 0 ) {
DRM_ERROR ( " failed to create crtc \n " ) ;
goto setup_fail ;
}
ret = drm_irq_install ( drm , platform_get_irq ( pdev , 0 ) ) ;
if ( ret < 0 ) {
DRM_ERROR ( " failed to install IRQ handler \n " ) ;
goto irq_fail ;
}
return 0 ;
irq_fail :
drm_crtc_cleanup ( & hdlcd - > crtc ) ;
setup_fail :
of_reserved_mem_device_release ( drm - > dev ) ;
return ret ;
}
static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = {
2017-08-13 15:31:48 +02:00
. fb_create = drm_gem_fb_create ,
2015-04-02 19:48:39 +01:00
. atomic_check = drm_atomic_helper_check ,
2016-06-08 14:19:04 +02:00
. atomic_commit = drm_atomic_helper_commit ,
2015-04-02 19:48:39 +01:00
} ;
static void hdlcd_setup_mode_config ( struct drm_device * drm )
{
drm_mode_config_init ( drm ) ;
drm - > mode_config . min_width = 0 ;
drm - > mode_config . min_height = 0 ;
drm - > mode_config . max_width = HDLCD_MAX_XRES ;
drm - > mode_config . max_height = HDLCD_MAX_YRES ;
drm - > mode_config . funcs = & hdlcd_mode_config_funcs ;
}
static irqreturn_t hdlcd_irq ( int irq , void * arg )
{
struct drm_device * drm = arg ;
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
unsigned long irq_status ;
irq_status = hdlcd_read ( hdlcd , HDLCD_REG_INT_STATUS ) ;
# ifdef CONFIG_DEBUG_FS
if ( irq_status & HDLCD_INTERRUPT_UNDERRUN )
atomic_inc ( & hdlcd - > buffer_underrun_count ) ;
if ( irq_status & HDLCD_INTERRUPT_DMA_END )
atomic_inc ( & hdlcd - > dma_end_count ) ;
if ( irq_status & HDLCD_INTERRUPT_BUS_ERROR )
atomic_inc ( & hdlcd - > bus_error_count ) ;
if ( irq_status & HDLCD_INTERRUPT_VSYNC )
atomic_inc ( & hdlcd - > vsync_count ) ;
# endif
2016-05-31 18:21:13 +02:00
if ( irq_status & HDLCD_INTERRUPT_VSYNC )
2015-04-02 19:48:39 +01:00
drm_crtc_handle_vblank ( & hdlcd - > crtc ) ;
/* acknowledge interrupt(s) */
hdlcd_write ( hdlcd , HDLCD_REG_INT_CLEAR , irq_status ) ;
return IRQ_HANDLED ;
}
static void hdlcd_irq_preinstall ( struct drm_device * drm )
{
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
/* Ensure interrupts are disabled */
hdlcd_write ( hdlcd , HDLCD_REG_INT_MASK , 0 ) ;
hdlcd_write ( hdlcd , HDLCD_REG_INT_CLEAR , ~ 0 ) ;
}
static int hdlcd_irq_postinstall ( struct drm_device * drm )
{
# ifdef CONFIG_DEBUG_FS
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
unsigned long irq_mask = hdlcd_read ( hdlcd , HDLCD_REG_INT_MASK ) ;
/* enable debug interrupts */
irq_mask | = HDLCD_DEBUG_INT_MASK ;
hdlcd_write ( hdlcd , HDLCD_REG_INT_MASK , irq_mask ) ;
# endif
return 0 ;
}
static void hdlcd_irq_uninstall ( struct drm_device * drm )
{
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
/* disable all the interrupts that we might have enabled */
unsigned long irq_mask = hdlcd_read ( hdlcd , HDLCD_REG_INT_MASK ) ;
# ifdef CONFIG_DEBUG_FS
/* disable debug interrupts */
irq_mask & = ~ HDLCD_DEBUG_INT_MASK ;
# endif
/* disable vsync interrupts */
irq_mask & = ~ HDLCD_INTERRUPT_VSYNC ;
hdlcd_write ( hdlcd , HDLCD_REG_INT_MASK , irq_mask ) ;
}
# ifdef CONFIG_DEBUG_FS
static int hdlcd_show_underrun_count ( struct seq_file * m , void * arg )
{
struct drm_info_node * node = ( struct drm_info_node * ) m - > private ;
struct drm_device * drm = node - > minor - > dev ;
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
seq_printf ( m , " underrun : %d \n " , atomic_read ( & hdlcd - > buffer_underrun_count ) ) ;
seq_printf ( m , " dma_end : %d \n " , atomic_read ( & hdlcd - > dma_end_count ) ) ;
seq_printf ( m , " bus_error: %d \n " , atomic_read ( & hdlcd - > bus_error_count ) ) ;
seq_printf ( m , " vsync : %d \n " , atomic_read ( & hdlcd - > vsync_count ) ) ;
return 0 ;
}
static int hdlcd_show_pxlclock ( struct seq_file * m , void * arg )
{
struct drm_info_node * node = ( struct drm_info_node * ) m - > private ;
struct drm_device * drm = node - > minor - > dev ;
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
unsigned long clkrate = clk_get_rate ( hdlcd - > clk ) ;
unsigned long mode_clock = hdlcd - > crtc . mode . crtc_clock * 1000 ;
seq_printf ( m , " hw : %lu \n " , clkrate ) ;
seq_printf ( m , " mode: %lu \n " , mode_clock ) ;
return 0 ;
}
static struct drm_info_list hdlcd_debugfs_list [ ] = {
{ " interrupt_count " , hdlcd_show_underrun_count , 0 } ,
{ " clocks " , hdlcd_show_pxlclock , 0 } ,
} ;
2020-03-10 16:31:21 +03:00
static void hdlcd_debugfs_init ( struct drm_minor * minor )
2015-04-02 19:48:39 +01:00
{
2020-03-10 16:31:10 +03:00
drm_debugfs_create_files ( hdlcd_debugfs_list ,
ARRAY_SIZE ( hdlcd_debugfs_list ) ,
minor - > debugfs_root , minor ) ;
2015-04-02 19:48:39 +01:00
}
# endif
2017-03-08 15:12:56 +01:00
DEFINE_DRM_GEM_CMA_FOPS ( fops ) ;
2015-04-02 19:48:39 +01:00
2020-11-04 11:04:24 +01:00
static const struct drm_driver hdlcd_driver = {
2019-06-17 17:39:24 +02:00
. driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC ,
2015-04-02 19:48:39 +01:00
. irq_handler = hdlcd_irq ,
. irq_preinstall = hdlcd_irq_preinstall ,
. irq_postinstall = hdlcd_irq_postinstall ,
. irq_uninstall = hdlcd_irq_uninstall ,
2020-06-05 09:32:11 +02:00
DRM_GEM_CMA_DRIVER_OPS ,
2015-04-02 19:48:39 +01:00
# ifdef CONFIG_DEBUG_FS
. debugfs_init = hdlcd_debugfs_init ,
# endif
. fops = & fops ,
. name = " hdlcd " ,
. desc = " ARM HDLCD Controller DRM " ,
. date = " 20151021 " ,
. major = 1 ,
. minor = 0 ,
} ;
static int hdlcd_drm_bind ( struct device * dev )
{
struct drm_device * drm ;
struct hdlcd_drm_private * hdlcd ;
int ret ;
hdlcd = devm_kzalloc ( dev , sizeof ( * hdlcd ) , GFP_KERNEL ) ;
if ( ! hdlcd )
return - ENOMEM ;
drm = drm_dev_alloc ( & hdlcd_driver , dev ) ;
2016-09-21 16:59:19 +02:00
if ( IS_ERR ( drm ) )
return PTR_ERR ( drm ) ;
2015-04-02 19:48:39 +01:00
drm - > dev_private = hdlcd ;
2016-05-17 10:06:54 +01:00
dev_set_drvdata ( dev , drm ) ;
2015-04-02 19:48:39 +01:00
hdlcd_setup_mode_config ( drm ) ;
ret = hdlcd_load ( drm , 0 ) ;
if ( ret )
goto err_free ;
2017-06-06 15:05:21 +01:00
/* Set the CRTC's port so that the encoder component can find it */
hdlcd - > crtc . port = of_graph_get_port_by_id ( dev - > of_node , 0 ) ;
2015-04-02 19:48:39 +01:00
ret = component_bind_all ( dev , drm ) ;
if ( ret ) {
DRM_ERROR ( " Failed to bind all components \n " ) ;
2016-10-24 15:27:59 +01:00
goto err_unload ;
2015-04-02 19:48:39 +01:00
}
2016-05-17 10:06:54 +01:00
ret = pm_runtime_set_active ( dev ) ;
if ( ret )
goto err_pm_active ;
pm_runtime_enable ( dev ) ;
2015-04-02 19:48:39 +01:00
ret = drm_vblank_init ( drm , drm - > mode_config . num_crtc ) ;
if ( ret < 0 ) {
DRM_ERROR ( " failed to initialise vblank \n " ) ;
goto err_vblank ;
}
drm_mode_config_reset ( drm ) ;
drm_kms_helper_poll_init ( drm ) ;
2016-10-24 15:27:59 +01:00
ret = drm_dev_register ( drm , 0 ) ;
if ( ret )
goto err_register ;
2018-09-08 15:46:36 +02:00
drm_fbdev_generic_setup ( drm , 32 ) ;
2015-04-02 19:48:39 +01:00
return 0 ;
2016-10-24 15:27:59 +01:00
err_register :
2015-04-02 19:48:39 +01:00
drm_kms_helper_poll_fini ( drm ) ;
err_vblank :
2016-05-17 10:06:54 +01:00
pm_runtime_disable ( drm - > dev ) ;
err_pm_active :
2018-07-23 12:05:53 +01:00
drm_atomic_helper_shutdown ( drm ) ;
2015-04-02 19:48:39 +01:00
component_unbind_all ( dev , drm ) ;
err_unload :
2017-06-06 15:05:21 +01:00
of_node_put ( hdlcd - > crtc . port ) ;
hdlcd - > crtc . port = NULL ;
2015-04-02 19:48:39 +01:00
drm_irq_uninstall ( drm ) ;
of_reserved_mem_device_release ( drm - > dev ) ;
err_free :
2016-11-24 14:40:50 +00:00
drm_mode_config_cleanup ( drm ) ;
2016-05-17 10:06:54 +01:00
dev_set_drvdata ( dev , NULL ) ;
2017-09-29 15:30:40 +05:30
drm_dev_put ( drm ) ;
2015-04-02 19:48:39 +01:00
return ret ;
}
static void hdlcd_drm_unbind ( struct device * dev )
{
struct drm_device * drm = dev_get_drvdata ( dev ) ;
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
2016-10-24 15:27:59 +01:00
drm_dev_unregister ( drm ) ;
2015-04-02 19:48:39 +01:00
drm_kms_helper_poll_fini ( drm ) ;
component_unbind_all ( dev , drm ) ;
2017-06-06 15:05:21 +01:00
of_node_put ( hdlcd - > crtc . port ) ;
hdlcd - > crtc . port = NULL ;
2018-07-23 12:05:53 +01:00
pm_runtime_get_sync ( dev ) ;
2018-01-17 23:55:28 +02:00
drm_atomic_helper_shutdown ( drm ) ;
2020-06-02 11:51:40 +02:00
drm_irq_uninstall ( drm ) ;
2018-07-23 12:05:53 +01:00
pm_runtime_put ( dev ) ;
if ( pm_runtime_enabled ( dev ) )
pm_runtime_disable ( dev ) ;
of_reserved_mem_device_release ( dev ) ;
2015-04-02 19:48:39 +01:00
drm_mode_config_cleanup ( drm ) ;
drm - > dev_private = NULL ;
dev_set_drvdata ( dev , NULL ) ;
2018-07-23 12:05:53 +01:00
drm_dev_put ( drm ) ;
2015-04-02 19:48:39 +01:00
}
static const struct component_master_ops hdlcd_master_ops = {
. bind = hdlcd_drm_bind ,
. unbind = hdlcd_drm_unbind ,
} ;
static int compare_dev ( struct device * dev , void * data )
{
return dev - > of_node = = data ;
}
static int hdlcd_probe ( struct platform_device * pdev )
{
2017-03-22 08:26:06 -05:00
struct device_node * port ;
2015-04-02 19:48:39 +01:00
struct component_match * match = NULL ;
/* there is only one output port inside each device, find it */
2017-03-22 08:26:06 -05:00
port = of_graph_get_remote_node ( pdev - > dev . of_node , 0 , 0 ) ;
if ( ! port )
2015-04-02 19:48:39 +01:00
return - ENODEV ;
2016-10-19 11:28:27 +01:00
drm_of_component_match_add ( & pdev - > dev , & match , compare_dev , port ) ;
of_node_put ( port ) ;
2015-04-02 19:48:39 +01:00
return component_master_add_with_match ( & pdev - > dev , & hdlcd_master_ops ,
match ) ;
}
static int hdlcd_remove ( struct platform_device * pdev )
{
component_master_del ( & pdev - > dev , & hdlcd_master_ops ) ;
return 0 ;
}
static const struct of_device_id hdlcd_of_match [ ] = {
{ . compatible = " arm,hdlcd " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , hdlcd_of_match ) ;
static int __maybe_unused hdlcd_pm_suspend ( struct device * dev )
{
struct drm_device * drm = dev_get_drvdata ( dev ) ;
2017-12-08 20:37:34 +01:00
return drm_mode_config_helper_suspend ( drm ) ;
2015-04-02 19:48:39 +01:00
}
static int __maybe_unused hdlcd_pm_resume ( struct device * dev )
{
struct drm_device * drm = dev_get_drvdata ( dev ) ;
2017-12-08 20:37:34 +01:00
drm_mode_config_helper_resume ( drm ) ;
2016-05-17 10:06:54 +01:00
2015-04-02 19:48:39 +01:00
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( hdlcd_pm_ops , hdlcd_pm_suspend , hdlcd_pm_resume ) ;
static struct platform_driver hdlcd_platform_driver = {
. probe = hdlcd_probe ,
. remove = hdlcd_remove ,
. driver = {
. name = " hdlcd " ,
. pm = & hdlcd_pm_ops ,
. of_match_table = hdlcd_of_match ,
} ,
} ;
module_platform_driver ( hdlcd_platform_driver ) ;
MODULE_AUTHOR ( " Liviu Dudau " ) ;
MODULE_DESCRIPTION ( " ARM HDLCD DRM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;