2013-10-17 18:55:41 +02: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 .
*
*/
# include <linux/clk-provider.h>
# include <linux/clkdev.h>
# include <linux/clk/at91_pmc.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/io.h>
# include "pmc.h"
# define USB_SOURCE_MAX 2
# define SAM9X5_USB_DIV_SHIFT 8
# define SAM9X5_USB_MAX_DIV 0xf
# define RM9200_USB_DIV_SHIFT 28
# define RM9200_USB_DIV_TAB_SIZE 4
struct at91sam9x5_clk_usb {
struct clk_hw hw ;
struct at91_pmc * pmc ;
} ;
# define to_at91sam9x5_clk_usb(hw) \
container_of ( hw , struct at91sam9x5_clk_usb , hw )
struct at91rm9200_clk_usb {
struct clk_hw hw ;
struct at91_pmc * pmc ;
u32 divisors [ 4 ] ;
} ;
# define to_at91rm9200_clk_usb(hw) \
container_of ( hw , struct at91rm9200_clk_usb , hw )
static unsigned long at91sam9x5_clk_usb_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
u32 tmp ;
u8 usbdiv ;
struct at91sam9x5_clk_usb * usb = to_at91sam9x5_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
tmp = pmc_read ( pmc , AT91_PMC_USB ) ;
usbdiv = ( tmp & AT91_PMC_OHCIUSBDIV ) > > SAM9X5_USB_DIV_SHIFT ;
return parent_rate / ( usbdiv + 1 ) ;
}
static long at91sam9x5_clk_usb_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
unsigned long div ;
unsigned long bestrate ;
unsigned long tmp ;
if ( rate > = * parent_rate )
return * parent_rate ;
div = * parent_rate / rate ;
if ( div > = SAM9X5_USB_MAX_DIV )
return * parent_rate / ( SAM9X5_USB_MAX_DIV + 1 ) ;
bestrate = * parent_rate / div ;
tmp = * parent_rate / ( div + 1 ) ;
if ( bestrate - rate > rate - tmp )
bestrate = tmp ;
return bestrate ;
}
static int at91sam9x5_clk_usb_set_parent ( struct clk_hw * hw , u8 index )
{
u32 tmp ;
struct at91sam9x5_clk_usb * usb = to_at91sam9x5_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
if ( index > 1 )
return - EINVAL ;
tmp = pmc_read ( pmc , AT91_PMC_USB ) & ~ AT91_PMC_USBS ;
if ( index )
tmp | = AT91_PMC_USBS ;
pmc_write ( pmc , AT91_PMC_USB , tmp ) ;
return 0 ;
}
static u8 at91sam9x5_clk_usb_get_parent ( struct clk_hw * hw )
{
struct at91sam9x5_clk_usb * usb = to_at91sam9x5_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
return pmc_read ( pmc , AT91_PMC_USB ) & AT91_PMC_USBS ;
}
static int at91sam9x5_clk_usb_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
u32 tmp ;
struct at91sam9x5_clk_usb * usb = to_at91sam9x5_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
unsigned long div = parent_rate / rate ;
if ( parent_rate % rate | | div < 1 | | div > = SAM9X5_USB_MAX_DIV )
return - EINVAL ;
tmp = pmc_read ( pmc , AT91_PMC_USB ) & ~ AT91_PMC_OHCIUSBDIV ;
tmp | = ( div - 1 ) < < SAM9X5_USB_DIV_SHIFT ;
pmc_write ( pmc , AT91_PMC_USB , tmp ) ;
return 0 ;
}
static const struct clk_ops at91sam9x5_usb_ops = {
. recalc_rate = at91sam9x5_clk_usb_recalc_rate ,
. round_rate = at91sam9x5_clk_usb_round_rate ,
. get_parent = at91sam9x5_clk_usb_get_parent ,
. set_parent = at91sam9x5_clk_usb_set_parent ,
. set_rate = at91sam9x5_clk_usb_set_rate ,
} ;
static int at91sam9n12_clk_usb_enable ( struct clk_hw * hw )
{
struct at91sam9x5_clk_usb * usb = to_at91sam9x5_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
pmc_write ( pmc , AT91_PMC_USB ,
pmc_read ( pmc , AT91_PMC_USB ) | AT91_PMC_USBS ) ;
return 0 ;
}
static void at91sam9n12_clk_usb_disable ( struct clk_hw * hw )
{
struct at91sam9x5_clk_usb * usb = to_at91sam9x5_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
pmc_write ( pmc , AT91_PMC_USB ,
pmc_read ( pmc , AT91_PMC_USB ) & ~ AT91_PMC_USBS ) ;
}
static int at91sam9n12_clk_usb_is_enabled ( struct clk_hw * hw )
{
struct at91sam9x5_clk_usb * usb = to_at91sam9x5_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
return ! ! ( pmc_read ( pmc , AT91_PMC_USB ) & AT91_PMC_USBS ) ;
}
static const struct clk_ops at91sam9n12_usb_ops = {
. enable = at91sam9n12_clk_usb_enable ,
. disable = at91sam9n12_clk_usb_disable ,
. is_enabled = at91sam9n12_clk_usb_is_enabled ,
. recalc_rate = at91sam9x5_clk_usb_recalc_rate ,
. round_rate = at91sam9x5_clk_usb_round_rate ,
. set_rate = at91sam9x5_clk_usb_set_rate ,
} ;
static struct clk * __init
at91sam9x5_clk_register_usb ( struct at91_pmc * pmc , const char * name ,
const char * * parent_names , u8 num_parents )
{
struct at91sam9x5_clk_usb * usb ;
struct clk * clk = NULL ;
struct clk_init_data init ;
usb = kzalloc ( sizeof ( * usb ) , GFP_KERNEL ) ;
if ( ! usb )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & at91sam9x5_usb_ops ;
init . parent_names = parent_names ;
init . num_parents = num_parents ;
init . flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE ;
usb - > hw . init = & init ;
usb - > pmc = pmc ;
clk = clk_register ( NULL , & usb - > hw ) ;
if ( IS_ERR ( clk ) )
kfree ( usb ) ;
return clk ;
}
static struct clk * __init
at91sam9n12_clk_register_usb ( struct at91_pmc * pmc , const char * name ,
const char * parent_name )
{
struct at91sam9x5_clk_usb * usb ;
struct clk * clk = NULL ;
struct clk_init_data init ;
usb = kzalloc ( sizeof ( * usb ) , GFP_KERNEL ) ;
if ( ! usb )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & at91sam9n12_usb_ops ;
init . parent_names = & parent_name ;
init . num_parents = 1 ;
init . flags = CLK_SET_RATE_GATE ;
usb - > hw . init = & init ;
usb - > pmc = pmc ;
clk = clk_register ( NULL , & usb - > hw ) ;
if ( IS_ERR ( clk ) )
kfree ( usb ) ;
return clk ;
}
static unsigned long at91rm9200_clk_usb_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct at91rm9200_clk_usb * usb = to_at91rm9200_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
u32 tmp ;
u8 usbdiv ;
tmp = pmc_read ( pmc , AT91_CKGR_PLLBR ) ;
usbdiv = ( tmp & AT91_PMC_USBDIV ) > > RM9200_USB_DIV_SHIFT ;
if ( usb - > divisors [ usbdiv ] )
return parent_rate / usb - > divisors [ usbdiv ] ;
return 0 ;
}
static long at91rm9200_clk_usb_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
struct at91rm9200_clk_usb * usb = to_at91rm9200_clk_usb ( hw ) ;
2014-09-02 09:50:17 +02:00
struct clk * parent = __clk_get_parent ( hw - > clk ) ;
2013-10-17 18:55:41 +02:00
unsigned long bestrate = 0 ;
int bestdiff = - 1 ;
unsigned long tmprate ;
int tmpdiff ;
int i = 0 ;
2014-09-02 09:50:17 +02:00
for ( i = 0 ; i < RM9200_USB_DIV_TAB_SIZE ; i + + ) {
unsigned long tmp_parent_rate ;
2013-10-17 18:55:41 +02:00
if ( ! usb - > divisors [ i ] )
continue ;
2014-09-02 09:50:17 +02:00
tmp_parent_rate = rate * usb - > divisors [ i ] ;
tmp_parent_rate = __clk_round_rate ( parent , tmp_parent_rate ) ;
tmprate = tmp_parent_rate / usb - > divisors [ i ] ;
2013-10-17 18:55:41 +02:00
if ( tmprate < rate )
tmpdiff = rate - tmprate ;
else
tmpdiff = tmprate - rate ;
if ( bestdiff < 0 | | bestdiff > tmpdiff ) {
bestrate = tmprate ;
bestdiff = tmpdiff ;
2014-09-02 09:50:17 +02:00
* parent_rate = tmp_parent_rate ;
2013-10-17 18:55:41 +02:00
}
if ( ! bestdiff )
break ;
}
return bestrate ;
}
static int at91rm9200_clk_usb_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
u32 tmp ;
int i ;
struct at91rm9200_clk_usb * usb = to_at91rm9200_clk_usb ( hw ) ;
struct at91_pmc * pmc = usb - > pmc ;
unsigned long div = parent_rate / rate ;
if ( parent_rate % rate )
return - EINVAL ;
for ( i = 0 ; i < RM9200_USB_DIV_TAB_SIZE ; i + + ) {
if ( usb - > divisors [ i ] = = div ) {
tmp = pmc_read ( pmc , AT91_CKGR_PLLBR ) &
~ AT91_PMC_USBDIV ;
tmp | = i < < RM9200_USB_DIV_SHIFT ;
pmc_write ( pmc , AT91_CKGR_PLLBR , tmp ) ;
return 0 ;
}
}
return - EINVAL ;
}
static const struct clk_ops at91rm9200_usb_ops = {
. recalc_rate = at91rm9200_clk_usb_recalc_rate ,
. round_rate = at91rm9200_clk_usb_round_rate ,
. set_rate = at91rm9200_clk_usb_set_rate ,
} ;
static struct clk * __init
at91rm9200_clk_register_usb ( struct at91_pmc * pmc , const char * name ,
const char * parent_name , const u32 * divisors )
{
struct at91rm9200_clk_usb * usb ;
struct clk * clk = NULL ;
struct clk_init_data init ;
usb = kzalloc ( sizeof ( * usb ) , GFP_KERNEL ) ;
if ( ! usb )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & at91rm9200_usb_ops ;
init . parent_names = & parent_name ;
init . num_parents = 1 ;
2014-09-02 09:50:17 +02:00
init . flags = CLK_SET_RATE_PARENT ;
2013-10-17 18:55:41 +02:00
usb - > hw . init = & init ;
usb - > pmc = pmc ;
memcpy ( usb - > divisors , divisors , sizeof ( usb - > divisors ) ) ;
clk = clk_register ( NULL , & usb - > hw ) ;
if ( IS_ERR ( clk ) )
kfree ( usb ) ;
return clk ;
}
void __init of_at91sam9x5_clk_usb_setup ( struct device_node * np ,
struct at91_pmc * pmc )
{
struct clk * clk ;
int i ;
int num_parents ;
const char * parent_names [ USB_SOURCE_MAX ] ;
const char * name = np - > name ;
num_parents = of_count_phandle_with_args ( np , " clocks " , " #clock-cells " ) ;
if ( num_parents < = 0 | | num_parents > USB_SOURCE_MAX )
return ;
for ( i = 0 ; i < num_parents ; i + + ) {
parent_names [ i ] = of_clk_get_parent_name ( np , i ) ;
if ( ! parent_names [ i ] )
return ;
}
of_property_read_string ( np , " clock-output-names " , & name ) ;
clk = at91sam9x5_clk_register_usb ( pmc , name , parent_names , num_parents ) ;
if ( IS_ERR ( clk ) )
return ;
of_clk_add_provider ( np , of_clk_src_simple_get , clk ) ;
}
void __init of_at91sam9n12_clk_usb_setup ( struct device_node * np ,
struct at91_pmc * pmc )
{
struct clk * clk ;
const char * parent_name ;
const char * name = np - > name ;
parent_name = of_clk_get_parent_name ( np , 0 ) ;
if ( ! parent_name )
return ;
of_property_read_string ( np , " clock-output-names " , & name ) ;
clk = at91sam9n12_clk_register_usb ( pmc , name , parent_name ) ;
if ( IS_ERR ( clk ) )
return ;
of_clk_add_provider ( np , of_clk_src_simple_get , clk ) ;
}
void __init of_at91rm9200_clk_usb_setup ( struct device_node * np ,
struct at91_pmc * pmc )
{
struct clk * clk ;
const char * parent_name ;
const char * name = np - > name ;
u32 divisors [ 4 ] = { 0 , 0 , 0 , 0 } ;
parent_name = of_clk_get_parent_name ( np , 0 ) ;
if ( ! parent_name )
return ;
of_property_read_u32_array ( np , " atmel,clk-divisors " , divisors , 4 ) ;
if ( ! divisors [ 0 ] )
return ;
of_property_read_string ( np , " clock-output-names " , & name ) ;
clk = at91rm9200_clk_register_usb ( pmc , name , parent_name , divisors ) ;
if ( IS_ERR ( clk ) )
return ;
of_clk_add_provider ( np , of_clk_src_simple_get , clk ) ;
}