2019-05-28 19:57:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2013-01-08 19:36:42 +04:00
/*
* Copyright ( C ) Overkiz SAS 2012
*
* Author : Boris BREZILLON < b . brezillon @ overkiz . com >
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/clocksource.h>
# include <linux/clockchips.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/ioport.h>
# include <linux/io.h>
2020-10-30 21:36:56 +03:00
# include <linux/mfd/syscon.h>
2013-01-08 19:36:42 +04:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/of_device.h>
2020-10-30 21:36:56 +03:00
# include <linux/of_irq.h>
# include <linux/regmap.h>
2013-01-08 19:36:42 +04:00
# include <linux/slab.h>
2019-04-27 00:47:10 +03:00
# include <soc/at91/atmel_tcb.h>
2013-01-08 19:36:42 +04:00
2020-10-30 21:36:56 +03:00
# define NPWM 2
2013-01-08 19:36:42 +04:00
# define ATMEL_TC_ACMR_MASK (ATMEL_TC_ACPA | ATMEL_TC_ACPC | \
ATMEL_TC_AEEVT | ATMEL_TC_ASWTRG )
# define ATMEL_TC_BCMR_MASK (ATMEL_TC_BCPB | ATMEL_TC_BCPC | \
ATMEL_TC_BEEVT | ATMEL_TC_BSWTRG )
struct atmel_tcb_pwm_device {
enum pwm_polarity polarity ; /* PWM polarity */
unsigned div ; /* PWM clock divider */
unsigned duty ; /* PWM duty expressed in clk cycles */
unsigned period ; /* PWM period expressed in clk cycles */
} ;
2017-10-19 19:44:10 +03:00
struct atmel_tcb_channel {
u32 enabled ;
u32 cmr ;
u32 ra ;
u32 rb ;
u32 rc ;
} ;
2013-01-08 19:36:42 +04:00
struct atmel_tcb_pwm_chip {
struct pwm_chip chip ;
spinlock_t lock ;
2020-10-30 21:36:56 +03:00
u8 channel ;
u8 width ;
struct regmap * regmap ;
struct clk * clk ;
2020-10-30 21:36:57 +03:00
struct clk * gclk ;
2020-10-30 21:36:56 +03:00
struct clk * slow_clk ;
2013-01-08 19:36:42 +04:00
struct atmel_tcb_pwm_device * pwms [ NPWM ] ;
2020-10-30 21:36:56 +03:00
struct atmel_tcb_channel bkup ;
2013-01-08 19:36:42 +04:00
} ;
2020-10-30 21:36:56 +03:00
const u8 atmel_tcb_divisors [ ] = { 2 , 8 , 32 , 128 , 0 , } ;
2013-01-08 19:36:42 +04:00
static inline struct atmel_tcb_pwm_chip * to_tcb_chip ( struct pwm_chip * chip )
{
return container_of ( chip , struct atmel_tcb_pwm_chip , chip ) ;
}
static int atmel_tcb_pwm_set_polarity ( struct pwm_chip * chip ,
struct pwm_device * pwm ,
enum pwm_polarity polarity )
{
struct atmel_tcb_pwm_device * tcbpwm = pwm_get_chip_data ( pwm ) ;
tcbpwm - > polarity = polarity ;
return 0 ;
}
static int atmel_tcb_pwm_request ( struct pwm_chip * chip ,
struct pwm_device * pwm )
{
struct atmel_tcb_pwm_chip * tcbpwmc = to_tcb_chip ( chip ) ;
struct atmel_tcb_pwm_device * tcbpwm ;
unsigned cmr ;
int ret ;
tcbpwm = devm_kzalloc ( chip - > dev , sizeof ( * tcbpwm ) , GFP_KERNEL ) ;
if ( ! tcbpwm )
return - ENOMEM ;
2020-10-30 21:36:56 +03:00
ret = clk_prepare_enable ( tcbpwmc - > clk ) ;
2013-01-08 19:36:42 +04:00
if ( ret ) {
devm_kfree ( chip - > dev , tcbpwm ) ;
return ret ;
}
pwm_set_chip_data ( pwm , tcbpwm ) ;
tcbpwm - > polarity = PWM_POLARITY_NORMAL ;
tcbpwm - > duty = 0 ;
tcbpwm - > period = 0 ;
tcbpwm - > div = 0 ;
spin_lock ( & tcbpwmc - > lock ) ;
2020-10-30 21:36:56 +03:00
regmap_read ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , CMR ) , & cmr ) ;
2013-01-08 19:36:42 +04:00
/*
* Get init config from Timer Counter registers if
* Timer Counter is already configured as a PWM generator .
*/
if ( cmr & ATMEL_TC_WAVE ) {
2020-10-30 21:36:56 +03:00
if ( pwm - > hwpwm = = 0 )
regmap_read ( tcbpwmc - > regmap ,
ATMEL_TC_REG ( tcbpwmc - > channel , RA ) ,
& tcbpwm - > duty ) ;
2013-01-08 19:36:42 +04:00
else
2020-10-30 21:36:56 +03:00
regmap_read ( tcbpwmc - > regmap ,
ATMEL_TC_REG ( tcbpwmc - > channel , RB ) ,
& tcbpwm - > duty ) ;
2013-01-08 19:36:42 +04:00
tcbpwm - > div = cmr & ATMEL_TC_TCCLKS ;
2020-10-30 21:36:56 +03:00
regmap_read ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , RC ) ,
& tcbpwm - > period ) ;
2013-01-08 19:36:42 +04:00
cmr & = ( ATMEL_TC_TCCLKS | ATMEL_TC_ACMR_MASK |
ATMEL_TC_BCMR_MASK ) ;
} else
cmr = 0 ;
cmr | = ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO | ATMEL_TC_EEVT_XC0 ;
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , CMR ) , cmr ) ;
2013-01-08 19:36:42 +04:00
spin_unlock ( & tcbpwmc - > lock ) ;
tcbpwmc - > pwms [ pwm - > hwpwm ] = tcbpwm ;
return 0 ;
}
static void atmel_tcb_pwm_free ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct atmel_tcb_pwm_chip * tcbpwmc = to_tcb_chip ( chip ) ;
struct atmel_tcb_pwm_device * tcbpwm = pwm_get_chip_data ( pwm ) ;
2020-10-30 21:36:56 +03:00
clk_disable_unprepare ( tcbpwmc - > clk ) ;
2013-01-08 19:36:42 +04:00
tcbpwmc - > pwms [ pwm - > hwpwm ] = NULL ;
devm_kfree ( chip - > dev , tcbpwm ) ;
}
static void atmel_tcb_pwm_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct atmel_tcb_pwm_chip * tcbpwmc = to_tcb_chip ( chip ) ;
struct atmel_tcb_pwm_device * tcbpwm = pwm_get_chip_data ( pwm ) ;
unsigned cmr ;
enum pwm_polarity polarity = tcbpwm - > polarity ;
/*
* If duty is 0 the timer will be stopped and we have to
* configure the output correctly on software trigger :
* - set output to high if PWM_POLARITY_INVERSED
* - set output to low if PWM_POLARITY_NORMAL
*
* This is why we ' re reverting polarity in this case .
*/
if ( tcbpwm - > duty = = 0 )
polarity = ! polarity ;
spin_lock ( & tcbpwmc - > lock ) ;
2020-10-30 21:36:56 +03:00
regmap_read ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , CMR ) , & cmr ) ;
2013-01-08 19:36:42 +04:00
/* flush old setting and set the new one */
2020-10-30 21:36:56 +03:00
if ( pwm - > hwpwm = = 0 ) {
2013-01-08 19:36:42 +04:00
cmr & = ~ ATMEL_TC_ACMR_MASK ;
if ( polarity = = PWM_POLARITY_INVERSED )
cmr | = ATMEL_TC_ASWTRG_CLEAR ;
else
cmr | = ATMEL_TC_ASWTRG_SET ;
} else {
cmr & = ~ ATMEL_TC_BCMR_MASK ;
if ( polarity = = PWM_POLARITY_INVERSED )
cmr | = ATMEL_TC_BSWTRG_CLEAR ;
else
cmr | = ATMEL_TC_BSWTRG_SET ;
}
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , CMR ) , cmr ) ;
2013-01-08 19:36:42 +04:00
/*
* Use software trigger to apply the new setting .
* If both PWM devices in this group are disabled we stop the clock .
*/
2017-10-19 19:44:10 +03:00
if ( ! ( cmr & ( ATMEL_TC_ACPC | ATMEL_TC_BCPC ) ) ) {
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwmc - > regmap ,
ATMEL_TC_REG ( tcbpwmc - > channel , CCR ) ,
ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS ) ;
tcbpwmc - > bkup . enabled = 1 ;
2017-10-19 19:44:10 +03:00
} else {
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwmc - > regmap ,
ATMEL_TC_REG ( tcbpwmc - > channel , CCR ) ,
ATMEL_TC_SWTRG ) ;
tcbpwmc - > bkup . enabled = 0 ;
2017-10-19 19:44:10 +03:00
}
2013-01-08 19:36:42 +04:00
spin_unlock ( & tcbpwmc - > lock ) ;
}
static int atmel_tcb_pwm_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct atmel_tcb_pwm_chip * tcbpwmc = to_tcb_chip ( chip ) ;
struct atmel_tcb_pwm_device * tcbpwm = pwm_get_chip_data ( pwm ) ;
u32 cmr ;
enum pwm_polarity polarity = tcbpwm - > polarity ;
/*
* If duty is 0 the timer will be stopped and we have to
* configure the output correctly on software trigger :
* - set output to high if PWM_POLARITY_INVERSED
* - set output to low if PWM_POLARITY_NORMAL
*
* This is why we ' re reverting polarity in this case .
*/
if ( tcbpwm - > duty = = 0 )
polarity = ! polarity ;
spin_lock ( & tcbpwmc - > lock ) ;
2020-10-30 21:36:56 +03:00
regmap_read ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , CMR ) , & cmr ) ;
2013-01-08 19:36:42 +04:00
/* flush old setting and set the new one */
cmr & = ~ ATMEL_TC_TCCLKS ;
2020-10-30 21:36:56 +03:00
if ( pwm - > hwpwm = = 0 ) {
2013-01-08 19:36:42 +04:00
cmr & = ~ ATMEL_TC_ACMR_MASK ;
/* Set CMR flags according to given polarity */
if ( polarity = = PWM_POLARITY_INVERSED )
cmr | = ATMEL_TC_ASWTRG_CLEAR ;
else
cmr | = ATMEL_TC_ASWTRG_SET ;
} else {
cmr & = ~ ATMEL_TC_BCMR_MASK ;
if ( polarity = = PWM_POLARITY_INVERSED )
cmr | = ATMEL_TC_BSWTRG_CLEAR ;
else
cmr | = ATMEL_TC_BSWTRG_SET ;
}
/*
* If duty is 0 or equal to period there ' s no need to register
* a specific action on RA / RB and RC compare .
* The output will be configured on software trigger and keep
* this config till next config call .
*/
if ( tcbpwm - > duty ! = tcbpwm - > period & & tcbpwm - > duty > 0 ) {
2020-10-30 21:36:56 +03:00
if ( pwm - > hwpwm = = 0 ) {
2013-01-08 19:36:42 +04:00
if ( polarity = = PWM_POLARITY_INVERSED )
cmr | = ATMEL_TC_ACPA_SET | ATMEL_TC_ACPC_CLEAR ;
else
cmr | = ATMEL_TC_ACPA_CLEAR | ATMEL_TC_ACPC_SET ;
} else {
if ( polarity = = PWM_POLARITY_INVERSED )
cmr | = ATMEL_TC_BCPB_SET | ATMEL_TC_BCPC_CLEAR ;
else
cmr | = ATMEL_TC_BCPB_CLEAR | ATMEL_TC_BCPC_SET ;
}
}
2013-09-18 19:06:05 +04:00
cmr | = ( tcbpwm - > div & ATMEL_TC_TCCLKS ) ;
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , CMR ) , cmr ) ;
2013-01-08 19:36:42 +04:00
2020-10-30 21:36:56 +03:00
if ( pwm - > hwpwm = = 0 )
regmap_write ( tcbpwmc - > regmap ,
ATMEL_TC_REG ( tcbpwmc - > channel , RA ) ,
tcbpwm - > duty ) ;
2013-01-08 19:36:42 +04:00
else
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwmc - > regmap ,
ATMEL_TC_REG ( tcbpwmc - > channel , RB ) ,
tcbpwm - > duty ) ;
2013-01-08 19:36:42 +04:00
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , RC ) ,
tcbpwm - > period ) ;
2013-01-08 19:36:42 +04:00
/* Use software trigger to apply the new setting */
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwmc - > regmap , ATMEL_TC_REG ( tcbpwmc - > channel , CCR ) ,
ATMEL_TC_SWTRG | ATMEL_TC_CLKEN ) ;
tcbpwmc - > bkup . enabled = 1 ;
2013-01-08 19:36:42 +04:00
spin_unlock ( & tcbpwmc - > lock ) ;
return 0 ;
}
static int atmel_tcb_pwm_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
{
struct atmel_tcb_pwm_chip * tcbpwmc = to_tcb_chip ( chip ) ;
struct atmel_tcb_pwm_device * tcbpwm = pwm_get_chip_data ( pwm ) ;
struct atmel_tcb_pwm_device * atcbpwm = NULL ;
2020-10-30 21:36:57 +03:00
int i = 0 ;
2013-01-08 19:36:42 +04:00
int slowclk = 0 ;
unsigned period ;
unsigned duty ;
2020-10-30 21:36:56 +03:00
unsigned rate = clk_get_rate ( tcbpwmc - > clk ) ;
2013-01-08 19:36:42 +04:00
unsigned long long min ;
unsigned long long max ;
/*
* Find best clk divisor :
* the smallest divisor which can fulfill the period_ns requirements .
2020-10-30 21:36:57 +03:00
* If there is a gclk , the first divisor is actuallly the gclk selector
2013-01-08 19:36:42 +04:00
*/
2020-10-30 21:36:57 +03:00
if ( tcbpwmc - > gclk )
i = 1 ;
for ( ; i < ARRAY_SIZE ( atmel_tcb_divisors ) ; + + i ) {
2020-10-30 21:36:56 +03:00
if ( atmel_tcb_divisors [ i ] = = 0 ) {
2013-01-08 19:36:42 +04:00
slowclk = i ;
continue ;
}
2020-10-30 21:36:56 +03:00
min = div_u64 ( ( u64 ) NSEC_PER_SEC * atmel_tcb_divisors [ i ] , rate ) ;
max = min < < tcbpwmc - > width ;
2013-01-08 19:36:42 +04:00
if ( max > = period_ns )
break ;
}
/*
* If none of the divisor are small enough to represent period_ns
* take slow clock ( 32 KHz ) .
*/
2020-10-30 21:36:56 +03:00
if ( i = = ARRAY_SIZE ( atmel_tcb_divisors ) ) {
2013-01-08 19:36:42 +04:00
i = slowclk ;
2020-10-30 21:36:56 +03:00
rate = clk_get_rate ( tcbpwmc - > slow_clk ) ;
2013-01-08 19:36:42 +04:00
min = div_u64 ( NSEC_PER_SEC , rate ) ;
2020-10-30 21:36:56 +03:00
max = min < < tcbpwmc - > width ;
2013-01-08 19:36:42 +04:00
/* If period is too big return ERANGE error */
if ( max < period_ns )
return - ERANGE ;
}
duty = div_u64 ( duty_ns , min ) ;
period = div_u64 ( period_ns , min ) ;
2020-10-30 21:36:56 +03:00
if ( pwm - > hwpwm = = 0 )
atcbpwm = tcbpwmc - > pwms [ 1 ] ;
2013-01-08 19:36:42 +04:00
else
2020-10-30 21:36:56 +03:00
atcbpwm = tcbpwmc - > pwms [ 0 ] ;
2013-01-08 19:36:42 +04:00
/*
2020-10-30 21:36:56 +03:00
* PWM devices provided by the TCB driver are grouped by 2.
2013-01-08 19:36:42 +04:00
* PWM devices in a given group must be configured with the
* same period_ns .
*
* We ' re checking the period value of the second PWM device
* in this group before applying the new config .
*/
if ( ( atcbpwm & & atcbpwm - > duty > 0 & &
atcbpwm - > duty ! = atcbpwm - > period ) & &
( atcbpwm - > div ! = i | | atcbpwm - > period ! = period ) ) {
dev_err ( chip - > dev ,
" failed to configure period_ns: PWM group already configured with a different value \n " ) ;
return - EINVAL ;
}
tcbpwm - > period = period ;
tcbpwm - > div = i ;
tcbpwm - > duty = duty ;
return 0 ;
}
2021-03-08 12:50:12 +03:00
static int atmel_tcb_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
const struct pwm_state * state )
{
int duty_cycle , period ;
int ret ;
/* This function only sets a flag in driver data */
atmel_tcb_pwm_set_polarity ( chip , pwm , state - > polarity ) ;
if ( ! state - > enabled ) {
atmel_tcb_pwm_disable ( chip , pwm ) ;
return 0 ;
}
period = state - > period < INT_MAX ? state - > period : INT_MAX ;
duty_cycle = state - > duty_cycle < INT_MAX ? state - > duty_cycle : INT_MAX ;
ret = atmel_tcb_pwm_config ( chip , pwm , duty_cycle , period ) ;
if ( ret )
return ret ;
return atmel_tcb_pwm_enable ( chip , pwm ) ;
}
2013-01-08 19:36:42 +04:00
static const struct pwm_ops atmel_tcb_pwm_ops = {
. request = atmel_tcb_pwm_request ,
. free = atmel_tcb_pwm_free ,
2021-03-08 12:50:12 +03:00
. apply = atmel_tcb_pwm_apply ,
2013-03-31 07:15:15 +04:00
. owner = THIS_MODULE ,
2013-01-08 19:36:42 +04:00
} ;
2020-10-30 21:36:56 +03:00
static struct atmel_tcb_config tcb_rm9200_config = {
. counter_width = 16 ,
} ;
static struct atmel_tcb_config tcb_sam9x5_config = {
. counter_width = 32 ,
} ;
2020-10-30 21:36:57 +03:00
static struct atmel_tcb_config tcb_sama5d2_config = {
. counter_width = 32 ,
. has_gclk = 1 ,
} ;
2020-10-30 21:36:56 +03:00
static const struct of_device_id atmel_tcb_of_match [ ] = {
{ . compatible = " atmel,at91rm9200-tcb " , . data = & tcb_rm9200_config , } ,
{ . compatible = " atmel,at91sam9x5-tcb " , . data = & tcb_sam9x5_config , } ,
2020-10-30 21:36:57 +03:00
{ . compatible = " atmel,sama5d2-tcb " , . data = & tcb_sama5d2_config , } ,
2020-10-30 21:36:56 +03:00
{ /* sentinel */ }
} ;
2013-01-08 19:36:42 +04:00
static int atmel_tcb_pwm_probe ( struct platform_device * pdev )
{
2020-10-30 21:36:56 +03:00
const struct of_device_id * match ;
2013-01-08 19:36:42 +04:00
struct atmel_tcb_pwm_chip * tcbpwm ;
2020-10-30 21:36:56 +03:00
const struct atmel_tcb_config * config ;
2013-01-08 19:36:42 +04:00
struct device_node * np = pdev - > dev . of_node ;
2020-10-30 21:36:56 +03:00
struct regmap * regmap ;
2020-10-30 21:36:57 +03:00
struct clk * clk , * gclk = NULL ;
2020-10-30 21:36:56 +03:00
struct clk * slow_clk ;
char clk_name [ ] = " t0_clk " ;
2013-01-08 19:36:42 +04:00
int err ;
2020-10-30 21:36:56 +03:00
int channel ;
2013-01-08 19:36:42 +04:00
2020-10-30 21:36:56 +03:00
err = of_property_read_u32 ( np , " reg " , & channel ) ;
2013-01-08 19:36:42 +04:00
if ( err < 0 ) {
dev_err ( & pdev - > dev ,
2020-10-30 21:36:56 +03:00
" failed to get Timer Counter Block channel from device tree (error: %d) \n " ,
2013-01-08 19:36:42 +04:00
err ) ;
return err ;
}
2020-10-30 21:36:56 +03:00
regmap = syscon_node_to_regmap ( np - > parent ) ;
if ( IS_ERR ( regmap ) )
return PTR_ERR ( regmap ) ;
slow_clk = of_clk_get_by_name ( np - > parent , " slow_clk " ) ;
if ( IS_ERR ( slow_clk ) )
return PTR_ERR ( slow_clk ) ;
clk_name [ 1 ] + = channel ;
clk = of_clk_get_by_name ( np - > parent , clk_name ) ;
if ( IS_ERR ( clk ) )
clk = of_clk_get_by_name ( np - > parent , " t0_clk " ) ;
if ( IS_ERR ( clk ) )
return PTR_ERR ( clk ) ;
match = of_match_node ( atmel_tcb_of_match , np - > parent ) ;
config = match - > data ;
2013-01-08 19:36:42 +04:00
2020-10-30 21:36:57 +03:00
if ( config - > has_gclk ) {
gclk = of_clk_get_by_name ( np - > parent , " gclk " ) ;
if ( IS_ERR ( gclk ) )
return PTR_ERR ( gclk ) ;
}
2013-01-08 19:36:42 +04:00
tcbpwm = devm_kzalloc ( & pdev - > dev , sizeof ( * tcbpwm ) , GFP_KERNEL ) ;
if ( tcbpwm = = NULL ) {
2015-08-16 12:23:46 +03:00
err = - ENOMEM ;
2020-10-30 21:36:56 +03:00
goto err_slow_clk ;
2013-01-08 19:36:42 +04:00
}
tcbpwm - > chip . dev = & pdev - > dev ;
tcbpwm - > chip . ops = & atmel_tcb_pwm_ops ;
tcbpwm - > chip . npwm = NPWM ;
2020-10-30 21:36:56 +03:00
tcbpwm - > channel = channel ;
tcbpwm - > regmap = regmap ;
tcbpwm - > clk = clk ;
2020-10-30 21:36:57 +03:00
tcbpwm - > gclk = gclk ;
2020-10-30 21:36:56 +03:00
tcbpwm - > slow_clk = slow_clk ;
tcbpwm - > width = config - > counter_width ;
2013-01-08 19:36:42 +04:00
2020-10-30 21:36:56 +03:00
err = clk_prepare_enable ( slow_clk ) ;
2015-08-16 12:23:46 +03:00
if ( err )
2020-10-30 21:36:56 +03:00
goto err_slow_clk ;
2015-08-16 12:23:46 +03:00
2013-01-08 19:36:42 +04:00
spin_lock_init ( & tcbpwm - > lock ) ;
err = pwmchip_add ( & tcbpwm - > chip ) ;
2015-08-16 12:23:46 +03:00
if ( err < 0 )
goto err_disable_clk ;
2013-01-08 19:36:42 +04:00
platform_set_drvdata ( pdev , tcbpwm ) ;
return 0 ;
2015-08-16 12:23:46 +03:00
err_disable_clk :
2020-10-30 21:36:56 +03:00
clk_disable_unprepare ( tcbpwm - > slow_clk ) ;
2015-08-16 12:23:46 +03:00
2020-10-30 21:36:56 +03:00
err_slow_clk :
clk_put ( slow_clk ) ;
2015-08-16 12:23:46 +03:00
return err ;
2013-01-08 19:36:42 +04:00
}
static int atmel_tcb_pwm_remove ( struct platform_device * pdev )
{
struct atmel_tcb_pwm_chip * tcbpwm = platform_get_drvdata ( pdev ) ;
int err ;
err = pwmchip_remove ( & tcbpwm - > chip ) ;
if ( err < 0 )
return err ;
2021-03-08 12:51:50 +03:00
clk_disable_unprepare ( tcbpwm - > slow_clk ) ;
clk_put ( tcbpwm - > slow_clk ) ;
clk_put ( tcbpwm - > clk ) ;
2013-01-08 19:36:42 +04:00
return 0 ;
}
static const struct of_device_id atmel_tcb_pwm_dt_ids [ ] = {
{ . compatible = " atmel,tcb-pwm " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , atmel_tcb_pwm_dt_ids ) ;
2017-10-19 19:44:10 +03:00
# ifdef CONFIG_PM_SLEEP
static int atmel_tcb_pwm_suspend ( struct device * dev )
{
2018-04-19 17:06:13 +03:00
struct atmel_tcb_pwm_chip * tcbpwm = dev_get_drvdata ( dev ) ;
2020-10-30 21:36:56 +03:00
struct atmel_tcb_channel * chan = & tcbpwm - > bkup ;
unsigned int channel = tcbpwm - > channel ;
2017-10-19 19:44:10 +03:00
2020-10-30 21:36:56 +03:00
regmap_read ( tcbpwm - > regmap , ATMEL_TC_REG ( channel , CMR ) , & chan - > cmr ) ;
regmap_read ( tcbpwm - > regmap , ATMEL_TC_REG ( channel , RA ) , & chan - > ra ) ;
regmap_read ( tcbpwm - > regmap , ATMEL_TC_REG ( channel , RB ) , & chan - > rb ) ;
regmap_read ( tcbpwm - > regmap , ATMEL_TC_REG ( channel , RC ) , & chan - > rc ) ;
2017-10-19 19:44:10 +03:00
return 0 ;
}
static int atmel_tcb_pwm_resume ( struct device * dev )
{
2018-04-19 17:06:13 +03:00
struct atmel_tcb_pwm_chip * tcbpwm = dev_get_drvdata ( dev ) ;
2020-10-30 21:36:56 +03:00
struct atmel_tcb_channel * chan = & tcbpwm - > bkup ;
unsigned int channel = tcbpwm - > channel ;
2017-10-19 19:44:10 +03:00
2020-10-30 21:36:56 +03:00
regmap_write ( tcbpwm - > regmap , ATMEL_TC_REG ( channel , CMR ) , chan - > cmr ) ;
regmap_write ( tcbpwm - > regmap , ATMEL_TC_REG ( channel , RA ) , chan - > ra ) ;
regmap_write ( tcbpwm - > regmap , ATMEL_TC_REG ( channel , RB ) , chan - > rb ) ;
regmap_write ( tcbpwm - > regmap , ATMEL_TC_REG ( channel , RC ) , chan - > rc ) ;
if ( chan - > enabled )
regmap_write ( tcbpwm - > regmap ,
ATMEL_TC_CLKEN | ATMEL_TC_SWTRG ,
ATMEL_TC_REG ( channel , CCR ) ) ;
2017-10-19 19:44:10 +03:00
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( atmel_tcb_pwm_pm_ops , atmel_tcb_pwm_suspend ,
atmel_tcb_pwm_resume ) ;
2013-01-08 19:36:42 +04:00
static struct platform_driver atmel_tcb_pwm_driver = {
. driver = {
. name = " atmel-tcb-pwm " ,
. of_match_table = atmel_tcb_pwm_dt_ids ,
2017-10-19 19:44:10 +03:00
. pm = & atmel_tcb_pwm_pm_ops ,
2013-01-08 19:36:42 +04:00
} ,
. probe = atmel_tcb_pwm_probe ,
. remove = atmel_tcb_pwm_remove ,
} ;
module_platform_driver ( atmel_tcb_pwm_driver ) ;
MODULE_AUTHOR ( " Boris BREZILLON <b.brezillon@overkiz.com> " ) ;
MODULE_DESCRIPTION ( " Atmel Timer Counter Pulse Width Modulation Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;