2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2014-06-24 18:08:26 +02:00
/*
* Copyright ( c ) 2014 Samsung Electronics Co . , Ltd .
* Author : Tomasz Figa < t . figa @ samsung . com >
*
* Clock driver for Exynos clock output
*/
2015-06-19 15:00:46 -07:00
# include <linux/slab.h>
2014-06-24 18:08:26 +02:00
# include <linux/clk.h>
# include <linux/clk-provider.h>
2020-10-01 18:56:46 +02:00
# include <linux/module.h>
2019-04-18 15:20:22 -07:00
# include <linux/io.h>
2014-06-24 18:08:26 +02:00
# include <linux/of.h>
# include <linux/of_address.h>
2020-10-01 18:56:46 +02:00
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/pm.h>
2014-06-24 18:08:26 +02:00
# define EXYNOS_CLKOUT_NR_CLKS 1
# define EXYNOS_CLKOUT_PARENTS 32
# define EXYNOS_PMU_DEBUG_REG 0xa00
# define EXYNOS_CLKOUT_DISABLE_SHIFT 0
# define EXYNOS_CLKOUT_MUX_SHIFT 8
# define EXYNOS4_CLKOUT_MUX_MASK 0xf
# define EXYNOS5_CLKOUT_MUX_MASK 0x1f
struct exynos_clkout {
struct clk_gate gate ;
struct clk_mux mux ;
spinlock_t slock ;
void __iomem * reg ;
2020-10-01 18:56:46 +02:00
struct device_node * np ;
2014-06-24 18:08:26 +02:00
u32 pmu_debug_save ;
2017-04-24 08:42:22 +02:00
struct clk_hw_onecell_data data ;
2014-06-24 18:08:26 +02:00
} ;
2020-10-01 18:56:46 +02:00
struct exynos_clkout_variant {
u32 mux_mask ;
} ;
2014-06-24 18:08:26 +02:00
2020-10-01 18:56:46 +02:00
static const struct exynos_clkout_variant exynos_clkout_exynos4 = {
. mux_mask = EXYNOS4_CLKOUT_MUX_MASK ,
} ;
2014-06-24 18:08:26 +02:00
2020-10-01 18:56:46 +02:00
static const struct exynos_clkout_variant exynos_clkout_exynos5 = {
. mux_mask = EXYNOS5_CLKOUT_MUX_MASK ,
} ;
2014-06-24 18:08:26 +02:00
2020-10-01 18:56:46 +02:00
static const struct of_device_id exynos_clkout_ids [ ] = {
{
. compatible = " samsung,exynos3250-pmu " ,
. data = & exynos_clkout_exynos4 ,
} , {
. compatible = " samsung,exynos4210-pmu " ,
. data = & exynos_clkout_exynos4 ,
} , {
. compatible = " samsung,exynos4412-pmu " ,
. data = & exynos_clkout_exynos4 ,
} , {
. compatible = " samsung,exynos5250-pmu " ,
. data = & exynos_clkout_exynos5 ,
} , {
. compatible = " samsung,exynos5410-pmu " ,
. data = & exynos_clkout_exynos5 ,
} , {
. compatible = " samsung,exynos5420-pmu " ,
. data = & exynos_clkout_exynos5 ,
} , {
. compatible = " samsung,exynos5433-pmu " ,
. data = & exynos_clkout_exynos5 ,
} , { }
} ;
2020-11-10 20:37:49 +01:00
MODULE_DEVICE_TABLE ( of , exynos_clkout_ids ) ;
2020-10-01 18:56:46 +02:00
/*
* Device will be instantiated as child of PMU device without its own
* device node . Therefore match compatibles against parent .
*/
static int exynos_clkout_match_parent_dev ( struct device * dev , u32 * mux_mask )
2014-06-24 18:08:26 +02:00
{
2020-10-01 18:56:46 +02:00
const struct exynos_clkout_variant * variant ;
const struct of_device_id * match ;
2014-06-24 18:08:26 +02:00
2020-10-01 18:56:46 +02:00
if ( ! dev - > parent ) {
dev_err ( dev , " not instantiated from MFD \n " ) ;
return - EINVAL ;
}
match = of_match_device ( exynos_clkout_ids , dev - > parent ) ;
if ( ! match ) {
dev_err ( dev , " cannot match parent device \n " ) ;
return - EINVAL ;
}
variant = match - > data ;
2014-06-24 18:08:26 +02:00
2020-10-01 18:56:46 +02:00
* mux_mask = variant - > mux_mask ;
return 0 ;
}
static int exynos_clkout_probe ( struct platform_device * pdev )
2014-06-24 18:08:26 +02:00
{
const char * parent_names [ EXYNOS_CLKOUT_PARENTS ] ;
struct clk * parents [ EXYNOS_CLKOUT_PARENTS ] ;
2020-10-01 18:56:46 +02:00
struct exynos_clkout * clkout ;
int parent_count , ret , i ;
u32 mux_mask ;
2014-06-24 18:08:26 +02:00
2020-10-01 18:56:46 +02:00
clkout = devm_kzalloc ( & pdev - > dev ,
struct_size ( clkout , data . hws , EXYNOS_CLKOUT_NR_CLKS ) ,
GFP_KERNEL ) ;
2014-06-24 18:08:26 +02:00
if ( ! clkout )
2020-10-01 18:56:46 +02:00
return - ENOMEM ;
ret = exynos_clkout_match_parent_dev ( & pdev - > dev , & mux_mask ) ;
if ( ret )
return ret ;
clkout - > np = pdev - > dev . of_node ;
if ( ! clkout - > np ) {
/*
* pdev - > dev . parent was checked by exynos_clkout_match_parent_dev ( )
* so it is not NULL .
*/
clkout - > np = pdev - > dev . parent - > of_node ;
}
platform_set_drvdata ( pdev , clkout ) ;
2014-06-24 18:08:26 +02:00
spin_lock_init ( & clkout - > slock ) ;
parent_count = 0 ;
for ( i = 0 ; i < EXYNOS_CLKOUT_PARENTS ; + + i ) {
char name [ ] = " clkoutXX " ;
snprintf ( name , sizeof ( name ) , " clkout%d " , i ) ;
2020-10-01 18:56:46 +02:00
parents [ i ] = of_clk_get_by_name ( clkout - > np , name ) ;
2014-06-24 18:08:26 +02:00
if ( IS_ERR ( parents [ i ] ) ) {
parent_names [ i ] = " none " ;
continue ;
}
parent_names [ i ] = __clk_get_name ( parents [ i ] ) ;
parent_count = i + 1 ;
}
if ( ! parent_count )
2020-10-01 18:56:46 +02:00
return - EINVAL ;
2014-06-24 18:08:26 +02:00
2020-10-01 18:56:46 +02:00
clkout - > reg = of_iomap ( clkout - > np , 0 ) ;
if ( ! clkout - > reg ) {
ret = - ENODEV ;
2014-06-24 18:08:26 +02:00
goto clks_put ;
2020-10-01 18:56:46 +02:00
}
2014-06-24 18:08:26 +02:00
clkout - > gate . reg = clkout - > reg + EXYNOS_PMU_DEBUG_REG ;
clkout - > gate . bit_idx = EXYNOS_CLKOUT_DISABLE_SHIFT ;
clkout - > gate . flags = CLK_GATE_SET_TO_DISABLE ;
clkout - > gate . lock = & clkout - > slock ;
clkout - > mux . reg = clkout - > reg + EXYNOS_PMU_DEBUG_REG ;
clkout - > mux . mask = mux_mask ;
clkout - > mux . shift = EXYNOS_CLKOUT_MUX_SHIFT ;
clkout - > mux . lock = & clkout - > slock ;
2017-04-24 08:42:22 +02:00
clkout - > data . hws [ 0 ] = clk_hw_register_composite ( NULL , " clkout " ,
2014-06-24 18:08:26 +02:00
parent_names , parent_count , & clkout - > mux . hw ,
& clk_mux_ops , NULL , NULL , & clkout - > gate . hw ,
& clk_gate_ops , CLK_SET_RATE_PARENT
| CLK_SET_RATE_NO_REPARENT ) ;
2020-10-01 18:56:46 +02:00
if ( IS_ERR ( clkout - > data . hws [ 0 ] ) ) {
ret = PTR_ERR ( clkout - > data . hws [ 0 ] ) ;
2014-06-24 18:08:26 +02:00
goto err_unmap ;
2020-10-01 18:56:46 +02:00
}
2014-06-24 18:08:26 +02:00
2017-04-24 08:42:22 +02:00
clkout - > data . num = EXYNOS_CLKOUT_NR_CLKS ;
2020-10-01 18:56:46 +02:00
ret = of_clk_add_hw_provider ( clkout - > np , of_clk_hw_onecell_get , & clkout - > data ) ;
2014-06-24 18:08:26 +02:00
if ( ret )
goto err_clk_unreg ;
2020-10-01 18:56:46 +02:00
return 0 ;
2014-06-24 18:08:26 +02:00
err_clk_unreg :
2017-04-24 08:42:22 +02:00
clk_hw_unregister ( clkout - > data . hws [ 0 ] ) ;
2014-06-24 18:08:26 +02:00
err_unmap :
iounmap ( clkout - > reg ) ;
clks_put :
for ( i = 0 ; i < EXYNOS_CLKOUT_PARENTS ; + + i )
if ( ! IS_ERR ( parents [ i ] ) )
clk_put ( parents [ i ] ) ;
2020-10-01 18:56:46 +02:00
dev_err ( & pdev - > dev , " failed to register clkout clock \n " ) ;
return ret ;
2014-06-24 18:08:26 +02:00
}
2020-10-01 18:56:46 +02:00
static int exynos_clkout_remove ( struct platform_device * pdev )
{
struct exynos_clkout * clkout = platform_get_drvdata ( pdev ) ;
of_clk_del_provider ( clkout - > np ) ;
clk_hw_unregister ( clkout - > data . hws [ 0 ] ) ;
iounmap ( clkout - > reg ) ;
return 0 ;
}
2016-10-26 08:12:20 +02:00
2020-12-04 10:16:11 +01:00
static int __maybe_unused exynos_clkout_suspend ( struct device * dev )
2014-06-24 18:08:26 +02:00
{
2020-10-01 18:56:46 +02:00
struct exynos_clkout * clkout = dev_get_drvdata ( dev ) ;
clkout - > pmu_debug_save = readl ( clkout - > reg + EXYNOS_PMU_DEBUG_REG ) ;
return 0 ;
2014-06-24 18:08:26 +02:00
}
2020-10-01 18:56:46 +02:00
2020-12-04 10:16:11 +01:00
static int __maybe_unused exynos_clkout_resume ( struct device * dev )
2014-06-24 18:08:26 +02:00
{
2020-10-01 18:56:46 +02:00
struct exynos_clkout * clkout = dev_get_drvdata ( dev ) ;
writel ( clkout - > pmu_debug_save , clkout - > reg + EXYNOS_PMU_DEBUG_REG ) ;
return 0 ;
2014-06-24 18:08:26 +02:00
}
2020-10-01 18:56:46 +02:00
static SIMPLE_DEV_PM_OPS ( exynos_clkout_pm_ops , exynos_clkout_suspend ,
exynos_clkout_resume ) ;
static struct platform_driver exynos_clkout_driver = {
. driver = {
. name = " exynos-clkout " ,
. of_match_table = exynos_clkout_ids ,
. pm = & exynos_clkout_pm_ops ,
} ,
. probe = exynos_clkout_probe ,
. remove = exynos_clkout_remove ,
} ;
module_platform_driver ( exynos_clkout_driver ) ;
MODULE_AUTHOR ( " Krzysztof Kozlowski <krzk@kernel.org> " ) ;
MODULE_AUTHOR ( " Tomasz Figa <tomasz.figa@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Samsung Exynos clock output driver " ) ;
MODULE_LICENSE ( " GPL " ) ;