2013-10-16 20:28:10 +04:00
/*
* Samsung S5P / EXYNOS SoC series MIPI CSIS / DSIM DPHY driver
*
* Copyright ( C ) 2013 Samsung Electronics Co . , Ltd .
* 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>
2015-01-16 20:30:37 +03:00
# include <linux/mfd/syscon/exynos4-pmu.h>
2013-10-16 20:28:10 +04:00
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.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>
2015-01-16 20:30:37 +03:00
# include <linux/mfd/syscon.h>
2013-10-16 20:28:10 +04:00
2015-01-16 20:30:37 +03:00
/* MIPI_PHYn_CONTROL reg. offset (for base address from ioremap): n = 0..1 */
2013-10-16 20:28:10 +04:00
# define EXYNOS_MIPI_PHY_CONTROL(n) ((n) * 4)
enum exynos_mipi_phy_id {
EXYNOS_MIPI_PHY_ID_CSIS0 ,
EXYNOS_MIPI_PHY_ID_DSIM0 ,
EXYNOS_MIPI_PHY_ID_CSIS1 ,
EXYNOS_MIPI_PHY_ID_DSIM1 ,
EXYNOS_MIPI_PHYS_NUM
} ;
# define is_mipi_dsim_phy_id(id) \
( ( id ) = = EXYNOS_MIPI_PHY_ID_DSIM0 | | ( id ) = = EXYNOS_MIPI_PHY_ID_DSIM1 )
struct exynos_mipi_video_phy {
struct video_phy_desc {
struct phy * phy ;
unsigned int index ;
} phys [ EXYNOS_MIPI_PHYS_NUM ] ;
2015-01-16 20:30:37 +03:00
spinlock_t slock ;
2013-10-16 20:28:10 +04:00
void __iomem * regs ;
2015-01-16 20:30:37 +03:00
struct regmap * regmap ;
2013-10-16 20:28:10 +04:00
} ;
static int __set_phy_state ( struct exynos_mipi_video_phy * state ,
enum exynos_mipi_phy_id id , unsigned int on )
{
2015-01-16 20:30:37 +03:00
const unsigned int offset = EXYNOS4_MIPI_PHY_CONTROL ( id / 2 ) ;
2013-10-16 20:28:10 +04:00
void __iomem * addr ;
2015-01-16 20:30:37 +03:00
u32 val , reset ;
2013-10-16 20:28:10 +04:00
if ( is_mipi_dsim_phy_id ( id ) )
2015-01-16 20:30:37 +03:00
reset = EXYNOS4_MIPI_PHY_MRESETN ;
2013-10-16 20:28:10 +04:00
else
2015-01-16 20:30:37 +03:00
reset = EXYNOS4_MIPI_PHY_SRESETN ;
2015-02-27 06:50:57 +03:00
spin_lock ( & state - > slock ) ;
2015-02-26 06:48:08 +03:00
if ( ! IS_ERR ( state - > regmap ) ) {
2015-01-16 20:30:37 +03:00
regmap_read ( state - > regmap , offset , & val ) ;
if ( on )
val | = reset ;
else
val & = ~ reset ;
regmap_write ( state - > regmap , offset , val ) ;
if ( on )
val | = EXYNOS4_MIPI_PHY_ENABLE ;
else if ( ! ( val & EXYNOS4_MIPI_PHY_RESET_MASK ) )
val & = ~ EXYNOS4_MIPI_PHY_ENABLE ;
regmap_write ( state - > regmap , offset , val ) ;
} else {
addr = state - > regs + EXYNOS_MIPI_PHY_CONTROL ( id / 2 ) ;
val = readl ( addr ) ;
if ( on )
val | = reset ;
else
val & = ~ reset ;
writel ( val , addr ) ;
/* Clear ENABLE bit only if MRESETN, SRESETN bits are not set */
if ( on )
val | = EXYNOS4_MIPI_PHY_ENABLE ;
else if ( ! ( val & EXYNOS4_MIPI_PHY_RESET_MASK ) )
val & = ~ EXYNOS4_MIPI_PHY_ENABLE ;
writel ( val , addr ) ;
}
2013-10-16 20:28:10 +04:00
2015-02-27 06:50:57 +03:00
spin_unlock ( & state - > slock ) ;
2013-10-16 20:28:10 +04:00
return 0 ;
}
# define to_mipi_video_phy(desc) \
container_of ( ( desc ) , struct exynos_mipi_video_phy , phys [ ( desc ) - > index ] ) ;
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 ) ;
return __set_phy_state ( state , phy_desc - > index , 1 ) ;
}
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 ) ;
2013-10-16 21:03:45 +04:00
return __set_phy_state ( state , phy_desc - > index , 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 ) ;
2014-05-12 16:56:28 +04:00
if ( WARN_ON ( args - > args [ 0 ] > = EXYNOS_MIPI_PHYS_NUM ) )
2013-10-16 20:28:10 +04:00
return ERR_PTR ( - ENODEV ) ;
return state - > phys [ args - > args [ 0 ] ] . phy ;
}
static struct phy_ops exynos_mipi_video_phy_ops = {
. 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 )
{
struct exynos_mipi_video_phy * state ;
struct device * dev = & pdev - > dev ;
struct phy_provider * phy_provider ;
unsigned int i ;
state = devm_kzalloc ( dev , sizeof ( * state ) , GFP_KERNEL ) ;
if ( ! state )
return - ENOMEM ;
2015-01-16 20:30:37 +03:00
state - > regmap = syscon_regmap_lookup_by_phandle ( dev - > of_node , " syscon " ) ;
if ( IS_ERR ( state - > regmap ) ) {
struct resource * res ;
dev_info ( dev , " regmap lookup failed: %ld \n " ,
PTR_ERR ( state - > regmap ) ) ;
2013-10-16 20:28:10 +04:00
2015-01-16 20:30:37 +03:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
state - > regs = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( state - > regs ) )
return PTR_ERR ( state - > regs ) ;
}
2013-10-16 20:28:10 +04:00
dev_set_drvdata ( dev , state ) ;
spin_lock_init ( & state - > slock ) ;
for ( i = 0 ; i < EXYNOS_MIPI_PHYS_NUM ; 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 ;
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 [ ] = {
{ . compatible = " samsung,s5pv210-mipi-video-phy " } ,
{ } ,
} ;
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 " ) ;