2016-05-02 10:39:05 +01:00
/*
* Synopsys AXS10X SDP I2S PLL clock driver
*
* Copyright ( C ) 2016 Synopsys
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/platform_device.h>
# include <linux/module.h>
# include <linux/clk-provider.h>
# include <linux/err.h>
# include <linux/device.h>
2019-04-18 15:20:22 -07:00
# include <linux/io.h>
2016-05-02 10:39:05 +01:00
# include <linux/of_address.h>
# include <linux/slab.h>
# include <linux/of.h>
/* PLL registers addresses */
# define PLL_IDIV_REG 0x0
# define PLL_FBDIV_REG 0x4
# define PLL_ODIV0_REG 0x8
# define PLL_ODIV1_REG 0xC
struct i2s_pll_cfg {
unsigned int rate ;
unsigned int idiv ;
unsigned int fbdiv ;
unsigned int odiv0 ;
unsigned int odiv1 ;
} ;
static const struct i2s_pll_cfg i2s_pll_cfg_27m [ ] = {
/* 27 Mhz */
{ 1024000 , 0x104 , 0x451 , 0x10E38 , 0x2000 } ,
{ 1411200 , 0x104 , 0x596 , 0x10D35 , 0x2000 } ,
{ 1536000 , 0x208 , 0xA28 , 0x10B2C , 0x2000 } ,
{ 2048000 , 0x82 , 0x451 , 0x10E38 , 0x2000 } ,
{ 2822400 , 0x82 , 0x596 , 0x10D35 , 0x2000 } ,
{ 3072000 , 0x104 , 0xA28 , 0x10B2C , 0x2000 } ,
{ 2116800 , 0x82 , 0x3CF , 0x10C30 , 0x2000 } ,
{ 2304000 , 0x104 , 0x79E , 0x10B2C , 0x2000 } ,
{ 0 , 0 , 0 , 0 , 0 } ,
} ;
static const struct i2s_pll_cfg i2s_pll_cfg_28m [ ] = {
/* 28.224 Mhz */
{ 1024000 , 0x82 , 0x105 , 0x107DF , 0x2000 } ,
{ 1411200 , 0x28A , 0x1 , 0x10001 , 0x2000 } ,
{ 1536000 , 0xA28 , 0x187 , 0x10042 , 0x2000 } ,
{ 2048000 , 0x41 , 0x105 , 0x107DF , 0x2000 } ,
{ 2822400 , 0x145 , 0x1 , 0x10001 , 0x2000 } ,
{ 3072000 , 0x514 , 0x187 , 0x10042 , 0x2000 } ,
{ 2116800 , 0x514 , 0x42 , 0x10001 , 0x2000 } ,
{ 2304000 , 0x619 , 0x82 , 0x10001 , 0x2000 } ,
{ 0 , 0 , 0 , 0 , 0 } ,
} ;
struct i2s_pll_clk {
void __iomem * base ;
struct clk_hw hw ;
struct device * dev ;
} ;
static inline void i2s_pll_write ( struct i2s_pll_clk * clk , unsigned int reg ,
unsigned int val )
{
writel_relaxed ( val , clk - > base + reg ) ;
}
static inline unsigned int i2s_pll_read ( struct i2s_pll_clk * clk ,
unsigned int reg )
{
return readl_relaxed ( clk - > base + reg ) ;
}
static inline struct i2s_pll_clk * to_i2s_pll_clk ( struct clk_hw * hw )
{
return container_of ( hw , struct i2s_pll_clk , hw ) ;
}
static inline unsigned int i2s_pll_get_value ( unsigned int val )
{
return ( val & 0x3F ) + ( ( val > > 6 ) & 0x3F ) ;
}
static const struct i2s_pll_cfg * i2s_pll_get_cfg ( unsigned long prate )
{
switch ( prate ) {
case 27000000 :
return i2s_pll_cfg_27m ;
case 28224000 :
return i2s_pll_cfg_28m ;
default :
return NULL ;
}
}
static unsigned long i2s_pll_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct i2s_pll_clk * clk = to_i2s_pll_clk ( hw ) ;
unsigned int idiv , fbdiv , odiv ;
idiv = i2s_pll_get_value ( i2s_pll_read ( clk , PLL_IDIV_REG ) ) ;
fbdiv = i2s_pll_get_value ( i2s_pll_read ( clk , PLL_FBDIV_REG ) ) ;
odiv = i2s_pll_get_value ( i2s_pll_read ( clk , PLL_ODIV0_REG ) ) ;
return ( ( parent_rate / idiv ) * fbdiv ) / odiv ;
}
static long i2s_pll_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * prate )
{
struct i2s_pll_clk * clk = to_i2s_pll_clk ( hw ) ;
const struct i2s_pll_cfg * pll_cfg = i2s_pll_get_cfg ( * prate ) ;
int i ;
if ( ! pll_cfg ) {
dev_err ( clk - > dev , " invalid parent rate=%ld \n " , * prate ) ;
return - EINVAL ;
}
for ( i = 0 ; pll_cfg [ i ] . rate ! = 0 ; i + + )
if ( pll_cfg [ i ] . rate = = rate )
return rate ;
return - EINVAL ;
}
static int i2s_pll_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct i2s_pll_clk * clk = to_i2s_pll_clk ( hw ) ;
const struct i2s_pll_cfg * pll_cfg = i2s_pll_get_cfg ( parent_rate ) ;
int i ;
if ( ! pll_cfg ) {
dev_err ( clk - > dev , " invalid parent rate=%ld \n " , parent_rate ) ;
return - EINVAL ;
}
for ( i = 0 ; pll_cfg [ i ] . rate ! = 0 ; i + + ) {
if ( pll_cfg [ i ] . rate = = rate ) {
i2s_pll_write ( clk , PLL_IDIV_REG , pll_cfg [ i ] . idiv ) ;
i2s_pll_write ( clk , PLL_FBDIV_REG , pll_cfg [ i ] . fbdiv ) ;
i2s_pll_write ( clk , PLL_ODIV0_REG , pll_cfg [ i ] . odiv0 ) ;
i2s_pll_write ( clk , PLL_ODIV1_REG , pll_cfg [ i ] . odiv1 ) ;
return 0 ;
}
}
dev_err ( clk - > dev , " invalid rate=%ld, parent_rate=%ld \n " , rate ,
parent_rate ) ;
return - EINVAL ;
}
static const struct clk_ops i2s_pll_ops = {
. recalc_rate = i2s_pll_recalc_rate ,
. round_rate = i2s_pll_round_rate ,
. set_rate = i2s_pll_set_rate ,
} ;
static int i2s_pll_clk_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * node = dev - > of_node ;
const char * clk_name ;
const char * parent_name ;
struct clk * clk ;
struct i2s_pll_clk * pll_clk ;
struct clk_init_data init ;
pll_clk = devm_kzalloc ( dev , sizeof ( * pll_clk ) , GFP_KERNEL ) ;
if ( ! pll_clk )
return - ENOMEM ;
2019-10-15 22:22:59 +08:00
pll_clk - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2016-05-02 10:39:05 +01:00
if ( IS_ERR ( pll_clk - > base ) )
return PTR_ERR ( pll_clk - > base ) ;
2016-12-12 11:08:55 +00:00
memset ( & init , 0 , sizeof ( init ) ) ;
2016-05-02 10:39:05 +01:00
clk_name = node - > name ;
init . name = clk_name ;
init . ops = & i2s_pll_ops ;
parent_name = of_clk_get_parent_name ( node , 0 ) ;
init . parent_names = & parent_name ;
init . num_parents = 1 ;
pll_clk - > hw . init = & init ;
pll_clk - > dev = dev ;
clk = devm_clk_register ( dev , & pll_clk - > hw ) ;
if ( IS_ERR ( clk ) ) {
dev_err ( dev , " failed to register %s clock (%ld) \n " ,
clk_name , PTR_ERR ( clk ) ) ;
return PTR_ERR ( clk ) ;
}
return of_clk_add_provider ( node , of_clk_src_simple_get , clk ) ;
}
static int i2s_pll_clk_remove ( struct platform_device * pdev )
{
of_clk_del_provider ( pdev - > dev . of_node ) ;
return 0 ;
}
static const struct of_device_id i2s_pll_clk_id [ ] = {
{ . compatible = " snps,axs10x-i2s-pll-clock " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , i2s_pll_clk_id ) ;
static struct platform_driver i2s_pll_clk_driver = {
. driver = {
. name = " axs10x-i2s-pll-clock " ,
. of_match_table = i2s_pll_clk_id ,
} ,
. probe = i2s_pll_clk_probe ,
. remove = i2s_pll_clk_remove ,
} ;
module_platform_driver ( i2s_pll_clk_driver ) ;
MODULE_AUTHOR ( " Jose Abreu <joabreu@synopsys.com> " ) ;
MODULE_DESCRIPTION ( " Synopsys AXS10X SDP I2S PLL Clock Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;