2014-06-21 18:22:06 +04:00
/*
* PWM driver for Rockchip SoCs
*
* Copyright ( C ) 2014 Beniamino Galvani < b . galvani @ gmail . com >
2014-08-08 11:28:49 +04:00
* Copyright ( C ) 2014 ROCKCHIP , Inc .
2014-06-21 18:22:06 +04:00
*
* 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 .
*/
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
2014-08-08 11:28:49 +04:00
# include <linux/of_device.h>
2014-06-21 18:22:06 +04:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/time.h>
# define PWM_CTRL_TIMER_EN (1 << 0)
# define PWM_CTRL_OUTPUT_EN (1 << 3)
2014-08-08 11:28:49 +04:00
# define PWM_ENABLE (1 << 0)
# define PWM_CONTINUOUS (1 << 1)
# define PWM_DUTY_POSITIVE (1 << 3)
2014-08-26 02:59:25 +04:00
# define PWM_DUTY_NEGATIVE (0 << 3)
2014-08-08 11:28:49 +04:00
# define PWM_INACTIVE_NEGATIVE (0 << 4)
2014-08-26 02:59:25 +04:00
# define PWM_INACTIVE_POSITIVE (1 << 4)
2017-08-08 18:38:32 +03:00
# define PWM_POLARITY_MASK (PWM_DUTY_POSITIVE | PWM_INACTIVE_POSITIVE)
2014-08-08 11:28:49 +04:00
# define PWM_OUTPUT_LEFT (0 << 5)
2017-08-08 18:42:47 +03:00
# define PWM_LOCK_EN (1 << 6)
2014-08-08 11:28:49 +04:00
# define PWM_LP_DISABLE (0 << 8)
2014-06-21 18:22:06 +04:00
struct rockchip_pwm_chip {
struct pwm_chip chip ;
struct clk * clk ;
2017-08-08 18:38:29 +03:00
struct clk * pclk ;
2014-08-08 11:28:49 +04:00
const struct rockchip_pwm_data * data ;
2014-06-21 18:22:06 +04:00
void __iomem * base ;
} ;
2014-08-08 11:28:49 +04:00
struct rockchip_pwm_regs {
unsigned long duty ;
unsigned long period ;
unsigned long cntr ;
unsigned long ctrl ;
} ;
struct rockchip_pwm_data {
struct rockchip_pwm_regs regs ;
unsigned int prescaler ;
2016-06-14 12:13:14 +03:00
bool supports_polarity ;
2017-08-08 18:42:47 +03:00
bool supports_lock ;
2017-08-08 18:41:28 +03:00
u32 enable_conf ;
2014-08-08 11:28:49 +04:00
} ;
2014-06-21 18:22:06 +04:00
static inline struct rockchip_pwm_chip * to_rockchip_pwm_chip ( struct pwm_chip * c )
{
return container_of ( c , struct rockchip_pwm_chip , chip ) ;
}
2016-06-14 12:13:12 +03:00
static void rockchip_pwm_get_state ( struct pwm_chip * chip ,
struct pwm_device * pwm ,
struct pwm_state * state )
{
struct rockchip_pwm_chip * pc = to_rockchip_pwm_chip ( chip ) ;
2017-08-08 18:41:28 +03:00
u32 enable_conf = pc - > data - > enable_conf ;
2016-06-14 12:13:12 +03:00
unsigned long clk_rate ;
u64 tmp ;
2017-08-08 18:41:28 +03:00
u32 val ;
2016-06-14 12:13:12 +03:00
int ret ;
2017-08-08 18:38:29 +03:00
ret = clk_enable ( pc - > pclk ) ;
2016-06-14 12:13:12 +03:00
if ( ret )
return ;
clk_rate = clk_get_rate ( pc - > clk ) ;
tmp = readl_relaxed ( pc - > base + pc - > data - > regs . period ) ;
tmp * = pc - > data - > prescaler * NSEC_PER_SEC ;
state - > period = DIV_ROUND_CLOSEST_ULL ( tmp , clk_rate ) ;
tmp = readl_relaxed ( pc - > base + pc - > data - > regs . duty ) ;
tmp * = pc - > data - > prescaler * NSEC_PER_SEC ;
2017-08-08 18:41:28 +03:00
state - > duty_cycle = DIV_ROUND_CLOSEST_ULL ( tmp , clk_rate ) ;
2016-06-14 12:13:12 +03:00
2017-08-08 18:41:28 +03:00
val = readl_relaxed ( pc - > base + pc - > data - > regs . ctrl ) ;
if ( pc - > data - > supports_polarity )
state - > enabled = ( ( val & enable_conf ) ! = enable_conf ) ?
false : true ;
else
state - > enabled = ( ( val & enable_conf ) = = enable_conf ) ?
true : false ;
if ( pc - > data - > supports_polarity ) {
if ( ! ( val & PWM_DUTY_POSITIVE ) )
state - > polarity = PWM_POLARITY_INVERSED ;
}
2016-06-14 12:13:12 +03:00
2017-08-08 18:38:29 +03:00
clk_disable ( pc - > pclk ) ;
2016-06-14 12:13:12 +03:00
}
2017-08-08 18:38:30 +03:00
static void rockchip_pwm_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
2017-08-08 18:38:32 +03:00
struct pwm_state * state )
2014-06-21 18:22:06 +04:00
{
struct rockchip_pwm_chip * pc = to_rockchip_pwm_chip ( chip ) ;
unsigned long period , duty ;
u64 clk_rate , div ;
2017-08-08 18:38:32 +03:00
u32 ctrl ;
2014-06-21 18:22:06 +04:00
clk_rate = clk_get_rate ( pc - > clk ) ;
/*
* Since period and duty cycle registers have a width of 32
* bits , every possible input period can be obtained using the
* default prescaler value for all practical clock rate values .
*/
2017-08-08 18:38:32 +03:00
div = clk_rate * state - > period ;
2016-06-14 12:13:11 +03:00
period = DIV_ROUND_CLOSEST_ULL ( div ,
pc - > data - > prescaler * NSEC_PER_SEC ) ;
2014-06-21 18:22:06 +04:00
2017-08-08 18:38:32 +03:00
div = clk_rate * state - > duty_cycle ;
2016-06-14 12:13:11 +03:00
duty = DIV_ROUND_CLOSEST_ULL ( div , pc - > data - > prescaler * NSEC_PER_SEC ) ;
2014-06-21 18:22:06 +04:00
2017-08-08 18:42:47 +03:00
/*
* Lock the period and duty of previous configuration , then
* change the duty and period , that would not be effective .
*/
ctrl = readl_relaxed ( pc - > base + pc - > data - > regs . ctrl ) ;
if ( pc - > data - > supports_lock ) {
ctrl | = PWM_LOCK_EN ;
writel_relaxed ( ctrl , pc - > base + pc - > data - > regs . ctrl ) ;
}
2014-08-08 11:28:49 +04:00
writel ( period , pc - > base + pc - > data - > regs . period ) ;
writel ( duty , pc - > base + pc - > data - > regs . duty ) ;
2017-08-08 18:38:32 +03:00
if ( pc - > data - > supports_polarity ) {
ctrl & = ~ PWM_POLARITY_MASK ;
if ( state - > polarity = = PWM_POLARITY_INVERSED )
ctrl | = PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE ;
else
ctrl | = PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE ;
}
2017-08-08 18:42:47 +03:00
/*
* Unlock and set polarity at the same time ,
* the configuration of duty , period and polarity
* would be effective together at next period .
*/
if ( pc - > data - > supports_lock )
ctrl & = ~ PWM_LOCK_EN ;
2017-08-08 18:38:32 +03:00
writel ( ctrl , pc - > base + pc - > data - > regs . ctrl ) ;
2014-08-26 02:59:25 +04:00
}
2017-03-01 14:10:55 +03:00
static int rockchip_pwm_enable ( struct pwm_chip * chip ,
2017-08-08 18:38:32 +03:00
struct pwm_device * pwm ,
2017-08-08 18:41:28 +03:00
bool enable )
2017-03-01 14:10:55 +03:00
{
struct rockchip_pwm_chip * pc = to_rockchip_pwm_chip ( chip ) ;
2017-08-08 18:41:28 +03:00
u32 enable_conf = pc - > data - > enable_conf ;
2017-03-01 14:10:55 +03:00
int ret ;
2017-08-08 18:38:31 +03:00
u32 val ;
2017-03-01 14:10:55 +03:00
if ( enable ) {
ret = clk_enable ( pc - > clk ) ;
if ( ret )
return ret ;
}
2017-08-08 18:38:31 +03:00
val = readl_relaxed ( pc - > base + pc - > data - > regs . ctrl ) ;
if ( enable )
val | = enable_conf ;
else
val & = ~ enable_conf ;
writel_relaxed ( val , pc - > base + pc - > data - > regs . ctrl ) ;
2017-03-01 14:10:55 +03:00
if ( ! enable )
clk_disable ( pc - > clk ) ;
return 0 ;
}
2017-08-08 18:41:28 +03:00
static int rockchip_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
struct pwm_state * state )
2014-06-21 18:22:06 +04:00
{
2017-08-08 18:41:28 +03:00
struct rockchip_pwm_chip * pc = to_rockchip_pwm_chip ( chip ) ;
2016-06-14 12:13:14 +03:00
struct pwm_state curstate ;
bool enabled ;
2017-08-08 18:38:31 +03:00
int ret = 0 ;
2014-06-21 18:22:06 +04:00
2017-08-08 18:41:28 +03:00
ret = clk_enable ( pc - > pclk ) ;
if ( ret )
return ret ;
2016-06-14 12:13:14 +03:00
pwm_get_state ( pwm , & curstate ) ;
enabled = curstate . enabled ;
2017-08-08 18:42:47 +03:00
if ( state - > polarity ! = curstate . polarity & & enabled & &
! pc - > data - > supports_lock ) {
2017-08-08 18:41:28 +03:00
ret = rockchip_pwm_enable ( chip , pwm , false ) ;
2017-03-01 14:10:55 +03:00
if ( ret )
2017-08-08 18:41:28 +03:00
goto out ;
2016-06-14 12:13:14 +03:00
enabled = false ;
}
2014-06-21 18:22:06 +04:00
2017-08-08 18:38:32 +03:00
rockchip_pwm_config ( chip , pwm , state ) ;
2017-08-08 18:41:28 +03:00
if ( state - > enabled ! = enabled ) {
ret = rockchip_pwm_enable ( chip , pwm , state - > enabled ) ;
2017-03-01 14:10:55 +03:00
if ( ret )
2017-08-08 18:41:28 +03:00
goto out ;
2017-03-01 14:10:55 +03:00
}
2014-06-21 18:22:06 +04:00
2016-06-14 12:13:14 +03:00
/*
* Update the state with the real hardware , which can differ a bit
* because of period / duty_cycle approximation .
*/
rockchip_pwm_get_state ( chip , pwm , state ) ;
2014-06-21 18:22:06 +04:00
2016-06-14 12:13:14 +03:00
out :
2017-08-08 18:38:29 +03:00
clk_disable ( pc - > pclk ) ;
2016-06-14 12:13:14 +03:00
return ret ;
2014-06-21 18:22:06 +04:00
}
2017-08-08 18:41:28 +03:00
static const struct pwm_ops rockchip_pwm_ops = {
2016-06-14 12:13:12 +03:00
. get_state = rockchip_pwm_get_state ,
2016-06-14 12:13:14 +03:00
. apply = rockchip_pwm_apply ,
2014-08-26 02:59:25 +04:00
. owner = THIS_MODULE ,
} ;
2014-08-08 11:28:49 +04:00
static const struct rockchip_pwm_data pwm_data_v1 = {
. regs = {
. duty = 0x04 ,
. period = 0x08 ,
. cntr = 0x00 ,
. ctrl = 0x0c ,
} ,
. prescaler = 2 ,
2017-08-08 18:41:28 +03:00
. supports_polarity = false ,
2017-08-08 18:42:47 +03:00
. supports_lock = false ,
2017-08-08 18:41:28 +03:00
. enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN ,
2014-08-08 11:28:49 +04:00
} ;
static const struct rockchip_pwm_data pwm_data_v2 = {
. regs = {
. duty = 0x08 ,
. period = 0x04 ,
. cntr = 0x00 ,
. ctrl = 0x0c ,
} ,
. prescaler = 1 ,
2016-06-14 12:13:14 +03:00
. supports_polarity = true ,
2017-08-08 18:42:47 +03:00
. supports_lock = false ,
2017-08-08 18:41:28 +03:00
. enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS ,
2014-08-08 11:28:49 +04:00
} ;
static const struct rockchip_pwm_data pwm_data_vop = {
. regs = {
. duty = 0x08 ,
. period = 0x04 ,
. cntr = 0x0c ,
. ctrl = 0x00 ,
} ,
. prescaler = 1 ,
2016-06-14 12:13:14 +03:00
. supports_polarity = true ,
2017-08-08 18:42:47 +03:00
. supports_lock = false ,
. enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS ,
} ;
static const struct rockchip_pwm_data pwm_data_v3 = {
. regs = {
. duty = 0x08 ,
. period = 0x04 ,
. cntr = 0x00 ,
. ctrl = 0x0c ,
} ,
. prescaler = 1 ,
. supports_polarity = true ,
. supports_lock = true ,
2017-08-08 18:41:28 +03:00
. enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS ,
2014-08-08 11:28:49 +04:00
} ;
static const struct of_device_id rockchip_pwm_dt_ids [ ] = {
{ . compatible = " rockchip,rk2928-pwm " , . data = & pwm_data_v1 } ,
{ . compatible = " rockchip,rk3288-pwm " , . data = & pwm_data_v2 } ,
{ . compatible = " rockchip,vop-pwm " , . data = & pwm_data_vop } ,
2017-08-08 18:42:47 +03:00
{ . compatible = " rockchip,rk3328-pwm " , . data = & pwm_data_v3 } ,
2014-08-08 11:28:49 +04:00
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , rockchip_pwm_dt_ids ) ;
2014-06-21 18:22:06 +04:00
static int rockchip_pwm_probe ( struct platform_device * pdev )
{
2014-08-08 11:28:49 +04:00
const struct of_device_id * id ;
2014-06-21 18:22:06 +04:00
struct rockchip_pwm_chip * pc ;
struct resource * r ;
2017-08-08 18:38:29 +03:00
int ret , count ;
2014-06-21 18:22:06 +04:00
2014-08-08 11:28:49 +04:00
id = of_match_device ( rockchip_pwm_dt_ids , & pdev - > dev ) ;
if ( ! id )
return - EINVAL ;
2014-06-21 18:22:06 +04:00
pc = devm_kzalloc ( & pdev - > dev , sizeof ( * pc ) , GFP_KERNEL ) ;
if ( ! pc )
return - ENOMEM ;
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
pc - > base = devm_ioremap_resource ( & pdev - > dev , r ) ;
if ( IS_ERR ( pc - > base ) )
return PTR_ERR ( pc - > base ) ;
2017-08-08 18:38:29 +03:00
pc - > clk = devm_clk_get ( & pdev - > dev , " pwm " ) ;
if ( IS_ERR ( pc - > clk ) ) {
pc - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( pc - > clk ) ) {
ret = PTR_ERR ( pc - > clk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( & pdev - > dev , " Can't get bus clk: %d \n " ,
ret ) ;
return ret ;
}
}
count = of_count_phandle_with_args ( pdev - > dev . of_node ,
" clocks " , " #clock-cells " ) ;
if ( count = = 2 )
pc - > pclk = devm_clk_get ( & pdev - > dev , " pclk " ) ;
else
pc - > pclk = pc - > clk ;
if ( IS_ERR ( pc - > pclk ) ) {
ret = PTR_ERR ( pc - > pclk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( & pdev - > dev , " Can't get APB clk: %d \n " , ret ) ;
return ret ;
}
2014-06-21 18:22:06 +04:00
2016-06-14 12:13:13 +03:00
ret = clk_prepare_enable ( pc - > clk ) ;
2017-08-08 18:38:29 +03:00
if ( ret ) {
dev_err ( & pdev - > dev , " Can't prepare enable bus clk: %d \n " , ret ) ;
2014-06-21 18:22:06 +04:00
return ret ;
2017-08-08 18:38:29 +03:00
}
ret = clk_prepare ( pc - > pclk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Can't prepare APB clk: %d \n " , ret ) ;
goto err_clk ;
}
2014-06-21 18:22:06 +04:00
platform_set_drvdata ( pdev , pc ) ;
2014-08-08 11:28:49 +04:00
pc - > data = id - > data ;
2014-06-21 18:22:06 +04:00
pc - > chip . dev = & pdev - > dev ;
2017-08-08 18:41:28 +03:00
pc - > chip . ops = & rockchip_pwm_ops ;
2014-06-21 18:22:06 +04:00
pc - > chip . base = - 1 ;
pc - > chip . npwm = 1 ;
2016-06-14 12:13:14 +03:00
if ( pc - > data - > supports_polarity ) {
2014-08-26 02:59:25 +04:00
pc - > chip . of_xlate = of_pwm_xlate_with_flags ;
pc - > chip . of_pwm_n_cells = 3 ;
}
2014-06-21 18:22:06 +04:00
ret = pwmchip_add ( & pc - > chip ) ;
if ( ret < 0 ) {
clk_unprepare ( pc - > clk ) ;
dev_err ( & pdev - > dev , " pwmchip_add() failed: %d \n " , ret ) ;
2017-08-08 18:38:29 +03:00
goto err_pclk ;
2014-06-21 18:22:06 +04:00
}
2016-06-14 12:13:13 +03:00
/* Keep the PWM clk enabled if the PWM appears to be up and running. */
if ( ! pwm_is_enabled ( pc - > chip . pwms ) )
clk_disable ( pc - > clk ) ;
2017-08-08 18:38:29 +03:00
return 0 ;
err_pclk :
clk_unprepare ( pc - > pclk ) ;
err_clk :
clk_disable_unprepare ( pc - > clk ) ;
2014-06-21 18:22:06 +04:00
return ret ;
}
static int rockchip_pwm_remove ( struct platform_device * pdev )
{
struct rockchip_pwm_chip * pc = platform_get_drvdata ( pdev ) ;
2016-06-14 12:13:13 +03:00
/*
* Disable the PWM clk before unpreparing it if the PWM device is still
* running . This should only happen when the last PWM user left it
* enabled , or when nobody requested a PWM that was previously enabled
* by the bootloader .
*
* FIXME : Maybe the core should disable all PWM devices in
* pwmchip_remove ( ) . In this case we ' d only have to call
* clk_unprepare ( ) after pwmchip_remove ( ) .
*
*/
if ( pwm_is_enabled ( pc - > chip . pwms ) )
clk_disable ( pc - > clk ) ;
2017-08-08 18:38:29 +03:00
clk_unprepare ( pc - > pclk ) ;
2014-06-21 18:22:06 +04:00
clk_unprepare ( pc - > clk ) ;
return pwmchip_remove ( & pc - > chip ) ;
}
static struct platform_driver rockchip_pwm_driver = {
. driver = {
. name = " rockchip-pwm " ,
. of_match_table = rockchip_pwm_dt_ids ,
} ,
. probe = rockchip_pwm_probe ,
. remove = rockchip_pwm_remove ,
} ;
module_platform_driver ( rockchip_pwm_driver ) ;
MODULE_AUTHOR ( " Beniamino Galvani <b.galvani@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Rockchip SoC PWM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;