2013-10-11 13:44:36 +04:00
/*
* Copyright ( C ) 2013 Boris BREZILLON < b . brezillon @ overkiz . 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 .
*
*/
2019-04-02 15:50:50 +03:00
# include <linux/bitops.h>
2013-10-11 13:44:36 +04:00
# include <linux/clk-provider.h>
# include <linux/clkdev.h>
# include <linux/clk/at91_pmc.h>
# include <linux/of.h>
2014-09-07 10:14:29 +04:00
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
2013-10-11 13:44:36 +04:00
# include "pmc.h"
2014-09-07 10:14:29 +04:00
DEFINE_SPINLOCK ( pmc_pcr_lock ) ;
2013-10-11 13:44:36 +04:00
# define PERIPHERAL_ID_MIN 2
# define PERIPHERAL_ID_MAX 31
# define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
2015-05-28 15:01:08 +03:00
# define PERIPHERAL_MAX_SHIFT 3
2013-10-11 13:44:36 +04:00
struct clk_peripheral {
struct clk_hw hw ;
2014-09-07 10:14:29 +04:00
struct regmap * regmap ;
2013-10-11 13:44:36 +04:00
u32 id ;
} ;
# define to_clk_peripheral(hw) container_of(hw, struct clk_peripheral, hw)
struct clk_sam9x5_peripheral {
struct clk_hw hw ;
2014-09-07 10:14:29 +04:00
struct regmap * regmap ;
2013-10-11 13:44:36 +04:00
struct clk_range range ;
2014-09-07 10:14:29 +04:00
spinlock_t * lock ;
2013-10-11 13:44:36 +04:00
u32 id ;
u32 div ;
2019-04-02 15:50:50 +03:00
const struct clk_pcr_layout * layout ;
2013-10-11 13:44:36 +04:00
bool auto_div ;
} ;
# define to_clk_sam9x5_peripheral(hw) \
container_of ( hw , struct clk_sam9x5_peripheral , hw )
static int clk_peripheral_enable ( struct clk_hw * hw )
{
struct clk_peripheral * periph = to_clk_peripheral ( hw ) ;
int offset = AT91_PMC_PCER ;
u32 id = periph - > id ;
if ( id < PERIPHERAL_ID_MIN )
return 0 ;
if ( id > PERIPHERAL_ID_MAX )
offset = AT91_PMC_PCER1 ;
2014-09-07 10:14:29 +04:00
regmap_write ( periph - > regmap , offset , PERIPHERAL_MASK ( id ) ) ;
2013-10-11 13:44:36 +04:00
return 0 ;
}
static void clk_peripheral_disable ( struct clk_hw * hw )
{
struct clk_peripheral * periph = to_clk_peripheral ( hw ) ;
int offset = AT91_PMC_PCDR ;
u32 id = periph - > id ;
if ( id < PERIPHERAL_ID_MIN )
return ;
if ( id > PERIPHERAL_ID_MAX )
offset = AT91_PMC_PCDR1 ;
2014-09-07 10:14:29 +04:00
regmap_write ( periph - > regmap , offset , PERIPHERAL_MASK ( id ) ) ;
2013-10-11 13:44:36 +04:00
}
static int clk_peripheral_is_enabled ( struct clk_hw * hw )
{
struct clk_peripheral * periph = to_clk_peripheral ( hw ) ;
int offset = AT91_PMC_PCSR ;
2014-09-07 10:14:29 +04:00
unsigned int status ;
2013-10-11 13:44:36 +04:00
u32 id = periph - > id ;
if ( id < PERIPHERAL_ID_MIN )
return 1 ;
if ( id > PERIPHERAL_ID_MAX )
offset = AT91_PMC_PCSR1 ;
2014-09-07 10:14:29 +04:00
regmap_read ( periph - > regmap , offset , & status ) ;
return status & PERIPHERAL_MASK ( id ) ? 1 : 0 ;
2013-10-11 13:44:36 +04:00
}
static const struct clk_ops peripheral_ops = {
. enable = clk_peripheral_enable ,
. disable = clk_peripheral_disable ,
. is_enabled = clk_peripheral_is_enabled ,
} ;
2018-10-16 17:21:44 +03:00
struct clk_hw * __init
2014-09-07 10:14:29 +04:00
at91_clk_register_peripheral ( struct regmap * regmap , const char * name ,
2013-10-11 13:44:36 +04:00
const char * parent_name , u32 id )
{
struct clk_peripheral * periph ;
struct clk_init_data init ;
2016-06-02 00:31:22 +03:00
struct clk_hw * hw ;
int ret ;
2013-10-11 13:44:36 +04:00
2014-09-07 10:14:29 +04:00
if ( ! name | | ! parent_name | | id > PERIPHERAL_ID_MAX )
2013-10-11 13:44:36 +04:00
return ERR_PTR ( - EINVAL ) ;
periph = kzalloc ( sizeof ( * periph ) , GFP_KERNEL ) ;
if ( ! periph )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & peripheral_ops ;
init . parent_names = ( parent_name ? & parent_name : NULL ) ;
init . num_parents = ( parent_name ? 1 : 0 ) ;
init . flags = 0 ;
periph - > id = id ;
periph - > hw . init = & init ;
2014-09-07 10:14:29 +04:00
periph - > regmap = regmap ;
2013-10-11 13:44:36 +04:00
2016-06-02 00:31:22 +03:00
hw = & periph - > hw ;
ret = clk_hw_register ( NULL , & periph - > hw ) ;
if ( ret ) {
2013-10-11 13:44:36 +04:00
kfree ( periph ) ;
2016-06-02 00:31:22 +03:00
hw = ERR_PTR ( ret ) ;
}
2013-10-11 13:44:36 +04:00
2016-06-02 00:31:22 +03:00
return hw ;
2013-10-11 13:44:36 +04:00
}
static void clk_sam9x5_peripheral_autodiv ( struct clk_sam9x5_peripheral * periph )
{
2015-07-31 03:20:57 +03:00
struct clk_hw * parent ;
2013-10-11 13:44:36 +04:00
unsigned long parent_rate ;
int shift = 0 ;
if ( ! periph - > auto_div )
return ;
if ( periph - > range . max ) {
2015-07-31 03:20:57 +03:00
parent = clk_hw_get_parent_by_index ( & periph - > hw , 0 ) ;
parent_rate = clk_hw_get_rate ( parent ) ;
2013-10-11 13:44:36 +04:00
if ( ! parent_rate )
return ;
for ( ; shift < PERIPHERAL_MAX_SHIFT ; shift + + ) {
if ( parent_rate > > shift < = periph - > range . max )
break ;
}
}
periph - > auto_div = false ;
periph - > div = shift ;
}
static int clk_sam9x5_peripheral_enable ( struct clk_hw * hw )
{
struct clk_sam9x5_peripheral * periph = to_clk_sam9x5_peripheral ( hw ) ;
2014-09-07 10:14:29 +04:00
unsigned long flags ;
2013-10-11 13:44:36 +04:00
if ( periph - > id < PERIPHERAL_ID_MIN )
return 0 ;
2014-09-07 10:14:29 +04:00
spin_lock_irqsave ( periph - > lock , flags ) ;
2019-04-02 15:50:50 +03:00
regmap_write ( periph - > regmap , periph - > layout - > offset ,
( periph - > id & periph - > layout - > pid_mask ) ) ;
regmap_update_bits ( periph - > regmap , periph - > layout - > offset ,
periph - > layout - > div_mask | periph - > layout - > cmd |
2014-09-07 10:14:29 +04:00
AT91_PMC_PCR_EN ,
2019-04-02 15:50:50 +03:00
field_prep ( periph - > layout - > div_mask , periph - > div ) |
periph - > layout - > cmd |
2014-09-07 10:14:29 +04:00
AT91_PMC_PCR_EN ) ;
spin_unlock_irqrestore ( periph - > lock , flags ) ;
2013-10-11 13:44:36 +04:00
return 0 ;
}
static void clk_sam9x5_peripheral_disable ( struct clk_hw * hw )
{
struct clk_sam9x5_peripheral * periph = to_clk_sam9x5_peripheral ( hw ) ;
2014-09-07 10:14:29 +04:00
unsigned long flags ;
2013-10-11 13:44:36 +04:00
if ( periph - > id < PERIPHERAL_ID_MIN )
return ;
2014-09-07 10:14:29 +04:00
spin_lock_irqsave ( periph - > lock , flags ) ;
2019-04-02 15:50:50 +03:00
regmap_write ( periph - > regmap , periph - > layout - > offset ,
( periph - > id & periph - > layout - > pid_mask ) ) ;
regmap_update_bits ( periph - > regmap , periph - > layout - > offset ,
AT91_PMC_PCR_EN | periph - > layout - > cmd ,
periph - > layout - > cmd ) ;
2014-09-07 10:14:29 +04:00
spin_unlock_irqrestore ( periph - > lock , flags ) ;
2013-10-11 13:44:36 +04:00
}
static int clk_sam9x5_peripheral_is_enabled ( struct clk_hw * hw )
{
struct clk_sam9x5_peripheral * periph = to_clk_sam9x5_peripheral ( hw ) ;
2014-09-07 10:14:29 +04:00
unsigned long flags ;
unsigned int status ;
2013-10-11 13:44:36 +04:00
if ( periph - > id < PERIPHERAL_ID_MIN )
return 1 ;
2014-09-07 10:14:29 +04:00
spin_lock_irqsave ( periph - > lock , flags ) ;
2019-04-02 15:50:50 +03:00
regmap_write ( periph - > regmap , periph - > layout - > offset ,
( periph - > id & periph - > layout - > pid_mask ) ) ;
regmap_read ( periph - > regmap , periph - > layout - > offset , & status ) ;
2014-09-07 10:14:29 +04:00
spin_unlock_irqrestore ( periph - > lock , flags ) ;
2013-10-11 13:44:36 +04:00
2014-09-07 10:14:29 +04:00
return status & AT91_PMC_PCR_EN ? 1 : 0 ;
2013-10-11 13:44:36 +04:00
}
static unsigned long
clk_sam9x5_peripheral_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct clk_sam9x5_peripheral * periph = to_clk_sam9x5_peripheral ( hw ) ;
2014-09-07 10:14:29 +04:00
unsigned long flags ;
unsigned int status ;
2013-10-11 13:44:36 +04:00
if ( periph - > id < PERIPHERAL_ID_MIN )
return parent_rate ;
2014-09-07 10:14:29 +04:00
spin_lock_irqsave ( periph - > lock , flags ) ;
2019-04-02 15:50:50 +03:00
regmap_write ( periph - > regmap , periph - > layout - > offset ,
( periph - > id & periph - > layout - > pid_mask ) ) ;
regmap_read ( periph - > regmap , periph - > layout - > offset , & status ) ;
2014-09-07 10:14:29 +04:00
spin_unlock_irqrestore ( periph - > lock , flags ) ;
2013-10-11 13:44:36 +04:00
2014-09-07 10:14:29 +04:00
if ( status & AT91_PMC_PCR_EN ) {
2019-04-02 15:50:50 +03:00
periph - > div = field_get ( periph - > layout - > div_mask , status ) ;
2013-10-11 13:44:36 +04:00
periph - > auto_div = false ;
} else {
clk_sam9x5_peripheral_autodiv ( periph ) ;
}
return parent_rate > > periph - > div ;
}
static long clk_sam9x5_peripheral_round_rate ( struct clk_hw * hw ,
unsigned long rate ,
unsigned long * parent_rate )
{
int shift = 0 ;
unsigned long best_rate ;
unsigned long best_diff ;
unsigned long cur_rate = * parent_rate ;
unsigned long cur_diff ;
struct clk_sam9x5_peripheral * periph = to_clk_sam9x5_peripheral ( hw ) ;
if ( periph - > id < PERIPHERAL_ID_MIN | | ! periph - > range . max )
return * parent_rate ;
if ( periph - > range . max ) {
2015-05-28 15:01:08 +03:00
for ( ; shift < = PERIPHERAL_MAX_SHIFT ; shift + + ) {
2013-10-11 13:44:36 +04:00
cur_rate = * parent_rate > > shift ;
if ( cur_rate < = periph - > range . max )
break ;
}
}
if ( rate > = cur_rate )
return cur_rate ;
best_diff = cur_rate - rate ;
best_rate = cur_rate ;
2015-05-28 15:01:08 +03:00
for ( ; shift < = PERIPHERAL_MAX_SHIFT ; shift + + ) {
2013-10-11 13:44:36 +04:00
cur_rate = * parent_rate > > shift ;
if ( cur_rate < rate )
cur_diff = rate - cur_rate ;
else
cur_diff = cur_rate - rate ;
if ( cur_diff < best_diff ) {
best_diff = cur_diff ;
best_rate = cur_rate ;
}
if ( ! best_diff | | cur_rate < rate )
break ;
}
return best_rate ;
}
static int clk_sam9x5_peripheral_set_rate ( struct clk_hw * hw ,
unsigned long rate ,
unsigned long parent_rate )
{
int shift ;
struct clk_sam9x5_peripheral * periph = to_clk_sam9x5_peripheral ( hw ) ;
if ( periph - > id < PERIPHERAL_ID_MIN | | ! periph - > range . max ) {
if ( parent_rate = = rate )
return 0 ;
else
return - EINVAL ;
}
if ( periph - > range . max & & rate > periph - > range . max )
return - EINVAL ;
2015-05-28 15:01:08 +03:00
for ( shift = 0 ; shift < = PERIPHERAL_MAX_SHIFT ; shift + + ) {
2013-10-11 13:44:36 +04:00
if ( parent_rate > > shift = = rate ) {
periph - > auto_div = false ;
periph - > div = shift ;
return 0 ;
}
}
return - EINVAL ;
}
static const struct clk_ops sam9x5_peripheral_ops = {
. enable = clk_sam9x5_peripheral_enable ,
. disable = clk_sam9x5_peripheral_disable ,
. is_enabled = clk_sam9x5_peripheral_is_enabled ,
. recalc_rate = clk_sam9x5_peripheral_recalc_rate ,
. round_rate = clk_sam9x5_peripheral_round_rate ,
. set_rate = clk_sam9x5_peripheral_set_rate ,
} ;
2018-10-16 17:21:44 +03:00
struct clk_hw * __init
2014-09-07 10:14:29 +04:00
at91_clk_register_sam9x5_peripheral ( struct regmap * regmap , spinlock_t * lock ,
2019-04-02 15:50:50 +03:00
const struct clk_pcr_layout * layout ,
2014-09-07 10:14:29 +04:00
const char * name , const char * parent_name ,
u32 id , const struct clk_range * range )
2013-10-11 13:44:36 +04:00
{
struct clk_sam9x5_peripheral * periph ;
struct clk_init_data init ;
2016-06-02 00:31:22 +03:00
struct clk_hw * hw ;
int ret ;
2013-10-11 13:44:36 +04:00
2014-09-07 10:14:29 +04:00
if ( ! name | | ! parent_name )
2013-10-11 13:44:36 +04:00
return ERR_PTR ( - EINVAL ) ;
periph = kzalloc ( sizeof ( * periph ) , GFP_KERNEL ) ;
if ( ! periph )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & sam9x5_peripheral_ops ;
init . parent_names = ( parent_name ? & parent_name : NULL ) ;
init . num_parents = ( parent_name ? 1 : 0 ) ;
init . flags = 0 ;
periph - > id = id ;
periph - > hw . init = & init ;
periph - > div = 0 ;
2014-09-07 10:14:29 +04:00
periph - > regmap = regmap ;
periph - > lock = lock ;
2019-04-02 15:50:50 +03:00
if ( layout - > div_mask )
periph - > auto_div = true ;
periph - > layout = layout ;
2013-10-11 13:44:36 +04:00
periph - > range = * range ;
2016-06-02 00:31:22 +03:00
hw = & periph - > hw ;
ret = clk_hw_register ( NULL , & periph - > hw ) ;
if ( ret ) {
2013-10-11 13:44:36 +04:00
kfree ( periph ) ;
2016-06-02 00:31:22 +03:00
hw = ERR_PTR ( ret ) ;
2017-06-08 03:36:47 +03:00
} else {
2013-10-11 13:44:36 +04:00
clk_sam9x5_peripheral_autodiv ( periph ) ;
2017-06-08 03:36:47 +03:00
pmc_register_id ( id ) ;
}
2013-10-11 13:44:36 +04:00
2016-06-02 00:31:22 +03:00
return hw ;
2013-10-11 13:44:36 +04:00
}