2012-08-22 12:01:24 +04:00
/*
* Copyright ( C ) 2010 , Lars - Peter Clausen < lars @ metafoo . de >
* JZ4740 platform PWM support
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/gpio.h>
# include <linux/kernel.h>
# include <linux/module.h>
2018-01-06 19:58:42 +03:00
# include <linux/of_device.h>
2012-08-22 12:01:24 +04:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <asm/mach-jz4740/timer.h>
# define NUM_PWM 8
struct jz4740_pwm_chip {
struct pwm_chip chip ;
struct clk * clk ;
} ;
static inline struct jz4740_pwm_chip * to_jz4740 ( struct pwm_chip * chip )
{
return container_of ( chip , struct jz4740_pwm_chip , chip ) ;
}
static int jz4740_pwm_request ( struct pwm_chip * chip , struct pwm_device * pwm )
{
/*
* Timers 0 and 1 are used for system tasks , so they are unavailable
* for use as PWMs .
*/
if ( pwm - > hwpwm < 2 )
return - EBUSY ;
jz4740_timer_start ( pwm - > hwpwm ) ;
return 0 ;
}
static void jz4740_pwm_free ( struct pwm_chip * chip , struct pwm_device * pwm )
{
jz4740_timer_set_ctrl ( pwm - > hwpwm , 0 ) ;
jz4740_timer_stop ( pwm - > hwpwm ) ;
}
static int jz4740_pwm_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
uint32_t ctrl = jz4740_timer_get_ctrl ( pwm - > pwm ) ;
ctrl | = JZ_TIMER_CTRL_PWM_ENABLE ;
jz4740_timer_set_ctrl ( pwm - > hwpwm , ctrl ) ;
jz4740_timer_enable ( pwm - > hwpwm ) ;
return 0 ;
}
static void jz4740_pwm_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
uint32_t ctrl = jz4740_timer_get_ctrl ( pwm - > hwpwm ) ;
2018-01-06 19:58:40 +03:00
/* Disable PWM output.
* In TCU2 mode ( channel 1 / 2 on JZ4750 + ) , this must be done before the
* counter is stopped , while in TCU1 mode the order does not matter .
*/
2012-08-22 12:01:24 +04:00
ctrl & = ~ JZ_TIMER_CTRL_PWM_ENABLE ;
jz4740_timer_set_ctrl ( pwm - > hwpwm , ctrl ) ;
2018-01-06 19:58:40 +03:00
/* Stop counter */
jz4740_timer_disable ( pwm - > hwpwm ) ;
2012-08-22 12:01:24 +04:00
}
static int jz4740_pwm_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
{
struct jz4740_pwm_chip * jz4740 = to_jz4740 ( pwm - > chip ) ;
unsigned long long tmp ;
unsigned long period , duty ;
unsigned int prescaler = 0 ;
uint16_t ctrl ;
bool is_enabled ;
tmp = ( unsigned long long ) clk_get_rate ( jz4740 - > clk ) * period_ns ;
do_div ( tmp , 1000000000 ) ;
period = tmp ;
while ( period > 0xffff & & prescaler < 6 ) {
period > > = 2 ;
+ + prescaler ;
}
if ( prescaler = = 6 )
return - EINVAL ;
tmp = ( unsigned long long ) period * duty_ns ;
do_div ( tmp , period_ns ) ;
duty = period - tmp ;
if ( duty > = period )
duty = period - 1 ;
is_enabled = jz4740_timer_is_enabled ( pwm - > hwpwm ) ;
if ( is_enabled )
jz4740_pwm_disable ( chip , pwm ) ;
jz4740_timer_set_count ( pwm - > hwpwm , 0 ) ;
jz4740_timer_set_duty ( pwm - > hwpwm , duty ) ;
jz4740_timer_set_period ( pwm - > hwpwm , period ) ;
ctrl = JZ_TIMER_CTRL_PRESCALER ( prescaler ) | JZ_TIMER_CTRL_SRC_EXT |
JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN ;
jz4740_timer_set_ctrl ( pwm - > hwpwm , ctrl ) ;
if ( is_enabled )
jz4740_pwm_enable ( chip , pwm ) ;
return 0 ;
}
2018-01-06 19:58:41 +03:00
static int jz4740_pwm_set_polarity ( struct pwm_chip * chip ,
struct pwm_device * pwm , enum pwm_polarity polarity )
{
uint32_t ctrl = jz4740_timer_get_ctrl ( pwm - > pwm ) ;
switch ( polarity ) {
case PWM_POLARITY_NORMAL :
ctrl & = ~ JZ_TIMER_CTRL_PWM_ACTIVE_LOW ;
break ;
case PWM_POLARITY_INVERSED :
ctrl | = JZ_TIMER_CTRL_PWM_ACTIVE_LOW ;
break ;
}
jz4740_timer_set_ctrl ( pwm - > hwpwm , ctrl ) ;
return 0 ;
}
2012-08-22 12:01:24 +04:00
static const struct pwm_ops jz4740_pwm_ops = {
. request = jz4740_pwm_request ,
. free = jz4740_pwm_free ,
. config = jz4740_pwm_config ,
2018-01-06 19:58:41 +03:00
. set_polarity = jz4740_pwm_set_polarity ,
2012-08-22 12:01:24 +04:00
. enable = jz4740_pwm_enable ,
. disable = jz4740_pwm_disable ,
. owner = THIS_MODULE ,
} ;
2012-11-19 22:23:14 +04:00
static int jz4740_pwm_probe ( struct platform_device * pdev )
2012-08-22 12:01:24 +04:00
{
struct jz4740_pwm_chip * jz4740 ;
jz4740 = devm_kzalloc ( & pdev - > dev , sizeof ( * jz4740 ) , GFP_KERNEL ) ;
if ( ! jz4740 )
return - ENOMEM ;
2013-12-07 21:13:16 +04:00
jz4740 - > clk = devm_clk_get ( & pdev - > dev , " ext " ) ;
2012-08-22 12:01:24 +04:00
if ( IS_ERR ( jz4740 - > clk ) )
return PTR_ERR ( jz4740 - > clk ) ;
jz4740 - > chip . dev = & pdev - > dev ;
jz4740 - > chip . ops = & jz4740_pwm_ops ;
jz4740 - > chip . npwm = NUM_PWM ;
jz4740 - > chip . base = - 1 ;
2018-01-06 19:58:42 +03:00
jz4740 - > chip . of_xlate = of_pwm_xlate_with_flags ;
jz4740 - > chip . of_pwm_n_cells = 3 ;
2012-08-22 12:01:24 +04:00
platform_set_drvdata ( pdev , jz4740 ) ;
2013-12-07 21:13:16 +04:00
return pwmchip_add ( & jz4740 - > chip ) ;
2012-08-22 12:01:24 +04:00
}
2012-11-19 22:26:09 +04:00
static int jz4740_pwm_remove ( struct platform_device * pdev )
2012-08-22 12:01:24 +04:00
{
struct jz4740_pwm_chip * jz4740 = platform_get_drvdata ( pdev ) ;
2013-12-07 21:13:16 +04:00
return pwmchip_remove ( & jz4740 - > chip ) ;
2012-08-22 12:01:24 +04:00
}
2018-01-06 19:58:42 +03:00
# ifdef CONFIG_OF
static const struct of_device_id jz4740_pwm_dt_ids [ ] = {
{ . compatible = " ingenic,jz4740-pwm " , } ,
{ . compatible = " ingenic,jz4770-pwm " , } ,
{ . compatible = " ingenic,jz4780-pwm " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , jz4740_pwm_dt_ids ) ;
# endif
2012-08-22 12:01:24 +04:00
static struct platform_driver jz4740_pwm_driver = {
. driver = {
. name = " jz4740-pwm " ,
2018-01-06 19:58:42 +03:00
. of_match_table = of_match_ptr ( jz4740_pwm_dt_ids ) ,
2012-08-22 12:01:24 +04:00
} ,
. probe = jz4740_pwm_probe ,
2012-11-19 22:21:28 +04:00
. remove = jz4740_pwm_remove ,
2012-08-22 12:01:24 +04:00
} ;
module_platform_driver ( jz4740_pwm_driver ) ;
MODULE_AUTHOR ( " Lars-Peter Clausen <lars@metafoo.de> " ) ;
MODULE_DESCRIPTION ( " Ingenic JZ4740 PWM driver " ) ;
MODULE_ALIAS ( " platform:jz4740-pwm " ) ;
MODULE_LICENSE ( " GPL " ) ;