2014-06-27 10:01:11 +04:00
/*
* Clock driver for Palmas device .
*
* Copyright ( c ) 2013 , NVIDIA Corporation .
* Copyright ( c ) 2013 - 2014 Texas Instruments , Inc .
*
* Author : Laxman Dewangan < ldewangan @ nvidia . com >
* Peter Ujfalusi < peter . ujfalusi @ ti . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any kind ,
* whether express or implied ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*/
# include <linux/clk.h>
# include <linux/clkdev.h>
# include <linux/clk-provider.h>
# include <linux/mfd/palmas.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# define PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE1 1
# define PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE2 2
# define PALMAS_CLOCK_DT_EXT_CONTROL_NSLEEP 3
struct palmas_clk32k_desc {
const char * clk_name ;
unsigned int control_reg ;
unsigned int enable_mask ;
unsigned int sleep_mask ;
unsigned int sleep_reqstr_id ;
int delay ;
} ;
struct palmas_clock_info {
struct device * dev ;
struct clk * clk ;
struct clk_hw hw ;
struct palmas * palmas ;
struct palmas_clk32k_desc * clk_desc ;
int ext_control_pin ;
} ;
static inline struct palmas_clock_info * to_palmas_clks_info ( struct clk_hw * hw )
{
return container_of ( hw , struct palmas_clock_info , hw ) ;
}
static unsigned long palmas_clks_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
return 32768 ;
}
static int palmas_clks_prepare ( struct clk_hw * hw )
{
struct palmas_clock_info * cinfo = to_palmas_clks_info ( hw ) ;
int ret ;
ret = palmas_update_bits ( cinfo - > palmas , PALMAS_RESOURCE_BASE ,
cinfo - > clk_desc - > control_reg ,
cinfo - > clk_desc - > enable_mask ,
cinfo - > clk_desc - > enable_mask ) ;
if ( ret < 0 )
dev_err ( cinfo - > dev , " Reg 0x%02x update failed, %d \n " ,
cinfo - > clk_desc - > control_reg , ret ) ;
else if ( cinfo - > clk_desc - > delay )
udelay ( cinfo - > clk_desc - > delay ) ;
return ret ;
}
static void palmas_clks_unprepare ( struct clk_hw * hw )
{
struct palmas_clock_info * cinfo = to_palmas_clks_info ( hw ) ;
int ret ;
/*
* Clock can be disabled through external pin if it is externally
* controlled .
*/
if ( cinfo - > ext_control_pin )
return ;
ret = palmas_update_bits ( cinfo - > palmas , PALMAS_RESOURCE_BASE ,
cinfo - > clk_desc - > control_reg ,
cinfo - > clk_desc - > enable_mask , 0 ) ;
if ( ret < 0 )
dev_err ( cinfo - > dev , " Reg 0x%02x update failed, %d \n " ,
cinfo - > clk_desc - > control_reg , ret ) ;
}
static int palmas_clks_is_prepared ( struct clk_hw * hw )
{
struct palmas_clock_info * cinfo = to_palmas_clks_info ( hw ) ;
int ret ;
u32 val ;
if ( cinfo - > ext_control_pin )
return 1 ;
ret = palmas_read ( cinfo - > palmas , PALMAS_RESOURCE_BASE ,
cinfo - > clk_desc - > control_reg , & val ) ;
if ( ret < 0 ) {
dev_err ( cinfo - > dev , " Reg 0x%02x read failed, %d \n " ,
cinfo - > clk_desc - > control_reg , ret ) ;
return ret ;
}
return ! ! ( val & cinfo - > clk_desc - > enable_mask ) ;
}
static struct clk_ops palmas_clks_ops = {
. prepare = palmas_clks_prepare ,
. unprepare = palmas_clks_unprepare ,
. is_prepared = palmas_clks_is_prepared ,
. recalc_rate = palmas_clks_recalc_rate ,
} ;
struct palmas_clks_of_match_data {
struct clk_init_data init ;
struct palmas_clk32k_desc desc ;
} ;
static struct palmas_clks_of_match_data palmas_of_clk32kg = {
. init = {
. name = " clk32kg " ,
. ops = & palmas_clks_ops ,
. flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED ,
} ,
. desc = {
. clk_name = " clk32kg " ,
. control_reg = PALMAS_CLK32KG_CTRL ,
. enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE ,
. sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP ,
. sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KG ,
. delay = 200 ,
} ,
} ;
static struct palmas_clks_of_match_data palmas_of_clk32kgaudio = {
. init = {
. name = " clk32kgaudio " ,
. ops = & palmas_clks_ops ,
. flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED ,
} ,
. desc = {
. clk_name = " clk32kgaudio " ,
. control_reg = PALMAS_CLK32KGAUDIO_CTRL ,
. enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE ,
. sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP ,
. sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KGAUDIO ,
. delay = 200 ,
} ,
} ;
2015-03-31 21:50:42 +03:00
static const struct of_device_id palmas_clks_of_match [ ] = {
2014-06-27 10:01:11 +04:00
{
. compatible = " ti,palmas-clk32kg " ,
. data = & palmas_of_clk32kg ,
} ,
{
. compatible = " ti,palmas-clk32kgaudio " ,
. data = & palmas_of_clk32kgaudio ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , palmas_clks_of_match ) ;
static void palmas_clks_get_clk_data ( struct platform_device * pdev ,
struct palmas_clock_info * cinfo )
{
struct device_node * node = pdev - > dev . of_node ;
unsigned int prop ;
int ret ;
ret = of_property_read_u32 ( node , " ti,external-sleep-control " ,
& prop ) ;
if ( ret )
return ;
switch ( prop ) {
case PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE1 :
prop = PALMAS_EXT_CONTROL_ENABLE1 ;
break ;
case PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE2 :
prop = PALMAS_EXT_CONTROL_ENABLE2 ;
break ;
case PALMAS_CLOCK_DT_EXT_CONTROL_NSLEEP :
prop = PALMAS_EXT_CONTROL_NSLEEP ;
break ;
default :
dev_warn ( & pdev - > dev , " %s: Invalid ext control option: %u \n " ,
node - > name , prop ) ;
prop = 0 ;
break ;
}
cinfo - > ext_control_pin = prop ;
}
static int palmas_clks_init_configure ( struct palmas_clock_info * cinfo )
{
int ret ;
ret = palmas_update_bits ( cinfo - > palmas , PALMAS_RESOURCE_BASE ,
cinfo - > clk_desc - > control_reg ,
cinfo - > clk_desc - > sleep_mask , 0 ) ;
if ( ret < 0 ) {
dev_err ( cinfo - > dev , " Reg 0x%02x update failed, %d \n " ,
cinfo - > clk_desc - > control_reg , ret ) ;
return ret ;
}
if ( cinfo - > ext_control_pin ) {
ret = clk_prepare ( cinfo - > clk ) ;
if ( ret < 0 ) {
dev_err ( cinfo - > dev , " Clock prep failed, %d \n " , ret ) ;
return ret ;
}
ret = palmas_ext_control_req_config ( cinfo - > palmas ,
cinfo - > clk_desc - > sleep_reqstr_id ,
cinfo - > ext_control_pin , true ) ;
if ( ret < 0 ) {
dev_err ( cinfo - > dev , " Ext config for %s failed, %d \n " ,
cinfo - > clk_desc - > clk_name , ret ) ;
return ret ;
}
}
return ret ;
}
static int palmas_clks_probe ( struct platform_device * pdev )
{
struct palmas * palmas = dev_get_drvdata ( pdev - > dev . parent ) ;
struct device_node * node = pdev - > dev . of_node ;
struct palmas_clks_of_match_data * match_data ;
const struct of_device_id * match ;
struct palmas_clock_info * cinfo ;
struct clk * clk ;
int ret ;
match = of_match_device ( palmas_clks_of_match , & pdev - > dev ) ;
match_data = ( struct palmas_clks_of_match_data * ) match - > data ;
cinfo = devm_kzalloc ( & pdev - > dev , sizeof ( * cinfo ) , GFP_KERNEL ) ;
if ( ! cinfo )
return - ENOMEM ;
palmas_clks_get_clk_data ( pdev , cinfo ) ;
platform_set_drvdata ( pdev , cinfo ) ;
cinfo - > dev = & pdev - > dev ;
cinfo - > palmas = palmas ;
cinfo - > clk_desc = & match_data - > desc ;
cinfo - > hw . init = & match_data - > init ;
clk = devm_clk_register ( & pdev - > dev , & cinfo - > hw ) ;
if ( IS_ERR ( clk ) ) {
ret = PTR_ERR ( clk ) ;
dev_err ( & pdev - > dev , " Fail to register clock %s, %d \n " ,
match_data - > desc . clk_name , ret ) ;
return ret ;
}
cinfo - > clk = clk ;
ret = palmas_clks_init_configure ( cinfo ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Clock config failed, %d \n " , ret ) ;
return ret ;
}
ret = of_clk_add_provider ( node , of_clk_src_simple_get , cinfo - > clk ) ;
if ( ret < 0 )
dev_err ( & pdev - > dev , " Fail to add clock driver, %d \n " , ret ) ;
return ret ;
}
static int palmas_clks_remove ( struct platform_device * pdev )
{
of_clk_del_provider ( pdev - > dev . of_node ) ;
return 0 ;
}
static struct platform_driver palmas_clks_driver = {
. driver = {
. name = " palmas-clk " ,
. of_match_table = palmas_clks_of_match ,
} ,
. probe = palmas_clks_probe ,
. remove = palmas_clks_remove ,
} ;
module_platform_driver ( palmas_clks_driver ) ;
MODULE_DESCRIPTION ( " Clock driver for Palmas Series Devices " ) ;
MODULE_ALIAS ( " platform:palmas-clk " ) ;
MODULE_AUTHOR ( " Peter Ujfalusi <peter.ujfalusi@ti.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;