2013-02-25 11:44:26 -03:00
/*
* Copyright ( C ) 2013 Emilio López < emilio @ elopez . com . ar >
*
* 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 .
*
* Adjustable factor - based clock implementation
*/
# include <linux/clk-provider.h>
2014-07-04 22:24:52 +02:00
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/io.h>
2013-02-25 11:44:26 -03:00
# include <linux/module.h>
2014-07-04 22:24:52 +02:00
# include <linux/of_address.h>
2013-02-25 11:44:26 -03:00
# include <linux/slab.h>
# include <linux/string.h>
# include "clk-factors.h"
/*
2014-07-04 22:24:52 +02:00
* DOC : basic adjustable factor - based clock
2013-02-25 11:44:26 -03:00
*
* Traits of this clock :
* prepare - clk_prepare only ensures that parents are prepared
* enable - clk_enable only ensures that parents are enabled
* rate - rate is adjustable .
* clk - > rate = ( parent - > rate * N * ( K + 1 ) > > P ) / ( M + 1 )
* parent - fixed parent . No clk_set_parent support
*/
# define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw)
2014-07-04 22:24:52 +02:00
# define FACTORS_MAX_PARENTS 5
2013-09-20 22:03:10 -03:00
# define SETMASK(len, pos) (((1U << (len)) - 1) << (pos))
2013-02-25 11:44:26 -03:00
# define CLRMASK(len, pos) (~(SETMASK(len, pos)))
# define FACTOR_GET(bit, len, reg) (((reg) & SETMASK(len, bit)) >> (bit))
# define FACTOR_SET(bit, len, reg, val) \
( ( ( reg ) & CLRMASK ( len , bit ) ) | ( val < < ( bit ) ) )
static unsigned long clk_factors_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
u8 n = 1 , k = 0 , p = 0 , m = 0 ;
u32 reg ;
unsigned long rate ;
struct clk_factors * factors = to_clk_factors ( hw ) ;
2016-01-25 21:15:38 +08:00
const struct clk_factors_config * config = factors - > config ;
2013-02-25 11:44:26 -03:00
/* Fetch the register value */
reg = readl ( factors - > reg ) ;
/* Get each individual factor if applicable */
if ( config - > nwidth ! = SUNXI_FACTORS_NOT_APPLICABLE )
n = FACTOR_GET ( config - > nshift , config - > nwidth , reg ) ;
if ( config - > kwidth ! = SUNXI_FACTORS_NOT_APPLICABLE )
k = FACTOR_GET ( config - > kshift , config - > kwidth , reg ) ;
if ( config - > mwidth ! = SUNXI_FACTORS_NOT_APPLICABLE )
m = FACTOR_GET ( config - > mshift , config - > mwidth , reg ) ;
if ( config - > pwidth ! = SUNXI_FACTORS_NOT_APPLICABLE )
p = FACTOR_GET ( config - > pshift , config - > pwidth , reg ) ;
/* Calculate the rate */
2014-06-26 23:55:41 +08:00
rate = ( parent_rate * ( n + config - > n_start ) * ( k + 1 ) > > p ) / ( m + 1 ) ;
2013-02-25 11:44:26 -03:00
return rate ;
}
static long clk_factors_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
struct clk_factors * factors = to_clk_factors ( hw ) ;
factors - > get_factors ( ( u32 * ) & rate , ( u32 ) * parent_rate ,
NULL , NULL , NULL , NULL ) ;
return rate ;
}
2015-07-07 20:48:08 +02:00
static int clk_factors_determine_rate ( struct clk_hw * hw ,
struct clk_rate_request * req )
2014-05-02 17:57:15 +02:00
{
2015-07-30 17:20:57 -07:00
struct clk_hw * parent , * best_parent = NULL ;
2014-05-02 17:57:15 +02:00
int i , num_parents ;
unsigned long parent_rate , best = 0 , child_rate , best_child_rate = 0 ;
/* find the parent that can help provide the fastest rate <= rate */
2015-06-25 16:53:23 -07:00
num_parents = clk_hw_get_num_parents ( hw ) ;
2014-05-02 17:57:15 +02:00
for ( i = 0 ; i < num_parents ; i + + ) {
2015-07-30 17:20:57 -07:00
parent = clk_hw_get_parent_by_index ( hw , i ) ;
2014-05-02 17:57:15 +02:00
if ( ! parent )
continue ;
2015-06-29 16:56:30 -07:00
if ( clk_hw_get_flags ( hw ) & CLK_SET_RATE_PARENT )
2015-07-30 17:20:57 -07:00
parent_rate = clk_hw_round_rate ( parent , req - > rate ) ;
2014-05-02 17:57:15 +02:00
else
2015-07-30 17:20:57 -07:00
parent_rate = clk_hw_get_rate ( parent ) ;
2014-05-02 17:57:15 +02:00
2015-07-07 20:48:08 +02:00
child_rate = clk_factors_round_rate ( hw , req - > rate ,
& parent_rate ) ;
2014-05-02 17:57:15 +02:00
2015-07-07 20:48:08 +02:00
if ( child_rate < = req - > rate & & child_rate > best_child_rate ) {
2014-05-02 17:57:15 +02:00
best_parent = parent ;
best = parent_rate ;
best_child_rate = child_rate ;
}
}
2015-07-09 22:39:38 +02:00
if ( ! best_parent )
return - EINVAL ;
2015-07-30 17:20:57 -07:00
req - > best_parent_hw = best_parent ;
2015-07-07 20:48:08 +02:00
req - > best_parent_rate = best ;
req - > rate = best_child_rate ;
2014-05-02 17:57:15 +02:00
2015-07-07 20:48:08 +02:00
return 0 ;
2014-05-02 17:57:15 +02:00
}
2013-02-25 11:44:26 -03:00
static int clk_factors_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
2013-09-20 22:03:11 -03:00
u8 n = 0 , k = 0 , m = 0 , p = 0 ;
2013-02-25 11:44:26 -03:00
u32 reg ;
struct clk_factors * factors = to_clk_factors ( hw ) ;
2016-01-25 21:15:38 +08:00
const struct clk_factors_config * config = factors - > config ;
2013-02-25 11:44:26 -03:00
unsigned long flags = 0 ;
factors - > get_factors ( ( u32 * ) & rate , ( u32 ) parent_rate , & n , & k , & m , & p ) ;
if ( factors - > lock )
spin_lock_irqsave ( factors - > lock , flags ) ;
/* Fetch the register value */
reg = readl ( factors - > reg ) ;
/* Set up the new factors - macros do not do anything if width is 0 */
reg = FACTOR_SET ( config - > nshift , config - > nwidth , reg , n ) ;
reg = FACTOR_SET ( config - > kshift , config - > kwidth , reg , k ) ;
reg = FACTOR_SET ( config - > mshift , config - > mwidth , reg , m ) ;
reg = FACTOR_SET ( config - > pshift , config - > pwidth , reg , p ) ;
/* Apply them now */
writel ( reg , factors - > reg ) ;
/* delay 500us so pll stabilizes */
__delay ( ( rate > > 20 ) * 500 / 2 ) ;
if ( factors - > lock )
spin_unlock_irqrestore ( factors - > lock , flags ) ;
return 0 ;
}
2014-07-04 22:24:52 +02:00
static const struct clk_ops clk_factors_ops = {
2014-05-02 17:57:15 +02:00
. determine_rate = clk_factors_determine_rate ,
2013-02-25 11:44:26 -03:00
. recalc_rate = clk_factors_recalc_rate ,
. round_rate = clk_factors_round_rate ,
. set_rate = clk_factors_set_rate ,
} ;
2014-07-04 22:24:52 +02:00
2014-11-23 14:38:07 +01:00
struct clk * sunxi_factors_register ( struct device_node * node ,
const struct factors_data * data ,
spinlock_t * lock ,
void __iomem * reg )
2014-07-04 22:24:52 +02:00
{
struct clk * clk ;
struct clk_factors * factors ;
struct clk_gate * gate = NULL ;
struct clk_mux * mux = NULL ;
struct clk_hw * gate_hw = NULL ;
struct clk_hw * mux_hw = NULL ;
const char * clk_name = node - > name ;
const char * parents [ FACTORS_MAX_PARENTS ] ;
int i = 0 ;
/* if we have a mux, we will have >1 parents */
2015-07-06 22:59:05 -05:00
i = of_clk_parent_fill ( node , parents , FACTORS_MAX_PARENTS ) ;
2014-07-04 22:24:52 +02:00
/*
* some factor clocks , such as pll5 and pll6 , may have multiple
* outputs , and have their name designated in factors_data
*/
if ( data - > name )
clk_name = data - > name ;
else
of_property_read_string ( node , " clock-output-names " , & clk_name ) ;
factors = kzalloc ( sizeof ( struct clk_factors ) , GFP_KERNEL ) ;
if ( ! factors )
return NULL ;
/* set up factors properties */
factors - > reg = reg ;
factors - > config = data - > table ;
factors - > get_factors = data - > getter ;
factors - > lock = lock ;
/* Add a gate if this factor clock can be gated */
if ( data - > enable ) {
gate = kzalloc ( sizeof ( struct clk_gate ) , GFP_KERNEL ) ;
if ( ! gate ) {
kfree ( factors ) ;
return NULL ;
}
/* set up gate properties */
gate - > reg = reg ;
gate - > bit_idx = data - > enable ;
gate - > lock = factors - > lock ;
gate_hw = & gate - > hw ;
}
/* Add a mux if this factor clock can be muxed */
if ( data - > mux ) {
mux = kzalloc ( sizeof ( struct clk_mux ) , GFP_KERNEL ) ;
if ( ! mux ) {
kfree ( factors ) ;
kfree ( gate ) ;
return NULL ;
}
/* set up gate properties */
mux - > reg = reg ;
mux - > shift = data - > mux ;
2014-10-20 22:10:26 +08:00
mux - > mask = data - > muxmask ;
2014-07-04 22:24:52 +02:00
mux - > lock = factors - > lock ;
mux_hw = & mux - > hw ;
}
clk = clk_register_composite ( NULL , clk_name ,
parents , i ,
mux_hw , & clk_mux_ops ,
& factors - > hw , & clk_factors_ops ,
gate_hw , & clk_gate_ops , 0 ) ;
if ( ! IS_ERR ( clk ) ) {
of_clk_add_provider ( node , of_clk_src_simple_get , clk ) ;
clk_register_clkdev ( clk , clk_name , NULL ) ;
}
return clk ;
}