2020-05-27 01:20:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright ( C ) 2020 BAIKAL ELECTRONICS , JSC
*
* Authors :
* Serge Semin < Sergey . Semin @ baikalelectronics . ru >
* Dmitry Dunaev < dmitry . dunaev @ baikalelectronics . ru >
*
* Baikal - T1 CCU PLL clocks driver
*/
# define pr_fmt(fmt) "bt1-ccu-pll: " fmt
# include <linux/kernel.h>
2022-09-30 01:54:02 +03:00
# include <linux/platform_device.h>
2020-05-27 01:20:55 +03:00
# include <linux/printk.h>
# include <linux/slab.h>
# include <linux/clk-provider.h>
# include <linux/mfd/syscon.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/ioport.h>
# include <linux/regmap.h>
# include <dt-bindings/clock/bt1-ccu.h>
# include "ccu-pll.h"
# define CCU_CPU_PLL_BASE 0x000
# define CCU_SATA_PLL_BASE 0x008
# define CCU_DDR_PLL_BASE 0x010
# define CCU_PCIE_PLL_BASE 0x018
# define CCU_ETH_PLL_BASE 0x020
2022-09-30 01:54:02 +03:00
# define CCU_PLL_INFO(_id, _name, _pname, _base, _flags, _features) \
{ \
. id = _id , \
. name = _name , \
. parent_name = _pname , \
. base = _base , \
. flags = _flags , \
. features = _features , \
2020-05-27 01:20:55 +03:00
}
# define CCU_PLL_NUM ARRAY_SIZE(pll_info)
struct ccu_pll_info {
unsigned int id ;
const char * name ;
const char * parent_name ;
unsigned int base ;
unsigned long flags ;
2022-09-30 01:54:02 +03:00
unsigned long features ;
2020-05-27 01:20:55 +03:00
} ;
/*
2020-09-20 14:03:35 +03:00
* Alas we have to mark all PLLs as critical . CPU and DDR PLLs are sources of
* CPU cores and DDR controller reference clocks , due to which they obviously
* shouldn ' t be ever gated . SATA and PCIe PLLs are the parents of APB - bus and
* DDR controller AXI - bus clocks . If they are gated the system will be
* unusable . Moreover disabling SATA and Ethernet PLLs causes automatic reset
* of the corresponding subsystems . So until we aren ' t ready to re - initialize
* all the devices consuming those PLLs , they will be marked as critical too .
2020-05-27 01:20:55 +03:00
*/
static const struct ccu_pll_info pll_info [ ] = {
CCU_PLL_INFO ( CCU_CPU_PLL , " cpu_pll " , " ref_clk " , CCU_CPU_PLL_BASE ,
2022-09-30 01:54:02 +03:00
CLK_IS_CRITICAL , CCU_PLL_BASIC ) ,
2020-05-27 01:20:55 +03:00
CCU_PLL_INFO ( CCU_SATA_PLL , " sata_pll " , " ref_clk " , CCU_SATA_PLL_BASE ,
2022-09-30 01:54:02 +03:00
CLK_IS_CRITICAL | CLK_SET_RATE_GATE , 0 ) ,
2020-05-27 01:20:55 +03:00
CCU_PLL_INFO ( CCU_DDR_PLL , " ddr_pll " , " ref_clk " , CCU_DDR_PLL_BASE ,
2022-09-30 01:54:02 +03:00
CLK_IS_CRITICAL | CLK_SET_RATE_GATE , 0 ) ,
2020-05-27 01:20:55 +03:00
CCU_PLL_INFO ( CCU_PCIE_PLL , " pcie_pll " , " ref_clk " , CCU_PCIE_PLL_BASE ,
2022-09-30 01:54:02 +03:00
CLK_IS_CRITICAL , CCU_PLL_BASIC ) ,
2020-05-27 01:20:55 +03:00
CCU_PLL_INFO ( CCU_ETH_PLL , " eth_pll " , " ref_clk " , CCU_ETH_PLL_BASE ,
2022-09-30 01:54:02 +03:00
CLK_IS_CRITICAL | CLK_SET_RATE_GATE , 0 )
2020-05-27 01:20:55 +03:00
} ;
struct ccu_pll_data {
struct device_node * np ;
struct regmap * sys_regs ;
struct ccu_pll * plls [ CCU_PLL_NUM ] ;
} ;
2022-09-30 01:54:02 +03:00
static struct ccu_pll_data * pll_data ;
2020-05-27 01:20:55 +03:00
static struct ccu_pll * ccu_pll_find_desc ( struct ccu_pll_data * data ,
unsigned int clk_id )
{
int idx ;
for ( idx = 0 ; idx < CCU_PLL_NUM ; + + idx ) {
2022-09-30 01:54:02 +03:00
if ( pll_info [ idx ] . id = = clk_id )
return data - > plls [ idx ] ;
2020-05-27 01:20:55 +03:00
}
return ERR_PTR ( - EINVAL ) ;
}
static struct ccu_pll_data * ccu_pll_create_data ( struct device_node * np )
{
struct ccu_pll_data * data ;
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return ERR_PTR ( - ENOMEM ) ;
data - > np = np ;
return data ;
}
static void ccu_pll_free_data ( struct ccu_pll_data * data )
{
kfree ( data ) ;
}
static int ccu_pll_find_sys_regs ( struct ccu_pll_data * data )
{
data - > sys_regs = syscon_node_to_regmap ( data - > np - > parent ) ;
if ( IS_ERR ( data - > sys_regs ) ) {
pr_err ( " Failed to find syscon regs for '%s' \n " ,
of_node_full_name ( data - > np ) ) ;
return PTR_ERR ( data - > sys_regs ) ;
}
return 0 ;
}
static struct clk_hw * ccu_pll_of_clk_hw_get ( struct of_phandle_args * clkspec ,
void * priv )
{
struct ccu_pll_data * data = priv ;
struct ccu_pll * pll ;
unsigned int clk_id ;
clk_id = clkspec - > args [ 0 ] ;
pll = ccu_pll_find_desc ( data , clk_id ) ;
if ( IS_ERR ( pll ) ) {
2022-09-30 01:54:02 +03:00
if ( pll ! = ERR_PTR ( - EPROBE_DEFER ) )
pr_info ( " Invalid PLL clock ID %d specified \n " , clk_id ) ;
2020-05-27 01:20:55 +03:00
return ERR_CAST ( pll ) ;
}
return ccu_pll_get_clk_hw ( pll ) ;
}
2022-09-30 01:54:02 +03:00
static int ccu_pll_clk_register ( struct ccu_pll_data * data , bool defer )
2020-05-27 01:20:55 +03:00
{
int idx , ret ;
for ( idx = 0 ; idx < CCU_PLL_NUM ; + + idx ) {
const struct ccu_pll_info * info = & pll_info [ idx ] ;
struct ccu_pll_init_data init = { 0 } ;
2022-09-30 01:54:02 +03:00
/* Defer non-basic PLLs allocation for the probe stage */
if ( ! ! ( info - > features & CCU_PLL_BASIC ) ^ defer ) {
if ( ! data - > plls [ idx ] )
data - > plls [ idx ] = ERR_PTR ( - EPROBE_DEFER ) ;
continue ;
}
2020-05-27 01:20:55 +03:00
init . id = info - > id ;
init . name = info - > name ;
init . parent_name = info - > parent_name ;
init . base = info - > base ;
init . sys_regs = data - > sys_regs ;
init . np = data - > np ;
init . flags = info - > flags ;
2022-09-30 01:54:02 +03:00
init . features = info - > features ;
2020-05-27 01:20:55 +03:00
data - > plls [ idx ] = ccu_pll_hw_register ( & init ) ;
if ( IS_ERR ( data - > plls [ idx ] ) ) {
ret = PTR_ERR ( data - > plls [ idx ] ) ;
pr_err ( " Couldn't register PLL hw '%s' \n " ,
init . name ) ;
goto err_hw_unregister ;
}
}
2022-09-30 01:54:02 +03:00
return 0 ;
err_hw_unregister :
for ( - - idx ; idx > = 0 ; - - idx ) {
if ( ! ! ( pll_info [ idx ] . features & CCU_PLL_BASIC ) ^ defer )
continue ;
ccu_pll_hw_unregister ( data - > plls [ idx ] ) ;
}
return ret ;
}
static void ccu_pll_clk_unregister ( struct ccu_pll_data * data , bool defer )
{
int idx ;
/* Uninstall only the clocks registered on the specfied stage */
for ( idx = 0 ; idx < CCU_PLL_NUM ; + + idx ) {
if ( ! ! ( pll_info [ idx ] . features & CCU_PLL_BASIC ) ^ defer )
continue ;
ccu_pll_hw_unregister ( data - > plls [ idx ] ) ;
}
}
static int ccu_pll_of_register ( struct ccu_pll_data * data )
{
int ret ;
2020-05-27 01:20:55 +03:00
ret = of_clk_add_hw_provider ( data - > np , ccu_pll_of_clk_hw_get , data ) ;
if ( ret ) {
pr_err ( " Couldn't register PLL provider of '%s' \n " ,
of_node_full_name ( data - > np ) ) ;
}
2022-09-30 01:54:02 +03:00
return ret ;
}
2020-05-27 01:20:55 +03:00
2022-09-30 01:54:02 +03:00
static int ccu_pll_probe ( struct platform_device * pdev )
{
struct ccu_pll_data * data = pll_data ;
2020-05-27 01:20:55 +03:00
2022-09-30 01:54:02 +03:00
if ( ! data )
return - EINVAL ;
return ccu_pll_clk_register ( data , false ) ;
2020-05-27 01:20:55 +03:00
}
2022-09-30 01:54:02 +03:00
static const struct of_device_id ccu_pll_of_match [ ] = {
{ . compatible = " baikal,bt1-ccu-pll " } ,
{ }
} ;
static struct platform_driver ccu_pll_driver = {
. probe = ccu_pll_probe ,
. driver = {
. name = " clk-ccu-pll " ,
. of_match_table = ccu_pll_of_match ,
. suppress_bind_attrs = true ,
} ,
} ;
builtin_platform_driver ( ccu_pll_driver ) ;
2020-05-27 01:20:55 +03:00
static __init void ccu_pll_init ( struct device_node * np )
{
struct ccu_pll_data * data ;
int ret ;
data = ccu_pll_create_data ( np ) ;
if ( IS_ERR ( data ) )
return ;
ret = ccu_pll_find_sys_regs ( data ) ;
if ( ret )
goto err_free_data ;
2022-09-30 01:54:02 +03:00
ret = ccu_pll_clk_register ( data , true ) ;
2020-05-27 01:20:55 +03:00
if ( ret )
goto err_free_data ;
2022-09-30 01:54:02 +03:00
ret = ccu_pll_of_register ( data ) ;
if ( ret )
goto err_clk_unregister ;
pll_data = data ;
2020-05-27 01:20:55 +03:00
return ;
2022-09-30 01:54:02 +03:00
err_clk_unregister :
ccu_pll_clk_unregister ( data , true ) ;
2020-05-27 01:20:55 +03:00
err_free_data :
ccu_pll_free_data ( data ) ;
}
2022-09-30 01:54:02 +03:00
CLK_OF_DECLARE_DRIVER ( ccu_pll , " baikal,bt1-ccu-pll " , ccu_pll_init ) ;