2019-05-28 19:57:06 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2013-03-11 19:22:29 +04:00
/*
* AXI clkgen driver
*
* Copyright 2012 - 2013 Analog Devices Inc .
* Author : Lars - Peter Clausen < lars @ metafoo . de >
*/
# include <linux/platform_device.h>
# include <linux/clk-provider.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/module.h>
# include <linux/err.h>
2014-02-17 13:31:53 +04:00
# define AXI_CLKGEN_V2_REG_RESET 0x40
2015-11-30 19:54:56 +03:00
# define AXI_CLKGEN_V2_REG_CLKSEL 0x44
2014-02-17 13:31:53 +04:00
# define AXI_CLKGEN_V2_REG_DRP_CNTRL 0x70
# define AXI_CLKGEN_V2_REG_DRP_STATUS 0x74
# define AXI_CLKGEN_V2_RESET_MMCM_ENABLE BIT(1)
# define AXI_CLKGEN_V2_RESET_ENABLE BIT(0)
# define AXI_CLKGEN_V2_DRP_CNTRL_SEL BIT(29)
# define AXI_CLKGEN_V2_DRP_CNTRL_READ BIT(28)
# define AXI_CLKGEN_V2_DRP_STATUS_BUSY BIT(16)
2020-10-01 11:59:47 +03:00
# define MMCM_REG_CLKOUT5_2 0x07
2014-02-17 13:31:53 +04:00
# define MMCM_REG_CLKOUT0_1 0x08
# define MMCM_REG_CLKOUT0_2 0x09
2020-10-01 11:59:47 +03:00
# define MMCM_REG_CLKOUT6_2 0x13
2014-02-17 13:31:53 +04:00
# define MMCM_REG_CLK_FB1 0x14
# define MMCM_REG_CLK_FB2 0x15
# define MMCM_REG_CLK_DIV 0x16
# define MMCM_REG_LOCK1 0x18
# define MMCM_REG_LOCK2 0x19
# define MMCM_REG_LOCK3 0x1a
# define MMCM_REG_FILTER1 0x4e
# define MMCM_REG_FILTER2 0x4f
2017-09-05 12:32:40 +03:00
# define MMCM_CLKOUT_NOCOUNT BIT(6)
2020-10-01 11:59:47 +03:00
# define MMCM_CLK_DIV_DIVIDE BIT(11)
2017-09-05 12:32:40 +03:00
# define MMCM_CLK_DIV_NOCOUNT BIT(12)
2013-03-11 19:22:29 +04:00
struct axi_clkgen {
void __iomem * base ;
struct clk_hw clk_hw ;
} ;
static uint32_t axi_clkgen_lookup_filter ( unsigned int m )
{
switch ( m ) {
case 0 :
return 0x01001990 ;
case 1 :
return 0x01001190 ;
case 2 :
return 0x01009890 ;
case 3 :
return 0x01001890 ;
case 4 :
return 0x01008890 ;
case 5 . . . 8 :
return 0x01009090 ;
case 9 . . . 11 :
return 0x01000890 ;
case 12 :
return 0x08009090 ;
case 13 . . . 22 :
return 0x01001090 ;
case 23 . . . 36 :
return 0x01008090 ;
case 37 . . . 46 :
return 0x08001090 ;
default :
return 0x08008090 ;
}
}
static const uint32_t axi_clkgen_lock_table [ ] = {
0x060603e8 , 0x060603e8 , 0x080803e8 , 0x0b0b03e8 ,
0x0e0e03e8 , 0x111103e8 , 0x131303e8 , 0x161603e8 ,
0x191903e8 , 0x1c1c03e8 , 0x1f1f0384 , 0x1f1f0339 ,
0x1f1f02ee , 0x1f1f02bc , 0x1f1f028a , 0x1f1f0271 ,
0x1f1f023f , 0x1f1f0226 , 0x1f1f020d , 0x1f1f01f4 ,
0x1f1f01db , 0x1f1f01c2 , 0x1f1f01a9 , 0x1f1f0190 ,
0x1f1f0190 , 0x1f1f0177 , 0x1f1f015e , 0x1f1f015e ,
0x1f1f0145 , 0x1f1f0145 , 0x1f1f012c , 0x1f1f012c ,
0x1f1f012c , 0x1f1f0113 , 0x1f1f0113 , 0x1f1f0113 ,
} ;
static uint32_t axi_clkgen_lookup_lock ( unsigned int m )
{
if ( m < ARRAY_SIZE ( axi_clkgen_lock_table ) )
return axi_clkgen_lock_table [ m ] ;
return 0x1f1f00fa ;
}
static const unsigned int fpfd_min = 10000 ;
static const unsigned int fpfd_max = 300000 ;
static const unsigned int fvco_min = 600000 ;
static const unsigned int fvco_max = 1200000 ;
static void axi_clkgen_calc_params ( unsigned long fin , unsigned long fout ,
unsigned int * best_d , unsigned int * best_m , unsigned int * best_dout )
{
unsigned long d , d_min , d_max , _d_min , _d_max ;
unsigned long m , m_min , m_max ;
unsigned long f , dout , best_f , fvco ;
2020-10-01 11:59:47 +03:00
unsigned long fract_shift = 0 ;
unsigned long fvco_min_fract , fvco_max_fract ;
2013-03-11 19:22:29 +04:00
fin / = 1000 ;
fout / = 1000 ;
best_f = ULONG_MAX ;
* best_d = 0 ;
* best_m = 0 ;
* best_dout = 0 ;
d_min = max_t ( unsigned long , DIV_ROUND_UP ( fin , fpfd_max ) , 1 ) ;
d_max = min_t ( unsigned long , fin / fpfd_min , 80 ) ;
2020-10-01 11:59:47 +03:00
again :
fvco_min_fract = fvco_min < < fract_shift ;
fvco_max_fract = fvco_max < < fract_shift ;
m_min = max_t ( unsigned long , DIV_ROUND_UP ( fvco_min_fract , fin ) * d_min , 1 ) ;
m_max = min_t ( unsigned long , fvco_max_fract * d_max / fin , 64 < < fract_shift ) ;
2013-03-11 19:22:29 +04:00
for ( m = m_min ; m < = m_max ; m + + ) {
2020-10-01 11:59:47 +03:00
_d_min = max ( d_min , DIV_ROUND_UP ( fin * m , fvco_max_fract ) ) ;
_d_max = min ( d_max , fin * m / fvco_min_fract ) ;
2013-03-11 19:22:29 +04:00
for ( d = _d_min ; d < = _d_max ; d + + ) {
fvco = fin * m / d ;
dout = DIV_ROUND_CLOSEST ( fvco , fout ) ;
2020-10-01 11:59:47 +03:00
dout = clamp_t ( unsigned long , dout , 1 , 128 < < fract_shift ) ;
2013-03-11 19:22:29 +04:00
f = fvco / dout ;
if ( abs ( f - fout ) < abs ( best_f - fout ) ) {
best_f = f ;
* best_d = d ;
2020-10-01 11:59:47 +03:00
* best_m = m < < ( 3 - fract_shift ) ;
* best_dout = dout < < ( 3 - fract_shift ) ;
2013-03-11 19:22:29 +04:00
if ( best_f = = fout )
return ;
}
}
}
2020-10-01 11:59:47 +03:00
/* Lets see if we find a better setting in fractional mode */
if ( fract_shift = = 0 ) {
fract_shift = 3 ;
goto again ;
}
2013-03-11 19:22:29 +04:00
}
2020-10-01 11:59:47 +03:00
struct axi_clkgen_div_params {
unsigned int low ;
unsigned int high ;
unsigned int edge ;
unsigned int nocount ;
unsigned int frac_en ;
unsigned int frac ;
unsigned int frac_wf_f ;
unsigned int frac_wf_r ;
unsigned int frac_phase ;
} ;
static void axi_clkgen_calc_clk_params ( unsigned int divider ,
unsigned int frac_divider , struct axi_clkgen_div_params * params )
2013-03-11 19:22:29 +04:00
{
2020-10-01 11:59:47 +03:00
memset ( params , 0x0 , sizeof ( * params ) ) ;
if ( divider = = 1 ) {
params - > nocount = 1 ;
return ;
}
if ( frac_divider = = 0 ) {
params - > high = divider / 2 ;
params - > edge = divider % 2 ;
params - > low = divider - params - > high ;
} else {
params - > frac_en = 1 ;
params - > frac = frac_divider ;
params - > high = divider / 2 ;
params - > edge = divider % 2 ;
params - > low = params - > high ;
if ( params - > edge = = 0 ) {
params - > high - - ;
params - > frac_wf_r = 1 ;
}
if ( params - > edge = = 0 | | frac_divider = = 1 )
params - > low - - ;
if ( ( ( params - > edge = = 0 ) ^ ( frac_divider = = 1 ) ) | |
( divider = = 2 & & frac_divider = = 1 ) )
params - > frac_wf_f = 1 ;
params - > frac_phase = params - > edge * 4 + frac_divider / 2 ;
}
2013-03-11 19:22:29 +04:00
}
static void axi_clkgen_write ( struct axi_clkgen * axi_clkgen ,
unsigned int reg , unsigned int val )
{
writel ( val , axi_clkgen - > base + reg ) ;
}
static void axi_clkgen_read ( struct axi_clkgen * axi_clkgen ,
unsigned int reg , unsigned int * val )
{
* val = readl ( axi_clkgen - > base + reg ) ;
}
2014-02-17 13:31:53 +04:00
static int axi_clkgen_wait_non_busy ( struct axi_clkgen * axi_clkgen )
{
unsigned int timeout = 10000 ;
unsigned int val ;
do {
axi_clkgen_read ( axi_clkgen , AXI_CLKGEN_V2_REG_DRP_STATUS , & val ) ;
} while ( ( val & AXI_CLKGEN_V2_DRP_STATUS_BUSY ) & & - - timeout ) ;
if ( val & AXI_CLKGEN_V2_DRP_STATUS_BUSY )
return - EIO ;
return val & 0xffff ;
}
2015-11-30 19:54:55 +03:00
static int axi_clkgen_mmcm_read ( struct axi_clkgen * axi_clkgen ,
2014-02-17 13:31:53 +04:00
unsigned int reg , unsigned int * val )
{
unsigned int reg_val ;
int ret ;
ret = axi_clkgen_wait_non_busy ( axi_clkgen ) ;
if ( ret < 0 )
return ret ;
reg_val = AXI_CLKGEN_V2_DRP_CNTRL_SEL | AXI_CLKGEN_V2_DRP_CNTRL_READ ;
reg_val | = ( reg < < 16 ) ;
axi_clkgen_write ( axi_clkgen , AXI_CLKGEN_V2_REG_DRP_CNTRL , reg_val ) ;
ret = axi_clkgen_wait_non_busy ( axi_clkgen ) ;
if ( ret < 0 )
return ret ;
* val = ret ;
return 0 ;
}
2015-11-30 19:54:55 +03:00
static int axi_clkgen_mmcm_write ( struct axi_clkgen * axi_clkgen ,
2014-02-17 13:31:53 +04:00
unsigned int reg , unsigned int val , unsigned int mask )
{
unsigned int reg_val = 0 ;
int ret ;
ret = axi_clkgen_wait_non_busy ( axi_clkgen ) ;
if ( ret < 0 )
return ret ;
if ( mask ! = 0xffff ) {
2015-11-30 19:54:55 +03:00
axi_clkgen_mmcm_read ( axi_clkgen , reg , & reg_val ) ;
2014-02-17 13:31:53 +04:00
reg_val & = ~ mask ;
}
reg_val | = AXI_CLKGEN_V2_DRP_CNTRL_SEL | ( reg < < 16 ) | ( val & mask ) ;
axi_clkgen_write ( axi_clkgen , AXI_CLKGEN_V2_REG_DRP_CNTRL , reg_val ) ;
return 0 ;
}
2015-11-30 19:54:55 +03:00
static void axi_clkgen_mmcm_enable ( struct axi_clkgen * axi_clkgen ,
2014-02-17 13:31:53 +04:00
bool enable )
{
unsigned int val = AXI_CLKGEN_V2_RESET_ENABLE ;
if ( enable )
val | = AXI_CLKGEN_V2_RESET_MMCM_ENABLE ;
axi_clkgen_write ( axi_clkgen , AXI_CLKGEN_V2_REG_RESET , val ) ;
}
2013-03-11 19:22:29 +04:00
static struct axi_clkgen * clk_hw_to_axi_clkgen ( struct clk_hw * clk_hw )
{
return container_of ( clk_hw , struct axi_clkgen , clk_hw ) ;
}
2020-10-01 11:59:47 +03:00
static void axi_clkgen_set_div ( struct axi_clkgen * axi_clkgen ,
unsigned int reg1 , unsigned int reg2 , unsigned int reg3 ,
struct axi_clkgen_div_params * params )
{
axi_clkgen_mmcm_write ( axi_clkgen , reg1 ,
( params - > high < < 6 ) | params - > low , 0xefff ) ;
axi_clkgen_mmcm_write ( axi_clkgen , reg2 ,
( params - > frac < < 12 ) | ( params - > frac_en < < 11 ) |
( params - > frac_wf_r < < 10 ) | ( params - > edge < < 7 ) |
( params - > nocount < < 6 ) , 0x7fff ) ;
if ( reg3 ! = 0 ) {
axi_clkgen_mmcm_write ( axi_clkgen , reg3 ,
( params - > frac_phase < < 11 ) | ( params - > frac_wf_f < < 10 ) , 0x3c00 ) ;
}
}
2013-03-11 19:22:29 +04:00
static int axi_clkgen_set_rate ( struct clk_hw * clk_hw ,
unsigned long rate , unsigned long parent_rate )
{
struct axi_clkgen * axi_clkgen = clk_hw_to_axi_clkgen ( clk_hw ) ;
unsigned int d , m , dout ;
2020-10-01 11:59:47 +03:00
struct axi_clkgen_div_params params ;
2013-03-11 19:22:29 +04:00
uint32_t filter ;
uint32_t lock ;
if ( parent_rate = = 0 | | rate = = 0 )
return - EINVAL ;
axi_clkgen_calc_params ( parent_rate , rate , & d , & m , & dout ) ;
if ( d = = 0 | | dout = = 0 | | m = = 0 )
return - EINVAL ;
filter = axi_clkgen_lookup_filter ( m - 1 ) ;
lock = axi_clkgen_lookup_lock ( m - 1 ) ;
2020-10-01 11:59:47 +03:00
axi_clkgen_calc_clk_params ( dout > > 3 , dout & 0x7 , & params ) ;
axi_clkgen_set_div ( axi_clkgen , MMCM_REG_CLKOUT0_1 , MMCM_REG_CLKOUT0_2 ,
MMCM_REG_CLKOUT5_2 , & params ) ;
2013-03-11 19:22:29 +04:00
2020-10-01 11:59:47 +03:00
axi_clkgen_calc_clk_params ( d , 0 , & params ) ;
2014-02-17 13:31:53 +04:00
axi_clkgen_mmcm_write ( axi_clkgen , MMCM_REG_CLK_DIV ,
2020-10-01 11:59:47 +03:00
( params . edge < < 13 ) | ( params . nocount < < 12 ) |
( params . high < < 6 ) | params . low , 0x3fff ) ;
2013-03-11 19:22:29 +04:00
2020-10-01 11:59:47 +03:00
axi_clkgen_calc_clk_params ( m > > 3 , m & 0x7 , & params ) ;
axi_clkgen_set_div ( axi_clkgen , MMCM_REG_CLK_FB1 , MMCM_REG_CLK_FB2 ,
MMCM_REG_CLKOUT6_2 , & params ) ;
2014-02-17 13:31:53 +04:00
axi_clkgen_mmcm_write ( axi_clkgen , MMCM_REG_LOCK1 , lock & 0x3ff , 0x3ff ) ;
axi_clkgen_mmcm_write ( axi_clkgen , MMCM_REG_LOCK2 ,
( ( ( lock > > 16 ) & 0x1f ) < < 10 ) | 0x1 , 0x7fff ) ;
axi_clkgen_mmcm_write ( axi_clkgen , MMCM_REG_LOCK3 ,
( ( ( lock > > 24 ) & 0x1f ) < < 10 ) | 0x3e9 , 0x7fff ) ;
axi_clkgen_mmcm_write ( axi_clkgen , MMCM_REG_FILTER1 , filter > > 16 , 0x9900 ) ;
axi_clkgen_mmcm_write ( axi_clkgen , MMCM_REG_FILTER2 , filter , 0x9900 ) ;
2013-03-11 19:22:29 +04:00
return 0 ;
}
static long axi_clkgen_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
unsigned int d , m , dout ;
2017-09-05 12:32:41 +03:00
unsigned long long tmp ;
2013-03-11 19:22:29 +04:00
axi_clkgen_calc_params ( * parent_rate , rate , & d , & m , & dout ) ;
if ( d = = 0 | | dout = = 0 | | m = = 0 )
return - EINVAL ;
2017-09-05 12:32:41 +03:00
tmp = ( unsigned long long ) * parent_rate * m ;
tmp = DIV_ROUND_CLOSEST_ULL ( tmp , dout * d ) ;
return min_t ( unsigned long long , tmp , LONG_MAX ) ;
2013-03-11 19:22:29 +04:00
}
2020-10-01 11:59:47 +03:00
static unsigned int axi_clkgen_get_div ( struct axi_clkgen * axi_clkgen ,
unsigned int reg1 , unsigned int reg2 )
{
unsigned int val1 , val2 ;
unsigned int div ;
axi_clkgen_mmcm_read ( axi_clkgen , reg2 , & val2 ) ;
if ( val2 & MMCM_CLKOUT_NOCOUNT )
return 8 ;
axi_clkgen_mmcm_read ( axi_clkgen , reg1 , & val1 ) ;
div = ( val1 & 0x3f ) + ( ( val1 > > 6 ) & 0x3f ) ;
div < < = 3 ;
if ( val2 & MMCM_CLK_DIV_DIVIDE ) {
if ( ( val2 & BIT ( 7 ) ) & & ( val2 & 0x7000 ) ! = 0x1000 )
div + = 8 ;
else
div + = 16 ;
div + = ( val2 > > 12 ) & 0x7 ;
}
return div ;
}
2013-03-11 19:22:29 +04:00
static unsigned long axi_clkgen_recalc_rate ( struct clk_hw * clk_hw ,
unsigned long parent_rate )
{
struct axi_clkgen * axi_clkgen = clk_hw_to_axi_clkgen ( clk_hw ) ;
unsigned int d , m , dout ;
unsigned long long tmp ;
2020-10-01 11:59:47 +03:00
unsigned int val ;
2013-03-11 19:22:29 +04:00
2020-10-01 11:59:47 +03:00
dout = axi_clkgen_get_div ( axi_clkgen , MMCM_REG_CLKOUT0_1 ,
MMCM_REG_CLKOUT0_2 ) ;
m = axi_clkgen_get_div ( axi_clkgen , MMCM_REG_CLK_FB1 ,
MMCM_REG_CLK_FB2 ) ;
2017-09-05 12:32:40 +03:00
2020-10-01 11:59:47 +03:00
axi_clkgen_mmcm_read ( axi_clkgen , MMCM_REG_CLK_DIV , & val ) ;
if ( val & MMCM_CLK_DIV_NOCOUNT )
2017-09-05 12:32:40 +03:00
d = 1 ;
else
2020-10-01 11:59:47 +03:00
d = ( val & 0x3f ) + ( ( val > > 6 ) & 0x3f ) ;
2013-03-11 19:22:29 +04:00
if ( d = = 0 | | dout = = 0 )
return 0 ;
2017-09-05 12:32:41 +03:00
tmp = ( unsigned long long ) parent_rate * m ;
tmp = DIV_ROUND_CLOSEST_ULL ( tmp , dout * d ) ;
2013-03-11 19:22:29 +04:00
2016-01-30 04:09:01 +03:00
return min_t ( unsigned long long , tmp , ULONG_MAX ) ;
2013-03-11 19:22:29 +04:00
}
2014-02-17 13:31:53 +04:00
static int axi_clkgen_enable ( struct clk_hw * clk_hw )
{
struct axi_clkgen * axi_clkgen = clk_hw_to_axi_clkgen ( clk_hw ) ;
axi_clkgen_mmcm_enable ( axi_clkgen , true ) ;
return 0 ;
}
static void axi_clkgen_disable ( struct clk_hw * clk_hw )
{
struct axi_clkgen * axi_clkgen = clk_hw_to_axi_clkgen ( clk_hw ) ;
axi_clkgen_mmcm_enable ( axi_clkgen , false ) ;
}
2015-11-30 19:54:56 +03:00
static int axi_clkgen_set_parent ( struct clk_hw * clk_hw , u8 index )
{
struct axi_clkgen * axi_clkgen = clk_hw_to_axi_clkgen ( clk_hw ) ;
axi_clkgen_write ( axi_clkgen , AXI_CLKGEN_V2_REG_CLKSEL , index ) ;
return 0 ;
}
static u8 axi_clkgen_get_parent ( struct clk_hw * clk_hw )
{
struct axi_clkgen * axi_clkgen = clk_hw_to_axi_clkgen ( clk_hw ) ;
unsigned int parent ;
axi_clkgen_read ( axi_clkgen , AXI_CLKGEN_V2_REG_CLKSEL , & parent ) ;
return parent ;
}
2013-03-11 19:22:29 +04:00
static const struct clk_ops axi_clkgen_ops = {
. recalc_rate = axi_clkgen_recalc_rate ,
. round_rate = axi_clkgen_round_rate ,
. set_rate = axi_clkgen_set_rate ,
2014-02-17 13:31:53 +04:00
. enable = axi_clkgen_enable ,
. disable = axi_clkgen_disable ,
2015-11-30 19:54:56 +03:00
. set_parent = axi_clkgen_set_parent ,
. get_parent = axi_clkgen_get_parent ,
2013-03-11 19:22:29 +04:00
} ;
2014-02-17 13:31:53 +04:00
static const struct of_device_id axi_clkgen_ids [ ] = {
{
. compatible = " adi,axi-clkgen-2.00.a " ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , axi_clkgen_ids ) ;
2013-03-11 19:22:29 +04:00
static int axi_clkgen_probe ( struct platform_device * pdev )
{
2014-02-17 13:31:53 +04:00
const struct of_device_id * id ;
2013-03-11 19:22:29 +04:00
struct axi_clkgen * axi_clkgen ;
struct clk_init_data init ;
2015-11-30 19:54:56 +03:00
const char * parent_names [ 2 ] ;
2013-03-11 19:22:29 +04:00
const char * clk_name ;
struct resource * mem ;
2015-11-30 19:54:56 +03:00
unsigned int i ;
2016-06-02 02:15:08 +03:00
int ret ;
2013-03-11 19:22:29 +04:00
2014-02-17 13:31:53 +04:00
if ( ! pdev - > dev . of_node )
return - ENODEV ;
id = of_match_node ( axi_clkgen_ids , pdev - > dev . of_node ) ;
if ( ! id )
return - ENODEV ;
2013-03-11 19:22:29 +04:00
axi_clkgen = devm_kzalloc ( & pdev - > dev , sizeof ( * axi_clkgen ) , GFP_KERNEL ) ;
if ( ! axi_clkgen )
return - ENOMEM ;
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
axi_clkgen - > base = devm_ioremap_resource ( & pdev - > dev , mem ) ;
if ( IS_ERR ( axi_clkgen - > base ) )
return PTR_ERR ( axi_clkgen - > base ) ;
2015-11-30 19:54:56 +03:00
init . num_parents = of_clk_get_parent_count ( pdev - > dev . of_node ) ;
if ( init . num_parents < 1 | | init . num_parents > 2 )
2013-03-11 19:22:29 +04:00
return - EINVAL ;
2015-11-30 19:54:56 +03:00
for ( i = 0 ; i < init . num_parents ; i + + ) {
parent_names [ i ] = of_clk_get_parent_name ( pdev - > dev . of_node , i ) ;
if ( ! parent_names [ i ] )
return - EINVAL ;
}
2013-03-11 19:22:29 +04:00
clk_name = pdev - > dev . of_node - > name ;
of_property_read_string ( pdev - > dev . of_node , " clock-output-names " ,
& clk_name ) ;
init . name = clk_name ;
init . ops = & axi_clkgen_ops ;
2015-11-30 19:54:56 +03:00
init . flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE ;
init . parent_names = parent_names ;
2013-03-11 19:22:29 +04:00
2014-02-17 13:31:53 +04:00
axi_clkgen_mmcm_enable ( axi_clkgen , false ) ;
2013-03-11 19:22:29 +04:00
axi_clkgen - > clk_hw . init = & init ;
2016-06-02 02:15:08 +03:00
ret = devm_clk_hw_register ( & pdev - > dev , & axi_clkgen - > clk_hw ) ;
if ( ret )
return ret ;
2013-03-11 19:22:29 +04:00
2016-06-02 02:15:08 +03:00
return of_clk_add_hw_provider ( pdev - > dev . of_node , of_clk_hw_simple_get ,
& axi_clkgen - > clk_hw ) ;
2013-03-11 19:22:29 +04:00
}
static int axi_clkgen_remove ( struct platform_device * pdev )
{
of_clk_del_provider ( pdev - > dev . of_node ) ;
return 0 ;
}
static struct platform_driver axi_clkgen_driver = {
. driver = {
. name = " adi-axi-clkgen " ,
. of_match_table = axi_clkgen_ids ,
} ,
. probe = axi_clkgen_probe ,
. remove = axi_clkgen_remove ,
} ;
module_platform_driver ( axi_clkgen_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Lars-Peter Clausen <lars@metafoo.de> " ) ;
MODULE_DESCRIPTION ( " Driver for the Analog Devices' AXI clkgen pcore clock generator " ) ;