2013-10-08 13:07:00 +05:30
/*
* HDMI PHY
*
* Copyright ( C ) 2013 Texas Instruments Incorporated
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation .
*/
# include <linux/kernel.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/platform_device.h>
# include <video/omapdss.h>
# include "dss.h"
2013-09-12 17:45:57 +05:30
# include "hdmi.h"
2013-10-08 13:07:00 +05:30
void hdmi_phy_dump ( struct hdmi_phy_data * phy , struct seq_file * s )
{
# define DUMPPHY(r) seq_printf(s, "%-35s %08x\n", #r,\
hdmi_read_reg ( phy - > base , r ) )
DUMPPHY ( HDMI_TXPHY_TX_CTRL ) ;
DUMPPHY ( HDMI_TXPHY_DIGITAL_CTRL ) ;
DUMPPHY ( HDMI_TXPHY_POWER_CTRL ) ;
DUMPPHY ( HDMI_TXPHY_PAD_CFG_CTRL ) ;
}
static irqreturn_t hdmi_irq_handler ( int irq , void * data )
{
struct hdmi_wp_data * wp = data ;
u32 irqstatus ;
irqstatus = hdmi_wp_get_irqstatus ( wp ) ;
hdmi_wp_set_irqstatus ( wp , irqstatus ) ;
if ( ( irqstatus & HDMI_IRQ_LINK_CONNECT ) & &
irqstatus & HDMI_IRQ_LINK_DISCONNECT ) {
/*
* If we get both connect and disconnect interrupts at the same
* time , turn off the PHY , clear interrupts , and restart , which
* raises connect interrupt if a cable is connected , or nothing
* if cable is not connected .
*/
hdmi_wp_set_phy_pwr ( wp , HDMI_PHYPWRCMD_OFF ) ;
hdmi_wp_set_irqstatus ( wp , HDMI_IRQ_LINK_CONNECT |
HDMI_IRQ_LINK_DISCONNECT ) ;
hdmi_wp_set_phy_pwr ( wp , HDMI_PHYPWRCMD_LDOON ) ;
} else if ( irqstatus & HDMI_IRQ_LINK_CONNECT ) {
hdmi_wp_set_phy_pwr ( wp , HDMI_PHYPWRCMD_TXON ) ;
} else if ( irqstatus & HDMI_IRQ_LINK_DISCONNECT ) {
hdmi_wp_set_phy_pwr ( wp , HDMI_PHYPWRCMD_LDOON ) ;
}
return IRQ_HANDLED ;
}
2014-04-17 12:54:02 +03:00
int hdmi_phy_parse_lanes ( struct hdmi_phy_data * phy , const u32 * lanes )
{
int i ;
for ( i = 0 ; i < 8 ; i + = 2 ) {
u8 lane , pol ;
int dx , dy ;
dx = lanes [ i ] ;
dy = lanes [ i + 1 ] ;
if ( dx < 0 | | dx > = 8 )
return - EINVAL ;
if ( dy < 0 | | dy > = 8 )
return - EINVAL ;
if ( dx & 1 ) {
if ( dy ! = dx - 1 )
return - EINVAL ;
pol = 1 ;
} else {
if ( dy ! = dx + 1 )
return - EINVAL ;
pol = 0 ;
}
lane = dx / 2 ;
phy - > lane_function [ lane ] = i / 2 ;
phy - > lane_polarity [ lane ] = pol ;
}
return 0 ;
}
static void hdmi_phy_configure_lanes ( struct hdmi_phy_data * phy )
{
static const u16 pad_cfg_list [ ] = {
0x0123 ,
0x0132 ,
0x0312 ,
0x0321 ,
0x0231 ,
0x0213 ,
0x1023 ,
0x1032 ,
0x3012 ,
0x3021 ,
0x2031 ,
0x2013 ,
0x1203 ,
0x1302 ,
0x3102 ,
0x3201 ,
0x2301 ,
0x2103 ,
0x1230 ,
0x1320 ,
0x3120 ,
0x3210 ,
0x2310 ,
0x2130 ,
} ;
u16 lane_cfg = 0 ;
int i ;
unsigned lane_cfg_val ;
u16 pol_val = 0 ;
for ( i = 0 ; i < 4 ; + + i )
lane_cfg | = phy - > lane_function [ i ] < < ( ( 3 - i ) * 4 ) ;
pol_val | = phy - > lane_polarity [ 0 ] < < 0 ;
pol_val | = phy - > lane_polarity [ 1 ] < < 3 ;
pol_val | = phy - > lane_polarity [ 2 ] < < 2 ;
pol_val | = phy - > lane_polarity [ 3 ] < < 1 ;
for ( i = 0 ; i < ARRAY_SIZE ( pad_cfg_list ) ; + + i )
if ( pad_cfg_list [ i ] = = lane_cfg )
break ;
if ( WARN_ON ( i = = ARRAY_SIZE ( pad_cfg_list ) ) )
i = 0 ;
lane_cfg_val = i ;
REG_FLD_MOD ( phy - > base , HDMI_TXPHY_PAD_CFG_CTRL , lane_cfg_val , 26 , 22 ) ;
REG_FLD_MOD ( phy - > base , HDMI_TXPHY_PAD_CFG_CTRL , pol_val , 30 , 27 ) ;
}
2013-10-08 13:07:00 +05:30
int hdmi_phy_enable ( struct hdmi_phy_data * phy , struct hdmi_wp_data * wp ,
struct hdmi_config * cfg )
{
u16 r = 0 ;
u32 irqstatus ;
hdmi_wp_clear_irqenable ( wp , 0xffffffff ) ;
irqstatus = hdmi_wp_get_irqstatus ( wp ) ;
hdmi_wp_set_irqstatus ( wp , irqstatus ) ;
r = hdmi_wp_set_phy_pwr ( wp , HDMI_PHYPWRCMD_LDOON ) ;
if ( r )
return r ;
/*
* Read address 0 in order to get the SCP reset done completed
* Dummy access performed to make sure reset is done
*/
hdmi_read_reg ( phy - > base , HDMI_TXPHY_TX_CTRL ) ;
/*
* Write to phy address 0 to configure the clock
* use HFBITCLK write HDMI_TXPHY_TX_CONTROL_FREQOUT field
*/
REG_FLD_MOD ( phy - > base , HDMI_TXPHY_TX_CTRL , 0x1 , 31 , 30 ) ;
/* Write to phy address 1 to start HDMI line (TXVALID and TMDSCLKEN) */
hdmi_write_reg ( phy - > base , HDMI_TXPHY_DIGITAL_CTRL , 0xF0000000 ) ;
/* Setup max LDO voltage */
REG_FLD_MOD ( phy - > base , HDMI_TXPHY_POWER_CTRL , 0xB , 3 , 0 ) ;
2014-04-17 12:54:02 +03:00
hdmi_phy_configure_lanes ( phy ) ;
2013-10-08 13:07:00 +05:30
r = request_threaded_irq ( phy - > irq , NULL , hdmi_irq_handler ,
IRQF_ONESHOT , " OMAP HDMI " , wp ) ;
if ( r ) {
DSSERR ( " HDMI IRQ request failed \n " ) ;
hdmi_wp_set_phy_pwr ( wp , HDMI_PHYPWRCMD_OFF ) ;
return r ;
}
hdmi_wp_set_irqenable ( wp ,
HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT ) ;
return 0 ;
}
void hdmi_phy_disable ( struct hdmi_phy_data * phy , struct hdmi_wp_data * wp )
{
free_irq ( phy - > irq , wp ) ;
hdmi_wp_set_phy_pwr ( wp , HDMI_PHYPWRCMD_OFF ) ;
}
# define PHY_OFFSET 0x300
# define PHY_SIZE 0x100
int hdmi_phy_init ( struct platform_device * pdev , struct hdmi_phy_data * phy )
{
struct resource * res ;
struct resource temp_res ;
2013-12-17 14:41:14 +02:00
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " phy " ) ;
2013-10-08 13:07:00 +05:30
if ( ! res ) {
DSSDBG ( " can't get PHY mem resource by name \n " ) ;
/*
* if hwmod / DT doesn ' t have the memory resource information
* split into HDMI sub blocks by name , we try again by getting
* the platform ' s first resource . this code will be removed when
* the driver can get the mem resources by name
*/
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
DSSERR ( " can't get PHY mem resource \n " ) ;
return - EINVAL ;
}
temp_res . start = res - > start + PHY_OFFSET ;
temp_res . end = temp_res . start + PHY_SIZE - 1 ;
res = & temp_res ;
}
phy - > base = devm_ioremap ( & pdev - > dev , res - > start , resource_size ( res ) ) ;
if ( ! phy - > base ) {
DSSERR ( " can't ioremap TX PHY \n " ) ;
return - ENOMEM ;
}
phy - > irq = platform_get_irq ( pdev , 0 ) ;
if ( phy - > irq < 0 ) {
DSSERR ( " platform_get_irq failed \n " ) ;
return - ENODEV ;
}
return 0 ;
}