2022-06-07 17:11:11 +03:00
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2014 Broadcom Corporation
2015-05-05 21:13:19 +03:00
# include <linux/kernel.h>
# include <linux/err.h>
# include <linux/clk-provider.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/clkdev.h>
# include <linux/of_address.h>
# include <linux/delay.h>
# include "clk-iproc.h"
struct iproc_asiu ;
struct iproc_asiu_clk {
struct clk_hw hw ;
const char * name ;
struct iproc_asiu * asiu ;
unsigned long rate ;
struct iproc_asiu_div div ;
struct iproc_asiu_gate gate ;
} ;
struct iproc_asiu {
void __iomem * div_base ;
void __iomem * gate_base ;
2016-06-02 02:15:04 +03:00
struct clk_hw_onecell_data * clk_data ;
2015-05-05 21:13:19 +03:00
struct iproc_asiu_clk * clks ;
} ;
# define to_asiu_clk(hw) container_of(hw, struct iproc_asiu_clk, hw)
static int iproc_asiu_clk_enable ( struct clk_hw * hw )
{
struct iproc_asiu_clk * clk = to_asiu_clk ( hw ) ;
struct iproc_asiu * asiu = clk - > asiu ;
u32 val ;
/* some clocks at the ASIU level are always enabled */
if ( clk - > gate . offset = = IPROC_CLK_INVALID_OFFSET )
return 0 ;
val = readl ( asiu - > gate_base + clk - > gate . offset ) ;
val | = ( 1 < < clk - > gate . en_shift ) ;
writel ( val , asiu - > gate_base + clk - > gate . offset ) ;
return 0 ;
}
static void iproc_asiu_clk_disable ( struct clk_hw * hw )
{
struct iproc_asiu_clk * clk = to_asiu_clk ( hw ) ;
struct iproc_asiu * asiu = clk - > asiu ;
u32 val ;
/* some clocks at the ASIU level are always enabled */
if ( clk - > gate . offset = = IPROC_CLK_INVALID_OFFSET )
return ;
val = readl ( asiu - > gate_base + clk - > gate . offset ) ;
val & = ~ ( 1 < < clk - > gate . en_shift ) ;
writel ( val , asiu - > gate_base + clk - > gate . offset ) ;
}
static unsigned long iproc_asiu_clk_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct iproc_asiu_clk * clk = to_asiu_clk ( hw ) ;
struct iproc_asiu * asiu = clk - > asiu ;
u32 val ;
unsigned int div_h , div_l ;
if ( parent_rate = = 0 ) {
clk - > rate = 0 ;
return 0 ;
}
/* if clock divisor is not enabled, simply return parent rate */
val = readl ( asiu - > div_base + clk - > div . offset ) ;
if ( ( val & ( 1 < < clk - > div . en_shift ) ) = = 0 ) {
clk - > rate = parent_rate ;
return parent_rate ;
}
/* clock rate = parent rate / (high_div + 1) + (low_div + 1) */
div_h = ( val > > clk - > div . high_shift ) & bit_mask ( clk - > div . high_width ) ;
div_h + + ;
div_l = ( val > > clk - > div . low_shift ) & bit_mask ( clk - > div . low_width ) ;
div_l + + ;
clk - > rate = parent_rate / ( div_h + div_l ) ;
pr_debug ( " %s: rate: %lu. parent rate: %lu div_h: %u div_l: %u \n " ,
__func__ , clk - > rate , parent_rate , div_h , div_l ) ;
return clk - > rate ;
}
static long iproc_asiu_clk_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
unsigned int div ;
if ( rate = = 0 | | * parent_rate = = 0 )
return - EINVAL ;
if ( rate = = * parent_rate )
return * parent_rate ;
2020-06-13 01:52:12 +03:00
div = DIV_ROUND_CLOSEST ( * parent_rate , rate ) ;
2015-05-05 21:13:19 +03:00
if ( div < 2 )
return * parent_rate ;
return * parent_rate / div ;
}
static int iproc_asiu_clk_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct iproc_asiu_clk * clk = to_asiu_clk ( hw ) ;
struct iproc_asiu * asiu = clk - > asiu ;
unsigned int div , div_h , div_l ;
u32 val ;
if ( rate = = 0 | | parent_rate = = 0 )
return - EINVAL ;
/* simply disable the divisor if one wants the same rate as parent */
if ( rate = = parent_rate ) {
val = readl ( asiu - > div_base + clk - > div . offset ) ;
val & = ~ ( 1 < < clk - > div . en_shift ) ;
writel ( val , asiu - > div_base + clk - > div . offset ) ;
return 0 ;
}
2020-06-13 01:52:12 +03:00
div = DIV_ROUND_CLOSEST ( parent_rate , rate ) ;
2015-05-05 21:13:19 +03:00
if ( div < 2 )
return - EINVAL ;
div_h = div_l = div > > 1 ;
div_h - - ;
div_l - - ;
val = readl ( asiu - > div_base + clk - > div . offset ) ;
val | = 1 < < clk - > div . en_shift ;
if ( div_h ) {
val & = ~ ( bit_mask ( clk - > div . high_width )
< < clk - > div . high_shift ) ;
val | = div_h < < clk - > div . high_shift ;
} else {
val & = ~ ( bit_mask ( clk - > div . high_width )
< < clk - > div . high_shift ) ;
}
if ( div_l ) {
val & = ~ ( bit_mask ( clk - > div . low_width ) < < clk - > div . low_shift ) ;
val | = div_l < < clk - > div . low_shift ;
} else {
val & = ~ ( bit_mask ( clk - > div . low_width ) < < clk - > div . low_shift ) ;
}
writel ( val , asiu - > div_base + clk - > div . offset ) ;
return 0 ;
}
static const struct clk_ops iproc_asiu_ops = {
. enable = iproc_asiu_clk_enable ,
. disable = iproc_asiu_clk_disable ,
. recalc_rate = iproc_asiu_clk_recalc_rate ,
. round_rate = iproc_asiu_clk_round_rate ,
. set_rate = iproc_asiu_clk_set_rate ,
} ;
void __init iproc_asiu_setup ( struct device_node * node ,
const struct iproc_asiu_div * div ,
const struct iproc_asiu_gate * gate ,
unsigned int num_clks )
{
int i , ret ;
struct iproc_asiu * asiu ;
if ( WARN_ON ( ! gate | | ! div ) )
return ;
asiu = kzalloc ( sizeof ( * asiu ) , GFP_KERNEL ) ;
if ( WARN_ON ( ! asiu ) )
return ;
treewide: Use struct_size() for kmalloc()-family
One of the more common cases of allocation size calculations is finding
the size of a structure that has a zero-sized array at the end, along
with memory for some number of elements for that array. For example:
struct foo {
int stuff;
void *entry[];
};
instance = kmalloc(sizeof(struct foo) + sizeof(void *) * count, GFP_KERNEL);
Instead of leaving these open-coded and prone to type mistakes, we can
now use the new struct_size() helper:
instance = kmalloc(struct_size(instance, entry, count), GFP_KERNEL);
This patch makes the changes for kmalloc()-family (and kvmalloc()-family)
uses. It was done via automatic conversion with manual review for the
"CHECKME" non-standard cases noted below, using the following Coccinelle
script:
// pkey_cache = kmalloc(sizeof *pkey_cache + tprops->pkey_tbl_len *
// sizeof *pkey_cache->table, GFP_KERNEL);
@@
identifier alloc =~ "kmalloc|kzalloc|kvmalloc|kvzalloc";
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(sizeof(*VAR) + COUNT * sizeof(*VAR->ELEMENT), GFP)
+ alloc(struct_size(VAR, ELEMENT, COUNT), GFP)
// mr = kzalloc(sizeof(*mr) + m * sizeof(mr->map[0]), GFP_KERNEL);
@@
identifier alloc =~ "kmalloc|kzalloc|kvmalloc|kvzalloc";
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(sizeof(*VAR) + COUNT * sizeof(VAR->ELEMENT[0]), GFP)
+ alloc(struct_size(VAR, ELEMENT, COUNT), GFP)
// Same pattern, but can't trivially locate the trailing element name,
// or variable name.
@@
identifier alloc =~ "kmalloc|kzalloc|kvmalloc|kvzalloc";
expression GFP;
expression SOMETHING, COUNT, ELEMENT;
@@
- alloc(sizeof(SOMETHING) + COUNT * sizeof(ELEMENT), GFP)
+ alloc(CHECKME_struct_size(&SOMETHING, ELEMENT, COUNT), GFP)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-05-08 23:45:50 +03:00
asiu - > clk_data = kzalloc ( struct_size ( asiu - > clk_data , hws , num_clks ) ,
GFP_KERNEL ) ;
2016-06-02 02:15:04 +03:00
if ( WARN_ON ( ! asiu - > clk_data ) )
2015-05-05 21:13:19 +03:00
goto err_clks ;
2016-06-02 02:15:04 +03:00
asiu - > clk_data - > num = num_clks ;
2015-05-05 21:13:19 +03:00
asiu - > clks = kcalloc ( num_clks , sizeof ( * asiu - > clks ) , GFP_KERNEL ) ;
if ( WARN_ON ( ! asiu - > clks ) )
goto err_asiu_clks ;
asiu - > div_base = of_iomap ( node , 0 ) ;
if ( WARN_ON ( ! asiu - > div_base ) )
goto err_iomap_div ;
asiu - > gate_base = of_iomap ( node , 1 ) ;
if ( WARN_ON ( ! asiu - > gate_base ) )
goto err_iomap_gate ;
for ( i = 0 ; i < num_clks ; i + + ) {
struct clk_init_data init ;
const char * parent_name ;
struct iproc_asiu_clk * asiu_clk ;
const char * clk_name ;
ret = of_property_read_string_index ( node , " clock-output-names " ,
i , & clk_name ) ;
if ( WARN_ON ( ret ) )
goto err_clk_register ;
asiu_clk = & asiu - > clks [ i ] ;
asiu_clk - > name = clk_name ;
asiu_clk - > asiu = asiu ;
asiu_clk - > div = div [ i ] ;
asiu_clk - > gate = gate [ i ] ;
init . name = clk_name ;
init . ops = & iproc_asiu_ops ;
init . flags = 0 ;
parent_name = of_clk_get_parent_name ( node , 0 ) ;
init . parent_names = ( parent_name ? & parent_name : NULL ) ;
init . num_parents = ( parent_name ? 1 : 0 ) ;
asiu_clk - > hw . init = & init ;
2016-06-02 02:15:04 +03:00
ret = clk_hw_register ( NULL , & asiu_clk - > hw ) ;
if ( WARN_ON ( ret ) )
2015-05-05 21:13:19 +03:00
goto err_clk_register ;
2016-06-02 02:15:04 +03:00
asiu - > clk_data - > hws [ i ] = & asiu_clk - > hw ;
2015-05-05 21:13:19 +03:00
}
2016-06-02 02:15:04 +03:00
ret = of_clk_add_hw_provider ( node , of_clk_hw_onecell_get ,
asiu - > clk_data ) ;
2015-05-05 21:13:19 +03:00
if ( WARN_ON ( ret ) )
goto err_clk_register ;
return ;
err_clk_register :
2016-06-02 02:15:04 +03:00
while ( - - i > = 0 )
clk_hw_unregister ( asiu - > clk_data - > hws [ i ] ) ;
2015-05-05 21:13:19 +03:00
iounmap ( asiu - > gate_base ) ;
err_iomap_gate :
iounmap ( asiu - > div_base ) ;
err_iomap_div :
kfree ( asiu - > clks ) ;
err_asiu_clks :
2016-06-02 02:15:04 +03:00
kfree ( asiu - > clk_data ) ;
2015-05-05 21:13:19 +03:00
err_clks :
kfree ( asiu ) ;
}