2015-06-01 14:13:53 +03:00
/*
* Copyright ( c ) 2015 Endless Mobile , Inc .
* Author : Carlo Caione < carlo @ endlessm . com >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope 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 .
*
* You should have received a copy of the GNU General Public License along with
* this program . If not , see < http : //www.gnu.org/licenses/>.
*/
/*
* In the most basic form , a Meson PLL is composed as follows :
*
* PLL
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | |
* in - - - - - [ / N ] - - - [ * M ] - - - [ > > OD ] - - - - - > > out
* | ^ ^ |
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | |
* FREF VCO
*
* out = ( in * M / N ) > > OD
*/
# include <linux/clk-provider.h>
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/slab.h>
# include <linux/string.h>
# include "clkc.h"
# define MESON_PLL_RESET BIT(29)
# define MESON_PLL_LOCK BIT(31)
# define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw)
static unsigned long meson_clk_pll_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct meson_clk_pll * pll = to_meson_clk_pll ( hw ) ;
struct parm * p ;
unsigned long parent_rate_mhz = parent_rate / 1000000 ;
unsigned long rate_mhz ;
2016-06-07 04:08:15 +03:00
u16 n , m , frac = 0 , od , od2 = 0 ;
2015-06-01 14:13:53 +03:00
u32 reg ;
2016-04-28 22:01:42 +03:00
p = & pll - > n ;
2015-06-01 14:13:53 +03:00
reg = readl ( pll - > base + p - > reg_off ) ;
n = PARM_GET ( p - > width , p - > shift , reg ) ;
2016-04-28 22:01:42 +03:00
p = & pll - > m ;
2015-06-01 14:13:53 +03:00
reg = readl ( pll - > base + p - > reg_off ) ;
m = PARM_GET ( p - > width , p - > shift , reg ) ;
2016-04-28 22:01:42 +03:00
p = & pll - > od ;
2015-06-01 14:13:53 +03:00
reg = readl ( pll - > base + p - > reg_off ) ;
od = PARM_GET ( p - > width , p - > shift , reg ) ;
2016-06-07 04:08:15 +03:00
p = & pll - > od2 ;
if ( p - > width ) {
reg = readl ( pll - > base + p - > reg_off ) ;
od2 = PARM_GET ( p - > width , p - > shift , reg ) ;
}
p = & pll - > frac ;
if ( p - > width ) {
reg = readl ( pll - > base + p - > reg_off ) ;
frac = PARM_GET ( p - > width , p - > shift , reg ) ;
rate_mhz = ( parent_rate_mhz * m + \
( parent_rate_mhz * frac > > 12 ) ) * 2 / n ;
rate_mhz = rate_mhz > > od > > od2 ;
} else
rate_mhz = ( parent_rate_mhz * m / n ) > > od > > od2 ;
2015-06-01 14:13:53 +03:00
return rate_mhz * 1000000 ;
}
static long meson_clk_pll_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
struct meson_clk_pll * pll = to_meson_clk_pll ( hw ) ;
2016-04-28 22:01:42 +03:00
const struct pll_rate_table * rate_table = pll - > rate_table ;
2015-06-01 14:13:53 +03:00
int i ;
for ( i = 0 ; i < pll - > rate_count ; i + + ) {
if ( rate < = rate_table [ i ] . rate )
return rate_table [ i ] . rate ;
}
/* else return the smallest value */
return rate_table [ 0 ] . rate ;
}
static const struct pll_rate_table * meson_clk_get_pll_settings ( struct meson_clk_pll * pll ,
unsigned long rate )
{
2016-04-28 22:01:42 +03:00
const struct pll_rate_table * rate_table = pll - > rate_table ;
2015-06-01 14:13:53 +03:00
int i ;
for ( i = 0 ; i < pll - > rate_count ; i + + ) {
if ( rate = = rate_table [ i ] . rate )
return & rate_table [ i ] ;
}
return NULL ;
}
static int meson_clk_pll_wait_lock ( struct meson_clk_pll * pll ,
struct parm * p_n )
{
int delay = 24000000 ;
u32 reg ;
while ( delay > 0 ) {
reg = readl ( pll - > base + p_n - > reg_off ) ;
if ( reg & MESON_PLL_LOCK )
return 0 ;
delay - - ;
}
return - ETIMEDOUT ;
}
static int meson_clk_pll_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct meson_clk_pll * pll = to_meson_clk_pll ( hw ) ;
struct parm * p ;
const struct pll_rate_table * rate_set ;
unsigned long old_rate ;
int ret = 0 ;
u32 reg ;
if ( parent_rate = = 0 | | rate = = 0 )
return - EINVAL ;
old_rate = rate ;
rate_set = meson_clk_get_pll_settings ( pll , rate ) ;
if ( ! rate_set )
return - EINVAL ;
/* PLL reset */
2016-04-28 22:01:42 +03:00
p = & pll - > n ;
2015-06-01 14:13:53 +03:00
reg = readl ( pll - > base + p - > reg_off ) ;
writel ( reg | MESON_PLL_RESET , pll - > base + p - > reg_off ) ;
reg = PARM_SET ( p - > width , p - > shift , reg , rate_set - > n ) ;
writel ( reg , pll - > base + p - > reg_off ) ;
2016-04-28 22:01:42 +03:00
p = & pll - > m ;
2015-06-01 14:13:53 +03:00
reg = readl ( pll - > base + p - > reg_off ) ;
reg = PARM_SET ( p - > width , p - > shift , reg , rate_set - > m ) ;
writel ( reg , pll - > base + p - > reg_off ) ;
2016-04-28 22:01:42 +03:00
p = & pll - > od ;
2015-06-01 14:13:53 +03:00
reg = readl ( pll - > base + p - > reg_off ) ;
reg = PARM_SET ( p - > width , p - > shift , reg , rate_set - > od ) ;
writel ( reg , pll - > base + p - > reg_off ) ;
2016-06-07 04:08:15 +03:00
p = & pll - > od2 ;
if ( p - > width ) {
reg = readl ( pll - > base + p - > reg_off ) ;
reg = PARM_SET ( p - > width , p - > shift , reg , rate_set - > od2 ) ;
writel ( reg , pll - > base + p - > reg_off ) ;
}
p = & pll - > frac ;
if ( p - > width ) {
reg = readl ( pll - > base + p - > reg_off ) ;
reg = PARM_SET ( p - > width , p - > shift , reg , rate_set - > frac ) ;
writel ( reg , pll - > base + p - > reg_off ) ;
}
2016-04-28 22:01:42 +03:00
p = & pll - > n ;
2015-06-01 14:13:53 +03:00
ret = meson_clk_pll_wait_lock ( pll , p ) ;
if ( ret ) {
pr_warn ( " %s: pll did not lock, trying to restore old rate %lu \n " ,
__func__ , old_rate ) ;
meson_clk_pll_set_rate ( hw , old_rate , parent_rate ) ;
}
return ret ;
}
2016-04-28 22:01:42 +03:00
const struct clk_ops meson_clk_pll_ops = {
2015-06-01 14:13:53 +03:00
. recalc_rate = meson_clk_pll_recalc_rate ,
. round_rate = meson_clk_pll_round_rate ,
. set_rate = meson_clk_pll_set_rate ,
} ;
2016-04-28 22:01:42 +03:00
const struct clk_ops meson_clk_pll_ro_ops = {
2015-06-01 14:13:53 +03:00
. recalc_rate = meson_clk_pll_recalc_rate ,
} ;