2019-06-03 08:44:50 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-10-07 17:38:14 +04:00
/*
* Copyright ( C ) 2014 Free Electrons
* Copyright ( C ) 2014 Atmel
*
* Author : Boris BREZILLON < boris . brezillon @ free - electrons . com >
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/mfd/atmel-hlcdc.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/regmap.h>
# define ATMEL_HLCDC_PWMCVAL_MASK GENMASK(15, 8)
# define ATMEL_HLCDC_PWMCVAL(x) (((x) << 8) & ATMEL_HLCDC_PWMCVAL_MASK)
# define ATMEL_HLCDC_PWMPOL BIT(4)
# define ATMEL_HLCDC_PWMPS_MASK GENMASK(2, 0)
# define ATMEL_HLCDC_PWMPS_MAX 0x6
# define ATMEL_HLCDC_PWMPS(x) ((x) & ATMEL_HLCDC_PWMPS_MASK)
2014-11-19 17:33:09 +03:00
struct atmel_hlcdc_pwm_errata {
bool slow_clk_erratum ;
bool div1_clk_erratum ;
} ;
2014-10-07 17:38:14 +04:00
struct atmel_hlcdc_pwm {
struct pwm_chip chip ;
struct atmel_hlcdc * hlcdc ;
struct clk * cur_clk ;
2014-11-19 17:33:09 +03:00
const struct atmel_hlcdc_pwm_errata * errata ;
2014-10-07 17:38:14 +04:00
} ;
static inline struct atmel_hlcdc_pwm * to_atmel_hlcdc_pwm ( struct pwm_chip * chip )
{
return container_of ( chip , struct atmel_hlcdc_pwm , chip ) ;
}
2017-03-01 17:48:51 +03:00
static int atmel_hlcdc_pwm_apply ( struct pwm_chip * c , struct pwm_device * pwm ,
2019-08-24 18:37:07 +03:00
const struct pwm_state * state )
2014-10-07 17:38:14 +04:00
{
struct atmel_hlcdc_pwm * chip = to_atmel_hlcdc_pwm ( c ) ;
struct atmel_hlcdc * hlcdc = chip - > hlcdc ;
2017-03-01 17:48:51 +03:00
unsigned int status ;
int ret ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
if ( state - > enabled ) {
struct clk * new_clk = hlcdc - > slow_clk ;
u64 pwmcval = state - > duty_cycle * 256 ;
unsigned long clk_freq ;
u64 clk_period_ns ;
u32 pwmcfg ;
int pres ;
if ( ! chip - > errata | | ! chip - > errata - > slow_clk_erratum ) {
clk_freq = clk_get_rate ( new_clk ) ;
if ( ! clk_freq )
return - EINVAL ;
clk_period_ns = ( u64 ) NSEC_PER_SEC * 256 ;
do_div ( clk_period_ns , clk_freq ) ;
}
/* Errata: cannot use slow clk on some IP revisions */
if ( ( chip - > errata & & chip - > errata - > slow_clk_erratum ) | |
clk_period_ns > state - > period ) {
new_clk = hlcdc - > sys_clk ;
clk_freq = clk_get_rate ( new_clk ) ;
if ( ! clk_freq )
return - EINVAL ;
clk_period_ns = ( u64 ) NSEC_PER_SEC * 256 ;
do_div ( clk_period_ns , clk_freq ) ;
}
for ( pres = 0 ; pres < = ATMEL_HLCDC_PWMPS_MAX ; pres + + ) {
2014-11-19 17:33:09 +03:00
/* Errata: cannot divide by 1 on some IP revisions */
2017-03-01 17:48:51 +03:00
if ( ! pres & & chip - > errata & &
chip - > errata - > div1_clk_erratum )
continue ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
if ( ( clk_period_ns < < pres ) > = state - > period )
break ;
}
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
if ( pres > ATMEL_HLCDC_PWMPS_MAX )
return - EINVAL ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
pwmcfg = ATMEL_HLCDC_PWMPS ( pres ) ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
if ( new_clk ! = chip - > cur_clk ) {
u32 gencfg = 0 ;
int ret ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
ret = clk_prepare_enable ( new_clk ) ;
if ( ret )
return ret ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
clk_disable_unprepare ( chip - > cur_clk ) ;
chip - > cur_clk = new_clk ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
if ( new_clk = = hlcdc - > sys_clk )
gencfg = ATMEL_HLCDC_CLKPWMSEL ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
ret = regmap_update_bits ( hlcdc - > regmap ,
ATMEL_HLCDC_CFG ( 0 ) ,
ATMEL_HLCDC_CLKPWMSEL ,
gencfg ) ;
if ( ret )
return ret ;
}
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
do_div ( pwmcval , state - > period ) ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
/*
* The PWM duty cycle is configurable from 0 / 256 to 255 / 256 of
* the period cycle . Hence we can ' t set a duty cycle occupying
* the whole period cycle if we ' re asked to .
* Set it to 255 if pwmcval is greater than 256.
*/
if ( pwmcval > 255 )
pwmcval = 255 ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
pwmcfg | = ATMEL_HLCDC_PWMCVAL ( pwmcval ) ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
if ( state - > polarity = = PWM_POLARITY_NORMAL )
pwmcfg | = ATMEL_HLCDC_PWMPOL ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
ret = regmap_update_bits ( hlcdc - > regmap , ATMEL_HLCDC_CFG ( 6 ) ,
ATMEL_HLCDC_PWMCVAL_MASK |
ATMEL_HLCDC_PWMPS_MASK |
ATMEL_HLCDC_PWMPOL ,
pwmcfg ) ;
if ( ret )
return ret ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
ret = regmap_write ( hlcdc - > regmap , ATMEL_HLCDC_EN ,
ATMEL_HLCDC_PWM ) ;
if ( ret )
return ret ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
ret = regmap_read_poll_timeout ( hlcdc - > regmap , ATMEL_HLCDC_SR ,
status ,
status & ATMEL_HLCDC_PWM ,
10 , 0 ) ;
if ( ret )
return ret ;
} else {
ret = regmap_write ( hlcdc - > regmap , ATMEL_HLCDC_DIS ,
ATMEL_HLCDC_PWM ) ;
2014-10-07 17:38:14 +04:00
if ( ret )
return ret ;
2017-03-01 17:48:51 +03:00
ret = regmap_read_poll_timeout ( hlcdc - > regmap , ATMEL_HLCDC_SR ,
status ,
! ( status & ATMEL_HLCDC_PWM ) ,
10 , 0 ) ;
if ( ret )
return ret ;
2014-10-07 17:38:14 +04:00
2017-03-01 17:48:51 +03:00
clk_disable_unprepare ( chip - > cur_clk ) ;
chip - > cur_clk = NULL ;
2014-10-07 17:38:14 +04:00
}
return 0 ;
}
static const struct pwm_ops atmel_hlcdc_pwm_ops = {
2017-03-01 17:48:51 +03:00
. apply = atmel_hlcdc_pwm_apply ,
2014-10-07 17:38:14 +04:00
. owner = THIS_MODULE ,
} ;
2014-11-19 17:33:09 +03:00
static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_at91sam9x5_errata = {
. slow_clk_erratum = true ,
} ;
static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_sama5d3_errata = {
. div1_clk_erratum = true ,
} ;
2017-03-01 17:52:27 +03:00
# ifdef CONFIG_PM_SLEEP
static int atmel_hlcdc_pwm_suspend ( struct device * dev )
{
struct atmel_hlcdc_pwm * chip = dev_get_drvdata ( dev ) ;
/* Keep the periph clock enabled if the PWM is still running. */
if ( pwm_is_enabled ( & chip - > chip . pwms [ 0 ] ) )
clk_disable_unprepare ( chip - > hlcdc - > periph_clk ) ;
return 0 ;
}
static int atmel_hlcdc_pwm_resume ( struct device * dev )
{
struct atmel_hlcdc_pwm * chip = dev_get_drvdata ( dev ) ;
struct pwm_state state ;
int ret ;
pwm_get_state ( & chip - > chip . pwms [ 0 ] , & state ) ;
/* Re-enable the periph clock it was stopped during suspend. */
if ( ! state . enabled ) {
ret = clk_prepare_enable ( chip - > hlcdc - > periph_clk ) ;
if ( ret )
return ret ;
}
return atmel_hlcdc_pwm_apply ( & chip - > chip , & chip - > chip . pwms [ 0 ] , & state ) ;
}
# endif
static SIMPLE_DEV_PM_OPS ( atmel_hlcdc_pwm_pm_ops ,
atmel_hlcdc_pwm_suspend , atmel_hlcdc_pwm_resume ) ;
2014-11-19 17:33:09 +03:00
static const struct of_device_id atmel_hlcdc_dt_ids [ ] = {
2015-07-31 19:51:20 +03:00
{
. compatible = " atmel,at91sam9n12-hlcdc " ,
/* 9n12 has same errata as 9x5 HLCDC PWM */
. data = & atmel_hlcdc_pwm_at91sam9x5_errata ,
} ,
2014-11-19 17:33:09 +03:00
{
. compatible = " atmel,at91sam9x5-hlcdc " ,
. data = & atmel_hlcdc_pwm_at91sam9x5_errata ,
} ,
2015-09-09 16:32:30 +03:00
{
. compatible = " atmel,sama5d2-hlcdc " ,
} ,
2014-11-19 17:33:09 +03:00
{
. compatible = " atmel,sama5d3-hlcdc " ,
. data = & atmel_hlcdc_pwm_sama5d3_errata ,
} ,
2015-02-20 18:58:18 +03:00
{
. compatible = " atmel,sama5d4-hlcdc " ,
. data = & atmel_hlcdc_pwm_sama5d3_errata ,
} ,
2019-06-05 13:25:44 +03:00
{ . compatible = " microchip,sam9x60-hlcdc " , } ,
2014-11-19 17:33:09 +03:00
{ /* sentinel */ } ,
} ;
2015-09-18 19:58:21 +03:00
MODULE_DEVICE_TABLE ( of , atmel_hlcdc_dt_ids ) ;
2014-11-19 17:33:09 +03:00
2014-10-07 17:38:14 +04:00
static int atmel_hlcdc_pwm_probe ( struct platform_device * pdev )
{
2014-11-19 17:33:09 +03:00
const struct of_device_id * match ;
2014-10-07 17:38:14 +04:00
struct device * dev = & pdev - > dev ;
struct atmel_hlcdc_pwm * chip ;
struct atmel_hlcdc * hlcdc ;
int ret ;
hlcdc = dev_get_drvdata ( dev - > parent ) ;
chip = devm_kzalloc ( dev , sizeof ( * chip ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
ret = clk_prepare_enable ( hlcdc - > periph_clk ) ;
if ( ret )
return ret ;
2014-11-19 17:33:09 +03:00
match = of_match_node ( atmel_hlcdc_dt_ids , dev - > parent - > of_node ) ;
if ( match )
chip - > errata = match - > data ;
2014-10-07 17:38:14 +04:00
chip - > hlcdc = hlcdc ;
chip - > chip . ops = & atmel_hlcdc_pwm_ops ;
chip - > chip . dev = dev ;
chip - > chip . npwm = 1 ;
2020-12-07 16:45:55 +03:00
ret = pwmchip_add ( & chip - > chip ) ;
2014-10-07 17:38:14 +04:00
if ( ret ) {
clk_disable_unprepare ( hlcdc - > periph_clk ) ;
return ret ;
}
platform_set_drvdata ( pdev , chip ) ;
return 0 ;
}
static int atmel_hlcdc_pwm_remove ( struct platform_device * pdev )
{
struct atmel_hlcdc_pwm * chip = platform_get_drvdata ( pdev ) ;
int ret ;
ret = pwmchip_remove ( & chip - > chip ) ;
if ( ret )
return ret ;
clk_disable_unprepare ( chip - > hlcdc - > periph_clk ) ;
return 0 ;
}
static const struct of_device_id atmel_hlcdc_pwm_dt_ids [ ] = {
{ . compatible = " atmel,hlcdc-pwm " } ,
{ /* sentinel */ } ,
} ;
static struct platform_driver atmel_hlcdc_pwm_driver = {
. driver = {
. name = " atmel-hlcdc-pwm " ,
. of_match_table = atmel_hlcdc_pwm_dt_ids ,
2017-03-01 17:52:27 +03:00
. pm = & atmel_hlcdc_pwm_pm_ops ,
2014-10-07 17:38:14 +04:00
} ,
. probe = atmel_hlcdc_pwm_probe ,
. remove = atmel_hlcdc_pwm_remove ,
} ;
module_platform_driver ( atmel_hlcdc_pwm_driver ) ;
MODULE_ALIAS ( " platform:atmel-hlcdc-pwm " ) ;
MODULE_AUTHOR ( " Boris Brezillon <boris.brezillon@free-electrons.com> " ) ;
MODULE_DESCRIPTION ( " Atmel HLCDC PWM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;