2019-04-02 14:50:54 +02:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright ( C ) 2019 Microchip Technology Inc .
*
*/
# include <linux/bitfield.h>
# include <linux/clk-provider.h>
# include <linux/clkdev.h>
# include <linux/clk/at91_pmc.h>
# include <linux/of.h>
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
# include "pmc.h"
2020-01-20 14:10:06 +02:00
# define PMC_PLL_CTRL0_DIV_MSK GENMASK(7, 0)
# define PMC_PLL_CTRL1_MUL_MSK GENMASK(30, 24)
2019-04-02 14:50:54 +02:00
# define PLL_DIV_MAX (FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, UINT_MAX) + 1)
# define UPLL_DIV 2
# define PLL_MUL_MAX (FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, UINT_MAX) + 1)
# define PLL_MAX_ID 1
struct sam9x60_pll {
struct clk_hw hw ;
struct regmap * regmap ;
spinlock_t * lock ;
const struct clk_pll_characteristics * characteristics ;
u32 frac ;
u8 id ;
u8 div ;
u16 mul ;
} ;
# define to_sam9x60_pll(hw) container_of(hw, struct sam9x60_pll, hw)
static inline bool sam9x60_pll_ready ( struct regmap * regmap , int id )
{
unsigned int status ;
2020-01-20 14:10:06 +02:00
regmap_read ( regmap , AT91_PMC_PLL_ISR0 , & status ) ;
2019-04-02 14:50:54 +02:00
return ! ! ( status & BIT ( id ) ) ;
}
static int sam9x60_pll_prepare ( struct clk_hw * hw )
{
struct sam9x60_pll * pll = to_sam9x60_pll ( hw ) ;
struct regmap * regmap = pll - > regmap ;
unsigned long flags ;
u8 div ;
u16 mul ;
u32 val ;
spin_lock_irqsave ( pll - > lock , flags ) ;
2020-01-20 14:10:06 +02:00
regmap_write ( regmap , AT91_PMC_PLL_UPDT , pll - > id ) ;
2019-04-02 14:50:54 +02:00
2020-01-20 14:10:06 +02:00
regmap_read ( regmap , AT91_PMC_PLL_CTRL0 , & val ) ;
2019-04-02 14:50:54 +02:00
div = FIELD_GET ( PMC_PLL_CTRL0_DIV_MSK , val ) ;
2020-01-20 14:10:06 +02:00
regmap_read ( regmap , AT91_PMC_PLL_CTRL1 , & val ) ;
2019-04-02 14:50:54 +02:00
mul = FIELD_GET ( PMC_PLL_CTRL1_MUL_MSK , val ) ;
if ( sam9x60_pll_ready ( regmap , pll - > id ) & &
( div = = pll - > div & & mul = = pll - > mul ) ) {
spin_unlock_irqrestore ( pll - > lock , flags ) ;
return 0 ;
}
2020-01-20 14:10:06 +02:00
/* Recommended value for AT91_PMC_PLL_ACR */
2019-11-11 13:28:57 +00:00
if ( pll - > characteristics - > upll )
2020-01-20 14:10:06 +02:00
val = AT91_PMC_PLL_ACR_DEFAULT_UPLL ;
2019-11-11 13:28:57 +00:00
else
2020-01-20 14:10:06 +02:00
val = AT91_PMC_PLL_ACR_DEFAULT_PLLA ;
regmap_write ( regmap , AT91_PMC_PLL_ACR , val ) ;
2019-04-02 14:50:54 +02:00
2020-01-20 14:10:06 +02:00
regmap_write ( regmap , AT91_PMC_PLL_CTRL1 ,
2019-04-02 14:50:54 +02:00
FIELD_PREP ( PMC_PLL_CTRL1_MUL_MSK , pll - > mul ) ) ;
if ( pll - > characteristics - > upll ) {
/* Enable the UTMI internal bandgap */
2020-01-20 14:10:06 +02:00
val | = AT91_PMC_PLL_ACR_UTMIBG ;
regmap_write ( regmap , AT91_PMC_PLL_ACR , val ) ;
2019-04-02 14:50:54 +02:00
udelay ( 10 ) ;
/* Enable the UTMI internal regulator */
2020-01-20 14:10:06 +02:00
val | = AT91_PMC_PLL_ACR_UTMIVR ;
regmap_write ( regmap , AT91_PMC_PLL_ACR , val ) ;
2019-04-02 14:50:54 +02:00
udelay ( 10 ) ;
}
2020-01-20 14:10:06 +02:00
regmap_update_bits ( regmap , AT91_PMC_PLL_UPDT ,
AT91_PMC_PLL_UPDT_UPDATE , AT91_PMC_PLL_UPDT_UPDATE ) ;
2019-04-02 14:50:54 +02:00
2020-01-20 14:10:06 +02:00
regmap_write ( regmap , AT91_PMC_PLL_CTRL0 ,
AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL |
AT91_PMC_PLL_CTRL0_ENPLLCK | pll - > div ) ;
2019-04-02 14:50:54 +02:00
2020-01-20 14:10:06 +02:00
regmap_update_bits ( regmap , AT91_PMC_PLL_UPDT ,
AT91_PMC_PLL_UPDT_UPDATE , AT91_PMC_PLL_UPDT_UPDATE ) ;
2019-04-02 14:50:54 +02:00
while ( ! sam9x60_pll_ready ( regmap , pll - > id ) )
cpu_relax ( ) ;
spin_unlock_irqrestore ( pll - > lock , flags ) ;
return 0 ;
}
static int sam9x60_pll_is_prepared ( struct clk_hw * hw )
{
struct sam9x60_pll * pll = to_sam9x60_pll ( hw ) ;
return sam9x60_pll_ready ( pll - > regmap , pll - > id ) ;
}
static void sam9x60_pll_unprepare ( struct clk_hw * hw )
{
struct sam9x60_pll * pll = to_sam9x60_pll ( hw ) ;
unsigned long flags ;
spin_lock_irqsave ( pll - > lock , flags ) ;
2020-01-20 14:10:06 +02:00
regmap_write ( pll - > regmap , AT91_PMC_PLL_UPDT , pll - > id ) ;
2019-04-02 14:50:54 +02:00
2020-01-20 14:10:06 +02:00
regmap_update_bits ( pll - > regmap , AT91_PMC_PLL_CTRL0 ,
AT91_PMC_PLL_CTRL0_ENPLLCK , 0 ) ;
2019-04-02 14:50:54 +02:00
2020-01-20 14:10:06 +02:00
regmap_update_bits ( pll - > regmap , AT91_PMC_PLL_UPDT ,
AT91_PMC_PLL_UPDT_UPDATE , AT91_PMC_PLL_UPDT_UPDATE ) ;
2019-04-02 14:50:54 +02:00
2020-01-20 14:10:06 +02:00
regmap_update_bits ( pll - > regmap , AT91_PMC_PLL_CTRL0 ,
AT91_PMC_PLL_CTRL0_ENPLL , 0 ) ;
2019-04-02 14:50:54 +02:00
if ( pll - > characteristics - > upll )
2020-01-20 14:10:06 +02:00
regmap_update_bits ( pll - > regmap , AT91_PMC_PLL_ACR ,
AT91_PMC_PLL_ACR_UTMIBG |
AT91_PMC_PLL_ACR_UTMIVR , 0 ) ;
2019-04-02 14:50:54 +02:00
2020-01-20 14:10:06 +02:00
regmap_update_bits ( pll - > regmap , AT91_PMC_PLL_UPDT ,
AT91_PMC_PLL_UPDT_UPDATE , AT91_PMC_PLL_UPDT_UPDATE ) ;
2019-04-02 14:50:54 +02:00
spin_unlock_irqrestore ( pll - > lock , flags ) ;
}
static unsigned long sam9x60_pll_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct sam9x60_pll * pll = to_sam9x60_pll ( hw ) ;
return ( parent_rate * ( pll - > mul + 1 ) ) / ( pll - > div + 1 ) ;
}
static long sam9x60_pll_get_best_div_mul ( struct sam9x60_pll * pll ,
unsigned long rate ,
unsigned long parent_rate ,
bool update )
{
const struct clk_pll_characteristics * characteristics =
pll - > characteristics ;
unsigned long bestremainder = ULONG_MAX ;
unsigned long maxdiv , mindiv , tmpdiv ;
long bestrate = - ERANGE ;
unsigned long bestdiv = 0 ;
unsigned long bestmul = 0 ;
unsigned long bestfrac = 0 ;
if ( rate < characteristics - > output [ 0 ] . min | |
rate > characteristics - > output [ 0 ] . max )
return - ERANGE ;
if ( ! pll - > characteristics - > upll ) {
mindiv = parent_rate / rate ;
if ( mindiv < 2 )
mindiv = 2 ;
maxdiv = DIV_ROUND_UP ( parent_rate * PLL_MUL_MAX , rate ) ;
if ( maxdiv > PLL_DIV_MAX )
maxdiv = PLL_DIV_MAX ;
} else {
mindiv = maxdiv = UPLL_DIV ;
}
for ( tmpdiv = mindiv ; tmpdiv < = maxdiv ; tmpdiv + + ) {
unsigned long remainder ;
unsigned long tmprate ;
unsigned long tmpmul ;
unsigned long tmpfrac = 0 ;
/*
* Calculate the multiplier associated with the current
* divider that provide the closest rate to the requested one .
*/
tmpmul = mult_frac ( rate , tmpdiv , parent_rate ) ;
tmprate = mult_frac ( parent_rate , tmpmul , tmpdiv ) ;
remainder = rate - tmprate ;
if ( remainder ) {
tmpfrac = DIV_ROUND_CLOSEST_ULL ( ( u64 ) remainder * tmpdiv * ( 1 < < 22 ) ,
parent_rate ) ;
tmprate + = DIV_ROUND_CLOSEST_ULL ( ( u64 ) tmpfrac * parent_rate ,
tmpdiv * ( 1 < < 22 ) ) ;
if ( tmprate > rate )
remainder = tmprate - rate ;
else
remainder = rate - tmprate ;
}
/*
* Compare the remainder with the best remainder found until
* now and elect a new best multiplier / divider pair if the
* current remainder is smaller than the best one .
*/
if ( remainder < bestremainder ) {
bestremainder = remainder ;
bestdiv = tmpdiv ;
bestmul = tmpmul ;
bestrate = tmprate ;
bestfrac = tmpfrac ;
}
/* We've found a perfect match! */
if ( ! remainder )
break ;
}
/* Check if bestrate is a valid output rate */
if ( bestrate < characteristics - > output [ 0 ] . min & &
bestrate > characteristics - > output [ 0 ] . max )
return - ERANGE ;
if ( update ) {
pll - > div = bestdiv - 1 ;
pll - > mul = bestmul - 1 ;
pll - > frac = bestfrac ;
}
return bestrate ;
}
static long sam9x60_pll_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
struct sam9x60_pll * pll = to_sam9x60_pll ( hw ) ;
return sam9x60_pll_get_best_div_mul ( pll , rate , * parent_rate , false ) ;
}
static int sam9x60_pll_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct sam9x60_pll * pll = to_sam9x60_pll ( hw ) ;
return sam9x60_pll_get_best_div_mul ( pll , rate , parent_rate , true ) ;
}
static const struct clk_ops pll_ops = {
. prepare = sam9x60_pll_prepare ,
. unprepare = sam9x60_pll_unprepare ,
. is_prepared = sam9x60_pll_is_prepared ,
. recalc_rate = sam9x60_pll_recalc_rate ,
. round_rate = sam9x60_pll_round_rate ,
. set_rate = sam9x60_pll_set_rate ,
} ;
struct clk_hw * __init
sam9x60_clk_register_pll ( struct regmap * regmap , spinlock_t * lock ,
const char * name , const char * parent_name , u8 id ,
const struct clk_pll_characteristics * characteristics )
{
struct sam9x60_pll * pll ;
struct clk_hw * hw ;
struct clk_init_data init ;
unsigned int pllr ;
int ret ;
if ( id > PLL_MAX_ID )
return ERR_PTR ( - EINVAL ) ;
pll = kzalloc ( sizeof ( * pll ) , GFP_KERNEL ) ;
if ( ! pll )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & pll_ops ;
init . parent_names = & parent_name ;
init . num_parents = 1 ;
init . flags = CLK_SET_RATE_GATE ;
pll - > id = id ;
pll - > hw . init = & init ;
pll - > characteristics = characteristics ;
pll - > regmap = regmap ;
pll - > lock = lock ;
2020-01-20 14:10:06 +02:00
regmap_write ( regmap , AT91_PMC_PLL_UPDT , id ) ;
regmap_read ( regmap , AT91_PMC_PLL_CTRL0 , & pllr ) ;
2019-04-02 14:50:54 +02:00
pll - > div = FIELD_GET ( PMC_PLL_CTRL0_DIV_MSK , pllr ) ;
2020-01-20 14:10:06 +02:00
regmap_read ( regmap , AT91_PMC_PLL_CTRL1 , & pllr ) ;
2019-04-02 14:50:54 +02:00
pll - > mul = FIELD_GET ( PMC_PLL_CTRL1_MUL_MSK , pllr ) ;
hw = & pll - > hw ;
ret = clk_hw_register ( NULL , hw ) ;
if ( ret ) {
kfree ( pll ) ;
hw = ERR_PTR ( ret ) ;
}
return hw ;
}