2017-12-06 12:29:47 +01:00
// SPDX-License-Identifier: GPL-2.0
2014-07-30 18:20:56 +02: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 .
*/
# include <linux/module.h>
2019-06-05 15:48:35 +02:00
# include <linux/io.h>
2014-07-30 18:20:56 +02:00
# include <linux/notifier.h>
2017-11-08 14:50:14 +01:00
# include <linux/of_platform.h>
2014-07-30 18:20:56 +02:00
# include <linux/platform_device.h>
2019-06-05 15:48:35 +02:00
# include <drm/drm_modes.h>
# include <drm/drm_print.h>
2014-07-30 18:20:56 +02:00
2016-09-19 16:33:53 +03:00
# include "sti_drv.h"
2014-07-30 18:20:56 +02:00
# include "sti_vtg.h"
2016-01-28 13:08:48 +01:00
# define VTG_MODE_MASTER 0
2014-07-30 18:20:56 +02:00
/* 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 10:14:11 +01: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 18:20:56 +02: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 10:14:11 +01:00
/* Delay introduced by the HDMI in nb of pixel */
2015-06-05 11:47:49 +02:00
# define HDMI_DELAY (5)
2014-12-05 10:14:11 +01:00
2016-02-02 14:30:13 +01:00
/* Delay introduced by the DVO in nb of pixel */
2016-03-15 17:11:14 +01:00
# define DVO_DELAY (7)
2016-02-02 14:30:13 +01:00
2014-07-30 18:20:56 +02: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)
2016-01-28 13:08:48 +01:00
/*
* STI VTG register offset structure
*
* @ h_hd : stores the VTG_H_HD_x register offset
* @ top_v_vd : stores the VTG_TOP_V_VD_x register offset
* @ bot_v_vd : stores the VTG_BOT_V_VD_x register offset
* @ top_v_hd : stores the VTG_TOP_V_HD_x register offset
* @ bot_v_hd : stores the VTG_BOT_V_HD_x register offset
*/
struct sti_vtg_regs_offs {
u32 h_hd ;
u32 top_v_vd ;
u32 bot_v_vd ;
u32 top_v_hd ;
u32 bot_v_hd ;
} ;
# define VTG_MAX_SYNC_OUTPUT 4
static const struct sti_vtg_regs_offs vtg_regs_offs [ VTG_MAX_SYNC_OUTPUT ] = {
{ VTG_H_HD_1 ,
VTG_TOP_V_VD_1 , VTG_BOT_V_VD_1 , VTG_TOP_V_HD_1 , VTG_BOT_V_HD_1 } ,
{ VTG_H_HD_2 ,
VTG_TOP_V_VD_2 , VTG_BOT_V_VD_2 , VTG_TOP_V_HD_2 , VTG_BOT_V_HD_2 } ,
{ VTG_H_HD_3 ,
VTG_TOP_V_VD_3 , VTG_BOT_V_VD_3 , VTG_TOP_V_HD_3 , VTG_BOT_V_HD_3 } ,
{ VTG_H_HD_4 ,
VTG_TOP_V_VD_4 , VTG_BOT_V_VD_4 , VTG_TOP_V_HD_4 , VTG_BOT_V_HD_4 }
} ;
/*
* STI VTG synchronisation parameters structure
*
* @ hsync : sample number falling and rising edge
* @ vsync_line_top : vertical top field line number falling and rising edge
* @ vsync_line_bot : vertical bottom field line number falling and rising edge
* @ vsync_off_top : vertical top field sample number rising and falling edge
* @ vsync_off_bot : vertical bottom field sample number rising and falling edge
*/
struct sti_vtg_sync_params {
u32 hsync ;
u32 vsync_line_top ;
u32 vsync_line_bot ;
u32 vsync_off_top ;
u32 vsync_off_bot ;
} ;
2019-09-09 12:12:53 +02:00
/*
2014-07-30 18:20:56 +02:00
* STI VTG structure
*
2016-01-28 13:08:48 +01:00
* @ regs : register mapping
* @ sync_params : synchronisation parameters used to generate timings
2014-07-30 18:20:56 +02:00
* @ irq : VTG irq
2016-01-28 13:08:48 +01:00
* @ irq_status : store the IRQ status value
2014-07-30 18:20:56 +02:00
* @ notifier_list : notifier callback
2015-09-24 18:35:38 +02:00
* @ crtc : the CRTC for vblank event
2014-07-30 18:20:56 +02:00
*/
struct sti_vtg {
void __iomem * regs ;
2016-01-28 13:08:48 +01:00
struct sti_vtg_sync_params sync_params [ VTG_MAX_SYNC_OUTPUT ] ;
2014-07-30 18:20:56 +02:00
int irq ;
u32 irq_status ;
struct raw_notifier_head notifier_list ;
2015-09-24 18:35:38 +02:00
struct drm_crtc * crtc ;
2014-07-30 18:20:56 +02:00
} ;
struct sti_vtg * of_vtg_find ( struct device_node * np )
{
2017-11-08 14:50:14 +01:00
struct platform_device * pdev ;
2014-07-30 18:20:56 +02:00
2017-11-08 14:50:14 +01:00
pdev = of_find_device_by_node ( np ) ;
if ( ! pdev )
return NULL ;
return ( struct sti_vtg * ) platform_get_drvdata ( pdev ) ;
2014-07-30 18:20:56 +02:00
}
static void vtg_reset ( struct sti_vtg * vtg )
{
writel ( 1 , vtg - > regs + VTG_DRST_AUTOC ) ;
}
2015-06-05 11:47:49 +02:00
static void vtg_set_output_window ( void __iomem * regs ,
const struct drm_display_mode * mode )
{
u32 video_top_field_start ;
u32 video_top_field_stop ;
u32 video_bottom_field_start ;
u32 video_bottom_field_stop ;
u32 xstart = sti_vtg_get_pixel_number ( * mode , 0 ) ;
u32 ystart = sti_vtg_get_line_number ( * mode , 0 ) ;
u32 xstop = sti_vtg_get_pixel_number ( * mode , mode - > hdisplay - 1 ) ;
u32 ystop = sti_vtg_get_line_number ( * mode , mode - > vdisplay - 1 ) ;
/* Set output window to fit the display mode selected */
video_top_field_start = ( ystart < < 16 ) | xstart ;
video_top_field_stop = ( ystop < < 16 ) | xstop ;
/* Only progressive supported for now */
video_bottom_field_start = video_top_field_start ;
video_bottom_field_stop = video_top_field_stop ;
writel ( video_top_field_start , regs + VTG_VID_TFO ) ;
writel ( video_top_field_stop , regs + VTG_VID_TFS ) ;
writel ( video_bottom_field_start , regs + VTG_VID_BFO ) ;
writel ( video_bottom_field_stop , regs + VTG_VID_BFS ) ;
}
2016-01-28 13:08:48 +01:00
static void vtg_set_hsync_vsync_pos ( struct sti_vtg_sync_params * sync ,
int delay ,
const struct drm_display_mode * mode )
{
long clocksperline , start , stop ;
u32 risesync_top , fallsync_top ;
u32 risesync_offs_top , fallsync_offs_top ;
clocksperline = mode - > htotal ;
/* Get the hsync position */
start = 0 ;
stop = mode - > hsync_end - mode - > hsync_start ;
start + = delay ;
stop + = delay ;
if ( start < 0 )
start + = clocksperline ;
else if ( start > = clocksperline )
start - = clocksperline ;
if ( stop < 0 )
stop + = clocksperline ;
else if ( stop > = clocksperline )
stop - = clocksperline ;
sync - > hsync = ( stop < < 16 ) | start ;
/* Get the vsync position */
if ( delay > = 0 ) {
risesync_top = 1 ;
fallsync_top = risesync_top ;
fallsync_top + = mode - > vsync_end - mode - > vsync_start ;
fallsync_offs_top = ( u32 ) delay ;
risesync_offs_top = ( u32 ) delay ;
} else {
risesync_top = mode - > vtotal ;
fallsync_top = mode - > vsync_end - mode - > vsync_start ;
fallsync_offs_top = clocksperline + delay ;
risesync_offs_top = clocksperline + delay ;
}
sync - > vsync_line_top = ( fallsync_top < < 16 ) | risesync_top ;
sync - > vsync_off_top = ( fallsync_offs_top < < 16 ) | risesync_offs_top ;
/* Only progressive supported for now */
sync - > vsync_line_bot = sync - > vsync_line_top ;
sync - > vsync_off_bot = sync - > vsync_off_top ;
}
2014-07-30 18:20:56 +02:00
static void vtg_set_mode ( struct sti_vtg * vtg ,
2016-01-28 13:08:48 +01:00
int type ,
struct sti_vtg_sync_params * sync ,
const struct drm_display_mode * mode )
2014-07-30 18:20:56 +02:00
{
2016-01-28 13:08:48 +01:00
unsigned int i ;
2014-07-30 18:20:56 +02:00
2015-06-05 11:47:49 +02:00
/* Set the number of clock cycles per line */
2014-07-30 18:20:56 +02:00
writel ( mode - > htotal , vtg - > regs + VTG_CLKLN ) ;
2015-06-05 11:47:49 +02:00
/* Set Half Line Per Field (only progressive supported for now) */
writel ( mode - > vtotal * 2 , vtg - > regs + VTG_HLFLN ) ;
2014-07-30 18:20:56 +02:00
2015-06-05 11:47:49 +02:00
/* Program output window */
vtg_set_output_window ( vtg - > regs , mode ) ;
2014-07-30 18:20:56 +02:00
2016-01-28 13:08:48 +01:00
/* Set hsync and vsync position for HDMI */
vtg_set_hsync_vsync_pos ( & sync [ VTG_SYNC_ID_HDMI - 1 ] , HDMI_DELAY , mode ) ;
/* Set hsync and vsync position for HD DCS */
vtg_set_hsync_vsync_pos ( & sync [ VTG_SYNC_ID_HDDCS - 1 ] , 0 , mode ) ;
/* Set hsync and vsync position for HDF */
vtg_set_hsync_vsync_pos ( & sync [ VTG_SYNC_ID_HDF - 1 ] , AWG_DELAY_HD , mode ) ;
/* Set hsync and vsync position for DVO */
2016-02-02 14:30:13 +01:00
vtg_set_hsync_vsync_pos ( & sync [ VTG_SYNC_ID_DVO - 1 ] , DVO_DELAY , mode ) ;
2016-01-28 13:08:48 +01:00
/* Progam the syncs outputs */
for ( i = 0 ; i < VTG_MAX_SYNC_OUTPUT ; i + + ) {
writel ( sync [ i ] . hsync ,
vtg - > regs + vtg_regs_offs [ i ] . h_hd ) ;
writel ( sync [ i ] . vsync_line_top ,
vtg - > regs + vtg_regs_offs [ i ] . top_v_vd ) ;
writel ( sync [ i ] . vsync_line_bot ,
vtg - > regs + vtg_regs_offs [ i ] . bot_v_vd ) ;
writel ( sync [ i ] . vsync_off_top ,
vtg - > regs + vtg_regs_offs [ i ] . top_v_hd ) ;
writel ( sync [ i ] . vsync_off_bot ,
vtg - > regs + vtg_regs_offs [ i ] . bot_v_hd ) ;
}
2014-12-05 10:14:11 +01:00
2014-07-30 18:20:56 +02: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 */
2016-01-28 13:08:48 +01:00
vtg_set_mode ( vtg , VTG_MODE_MASTER , vtg - > sync_params , mode ) ;
2014-07-30 18:20:56 +02:00
vtg_reset ( vtg ) ;
2017-02-02 09:52:32 +01:00
vtg_enable_irq ( vtg ) ;
2014-07-30 18:20:56 +02:00
}
/**
* 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 ;
}
/**
* 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 ;
}
2015-09-24 18:35:38 +02:00
int sti_vtg_register_client ( struct sti_vtg * vtg , struct notifier_block * nb ,
struct drm_crtc * crtc )
2014-07-30 18:20:56 +02:00
{
2015-09-24 18:35:38 +02:00
vtg - > crtc = crtc ;
2014-07-30 18:20:56 +02:00
return raw_notifier_chain_register ( & vtg - > notifier_list , nb ) ;
}
int sti_vtg_unregister_client ( struct sti_vtg * vtg , struct notifier_block * nb )
{
return raw_notifier_chain_unregister ( & vtg - > notifier_list , nb ) ;
}
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 ;
2015-09-24 18:35:38 +02:00
raw_notifier_call_chain ( & vtg - > notifier_list , event , vtg - > crtc ) ;
2014-07-30 18:20:56 +02:00
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 sti_vtg * vtg ;
struct resource * res ;
int ret ;
vtg = devm_kzalloc ( dev , sizeof ( * vtg ) , GFP_KERNEL ) ;
if ( ! vtg )
return - ENOMEM ;
/* Get Memory ressources */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
DRM_ERROR ( " Get memory resource failed \n " ) ;
return - ENOMEM ;
}
2020-01-06 09:43:50 +01:00
vtg - > regs = devm_ioremap ( dev , res - > start , resource_size ( res ) ) ;
2016-12-21 11:00:12 +05:30
if ( ! vtg - > regs ) {
DRM_ERROR ( " failed to remap I/O memory \n " ) ;
return - ENOMEM ;
}
2014-07-30 18:20:56 +02:00
2017-02-02 09:52:32 +01:00
vtg - > irq = platform_get_irq ( pdev , 0 ) ;
if ( vtg - > irq < 0 ) {
DRM_ERROR ( " Failed to get VTG interrupt \n " ) ;
return vtg - > irq ;
}
2014-07-30 18:20:56 +02:00
2017-02-02 09:52:32 +01:00
RAW_INIT_NOTIFIER_HEAD ( & vtg - > notifier_list ) ;
ret = devm_request_threaded_irq ( dev , vtg - > irq , vtg_irq ,
vtg_irq_thread , IRQF_ONESHOT ,
dev_name ( dev ) , vtg ) ;
if ( ret < 0 ) {
DRM_ERROR ( " Failed to register VTG interrupt \n " ) ;
return ret ;
2014-07-30 18:20:56 +02:00
}
platform_set_drvdata ( pdev , vtg ) ;
2017-11-08 14:50:14 +01:00
DRM_INFO ( " %s %s \n " , __func__ , dev_name ( dev ) ) ;
2014-07-30 18:20:56 +02:00
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 ,
} ;
MODULE_AUTHOR ( " Benjamin Gaignard <benjamin.gaignard@st.com> " ) ;
MODULE_DESCRIPTION ( " STMicroelectronics SoC DRM driver " ) ;
MODULE_LICENSE ( " GPL " ) ;