2014-12-30 17:08:16 +03:00
/*
* Copyright ( C ) STMicroelectronics SA 2014
* Author : Vincent Abriou < vincent . abriou @ st . com > for STMicroelectronics .
* License terms : GNU General Public License ( GPL ) , version 2
*/
# include <linux/clk.h>
# include <linux/component.h>
# include <linux/module.h>
# include <linux/of_gpio.h>
# include <linux/platform_device.h>
# include <drm/drmP.h>
2015-03-19 15:35:16 +03:00
# include <drm/drm_atomic_helper.h>
2014-12-30 17:08:16 +03:00
# include <drm/drm_crtc_helper.h>
# include <drm/drm_panel.h>
# include "sti_awg_utils.h"
# include "sti_mixer.h"
/* DVO registers */
# define DVO_AWG_DIGSYNC_CTRL 0x0000
# define DVO_DOF_CFG 0x0004
# define DVO_LUT_PROG_LOW 0x0008
# define DVO_LUT_PROG_MID 0x000C
# define DVO_LUT_PROG_HIGH 0x0010
# define DVO_DIGSYNC_INSTR_I 0x0100
# define DVO_AWG_CTRL_EN BIT(0)
# define DVO_AWG_FRAME_BASED_SYNC BIT(2)
# define DVO_DOF_EN_LOWBYTE BIT(0)
# define DVO_DOF_EN_MIDBYTE BIT(1)
# define DVO_DOF_EN_HIGHBYTE BIT(2)
# define DVO_DOF_EN BIT(6)
# define DVO_DOF_MOD_COUNT_SHIFT 8
# define DVO_LUT_ZERO 0
# define DVO_LUT_Y_G 1
# define DVO_LUT_Y_G_DEL 2
# define DVO_LUT_CB_B 3
# define DVO_LUT_CB_B_DEL 4
# define DVO_LUT_CR_R 5
# define DVO_LUT_CR_R_DEL 6
# define DVO_LUT_HOLD 7
struct dvo_config {
u32 flags ;
u32 lowbyte ;
u32 midbyte ;
u32 highbyte ;
int ( * awg_fwgen_fct ) (
struct awg_code_generation_params * fw_gen_params ,
struct awg_timing * timing ) ;
} ;
static struct dvo_config rgb_24bit_de_cfg = {
. flags = ( 0L < < DVO_DOF_MOD_COUNT_SHIFT ) ,
. lowbyte = DVO_LUT_CR_R ,
. midbyte = DVO_LUT_Y_G ,
. highbyte = DVO_LUT_CB_B ,
. awg_fwgen_fct = sti_awg_generate_code_data_enable_mode ,
} ;
/**
* STI digital video output structure
*
* @ dev : driver device
* @ drm_dev : pointer to drm device
* @ mode : current display mode selected
* @ regs : dvo registers
* @ clk_pix : pixel clock for dvo
* @ clk : clock for dvo
* @ clk_main_parent : dvo parent clock if main path used
* @ clk_aux_parent : dvo parent clock if aux path used
* @ panel_node : panel node reference from device tree
* @ panel : reference to the panel connected to the dvo
* @ enabled : true if dvo is enabled else false
* @ encoder : drm_encoder it is bound
*/
struct sti_dvo {
struct device dev ;
struct drm_device * drm_dev ;
struct drm_display_mode mode ;
void __iomem * regs ;
struct clk * clk_pix ;
struct clk * clk ;
struct clk * clk_main_parent ;
struct clk * clk_aux_parent ;
struct device_node * panel_node ;
struct drm_panel * panel ;
struct dvo_config * config ;
bool enabled ;
struct drm_encoder * encoder ;
2015-01-27 02:37:03 +03:00
struct drm_bridge * bridge ;
2014-12-30 17:08:16 +03:00
} ;
struct sti_dvo_connector {
struct drm_connector drm_connector ;
struct drm_encoder * encoder ;
struct sti_dvo * dvo ;
} ;
# define to_sti_dvo_connector(x) \
container_of ( x , struct sti_dvo_connector , drm_connector )
# define BLANKING_LEVEL 16
int dvo_awg_generate_code ( struct sti_dvo * dvo , u8 * ram_size , u32 * ram_code )
{
struct drm_display_mode * mode = & dvo - > mode ;
struct dvo_config * config = dvo - > config ;
struct awg_code_generation_params fw_gen_params ;
struct awg_timing timing ;
fw_gen_params . ram_code = ram_code ;
fw_gen_params . instruction_offset = 0 ;
timing . total_lines = mode - > vtotal ;
timing . active_lines = mode - > vdisplay ;
timing . blanking_lines = mode - > vsync_start - mode - > vdisplay ;
timing . trailing_lines = mode - > vtotal - mode - > vsync_start ;
timing . total_pixels = mode - > htotal ;
timing . active_pixels = mode - > hdisplay ;
timing . blanking_pixels = mode - > hsync_start - mode - > hdisplay ;
timing . trailing_pixels = mode - > htotal - mode - > hsync_start ;
timing . blanking_level = BLANKING_LEVEL ;
if ( config - > awg_fwgen_fct ( & fw_gen_params , & timing ) ) {
DRM_ERROR ( " AWG firmware not properly generated \n " ) ;
return - EINVAL ;
}
* ram_size = fw_gen_params . instruction_offset ;
return 0 ;
}
/* Configure AWG, writing instructions
*
* @ dvo : pointer to DVO structure
* @ awg_ram_code : pointer to AWG instructions table
* @ nb : nb of AWG instructions
*/
static void dvo_awg_configure ( struct sti_dvo * dvo , u32 * awg_ram_code , int nb )
{
int i ;
DRM_DEBUG_DRIVER ( " \n " ) ;
for ( i = 0 ; i < nb ; i + + )
writel ( awg_ram_code [ i ] ,
dvo - > regs + DVO_DIGSYNC_INSTR_I + i * 4 ) ;
for ( i = nb ; i < AWG_MAX_INST ; i + + )
writel ( 0 , dvo - > regs + DVO_DIGSYNC_INSTR_I + i * 4 ) ;
writel ( DVO_AWG_CTRL_EN , dvo - > regs + DVO_AWG_DIGSYNC_CTRL ) ;
}
static void sti_dvo_disable ( struct drm_bridge * bridge )
{
struct sti_dvo * dvo = bridge - > driver_private ;
if ( ! dvo - > enabled )
return ;
DRM_DEBUG_DRIVER ( " \n " ) ;
if ( dvo - > config - > awg_fwgen_fct )
writel ( 0x00000000 , dvo - > regs + DVO_AWG_DIGSYNC_CTRL ) ;
writel ( 0x00000000 , dvo - > regs + DVO_DOF_CFG ) ;
if ( dvo - > panel )
dvo - > panel - > funcs - > disable ( dvo - > panel ) ;
/* Disable/unprepare dvo clock */
clk_disable_unprepare ( dvo - > clk_pix ) ;
clk_disable_unprepare ( dvo - > clk ) ;
dvo - > enabled = false ;
}
static void sti_dvo_pre_enable ( struct drm_bridge * bridge )
{
struct sti_dvo * dvo = bridge - > driver_private ;
struct dvo_config * config = dvo - > config ;
u32 val ;
DRM_DEBUG_DRIVER ( " \n " ) ;
if ( dvo - > enabled )
return ;
/* Make sure DVO is disabled */
writel ( 0x00000000 , dvo - > regs + DVO_DOF_CFG ) ;
writel ( 0x00000000 , dvo - > regs + DVO_AWG_DIGSYNC_CTRL ) ;
if ( config - > awg_fwgen_fct ) {
u8 nb_instr ;
u32 awg_ram_code [ AWG_MAX_INST ] ;
/* Configure AWG */
if ( ! dvo_awg_generate_code ( dvo , & nb_instr , awg_ram_code ) )
dvo_awg_configure ( dvo , awg_ram_code , nb_instr ) ;
else
return ;
}
/* Prepare/enable clocks */
if ( clk_prepare_enable ( dvo - > clk_pix ) )
DRM_ERROR ( " Failed to prepare/enable dvo_pix clk \n " ) ;
if ( clk_prepare_enable ( dvo - > clk ) )
DRM_ERROR ( " Failed to prepare/enable dvo clk \n " ) ;
if ( dvo - > panel )
dvo - > panel - > funcs - > enable ( dvo - > panel ) ;
/* Set LUT */
writel ( config - > lowbyte , dvo - > regs + DVO_LUT_PROG_LOW ) ;
writel ( config - > midbyte , dvo - > regs + DVO_LUT_PROG_MID ) ;
writel ( config - > highbyte , dvo - > regs + DVO_LUT_PROG_HIGH ) ;
/* Digital output formatter config */
val = ( config - > flags | DVO_DOF_EN ) ;
writel ( val , dvo - > regs + DVO_DOF_CFG ) ;
dvo - > enabled = true ;
}
static void sti_dvo_set_mode ( struct drm_bridge * bridge ,
struct drm_display_mode * mode ,
struct drm_display_mode * adjusted_mode )
{
struct sti_dvo * dvo = bridge - > driver_private ;
struct sti_mixer * mixer = to_sti_mixer ( dvo - > encoder - > crtc ) ;
int rate = mode - > clock * 1000 ;
struct clk * clkp ;
int ret ;
DRM_DEBUG_DRIVER ( " \n " ) ;
memcpy ( & dvo - > mode , mode , sizeof ( struct drm_display_mode ) ) ;
/* According to the path used (main or aux), the dvo clocks should
* have a different parent clock . */
if ( mixer - > id = = STI_MIXER_MAIN )
clkp = dvo - > clk_main_parent ;
else
clkp = dvo - > clk_aux_parent ;
if ( clkp ) {
clk_set_parent ( dvo - > clk_pix , clkp ) ;
clk_set_parent ( dvo - > clk , clkp ) ;
}
/* DVO clocks = compositor clock */
ret = clk_set_rate ( dvo - > clk_pix , rate ) ;
if ( ret < 0 ) {
DRM_ERROR ( " Cannot set rate (%dHz) for dvo_pix clk \n " , rate ) ;
return ;
}
ret = clk_set_rate ( dvo - > clk , rate ) ;
if ( ret < 0 ) {
DRM_ERROR ( " Cannot set rate (%dHz) for dvo clk \n " , rate ) ;
return ;
}
/* For now, we only support 24bit data enable (DE) synchro format */
dvo - > config = & rgb_24bit_de_cfg ;
}
static void sti_dvo_bridge_nope ( struct drm_bridge * bridge )
{
/* do nothing */
}
static const struct drm_bridge_funcs sti_dvo_bridge_funcs = {
. pre_enable = sti_dvo_pre_enable ,
. enable = sti_dvo_bridge_nope ,
. disable = sti_dvo_disable ,
. post_disable = sti_dvo_bridge_nope ,
. mode_set = sti_dvo_set_mode ,
} ;
static int sti_dvo_connector_get_modes ( struct drm_connector * connector )
{
struct sti_dvo_connector * dvo_connector
= to_sti_dvo_connector ( connector ) ;
struct sti_dvo * dvo = dvo_connector - > dvo ;
if ( dvo - > panel )
return dvo - > panel - > funcs - > get_modes ( dvo - > panel ) ;
return 0 ;
}
# define CLK_TOLERANCE_HZ 50
static int sti_dvo_connector_mode_valid ( struct drm_connector * connector ,
struct drm_display_mode * mode )
{
int target = mode - > clock * 1000 ;
int target_min = target - CLK_TOLERANCE_HZ ;
int target_max = target + CLK_TOLERANCE_HZ ;
int result ;
struct sti_dvo_connector * dvo_connector
= to_sti_dvo_connector ( connector ) ;
struct sti_dvo * dvo = dvo_connector - > dvo ;
result = clk_round_rate ( dvo - > clk_pix , target ) ;
DRM_DEBUG_DRIVER ( " target rate = %d => available rate = %d \n " ,
target , result ) ;
if ( ( result < target_min ) | | ( result > target_max ) ) {
DRM_DEBUG_DRIVER ( " dvo pixclk=%d not supported \n " , target ) ;
return MODE_BAD ;
}
return MODE_OK ;
}
struct drm_encoder * sti_dvo_best_encoder ( struct drm_connector * connector )
{
struct sti_dvo_connector * dvo_connector
= to_sti_dvo_connector ( connector ) ;
/* Best encoder is the one associated during connector creation */
return dvo_connector - > encoder ;
}
static struct drm_connector_helper_funcs sti_dvo_connector_helper_funcs = {
. get_modes = sti_dvo_connector_get_modes ,
. mode_valid = sti_dvo_connector_mode_valid ,
. best_encoder = sti_dvo_best_encoder ,
} ;
static enum drm_connector_status
sti_dvo_connector_detect ( struct drm_connector * connector , bool force )
{
struct sti_dvo_connector * dvo_connector
= to_sti_dvo_connector ( connector ) ;
struct sti_dvo * dvo = dvo_connector - > dvo ;
DRM_DEBUG_DRIVER ( " \n " ) ;
if ( ! dvo - > panel )
dvo - > panel = of_drm_find_panel ( dvo - > panel_node ) ;
if ( dvo - > panel )
if ( ! drm_panel_attach ( dvo - > panel , connector ) )
return connector_status_connected ;
return connector_status_disconnected ;
}
static void sti_dvo_connector_destroy ( struct drm_connector * connector )
{
struct sti_dvo_connector * dvo_connector
= to_sti_dvo_connector ( connector ) ;
drm_connector_unregister ( connector ) ;
drm_connector_cleanup ( connector ) ;
kfree ( dvo_connector ) ;
}
static struct drm_connector_funcs sti_dvo_connector_funcs = {
2015-03-19 15:35:16 +03:00
. dpms = drm_atomic_helper_connector_dpms ,
2014-12-30 17:08:16 +03:00
. fill_modes = drm_helper_probe_single_connector_modes ,
. detect = sti_dvo_connector_detect ,
. destroy = sti_dvo_connector_destroy ,
2015-03-19 15:35:16 +03:00
. reset = drm_atomic_helper_connector_reset ,
. atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_connector_destroy_state ,
2014-12-30 17:08:16 +03:00
} ;
static struct drm_encoder * sti_dvo_find_encoder ( struct drm_device * dev )
{
struct drm_encoder * encoder ;
list_for_each_entry ( encoder , & dev - > mode_config . encoder_list , head ) {
if ( encoder - > encoder_type = = DRM_MODE_ENCODER_LVDS )
return encoder ;
}
return NULL ;
}
static int sti_dvo_bind ( struct device * dev , struct device * master , void * data )
{
struct sti_dvo * dvo = dev_get_drvdata ( dev ) ;
struct drm_device * drm_dev = data ;
struct drm_encoder * encoder ;
struct sti_dvo_connector * connector ;
struct drm_connector * drm_connector ;
struct drm_bridge * bridge ;
int err ;
/* Set the drm device handle */
dvo - > drm_dev = drm_dev ;
encoder = sti_dvo_find_encoder ( drm_dev ) ;
if ( ! encoder )
return - ENOMEM ;
connector = devm_kzalloc ( dev , sizeof ( * connector ) , GFP_KERNEL ) ;
if ( ! connector )
return - ENOMEM ;
connector - > dvo = dvo ;
bridge = devm_kzalloc ( dev , sizeof ( * bridge ) , GFP_KERNEL ) ;
if ( ! bridge )
return - ENOMEM ;
bridge - > driver_private = dvo ;
2015-01-27 02:37:03 +03:00
bridge - > funcs = & sti_dvo_bridge_funcs ;
bridge - > of_node = dvo - > dev . of_node ;
err = drm_bridge_add ( bridge ) ;
if ( err ) {
DRM_ERROR ( " Failed to add bridge \n " ) ;
return err ;
}
2014-12-30 17:08:16 +03:00
2015-01-27 02:37:03 +03:00
err = drm_bridge_attach ( drm_dev , bridge ) ;
if ( err ) {
DRM_ERROR ( " Failed to attach bridge \n " ) ;
return err ;
}
dvo - > bridge = bridge ;
2014-12-30 17:08:16 +03:00
encoder - > bridge = bridge ;
connector - > encoder = encoder ;
dvo - > encoder = encoder ;
drm_connector = ( struct drm_connector * ) connector ;
drm_connector - > polled = DRM_CONNECTOR_POLL_HPD ;
drm_connector_init ( drm_dev , drm_connector ,
& sti_dvo_connector_funcs , DRM_MODE_CONNECTOR_LVDS ) ;
drm_connector_helper_add ( drm_connector ,
& sti_dvo_connector_helper_funcs ) ;
err = drm_connector_register ( drm_connector ) ;
if ( err )
goto err_connector ;
err = drm_mode_connector_attach_encoder ( drm_connector , encoder ) ;
if ( err ) {
DRM_ERROR ( " Failed to attach a connector to a encoder \n " ) ;
goto err_sysfs ;
}
return 0 ;
err_sysfs :
drm_connector_unregister ( drm_connector ) ;
err_connector :
2015-01-27 02:37:03 +03:00
drm_bridge_remove ( bridge ) ;
2014-12-30 17:08:16 +03:00
drm_connector_cleanup ( drm_connector ) ;
return - EINVAL ;
}
static void sti_dvo_unbind ( struct device * dev ,
struct device * master , void * data )
{
2015-01-27 02:37:03 +03:00
struct sti_dvo * dvo = dev_get_drvdata ( dev ) ;
drm_bridge_remove ( dvo - > bridge ) ;
2014-12-30 17:08:16 +03:00
}
static const struct component_ops sti_dvo_ops = {
. bind = sti_dvo_bind ,
. unbind = sti_dvo_unbind ,
} ;
static int sti_dvo_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct sti_dvo * dvo ;
struct resource * res ;
struct device_node * np = dev - > of_node ;
DRM_INFO ( " %s \n " , __func__ ) ;
dvo = devm_kzalloc ( dev , sizeof ( * dvo ) , GFP_KERNEL ) ;
if ( ! dvo ) {
DRM_ERROR ( " Failed to allocate memory for DVO \n " ) ;
return - ENOMEM ;
}
dvo - > dev = pdev - > dev ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " dvo-reg " ) ;
if ( ! res ) {
DRM_ERROR ( " Invalid dvo resource \n " ) ;
return - ENOMEM ;
}
dvo - > regs = devm_ioremap_nocache ( dev , res - > start ,
resource_size ( res ) ) ;
2015-06-08 16:25:52 +03:00
if ( ! dvo - > regs )
return - ENOMEM ;
2014-12-30 17:08:16 +03:00
dvo - > clk_pix = devm_clk_get ( dev , " dvo_pix " ) ;
if ( IS_ERR ( dvo - > clk_pix ) ) {
DRM_ERROR ( " Cannot get dvo_pix clock \n " ) ;
return PTR_ERR ( dvo - > clk_pix ) ;
}
dvo - > clk = devm_clk_get ( dev , " dvo " ) ;
if ( IS_ERR ( dvo - > clk ) ) {
DRM_ERROR ( " Cannot get dvo clock \n " ) ;
return PTR_ERR ( dvo - > clk ) ;
}
dvo - > clk_main_parent = devm_clk_get ( dev , " main_parent " ) ;
if ( IS_ERR ( dvo - > clk_main_parent ) ) {
DRM_DEBUG_DRIVER ( " Cannot get main_parent clock \n " ) ;
dvo - > clk_main_parent = NULL ;
}
dvo - > clk_aux_parent = devm_clk_get ( dev , " aux_parent " ) ;
if ( IS_ERR ( dvo - > clk_aux_parent ) ) {
DRM_DEBUG_DRIVER ( " Cannot get aux_parent clock \n " ) ;
dvo - > clk_aux_parent = NULL ;
}
dvo - > panel_node = of_parse_phandle ( np , " sti,panel " , 0 ) ;
if ( ! dvo - > panel_node )
DRM_ERROR ( " No panel associated to the dvo output \n " ) ;
platform_set_drvdata ( pdev , dvo ) ;
return component_add ( & pdev - > dev , & sti_dvo_ops ) ;
}
static int sti_dvo_remove ( struct platform_device * pdev )
{
component_del ( & pdev - > dev , & sti_dvo_ops ) ;
return 0 ;
}
static struct of_device_id dvo_of_match [ ] = {
{ . compatible = " st,stih407-dvo " , } ,
{ /* end node */ }
} ;
MODULE_DEVICE_TABLE ( of , dvo_of_match ) ;
struct platform_driver sti_dvo_driver = {
. driver = {
. name = " sti-dvo " ,
. owner = THIS_MODULE ,
. of_match_table = dvo_of_match ,
} ,
. probe = sti_dvo_probe ,
. remove = sti_dvo_remove ,
} ;
module_platform_driver ( sti_dvo_driver ) ;
MODULE_AUTHOR ( " Benjamin Gaignard <benjamin.gaignard@st.com> " ) ;
MODULE_DESCRIPTION ( " STMicroelectronics SoC DRM driver " ) ;
MODULE_LICENSE ( " GPL " ) ;