2019-05-29 07:17:58 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2017-11-21 14:41:04 +05:30
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*/
# include <linux/bitops.h>
# include <linux/clk.h>
# include <linux/clk-provider.h>
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/log2.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# include <linux/types.h>
# define REG_DIV_CTL1 0x43
# define DIV_CTL1_DIV_FACTOR_MASK GENMASK(2, 0)
# define REG_EN_CTL 0x46
# define REG_EN_MASK BIT(7)
struct clkdiv {
struct regmap * regmap ;
u16 base ;
spinlock_t lock ;
struct clk_hw hw ;
unsigned int cxo_period_ns ;
} ;
static inline struct clkdiv * to_clkdiv ( struct clk_hw * hw )
{
return container_of ( hw , struct clkdiv , hw ) ;
}
static inline unsigned int div_factor_to_div ( unsigned int div_factor )
{
if ( ! div_factor )
div_factor = 1 ;
return 1 < < ( div_factor - 1 ) ;
}
static inline unsigned int div_to_div_factor ( unsigned int div )
{
return min ( ilog2 ( div ) + 1 , 7 ) ;
}
static bool is_spmi_pmic_clkdiv_enabled ( struct clkdiv * clkdiv )
{
unsigned int val = 0 ;
regmap_read ( clkdiv - > regmap , clkdiv - > base + REG_EN_CTL , & val ) ;
return val & REG_EN_MASK ;
}
static int
__spmi_pmic_clkdiv_set_enable_state ( struct clkdiv * clkdiv , bool enable ,
unsigned int div_factor )
{
int ret ;
unsigned int ns = clkdiv - > cxo_period_ns ;
unsigned int div = div_factor_to_div ( div_factor ) ;
ret = regmap_update_bits ( clkdiv - > regmap , clkdiv - > base + REG_EN_CTL ,
REG_EN_MASK , enable ? REG_EN_MASK : 0 ) ;
if ( ret )
return ret ;
if ( enable )
ndelay ( ( 2 + 3 * div ) * ns ) ;
else
ndelay ( 3 * div * ns ) ;
return 0 ;
}
static int spmi_pmic_clkdiv_set_enable_state ( struct clkdiv * clkdiv , bool enable )
{
unsigned int div_factor ;
regmap_read ( clkdiv - > regmap , clkdiv - > base + REG_DIV_CTL1 , & div_factor ) ;
div_factor & = DIV_CTL1_DIV_FACTOR_MASK ;
return __spmi_pmic_clkdiv_set_enable_state ( clkdiv , enable , div_factor ) ;
}
static int clk_spmi_pmic_div_enable ( struct clk_hw * hw )
{
struct clkdiv * clkdiv = to_clkdiv ( hw ) ;
unsigned long flags ;
int ret ;
spin_lock_irqsave ( & clkdiv - > lock , flags ) ;
ret = spmi_pmic_clkdiv_set_enable_state ( clkdiv , true ) ;
spin_unlock_irqrestore ( & clkdiv - > lock , flags ) ;
return ret ;
}
static void clk_spmi_pmic_div_disable ( struct clk_hw * hw )
{
struct clkdiv * clkdiv = to_clkdiv ( hw ) ;
unsigned long flags ;
spin_lock_irqsave ( & clkdiv - > lock , flags ) ;
spmi_pmic_clkdiv_set_enable_state ( clkdiv , false ) ;
spin_unlock_irqrestore ( & clkdiv - > lock , flags ) ;
}
static long clk_spmi_pmic_div_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
unsigned int div , div_factor ;
div = DIV_ROUND_UP ( * parent_rate , rate ) ;
div_factor = div_to_div_factor ( div ) ;
div = div_factor_to_div ( div_factor ) ;
return * parent_rate / div ;
}
static unsigned long
clk_spmi_pmic_div_recalc_rate ( struct clk_hw * hw , unsigned long parent_rate )
{
struct clkdiv * clkdiv = to_clkdiv ( hw ) ;
unsigned int div_factor ;
regmap_read ( clkdiv - > regmap , clkdiv - > base + REG_DIV_CTL1 , & div_factor ) ;
div_factor & = DIV_CTL1_DIV_FACTOR_MASK ;
return parent_rate / div_factor_to_div ( div_factor ) ;
}
static int clk_spmi_pmic_div_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct clkdiv * clkdiv = to_clkdiv ( hw ) ;
unsigned int div_factor = div_to_div_factor ( parent_rate / rate ) ;
unsigned long flags ;
bool enabled ;
int ret ;
spin_lock_irqsave ( & clkdiv - > lock , flags ) ;
enabled = is_spmi_pmic_clkdiv_enabled ( clkdiv ) ;
if ( enabled ) {
ret = spmi_pmic_clkdiv_set_enable_state ( clkdiv , false ) ;
if ( ret )
goto unlock ;
}
ret = regmap_update_bits ( clkdiv - > regmap , clkdiv - > base + REG_DIV_CTL1 ,
DIV_CTL1_DIV_FACTOR_MASK , div_factor ) ;
if ( ret )
goto unlock ;
if ( enabled )
ret = __spmi_pmic_clkdiv_set_enable_state ( clkdiv , true ,
div_factor ) ;
unlock :
spin_unlock_irqrestore ( & clkdiv - > lock , flags ) ;
return ret ;
}
static const struct clk_ops clk_spmi_pmic_div_ops = {
. enable = clk_spmi_pmic_div_enable ,
. disable = clk_spmi_pmic_div_disable ,
. set_rate = clk_spmi_pmic_div_set_rate ,
. recalc_rate = clk_spmi_pmic_div_recalc_rate ,
. round_rate = clk_spmi_pmic_div_round_rate ,
} ;
struct spmi_pmic_div_clk_cc {
int nclks ;
struct clkdiv clks [ ] ;
} ;
static struct clk_hw *
spmi_pmic_div_clk_hw_get ( struct of_phandle_args * clkspec , void * data )
{
struct spmi_pmic_div_clk_cc * cc = data ;
int idx = clkspec - > args [ 0 ] - 1 ; /* Start at 1 instead of 0 */
if ( idx < 0 | | idx > = cc - > nclks ) {
pr_err ( " %s: index value %u is invalid; allowed range [1, %d] \n " ,
__func__ , clkspec - > args [ 0 ] , cc - > nclks ) ;
return ERR_PTR ( - EINVAL ) ;
}
return & cc - > clks [ idx ] . hw ;
}
static int spmi_pmic_clkdiv_probe ( struct platform_device * pdev )
{
struct spmi_pmic_div_clk_cc * cc ;
struct clk_init_data init = { } ;
struct clkdiv * clkdiv ;
struct clk * cxo ;
struct regmap * regmap ;
struct device * dev = & pdev - > dev ;
struct device_node * of_node = dev - > of_node ;
const char * parent_name ;
int nclks , i , ret , cxo_hz ;
char name [ 20 ] ;
u32 start ;
ret = of_property_read_u32 ( of_node , " reg " , & start ) ;
if ( ret < 0 ) {
dev_err ( dev , " reg property reading failed \n " ) ;
return ret ;
}
regmap = dev_get_regmap ( dev - > parent , NULL ) ;
if ( ! regmap ) {
dev_err ( dev , " Couldn't get parent's regmap \n " ) ;
return - EINVAL ;
}
ret = of_property_read_u32 ( of_node , " qcom,num-clkdivs " , & nclks ) ;
if ( ret < 0 ) {
dev_err ( dev , " qcom,num-clkdivs property reading failed, ret=%d \n " ,
ret ) ;
return ret ;
}
if ( ! nclks )
return - EINVAL ;
treewide: Use struct_size() for devm_kmalloc() and friends
Replaces open-coded struct size calculations with struct_size() for
devm_*, f2fs_*, and sock_* allocations. Automatically generated (and
manually adjusted) from the following Coccinelle script:
// Direct reference to struct field.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(*VAR->ELEMENT), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// mr = kzalloc(sizeof(*mr) + m * sizeof(mr->map[0]), GFP_KERNEL);
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(VAR->ELEMENT[0]), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// Same pattern, but can't trivially locate the trailing element name,
// or variable name.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
expression SOMETHING, COUNT, ELEMENT;
@@
- alloc(HANDLE, sizeof(SOMETHING) + COUNT * sizeof(ELEMENT), GFP)
+ alloc(HANDLE, CHECKME_struct_size(&SOMETHING, ELEMENT, COUNT), GFP)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-05-08 16:08:53 -07:00
cc = devm_kzalloc ( dev , struct_size ( cc , clks , nclks ) , GFP_KERNEL ) ;
2017-11-21 14:41:04 +05:30
if ( ! cc )
return - ENOMEM ;
cc - > nclks = nclks ;
cxo = clk_get ( dev , " xo " ) ;
if ( IS_ERR ( cxo ) ) {
ret = PTR_ERR ( cxo ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get xo clock \n " ) ;
return ret ;
}
cxo_hz = clk_get_rate ( cxo ) ;
clk_put ( cxo ) ;
parent_name = of_clk_get_parent_name ( of_node , 0 ) ;
if ( ! parent_name ) {
dev_err ( dev , " missing parent clock \n " ) ;
return - ENODEV ;
}
init . name = name ;
init . parent_names = & parent_name ;
init . num_parents = 1 ;
init . ops = & clk_spmi_pmic_div_ops ;
for ( i = 0 , clkdiv = cc - > clks ; i < nclks ; i + + ) {
snprintf ( name , sizeof ( name ) , " div_clk%d " , i + 1 ) ;
spin_lock_init ( & clkdiv [ i ] . lock ) ;
clkdiv [ i ] . base = start + i * 0x100 ;
clkdiv [ i ] . regmap = regmap ;
clkdiv [ i ] . cxo_period_ns = NSEC_PER_SEC / cxo_hz ;
clkdiv [ i ] . hw . init = & init ;
ret = devm_clk_hw_register ( dev , & clkdiv [ i ] . hw ) ;
if ( ret )
return ret ;
}
return devm_of_clk_add_hw_provider ( dev , spmi_pmic_div_clk_hw_get , cc ) ;
}
static const struct of_device_id spmi_pmic_clkdiv_match_table [ ] = {
{ . compatible = " qcom,spmi-clkdiv " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , spmi_pmic_clkdiv_match_table ) ;
static struct platform_driver spmi_pmic_clkdiv_driver = {
. driver = {
. name = " qcom,spmi-pmic-clkdiv " ,
. of_match_table = spmi_pmic_clkdiv_match_table ,
} ,
. probe = spmi_pmic_clkdiv_probe ,
} ;
module_platform_driver ( spmi_pmic_clkdiv_driver ) ;
MODULE_DESCRIPTION ( " QCOM SPMI PMIC clkdiv driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;