2014-07-30 20:20:56 +04:00
/*
* Copyright ( C ) STMicroelectronics SA 2014
* Authors : Benjamin Gaignard < benjamin . gaignard @ st . com >
* Fabien Dessenne < fabien . dessenne @ st . com >
* Vincent Abriou < vincent . abriou @ st . com >
* for STMicroelectronics .
* License terms : GNU General Public License ( GPL ) , version 2
*/
# include <linux/module.h>
# include <linux/notifier.h>
# include <linux/platform_device.h>
# include <drm/drmP.h>
# include "sti_vtg.h"
# define VTG_TYPE_MASTER 0
# define VTG_TYPE_SLAVE_BY_EXT0 1
/* registers offset */
# define VTG_MODE 0x0000
# define VTG_CLKLN 0x0008
# define VTG_HLFLN 0x000C
# define VTG_DRST_AUTOC 0x0010
# define VTG_VID_TFO 0x0040
# define VTG_VID_TFS 0x0044
# define VTG_VID_BFO 0x0048
# define VTG_VID_BFS 0x004C
# define VTG_HOST_ITS 0x0078
# define VTG_HOST_ITS_BCLR 0x007C
# define VTG_HOST_ITM_BCLR 0x0088
# define VTG_HOST_ITM_BSET 0x008C
# define VTG_H_HD_1 0x00C0
# define VTG_TOP_V_VD_1 0x00C4
# define VTG_BOT_V_VD_1 0x00C8
# define VTG_TOP_V_HD_1 0x00CC
# define VTG_BOT_V_HD_1 0x00D0
# define VTG_H_HD_2 0x00E0
# define VTG_TOP_V_VD_2 0x00E4
# define VTG_BOT_V_VD_2 0x00E8
# define VTG_TOP_V_HD_2 0x00EC
# define VTG_BOT_V_HD_2 0x00F0
# define VTG_H_HD_3 0x0100
# define VTG_TOP_V_VD_3 0x0104
# define VTG_BOT_V_VD_3 0x0108
# define VTG_TOP_V_HD_3 0x010C
# define VTG_BOT_V_HD_3 0x0110
2014-12-05 12:14:11 +03:00
# define VTG_H_HD_4 0x0120
# define VTG_TOP_V_VD_4 0x0124
# define VTG_BOT_V_VD_4 0x0128
# define VTG_TOP_V_HD_4 0x012c
# define VTG_BOT_V_HD_4 0x0130
2014-07-30 20:20:56 +04:00
# define VTG_IRQ_BOTTOM BIT(0)
# define VTG_IRQ_TOP BIT(1)
# define VTG_IRQ_MASK (VTG_IRQ_TOP | VTG_IRQ_BOTTOM)
2014-12-05 12:14:11 +03:00
/* Delay introduced by the HDMI in nb of pixel */
# define HDMI_DELAY (6)
2014-07-30 20:20:56 +04:00
/* delay introduced by the Arbitrary Waveform Generator in nb of pixels */
# define AWG_DELAY_HD (-9)
# define AWG_DELAY_ED (-8)
# define AWG_DELAY_SD (-7)
LIST_HEAD ( vtg_lookup ) ;
/**
* STI VTG structure
*
* @ dev : pointer to device driver
* @ data : data associated to the device
* @ irq : VTG irq
* @ type : VTG type ( main or aux )
* @ notifier_list : notifier callback
* @ crtc_id : the crtc id for vblank event
* @ slave : slave vtg
* @ link : List node to link the structure in lookup list
*/
struct sti_vtg {
struct device * dev ;
struct device_node * np ;
void __iomem * regs ;
int irq ;
u32 irq_status ;
struct raw_notifier_head notifier_list ;
int crtc_id ;
struct sti_vtg * slave ;
struct list_head link ;
} ;
static void vtg_register ( struct sti_vtg * vtg )
{
list_add_tail ( & vtg - > link , & vtg_lookup ) ;
}
struct sti_vtg * of_vtg_find ( struct device_node * np )
{
struct sti_vtg * vtg ;
list_for_each_entry ( vtg , & vtg_lookup , link ) {
if ( vtg - > np = = np )
return vtg ;
}
return NULL ;
}
EXPORT_SYMBOL ( of_vtg_find ) ;
static void vtg_reset ( struct sti_vtg * vtg )
{
/* reset slave and then master */
if ( vtg - > slave )
vtg_reset ( vtg - > slave ) ;
writel ( 1 , vtg - > regs + VTG_DRST_AUTOC ) ;
}
static void vtg_set_mode ( struct sti_vtg * vtg ,
int type , const struct drm_display_mode * mode )
{
u32 tmp ;
if ( vtg - > slave )
vtg_set_mode ( vtg - > slave , VTG_TYPE_SLAVE_BY_EXT0 , mode ) ;
writel ( mode - > htotal , vtg - > regs + VTG_CLKLN ) ;
writel ( mode - > vtotal * 2 , vtg - > regs + VTG_HLFLN ) ;
tmp = ( mode - > vtotal - mode - > vsync_start + 1 ) < < 16 ;
tmp | = mode - > htotal - mode - > hsync_start ;
writel ( tmp , vtg - > regs + VTG_VID_TFO ) ;
writel ( tmp , vtg - > regs + VTG_VID_BFO ) ;
tmp = ( mode - > vdisplay + mode - > vtotal - mode - > vsync_start + 1 ) < < 16 ;
tmp | = mode - > hdisplay + mode - > htotal - mode - > hsync_start ;
writel ( tmp , vtg - > regs + VTG_VID_TFS ) ;
writel ( tmp , vtg - > regs + VTG_VID_BFS ) ;
2014-12-05 12:14:11 +03:00
/* prepare VTG set 1 for HDMI */
tmp = ( mode - > hsync_end - mode - > hsync_start + HDMI_DELAY ) < < 16 ;
tmp | = HDMI_DELAY ;
2014-07-30 20:20:56 +04:00
writel ( tmp , vtg - > regs + VTG_H_HD_1 ) ;
tmp = ( mode - > vsync_end - mode - > vsync_start + 1 ) < < 16 ;
tmp | = 1 ;
writel ( tmp , vtg - > regs + VTG_TOP_V_VD_1 ) ;
writel ( tmp , vtg - > regs + VTG_BOT_V_VD_1 ) ;
writel ( 0 , vtg - > regs + VTG_TOP_V_HD_1 ) ;
writel ( 0 , vtg - > regs + VTG_BOT_V_HD_1 ) ;
/* prepare VTG set 2 for for HD DCS */
2014-12-05 12:14:11 +03:00
tmp = ( mode - > hsync_end - mode - > hsync_start ) < < 16 ;
writel ( tmp , vtg - > regs + VTG_H_HD_2 ) ;
tmp = ( mode - > vsync_end - mode - > vsync_start + 1 ) < < 16 ;
tmp | = 1 ;
2014-07-30 20:20:56 +04:00
writel ( tmp , vtg - > regs + VTG_TOP_V_VD_2 ) ;
writel ( tmp , vtg - > regs + VTG_BOT_V_VD_2 ) ;
writel ( 0 , vtg - > regs + VTG_TOP_V_HD_2 ) ;
writel ( 0 , vtg - > regs + VTG_BOT_V_HD_2 ) ;
/* prepare VTG set 3 for HD Analog in HD mode */
tmp = ( mode - > hsync_end - mode - > hsync_start + AWG_DELAY_HD ) < < 16 ;
tmp | = mode - > htotal + AWG_DELAY_HD ;
writel ( tmp , vtg - > regs + VTG_H_HD_3 ) ;
tmp = ( mode - > vsync_end - mode - > vsync_start ) < < 16 ;
tmp | = mode - > vtotal ;
writel ( tmp , vtg - > regs + VTG_TOP_V_VD_3 ) ;
writel ( tmp , vtg - > regs + VTG_BOT_V_VD_3 ) ;
tmp = ( mode - > htotal + AWG_DELAY_HD ) < < 16 ;
tmp | = mode - > htotal + AWG_DELAY_HD ;
writel ( tmp , vtg - > regs + VTG_TOP_V_HD_3 ) ;
writel ( tmp , vtg - > regs + VTG_BOT_V_HD_3 ) ;
2014-12-05 12:14:11 +03:00
/* Prepare VTG set 4 for DVO */
tmp = ( mode - > hsync_end - mode - > hsync_start ) < < 16 ;
writel ( tmp , vtg - > regs + VTG_H_HD_4 ) ;
tmp = ( mode - > vsync_end - mode - > vsync_start + 1 ) < < 16 ;
tmp | = 1 ;
writel ( tmp , vtg - > regs + VTG_TOP_V_VD_4 ) ;
writel ( tmp , vtg - > regs + VTG_BOT_V_VD_4 ) ;
writel ( 0 , vtg - > regs + VTG_TOP_V_HD_4 ) ;
writel ( 0 , vtg - > regs + VTG_BOT_V_HD_4 ) ;
2014-07-30 20:20:56 +04:00
/* mode */
writel ( type , vtg - > regs + VTG_MODE ) ;
}
static void vtg_enable_irq ( struct sti_vtg * vtg )
{
/* clear interrupt status and mask */
writel ( 0xFFFF , vtg - > regs + VTG_HOST_ITS_BCLR ) ;
writel ( 0xFFFF , vtg - > regs + VTG_HOST_ITM_BCLR ) ;
writel ( VTG_IRQ_MASK , vtg - > regs + VTG_HOST_ITM_BSET ) ;
}
void sti_vtg_set_config ( struct sti_vtg * vtg ,
const struct drm_display_mode * mode )
{
/* write configuration */
vtg_set_mode ( vtg , VTG_TYPE_MASTER , mode ) ;
vtg_reset ( vtg ) ;
/* enable irq for the vtg vblank synchro */
if ( vtg - > slave )
vtg_enable_irq ( vtg - > slave ) ;
else
vtg_enable_irq ( vtg ) ;
}
EXPORT_SYMBOL ( sti_vtg_set_config ) ;
/**
* sti_vtg_get_line_number
*
* @ mode : display mode to be used
* @ y : line
*
* Return the line number according to the display mode taking
* into account the Sync and Back Porch information .
* Video frame line numbers start at 1 , y starts at 0.
* In interlaced modes the start line is the field line number of the odd
* field , but y is still defined as a progressive frame .
*/
u32 sti_vtg_get_line_number ( struct drm_display_mode mode , int y )
{
u32 start_line = mode . vtotal - mode . vsync_start + 1 ;
if ( mode . flags & DRM_MODE_FLAG_INTERLACE )
start_line * = 2 ;
return start_line + y ;
}
EXPORT_SYMBOL ( sti_vtg_get_line_number ) ;
/**
* sti_vtg_get_pixel_number
*
* @ mode : display mode to be used
* @ x : row
*
* Return the pixel number according to the display mode taking
* into account the Sync and Back Porch information .
* Pixels are counted from 0.
*/
u32 sti_vtg_get_pixel_number ( struct drm_display_mode mode , int x )
{
return mode . htotal - mode . hsync_start + x ;
}
EXPORT_SYMBOL ( sti_vtg_get_pixel_number ) ;
int sti_vtg_register_client ( struct sti_vtg * vtg ,
struct notifier_block * nb , int crtc_id )
{
if ( vtg - > slave )
return sti_vtg_register_client ( vtg - > slave , nb , crtc_id ) ;
vtg - > crtc_id = crtc_id ;
return raw_notifier_chain_register ( & vtg - > notifier_list , nb ) ;
}
EXPORT_SYMBOL ( sti_vtg_register_client ) ;
int sti_vtg_unregister_client ( struct sti_vtg * vtg , struct notifier_block * nb )
{
if ( vtg - > slave )
return sti_vtg_unregister_client ( vtg - > slave , nb ) ;
return raw_notifier_chain_unregister ( & vtg - > notifier_list , nb ) ;
}
EXPORT_SYMBOL ( sti_vtg_unregister_client ) ;
static irqreturn_t vtg_irq_thread ( int irq , void * arg )
{
struct sti_vtg * vtg = arg ;
u32 event ;
event = ( vtg - > irq_status & VTG_IRQ_TOP ) ?
VTG_TOP_FIELD_EVENT : VTG_BOTTOM_FIELD_EVENT ;
raw_notifier_call_chain ( & vtg - > notifier_list , event , & vtg - > crtc_id ) ;
return IRQ_HANDLED ;
}
static irqreturn_t vtg_irq ( int irq , void * arg )
{
struct sti_vtg * vtg = arg ;
vtg - > irq_status = readl ( vtg - > regs + VTG_HOST_ITS ) ;
writel ( vtg - > irq_status , vtg - > regs + VTG_HOST_ITS_BCLR ) ;
/* force sync bus write */
readl ( vtg - > regs + VTG_HOST_ITS ) ;
return IRQ_WAKE_THREAD ;
}
static int vtg_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * np ;
struct sti_vtg * vtg ;
struct resource * res ;
char irq_name [ 32 ] ;
int ret ;
vtg = devm_kzalloc ( dev , sizeof ( * vtg ) , GFP_KERNEL ) ;
if ( ! vtg )
return - ENOMEM ;
vtg - > dev = dev ;
vtg - > np = pdev - > dev . of_node ;
/* Get Memory ressources */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
DRM_ERROR ( " Get memory resource failed \n " ) ;
return - ENOMEM ;
}
vtg - > regs = devm_ioremap_nocache ( dev , res - > start , resource_size ( res ) ) ;
np = of_parse_phandle ( pdev - > dev . of_node , " st,slave " , 0 ) ;
if ( np ) {
vtg - > slave = of_vtg_find ( np ) ;
if ( ! vtg - > slave )
return - EPROBE_DEFER ;
} else {
vtg - > irq = platform_get_irq ( pdev , 0 ) ;
if ( IS_ERR_VALUE ( vtg - > irq ) ) {
DRM_ERROR ( " Failed to get VTG interrupt \n " ) ;
return vtg - > irq ;
}
snprintf ( irq_name , sizeof ( irq_name ) , " vsync-%s " ,
dev_name ( vtg - > dev ) ) ;
RAW_INIT_NOTIFIER_HEAD ( & vtg - > notifier_list ) ;
ret = devm_request_threaded_irq ( dev , vtg - > irq , vtg_irq ,
vtg_irq_thread , IRQF_ONESHOT , irq_name , vtg ) ;
if ( IS_ERR_VALUE ( ret ) ) {
DRM_ERROR ( " Failed to register VTG interrupt \n " ) ;
return ret ;
}
}
vtg_register ( vtg ) ;
platform_set_drvdata ( pdev , vtg ) ;
DRM_INFO ( " %s %s \n " , __func__ , dev_name ( vtg - > dev ) ) ;
return 0 ;
}
static int vtg_remove ( struct platform_device * pdev )
{
return 0 ;
}
static const struct of_device_id vtg_of_match [ ] = {
{ . compatible = " st,vtg " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , vtg_of_match ) ;
struct platform_driver sti_vtg_driver = {
. driver = {
. name = " sti-vtg " ,
. owner = THIS_MODULE ,
. of_match_table = vtg_of_match ,
} ,
. probe = vtg_probe ,
. remove = vtg_remove ,
} ;
module_platform_driver ( sti_vtg_driver ) ;
MODULE_AUTHOR ( " Benjamin Gaignard <benjamin.gaignard@st.com> " ) ;
MODULE_DESCRIPTION ( " STMicroelectronics SoC DRM driver " ) ;
MODULE_LICENSE ( " GPL " ) ;