2013-10-16 20:28:10 +04:00
/*
* Samsung S5P / EXYNOS SoC series MIPI CSIS / DSIM DPHY driver
*
2016-03-23 14:09:18 +03:00
* Copyright ( C ) 2013 , 2016 Samsung Electronics Co . , Ltd .
2013-10-16 20:28:10 +04:00
* Author : Sylwester Nawrocki < s . nawrocki @ 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 Foundation .
*/
2014-05-29 10:30:48 +04:00
# include <linux/err.h>
2013-10-16 20:28:10 +04:00
# include <linux/io.h>
# include <linux/kernel.h>
2016-03-23 14:09:18 +03:00
# include <linux/mfd/syscon/exynos5-pmu.h>
2013-10-16 20:28:10 +04:00
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
2016-03-23 14:09:17 +03:00
# include <linux/of_device.h>
2013-10-16 20:28:10 +04:00
# include <linux/phy/phy.h>
2015-01-16 20:30:37 +03:00
# include <linux/regmap.h>
2013-10-16 20:28:10 +04:00
# include <linux/spinlock.h>
2017-03-29 11:25:57 +03:00
# include <linux/soc/samsung/exynos-regs-pmu.h>
2015-01-16 20:30:37 +03:00
# include <linux/mfd/syscon.h>
2013-10-16 20:28:10 +04:00
enum exynos_mipi_phy_id {
2016-03-23 14:09:17 +03:00
EXYNOS_MIPI_PHY_ID_NONE = - 1 ,
2013-10-16 20:28:10 +04:00
EXYNOS_MIPI_PHY_ID_CSIS0 ,
EXYNOS_MIPI_PHY_ID_DSIM0 ,
EXYNOS_MIPI_PHY_ID_CSIS1 ,
EXYNOS_MIPI_PHY_ID_DSIM1 ,
2016-03-23 14:09:18 +03:00
EXYNOS_MIPI_PHY_ID_CSIS2 ,
2013-10-16 20:28:10 +04:00
EXYNOS_MIPI_PHYS_NUM
} ;
2016-03-23 14:09:17 +03:00
enum exynos_mipi_phy_regmap_id {
EXYNOS_MIPI_REGMAP_PMU ,
2016-03-23 14:09:18 +03:00
EXYNOS_MIPI_REGMAP_DISP ,
EXYNOS_MIPI_REGMAP_CAM0 ,
EXYNOS_MIPI_REGMAP_CAM1 ,
2016-03-23 14:09:17 +03:00
EXYNOS_MIPI_REGMAPS_NUM
} ;
struct mipi_phy_device_desc {
int num_phys ;
int num_regmaps ;
const char * regmap_names [ EXYNOS_MIPI_REGMAPS_NUM ] ;
struct exynos_mipi_phy_desc {
enum exynos_mipi_phy_id coupled_phy_id ;
u32 enable_val ;
unsigned int enable_reg ;
enum exynos_mipi_phy_regmap_id enable_map ;
u32 resetn_val ;
unsigned int resetn_reg ;
enum exynos_mipi_phy_regmap_id resetn_map ;
} phys [ EXYNOS_MIPI_PHYS_NUM ] ;
} ;
static const struct mipi_phy_device_desc s5pv210_mipi_phy = {
. num_regmaps = 1 ,
. regmap_names = { " syscon " } ,
. num_phys = 4 ,
. phys = {
{
/* EXYNOS_MIPI_PHY_ID_CSIS0 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0 ,
. enable_val = EXYNOS4_MIPI_PHY_ENABLE ,
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 0 ) ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS4_MIPI_PHY_SRESETN ,
. resetn_reg = EXYNOS4_MIPI_PHY_CONTROL ( 0 ) ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} , {
/* EXYNOS_MIPI_PHY_ID_DSIM0 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0 ,
. enable_val = EXYNOS4_MIPI_PHY_ENABLE ,
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 0 ) ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS4_MIPI_PHY_MRESETN ,
. resetn_reg = EXYNOS4_MIPI_PHY_CONTROL ( 0 ) ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} , {
/* EXYNOS_MIPI_PHY_ID_CSIS1 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1 ,
. enable_val = EXYNOS4_MIPI_PHY_ENABLE ,
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 1 ) ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS4_MIPI_PHY_SRESETN ,
. resetn_reg = EXYNOS4_MIPI_PHY_CONTROL ( 1 ) ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} , {
/* EXYNOS_MIPI_PHY_ID_DSIM1 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1 ,
. enable_val = EXYNOS4_MIPI_PHY_ENABLE ,
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 1 ) ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS4_MIPI_PHY_MRESETN ,
. resetn_reg = EXYNOS4_MIPI_PHY_CONTROL ( 1 ) ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} ,
} ,
} ;
2016-03-23 14:09:18 +03:00
static const struct mipi_phy_device_desc exynos5420_mipi_phy = {
. num_regmaps = 1 ,
. regmap_names = { " syscon " } ,
. num_phys = 5 ,
. phys = {
{
/* EXYNOS_MIPI_PHY_ID_CSIS0 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0 ,
. enable_val = EXYNOS5_PHY_ENABLE ,
. enable_reg = EXYNOS5420_MIPI_PHY0_CONTROL ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS5_MIPI_PHY_S_RESETN ,
. resetn_reg = EXYNOS5420_MIPI_PHY0_CONTROL ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} , {
/* EXYNOS_MIPI_PHY_ID_DSIM0 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0 ,
. enable_val = EXYNOS5_PHY_ENABLE ,
. enable_reg = EXYNOS5420_MIPI_PHY0_CONTROL ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS5_MIPI_PHY_M_RESETN ,
. resetn_reg = EXYNOS5420_MIPI_PHY0_CONTROL ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} , {
/* EXYNOS_MIPI_PHY_ID_CSIS1 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1 ,
. enable_val = EXYNOS5_PHY_ENABLE ,
. enable_reg = EXYNOS5420_MIPI_PHY1_CONTROL ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS5_MIPI_PHY_S_RESETN ,
. resetn_reg = EXYNOS5420_MIPI_PHY1_CONTROL ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} , {
/* EXYNOS_MIPI_PHY_ID_DSIM1 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1 ,
. enable_val = EXYNOS5_PHY_ENABLE ,
. enable_reg = EXYNOS5420_MIPI_PHY1_CONTROL ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS5_MIPI_PHY_M_RESETN ,
. resetn_reg = EXYNOS5420_MIPI_PHY1_CONTROL ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} , {
/* EXYNOS_MIPI_PHY_ID_CSIS2 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE ,
. enable_val = EXYNOS5_PHY_ENABLE ,
. enable_reg = EXYNOS5420_MIPI_PHY2_CONTROL ,
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = EXYNOS5_MIPI_PHY_S_RESETN ,
. resetn_reg = EXYNOS5420_MIPI_PHY2_CONTROL ,
. resetn_map = EXYNOS_MIPI_REGMAP_PMU ,
} ,
} ,
} ;
# define EXYNOS5433_SYSREG_DISP_MIPI_PHY 0x100C
# define EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON 0x1014
# define EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON 0x1020
static const struct mipi_phy_device_desc exynos5433_mipi_phy = {
. num_regmaps = 4 ,
. regmap_names = {
" samsung,pmu-syscon " ,
" samsung,disp-sysreg " ,
" samsung,cam0-sysreg " ,
" samsung,cam1-sysreg "
} ,
. num_phys = 5 ,
. phys = {
{
/* EXYNOS_MIPI_PHY_ID_CSIS0 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0 ,
. enable_val = EXYNOS5_PHY_ENABLE ,
2017-03-29 11:25:57 +03:00
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 0 ) ,
2016-03-23 14:09:18 +03:00
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = BIT ( 0 ) ,
. resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON ,
. resetn_map = EXYNOS_MIPI_REGMAP_CAM0 ,
} , {
/* EXYNOS_MIPI_PHY_ID_DSIM0 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0 ,
. enable_val = EXYNOS5_PHY_ENABLE ,
2017-03-29 11:25:57 +03:00
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 0 ) ,
2016-03-23 14:09:18 +03:00
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = BIT ( 0 ) ,
. resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY ,
. resetn_map = EXYNOS_MIPI_REGMAP_DISP ,
} , {
/* EXYNOS_MIPI_PHY_ID_CSIS1 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE ,
. enable_val = EXYNOS5_PHY_ENABLE ,
2017-03-29 11:25:57 +03:00
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 1 ) ,
2016-03-23 14:09:18 +03:00
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = BIT ( 1 ) ,
. resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON ,
. resetn_map = EXYNOS_MIPI_REGMAP_CAM0 ,
} , {
/* EXYNOS_MIPI_PHY_ID_DSIM1 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE ,
. enable_val = EXYNOS5_PHY_ENABLE ,
2017-03-29 11:25:57 +03:00
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 1 ) ,
2016-03-23 14:09:18 +03:00
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = BIT ( 1 ) ,
. resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY ,
. resetn_map = EXYNOS_MIPI_REGMAP_DISP ,
} , {
/* EXYNOS_MIPI_PHY_ID_CSIS2 */
. coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE ,
. enable_val = EXYNOS5_PHY_ENABLE ,
2017-03-29 11:25:57 +03:00
. enable_reg = EXYNOS4_MIPI_PHY_CONTROL ( 2 ) ,
2016-03-23 14:09:18 +03:00
. enable_map = EXYNOS_MIPI_REGMAP_PMU ,
. resetn_val = BIT ( 0 ) ,
. resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON ,
. resetn_map = EXYNOS_MIPI_REGMAP_CAM1 ,
} ,
} ,
} ;
2013-10-16 20:28:10 +04:00
struct exynos_mipi_video_phy {
2016-03-23 14:09:17 +03:00
struct regmap * regmaps [ EXYNOS_MIPI_REGMAPS_NUM ] ;
int num_phys ;
2013-10-16 20:28:10 +04:00
struct video_phy_desc {
struct phy * phy ;
unsigned int index ;
2016-03-23 14:09:17 +03:00
const struct exynos_mipi_phy_desc * data ;
2013-10-16 20:28:10 +04:00
} phys [ EXYNOS_MIPI_PHYS_NUM ] ;
2015-01-16 20:30:37 +03:00
spinlock_t slock ;
2013-10-16 20:28:10 +04:00
} ;
2016-03-23 14:09:17 +03:00
static int __set_phy_state ( const struct exynos_mipi_phy_desc * data ,
struct exynos_mipi_video_phy * state , unsigned int on )
{
u32 val ;
2015-01-16 20:30:37 +03:00
2015-02-27 06:50:57 +03:00
spin_lock ( & state - > slock ) ;
2016-03-23 14:09:17 +03:00
/* disable in PMU sysreg */
if ( ! on & & data - > coupled_phy_id > = 0 & &
2016-11-07 13:48:32 +03:00
state - > phys [ data - > coupled_phy_id ] . phy - > power_count = = 0 ) {
2016-03-23 14:09:17 +03:00
regmap_read ( state - > regmaps [ data - > enable_map ] , data - > enable_reg ,
& val ) ;
val & = ~ data - > enable_val ;
regmap_write ( state - > regmaps [ data - > enable_map ] , data - > enable_reg ,
val ) ;
}
/* PHY reset */
regmap_read ( state - > regmaps [ data - > resetn_map ] , data - > resetn_reg , & val ) ;
val = on ? ( val | data - > resetn_val ) : ( val & ~ data - > resetn_val ) ;
regmap_write ( state - > regmaps [ data - > resetn_map ] , data - > resetn_reg , val ) ;
/* enable in PMU sysreg */
if ( on ) {
regmap_read ( state - > regmaps [ data - > enable_map ] , data - > enable_reg ,
& val ) ;
val | = data - > enable_val ;
regmap_write ( state - > regmaps [ data - > enable_map ] , data - > enable_reg ,
val ) ;
}
2013-10-16 20:28:10 +04:00
2015-02-27 06:50:57 +03:00
spin_unlock ( & state - > slock ) ;
2016-03-23 14:09:17 +03:00
2013-10-16 20:28:10 +04:00
return 0 ;
}
# define to_mipi_video_phy(desc) \
2016-03-23 14:09:17 +03:00
container_of ( ( desc ) , struct exynos_mipi_video_phy , phys [ ( desc ) - > index ] )
2013-10-16 20:28:10 +04:00
static int exynos_mipi_video_phy_power_on ( struct phy * phy )
{
struct video_phy_desc * phy_desc = phy_get_drvdata ( phy ) ;
struct exynos_mipi_video_phy * state = to_mipi_video_phy ( phy_desc ) ;
2016-03-23 14:09:17 +03:00
return __set_phy_state ( phy_desc - > data , state , 1 ) ;
2013-10-16 20:28:10 +04:00
}
static int exynos_mipi_video_phy_power_off ( struct phy * phy )
{
struct video_phy_desc * phy_desc = phy_get_drvdata ( phy ) ;
struct exynos_mipi_video_phy * state = to_mipi_video_phy ( phy_desc ) ;
2016-03-23 14:09:17 +03:00
return __set_phy_state ( phy_desc - > data , state , 0 ) ;
2013-10-16 20:28:10 +04:00
}
static struct phy * exynos_mipi_video_phy_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct exynos_mipi_video_phy * state = dev_get_drvdata ( dev ) ;
2016-03-23 14:09:17 +03:00
if ( WARN_ON ( args - > args [ 0 ] > = state - > num_phys ) )
2013-10-16 20:28:10 +04:00
return ERR_PTR ( - ENODEV ) ;
return state - > phys [ args - > args [ 0 ] ] . phy ;
}
2015-07-15 10:33:51 +03:00
static const struct phy_ops exynos_mipi_video_phy_ops = {
2013-10-16 20:28:10 +04:00
. power_on = exynos_mipi_video_phy_power_on ,
. power_off = exynos_mipi_video_phy_power_off ,
. owner = THIS_MODULE ,
} ;
static int exynos_mipi_video_phy_probe ( struct platform_device * pdev )
{
2016-03-23 14:09:17 +03:00
const struct mipi_phy_device_desc * phy_dev ;
2013-10-16 20:28:10 +04:00
struct exynos_mipi_video_phy * state ;
struct device * dev = & pdev - > dev ;
2016-03-23 14:09:17 +03:00
struct device_node * np = dev - > of_node ;
2013-10-16 20:28:10 +04:00
struct phy_provider * phy_provider ;
unsigned int i ;
2016-03-23 14:09:17 +03:00
phy_dev = of_device_get_match_data ( dev ) ;
if ( ! phy_dev )
return - ENODEV ;
2013-10-16 20:28:10 +04:00
state = devm_kzalloc ( dev , sizeof ( * state ) , GFP_KERNEL ) ;
if ( ! state )
return - ENOMEM ;
2016-03-23 14:09:17 +03:00
for ( i = 0 ; i < phy_dev - > num_regmaps ; i + + ) {
state - > regmaps [ i ] = syscon_regmap_lookup_by_phandle ( np ,
phy_dev - > regmap_names [ i ] ) ;
if ( IS_ERR ( state - > regmaps [ i ] ) )
return PTR_ERR ( state - > regmaps [ i ] ) ;
}
state - > num_phys = phy_dev - > num_phys ;
spin_lock_init ( & state - > slock ) ;
2013-10-16 20:28:10 +04:00
dev_set_drvdata ( dev , state ) ;
2016-03-23 14:09:17 +03:00
for ( i = 0 ; i < state - > num_phys ; i + + ) {
2014-07-14 14:25:02 +04:00
struct phy * phy = devm_phy_create ( dev , NULL ,
2014-11-19 18:28:21 +03:00
& exynos_mipi_video_phy_ops ) ;
2013-10-16 20:28:10 +04:00
if ( IS_ERR ( phy ) ) {
dev_err ( dev , " failed to create PHY %d \n " , i ) ;
return PTR_ERR ( phy ) ;
}
state - > phys [ i ] . phy = phy ;
state - > phys [ i ] . index = i ;
2016-03-23 14:09:17 +03:00
state - > phys [ i ] . data = & phy_dev - > phys [ i ] ;
2013-10-16 20:28:10 +04:00
phy_set_drvdata ( phy , & state - > phys [ i ] ) ;
}
2014-02-17 12:59:25 +04:00
phy_provider = devm_of_phy_provider_register ( dev ,
exynos_mipi_video_phy_xlate ) ;
2014-05-29 10:30:48 +04:00
return PTR_ERR_OR_ZERO ( phy_provider ) ;
2013-10-16 20:28:10 +04:00
}
static const struct of_device_id exynos_mipi_video_phy_of_match [ ] = {
2016-03-23 14:09:17 +03:00
{
. compatible = " samsung,s5pv210-mipi-video-phy " ,
. data = & s5pv210_mipi_phy ,
2016-03-23 14:09:18 +03:00
} , {
. compatible = " samsung,exynos5420-mipi-video-phy " ,
. data = & exynos5420_mipi_phy ,
} , {
. compatible = " samsung,exynos5433-mipi-video-phy " ,
. data = & exynos5433_mipi_phy ,
2016-03-23 14:09:17 +03:00
} ,
{ /* sentinel */ } ,
2013-10-16 20:28:10 +04:00
} ;
MODULE_DEVICE_TABLE ( of , exynos_mipi_video_phy_of_match ) ;
static struct platform_driver exynos_mipi_video_phy_driver = {
. probe = exynos_mipi_video_phy_probe ,
. driver = {
. of_match_table = exynos_mipi_video_phy_of_match ,
. name = " exynos-mipi-video-phy " ,
}
} ;
module_platform_driver ( exynos_mipi_video_phy_driver ) ;
MODULE_DESCRIPTION ( " Samsung S5P/EXYNOS SoC MIPI CSI-2/DSI PHY driver " ) ;
MODULE_AUTHOR ( " Sylwester Nawrocki <s.nawrocki@samsung.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;