2019-05-27 08:55:01 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2013-12-02 15:07:02 +01:00
/*
* Copyright ( C ) 2013 Boris BREZILLON < b . brezillon @ overkiz . com >
*/
# include <linux/clk-provider.h>
# include <linux/clkdev.h>
# include <linux/clk/at91_pmc.h>
# include <linux/of.h>
2014-09-05 09:54:13 +02:00
# include <linux/mfd/syscon.h>
2017-06-08 02:36:47 +02:00
# include <linux/platform_device.h>
2014-09-07 08:14:29 +02:00
# include <linux/regmap.h>
2017-06-08 02:36:47 +02:00
# include <linux/syscore_ops.h>
2013-12-02 15:07:02 +01:00
# include <asm/proc-fns.h>
2018-10-16 16:21:47 +02:00
# include <dt-bindings/clock/at91.h>
2013-12-02 15:07:02 +01:00
# include "pmc.h"
2017-06-08 02:36:47 +02:00
# define PMC_MAX_IDS 128
2017-12-11 17:55:35 +01:00
# define PMC_MAX_PCKS 8
2017-06-08 02:36:47 +02:00
2013-12-02 15:07:02 +01:00
int of_at91_get_clk_range ( struct device_node * np , const char * propname ,
struct clk_range * range )
{
u32 min , max ;
int ret ;
ret = of_property_read_u32_index ( np , propname , 0 , & min ) ;
if ( ret )
return ret ;
ret = of_property_read_u32_index ( np , propname , 1 , & max ) ;
if ( ret )
return ret ;
if ( range ) {
range - > min = min ;
range - > max = max ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( of_at91_get_clk_range ) ;
2017-06-08 02:36:47 +02:00
2018-10-16 16:21:47 +02:00
struct clk_hw * of_clk_hw_pmc_get ( struct of_phandle_args * clkspec , void * data )
{
unsigned int type = clkspec - > args [ 0 ] ;
unsigned int idx = clkspec - > args [ 1 ] ;
struct pmc_data * pmc_data = data ;
switch ( type ) {
case PMC_TYPE_CORE :
if ( idx < pmc_data - > ncore )
return pmc_data - > chws [ idx ] ;
break ;
case PMC_TYPE_SYSTEM :
if ( idx < pmc_data - > nsystem )
return pmc_data - > shws [ idx ] ;
break ;
case PMC_TYPE_PERIPHERAL :
if ( idx < pmc_data - > nperiph )
return pmc_data - > phws [ idx ] ;
break ;
case PMC_TYPE_GCK :
if ( idx < pmc_data - > ngck )
return pmc_data - > ghws [ idx ] ;
break ;
2020-05-05 00:37:56 +02:00
case PMC_TYPE_PROGRAMMABLE :
if ( idx < pmc_data - > npck )
return pmc_data - > pchws [ idx ] ;
break ;
2018-10-16 16:21:47 +02:00
default :
break ;
}
pr_err ( " %s: invalid type (%u) or index (%u) \n " , __func__ , type , idx ) ;
return ERR_PTR ( - EINVAL ) ;
}
2018-10-16 16:21:45 +02:00
struct pmc_data * pmc_data_allocate ( unsigned int ncore , unsigned int nsystem ,
2020-05-05 00:37:56 +02:00
unsigned int nperiph , unsigned int ngck ,
unsigned int npck )
2018-10-16 16:21:45 +02:00
{
2020-05-05 00:37:56 +02:00
unsigned int num_clks = ncore + nsystem + nperiph + ngck + npck ;
2020-05-05 00:37:56 +02:00
struct pmc_data * pmc_data ;
2018-10-16 16:21:45 +02:00
2020-05-05 00:37:56 +02:00
pmc_data = kzalloc ( struct_size ( pmc_data , hwtable , num_clks ) ,
GFP_KERNEL ) ;
2018-10-16 16:21:45 +02:00
if ( ! pmc_data )
return NULL ;
pmc_data - > ncore = ncore ;
2020-05-05 00:37:56 +02:00
pmc_data - > chws = pmc_data - > hwtable ;
2018-10-16 16:21:45 +02:00
pmc_data - > nsystem = nsystem ;
2020-05-05 00:37:56 +02:00
pmc_data - > shws = pmc_data - > chws + ncore ;
2018-10-16 16:21:45 +02:00
pmc_data - > nperiph = nperiph ;
2020-05-05 00:37:56 +02:00
pmc_data - > phws = pmc_data - > shws + nsystem ;
2018-10-16 16:21:45 +02:00
pmc_data - > ngck = ngck ;
2020-05-05 00:37:56 +02:00
pmc_data - > ghws = pmc_data - > phws + nperiph ;
2018-10-16 16:21:45 +02:00
2020-05-05 00:37:56 +02:00
pmc_data - > npck = npck ;
pmc_data - > pchws = pmc_data - > ghws + ngck ;
2018-10-16 16:21:45 +02:00
return pmc_data ;
}
2017-06-08 02:36:47 +02:00
# ifdef CONFIG_PM
static struct regmap * pmcreg ;
static u8 registered_ids [ PMC_MAX_IDS ] ;
2017-12-11 17:55:35 +01:00
static u8 registered_pcks [ PMC_MAX_PCKS ] ;
2017-06-08 02:36:47 +02:00
static struct
{
u32 scsr ;
u32 pcsr0 ;
u32 uckr ;
u32 mor ;
u32 mcfr ;
u32 pllar ;
u32 mckr ;
u32 usb ;
u32 imr ;
u32 pcsr1 ;
u32 pcr [ PMC_MAX_IDS ] ;
u32 audio_pll0 ;
u32 audio_pll1 ;
2017-12-11 17:55:35 +01:00
u32 pckr [ PMC_MAX_PCKS ] ;
2017-06-08 02:36:47 +02:00
} pmc_cache ;
2017-12-11 17:55:35 +01:00
/*
* As Peripheral ID 0 is invalid on AT91 chips , the identifier is stored
* without alteration in the table , and 0 is for unused clocks .
*/
2017-06-08 02:36:47 +02:00
void pmc_register_id ( u8 id )
{
int i ;
for ( i = 0 ; i < PMC_MAX_IDS ; i + + ) {
if ( registered_ids [ i ] = = 0 ) {
registered_ids [ i ] = id ;
break ;
}
if ( registered_ids [ i ] = = id )
break ;
}
}
2017-12-11 17:55:35 +01:00
/*
* As Programmable Clock 0 is valid on AT91 chips , there is an offset
* of 1 between the stored value and the real clock ID .
*/
void pmc_register_pck ( u8 pck )
{
int i ;
for ( i = 0 ; i < PMC_MAX_PCKS ; i + + ) {
if ( registered_pcks [ i ] = = 0 ) {
registered_pcks [ i ] = pck + 1 ;
break ;
}
if ( registered_pcks [ i ] = = ( pck + 1 ) )
break ;
}
}
2017-06-08 02:36:47 +02:00
static int pmc_suspend ( void )
{
int i ;
2017-12-11 17:55:35 +01:00
u8 num ;
2017-06-08 02:36:47 +02:00
2017-12-11 17:55:34 +01:00
regmap_read ( pmcreg , AT91_PMC_SCSR , & pmc_cache . scsr ) ;
2017-06-08 02:36:47 +02:00
regmap_read ( pmcreg , AT91_PMC_PCSR , & pmc_cache . pcsr0 ) ;
regmap_read ( pmcreg , AT91_CKGR_UCKR , & pmc_cache . uckr ) ;
regmap_read ( pmcreg , AT91_CKGR_MOR , & pmc_cache . mor ) ;
regmap_read ( pmcreg , AT91_CKGR_MCFR , & pmc_cache . mcfr ) ;
regmap_read ( pmcreg , AT91_CKGR_PLLAR , & pmc_cache . pllar ) ;
regmap_read ( pmcreg , AT91_PMC_MCKR , & pmc_cache . mckr ) ;
regmap_read ( pmcreg , AT91_PMC_USB , & pmc_cache . usb ) ;
regmap_read ( pmcreg , AT91_PMC_IMR , & pmc_cache . imr ) ;
regmap_read ( pmcreg , AT91_PMC_PCSR1 , & pmc_cache . pcsr1 ) ;
for ( i = 0 ; registered_ids [ i ] ; i + + ) {
regmap_write ( pmcreg , AT91_PMC_PCR ,
( registered_ids [ i ] & AT91_PMC_PCR_PID_MASK ) ) ;
regmap_read ( pmcreg , AT91_PMC_PCR ,
& pmc_cache . pcr [ registered_ids [ i ] ] ) ;
}
2017-12-11 17:55:35 +01:00
for ( i = 0 ; registered_pcks [ i ] ; i + + ) {
num = registered_pcks [ i ] - 1 ;
regmap_read ( pmcreg , AT91_PMC_PCKR ( num ) , & pmc_cache . pckr [ num ] ) ;
}
2017-06-08 02:36:47 +02:00
return 0 ;
}
2017-12-11 17:55:33 +01:00
static bool pmc_ready ( unsigned int mask )
{
unsigned int status ;
regmap_read ( pmcreg , AT91_PMC_SR , & status ) ;
return ( ( status & mask ) = = mask ) ? 1 : 0 ;
}
2017-06-08 02:36:47 +02:00
static void pmc_resume ( void )
{
2017-12-11 17:55:33 +01:00
int i ;
2017-12-11 17:55:35 +01:00
u8 num ;
2017-06-08 02:36:47 +02:00
u32 tmp ;
2017-12-11 17:55:33 +01:00
u32 mask = AT91_PMC_MCKRDY | AT91_PMC_LOCKA ;
2017-06-08 02:36:47 +02:00
regmap_read ( pmcreg , AT91_PMC_MCKR , & tmp ) ;
if ( pmc_cache . mckr ! = tmp )
pr_warn ( " MCKR was not configured properly by the firmware \n " ) ;
regmap_read ( pmcreg , AT91_CKGR_PLLAR , & tmp ) ;
if ( pmc_cache . pllar ! = tmp )
pr_warn ( " PLLAR was not configured properly by the firmware \n " ) ;
2017-12-11 17:55:34 +01:00
regmap_write ( pmcreg , AT91_PMC_SCER , pmc_cache . scsr ) ;
2017-06-08 02:36:47 +02:00
regmap_write ( pmcreg , AT91_PMC_PCER , pmc_cache . pcsr0 ) ;
regmap_write ( pmcreg , AT91_CKGR_UCKR , pmc_cache . uckr ) ;
regmap_write ( pmcreg , AT91_CKGR_MOR , pmc_cache . mor ) ;
regmap_write ( pmcreg , AT91_CKGR_MCFR , pmc_cache . mcfr ) ;
regmap_write ( pmcreg , AT91_PMC_USB , pmc_cache . usb ) ;
regmap_write ( pmcreg , AT91_PMC_IMR , pmc_cache . imr ) ;
regmap_write ( pmcreg , AT91_PMC_PCER1 , pmc_cache . pcsr1 ) ;
for ( i = 0 ; registered_ids [ i ] ; i + + ) {
regmap_write ( pmcreg , AT91_PMC_PCR ,
pmc_cache . pcr [ registered_ids [ i ] ] |
AT91_PMC_PCR_CMD ) ;
}
2017-12-11 17:55:35 +01:00
for ( i = 0 ; registered_pcks [ i ] ; i + + ) {
num = registered_pcks [ i ] - 1 ;
regmap_write ( pmcreg , AT91_PMC_PCKR ( num ) , pmc_cache . pckr [ num ] ) ;
}
2017-06-08 02:36:47 +02:00
2017-12-11 17:55:33 +01:00
if ( pmc_cache . uckr & AT91_PMC_UPLLEN )
mask | = AT91_PMC_LOCKU ;
while ( ! pmc_ready ( mask ) )
cpu_relax ( ) ;
2017-06-08 02:36:47 +02:00
}
static struct syscore_ops pmc_syscore_ops = {
. suspend = pmc_suspend ,
. resume = pmc_resume ,
} ;
static const struct of_device_id sama5d2_pmc_dt_ids [ ] = {
{ . compatible = " atmel,sama5d2-pmc " } ,
{ /* sentinel */ }
} ;
static int __init pmc_register_ops ( void )
{
struct device_node * np ;
np = of_find_matching_node ( NULL , sama5d2_pmc_dt_ids ) ;
2020-05-03 15:18:08 +03:00
if ( ! np )
return - ENODEV ;
2017-06-08 02:36:47 +02:00
2019-11-28 11:25:31 +01:00
pmcreg = device_node_to_regmap ( np ) ;
2020-05-03 15:18:09 +03:00
of_node_put ( np ) ;
2017-06-08 02:36:47 +02:00
if ( IS_ERR ( pmcreg ) )
return PTR_ERR ( pmcreg ) ;
register_syscore_ops ( & pmc_syscore_ops ) ;
return 0 ;
}
/* This has to happen before arch_initcall because of the tcb_clksrc driver */
postcore_initcall ( pmc_register_ops ) ;
# endif