2019-03-19 16:37:00 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Lochnagar clock control
*
* Copyright ( c ) 2017 - 2018 Cirrus Logic , Inc . and
* Cirrus Logic International Semiconductor Ltd .
*
* Author : Charles Keepax < ckeepax @ opensource . cirrus . com >
*/
# include <linux/clk-provider.h>
# include <linux/device.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/mfd/lochnagar1_regs.h>
# include <linux/mfd/lochnagar2_regs.h>
# include <dt-bindings/clk/lochnagar.h>
# define LOCHNAGAR_NUM_CLOCKS (LOCHNAGAR_SPDIF_CLKOUT + 1)
struct lochnagar_clk {
const char * const name ;
struct clk_hw hw ;
struct lochnagar_clk_priv * priv ;
u16 cfg_reg ;
u16 ena_mask ;
u16 src_reg ;
u16 src_mask ;
} ;
struct lochnagar_clk_priv {
struct device * dev ;
struct regmap * regmap ;
struct lochnagar_clk lclks [ LOCHNAGAR_NUM_CLOCKS ] ;
} ;
2019-06-25 16:10:53 +03:00
# define LN_PARENT(NAME) { .name = NAME, .fw_name = NAME }
static const struct clk_parent_data lochnagar1_clk_parents [ ] = {
LN_PARENT ( " ln-none " ) ,
LN_PARENT ( " ln-spdif-mclk " ) ,
LN_PARENT ( " ln-psia1-mclk " ) ,
LN_PARENT ( " ln-psia2-mclk " ) ,
LN_PARENT ( " ln-cdc-clkout " ) ,
LN_PARENT ( " ln-dsp-clkout " ) ,
LN_PARENT ( " ln-pmic-32k " ) ,
LN_PARENT ( " ln-gf-mclk1 " ) ,
LN_PARENT ( " ln-gf-mclk3 " ) ,
LN_PARENT ( " ln-gf-mclk2 " ) ,
LN_PARENT ( " ln-gf-mclk4 " ) ,
2019-03-19 16:37:00 +03:00
} ;
2019-06-25 16:10:53 +03:00
static const struct clk_parent_data lochnagar2_clk_parents [ ] = {
LN_PARENT ( " ln-none " ) ,
LN_PARENT ( " ln-cdc-clkout " ) ,
LN_PARENT ( " ln-dsp-clkout " ) ,
LN_PARENT ( " ln-pmic-32k " ) ,
LN_PARENT ( " ln-spdif-mclk " ) ,
LN_PARENT ( " ln-clk-12m " ) ,
LN_PARENT ( " ln-clk-11m " ) ,
LN_PARENT ( " ln-clk-24m " ) ,
LN_PARENT ( " ln-clk-22m " ) ,
LN_PARENT ( " ln-clk-8m " ) ,
LN_PARENT ( " ln-usb-clk-24m " ) ,
LN_PARENT ( " ln-gf-mclk1 " ) ,
LN_PARENT ( " ln-gf-mclk3 " ) ,
LN_PARENT ( " ln-gf-mclk2 " ) ,
LN_PARENT ( " ln-psia1-mclk " ) ,
LN_PARENT ( " ln-psia2-mclk " ) ,
LN_PARENT ( " ln-spdif-clkout " ) ,
LN_PARENT ( " ln-adat-mclk " ) ,
LN_PARENT ( " ln-usb-clk-12m " ) ,
2019-03-19 16:37:00 +03:00
} ;
# define LN1_CLK(ID, NAME, REG) \
[ LOCHNAGAR_ # # ID ] = { \
. name = NAME , \
. cfg_reg = LOCHNAGAR1_ # # REG , \
. ena_mask = LOCHNAGAR1_ # # ID # # _ENA_MASK , \
. src_reg = LOCHNAGAR1_ # # ID # # _SEL , \
. src_mask = LOCHNAGAR1_SRC_MASK , \
}
# define LN2_CLK(ID, NAME) \
[ LOCHNAGAR_ # # ID ] = { \
. name = NAME , \
. cfg_reg = LOCHNAGAR2_ # # ID # # _CTRL , \
. src_reg = LOCHNAGAR2_ # # ID # # _CTRL , \
. ena_mask = LOCHNAGAR2_CLK_ENA_MASK , \
. src_mask = LOCHNAGAR2_CLK_SRC_MASK , \
}
static const struct lochnagar_clk lochnagar1_clks [ LOCHNAGAR_NUM_CLOCKS ] = {
LN1_CLK ( CDC_MCLK1 , " ln-cdc-mclk1 " , CDC_AIF_CTRL2 ) ,
LN1_CLK ( CDC_MCLK2 , " ln-cdc-mclk2 " , CDC_AIF_CTRL2 ) ,
LN1_CLK ( DSP_CLKIN , " ln-dsp-clkin " , DSP_AIF ) ,
LN1_CLK ( GF_CLKOUT1 , " ln-gf-clkout1 " , GF_AIF1 ) ,
} ;
static const struct lochnagar_clk lochnagar2_clks [ LOCHNAGAR_NUM_CLOCKS ] = {
LN2_CLK ( CDC_MCLK1 , " ln-cdc-mclk1 " ) ,
LN2_CLK ( CDC_MCLK2 , " ln-cdc-mclk2 " ) ,
LN2_CLK ( DSP_CLKIN , " ln-dsp-clkin " ) ,
LN2_CLK ( GF_CLKOUT1 , " ln-gf-clkout1 " ) ,
LN2_CLK ( GF_CLKOUT2 , " ln-gf-clkout2 " ) ,
LN2_CLK ( PSIA1_MCLK , " ln-psia1-mclk " ) ,
LN2_CLK ( PSIA2_MCLK , " ln-psia2-mclk " ) ,
LN2_CLK ( SPDIF_MCLK , " ln-spdif-mclk " ) ,
LN2_CLK ( ADAT_MCLK , " ln-adat-mclk " ) ,
LN2_CLK ( SOUNDCARD_MCLK , " ln-soundcard-mclk " ) ,
} ;
2019-06-25 16:10:53 +03:00
struct lochnagar_config {
const struct clk_parent_data * parents ;
int nparents ;
const struct lochnagar_clk * clks ;
} ;
static const struct lochnagar_config lochnagar1_conf = {
. parents = lochnagar1_clk_parents ,
. nparents = ARRAY_SIZE ( lochnagar1_clk_parents ) ,
. clks = lochnagar1_clks ,
} ;
static const struct lochnagar_config lochnagar2_conf = {
. parents = lochnagar2_clk_parents ,
. nparents = ARRAY_SIZE ( lochnagar2_clk_parents ) ,
. clks = lochnagar2_clks ,
} ;
2019-03-19 16:37:00 +03:00
static inline struct lochnagar_clk * lochnagar_hw_to_lclk ( struct clk_hw * hw )
{
return container_of ( hw , struct lochnagar_clk , hw ) ;
}
static int lochnagar_clk_prepare ( struct clk_hw * hw )
{
struct lochnagar_clk * lclk = lochnagar_hw_to_lclk ( hw ) ;
struct lochnagar_clk_priv * priv = lclk - > priv ;
struct regmap * regmap = priv - > regmap ;
int ret ;
ret = regmap_update_bits ( regmap , lclk - > cfg_reg ,
lclk - > ena_mask , lclk - > ena_mask ) ;
if ( ret < 0 )
dev_dbg ( priv - > dev , " Failed to prepare %s: %d \n " ,
lclk - > name , ret ) ;
return ret ;
}
static void lochnagar_clk_unprepare ( struct clk_hw * hw )
{
struct lochnagar_clk * lclk = lochnagar_hw_to_lclk ( hw ) ;
struct lochnagar_clk_priv * priv = lclk - > priv ;
struct regmap * regmap = priv - > regmap ;
int ret ;
ret = regmap_update_bits ( regmap , lclk - > cfg_reg , lclk - > ena_mask , 0 ) ;
if ( ret < 0 )
dev_dbg ( priv - > dev , " Failed to unprepare %s: %d \n " ,
lclk - > name , ret ) ;
}
static int lochnagar_clk_set_parent ( struct clk_hw * hw , u8 index )
{
struct lochnagar_clk * lclk = lochnagar_hw_to_lclk ( hw ) ;
struct lochnagar_clk_priv * priv = lclk - > priv ;
struct regmap * regmap = priv - > regmap ;
int ret ;
ret = regmap_update_bits ( regmap , lclk - > src_reg , lclk - > src_mask , index ) ;
if ( ret < 0 )
dev_dbg ( priv - > dev , " Failed to reparent %s: %d \n " ,
lclk - > name , ret ) ;
return ret ;
}
static u8 lochnagar_clk_get_parent ( struct clk_hw * hw )
{
struct lochnagar_clk * lclk = lochnagar_hw_to_lclk ( hw ) ;
struct lochnagar_clk_priv * priv = lclk - > priv ;
struct regmap * regmap = priv - > regmap ;
unsigned int val ;
int ret ;
ret = regmap_read ( regmap , lclk - > src_reg , & val ) ;
if ( ret < 0 ) {
dev_dbg ( priv - > dev , " Failed to read parent of %s: %d \n " ,
lclk - > name , ret ) ;
2019-07-31 22:35:10 +03:00
return clk_hw_get_num_parents ( hw ) ;
2019-03-19 16:37:00 +03:00
}
val & = lclk - > src_mask ;
return val ;
}
static const struct clk_ops lochnagar_clk_ops = {
. prepare = lochnagar_clk_prepare ,
. unprepare = lochnagar_clk_unprepare ,
. set_parent = lochnagar_clk_set_parent ,
. get_parent = lochnagar_clk_get_parent ,
} ;
static struct clk_hw *
lochnagar_of_clk_hw_get ( struct of_phandle_args * clkspec , void * data )
{
struct lochnagar_clk_priv * priv = data ;
unsigned int idx = clkspec - > args [ 0 ] ;
if ( idx > = ARRAY_SIZE ( priv - > lclks ) ) {
dev_err ( priv - > dev , " Invalid index %u \n " , idx ) ;
return ERR_PTR ( - EINVAL ) ;
}
return & priv - > lclks [ idx ] . hw ;
}
2019-06-25 16:10:53 +03:00
static const struct of_device_id lochnagar_of_match [ ] = {
{ . compatible = " cirrus,lochnagar1-clk " , . data = & lochnagar1_conf } ,
{ . compatible = " cirrus,lochnagar2-clk " , . data = & lochnagar2_conf } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , lochnagar_of_match ) ;
static int lochnagar_clk_probe ( struct platform_device * pdev )
2019-03-19 16:37:00 +03:00
{
struct clk_init_data clk_init = {
. ops = & lochnagar_clk_ops ,
} ;
2019-06-25 16:10:53 +03:00
struct device * dev = & pdev - > dev ;
struct lochnagar_clk_priv * priv ;
const struct of_device_id * of_id ;
2019-03-19 16:37:00 +03:00
struct lochnagar_clk * lclk ;
2019-06-25 16:10:53 +03:00
struct lochnagar_config * conf ;
2019-03-19 16:37:00 +03:00
int ret , i ;
2019-06-25 16:10:53 +03:00
of_id = of_match_device ( lochnagar_of_match , dev ) ;
if ( ! of_id )
return - EINVAL ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > dev = dev ;
priv - > regmap = dev_get_regmap ( dev - > parent , NULL ) ;
conf = ( struct lochnagar_config * ) of_id - > data ;
memcpy ( priv - > lclks , conf - > clks , sizeof ( priv - > lclks ) ) ;
clk_init . parent_data = conf - > parents ;
clk_init . num_parents = conf - > nparents ;
2019-03-19 16:37:00 +03:00
for ( i = 0 ; i < ARRAY_SIZE ( priv - > lclks ) ; i + + ) {
lclk = & priv - > lclks [ i ] ;
if ( ! lclk - > name )
continue ;
clk_init . name = lclk - > name ;
lclk - > priv = priv ;
lclk - > hw . init = & clk_init ;
2019-06-25 16:10:53 +03:00
ret = devm_clk_hw_register ( dev , & lclk - > hw ) ;
2019-03-19 16:37:00 +03:00
if ( ret ) {
2019-06-25 16:10:53 +03:00
dev_err ( dev , " Failed to register %s: %d \n " ,
2019-03-19 16:37:00 +03:00
lclk - > name , ret ) ;
return ret ;
}
}
2019-06-25 16:10:53 +03:00
ret = devm_of_clk_add_hw_provider ( dev , lochnagar_of_clk_hw_get , priv ) ;
2019-03-19 16:37:00 +03:00
if ( ret < 0 )
2019-06-25 16:10:53 +03:00
dev_err ( dev , " Failed to register provider: %d \n " , ret ) ;
2019-03-19 16:37:00 +03:00
return ret ;
}
static struct platform_driver lochnagar_clk_driver = {
. driver = {
. name = " lochnagar-clk " ,
. of_match_table = lochnagar_of_match ,
} ,
. probe = lochnagar_clk_probe ,
} ;
module_platform_driver ( lochnagar_clk_driver ) ;
MODULE_AUTHOR ( " Charles Keepax <ckeepax@opensource.cirrus.com> " ) ;
MODULE_DESCRIPTION ( " Clock driver for Cirrus Logic Lochnagar Board " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;