2019-05-27 08:55:00 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2012-08-22 10:01:24 +02:00
/*
* Copyright ( C ) 2010 , Lars - Peter Clausen < lars @ metafoo . de >
* JZ4740 platform PWM support
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/gpio.h>
# include <linux/kernel.h>
# include <linux/module.h>
2018-01-06 17:58:42 +01:00
# include <linux/of_device.h>
2012-08-22 10:01:24 +02: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 ) ;
2019-06-07 17:44:09 +02:00
/*
* Set duty > period . This trick allows the TCU channels in TCU2 mode to
* properly return to their init level .
*/
jz4740_timer_set_duty ( pwm - > hwpwm , 0xffff ) ;
jz4740_timer_set_period ( pwm - > hwpwm , 0x0 ) ;
/*
* Disable PWM output .
2018-01-06 17:58:40 +01:00
* 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 10:01:24 +02:00
ctrl & = ~ JZ_TIMER_CTRL_PWM_ENABLE ;
jz4740_timer_set_ctrl ( pwm - > hwpwm , ctrl ) ;
2018-01-06 17:58:40 +01:00
/* Stop counter */
jz4740_timer_disable ( pwm - > hwpwm ) ;
2012-08-22 10:01:24 +02:00
}
2019-06-07 17:44:07 +02:00
static int jz4740_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
struct pwm_state * state )
2012-08-22 10:01:24 +02:00
{
struct jz4740_pwm_chip * jz4740 = to_jz4740 ( pwm - > chip ) ;
unsigned long long tmp ;
unsigned long period , duty ;
unsigned int prescaler = 0 ;
uint16_t ctrl ;
2019-06-07 17:44:07 +02:00
tmp = ( unsigned long long ) clk_get_rate ( jz4740 - > clk ) * state - > period ;
2012-08-22 10:01:24 +02:00
do_div ( tmp , 1000000000 ) ;
period = tmp ;
while ( period > 0xffff & & prescaler < 6 ) {
period > > = 2 ;
+ + prescaler ;
}
if ( prescaler = = 6 )
return - EINVAL ;
2019-06-07 17:44:07 +02:00
tmp = ( unsigned long long ) period * state - > duty_cycle ;
do_div ( tmp , state - > period ) ;
2012-08-22 10:01:24 +02:00
duty = period - tmp ;
if ( duty > = period )
duty = period - 1 ;
2019-06-07 17:44:07 +02:00
jz4740_pwm_disable ( chip , pwm ) ;
2012-08-22 10:01:24 +02:00
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 ) ;
2019-06-07 17:44:07 +02:00
switch ( state - > polarity ) {
2018-01-06 17:58:41 +01:00
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 ) ;
2019-06-07 17:44:07 +02:00
if ( state - > enabled )
jz4740_pwm_enable ( chip , pwm ) ;
2018-01-06 17:58:41 +01:00
return 0 ;
}
2012-08-22 10:01:24 +02:00
static const struct pwm_ops jz4740_pwm_ops = {
. request = jz4740_pwm_request ,
. free = jz4740_pwm_free ,
2019-06-07 17:44:07 +02:00
. apply = jz4740_pwm_apply ,
2012-08-22 10:01:24 +02:00
. owner = THIS_MODULE ,
} ;
2012-11-19 13:23:14 -05:00
static int jz4740_pwm_probe ( struct platform_device * pdev )
2012-08-22 10:01:24 +02:00
{
struct jz4740_pwm_chip * jz4740 ;
jz4740 = devm_kzalloc ( & pdev - > dev , sizeof ( * jz4740 ) , GFP_KERNEL ) ;
if ( ! jz4740 )
return - ENOMEM ;
2013-12-07 18:13:16 +01:00
jz4740 - > clk = devm_clk_get ( & pdev - > dev , " ext " ) ;
2012-08-22 10:01:24 +02: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 17:58:42 +01:00
jz4740 - > chip . of_xlate = of_pwm_xlate_with_flags ;
jz4740 - > chip . of_pwm_n_cells = 3 ;
2012-08-22 10:01:24 +02:00
platform_set_drvdata ( pdev , jz4740 ) ;
2013-12-07 18:13:16 +01:00
return pwmchip_add ( & jz4740 - > chip ) ;
2012-08-22 10:01:24 +02:00
}
2012-11-19 13:26:09 -05:00
static int jz4740_pwm_remove ( struct platform_device * pdev )
2012-08-22 10:01:24 +02:00
{
struct jz4740_pwm_chip * jz4740 = platform_get_drvdata ( pdev ) ;
2013-12-07 18:13:16 +01:00
return pwmchip_remove ( & jz4740 - > chip ) ;
2012-08-22 10:01:24 +02:00
}
2018-01-06 17:58:42 +01:00
# ifdef CONFIG_OF
static const struct of_device_id jz4740_pwm_dt_ids [ ] = {
{ . compatible = " ingenic,jz4740-pwm " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , jz4740_pwm_dt_ids ) ;
# endif
2012-08-22 10:01:24 +02:00
static struct platform_driver jz4740_pwm_driver = {
. driver = {
. name = " jz4740-pwm " ,
2018-01-06 17:58:42 +01:00
. of_match_table = of_match_ptr ( jz4740_pwm_dt_ids ) ,
2012-08-22 10:01:24 +02:00
} ,
. probe = jz4740_pwm_probe ,
2012-11-19 13:21:28 -05:00
. remove = jz4740_pwm_remove ,
2012-08-22 10:01:24 +02: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 " ) ;