2014-07-03 03:58:39 +04:00
/*
* Copyright ( c ) 2014 MundoReader S . L .
* Author : Heiko Stuebner < heiko @ sntech . de >
*
* based on
*
* samsung / clk . c
* Copyright ( c ) 2013 Samsung Electronics Co . , Ltd .
* Copyright ( c ) 2013 Linaro 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 as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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/slab.h>
# include <linux/clk.h>
# include <linux/clk-provider.h>
2014-07-03 03:59:10 +04:00
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
2014-08-20 04:45:38 +04:00
# include <linux/reboot.h>
2014-07-03 03:58:39 +04:00
# include "clk.h"
/**
* Register a clock branch .
* Most clock branches have a form like
*
* src1 - - | - - \
* | M | - - [ GATE ] - [ DIV ] -
* src2 - - | - - /
*
* sometimes without one of those components .
*/
2014-08-27 02:54:56 +04:00
static struct clk * rockchip_clk_register_branch ( const char * name ,
2015-05-28 11:45:51 +03:00
const char * const * parent_names , u8 num_parents , void __iomem * base ,
2014-07-03 03:58:39 +04:00
int muxdiv_offset , u8 mux_shift , u8 mux_width , u8 mux_flags ,
u8 div_shift , u8 div_width , u8 div_flags ,
struct clk_div_table * div_table , int gate_offset ,
u8 gate_shift , u8 gate_flags , unsigned long flags ,
spinlock_t * lock )
{
struct clk * clk ;
struct clk_mux * mux = NULL ;
struct clk_gate * gate = NULL ;
struct clk_divider * div = NULL ;
const struct clk_ops * mux_ops = NULL , * div_ops = NULL ,
* gate_ops = NULL ;
if ( num_parents > 1 ) {
mux = kzalloc ( sizeof ( * mux ) , GFP_KERNEL ) ;
if ( ! mux )
return ERR_PTR ( - ENOMEM ) ;
mux - > reg = base + muxdiv_offset ;
mux - > shift = mux_shift ;
mux - > mask = BIT ( mux_width ) - 1 ;
mux - > flags = mux_flags ;
mux - > lock = lock ;
mux_ops = ( mux_flags & CLK_MUX_READ_ONLY ) ? & clk_mux_ro_ops
: & clk_mux_ops ;
}
if ( gate_offset > = 0 ) {
gate = kzalloc ( sizeof ( * gate ) , GFP_KERNEL ) ;
if ( ! gate )
return ERR_PTR ( - ENOMEM ) ;
gate - > flags = gate_flags ;
gate - > reg = base + gate_offset ;
gate - > bit_idx = gate_shift ;
gate - > lock = lock ;
gate_ops = & clk_gate_ops ;
}
if ( div_width > 0 ) {
div = kzalloc ( sizeof ( * div ) , GFP_KERNEL ) ;
if ( ! div )
return ERR_PTR ( - ENOMEM ) ;
div - > flags = div_flags ;
div - > reg = base + muxdiv_offset ;
div - > shift = div_shift ;
div - > width = div_width ;
div - > lock = lock ;
div - > table = div_table ;
2014-11-14 18:32:09 +03:00
div_ops = & clk_divider_ops ;
2014-07-03 03:58:39 +04:00
}
clk = clk_register_composite ( NULL , name , parent_names , num_parents ,
mux ? & mux - > hw : NULL , mux_ops ,
div ? & div - > hw : NULL , div_ops ,
gate ? & gate - > hw : NULL , gate_ops ,
flags ) ;
return clk ;
}
2014-08-27 02:54:21 +04:00
static struct clk * rockchip_clk_register_frac_branch ( const char * name ,
2015-05-28 11:45:51 +03:00
const char * const * parent_names , u8 num_parents ,
void __iomem * base , int muxdiv_offset , u8 div_flags ,
2014-08-27 02:54:21 +04:00
int gate_offset , u8 gate_shift , u8 gate_flags ,
unsigned long flags , spinlock_t * lock )
{
struct clk * clk ;
struct clk_gate * gate = NULL ;
struct clk_fractional_divider * div = NULL ;
const struct clk_ops * div_ops = NULL , * gate_ops = NULL ;
if ( gate_offset > = 0 ) {
gate = kzalloc ( sizeof ( * gate ) , GFP_KERNEL ) ;
if ( ! gate )
return ERR_PTR ( - ENOMEM ) ;
gate - > flags = gate_flags ;
gate - > reg = base + gate_offset ;
gate - > bit_idx = gate_shift ;
gate - > lock = lock ;
gate_ops = & clk_gate_ops ;
}
if ( muxdiv_offset < 0 )
return ERR_PTR ( - EINVAL ) ;
div = kzalloc ( sizeof ( * div ) , GFP_KERNEL ) ;
if ( ! div )
return ERR_PTR ( - ENOMEM ) ;
div - > flags = div_flags ;
div - > reg = base + muxdiv_offset ;
div - > mshift = 16 ;
div - > mmask = 0xffff0000 ;
div - > nshift = 0 ;
div - > nmask = 0xffff ;
div - > lock = lock ;
div_ops = & clk_fractional_divider_ops ;
clk = clk_register_composite ( NULL , name , parent_names , num_parents ,
NULL , NULL ,
& div - > hw , div_ops ,
gate ? & gate - > hw : NULL , gate_ops ,
flags ) ;
return clk ;
}
2014-07-03 03:58:39 +04:00
static DEFINE_SPINLOCK ( clk_lock ) ;
static struct clk * * clk_table ;
static void __iomem * reg_base ;
static struct clk_onecell_data clk_data ;
2014-07-03 03:59:10 +04:00
static struct device_node * cru_node ;
static struct regmap * grf ;
2014-07-03 03:58:39 +04:00
void __init rockchip_clk_init ( struct device_node * np , void __iomem * base ,
unsigned long nr_clks )
{
reg_base = base ;
2014-07-03 03:59:10 +04:00
cru_node = np ;
grf = ERR_PTR ( - EPROBE_DEFER ) ;
2014-07-03 03:58:39 +04:00
clk_table = kcalloc ( nr_clks , sizeof ( struct clk * ) , GFP_KERNEL ) ;
if ( ! clk_table )
pr_err ( " %s: could not allocate clock lookup table \n " , __func__ ) ;
clk_data . clks = clk_table ;
clk_data . clk_num = nr_clks ;
of_clk_add_provider ( np , of_clk_src_onecell_get , & clk_data ) ;
}
2014-07-03 03:59:10 +04:00
struct regmap * rockchip_clk_get_grf ( void )
{
if ( IS_ERR ( grf ) )
grf = syscon_regmap_lookup_by_phandle ( cru_node , " rockchip,grf " ) ;
return grf ;
}
2014-07-03 03:58:39 +04:00
void rockchip_clk_add_lookup ( struct clk * clk , unsigned int id )
{
if ( clk_table & & id )
clk_table [ id ] = clk ;
}
2014-07-03 03:59:10 +04:00
void __init rockchip_clk_register_plls ( struct rockchip_pll_clock * list ,
unsigned int nr_pll , int grf_lock_offset )
{
struct clk * clk ;
int idx ;
for ( idx = 0 ; idx < nr_pll ; idx + + , list + + ) {
clk = rockchip_clk_register_pll ( list - > type , list - > name ,
list - > parent_names , list - > num_parents ,
reg_base , list - > con_offset , grf_lock_offset ,
list - > lock_shift , list - > mode_offset ,
2014-11-20 22:38:50 +03:00
list - > mode_shift , list - > rate_table ,
list - > pll_flags , & clk_lock ) ;
2014-07-03 03:59:10 +04:00
if ( IS_ERR ( clk ) ) {
pr_err ( " %s: failed to register clock %s \n " , __func__ ,
list - > name ) ;
continue ;
}
rockchip_clk_add_lookup ( clk , list - > id ) ;
}
}
2014-07-03 03:58:39 +04:00
void __init rockchip_clk_register_branches (
struct rockchip_clk_branch * list ,
unsigned int nr_clk )
{
struct clk * clk = NULL ;
unsigned int idx ;
unsigned long flags ;
for ( idx = 0 ; idx < nr_clk ; idx + + , list + + ) {
flags = list - > flags ;
/* catch simple muxes */
switch ( list - > branch_type ) {
case branch_mux :
clk = clk_register_mux ( NULL , list - > name ,
list - > parent_names , list - > num_parents ,
flags , reg_base + list - > muxdiv_offset ,
list - > mux_shift , list - > mux_width ,
list - > mux_flags , & clk_lock ) ;
break ;
case branch_divider :
if ( list - > div_table )
clk = clk_register_divider_table ( NULL ,
list - > name , list - > parent_names [ 0 ] ,
flags , reg_base + list - > muxdiv_offset ,
list - > div_shift , list - > div_width ,
list - > div_flags , list - > div_table ,
& clk_lock ) ;
else
clk = clk_register_divider ( NULL , list - > name ,
list - > parent_names [ 0 ] , flags ,
reg_base + list - > muxdiv_offset ,
list - > div_shift , list - > div_width ,
list - > div_flags , & clk_lock ) ;
break ;
case branch_fraction_divider :
2014-08-27 02:54:21 +04:00
clk = rockchip_clk_register_frac_branch ( list - > name ,
list - > parent_names , list - > num_parents ,
reg_base , list - > muxdiv_offset , list - > div_flags ,
list - > gate_offset , list - > gate_shift ,
list - > gate_flags , flags , & clk_lock ) ;
2014-07-03 03:58:39 +04:00
break ;
case branch_gate :
flags | = CLK_SET_RATE_PARENT ;
clk = clk_register_gate ( NULL , list - > name ,
list - > parent_names [ 0 ] , flags ,
reg_base + list - > gate_offset ,
list - > gate_shift , list - > gate_flags , & clk_lock ) ;
break ;
case branch_composite :
clk = rockchip_clk_register_branch ( list - > name ,
list - > parent_names , list - > num_parents ,
reg_base , list - > muxdiv_offset , list - > mux_shift ,
list - > mux_width , list - > mux_flags ,
list - > div_shift , list - > div_width ,
list - > div_flags , list - > div_table ,
list - > gate_offset , list - > gate_shift ,
list - > gate_flags , flags , & clk_lock ) ;
break ;
2014-11-27 04:30:27 +03:00
case branch_mmc :
clk = rockchip_clk_register_mmc (
list - > name ,
list - > parent_names , list - > num_parents ,
reg_base + list - > muxdiv_offset ,
list - > div_shift
) ;
break ;
2014-07-03 03:58:39 +04:00
}
/* none of the cases above matched */
if ( ! clk ) {
pr_err ( " %s: unknown clock type %d \n " ,
__func__ , list - > branch_type ) ;
continue ;
}
if ( IS_ERR ( clk ) ) {
pr_err ( " %s: failed to register clock %s: %ld \n " ,
__func__ , list - > name , PTR_ERR ( clk ) ) ;
continue ;
}
rockchip_clk_add_lookup ( clk , list - > id ) ;
}
}
2014-08-15 01:00:26 +04:00
2014-09-05 00:10:43 +04:00
void __init rockchip_clk_register_armclk ( unsigned int lookup_id ,
2015-05-28 11:45:51 +03:00
const char * name , const char * const * parent_names ,
2014-09-05 00:10:43 +04:00
u8 num_parents ,
const struct rockchip_cpuclk_reg_data * reg_data ,
const struct rockchip_cpuclk_rate_table * rates ,
int nrates )
{
struct clk * clk ;
clk = rockchip_clk_register_cpuclk ( name , parent_names , num_parents ,
reg_data , rates , nrates , reg_base ,
& clk_lock ) ;
if ( IS_ERR ( clk ) ) {
pr_err ( " %s: failed to register clock %s: %ld \n " ,
__func__ , name , PTR_ERR ( clk ) ) ;
return ;
}
rockchip_clk_add_lookup ( clk , lookup_id ) ;
}
2015-02-18 12:59:45 +03:00
void __init rockchip_clk_protect_critical ( const char * const clocks [ ] ,
int nclocks )
2014-08-15 01:00:26 +04:00
{
int i ;
/* Protect the clocks that needs to stay on */
for ( i = 0 ; i < nclocks ; i + + ) {
struct clk * clk = __clk_lookup ( clocks [ i ] ) ;
if ( clk )
clk_prepare_enable ( clk ) ;
}
}
2014-08-20 04:45:38 +04:00
static unsigned int reg_restart ;
static int rockchip_restart_notify ( struct notifier_block * this ,
unsigned long mode , void * cmd )
{
writel ( 0xfdb9 , reg_base + reg_restart ) ;
return NOTIFY_DONE ;
}
static struct notifier_block rockchip_restart_handler = {
. notifier_call = rockchip_restart_notify ,
. priority = 128 ,
} ;
void __init rockchip_register_restart_notifier ( unsigned int reg )
{
int ret ;
reg_restart = reg ;
ret = register_restart_handler ( & rockchip_restart_handler ) ;
if ( ret )
pr_err ( " %s: cannot register restart handler, %d \n " ,
__func__ , ret ) ;
}