2015-06-12 15:59:02 +03:00
/*
* Copyright ( C ) 2015 Samsung Electronics Co . Ltd
* Authors :
* Hyungwon Hwang < human . hwang @ samsung . com >
*
* 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 Foundationr
*/
# include <linux/platform_device.h>
# include <video/of_videomode.h>
# include <linux/of_address.h>
# include <video/videomode.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/mutex.h>
# include <linux/of.h>
# include <linux/of_graph.h>
# include <linux/clk.h>
2016-02-03 15:42:49 +03:00
# include <linux/component.h>
2015-06-12 15:59:02 +03:00
# include <drm/drmP.h>
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
/* Sysreg registers for MIC */
# define DSD_CFG_MUX 0x1004
# define MIC0_RGB_MUX (1 << 0)
# define MIC0_I80_MUX (1 << 1)
# define MIC0_ON_MUX (1 << 5)
/* MIC registers */
# define MIC_OP 0x0
# define MIC_IP_VER 0x0004
# define MIC_V_TIMING_0 0x0008
# define MIC_V_TIMING_1 0x000C
# define MIC_IMG_SIZE 0x0010
# define MIC_INPUT_TIMING_0 0x0014
# define MIC_INPUT_TIMING_1 0x0018
# define MIC_2D_OUTPUT_TIMING_0 0x001C
# define MIC_2D_OUTPUT_TIMING_1 0x0020
# define MIC_2D_OUTPUT_TIMING_2 0x0024
# define MIC_3D_OUTPUT_TIMING_0 0x0028
# define MIC_3D_OUTPUT_TIMING_1 0x002C
# define MIC_3D_OUTPUT_TIMING_2 0x0030
# define MIC_CORE_PARA_0 0x0034
# define MIC_CORE_PARA_1 0x0038
# define MIC_CTC_CTRL 0x0040
# define MIC_RD_DATA 0x0044
# define MIC_UPD_REG (1 << 31)
# define MIC_ON_REG (1 << 30)
# define MIC_TD_ON_REG (1 << 29)
# define MIC_BS_CHG_OUT (1 << 16)
# define MIC_VIDEO_TYPE(x) (((x) & 0xf) << 12)
# define MIC_PSR_EN (1 << 5)
# define MIC_SW_RST (1 << 4)
# define MIC_ALL_RST (1 << 3)
# define MIC_CORE_VER_CONTROL (1 << 2)
# define MIC_MODE_SEL_COMMAND_MODE (1 << 1)
# define MIC_MODE_SEL_MASK (1 << 1)
# define MIC_CORE_EN (1 << 0)
# define MIC_V_PULSE_WIDTH(x) (((x) & 0x3fff) << 16)
# define MIC_V_PERIOD_LINE(x) ((x) & 0x3fff)
# define MIC_VBP_SIZE(x) (((x) & 0x3fff) << 16)
# define MIC_VFP_SIZE(x) ((x) & 0x3fff)
# define MIC_IMG_V_SIZE(x) (((x) & 0x3fff) << 16)
# define MIC_IMG_H_SIZE(x) ((x) & 0x3fff)
# define MIC_H_PULSE_WIDTH_IN(x) (((x) & 0x3fff) << 16)
# define MIC_H_PERIOD_PIXEL_IN(x) ((x) & 0x3fff)
# define MIC_HBP_SIZE_IN(x) (((x) & 0x3fff) << 16)
# define MIC_HFP_SIZE_IN(x) ((x) & 0x3fff)
# define MIC_H_PULSE_WIDTH_2D(x) (((x) & 0x3fff) << 16)
# define MIC_H_PERIOD_PIXEL_2D(x) ((x) & 0x3fff)
# define MIC_HBP_SIZE_2D(x) (((x) & 0x3fff) << 16)
# define MIC_HFP_SIZE_2D(x) ((x) & 0x3fff)
# define MIC_BS_SIZE_2D(x) ((x) & 0x3fff)
enum {
ENDPOINT_DECON_NODE ,
ENDPOINT_DSI_NODE ,
NUM_ENDPOINTS
} ;
static char * clk_names [ ] = { " pclk_mic0 " , " sclk_rgb_vclk_to_mic0 " } ;
# define NUM_CLKS ARRAY_SIZE(clk_names)
static DEFINE_MUTEX ( mic_mutex ) ;
struct exynos_mic {
struct device * dev ;
void __iomem * reg ;
struct regmap * sysreg ;
struct clk * clks [ NUM_CLKS ] ;
bool i80_mode ;
struct videomode vm ;
struct drm_encoder * encoder ;
struct drm_bridge bridge ;
bool enabled ;
} ;
static void mic_set_path ( struct exynos_mic * mic , bool enable )
{
int ret ;
unsigned int val ;
ret = regmap_read ( mic - > sysreg , DSD_CFG_MUX , & val ) ;
if ( ret ) {
DRM_ERROR ( " mic: Failed to read system register \n " ) ;
return ;
}
if ( enable ) {
if ( mic - > i80_mode )
val | = MIC0_I80_MUX ;
else
val | = MIC0_RGB_MUX ;
val | = MIC0_ON_MUX ;
} else
val & = ~ ( MIC0_RGB_MUX | MIC0_I80_MUX | MIC0_ON_MUX ) ;
2016-03-25 23:05:59 +03:00
ret = regmap_write ( mic - > sysreg , DSD_CFG_MUX , val ) ;
2015-06-12 15:59:02 +03:00
if ( ret )
DRM_ERROR ( " mic: Failed to read system register \n " ) ;
}
static int mic_sw_reset ( struct exynos_mic * mic )
{
unsigned int retry = 100 ;
int ret ;
writel ( MIC_SW_RST , mic - > reg + MIC_OP ) ;
while ( retry - - > 0 ) {
ret = readl ( mic - > reg + MIC_OP ) ;
if ( ! ( ret & MIC_SW_RST ) )
return 0 ;
udelay ( 10 ) ;
}
return - ETIMEDOUT ;
}
static void mic_set_porch_timing ( struct exynos_mic * mic )
{
struct videomode vm = mic - > vm ;
u32 reg ;
reg = MIC_V_PULSE_WIDTH ( vm . vsync_len ) +
MIC_V_PERIOD_LINE ( vm . vsync_len + vm . vactive +
vm . vback_porch + vm . vfront_porch ) ;
writel ( reg , mic - > reg + MIC_V_TIMING_0 ) ;
reg = MIC_VBP_SIZE ( vm . vback_porch ) +
MIC_VFP_SIZE ( vm . vfront_porch ) ;
writel ( reg , mic - > reg + MIC_V_TIMING_1 ) ;
reg = MIC_V_PULSE_WIDTH ( vm . hsync_len ) +
MIC_V_PERIOD_LINE ( vm . hsync_len + vm . hactive +
vm . hback_porch + vm . hfront_porch ) ;
writel ( reg , mic - > reg + MIC_INPUT_TIMING_0 ) ;
reg = MIC_VBP_SIZE ( vm . hback_porch ) +
MIC_VFP_SIZE ( vm . hfront_porch ) ;
writel ( reg , mic - > reg + MIC_INPUT_TIMING_1 ) ;
}
static void mic_set_img_size ( struct exynos_mic * mic )
{
struct videomode * vm = & mic - > vm ;
u32 reg ;
reg = MIC_IMG_H_SIZE ( vm - > hactive ) +
MIC_IMG_V_SIZE ( vm - > vactive ) ;
writel ( reg , mic - > reg + MIC_IMG_SIZE ) ;
}
static void mic_set_output_timing ( struct exynos_mic * mic )
{
struct videomode vm = mic - > vm ;
u32 reg , bs_size_2d ;
DRM_DEBUG ( " w: %u, h: %u \n " , vm . hactive , vm . vactive ) ;
bs_size_2d = ( ( vm . hactive > > 2 ) < < 1 ) + ( vm . vactive % 4 ) ;
reg = MIC_BS_SIZE_2D ( bs_size_2d ) ;
writel ( reg , mic - > reg + MIC_2D_OUTPUT_TIMING_2 ) ;
if ( ! mic - > i80_mode ) {
reg = MIC_H_PULSE_WIDTH_2D ( vm . hsync_len ) +
MIC_H_PERIOD_PIXEL_2D ( vm . hsync_len + bs_size_2d +
vm . hback_porch + vm . hfront_porch ) ;
writel ( reg , mic - > reg + MIC_2D_OUTPUT_TIMING_0 ) ;
reg = MIC_HBP_SIZE_2D ( vm . hback_porch ) +
MIC_H_PERIOD_PIXEL_2D ( vm . hfront_porch ) ;
writel ( reg , mic - > reg + MIC_2D_OUTPUT_TIMING_1 ) ;
}
}
static void mic_set_reg_on ( struct exynos_mic * mic , bool enable )
{
u32 reg = readl ( mic - > reg + MIC_OP ) ;
if ( enable ) {
reg & = ~ ( MIC_MODE_SEL_MASK | MIC_CORE_VER_CONTROL | MIC_PSR_EN ) ;
reg | = ( MIC_CORE_EN | MIC_BS_CHG_OUT | MIC_ON_REG ) ;
reg & = ~ MIC_MODE_SEL_COMMAND_MODE ;
if ( mic - > i80_mode )
reg | = MIC_MODE_SEL_COMMAND_MODE ;
} else {
reg & = ~ MIC_CORE_EN ;
}
reg | = MIC_UPD_REG ;
writel ( reg , mic - > reg + MIC_OP ) ;
}
static struct device_node * get_remote_node ( struct device_node * from , int reg )
{
struct device_node * endpoint = NULL , * remote_node = NULL ;
endpoint = of_graph_get_endpoint_by_regs ( from , reg , - 1 ) ;
if ( ! endpoint ) {
DRM_ERROR ( " mic: Failed to find remote port from %s " ,
from - > full_name ) ;
goto exit ;
}
remote_node = of_graph_get_remote_port_parent ( endpoint ) ;
if ( ! remote_node ) {
DRM_ERROR ( " mic: Failed to find remote port parent from %s " ,
from - > full_name ) ;
goto exit ;
}
exit :
of_node_put ( endpoint ) ;
return remote_node ;
}
static int parse_dt ( struct exynos_mic * mic )
{
int ret = 0 , i , j ;
struct device_node * remote_node ;
struct device_node * nodes [ 3 ] ;
/*
* The order of endpoints does matter .
* The first node must be for decon and the second one must be for dsi .
*/
for ( i = 0 , j = 0 ; i < NUM_ENDPOINTS ; i + + ) {
remote_node = get_remote_node ( mic - > dev - > of_node , i ) ;
if ( ! remote_node ) {
ret = - EPIPE ;
goto exit ;
}
nodes [ j + + ] = remote_node ;
switch ( i ) {
case ENDPOINT_DECON_NODE :
/* decon node */
if ( of_get_child_by_name ( remote_node ,
" i80-if-timings " ) )
mic - > i80_mode = 1 ;
break ;
case ENDPOINT_DSI_NODE :
/* panel node */
remote_node = get_remote_node ( remote_node , 1 ) ;
if ( ! remote_node ) {
ret = - EPIPE ;
goto exit ;
}
nodes [ j + + ] = remote_node ;
ret = of_get_videomode ( remote_node ,
& mic - > vm , 0 ) ;
if ( ret ) {
DRM_ERROR ( " mic: failed to get videomode " ) ;
goto exit ;
}
break ;
default :
DRM_ERROR ( " mic: Unknown endpoint from MIC " ) ;
break ;
}
}
exit :
while ( - - j > - 1 )
of_node_put ( nodes [ j ] ) ;
return ret ;
}
2016-02-03 15:42:50 +03:00
static void mic_disable ( struct drm_bridge * bridge ) { }
2015-06-12 15:59:02 +03:00
2016-02-03 15:42:50 +03:00
static void mic_post_disable ( struct drm_bridge * bridge )
2015-06-12 15:59:02 +03:00
{
struct exynos_mic * mic = bridge - > driver_private ;
int i ;
mutex_lock ( & mic_mutex ) ;
if ( ! mic - > enabled )
goto already_disabled ;
mic_set_path ( mic , 0 ) ;
for ( i = NUM_CLKS - 1 ; i > - 1 ; i - - )
clk_disable_unprepare ( mic - > clks [ i ] ) ;
mic - > enabled = 0 ;
already_disabled :
mutex_unlock ( & mic_mutex ) ;
}
2016-02-03 15:42:50 +03:00
static void mic_pre_enable ( struct drm_bridge * bridge )
2015-06-12 15:59:02 +03:00
{
struct exynos_mic * mic = bridge - > driver_private ;
int ret , i ;
mutex_lock ( & mic_mutex ) ;
if ( mic - > enabled )
goto already_enabled ;
for ( i = 0 ; i < NUM_CLKS ; i + + ) {
ret = clk_prepare_enable ( mic - > clks [ i ] ) ;
if ( ret < 0 ) {
DRM_ERROR ( " Failed to enable clock (%s) \n " ,
clk_names [ i ] ) ;
goto turn_off_clks ;
}
}
mic_set_path ( mic , 1 ) ;
ret = mic_sw_reset ( mic ) ;
if ( ret ) {
DRM_ERROR ( " Failed to reset \n " ) ;
goto turn_off_clks ;
}
if ( ! mic - > i80_mode )
mic_set_porch_timing ( mic ) ;
mic_set_img_size ( mic ) ;
mic_set_output_timing ( mic ) ;
mic_set_reg_on ( mic , 1 ) ;
mic - > enabled = 1 ;
mutex_unlock ( & mic_mutex ) ;
return ;
turn_off_clks :
while ( - - i > - 1 )
clk_disable_unprepare ( mic - > clks [ i ] ) ;
already_enabled :
mutex_unlock ( & mic_mutex ) ;
}
2016-02-03 15:42:50 +03:00
static void mic_enable ( struct drm_bridge * bridge ) { }
2015-06-12 15:59:02 +03:00
2016-02-03 15:42:49 +03:00
static const struct drm_bridge_funcs mic_bridge_funcs = {
. disable = mic_disable ,
. post_disable = mic_post_disable ,
. pre_enable = mic_pre_enable ,
. enable = mic_enable ,
} ;
static int exynos_mic_bind ( struct device * dev , struct device * master ,
void * data )
2015-06-12 15:59:02 +03:00
{
2016-02-03 15:42:49 +03:00
struct exynos_mic * mic = dev_get_drvdata ( dev ) ;
int ret ;
mic - > bridge . funcs = & mic_bridge_funcs ;
mic - > bridge . of_node = dev - > of_node ;
mic - > bridge . driver_private = mic ;
ret = drm_bridge_add ( & mic - > bridge ) ;
if ( ret )
DRM_ERROR ( " mic: Failed to add MIC to the global bridge list \n " ) ;
return ret ;
}
static void exynos_mic_unbind ( struct device * dev , struct device * master ,
void * data )
{
struct exynos_mic * mic = dev_get_drvdata ( dev ) ;
2015-06-12 15:59:02 +03:00
int i ;
mutex_lock ( & mic_mutex ) ;
if ( ! mic - > enabled )
goto already_disabled ;
for ( i = NUM_CLKS - 1 ; i > - 1 ; i - - )
clk_disable_unprepare ( mic - > clks [ i ] ) ;
already_disabled :
mutex_unlock ( & mic_mutex ) ;
2016-02-03 15:42:49 +03:00
drm_bridge_remove ( & mic - > bridge ) ;
2015-06-12 15:59:02 +03:00
}
2016-02-03 15:42:49 +03:00
static const struct component_ops exynos_mic_component_ops = {
. bind = exynos_mic_bind ,
. unbind = exynos_mic_unbind ,
2015-06-12 15:59:02 +03:00
} ;
2016-02-03 15:42:50 +03:00
static int exynos_mic_probe ( struct platform_device * pdev )
2015-06-12 15:59:02 +03:00
{
struct device * dev = & pdev - > dev ;
struct exynos_mic * mic ;
struct resource res ;
int ret , i ;
mic = devm_kzalloc ( dev , sizeof ( * mic ) , GFP_KERNEL ) ;
if ( ! mic ) {
DRM_ERROR ( " mic: Failed to allocate memory for MIC object \n " ) ;
ret = - ENOMEM ;
goto err ;
}
mic - > dev = dev ;
ret = parse_dt ( mic ) ;
if ( ret )
goto err ;
ret = of_address_to_resource ( dev - > of_node , 0 , & res ) ;
if ( ret ) {
DRM_ERROR ( " mic: Failed to get mem region for MIC \n " ) ;
goto err ;
}
mic - > reg = devm_ioremap ( dev , res . start , resource_size ( & res ) ) ;
if ( ! mic - > reg ) {
DRM_ERROR ( " mic: Failed to remap for MIC \n " ) ;
ret = - ENOMEM ;
goto err ;
}
mic - > sysreg = syscon_regmap_lookup_by_phandle ( dev - > of_node ,
" samsung,disp-syscon " ) ;
if ( IS_ERR ( mic - > sysreg ) ) {
DRM_ERROR ( " mic: Failed to get system register. \n " ) ;
2016-03-17 13:32:15 +03:00
ret = PTR_ERR ( mic - > sysreg ) ;
2015-06-12 15:59:02 +03:00
goto err ;
}
for ( i = 0 ; i < NUM_CLKS ; i + + ) {
2016-02-03 15:42:48 +03:00
mic - > clks [ i ] = devm_clk_get ( dev , clk_names [ i ] ) ;
2015-06-12 15:59:02 +03:00
if ( IS_ERR ( mic - > clks [ i ] ) ) {
DRM_ERROR ( " mic: Failed to get clock (%s) \n " ,
clk_names [ i ] ) ;
ret = PTR_ERR ( mic - > clks [ i ] ) ;
goto err ;
}
}
2016-02-03 15:42:49 +03:00
platform_set_drvdata ( pdev , mic ) ;
2015-06-12 15:59:02 +03:00
DRM_DEBUG_KMS ( " MIC has been probed \n " ) ;
2016-02-03 15:42:49 +03:00
return component_add ( dev , & exynos_mic_component_ops ) ;
2015-06-12 15:59:02 +03:00
err :
return ret ;
}
static int exynos_mic_remove ( struct platform_device * pdev )
{
2016-02-03 15:42:49 +03:00
component_del ( & pdev - > dev , & exynos_mic_component_ops ) ;
2015-06-12 15:59:02 +03:00
return 0 ;
}
static const struct of_device_id exynos_mic_of_match [ ] = {
{ . compatible = " samsung,exynos5433-mic " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , exynos_mic_of_match ) ;
struct platform_driver mic_driver = {
. probe = exynos_mic_probe ,
. remove = exynos_mic_remove ,
. driver = {
. name = " exynos-mic " ,
. owner = THIS_MODULE ,
. of_match_table = exynos_mic_of_match ,
} ,
} ;