2014-03-22 04:59:37 +04:00
/*
* Copyright ( c ) 2013 - 2014 , The Linux Foundation . All rights reserved .
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/export.h>
2015-07-24 21:55:42 +03:00
# include <linux/module.h>
2014-03-22 04:59:37 +04:00
# include <linux/regmap.h>
# include <linux/platform_device.h>
# include <linux/clk-provider.h>
# include <linux/reset-controller.h>
2015-10-27 03:11:32 +03:00
# include <linux/of.h>
2014-03-22 04:59:37 +04:00
# include "common.h"
2014-09-05 00:21:50 +04:00
# include "clk-rcg.h"
2014-03-22 04:59:37 +04:00
# include "clk-regmap.h"
# include "reset.h"
2015-08-06 13:37:43 +03:00
# include "gdsc.h"
2014-03-22 04:59:37 +04:00
struct qcom_cc {
struct qcom_reset_controller reset ;
2016-08-17 01:38:27 +03:00
struct clk_regmap * * rclks ;
size_t num_rclks ;
2014-03-22 04:59:37 +04:00
} ;
2014-09-05 00:21:50 +04:00
const
struct freq_tbl * qcom_find_freq ( const struct freq_tbl * f , unsigned long rate )
{
if ( ! f )
return NULL ;
for ( ; f - > freq ; f + + )
if ( rate < = f - > freq )
return f ;
/* Default to our fastest rate */
return f - 1 ;
}
EXPORT_SYMBOL_GPL ( qcom_find_freq ) ;
2016-11-21 09:37:11 +03:00
const struct freq_tbl * qcom_find_freq_floor ( const struct freq_tbl * f ,
unsigned long rate )
{
const struct freq_tbl * best = NULL ;
for ( ; f - > freq ; f + + ) {
if ( rate > = f - > freq )
best = f ;
else
break ;
}
return best ;
}
EXPORT_SYMBOL_GPL ( qcom_find_freq_floor ) ;
2015-03-20 19:30:26 +03:00
int qcom_find_src_index ( struct clk_hw * hw , const struct parent_map * map , u8 src )
{
2015-06-26 02:53:23 +03:00
int i , num_parents = clk_hw_get_num_parents ( hw ) ;
2015-03-20 19:30:26 +03:00
for ( i = 0 ; i < num_parents ; i + + )
if ( src = = map [ i ] . src )
return i ;
return - ENOENT ;
}
EXPORT_SYMBOL_GPL ( qcom_find_src_index ) ;
2014-07-16 01:59:21 +04:00
struct regmap *
qcom_cc_map ( struct platform_device * pdev , const struct qcom_cc_desc * desc )
2014-03-22 04:59:37 +04:00
{
void __iomem * base ;
struct resource * res ;
2014-07-16 01:59:21 +04:00
struct device * dev = & pdev - > dev ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( base ) )
return ERR_CAST ( base ) ;
return devm_regmap_init_mmio ( dev , base , desc - > config ) ;
}
EXPORT_SYMBOL_GPL ( qcom_cc_map ) ;
2016-09-29 11:35:45 +03:00
void
qcom_pll_set_fsm_mode ( struct regmap * map , u32 reg , u8 bias_count , u8 lock_count )
{
u32 val ;
u32 mask ;
/* De-assert reset to FSM */
regmap_update_bits ( map , reg , PLL_VOTE_FSM_RESET , 0 ) ;
/* Program bias count and lock count */
val = bias_count < < PLL_BIAS_COUNT_SHIFT |
lock_count < < PLL_LOCK_COUNT_SHIFT ;
mask = PLL_BIAS_COUNT_MASK < < PLL_BIAS_COUNT_SHIFT ;
mask | = PLL_LOCK_COUNT_MASK < < PLL_LOCK_COUNT_SHIFT ;
regmap_update_bits ( map , reg , mask , val ) ;
/* Enable PLL FSM voting */
regmap_update_bits ( map , reg , PLL_VOTE_FSM_ENA , PLL_VOTE_FSM_ENA ) ;
}
EXPORT_SYMBOL_GPL ( qcom_pll_set_fsm_mode ) ;
2015-10-08 09:59:57 +03:00
static void qcom_cc_gdsc_unregister ( void * data )
{
gdsc_unregister ( data ) ;
}
2015-10-27 03:11:32 +03:00
/*
* Backwards compatibility with old DTs . Register a pass - through factor 1 / 1
2017-05-09 01:57:50 +03:00
* clock to translate ' path ' clk into ' name ' clk and register the ' path '
2015-10-27 03:11:32 +03:00
* clk as a fixed rate clock if it isn ' t present .
*/
static int _qcom_cc_register_board_clk ( struct device * dev , const char * path ,
const char * name , unsigned long rate ,
bool add_factor )
{
struct device_node * node = NULL ;
struct device_node * clocks_node ;
struct clk_fixed_factor * factor ;
struct clk_fixed_rate * fixed ;
struct clk_init_data init_data = { } ;
2016-08-17 01:38:27 +03:00
int ret ;
2015-10-27 03:11:32 +03:00
clocks_node = of_find_node_by_path ( " /clocks " ) ;
2017-11-11 19:29:28 +03:00
if ( clocks_node ) {
node = of_get_child_by_name ( clocks_node , path ) ;
of_node_put ( clocks_node ) ;
}
2015-10-27 03:11:32 +03:00
if ( ! node ) {
fixed = devm_kzalloc ( dev , sizeof ( * fixed ) , GFP_KERNEL ) ;
if ( ! fixed )
return - EINVAL ;
fixed - > fixed_rate = rate ;
fixed - > hw . init = & init_data ;
init_data . name = path ;
init_data . ops = & clk_fixed_rate_ops ;
2016-08-17 01:38:27 +03:00
ret = devm_clk_hw_register ( dev , & fixed - > hw ) ;
if ( ret )
return ret ;
2015-10-27 03:11:32 +03:00
}
of_node_put ( node ) ;
if ( add_factor ) {
factor = devm_kzalloc ( dev , sizeof ( * factor ) , GFP_KERNEL ) ;
if ( ! factor )
return - EINVAL ;
factor - > mult = factor - > div = 1 ;
factor - > hw . init = & init_data ;
init_data . name = name ;
init_data . parent_names = & path ;
init_data . num_parents = 1 ;
init_data . flags = 0 ;
init_data . ops = & clk_fixed_factor_ops ;
2016-08-17 01:38:27 +03:00
ret = devm_clk_hw_register ( dev , & factor - > hw ) ;
if ( ret )
return ret ;
2015-10-27 03:11:32 +03:00
}
return 0 ;
}
int qcom_cc_register_board_clk ( struct device * dev , const char * path ,
const char * name , unsigned long rate )
{
bool add_factor = true ;
2016-11-02 18:56:58 +03:00
/*
* TODO : The RPM clock driver currently does not support the xo clock .
* When xo is added to the RPM clock driver , we should change this
* function to skip registration of xo factor clocks .
*/
2015-10-27 03:11:32 +03:00
return _qcom_cc_register_board_clk ( dev , path , name , rate , add_factor ) ;
}
EXPORT_SYMBOL_GPL ( qcom_cc_register_board_clk ) ;
int qcom_cc_register_sleep_clk ( struct device * dev )
{
return _qcom_cc_register_board_clk ( dev , " sleep_clk " , " sleep_clk_src " ,
32768 , true ) ;
}
EXPORT_SYMBOL_GPL ( qcom_cc_register_sleep_clk ) ;
2016-08-17 01:38:27 +03:00
static struct clk_hw * qcom_cc_clk_hw_get ( struct of_phandle_args * clkspec ,
void * data )
{
struct qcom_cc * cc = data ;
unsigned int idx = clkspec - > args [ 0 ] ;
if ( idx > = cc - > num_rclks ) {
pr_err ( " %s: invalid index %u \n " , __func__ , idx ) ;
return ERR_PTR ( - EINVAL ) ;
}
return cc - > rclks [ idx ] ? & cc - > rclks [ idx ] - > hw : ERR_PTR ( - ENOENT ) ;
}
2014-07-16 01:59:21 +04:00
int qcom_cc_really_probe ( struct platform_device * pdev ,
const struct qcom_cc_desc * desc , struct regmap * regmap )
{
2014-03-22 04:59:37 +04:00
int i , ret ;
struct device * dev = & pdev - > dev ;
struct qcom_reset_controller * reset ;
struct qcom_cc * cc ;
2015-12-01 19:12:11 +03:00
struct gdsc_desc * scd ;
2014-03-22 04:59:37 +04:00
size_t num_clks = desc - > num_clks ;
struct clk_regmap * * rclks = desc - > clks ;
2016-08-17 01:38:27 +03:00
cc = devm_kzalloc ( dev , sizeof ( * cc ) , GFP_KERNEL ) ;
2014-03-22 04:59:37 +04:00
if ( ! cc )
return - ENOMEM ;
2016-08-17 01:38:27 +03:00
cc - > rclks = rclks ;
cc - > num_rclks = num_clks ;
2014-03-22 04:59:37 +04:00
for ( i = 0 ; i < num_clks ; i + + ) {
2016-08-17 01:38:27 +03:00
if ( ! rclks [ i ] )
2014-03-22 04:59:37 +04:00
continue ;
2016-08-17 01:38:27 +03:00
ret = devm_clk_register_regmap ( dev , rclks [ i ] ) ;
if ( ret )
return ret ;
2014-03-22 04:59:37 +04:00
}
2017-09-02 02:16:41 +03:00
ret = devm_of_clk_add_hw_provider ( dev , qcom_cc_clk_hw_get , cc ) ;
2015-12-23 15:27:20 +03:00
if ( ret )
return ret ;
2015-10-08 09:59:57 +03:00
2014-03-22 04:59:37 +04:00
reset = & cc - > reset ;
reset - > rcdev . of_node = dev - > of_node ;
reset - > rcdev . ops = & qcom_reset_ops ;
reset - > rcdev . owner = dev - > driver - > owner ;
reset - > rcdev . nr_resets = desc - > num_resets ;
reset - > regmap = regmap ;
reset - > reset_map = desc - > resets ;
2017-09-02 02:16:41 +03:00
ret = devm_reset_controller_register ( dev , & reset - > rcdev ) ;
2015-12-23 15:27:20 +03:00
if ( ret )
return ret ;
2014-03-22 04:59:37 +04:00
2015-08-06 13:37:43 +03:00
if ( desc - > gdscs & & desc - > num_gdscs ) {
2015-12-01 19:12:11 +03:00
scd = devm_kzalloc ( dev , sizeof ( * scd ) , GFP_KERNEL ) ;
if ( ! scd )
return - ENOMEM ;
scd - > dev = dev ;
scd - > scs = desc - > gdscs ;
scd - > num = desc - > num_gdscs ;
ret = gdsc_register ( scd , & reset - > rcdev , regmap ) ;
if ( ret )
return ret ;
ret = devm_add_action_or_reset ( dev , qcom_cc_gdsc_unregister ,
scd ) ;
2015-08-06 13:37:43 +03:00
if ( ret )
2015-10-08 09:59:57 +03:00
return ret ;
2015-08-06 13:37:43 +03:00
}
2015-12-01 19:12:11 +03:00
return 0 ;
2014-03-22 04:59:37 +04:00
}
2014-07-16 01:59:21 +04:00
EXPORT_SYMBOL_GPL ( qcom_cc_really_probe ) ;
int qcom_cc_probe ( struct platform_device * pdev , const struct qcom_cc_desc * desc )
{
struct regmap * regmap ;
regmap = qcom_cc_map ( pdev , desc ) ;
if ( IS_ERR ( regmap ) )
return PTR_ERR ( regmap ) ;
return qcom_cc_really_probe ( pdev , desc , regmap ) ;
}
2014-03-22 04:59:37 +04:00
EXPORT_SYMBOL_GPL ( qcom_cc_probe ) ;
2015-07-24 21:55:42 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;