2015-02-13 22:18:52 +03:00
/*
* Copyright ( C ) 2014 Philipp Zabel , Pengutronix
*
* 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 .
*
* PWM ( mis ) used as clock output
*/
# include <linux/clk-provider.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
struct clk_pwm {
struct clk_hw hw ;
struct pwm_device * pwm ;
u32 fixed_rate ;
} ;
static inline struct clk_pwm * to_clk_pwm ( struct clk_hw * hw )
{
return container_of ( hw , struct clk_pwm , hw ) ;
}
static int clk_pwm_prepare ( struct clk_hw * hw )
{
struct clk_pwm * clk_pwm = to_clk_pwm ( hw ) ;
return pwm_enable ( clk_pwm - > pwm ) ;
}
static void clk_pwm_unprepare ( struct clk_hw * hw )
{
struct clk_pwm * clk_pwm = to_clk_pwm ( hw ) ;
pwm_disable ( clk_pwm - > pwm ) ;
}
static unsigned long clk_pwm_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct clk_pwm * clk_pwm = to_clk_pwm ( hw ) ;
return clk_pwm - > fixed_rate ;
}
static const struct clk_ops clk_pwm_ops = {
. prepare = clk_pwm_prepare ,
. unprepare = clk_pwm_unprepare ,
. recalc_rate = clk_pwm_recalc_rate ,
} ;
static int clk_pwm_probe ( struct platform_device * pdev )
{
struct device_node * node = pdev - > dev . of_node ;
struct clk_init_data init ;
struct clk_pwm * clk_pwm ;
struct pwm_device * pwm ;
2016-04-14 22:17:23 +03:00
struct pwm_args pargs ;
2015-02-13 22:18:52 +03:00
const char * clk_name ;
int ret ;
clk_pwm = devm_kzalloc ( & pdev - > dev , sizeof ( * clk_pwm ) , GFP_KERNEL ) ;
if ( ! clk_pwm )
return - ENOMEM ;
pwm = devm_pwm_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( pwm ) )
return PTR_ERR ( pwm ) ;
2016-04-14 22:17:23 +03:00
pwm_get_args ( pwm , & pargs ) ;
if ( ! pargs . period ) {
2015-02-13 22:18:52 +03:00
dev_err ( & pdev - > dev , " invalid PWM period \n " ) ;
return - EINVAL ;
}
if ( of_property_read_u32 ( node , " clock-frequency " , & clk_pwm - > fixed_rate ) )
2016-04-14 22:17:23 +03:00
clk_pwm - > fixed_rate = NSEC_PER_SEC / pargs . period ;
2015-02-13 22:18:52 +03:00
2016-04-14 22:17:23 +03:00
if ( pargs . period ! = NSEC_PER_SEC / clk_pwm - > fixed_rate & &
pargs . period ! = DIV_ROUND_UP ( NSEC_PER_SEC , clk_pwm - > fixed_rate ) ) {
2015-02-13 22:18:52 +03:00
dev_err ( & pdev - > dev ,
" clock-frequency does not match PWM period \n " ) ;
return - EINVAL ;
}
2016-04-14 22:17:23 +03:00
/*
* FIXME : pwm_apply_args ( ) should be removed when switching to the
* atomic PWM API .
*/
pwm_apply_args ( pwm ) ;
ret = pwm_config ( pwm , ( pargs . period + 1 ) > > 1 , pargs . period ) ;
2015-02-13 22:18:52 +03:00
if ( ret < 0 )
return ret ;
clk_name = node - > name ;
of_property_read_string ( node , " clock-output-names " , & clk_name ) ;
init . name = clk_name ;
init . ops = & clk_pwm_ops ;
2016-03-01 22:00:18 +03:00
init . flags = CLK_IS_BASIC ;
2015-02-13 22:18:52 +03:00
init . num_parents = 0 ;
clk_pwm - > pwm = pwm ;
clk_pwm - > hw . init = & init ;
2016-06-02 02:15:22 +03:00
ret = devm_clk_hw_register ( & pdev - > dev , & clk_pwm - > hw ) ;
if ( ret )
return ret ;
2015-02-13 22:18:52 +03:00
2016-06-02 02:15:22 +03:00
return of_clk_add_hw_provider ( node , of_clk_hw_simple_get , & clk_pwm - > hw ) ;
2015-02-13 22:18:52 +03:00
}
static int clk_pwm_remove ( struct platform_device * pdev )
{
of_clk_del_provider ( pdev - > dev . of_node ) ;
return 0 ;
}
static const struct of_device_id clk_pwm_dt_ids [ ] = {
{ . compatible = " pwm-clock " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , clk_pwm_dt_ids ) ;
static struct platform_driver clk_pwm_driver = {
. probe = clk_pwm_probe ,
. remove = clk_pwm_remove ,
. driver = {
. name = " pwm-clock " ,
. of_match_table = of_match_ptr ( clk_pwm_dt_ids ) ,
} ,
} ;
module_platform_driver ( clk_pwm_driver ) ;
MODULE_AUTHOR ( " Philipp Zabel <p.zabel@pengutronix.de> " ) ;
MODULE_DESCRIPTION ( " PWM clock driver " ) ;
MODULE_LICENSE ( " GPL " ) ;