2014-05-07 13:20:47 +03:00
/*
* DRA7 ATL ( Audio Tracking Logic ) clock driver
*
* Copyright ( C ) 2013 Texas Instruments , Inc .
*
* Peter Ujfalusi < peter . ujfalusi @ ti . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
2016-07-04 17:12:20 -04:00
# include <linux/init.h>
2015-06-19 15:00:46 -07:00
# include <linux/clk.h>
2014-05-07 13:20:47 +03:00
# include <linux/clk-provider.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
2016-09-29 12:06:40 +03:00
# include <linux/clk/ti.h>
# include "clock.h"
2014-05-07 13:20:47 +03:00
# define DRA7_ATL_INSTANCES 4
# define DRA7_ATL_PPMR_REG(id) (0x200 + (id * 0x80))
# define DRA7_ATL_BBSR_REG(id) (0x204 + (id * 0x80))
# define DRA7_ATL_ATLCR_REG(id) (0x208 + (id * 0x80))
# define DRA7_ATL_SWEN_REG(id) (0x210 + (id * 0x80))
# define DRA7_ATL_BWSMUX_REG(id) (0x214 + (id * 0x80))
# define DRA7_ATL_AWSMUX_REG(id) (0x218 + (id * 0x80))
# define DRA7_ATL_PCLKMUX_REG(id) (0x21c + (id * 0x80))
# define DRA7_ATL_SWEN BIT(0)
# define DRA7_ATL_DIVIDER_MASK (0x1f)
# define DRA7_ATL_PCLKMUX BIT(0)
struct dra7_atl_clock_info ;
struct dra7_atl_desc {
struct clk * clk ;
struct clk_hw hw ;
struct dra7_atl_clock_info * cinfo ;
int id ;
bool probed ; /* the driver for the IP has been loaded */
bool valid ; /* configured */
bool enabled ;
u32 bws ; /* Baseband Word Select Mux */
u32 aws ; /* Audio Word Select Mux */
u32 divider ; /* Cached divider value */
} ;
struct dra7_atl_clock_info {
struct device * dev ;
void __iomem * iobase ;
struct dra7_atl_desc * cdesc ;
} ;
# define to_atl_desc(_hw) container_of(_hw, struct dra7_atl_desc, hw)
static inline void atl_write ( struct dra7_atl_clock_info * cinfo , u32 reg ,
u32 val )
{
__raw_writel ( val , cinfo - > iobase + reg ) ;
}
static inline int atl_read ( struct dra7_atl_clock_info * cinfo , u32 reg )
{
return __raw_readl ( cinfo - > iobase + reg ) ;
}
static int atl_clk_enable ( struct clk_hw * hw )
{
struct dra7_atl_desc * cdesc = to_atl_desc ( hw ) ;
if ( ! cdesc - > probed )
goto out ;
if ( unlikely ( ! cdesc - > valid ) )
dev_warn ( cdesc - > cinfo - > dev , " atl%d has not been configured \n " ,
cdesc - > id ) ;
pm_runtime_get_sync ( cdesc - > cinfo - > dev ) ;
atl_write ( cdesc - > cinfo , DRA7_ATL_ATLCR_REG ( cdesc - > id ) ,
cdesc - > divider - 1 ) ;
atl_write ( cdesc - > cinfo , DRA7_ATL_SWEN_REG ( cdesc - > id ) , DRA7_ATL_SWEN ) ;
out :
cdesc - > enabled = true ;
return 0 ;
}
static void atl_clk_disable ( struct clk_hw * hw )
{
struct dra7_atl_desc * cdesc = to_atl_desc ( hw ) ;
if ( ! cdesc - > probed )
goto out ;
atl_write ( cdesc - > cinfo , DRA7_ATL_SWEN_REG ( cdesc - > id ) , 0 ) ;
pm_runtime_put_sync ( cdesc - > cinfo - > dev ) ;
out :
cdesc - > enabled = false ;
}
static int atl_clk_is_enabled ( struct clk_hw * hw )
{
struct dra7_atl_desc * cdesc = to_atl_desc ( hw ) ;
return cdesc - > enabled ;
}
static unsigned long atl_clk_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct dra7_atl_desc * cdesc = to_atl_desc ( hw ) ;
return parent_rate / cdesc - > divider ;
}
static long atl_clk_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
unsigned divider ;
divider = ( * parent_rate + rate / 2 ) / rate ;
if ( divider > DRA7_ATL_DIVIDER_MASK + 1 )
divider = DRA7_ATL_DIVIDER_MASK + 1 ;
return * parent_rate / divider ;
}
static int atl_clk_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
2014-08-18 11:56:55 -05:00
struct dra7_atl_desc * cdesc ;
2014-05-07 13:20:47 +03:00
u32 divider ;
2014-08-18 11:56:55 -05:00
if ( ! hw | | ! rate )
return - EINVAL ;
cdesc = to_atl_desc ( hw ) ;
2014-05-07 13:20:47 +03:00
divider = ( ( parent_rate + rate / 2 ) / rate ) - 1 ;
if ( divider > DRA7_ATL_DIVIDER_MASK )
divider = DRA7_ATL_DIVIDER_MASK ;
cdesc - > divider = divider + 1 ;
return 0 ;
}
2015-05-01 12:59:32 -07:00
static const struct clk_ops atl_clk_ops = {
2014-05-07 13:20:47 +03:00
. enable = atl_clk_enable ,
. disable = atl_clk_disable ,
. is_enabled = atl_clk_is_enabled ,
. recalc_rate = atl_clk_recalc_rate ,
. round_rate = atl_clk_round_rate ,
. set_rate = atl_clk_set_rate ,
} ;
static void __init of_dra7_atl_clock_setup ( struct device_node * node )
{
struct dra7_atl_desc * clk_hw = NULL ;
2015-05-01 12:59:32 -07:00
struct clk_init_data init = { NULL } ;
2014-05-07 13:20:47 +03:00
const char * * parent_names = NULL ;
struct clk * clk ;
clk_hw = kzalloc ( sizeof ( * clk_hw ) , GFP_KERNEL ) ;
if ( ! clk_hw ) {
pr_err ( " %s: could not allocate dra7_atl_desc \n " , __func__ ) ;
return ;
}
clk_hw - > hw . init = & init ;
clk_hw - > divider = 1 ;
init . name = node - > name ;
init . ops = & atl_clk_ops ;
init . flags = CLK_IGNORE_UNUSED ;
init . num_parents = of_clk_get_parent_count ( node ) ;
if ( init . num_parents ! = 1 ) {
2018-08-28 10:44:29 -05:00
pr_err ( " %s: atl clock %pOFn must have 1 parent \n " , __func__ ,
node ) ;
2014-05-07 13:20:47 +03:00
goto cleanup ;
}
parent_names = kzalloc ( sizeof ( char * ) , GFP_KERNEL ) ;
if ( ! parent_names )
goto cleanup ;
parent_names [ 0 ] = of_clk_get_parent_name ( node , 0 ) ;
init . parent_names = parent_names ;
2016-09-29 12:06:40 +03:00
clk = ti_clk_register ( NULL , & clk_hw - > hw , node - > name ) ;
2014-05-07 13:20:47 +03:00
if ( ! IS_ERR ( clk ) ) {
of_clk_add_provider ( node , of_clk_src_simple_get , clk ) ;
2014-09-12 16:39:07 +03:00
kfree ( parent_names ) ;
2014-05-07 13:20:47 +03:00
return ;
}
cleanup :
kfree ( parent_names ) ;
kfree ( clk_hw ) ;
}
CLK_OF_DECLARE ( dra7_atl_clock , " ti,dra7-atl-clock " , of_dra7_atl_clock_setup ) ;
static int of_dra7_atl_clk_probe ( struct platform_device * pdev )
{
struct device_node * node = pdev - > dev . of_node ;
struct dra7_atl_clock_info * cinfo ;
int i ;
int ret = 0 ;
if ( ! node )
return - ENODEV ;
cinfo = devm_kzalloc ( & pdev - > dev , sizeof ( * cinfo ) , GFP_KERNEL ) ;
if ( ! cinfo )
return - ENOMEM ;
cinfo - > iobase = of_iomap ( node , 0 ) ;
cinfo - > dev = & pdev - > dev ;
pm_runtime_enable ( cinfo - > dev ) ;
pm_runtime_get_sync ( cinfo - > dev ) ;
atl_write ( cinfo , DRA7_ATL_PCLKMUX_REG ( 0 ) , DRA7_ATL_PCLKMUX ) ;
for ( i = 0 ; i < DRA7_ATL_INSTANCES ; i + + ) {
struct device_node * cfg_node ;
char prop [ 5 ] ;
struct dra7_atl_desc * cdesc ;
struct of_phandle_args clkspec ;
struct clk * clk ;
int rc ;
rc = of_parse_phandle_with_args ( node , " ti,provided-clocks " ,
NULL , i , & clkspec ) ;
if ( rc ) {
pr_err ( " %s: failed to lookup atl clock %d \n " , __func__ ,
i ) ;
return - EINVAL ;
}
clk = of_clk_get_from_provider ( & clkspec ) ;
2015-05-13 15:54:40 +09:00
if ( IS_ERR ( clk ) ) {
pr_err ( " %s: failed to get atl clock %d from provider \n " ,
__func__ , i ) ;
return PTR_ERR ( clk ) ;
}
2014-05-07 13:20:47 +03:00
cdesc = to_atl_desc ( __clk_get_hw ( clk ) ) ;
cdesc - > cinfo = cinfo ;
cdesc - > id = i ;
/* Get configuration for the ATL instances */
snprintf ( prop , sizeof ( prop ) , " atl%u " , i ) ;
2017-11-11 17:29:29 +01:00
cfg_node = of_get_child_by_name ( node , prop ) ;
2014-05-07 13:20:47 +03:00
if ( cfg_node ) {
ret = of_property_read_u32 ( cfg_node , " bws " ,
& cdesc - > bws ) ;
ret | = of_property_read_u32 ( cfg_node , " aws " ,
& cdesc - > aws ) ;
if ( ! ret ) {
cdesc - > valid = true ;
atl_write ( cinfo , DRA7_ATL_BWSMUX_REG ( i ) ,
cdesc - > bws ) ;
atl_write ( cinfo , DRA7_ATL_AWSMUX_REG ( i ) ,
cdesc - > aws ) ;
}
2016-03-11 16:13:32 +02:00
of_node_put ( cfg_node ) ;
2014-05-07 13:20:47 +03:00
}
cdesc - > probed = true ;
/*
* Enable the clock if it has been asked prior to loading the
* hw driver
*/
if ( cdesc - > enabled )
atl_clk_enable ( __clk_get_hw ( clk ) ) ;
}
pm_runtime_put_sync ( cinfo - > dev ) ;
return ret ;
}
2015-03-31 20:50:42 +02:00
static const struct of_device_id of_dra7_atl_clk_match_tbl [ ] = {
2014-05-07 13:20:47 +03:00
{ . compatible = " ti,dra7-atl " , } ,
{ } ,
} ;
static struct platform_driver dra7_atl_clk_driver = {
. driver = {
. name = " dra7-atl " ,
2016-07-04 17:12:20 -04:00
. suppress_bind_attrs = true ,
2014-05-07 13:20:47 +03:00
. of_match_table = of_dra7_atl_clk_match_tbl ,
} ,
. probe = of_dra7_atl_clk_probe ,
} ;
2016-07-04 17:12:20 -04:00
builtin_platform_driver ( dra7_atl_clk_driver ) ;