2023-03-23 10:52:29 +08:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Author : Yinbo Zhu < zhuyinbo @ loongson . cn >
* Copyright ( C ) 2022 - 2023 Loongson Technology Corporation Limited
*/
# include <linux/err.h>
# include <linux/init.h>
# include <linux/clk-provider.h>
# include <linux/slab.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/io-64-nonatomic-lo-hi.h>
# include <dt-bindings/clock/loongson,ls2k-clk.h>
static const struct clk_parent_data pdata [ ] = {
2024-04-11 10:58:07 +08:00
{ . fw_name = " ref_100m " , } ,
2023-03-23 10:52:29 +08:00
} ;
2024-04-11 10:58:07 +08:00
enum loongson2_clk_type {
CLK_TYPE_PLL ,
CLK_TYPE_SCALE ,
CLK_TYPE_DIVIDER ,
2024-04-11 10:58:33 +08:00
CLK_TYPE_GATE ,
CLK_TYPE_FIXED ,
2024-04-11 10:58:07 +08:00
CLK_TYPE_NONE ,
2023-03-23 10:52:29 +08:00
} ;
2024-04-11 10:58:07 +08:00
struct loongson2_clk_provider {
void __iomem * base ;
struct device * dev ;
struct clk_hw_onecell_data clk_data ;
spinlock_t clk_lock ; /* protect access to DIV registers */
} ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
struct loongson2_clk_data {
struct clk_hw hw ;
void __iomem * reg ;
u8 div_shift ;
u8 div_width ;
u8 mult_shift ;
u8 mult_width ;
2023-03-23 10:52:29 +08:00
} ;
2024-04-11 10:58:07 +08:00
struct loongson2_clk_board_info {
u8 id ;
enum loongson2_clk_type type ;
const char * name ;
const char * parent_name ;
2024-04-11 10:58:33 +08:00
unsigned long fixed_rate ;
2024-04-11 10:58:07 +08:00
u8 reg_offset ;
u8 div_shift ;
u8 div_width ;
u8 mult_shift ;
u8 mult_width ;
2024-04-11 10:58:33 +08:00
u8 bit_idx ;
2024-04-11 10:58:07 +08:00
} ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
# define CLK_DIV(_id, _name, _pname, _offset, _dshift, _dwidth) \
{ \
. id = _id , \
. type = CLK_TYPE_DIVIDER , \
. name = _name , \
. parent_name = _pname , \
. reg_offset = _offset , \
. div_shift = _dshift , \
. div_width = _dwidth , \
}
# define CLK_PLL(_id, _name, _offset, _mshift, _mwidth, \
_dshift , _dwidth ) \
{ \
. id = _id , \
. type = CLK_TYPE_PLL , \
. name = _name , \
. parent_name = NULL , \
. reg_offset = _offset , \
. mult_shift = _mshift , \
. mult_width = _mwidth , \
. div_shift = _dshift , \
. div_width = _dwidth , \
}
# define CLK_SCALE(_id, _name, _pname, _offset, \
_dshift , _dwidth ) \
{ \
. id = _id , \
. type = CLK_TYPE_SCALE , \
. name = _name , \
. parent_name = _pname , \
. reg_offset = _offset , \
. div_shift = _dshift , \
. div_width = _dwidth , \
}
2024-04-11 10:58:33 +08:00
# define CLK_GATE(_id, _name, _pname, _offset, _bidx) \
{ \
. id = _id , \
. type = CLK_TYPE_GATE , \
. name = _name , \
. parent_name = _pname , \
. reg_offset = _offset , \
. bit_idx = _bidx , \
}
# define CLK_FIXED(_id, _name, _pname, _rate) \
{ \
. id = _id , \
. type = CLK_TYPE_FIXED , \
. name = _name , \
. parent_name = _pname , \
. fixed_rate = _rate , \
}
2024-04-11 10:58:31 +08:00
static const struct loongson2_clk_board_info ls2k0500_clks [ ] = {
CLK_PLL ( LOONGSON2_NODE_PLL , " pll_node " , 0 , 16 , 8 , 8 , 6 ) ,
CLK_PLL ( LOONGSON2_DDR_PLL , " pll_ddr " , 0x8 , 16 , 8 , 8 , 6 ) ,
CLK_PLL ( LOONGSON2_DC_PLL , " pll_soc " , 0x10 , 16 , 8 , 8 , 6 ) ,
CLK_PLL ( LOONGSON2_PIX0_PLL , " pll_pix0 " , 0x18 , 16 , 8 , 8 , 6 ) ,
CLK_PLL ( LOONGSON2_PIX1_PLL , " pll_pix1 " , 0x20 , 16 , 8 , 8 , 6 ) ,
CLK_DIV ( LOONGSON2_NODE_CLK , " clk_node " , " pll_node " , 0 , 24 , 6 ) ,
CLK_DIV ( LOONGSON2_DDR_CLK , " clk_ddr " , " pll_ddr " , 0x8 , 24 , 6 ) ,
CLK_DIV ( LOONGSON2_HDA_CLK , " clk_hda " , " pll_ddr " , 0xc , 8 , 6 ) ,
CLK_DIV ( LOONGSON2_GPU_CLK , " clk_gpu " , " pll_soc " , 0x10 , 24 , 6 ) ,
CLK_DIV ( LOONGSON2_DC_CLK , " clk_sb " , " pll_soc " , 0x14 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_GMAC_CLK , " clk_gmac " , " pll_soc " , 0x14 , 8 , 6 ) ,
CLK_DIV ( LOONGSON2_PIX0_CLK , " clk_pix0 " , " pll_pix0 " , 0x18 , 24 , 6 ) ,
CLK_DIV ( LOONGSON2_PIX1_CLK , " clk_pix1 " , " pll_pix1 " , 0x20 , 24 , 6 ) ,
CLK_SCALE ( LOONGSON2_BOOT_CLK , " clk_boot " , " clk_sb " , 0x28 , 8 , 3 ) ,
CLK_SCALE ( LOONGSON2_SATA_CLK , " clk_sata " , " clk_sb " , 0x28 , 12 , 3 ) ,
CLK_SCALE ( LOONGSON2_USB_CLK , " clk_usb " , " clk_sb " , 0x28 , 16 , 3 ) ,
CLK_SCALE ( LOONGSON2_APB_CLK , " clk_apb " , " clk_sb " , 0x28 , 20 , 3 ) ,
{ /* Sentinel */ } ,
} ;
2024-04-11 10:58:07 +08:00
static const struct loongson2_clk_board_info ls2k1000_clks [ ] = {
CLK_PLL ( LOONGSON2_NODE_PLL , " pll_node " , 0 , 32 , 10 , 26 , 6 ) ,
CLK_PLL ( LOONGSON2_DDR_PLL , " pll_ddr " , 0x10 , 32 , 10 , 26 , 6 ) ,
CLK_PLL ( LOONGSON2_DC_PLL , " pll_dc " , 0x20 , 32 , 10 , 26 , 6 ) ,
CLK_PLL ( LOONGSON2_PIX0_PLL , " pll_pix0 " , 0x30 , 32 , 10 , 26 , 6 ) ,
CLK_PLL ( LOONGSON2_PIX1_PLL , " pll_pix1 " , 0x40 , 32 , 10 , 26 , 6 ) ,
CLK_DIV ( LOONGSON2_NODE_CLK , " clk_node " , " pll_node " , 0x8 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_DDR_CLK , " clk_ddr " , " pll_ddr " , 0x18 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_GPU_CLK , " clk_gpu " , " pll_ddr " , 0x18 , 22 , 6 ) ,
/*
* The hda clk divisor in the upper 32 bits and the clk - prodiver
* layer code doesn ' t support 64 bit io operation thus a conversion
* is required that subtract shift by 32 and add 4 byte to the hda
* address
*/
CLK_DIV ( LOONGSON2_HDA_CLK , " clk_hda " , " pll_ddr " , 0x22 , 12 , 7 ) ,
CLK_DIV ( LOONGSON2_DC_CLK , " clk_dc " , " pll_dc " , 0x28 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_GMAC_CLK , " clk_gmac " , " pll_dc " , 0x28 , 22 , 6 ) ,
CLK_DIV ( LOONGSON2_PIX0_CLK , " clk_pix0 " , " pll_pix0 " , 0x38 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_PIX1_CLK , " clk_pix1 " , " pll_pix1 " , 0x38 , 0 , 6 ) ,
CLK_SCALE ( LOONGSON2_BOOT_CLK , " clk_boot " , NULL , 0x50 , 8 , 3 ) ,
CLK_SCALE ( LOONGSON2_SATA_CLK , " clk_sata " , " clk_gmac " , 0x50 , 12 , 3 ) ,
CLK_SCALE ( LOONGSON2_USB_CLK , " clk_usb " , " clk_gmac " , 0x50 , 16 , 3 ) ,
CLK_SCALE ( LOONGSON2_APB_CLK , " clk_apb " , " clk_gmac " , 0x50 , 20 , 3 ) ,
{ /* Sentinel */ } ,
2023-03-23 10:52:29 +08:00
} ;
2024-04-11 10:58:33 +08:00
static const struct loongson2_clk_board_info ls2k2000_clks [ ] = {
CLK_PLL ( LOONGSON2_DC_PLL , " pll_0 " , 0 , 21 , 9 , 32 , 6 ) ,
CLK_PLL ( LOONGSON2_DDR_PLL , " pll_1 " , 0x10 , 21 , 9 , 32 , 6 ) ,
CLK_PLL ( LOONGSON2_NODE_PLL , " pll_2 " , 0x20 , 21 , 9 , 32 , 6 ) ,
CLK_PLL ( LOONGSON2_PIX0_PLL , " pll_pix0 " , 0x30 , 21 , 9 , 32 , 6 ) ,
CLK_PLL ( LOONGSON2_PIX1_PLL , " pll_pix1 " , 0x40 , 21 , 9 , 32 , 6 ) ,
CLK_GATE ( LOONGSON2_OUT0_GATE , " out0_gate " , " pll_0 " , 0 , 40 ) ,
CLK_GATE ( LOONGSON2_GMAC_GATE , " gmac_gate " , " pll_0 " , 0 , 41 ) ,
CLK_GATE ( LOONGSON2_RIO_GATE , " rio_gate " , " pll_0 " , 0 , 42 ) ,
CLK_GATE ( LOONGSON2_DC_GATE , " dc_gate " , " pll_1 " , 0x10 , 40 ) ,
CLK_GATE ( LOONGSON2_DDR_GATE , " ddr_gate " , " pll_1 " , 0x10 , 41 ) ,
CLK_GATE ( LOONGSON2_GPU_GATE , " gpu_gate " , " pll_1 " , 0x10 , 42 ) ,
CLK_GATE ( LOONGSON2_HDA_GATE , " hda_gate " , " pll_2 " , 0x20 , 40 ) ,
CLK_GATE ( LOONGSON2_NODE_GATE , " node_gate " , " pll_2 " , 0x20 , 41 ) ,
CLK_GATE ( LOONGSON2_EMMC_GATE , " emmc_gate " , " pll_2 " , 0x20 , 42 ) ,
CLK_GATE ( LOONGSON2_PIX0_GATE , " pix0_gate " , " pll_pix0 " , 0x30 , 40 ) ,
CLK_GATE ( LOONGSON2_PIX1_GATE , " pix1_gate " , " pll_pix1 " , 0x40 , 40 ) ,
CLK_DIV ( LOONGSON2_OUT0_CLK , " clk_out0 " , " out0_gate " , 0 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_GMAC_CLK , " clk_gmac " , " gmac_gate " , 0 , 7 , 6 ) ,
CLK_DIV ( LOONGSON2_RIO_CLK , " clk_rio " , " rio_gate " , 0 , 14 , 6 ) ,
CLK_DIV ( LOONGSON2_DC_CLK , " clk_dc " , " dc_gate " , 0x10 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_GPU_CLK , " clk_gpu " , " gpu_gate " , 0x10 , 7 , 6 ) ,
CLK_DIV ( LOONGSON2_DDR_CLK , " clk_ddr " , " ddr_gate " , 0x10 , 14 , 6 ) ,
CLK_DIV ( LOONGSON2_HDA_CLK , " clk_hda " , " hda_gate " , 0x20 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_NODE_CLK , " clk_node " , " node_gate " , 0x20 , 7 , 6 ) ,
CLK_DIV ( LOONGSON2_EMMC_CLK , " clk_emmc " , " emmc_gate " , 0x20 , 14 , 6 ) ,
CLK_DIV ( LOONGSON2_PIX0_CLK , " clk_pix0 " , " pll_pix0 " , 0x30 , 0 , 6 ) ,
CLK_DIV ( LOONGSON2_PIX1_CLK , " clk_pix1 " , " pll_pix1 " , 0x40 , 0 , 6 ) ,
CLK_SCALE ( LOONGSON2_SATA_CLK , " clk_sata " , " clk_out0 " , 0x50 , 12 , 3 ) ,
CLK_SCALE ( LOONGSON2_USB_CLK , " clk_usb " , " clk_out0 " , 0x50 , 16 , 3 ) ,
CLK_SCALE ( LOONGSON2_APB_CLK , " clk_apb " , " clk_node " , 0x50 , 20 , 3 ) ,
CLK_SCALE ( LOONGSON2_BOOT_CLK , " clk_boot " , NULL , 0x50 , 23 , 3 ) ,
CLK_SCALE ( LOONGSON2_DES_CLK , " clk_des " , " clk_node " , 0x50 , 40 , 3 ) ,
CLK_SCALE ( LOONGSON2_I2S_CLK , " clk_i2s " , " clk_node " , 0x50 , 44 , 3 ) ,
CLK_FIXED ( LOONGSON2_MISC_CLK , " clk_misc " , NULL , 50000000 ) ,
{ /* Sentinel */ } ,
} ;
2024-04-11 10:58:07 +08:00
static inline struct loongson2_clk_data * to_loongson2_clk ( struct clk_hw * hw )
2023-03-23 10:52:29 +08:00
{
2024-04-11 10:58:07 +08:00
return container_of ( hw , struct loongson2_clk_data , hw ) ;
2023-03-23 10:52:29 +08:00
}
2024-04-11 10:58:07 +08:00
static inline unsigned long loongson2_rate_part ( u64 val , u8 shift , u8 width )
2023-03-23 10:52:29 +08:00
{
2024-04-11 10:58:07 +08:00
return ( val & GENMASK ( shift + width - 1 , shift ) ) > > shift ;
2023-03-23 10:52:29 +08:00
}
2024-04-11 10:58:07 +08:00
static unsigned long loongson2_pll_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
2023-03-23 10:52:29 +08:00
{
2024-04-11 10:58:07 +08:00
u64 val , mult , div ;
struct loongson2_clk_data * clk = to_loongson2_clk ( hw ) ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
val = readq ( clk - > reg ) ;
mult = loongson2_rate_part ( val , clk - > mult_shift , clk - > mult_width ) ;
div = loongson2_rate_part ( val , clk - > div_shift , clk - > div_width ) ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
return div_u64 ( ( u64 ) parent_rate * mult , div ) ;
2023-03-23 10:52:29 +08:00
}
2024-04-11 10:58:07 +08:00
static const struct clk_ops loongson2_pll_recalc_ops = {
. recalc_rate = loongson2_pll_recalc_rate ,
2023-03-23 10:52:29 +08:00
} ;
2024-04-11 10:58:07 +08:00
static unsigned long loongson2_freqscale_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
2023-03-23 10:52:29 +08:00
{
2024-04-11 10:58:07 +08:00
u64 val , mult ;
struct loongson2_clk_data * clk = to_loongson2_clk ( hw ) ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
val = readq ( clk - > reg ) ;
mult = loongson2_rate_part ( val , clk - > div_shift , clk - > div_width ) + 1 ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
return div_u64 ( ( u64 ) parent_rate * mult , 8 ) ;
2023-03-23 10:52:29 +08:00
}
2024-04-11 10:58:07 +08:00
static const struct clk_ops loongson2_freqscale_recalc_ops = {
. recalc_rate = loongson2_freqscale_recalc_rate ,
2023-03-23 10:52:29 +08:00
} ;
2024-04-11 10:58:07 +08:00
static struct clk_hw * loongson2_clk_register ( struct loongson2_clk_provider * clp ,
const struct loongson2_clk_board_info * cld ,
const struct clk_ops * ops )
2023-03-23 10:52:29 +08:00
{
2024-04-11 10:58:07 +08:00
int ret ;
struct clk_hw * hw ;
struct loongson2_clk_data * clk ;
struct clk_init_data init = { } ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
clk = devm_kzalloc ( clp - > dev , sizeof ( * clk ) , GFP_KERNEL ) ;
if ( ! clk )
return ERR_PTR ( - ENOMEM ) ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
init . name = cld - > name ;
init . ops = ops ;
init . flags = 0 ;
init . num_parents = 1 ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
if ( ! cld - > parent_name )
init . parent_data = pdata ;
else
init . parent_names = & cld - > parent_name ;
clk - > reg = clp - > base + cld - > reg_offset ;
clk - > div_shift = cld - > div_shift ;
clk - > div_width = cld - > div_width ;
clk - > mult_shift = cld - > mult_shift ;
clk - > mult_width = cld - > mult_width ;
clk - > hw . init = & init ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
hw = & clk - > hw ;
ret = devm_clk_hw_register ( clp - > dev , hw ) ;
if ( ret )
clk = ERR_PTR ( ret ) ;
return hw ;
2023-03-23 10:52:29 +08:00
}
static int loongson2_clk_probe ( struct platform_device * pdev )
{
2024-04-11 10:58:07 +08:00
int i , clks_num = 0 ;
struct clk_hw * hw ;
2023-03-23 10:52:29 +08:00
struct device * dev = & pdev - > dev ;
2024-04-11 10:58:07 +08:00
struct loongson2_clk_provider * clp ;
const struct loongson2_clk_board_info * p , * data ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
data = device_get_match_data ( dev ) ;
if ( ! data )
return - EINVAL ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
for ( p = data ; p - > name ; p + + )
clks_num + + ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
clp = devm_kzalloc ( dev , struct_size ( clp , clk_data . hws , clks_num ) ,
GFP_KERNEL ) ;
if ( ! clp )
return - ENOMEM ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
clp - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( clp - > base ) )
return PTR_ERR ( clp - > base ) ;
spin_lock_init ( & clp - > clk_lock ) ;
clp - > clk_data . num = clks_num + 1 ;
clp - > dev = dev ;
for ( i = 0 ; i < clks_num ; i + + ) {
p = & data [ i ] ;
switch ( p - > type ) {
case CLK_TYPE_PLL :
hw = loongson2_clk_register ( clp , p ,
& loongson2_pll_recalc_ops ) ;
break ;
case CLK_TYPE_SCALE :
hw = loongson2_clk_register ( clp , p ,
& loongson2_freqscale_recalc_ops ) ;
break ;
case CLK_TYPE_DIVIDER :
hw = devm_clk_hw_register_divider ( dev , p - > name ,
p - > parent_name , 0 ,
clp - > base + p - > reg_offset ,
p - > div_shift , p - > div_width ,
CLK_DIVIDER_ONE_BASED ,
& clp - > clk_lock ) ;
break ;
2024-04-11 10:58:33 +08:00
case CLK_TYPE_GATE :
hw = devm_clk_hw_register_gate ( dev , p - > name , p - > parent_name , 0 ,
clp - > base + p - > reg_offset ,
p - > bit_idx , 0 ,
& clp - > clk_lock ) ;
break ;
case CLK_TYPE_FIXED :
hw = clk_hw_register_fixed_rate_parent_data ( dev , p - > name , pdata ,
0 , p - > fixed_rate ) ;
break ;
2024-04-11 10:58:07 +08:00
default :
return dev_err_probe ( dev , - EINVAL , " Invalid clk type \n " ) ;
}
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
if ( IS_ERR ( hw ) )
return dev_err_probe ( dev , PTR_ERR ( hw ) ,
" Register clk: %s, type: %u failed! \n " ,
p - > name , p - > type ) ;
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
clp - > clk_data . hws [ p - > id ] = hw ;
}
2023-03-23 10:52:29 +08:00
2024-04-11 10:58:07 +08:00
return devm_of_clk_add_hw_provider ( dev , of_clk_hw_onecell_get , & clp - > clk_data ) ;
2023-03-23 10:52:29 +08:00
}
static const struct of_device_id loongson2_clk_match_table [ ] = {
2024-04-11 10:58:31 +08:00
{ . compatible = " loongson,ls2k0500-clk " , . data = & ls2k0500_clks } ,
2024-04-11 10:58:07 +08:00
{ . compatible = " loongson,ls2k-clk " , . data = & ls2k1000_clks } ,
2024-04-11 10:58:33 +08:00
{ . compatible = " loongson,ls2k2000-clk " , . data = & ls2k2000_clks } ,
2023-03-23 10:52:29 +08:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , loongson2_clk_match_table ) ;
static struct platform_driver loongson2_clk_driver = {
. probe = loongson2_clk_probe ,
. driver = {
. name = " loongson2-clk " ,
. of_match_table = loongson2_clk_match_table ,
} ,
} ;
module_platform_driver ( loongson2_clk_driver ) ;
MODULE_DESCRIPTION ( " Loongson2 clock driver " ) ;
2024-04-11 10:58:07 +08:00
MODULE_AUTHOR ( " Loongson Technology Corporation Limited " ) ;
2023-03-23 10:52:29 +08:00
MODULE_LICENSE ( " GPL " ) ;