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 .
*
* Implementation of a CRTC class for the HDLCD driver .
*/
2019-08-04 11:41:32 +02:00
# include <linux/clk.h>
# include <linux/of_graph.h>
# include <linux/platform_data/simplefb.h>
# include <video/videomode.h>
2017-03-08 16:10:19 +00:00
# include <drm/drm_atomic.h>
2015-04-02 19:48:39 +01:00
# include <drm/drm_atomic_helper.h>
# include <drm/drm_crtc.h>
# 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>
# include <drm/drm_of.h>
# include <drm/drm_plane_helper.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"
/*
* The HDLCD controller is a dumb RGB streamer that gets connected to
* a single HDMI transmitter or in the case of the ARM Models it gets
* emulated by the software that does the actual rendering .
*
*/
2016-05-17 10:06:54 +01:00
static void hdlcd_crtc_cleanup ( struct drm_crtc * crtc )
{
struct hdlcd_drm_private * hdlcd = crtc_to_hdlcd_priv ( crtc ) ;
/* stop the controller on cleanup */
hdlcd_write ( hdlcd , HDLCD_REG_COMMAND , 0 ) ;
drm_crtc_cleanup ( crtc ) ;
}
2017-02-07 17:16:16 +08:00
static int hdlcd_crtc_enable_vblank ( struct drm_crtc * crtc )
{
struct hdlcd_drm_private * hdlcd = crtc_to_hdlcd_priv ( crtc ) ;
unsigned int mask = hdlcd_read ( hdlcd , HDLCD_REG_INT_MASK ) ;
hdlcd_write ( hdlcd , HDLCD_REG_INT_MASK , mask | HDLCD_INTERRUPT_VSYNC ) ;
return 0 ;
}
static void hdlcd_crtc_disable_vblank ( struct drm_crtc * crtc )
{
struct hdlcd_drm_private * hdlcd = crtc_to_hdlcd_priv ( crtc ) ;
unsigned int mask = hdlcd_read ( hdlcd , HDLCD_REG_INT_MASK ) ;
hdlcd_write ( hdlcd , HDLCD_REG_INT_MASK , mask & ~ HDLCD_INTERRUPT_VSYNC ) ;
}
2015-04-02 19:48:39 +01:00
static const struct drm_crtc_funcs hdlcd_crtc_funcs = {
2016-05-17 10:06:54 +01:00
. destroy = hdlcd_crtc_cleanup ,
2015-04-02 19:48:39 +01:00
. set_config = drm_atomic_helper_set_config ,
. page_flip = drm_atomic_helper_page_flip ,
. reset = drm_atomic_helper_crtc_reset ,
. atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_crtc_destroy_state ,
2017-02-07 17:16:16 +08:00
. enable_vblank = hdlcd_crtc_enable_vblank ,
. disable_vblank = hdlcd_crtc_disable_vblank ,
2015-04-02 19:48:39 +01:00
} ;
static struct simplefb_format supported_formats [ ] = SIMPLEFB_FORMATS ;
/*
* Setup the HDLCD registers for decoding the pixels out of the framebuffer
*/
static int hdlcd_set_pxl_fmt ( struct drm_crtc * crtc )
{
unsigned int btpp ;
struct hdlcd_drm_private * hdlcd = crtc_to_hdlcd_priv ( crtc ) ;
2016-11-18 21:52:45 +02:00
const struct drm_framebuffer * fb = crtc - > primary - > state - > fb ;
2015-04-02 19:48:39 +01:00
uint32_t pixel_format ;
struct simplefb_format * format = NULL ;
int i ;
2016-12-14 23:32:55 +02:00
pixel_format = fb - > format - > format ;
2015-04-02 19:48:39 +01:00
for ( i = 0 ; i < ARRAY_SIZE ( supported_formats ) ; i + + ) {
if ( supported_formats [ i ] . fourcc = = pixel_format )
format = & supported_formats [ i ] ;
}
if ( WARN_ON ( ! format ) )
return 0 ;
/* HDLCD uses 'bytes per pixel', zero means 1 byte */
btpp = ( format - > bits_per_pixel + 7 ) / 8 ;
hdlcd_write ( hdlcd , HDLCD_REG_PIXEL_FORMAT , ( btpp - 1 ) < < 3 ) ;
/*
* The format of the HDLCD_REG_ < color > _SELECT register is :
* - bits [ 23 : 16 ] - default value for that color component
* - bits [ 11 : 8 ] - number of bits to extract for each color component
* - bits [ 4 : 0 ] - index of the lowest bit to extract
*
* The default color value is used when bits [ 11 : 8 ] are zero , when the
* pixel is outside the visible frame area or when there is a
* buffer underrun .
*/
hdlcd_write ( hdlcd , HDLCD_REG_RED_SELECT , format - > red . offset |
# ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN
0x00ff0000 | /* show underruns in red */
# endif
( ( format - > red . length & 0xf ) < < 8 ) ) ;
hdlcd_write ( hdlcd , HDLCD_REG_GREEN_SELECT , format - > green . offset |
( ( format - > green . length & 0xf ) < < 8 ) ) ;
hdlcd_write ( hdlcd , HDLCD_REG_BLUE_SELECT , format - > blue . offset |
( ( format - > blue . length & 0xf ) < < 8 ) ) ;
return 0 ;
}
static void hdlcd_crtc_mode_set_nofb ( struct drm_crtc * crtc )
{
struct hdlcd_drm_private * hdlcd = crtc_to_hdlcd_priv ( crtc ) ;
struct drm_display_mode * m = & crtc - > state - > adjusted_mode ;
struct videomode vm ;
2016-06-01 15:00:15 +01:00
unsigned int polarities , err ;
2015-04-02 19:48:39 +01:00
vm . vfront_porch = m - > crtc_vsync_start - m - > crtc_vdisplay ;
vm . vback_porch = m - > crtc_vtotal - m - > crtc_vsync_end ;
vm . vsync_len = m - > crtc_vsync_end - m - > crtc_vsync_start ;
vm . hfront_porch = m - > crtc_hsync_start - m - > crtc_hdisplay ;
vm . hback_porch = m - > crtc_htotal - m - > crtc_hsync_end ;
vm . hsync_len = m - > crtc_hsync_end - m - > crtc_hsync_start ;
polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_DATA ;
if ( m - > flags & DRM_MODE_FLAG_PHSYNC )
polarities | = HDLCD_POLARITY_HSYNC ;
if ( m - > flags & DRM_MODE_FLAG_PVSYNC )
polarities | = HDLCD_POLARITY_VSYNC ;
/* Allow max number of outstanding requests and largest burst size */
hdlcd_write ( hdlcd , HDLCD_REG_BUS_OPTIONS ,
HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16 ) ;
hdlcd_write ( hdlcd , HDLCD_REG_V_DATA , m - > crtc_vdisplay - 1 ) ;
hdlcd_write ( hdlcd , HDLCD_REG_V_BACK_PORCH , vm . vback_porch - 1 ) ;
hdlcd_write ( hdlcd , HDLCD_REG_V_FRONT_PORCH , vm . vfront_porch - 1 ) ;
hdlcd_write ( hdlcd , HDLCD_REG_V_SYNC , vm . vsync_len - 1 ) ;
2016-06-01 15:00:15 +01:00
hdlcd_write ( hdlcd , HDLCD_REG_H_DATA , m - > crtc_hdisplay - 1 ) ;
2015-04-02 19:48:39 +01:00
hdlcd_write ( hdlcd , HDLCD_REG_H_BACK_PORCH , vm . hback_porch - 1 ) ;
hdlcd_write ( hdlcd , HDLCD_REG_H_FRONT_PORCH , vm . hfront_porch - 1 ) ;
hdlcd_write ( hdlcd , HDLCD_REG_H_SYNC , vm . hsync_len - 1 ) ;
hdlcd_write ( hdlcd , HDLCD_REG_POLARITIES , polarities ) ;
err = hdlcd_set_pxl_fmt ( crtc ) ;
if ( err )
return ;
clk_set_rate ( hdlcd - > clk , m - > crtc_clock * 1000 ) ;
}
2017-06-30 12:36:44 +03:00
static void hdlcd_crtc_atomic_enable ( struct drm_crtc * crtc ,
struct drm_crtc_state * old_state )
2015-04-02 19:48:39 +01:00
{
struct hdlcd_drm_private * hdlcd = crtc_to_hdlcd_priv ( crtc ) ;
clk_prepare_enable ( hdlcd - > clk ) ;
2016-06-01 15:00:15 +01:00
hdlcd_crtc_mode_set_nofb ( crtc ) ;
2015-04-02 19:48:39 +01:00
hdlcd_write ( hdlcd , HDLCD_REG_COMMAND , 1 ) ;
2016-11-22 13:56:54 +00:00
drm_crtc_vblank_on ( crtc ) ;
2015-04-02 19:48:39 +01:00
}
2017-06-30 12:36:45 +03:00
static void hdlcd_crtc_atomic_disable ( struct drm_crtc * crtc ,
struct drm_crtc_state * old_state )
2015-04-02 19:48:39 +01:00
{
struct hdlcd_drm_private * hdlcd = crtc_to_hdlcd_priv ( crtc ) ;
2016-11-22 13:56:54 +00:00
drm_crtc_vblank_off ( crtc ) ;
2015-04-02 19:48:39 +01:00
hdlcd_write ( hdlcd , HDLCD_REG_COMMAND , 0 ) ;
2016-05-17 10:06:54 +01:00
clk_disable_unprepare ( hdlcd - > clk ) ;
2015-04-02 19:48:39 +01:00
}
2019-05-17 17:37:21 +01:00
static enum drm_mode_status hdlcd_crtc_mode_valid ( struct drm_crtc * crtc ,
const struct drm_display_mode * mode )
2015-04-02 19:48:39 +01:00
{
struct hdlcd_drm_private * hdlcd = crtc_to_hdlcd_priv ( crtc ) ;
long rate , clk_rate = mode - > clock * 1000 ;
rate = clk_round_rate ( hdlcd - > clk , clk_rate ) ;
2019-05-17 17:37:22 +01:00
/* 0.1% seems a close enough tolerance for the TDA19988 on Juno */
if ( abs ( rate - clk_rate ) * 1000 > clk_rate ) {
2015-04-02 19:48:39 +01:00
/* clock required by mode not supported by hardware */
2019-05-17 17:37:21 +01:00
return MODE_NOCLOCK ;
2015-04-02 19:48:39 +01:00
}
2019-05-17 17:37:21 +01:00
return MODE_OK ;
2015-04-02 19:48:39 +01:00
}
static void hdlcd_crtc_atomic_begin ( struct drm_crtc * crtc ,
struct drm_crtc_state * state )
{
2016-05-31 18:21:13 +02:00
struct drm_pending_vblank_event * event = crtc - > state - > event ;
2015-04-02 19:48:39 +01:00
2016-05-31 18:21:13 +02:00
if ( event ) {
2015-04-02 19:48:39 +01:00
crtc - > state - > event = NULL ;
2016-05-31 18:21:13 +02:00
spin_lock_irq ( & crtc - > dev - > event_lock ) ;
if ( drm_crtc_vblank_get ( crtc ) = = 0 )
drm_crtc_arm_vblank_event ( crtc , event ) ;
else
drm_crtc_send_vblank_event ( crtc , event ) ;
spin_unlock_irq ( & crtc - > dev - > event_lock ) ;
2015-04-02 19:48:39 +01:00
}
}
static const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = {
2019-05-17 17:37:21 +01:00
. mode_valid = hdlcd_crtc_mode_valid ,
2015-04-02 19:48:39 +01:00
. atomic_begin = hdlcd_crtc_atomic_begin ,
2017-06-30 12:36:44 +03:00
. atomic_enable = hdlcd_crtc_atomic_enable ,
2017-06-30 12:36:45 +03:00
. atomic_disable = hdlcd_crtc_atomic_disable ,
2015-04-02 19:48:39 +01:00
} ;
static int hdlcd_plane_atomic_check ( struct drm_plane * plane ,
struct drm_plane_state * state )
{
2018-07-23 12:05:53 +01:00
int i ;
struct drm_crtc * crtc ;
2017-03-08 16:10:19 +00:00
struct drm_crtc_state * crtc_state ;
u32 src_h = state - > src_h > > 16 ;
2016-06-01 15:00:15 +01:00
2017-03-08 16:10:19 +00:00
/* only the HDLCD_REG_FB_LINE_COUNT register has a limit */
if ( src_h > = HDLCD_MAX_YRES ) {
DRM_DEBUG_KMS ( " Invalid source width: %d \n " , src_h ) ;
return - EINVAL ;
}
2018-07-23 12:05:53 +01:00
for_each_new_crtc_in_state ( state - > state , crtc , crtc_state , i ) {
/* we cannot disable the plane while the CRTC is active */
if ( ! state - > fb & & crtc_state - > active )
return - EINVAL ;
return drm_atomic_helper_check_plane_state ( state , crtc_state ,
DRM_PLANE_HELPER_NO_SCALING ,
DRM_PLANE_HELPER_NO_SCALING ,
false , true ) ;
2017-03-08 16:10:19 +00:00
}
2016-06-01 15:00:15 +01:00
2018-07-23 12:05:53 +01:00
return 0 ;
2015-04-02 19:48:39 +01:00
}
static void hdlcd_plane_atomic_update ( struct drm_plane * plane ,
struct drm_plane_state * state )
{
2016-11-18 21:52:45 +02:00
struct drm_framebuffer * fb = plane - > state - > fb ;
2015-04-02 19:48:39 +01:00
struct hdlcd_drm_private * hdlcd ;
2017-06-20 22:30:38 +02:00
u32 dest_h ;
2015-04-02 19:48:39 +01:00
dma_addr_t scanout_start ;
2016-11-18 21:52:45 +02:00
if ( ! fb )
2015-04-02 19:48:39 +01:00
return ;
2017-03-08 16:10:19 +00:00
dest_h = drm_rect_height ( & plane - > state - > dst ) ;
2017-06-13 12:18:03 +01:00
scanout_start = drm_fb_cma_get_gem_addr ( fb , plane - > state , 0 ) ;
2016-06-01 15:00:15 +01:00
hdlcd = plane - > dev - > dev_private ;
2016-11-18 21:52:45 +02:00
hdlcd_write ( hdlcd , HDLCD_REG_FB_LINE_LENGTH , fb - > pitches [ 0 ] ) ;
hdlcd_write ( hdlcd , HDLCD_REG_FB_LINE_PITCH , fb - > pitches [ 0 ] ) ;
2016-06-01 15:00:15 +01:00
hdlcd_write ( hdlcd , HDLCD_REG_FB_LINE_COUNT , dest_h - 1 ) ;
2015-04-02 19:48:39 +01:00
hdlcd_write ( hdlcd , HDLCD_REG_FB_BASE , scanout_start ) ;
}
static const struct drm_plane_helper_funcs hdlcd_plane_helper_funcs = {
. atomic_check = hdlcd_plane_atomic_check ,
. atomic_update = hdlcd_plane_atomic_update ,
} ;
static const struct drm_plane_funcs hdlcd_plane_funcs = {
. update_plane = drm_atomic_helper_update_plane ,
. disable_plane = drm_atomic_helper_disable_plane ,
2018-01-17 23:55:28 +02:00
. destroy = drm_plane_cleanup ,
2015-04-02 19:48:39 +01:00
. reset = drm_atomic_helper_plane_reset ,
. atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_plane_destroy_state ,
} ;
static struct drm_plane * hdlcd_plane_init ( struct drm_device * drm )
{
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
struct drm_plane * plane = NULL ;
u32 formats [ ARRAY_SIZE ( supported_formats ) ] , i ;
int ret ;
plane = devm_kzalloc ( drm - > dev , sizeof ( * plane ) , GFP_KERNEL ) ;
if ( ! plane )
return ERR_PTR ( - ENOMEM ) ;
for ( i = 0 ; i < ARRAY_SIZE ( supported_formats ) ; i + + )
formats [ i ] = supported_formats [ i ] . fourcc ;
ret = drm_universal_plane_init ( drm , plane , 0xff , & hdlcd_plane_funcs ,
formats , ARRAY_SIZE ( formats ) ,
2017-07-23 20:46:38 -07:00
NULL ,
2015-04-02 19:48:39 +01:00
DRM_PLANE_TYPE_PRIMARY , NULL ) ;
2017-10-30 11:36:54 -02:00
if ( ret )
2015-04-02 19:48:39 +01:00
return ERR_PTR ( ret ) ;
drm_plane_helper_add ( plane , & hdlcd_plane_helper_funcs ) ;
hdlcd - > plane = plane ;
return plane ;
}
int hdlcd_setup_crtc ( struct drm_device * drm )
{
struct hdlcd_drm_private * hdlcd = drm - > dev_private ;
struct drm_plane * primary ;
int ret ;
primary = hdlcd_plane_init ( drm ) ;
if ( IS_ERR ( primary ) )
return PTR_ERR ( primary ) ;
ret = drm_crtc_init_with_planes ( drm , & hdlcd - > crtc , primary , NULL ,
& hdlcd_crtc_funcs , NULL ) ;
2018-01-17 23:55:27 +02:00
if ( ret )
2015-04-02 19:48:39 +01:00
return ret ;
drm_crtc_helper_add ( & hdlcd - > crtc , & hdlcd_crtc_helper_funcs ) ;
return 0 ;
}