2014-09-05 00:10:43 +04:00
/*
* Copyright ( c ) 2014 MundoReader S . L .
* Author : Heiko Stuebner < heiko @ sntech . de >
*
* based on clk / samsung / clk - cpu . c
* Copyright ( c ) 2014 Samsung Electronics Co . , Ltd .
* Author : Thomas Abraham < thomas . ab @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* A CPU clock is defined as a clock supplied to a CPU or a group of CPUs .
* The CPU clock is typically derived from a hierarchy of clock
* blocks which includes mux and divider blocks . There are a number of other
* auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI
* clock for CPU domain . The rates of these auxiliary clocks are related to the
* CPU clock rate and this relation is usually specified in the hardware manual
* of the SoC or supplied after the SoC characterization .
*
* The below implementation of the CPU clock allows the rate changes of the CPU
* clock and the corresponding rate changes of the auxillary clocks of the CPU
* domain . The platform clock driver provides a clock register configuration
* for each configurable rate which is then used to program the clock hardware
* registers to acheive a fast co - oridinated rate change for all the CPU domain
* clocks .
*
* On a rate change request for the CPU clock , the rate change is propagated
* upto the PLL supplying the clock to the CPU domain clock blocks . While the
* CPU domain PLL is reconfigured , the CPU domain clocks are driven using an
* alternate clock source . If required , the alternate clock source is divided
* down in order to keep the output clock rate within the previous OPP limits .
*/
# include <linux/of.h>
# include <linux/slab.h>
# include <linux/io.h>
2015-06-20 01:00:46 +03:00
# include <linux/clk.h>
2014-09-05 00:10:43 +04:00
# include <linux/clk-provider.h>
# include "clk.h"
/**
* struct rockchip_cpuclk : information about clock supplied to a CPU core .
* @ hw : handle between ccf and cpu clock .
* @ alt_parent : alternate parent clock to use when switching the speed
* of the primary parent clock .
* @ reg_base : base register for cpu - clock values .
* @ clk_nb : clock notifier registered for changes in clock speed of the
* primary parent clock .
* @ rate_count : number of rates in the rate_table
* @ rate_table : pll - rates and their associated dividers
* @ reg_data : cpu - specific register settings
* @ lock : clock lock
*/
struct rockchip_cpuclk {
struct clk_hw hw ;
struct clk_mux cpu_mux ;
const struct clk_ops * cpu_mux_ops ;
struct clk * alt_parent ;
void __iomem * reg_base ;
struct notifier_block clk_nb ;
unsigned int rate_count ;
struct rockchip_cpuclk_rate_table * rate_table ;
const struct rockchip_cpuclk_reg_data * reg_data ;
spinlock_t * lock ;
} ;
# define to_rockchip_cpuclk_hw(hw) container_of(hw, struct rockchip_cpuclk, hw)
# define to_rockchip_cpuclk_nb(nb) \
container_of ( nb , struct rockchip_cpuclk , clk_nb )
static const struct rockchip_cpuclk_rate_table * rockchip_get_cpuclk_settings (
struct rockchip_cpuclk * cpuclk , unsigned long rate )
{
const struct rockchip_cpuclk_rate_table * rate_table =
cpuclk - > rate_table ;
int i ;
for ( i = 0 ; i < cpuclk - > rate_count ; i + + ) {
if ( rate = = rate_table [ i ] . prate )
return & rate_table [ i ] ;
}
return NULL ;
}
static unsigned long rockchip_cpuclk_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct rockchip_cpuclk * cpuclk = to_rockchip_cpuclk_hw ( hw ) ;
const struct rockchip_cpuclk_reg_data * reg_data = cpuclk - > reg_data ;
u32 clksel0 = readl_relaxed ( cpuclk - > reg_base + reg_data - > core_reg ) ;
clksel0 > > = reg_data - > div_core_shift ;
clksel0 & = reg_data - > div_core_mask ;
return parent_rate / ( clksel0 + 1 ) ;
}
static const struct clk_ops rockchip_cpuclk_ops = {
. recalc_rate = rockchip_cpuclk_recalc_rate ,
} ;
static void rockchip_cpuclk_set_dividers ( struct rockchip_cpuclk * cpuclk ,
const struct rockchip_cpuclk_rate_table * rate )
{
int i ;
/* alternate parent is active now. set the dividers */
for ( i = 0 ; i < ARRAY_SIZE ( rate - > divs ) ; i + + ) {
const struct rockchip_cpuclk_clksel * clksel = & rate - > divs [ i ] ;
if ( ! clksel - > reg )
continue ;
pr_debug ( " %s: setting reg 0x%x to 0x%x \n " ,
__func__ , clksel - > reg , clksel - > val ) ;
2016-02-15 06:33:50 +03:00
writel ( clksel - > val , cpuclk - > reg_base + clksel - > reg ) ;
2014-09-05 00:10:43 +04:00
}
}
static int rockchip_cpuclk_pre_rate_change ( struct rockchip_cpuclk * cpuclk ,
struct clk_notifier_data * ndata )
{
const struct rockchip_cpuclk_reg_data * reg_data = cpuclk - > reg_data ;
2016-11-08 12:10:14 +03:00
const struct rockchip_cpuclk_rate_table * rate ;
2014-09-05 00:10:43 +04:00
unsigned long alt_prate , alt_div ;
2015-01-16 19:52:44 +03:00
unsigned long flags ;
2014-09-05 00:10:43 +04:00
2016-11-08 12:10:14 +03:00
/* check validity of the new rate */
rate = rockchip_get_cpuclk_settings ( cpuclk , ndata - > new_rate ) ;
if ( ! rate ) {
pr_err ( " %s: Invalid rate : %lu for cpuclk \n " ,
__func__ , ndata - > new_rate ) ;
return - EINVAL ;
}
2014-09-05 00:10:43 +04:00
alt_prate = clk_get_rate ( cpuclk - > alt_parent ) ;
2015-01-16 19:52:44 +03:00
spin_lock_irqsave ( cpuclk - > lock , flags ) ;
2014-09-05 00:10:43 +04:00
/*
* If the old parent clock speed is less than the clock speed
* of the alternate parent , then it should be ensured that at no point
* the armclk speed is more than the old_rate until the dividers are
* set .
*/
if ( alt_prate > ndata - > old_rate ) {
/* calculate dividers */
alt_div = DIV_ROUND_UP ( alt_prate , ndata - > old_rate ) - 1 ;
if ( alt_div > reg_data - > div_core_mask ) {
pr_warn ( " %s: limiting alt-divider %lu to %d \n " ,
__func__ , alt_div , reg_data - > div_core_mask ) ;
alt_div = reg_data - > div_core_mask ;
}
/*
* Change parents and add dividers in a single transaction .
*
* NOTE : we do this in a single transaction so we ' re never
* dividing the primary parent by the extra dividers that were
* needed for the alt .
*/
pr_debug ( " %s: setting div %lu as alt-rate %lu > old-rate %lu \n " ,
__func__ , alt_div , alt_prate , ndata - > old_rate ) ;
writel ( HIWORD_UPDATE ( alt_div , reg_data - > div_core_mask ,
reg_data - > div_core_shift ) |
2016-03-09 05:37:03 +03:00
HIWORD_UPDATE ( reg_data - > mux_core_alt ,
reg_data - > mux_core_mask ,
reg_data - > mux_core_shift ) ,
2014-09-05 00:10:43 +04:00
cpuclk - > reg_base + reg_data - > core_reg ) ;
} else {
/* select alternate parent */
2016-03-09 05:37:03 +03:00
writel ( HIWORD_UPDATE ( reg_data - > mux_core_alt ,
reg_data - > mux_core_mask ,
reg_data - > mux_core_shift ) ,
cpuclk - > reg_base + reg_data - > core_reg ) ;
2014-09-05 00:10:43 +04:00
}
2015-01-16 19:52:44 +03:00
spin_unlock_irqrestore ( cpuclk - > lock , flags ) ;
2014-09-05 00:10:43 +04:00
return 0 ;
}
static int rockchip_cpuclk_post_rate_change ( struct rockchip_cpuclk * cpuclk ,
struct clk_notifier_data * ndata )
{
const struct rockchip_cpuclk_reg_data * reg_data = cpuclk - > reg_data ;
const struct rockchip_cpuclk_rate_table * rate ;
2015-01-16 19:52:44 +03:00
unsigned long flags ;
2014-09-05 00:10:43 +04:00
rate = rockchip_get_cpuclk_settings ( cpuclk , ndata - > new_rate ) ;
if ( ! rate ) {
pr_err ( " %s: Invalid rate : %lu for cpuclk \n " ,
__func__ , ndata - > new_rate ) ;
return - EINVAL ;
}
2015-01-16 19:52:44 +03:00
spin_lock_irqsave ( cpuclk - > lock , flags ) ;
2014-09-05 00:10:43 +04:00
if ( ndata - > old_rate < ndata - > new_rate )
rockchip_cpuclk_set_dividers ( cpuclk , rate ) ;
/*
* post - rate change event , re - mux to primary parent and remove dividers .
*
* NOTE : we do this in a single transaction so we ' re never dividing the
* primary parent by the extra dividers that were needed for the alt .
*/
writel ( HIWORD_UPDATE ( 0 , reg_data - > div_core_mask ,
reg_data - > div_core_shift ) |
2016-03-09 05:37:03 +03:00
HIWORD_UPDATE ( reg_data - > mux_core_main ,
reg_data - > mux_core_mask ,
reg_data - > mux_core_shift ) ,
2014-09-05 00:10:43 +04:00
cpuclk - > reg_base + reg_data - > core_reg ) ;
if ( ndata - > old_rate > ndata - > new_rate )
rockchip_cpuclk_set_dividers ( cpuclk , rate ) ;
2015-01-16 19:52:44 +03:00
spin_unlock_irqrestore ( cpuclk - > lock , flags ) ;
2014-09-05 00:10:43 +04:00
return 0 ;
}
/*
* This clock notifier is called when the frequency of the parent clock
* of cpuclk is to be changed . This notifier handles the setting up all
* the divider clocks , remux to temporary parent and handling the safe
* frequency levels when using temporary parent .
*/
static int rockchip_cpuclk_notifier_cb ( struct notifier_block * nb ,
unsigned long event , void * data )
{
struct clk_notifier_data * ndata = data ;
struct rockchip_cpuclk * cpuclk = to_rockchip_cpuclk_nb ( nb ) ;
int ret = 0 ;
pr_debug ( " %s: event %lu, old_rate %lu, new_rate: %lu \n " ,
__func__ , event , ndata - > old_rate , ndata - > new_rate ) ;
if ( event = = PRE_RATE_CHANGE )
ret = rockchip_cpuclk_pre_rate_change ( cpuclk , ndata ) ;
else if ( event = = POST_RATE_CHANGE )
ret = rockchip_cpuclk_post_rate_change ( cpuclk , ndata ) ;
return notifier_from_errno ( ret ) ;
}
struct clk * rockchip_clk_register_cpuclk ( const char * name ,
2015-05-28 11:45:51 +03:00
const char * const * parent_names , u8 num_parents ,
2014-09-05 00:10:43 +04:00
const struct rockchip_cpuclk_reg_data * reg_data ,
const struct rockchip_cpuclk_rate_table * rates ,
int nrates , void __iomem * reg_base , spinlock_t * lock )
{
struct rockchip_cpuclk * cpuclk ;
struct clk_init_data init ;
struct clk * clk , * cclk ;
int ret ;
2015-12-09 12:04:10 +03:00
if ( num_parents < 2 ) {
pr_err ( " %s: needs at least two parent clocks \n " , __func__ ) ;
2014-09-05 00:10:43 +04:00
return ERR_PTR ( - EINVAL ) ;
}
cpuclk = kzalloc ( sizeof ( * cpuclk ) , GFP_KERNEL ) ;
if ( ! cpuclk )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
2016-03-09 05:37:03 +03:00
init . parent_names = & parent_names [ reg_data - > mux_core_main ] ;
2014-09-05 00:10:43 +04:00
init . num_parents = 1 ;
init . ops = & rockchip_cpuclk_ops ;
/* only allow rate changes when we have a rate table */
init . flags = ( nrates > 0 ) ? CLK_SET_RATE_PARENT : 0 ;
/* disallow automatic parent changes by ccf */
init . flags | = CLK_SET_RATE_NO_REPARENT ;
init . flags | = CLK_GET_RATE_NOCACHE ;
cpuclk - > reg_base = reg_base ;
cpuclk - > lock = lock ;
cpuclk - > reg_data = reg_data ;
cpuclk - > clk_nb . notifier_call = rockchip_cpuclk_notifier_cb ;
cpuclk - > hw . init = & init ;
2016-03-09 05:37:03 +03:00
cpuclk - > alt_parent = __clk_lookup ( parent_names [ reg_data - > mux_core_alt ] ) ;
2014-09-05 00:10:43 +04:00
if ( ! cpuclk - > alt_parent ) {
2016-03-09 05:37:03 +03:00
pr_err ( " %s: could not lookup alternate parent: (%d) \n " ,
__func__ , reg_data - > mux_core_alt ) ;
2014-09-05 00:10:43 +04:00
ret = - EINVAL ;
goto free_cpuclk ;
}
ret = clk_prepare_enable ( cpuclk - > alt_parent ) ;
if ( ret ) {
pr_err ( " %s: could not enable alternate parent \n " ,
__func__ ) ;
goto free_cpuclk ;
}
2016-03-09 05:37:03 +03:00
clk = __clk_lookup ( parent_names [ reg_data - > mux_core_main ] ) ;
2014-09-05 00:10:43 +04:00
if ( ! clk ) {
2016-03-09 05:37:03 +03:00
pr_err ( " %s: could not lookup parent clock: (%d) %s \n " ,
__func__ , reg_data - > mux_core_main ,
parent_names [ reg_data - > mux_core_main ] ) ;
2014-09-05 00:10:43 +04:00
ret = - EINVAL ;
2016-02-15 06:33:15 +03:00
goto free_alt_parent ;
2014-09-05 00:10:43 +04:00
}
ret = clk_notifier_register ( clk , & cpuclk - > clk_nb ) ;
if ( ret ) {
pr_err ( " %s: failed to register clock notifier for %s \n " ,
__func__ , name ) ;
2016-02-15 06:33:15 +03:00
goto free_alt_parent ;
2014-09-05 00:10:43 +04:00
}
if ( nrates > 0 ) {
cpuclk - > rate_count = nrates ;
cpuclk - > rate_table = kmemdup ( rates ,
sizeof ( * rates ) * nrates ,
GFP_KERNEL ) ;
if ( ! cpuclk - > rate_table ) {
ret = - ENOMEM ;
goto unregister_notifier ;
}
}
cclk = clk_register ( NULL , & cpuclk - > hw ) ;
2016-05-26 16:49:08 +03:00
if ( IS_ERR ( cclk ) ) {
2014-09-05 00:10:43 +04:00
pr_err ( " %s: could not register cpuclk %s \n " , __func__ , name ) ;
2016-05-26 16:49:08 +03:00
ret = PTR_ERR ( cclk ) ;
2014-09-05 00:10:43 +04:00
goto free_rate_table ;
}
return cclk ;
free_rate_table :
kfree ( cpuclk - > rate_table ) ;
unregister_notifier :
clk_notifier_unregister ( clk , & cpuclk - > clk_nb ) ;
2016-02-15 06:33:15 +03:00
free_alt_parent :
clk_disable_unprepare ( cpuclk - > alt_parent ) ;
2014-09-05 00:10:43 +04:00
free_cpuclk :
kfree ( cpuclk ) ;
return ERR_PTR ( ret ) ;
}