2019-05-19 15:51:43 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-11-10 15:29:37 +01:00
/*
* Copyright ( C ) 2016 BayLibre , SAS
* Author : Neil Armstrong < narmstrong @ baylibre . com >
* Copyright ( C ) 2015 Amlogic , Inc . All rights reserved .
* Copyright ( C ) 2014 Endless Mobile
*
* Written by :
* Jasper St . Pierre < jstpierre @ mecheye . net >
*/
2019-07-16 08:42:02 +02:00
# include <linux/export.h>
2016-11-10 15:29:37 +01:00
# include <linux/of_graph.h>
# include <drm/drm_atomic_helper.h>
2021-10-20 14:39:47 +02:00
# include <drm/drm_bridge.h>
# include <drm/drm_bridge_connector.h>
2019-07-16 08:42:02 +02:00
# include <drm/drm_device.h>
2019-01-17 22:03:34 +01:00
# include <drm/drm_edid.h>
# include <drm/drm_probe_helper.h>
2021-10-20 14:39:47 +02:00
# include <drm/drm_simple_kms_helper.h>
2016-11-10 15:29:37 +01:00
# include "meson_registers.h"
2019-07-16 08:42:02 +02:00
# include "meson_vclk.h"
2021-10-20 14:39:46 +02:00
# include "meson_encoder_cvbs.h"
2016-11-10 15:29:37 +01:00
/* HHI VDAC Registers */
# define HHI_VDAC_CNTL0 0x2F4 /* 0xbd offset in data sheet */
2019-03-25 15:18:21 +01:00
# define HHI_VDAC_CNTL0_G12A 0x2EC /* 0xbd offset in data sheet */
2016-11-10 15:29:37 +01:00
# define HHI_VDAC_CNTL1 0x2F8 /* 0xbe offset in data sheet */
2019-03-25 15:18:21 +01:00
# define HHI_VDAC_CNTL1_G12A 0x2F0 /* 0xbe offset in data sheet */
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:46 +02:00
struct meson_encoder_cvbs {
2016-11-10 15:29:37 +01:00
struct drm_encoder encoder ;
2021-10-20 14:39:47 +02:00
struct drm_bridge bridge ;
struct drm_bridge * next_bridge ;
2016-11-10 15:29:37 +01:00
struct meson_drm * priv ;
} ;
2021-10-20 14:39:47 +02:00
# define bridge_to_meson_encoder_cvbs(x) \
container_of ( x , struct meson_encoder_cvbs , bridge )
2016-11-10 15:29:37 +01:00
/* Supported Modes */
struct meson_cvbs_mode meson_cvbs_modes [ MESON_CVBS_MODES_COUNT ] = {
{ /* PAL */
. enci = & meson_cvbs_enci_pal ,
. mode = {
DRM_MODE ( " 720x576i " , DRM_MODE_TYPE_DRIVER , 13500 ,
720 , 732 , 795 , 864 , 0 , 576 , 580 , 586 , 625 , 0 ,
DRM_MODE_FLAG_INTERLACE ) ,
. picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3 ,
} ,
} ,
{ /* NTSC */
. enci = & meson_cvbs_enci_ntsc ,
. mode = {
DRM_MODE ( " 720x480i " , DRM_MODE_TYPE_DRIVER , 13500 ,
720 , 739 , 801 , 858 , 0 , 480 , 488 , 494 , 525 , 0 ,
DRM_MODE_FLAG_INTERLACE ) ,
. picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3 ,
} ,
} ,
} ;
2019-12-08 18:18:31 +01:00
static const struct meson_cvbs_mode *
meson_cvbs_get_mode ( const struct drm_display_mode * req_mode )
{
int i ;
for ( i = 0 ; i < MESON_CVBS_MODES_COUNT ; + + i ) {
struct meson_cvbs_mode * meson_mode = & meson_cvbs_modes [ i ] ;
if ( drm_mode_match ( req_mode , & meson_mode - > mode ,
DRM_MODE_MATCH_TIMINGS |
DRM_MODE_MATCH_CLOCK |
DRM_MODE_MATCH_FLAGS |
DRM_MODE_MATCH_3D_FLAGS ) )
return meson_mode ;
}
return NULL ;
}
2021-10-20 14:39:47 +02:00
static int meson_encoder_cvbs_attach ( struct drm_bridge * bridge ,
enum drm_bridge_attach_flags flags )
2016-11-10 15:29:37 +01:00
{
2021-10-20 14:39:47 +02:00
struct meson_encoder_cvbs * meson_encoder_cvbs =
bridge_to_meson_encoder_cvbs ( bridge ) ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
return drm_bridge_attach ( bridge - > encoder , meson_encoder_cvbs - > next_bridge ,
& meson_encoder_cvbs - > bridge , flags ) ;
2016-11-10 15:29:37 +01:00
}
2021-10-20 14:39:47 +02:00
static int meson_encoder_cvbs_get_modes ( struct drm_bridge * bridge ,
struct drm_connector * connector )
2016-11-10 15:29:37 +01:00
{
2021-10-20 14:39:47 +02:00
struct meson_encoder_cvbs * meson_encoder_cvbs =
bridge_to_meson_encoder_cvbs ( bridge ) ;
struct meson_drm * priv = meson_encoder_cvbs - > priv ;
2016-11-10 15:29:37 +01:00
struct drm_display_mode * mode ;
int i ;
for ( i = 0 ; i < MESON_CVBS_MODES_COUNT ; + + i ) {
struct meson_cvbs_mode * meson_mode = & meson_cvbs_modes [ i ] ;
2021-10-20 14:39:47 +02:00
mode = drm_mode_duplicate ( priv - > drm , & meson_mode - > mode ) ;
2016-11-10 15:29:37 +01:00
if ( ! mode ) {
2021-10-20 14:39:47 +02:00
dev_err ( priv - > dev , " Failed to create a new display mode \n " ) ;
2016-11-10 15:29:37 +01:00
return 0 ;
}
drm_mode_probed_add ( connector , mode ) ;
}
return i ;
}
2021-10-20 14:39:47 +02:00
static int meson_encoder_cvbs_mode_valid ( struct drm_bridge * bridge ,
const struct drm_display_info * display_info ,
const struct drm_display_mode * mode )
2016-11-10 15:29:37 +01:00
{
2021-10-20 14:39:47 +02:00
if ( meson_cvbs_get_mode ( mode ) )
return MODE_OK ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
return MODE_BAD ;
2016-11-10 15:29:37 +01:00
}
2021-10-20 14:39:47 +02:00
static int meson_encoder_cvbs_atomic_check ( struct drm_bridge * bridge ,
struct drm_bridge_state * bridge_state ,
2016-11-10 15:29:37 +01:00
struct drm_crtc_state * crtc_state ,
struct drm_connector_state * conn_state )
{
2019-12-08 18:18:31 +01:00
if ( meson_cvbs_get_mode ( & crtc_state - > mode ) )
return 0 ;
2016-11-10 15:29:37 +01:00
return - EINVAL ;
}
2021-10-20 14:39:47 +02:00
static void meson_encoder_cvbs_atomic_enable ( struct drm_bridge * bridge ,
struct drm_bridge_state * bridge_state )
2016-11-10 15:29:37 +01:00
{
2021-10-20 14:39:47 +02:00
struct meson_encoder_cvbs * encoder_cvbs = bridge_to_meson_encoder_cvbs ( bridge ) ;
struct drm_atomic_state * state = bridge_state - > base . state ;
struct meson_drm * priv = encoder_cvbs - > priv ;
const struct meson_cvbs_mode * meson_mode ;
struct drm_connector_state * conn_state ;
struct drm_crtc_state * crtc_state ;
struct drm_connector * connector ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
connector = drm_atomic_get_new_connector_for_encoder ( state , bridge - > encoder ) ;
if ( WARN_ON ( ! connector ) )
return ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
conn_state = drm_atomic_get_new_connector_state ( state , connector ) ;
if ( WARN_ON ( ! conn_state ) )
return ;
crtc_state = drm_atomic_get_new_crtc_state ( state , conn_state - > crtc ) ;
if ( WARN_ON ( ! crtc_state ) )
return ;
meson_mode = meson_cvbs_get_mode ( & crtc_state - > adjusted_mode ) ;
if ( WARN_ON ( ! meson_mode ) )
return ;
meson_venci_cvbs_mode_set ( priv , meson_mode - > enci ) ;
/* Setup 27MHz vclk2 for ENCI and VDAC */
meson_vclk_setup ( priv , MESON_VCLK_TARGET_CVBS ,
MESON_VCLK_CVBS , MESON_VCLK_CVBS ,
MESON_VCLK_CVBS , MESON_VCLK_CVBS ,
true ) ;
2016-11-10 15:29:37 +01:00
/* VDAC0 source is not from ATV */
2019-06-24 16:48:50 +02:00
writel_bits_relaxed ( VENC_VDAC_SEL_ATV_DMD , 0 ,
priv - > io_base + _REG ( VENC_VDAC_DACSEL0 ) ) ;
2016-11-10 15:29:37 +01:00
2019-08-22 16:43:41 +02:00
if ( meson_vpu_is_compatible ( priv , VPU_COMPATIBLE_GXBB ) ) {
2016-11-10 15:29:37 +01:00
regmap_write ( priv - > hhi , HHI_VDAC_CNTL0 , 1 ) ;
2019-03-25 15:18:21 +01:00
regmap_write ( priv - > hhi , HHI_VDAC_CNTL1 , 0 ) ;
2019-08-22 16:43:41 +02:00
} else if ( meson_vpu_is_compatible ( priv , VPU_COMPATIBLE_GXM ) | |
meson_vpu_is_compatible ( priv , VPU_COMPATIBLE_GXL ) ) {
2016-11-10 15:29:37 +01:00
regmap_write ( priv - > hhi , HHI_VDAC_CNTL0 , 0xf0001 ) ;
2019-03-25 15:18:21 +01:00
regmap_write ( priv - > hhi , HHI_VDAC_CNTL1 , 0 ) ;
2019-08-22 16:43:41 +02:00
} else if ( meson_vpu_is_compatible ( priv , VPU_COMPATIBLE_G12A ) ) {
2019-03-25 15:18:21 +01:00
regmap_write ( priv - > hhi , HHI_VDAC_CNTL0_G12A , 0x906001 ) ;
regmap_write ( priv - > hhi , HHI_VDAC_CNTL1_G12A , 0 ) ;
}
2016-11-10 15:29:37 +01:00
}
2021-10-20 14:39:47 +02:00
static void meson_encoder_cvbs_atomic_disable ( struct drm_bridge * bridge ,
struct drm_bridge_state * bridge_state )
2016-11-10 15:29:37 +01:00
{
2021-10-20 14:39:46 +02:00
struct meson_encoder_cvbs * meson_encoder_cvbs =
2021-10-20 14:39:47 +02:00
bridge_to_meson_encoder_cvbs ( bridge ) ;
2021-10-20 14:39:46 +02:00
struct meson_drm * priv = meson_encoder_cvbs - > priv ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
/* Disable CVBS VDAC */
if ( meson_vpu_is_compatible ( priv , VPU_COMPATIBLE_G12A ) ) {
regmap_write ( priv - > hhi , HHI_VDAC_CNTL0_G12A , 0 ) ;
regmap_write ( priv - > hhi , HHI_VDAC_CNTL1_G12A , 0 ) ;
} else {
regmap_write ( priv - > hhi , HHI_VDAC_CNTL0 , 0 ) ;
regmap_write ( priv - > hhi , HHI_VDAC_CNTL1 , 8 ) ;
2016-11-10 15:29:37 +01:00
}
}
2021-10-20 14:39:47 +02:00
static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = {
. attach = meson_encoder_cvbs_attach ,
. mode_valid = meson_encoder_cvbs_mode_valid ,
. get_modes = meson_encoder_cvbs_get_modes ,
. atomic_enable = meson_encoder_cvbs_atomic_enable ,
. atomic_disable = meson_encoder_cvbs_atomic_disable ,
. atomic_check = meson_encoder_cvbs_atomic_check ,
. atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_bridge_destroy_state ,
. atomic_reset = drm_atomic_helper_bridge_reset ,
2016-11-10 15:29:37 +01:00
} ;
2021-10-20 14:39:46 +02:00
int meson_encoder_cvbs_init ( struct meson_drm * priv )
2016-11-10 15:29:37 +01:00
{
struct drm_device * drm = priv - > drm ;
2021-10-20 14:39:46 +02:00
struct meson_encoder_cvbs * meson_encoder_cvbs ;
2016-11-10 15:29:37 +01:00
struct drm_connector * connector ;
2021-10-20 14:39:47 +02:00
struct device_node * remote ;
2016-11-10 15:29:37 +01:00
int ret ;
2021-10-20 14:39:47 +02:00
meson_encoder_cvbs = devm_kzalloc ( priv - > dev , sizeof ( * meson_encoder_cvbs ) , GFP_KERNEL ) ;
if ( ! meson_encoder_cvbs )
return - ENOMEM ;
/* CVBS Connector Bridge */
remote = of_graph_get_remote_node ( priv - > dev - > of_node , 0 , 0 ) ;
if ( ! remote ) {
2016-11-10 15:29:37 +01:00
dev_info ( drm - > dev , " CVBS Output connector not available \n " ) ;
2017-04-04 14:15:24 +02:00
return 0 ;
2016-11-10 15:29:37 +01:00
}
2021-10-20 14:39:47 +02:00
meson_encoder_cvbs - > next_bridge = of_drm_find_bridge ( remote ) ;
if ( ! meson_encoder_cvbs - > next_bridge ) {
dev_err ( priv - > dev , " Failed to find CVBS Connector bridge \n " ) ;
return - EPROBE_DEFER ;
}
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
/* CVBS Encoder Bridge */
meson_encoder_cvbs - > bridge . funcs = & meson_encoder_cvbs_bridge_funcs ;
meson_encoder_cvbs - > bridge . of_node = priv - > dev - > of_node ;
meson_encoder_cvbs - > bridge . type = DRM_MODE_CONNECTOR_Composite ;
meson_encoder_cvbs - > bridge . ops = DRM_BRIDGE_OP_MODES ;
meson_encoder_cvbs - > bridge . interlace_allowed = true ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
drm_bridge_add ( & meson_encoder_cvbs - > bridge ) ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
meson_encoder_cvbs - > priv = priv ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
/* Encoder */
ret = drm_simple_encoder_init ( priv - > drm , & meson_encoder_cvbs - > encoder ,
DRM_MODE_ENCODER_TVDAC ) ;
2016-11-10 15:29:37 +01:00
if ( ret ) {
2021-10-20 14:39:47 +02:00
dev_err ( priv - > dev , " Failed to init CVBS encoder: %d \n " , ret ) ;
2016-11-10 15:29:37 +01:00
return ret ;
}
2021-10-20 14:39:47 +02:00
meson_encoder_cvbs - > encoder . possible_crtcs = BIT ( 0 ) ;
2016-11-10 15:29:37 +01:00
2021-10-20 14:39:47 +02:00
/* Attach CVBS Encoder Bridge to Encoder */
ret = drm_bridge_attach ( & meson_encoder_cvbs - > encoder , & meson_encoder_cvbs - > bridge , NULL ,
DRM_BRIDGE_ATTACH_NO_CONNECTOR ) ;
2016-11-10 15:29:37 +01:00
if ( ret ) {
2021-10-20 14:39:47 +02:00
dev_err ( priv - > dev , " Failed to attach bridge: %d \n " , ret ) ;
2016-11-10 15:29:37 +01:00
return ret ;
}
2021-10-20 14:39:47 +02:00
/* Initialize & attach Bridge Connector */
connector = drm_bridge_connector_init ( priv - > drm , & meson_encoder_cvbs - > encoder ) ;
if ( IS_ERR ( connector ) ) {
dev_err ( priv - > dev , " Unable to create CVBS bridge connector \n " ) ;
return PTR_ERR ( connector ) ;
}
drm_connector_attach_encoder ( connector , & meson_encoder_cvbs - > encoder ) ;
2016-11-10 15:29:37 +01:00
return 0 ;
}