2019-05-28 19:57:07 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2013-04-06 04:40:36 +04:00
/*
* Copyright ( c ) 2007 Ben Dooks
* Copyright ( c ) 2008 Simtec Electronics
* Ben Dooks < ben @ simtec . co . uk > , < ben - linux @ fluff . org >
* Copyright ( c ) 2013 Tomasz Figa < tomasz . figa @ gmail . com >
2017-04-24 13:01:08 +03:00
* Copyright ( c ) 2017 Samsung Electronics Co . , Ltd .
2013-04-06 04:40:36 +04:00
*
* PWM driver for Samsung SoCs
*/
# include <linux/bitops.h>
# include <linux/clk.h>
# include <linux/export.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
2013-09-27 15:23:24 +04:00
# include <linux/of.h>
2013-04-06 04:40:36 +04:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/time.h>
/* For struct samsung_timer_variant and samsung_pwm_lock. */
# include <clocksource/samsung_pwm.h>
# define REG_TCFG0 0x00
# define REG_TCFG1 0x04
# define REG_TCON 0x08
# define REG_TCNTB(chan) (0x0c + ((chan) * 0xc))
# define REG_TCMPB(chan) (0x10 + ((chan) * 0xc))
# define TCFG0_PRESCALER_MASK 0xff
# define TCFG0_PRESCALER1_SHIFT 8
# define TCFG1_MUX_MASK 0xf
# define TCFG1_SHIFT(chan) (4 * (chan))
/*
* Each channel occupies 4 bits in TCON register , but there is a gap of 4
* bits ( one channel ) after channel 0 , so channels have different numbering
* when accessing TCON register . See to_tcon_channel ( ) function .
*
* In addition , the location of autoreload bit for channel 4 ( TCON channel 5 )
* in its set of bits is 2 as opposed to 3 for other channels .
*/
# define TCON_START(chan) BIT(4 * (chan) + 0)
# define TCON_MANUALUPDATE(chan) BIT(4 * (chan) + 1)
# define TCON_INVERT(chan) BIT(4 * (chan) + 2)
# define _TCON_AUTORELOAD(chan) BIT(4 * (chan) + 3)
# define _TCON_AUTORELOAD4(chan) BIT(4 * (chan) + 2)
# define TCON_AUTORELOAD(chan) \
( ( chan < 5 ) ? _TCON_AUTORELOAD ( chan ) : _TCON_AUTORELOAD4 ( chan ) )
/**
* struct samsung_pwm_channel - private data of PWM channel
* @ period_ns : current period in nanoseconds programmed to the hardware
* @ duty_ns : current duty time in nanoseconds programmed to the hardware
* @ tin_ns : time of one timer tick in nanoseconds with current timer rate
*/
struct samsung_pwm_channel {
u32 period_ns ;
u32 duty_ns ;
u32 tin_ns ;
} ;
/**
* struct samsung_pwm_chip - private data of PWM chip
* @ chip : generic PWM chip
* @ variant : local copy of hardware variant data
* @ inverter_mask : inverter status for all channels - one bit per channel
2017-04-24 13:01:08 +03:00
* @ disabled_mask : disabled status for all channels - one bit per channel
2013-04-06 04:40:36 +04:00
* @ base : base address of mapped PWM registers
* @ base_clk : base clock used to drive the timers
* @ tclk0 : external clock 0 ( can be ERR_PTR if not present )
* @ tclk1 : external clock 1 ( can be ERR_PTR if not present )
*/
struct samsung_pwm_chip {
struct pwm_chip chip ;
struct samsung_pwm_variant variant ;
u8 inverter_mask ;
2017-04-24 13:01:08 +03:00
u8 disabled_mask ;
2013-04-06 04:40:36 +04:00
void __iomem * base ;
struct clk * base_clk ;
struct clk * tclk0 ;
struct clk * tclk1 ;
} ;
# ifndef CONFIG_CLKSRC_SAMSUNG_PWM
/*
* PWM block is shared between pwm - samsung and samsung_pwm_timer drivers
* and some registers need access synchronization . If both drivers are
* compiled in , the spinlock is defined in the clocksource driver ,
* otherwise following definition is used .
*
* Currently we do not need any more complex synchronization method
* because all the supported SoCs contain only one instance of the PWM
* IP . Should this change , both drivers will need to be modified to
* properly synchronize accesses to particular instances .
*/
static DEFINE_SPINLOCK ( samsung_pwm_lock ) ;
# endif
static inline
struct samsung_pwm_chip * to_samsung_pwm_chip ( struct pwm_chip * chip )
{
return container_of ( chip , struct samsung_pwm_chip , chip ) ;
}
static inline unsigned int to_tcon_channel ( unsigned int channel )
{
/* TCON register has a gap of 4 bits (1 channel) after channel 0 */
return ( channel = = 0 ) ? 0 : ( channel + 1 ) ;
}
static void pwm_samsung_set_divisor ( struct samsung_pwm_chip * pwm ,
unsigned int channel , u8 divisor )
{
u8 shift = TCFG1_SHIFT ( channel ) ;
unsigned long flags ;
u32 reg ;
u8 bits ;
bits = ( fls ( divisor ) - 1 ) - pwm - > variant . div_base ;
spin_lock_irqsave ( & samsung_pwm_lock , flags ) ;
reg = readl ( pwm - > base + REG_TCFG1 ) ;
reg & = ~ ( TCFG1_MUX_MASK < < shift ) ;
reg | = bits < < shift ;
writel ( reg , pwm - > base + REG_TCFG1 ) ;
spin_unlock_irqrestore ( & samsung_pwm_lock , flags ) ;
}
static int pwm_samsung_is_tdiv ( struct samsung_pwm_chip * chip , unsigned int chan )
{
struct samsung_pwm_variant * variant = & chip - > variant ;
u32 reg ;
reg = readl ( chip - > base + REG_TCFG1 ) ;
reg > > = TCFG1_SHIFT ( chan ) ;
reg & = TCFG1_MUX_MASK ;
return ( BIT ( reg ) & variant - > tclk_mask ) = = 0 ;
}
static unsigned long pwm_samsung_get_tin_rate ( struct samsung_pwm_chip * chip ,
unsigned int chan )
{
unsigned long rate ;
u32 reg ;
rate = clk_get_rate ( chip - > base_clk ) ;
reg = readl ( chip - > base + REG_TCFG0 ) ;
if ( chan > = 2 )
reg > > = TCFG0_PRESCALER1_SHIFT ;
reg & = TCFG0_PRESCALER_MASK ;
return rate / ( reg + 1 ) ;
}
static unsigned long pwm_samsung_calc_tin ( struct samsung_pwm_chip * chip ,
unsigned int chan , unsigned long freq )
{
struct samsung_pwm_variant * variant = & chip - > variant ;
unsigned long rate ;
struct clk * clk ;
u8 div ;
if ( ! pwm_samsung_is_tdiv ( chip , chan ) ) {
clk = ( chan < 2 ) ? chip - > tclk0 : chip - > tclk1 ;
if ( ! IS_ERR ( clk ) ) {
rate = clk_get_rate ( clk ) ;
if ( rate )
return rate ;
}
dev_warn ( chip - > chip . dev ,
" tclk of PWM %d is inoperational, using tdiv \n " , chan ) ;
}
rate = pwm_samsung_get_tin_rate ( chip , chan ) ;
dev_dbg ( chip - > chip . dev , " tin parent at %lu \n " , rate ) ;
/*
* Compare minimum PWM frequency that can be achieved with possible
* divider settings and choose the lowest divisor that can generate
* frequencies lower than requested .
*/
2016-08-16 17:22:01 +03:00
if ( variant - > bits < 32 ) {
/* Only for s3c24xx */
for ( div = variant - > div_base ; div < 4 ; + + div )
if ( ( rate > > ( variant - > bits + div ) ) < freq )
break ;
} else {
/*
* Other variants have enough counter bits to generate any
* requested rate , so no need to check higher divisors .
*/
div = variant - > div_base ;
}
2013-04-06 04:40:36 +04:00
pwm_samsung_set_divisor ( chip , chan , BIT ( div ) ) ;
return rate > > div ;
}
static int pwm_samsung_request ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct samsung_pwm_chip * our_chip = to_samsung_pwm_chip ( chip ) ;
struct samsung_pwm_channel * our_chan ;
if ( ! ( our_chip - > variant . output_mask & BIT ( pwm - > hwpwm ) ) ) {
dev_warn ( chip - > dev ,
" tried to request PWM channel %d without output \n " ,
pwm - > hwpwm ) ;
return - EINVAL ;
}
2019-03-25 12:49:34 +03:00
our_chan = kzalloc ( sizeof ( * our_chan ) , GFP_KERNEL ) ;
2013-04-06 04:40:36 +04:00
if ( ! our_chan )
return - ENOMEM ;
pwm_set_chip_data ( pwm , our_chan ) ;
return 0 ;
}
static void pwm_samsung_free ( struct pwm_chip * chip , struct pwm_device * pwm )
{
2019-03-25 12:49:34 +03:00
kfree ( pwm_get_chip_data ( pwm ) ) ;
2013-04-06 04:40:36 +04:00
}
static int pwm_samsung_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct samsung_pwm_chip * our_chip = to_samsung_pwm_chip ( chip ) ;
unsigned int tcon_chan = to_tcon_channel ( pwm - > hwpwm ) ;
unsigned long flags ;
u32 tcon ;
spin_lock_irqsave ( & samsung_pwm_lock , flags ) ;
tcon = readl ( our_chip - > base + REG_TCON ) ;
tcon & = ~ TCON_START ( tcon_chan ) ;
tcon | = TCON_MANUALUPDATE ( tcon_chan ) ;
writel ( tcon , our_chip - > base + REG_TCON ) ;
tcon & = ~ TCON_MANUALUPDATE ( tcon_chan ) ;
tcon | = TCON_START ( tcon_chan ) | TCON_AUTORELOAD ( tcon_chan ) ;
writel ( tcon , our_chip - > base + REG_TCON ) ;
2017-04-24 13:01:08 +03:00
our_chip - > disabled_mask & = ~ BIT ( pwm - > hwpwm ) ;
2013-04-06 04:40:36 +04:00
spin_unlock_irqrestore ( & samsung_pwm_lock , flags ) ;
return 0 ;
}
static void pwm_samsung_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct samsung_pwm_chip * our_chip = to_samsung_pwm_chip ( chip ) ;
unsigned int tcon_chan = to_tcon_channel ( pwm - > hwpwm ) ;
unsigned long flags ;
u32 tcon ;
spin_lock_irqsave ( & samsung_pwm_lock , flags ) ;
tcon = readl ( our_chip - > base + REG_TCON ) ;
tcon & = ~ TCON_AUTORELOAD ( tcon_chan ) ;
writel ( tcon , our_chip - > base + REG_TCON ) ;
2017-04-24 13:01:08 +03:00
our_chip - > disabled_mask | = BIT ( pwm - > hwpwm ) ;
2013-04-06 04:40:36 +04:00
spin_unlock_irqrestore ( & samsung_pwm_lock , flags ) ;
}
pwm: samsung: Fix output race on disabling
When disabling the Samsung PWM the output state remains at the level it
was at the end of a PWM cycle. In other words, calling pwm_disable()
when at 100% duty cycle will keep the output active, while at all other
settings the output will go/stay inactive. On top of that the Samsung
PWM settings are double-buffered, which means the new settings only get
applied at the start of a new PWM cycle.
This results in a race if the PWM is at 100% duty cycle and a driver
calls:
pwm_config(pwm, 0, period);
pwm_disable(pwm);
In this case the PWMs output will unexpectedly stay active, unless a new
PWM cycle happened to start between the register writes in pwm_config()
and pwm_disable(). As far as I can tell this is a regression introduced
by 3bdf878, before that a call to pwm_config() would call
pwm_samsung_enable() which, while heavy-handed, made sure the expected
settings were live.
To resolve this, while not re-introducing the issues 3bdf878 (flickering
as the PWM got reset while in a PWM cycle) fixed, only force an update
of the settings when at 100% duty cycle, which shouldn't have any
noticeable effect on the output but is enough to ensure the behaviour is
as expected on disable.
Signed-off-by: Sjoerd Simons <sjoerd.simons@collabora.co.uk>
Reviewed-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
2015-03-05 11:14:03 +03:00
static void pwm_samsung_manual_update ( struct samsung_pwm_chip * chip ,
struct pwm_device * pwm )
{
unsigned int tcon_chan = to_tcon_channel ( pwm - > hwpwm ) ;
u32 tcon ;
unsigned long flags ;
spin_lock_irqsave ( & samsung_pwm_lock , flags ) ;
tcon = readl ( chip - > base + REG_TCON ) ;
tcon | = TCON_MANUALUPDATE ( tcon_chan ) ;
writel ( tcon , chip - > base + REG_TCON ) ;
tcon & = ~ TCON_MANUALUPDATE ( tcon_chan ) ;
writel ( tcon , chip - > base + REG_TCON ) ;
spin_unlock_irqrestore ( & samsung_pwm_lock , flags ) ;
}
2017-04-24 13:01:08 +03:00
static int __pwm_samsung_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns , bool force_period )
2013-04-06 04:40:36 +04:00
{
struct samsung_pwm_chip * our_chip = to_samsung_pwm_chip ( chip ) ;
struct samsung_pwm_channel * chan = pwm_get_chip_data ( pwm ) ;
pwm: samsung: Fix output race on disabling
When disabling the Samsung PWM the output state remains at the level it
was at the end of a PWM cycle. In other words, calling pwm_disable()
when at 100% duty cycle will keep the output active, while at all other
settings the output will go/stay inactive. On top of that the Samsung
PWM settings are double-buffered, which means the new settings only get
applied at the start of a new PWM cycle.
This results in a race if the PWM is at 100% duty cycle and a driver
calls:
pwm_config(pwm, 0, period);
pwm_disable(pwm);
In this case the PWMs output will unexpectedly stay active, unless a new
PWM cycle happened to start between the register writes in pwm_config()
and pwm_disable(). As far as I can tell this is a regression introduced
by 3bdf878, before that a call to pwm_config() would call
pwm_samsung_enable() which, while heavy-handed, made sure the expected
settings were live.
To resolve this, while not re-introducing the issues 3bdf878 (flickering
as the PWM got reset while in a PWM cycle) fixed, only force an update
of the settings when at 100% duty cycle, which shouldn't have any
noticeable effect on the output but is enough to ensure the behaviour is
as expected on disable.
Signed-off-by: Sjoerd Simons <sjoerd.simons@collabora.co.uk>
Reviewed-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
2015-03-05 11:14:03 +03:00
u32 tin_ns = chan - > tin_ns , tcnt , tcmp , oldtcmp ;
2013-04-06 04:40:36 +04:00
/*
* We currently avoid using 64 bit arithmetic by using the
* fact that anything faster than 1 Hz is easily representable
* by 32 bits .
*/
if ( period_ns > NSEC_PER_SEC )
return - ERANGE ;
tcnt = readl ( our_chip - > base + REG_TCNTB ( pwm - > hwpwm ) ) ;
pwm: samsung: Fix output race on disabling
When disabling the Samsung PWM the output state remains at the level it
was at the end of a PWM cycle. In other words, calling pwm_disable()
when at 100% duty cycle will keep the output active, while at all other
settings the output will go/stay inactive. On top of that the Samsung
PWM settings are double-buffered, which means the new settings only get
applied at the start of a new PWM cycle.
This results in a race if the PWM is at 100% duty cycle and a driver
calls:
pwm_config(pwm, 0, period);
pwm_disable(pwm);
In this case the PWMs output will unexpectedly stay active, unless a new
PWM cycle happened to start between the register writes in pwm_config()
and pwm_disable(). As far as I can tell this is a regression introduced
by 3bdf878, before that a call to pwm_config() would call
pwm_samsung_enable() which, while heavy-handed, made sure the expected
settings were live.
To resolve this, while not re-introducing the issues 3bdf878 (flickering
as the PWM got reset while in a PWM cycle) fixed, only force an update
of the settings when at 100% duty cycle, which shouldn't have any
noticeable effect on the output but is enough to ensure the behaviour is
as expected on disable.
Signed-off-by: Sjoerd Simons <sjoerd.simons@collabora.co.uk>
Reviewed-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
2015-03-05 11:14:03 +03:00
oldtcmp = readl ( our_chip - > base + REG_TCMPB ( pwm - > hwpwm ) ) ;
2013-04-06 04:40:36 +04:00
/* We need tick count for calculation, not last tick. */
+ + tcnt ;
/* Check to see if we are changing the clock rate of the PWM. */
2017-04-24 13:01:08 +03:00
if ( chan - > period_ns ! = period_ns | | force_period ) {
2013-04-06 04:40:36 +04:00
unsigned long tin_rate ;
u32 period ;
period = NSEC_PER_SEC / period_ns ;
dev_dbg ( our_chip - > chip . dev , " duty_ns=%d, period_ns=%d (%u) \n " ,
duty_ns , period_ns , period ) ;
tin_rate = pwm_samsung_calc_tin ( our_chip , pwm - > hwpwm , period ) ;
dev_dbg ( our_chip - > chip . dev , " tin_rate=%lu \n " , tin_rate ) ;
tin_ns = NSEC_PER_SEC / tin_rate ;
tcnt = period_ns / tin_ns ;
}
/* Period is too short. */
if ( tcnt < = 1 )
return - ERANGE ;
/* Note that counters count down. */
tcmp = duty_ns / tin_ns ;
/* 0% duty is not available */
if ( ! tcmp )
+ + tcmp ;
tcmp = tcnt - tcmp ;
/* Decrement to get tick numbers, instead of tick counts. */
- - tcnt ;
/* -1UL will give 100% duty. */
- - tcmp ;
dev_dbg ( our_chip - > chip . dev ,
" tin_ns=%u, tcmp=%u/%u \n " , tin_ns , tcmp , tcnt ) ;
/* Update PWM registers. */
writel ( tcnt , our_chip - > base + REG_TCNTB ( pwm - > hwpwm ) ) ;
writel ( tcmp , our_chip - > base + REG_TCMPB ( pwm - > hwpwm ) ) ;
pwm: samsung: Fix output race on disabling
When disabling the Samsung PWM the output state remains at the level it
was at the end of a PWM cycle. In other words, calling pwm_disable()
when at 100% duty cycle will keep the output active, while at all other
settings the output will go/stay inactive. On top of that the Samsung
PWM settings are double-buffered, which means the new settings only get
applied at the start of a new PWM cycle.
This results in a race if the PWM is at 100% duty cycle and a driver
calls:
pwm_config(pwm, 0, period);
pwm_disable(pwm);
In this case the PWMs output will unexpectedly stay active, unless a new
PWM cycle happened to start between the register writes in pwm_config()
and pwm_disable(). As far as I can tell this is a regression introduced
by 3bdf878, before that a call to pwm_config() would call
pwm_samsung_enable() which, while heavy-handed, made sure the expected
settings were live.
To resolve this, while not re-introducing the issues 3bdf878 (flickering
as the PWM got reset while in a PWM cycle) fixed, only force an update
of the settings when at 100% duty cycle, which shouldn't have any
noticeable effect on the output but is enough to ensure the behaviour is
as expected on disable.
Signed-off-by: Sjoerd Simons <sjoerd.simons@collabora.co.uk>
Reviewed-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
2015-03-05 11:14:03 +03:00
/*
* In case the PWM is currently at 100 % duty cycle , force a manual
* update to prevent the signal staying high if the PWM is disabled
* shortly afer this update ( before it autoreloaded the new values ) .
*/
if ( oldtcmp = = ( u32 ) - 1 ) {
dev_dbg ( our_chip - > chip . dev , " Forcing manual update " ) ;
pwm_samsung_manual_update ( our_chip , pwm ) ;
}
2013-04-06 04:40:36 +04:00
chan - > period_ns = period_ns ;
chan - > tin_ns = tin_ns ;
chan - > duty_ns = duty_ns ;
return 0 ;
}
2017-04-24 13:01:08 +03:00
static int pwm_samsung_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
{
return __pwm_samsung_config ( chip , pwm , duty_ns , period_ns , false ) ;
}
2013-04-06 04:40:36 +04:00
static void pwm_samsung_set_invert ( struct samsung_pwm_chip * chip ,
unsigned int channel , bool invert )
{
unsigned int tcon_chan = to_tcon_channel ( channel ) ;
unsigned long flags ;
u32 tcon ;
spin_lock_irqsave ( & samsung_pwm_lock , flags ) ;
tcon = readl ( chip - > base + REG_TCON ) ;
if ( invert ) {
chip - > inverter_mask | = BIT ( channel ) ;
tcon | = TCON_INVERT ( tcon_chan ) ;
} else {
chip - > inverter_mask & = ~ BIT ( channel ) ;
tcon & = ~ TCON_INVERT ( tcon_chan ) ;
}
writel ( tcon , chip - > base + REG_TCON ) ;
spin_unlock_irqrestore ( & samsung_pwm_lock , flags ) ;
}
static int pwm_samsung_set_polarity ( struct pwm_chip * chip ,
struct pwm_device * pwm ,
enum pwm_polarity polarity )
{
struct samsung_pwm_chip * our_chip = to_samsung_pwm_chip ( chip ) ;
bool invert = ( polarity = = PWM_POLARITY_NORMAL ) ;
/* Inverted means normal in the hardware. */
pwm_samsung_set_invert ( our_chip , pwm - > hwpwm , invert ) ;
return 0 ;
}
static const struct pwm_ops pwm_samsung_ops = {
. request = pwm_samsung_request ,
. free = pwm_samsung_free ,
. enable = pwm_samsung_enable ,
. disable = pwm_samsung_disable ,
. config = pwm_samsung_config ,
. set_polarity = pwm_samsung_set_polarity ,
. owner = THIS_MODULE ,
} ;
# ifdef CONFIG_OF
static const struct samsung_pwm_variant s3c24xx_variant = {
. bits = 16 ,
. div_base = 1 ,
. has_tint_cstat = false ,
. tclk_mask = BIT ( 4 ) ,
} ;
static const struct samsung_pwm_variant s3c64xx_variant = {
. bits = 32 ,
. div_base = 0 ,
. has_tint_cstat = true ,
. tclk_mask = BIT ( 7 ) | BIT ( 6 ) | BIT ( 5 ) ,
} ;
static const struct samsung_pwm_variant s5p64x0_variant = {
. bits = 32 ,
. div_base = 0 ,
. has_tint_cstat = true ,
. tclk_mask = 0 ,
} ;
static const struct samsung_pwm_variant s5pc100_variant = {
. bits = 32 ,
. div_base = 0 ,
. has_tint_cstat = true ,
. tclk_mask = BIT ( 5 ) ,
} ;
static const struct of_device_id samsung_pwm_matches [ ] = {
{ . compatible = " samsung,s3c2410-pwm " , . data = & s3c24xx_variant } ,
{ . compatible = " samsung,s3c6400-pwm " , . data = & s3c64xx_variant } ,
{ . compatible = " samsung,s5p6440-pwm " , . data = & s5p64x0_variant } ,
{ . compatible = " samsung,s5pc100-pwm " , . data = & s5pc100_variant } ,
{ . compatible = " samsung,exynos4210-pwm " , . data = & s5p64x0_variant } ,
{ } ,
} ;
2015-05-14 03:32:31 +03:00
MODULE_DEVICE_TABLE ( of , samsung_pwm_matches ) ;
2013-04-06 04:40:36 +04:00
static int pwm_samsung_parse_dt ( struct samsung_pwm_chip * chip )
{
struct device_node * np = chip - > chip . dev - > of_node ;
const struct of_device_id * match ;
struct property * prop ;
const __be32 * cur ;
u32 val ;
match = of_match_node ( samsung_pwm_matches , np ) ;
if ( ! match )
return - ENODEV ;
memcpy ( & chip - > variant , match - > data , sizeof ( chip - > variant ) ) ;
of_property_for_each_u32 ( np , " samsung,pwm-outputs " , prop , cur , val ) {
if ( val > = SAMSUNG_PWM_NUM ) {
dev_err ( chip - > chip . dev ,
" %s: invalid channel index in samsung,pwm-outputs property \n " ,
__func__ ) ;
continue ;
}
chip - > variant . output_mask | = BIT ( val ) ;
}
return 0 ;
}
# else
static int pwm_samsung_parse_dt ( struct samsung_pwm_chip * chip )
{
return - ENODEV ;
}
# endif
static int pwm_samsung_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct samsung_pwm_chip * chip ;
unsigned int chan ;
int ret ;
chip = devm_kzalloc ( & pdev - > dev , sizeof ( * chip ) , GFP_KERNEL ) ;
if ( chip = = NULL )
return - ENOMEM ;
chip - > chip . dev = & pdev - > dev ;
chip - > chip . ops = & pwm_samsung_ops ;
chip - > chip . npwm = SAMSUNG_PWM_NUM ;
chip - > inverter_mask = BIT ( SAMSUNG_PWM_NUM ) - 1 ;
if ( IS_ENABLED ( CONFIG_OF ) & & pdev - > dev . of_node ) {
ret = pwm_samsung_parse_dt ( chip ) ;
if ( ret )
return ret ;
} else {
if ( ! pdev - > dev . platform_data ) {
dev_err ( & pdev - > dev , " no platform data specified \n " ) ;
return - EINVAL ;
}
memcpy ( & chip - > variant , pdev - > dev . platform_data ,
sizeof ( chip - > variant ) ) ;
}
2019-12-29 11:05:55 +03:00
chip - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2013-04-06 04:40:36 +04:00
if ( IS_ERR ( chip - > base ) )
return PTR_ERR ( chip - > base ) ;
chip - > base_clk = devm_clk_get ( & pdev - > dev , " timers " ) ;
if ( IS_ERR ( chip - > base_clk ) ) {
dev_err ( dev , " failed to get timer base clk \n " ) ;
return PTR_ERR ( chip - > base_clk ) ;
}
ret = clk_prepare_enable ( chip - > base_clk ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to enable base clock \n " ) ;
return ret ;
}
for ( chan = 0 ; chan < SAMSUNG_PWM_NUM ; + + chan )
if ( chip - > variant . output_mask & BIT ( chan ) )
pwm_samsung_set_invert ( chip , chan , true ) ;
/* Following clocks are optional. */
chip - > tclk0 = devm_clk_get ( & pdev - > dev , " pwm-tclk0 " ) ;
chip - > tclk1 = devm_clk_get ( & pdev - > dev , " pwm-tclk1 " ) ;
platform_set_drvdata ( pdev , chip ) ;
ret = pwmchip_add ( & chip - > chip ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to register PWM chip \n " ) ;
clk_disable_unprepare ( chip - > base_clk ) ;
return ret ;
}
dev_dbg ( dev , " base_clk at %lu, tclk0 at %lu, tclk1 at %lu \n " ,
clk_get_rate ( chip - > base_clk ) ,
! IS_ERR ( chip - > tclk0 ) ? clk_get_rate ( chip - > tclk0 ) : 0 ,
! IS_ERR ( chip - > tclk1 ) ? clk_get_rate ( chip - > tclk1 ) : 0 ) ;
return 0 ;
}
static int pwm_samsung_remove ( struct platform_device * pdev )
{
struct samsung_pwm_chip * chip = platform_get_drvdata ( pdev ) ;
int ret ;
ret = pwmchip_remove ( & chip - > chip ) ;
if ( ret < 0 )
return ret ;
clk_disable_unprepare ( chip - > base_clk ) ;
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
2017-04-24 13:01:08 +03:00
static int pwm_samsung_resume ( struct device * dev )
2013-04-06 04:40:36 +04:00
{
2017-04-24 13:01:08 +03:00
struct samsung_pwm_chip * our_chip = dev_get_drvdata ( dev ) ;
struct pwm_chip * chip = & our_chip - > chip ;
2013-04-06 04:40:36 +04:00
unsigned int i ;
2017-04-24 13:01:08 +03:00
for ( i = 0 ; i < SAMSUNG_PWM_NUM ; i + + ) {
struct pwm_device * pwm = & chip - > pwms [ i ] ;
2013-04-06 04:40:36 +04:00
struct samsung_pwm_channel * chan = pwm_get_chip_data ( pwm ) ;
if ( ! chan )
continue ;
2017-04-24 13:01:08 +03:00
if ( our_chip - > variant . output_mask & BIT ( i ) )
pwm_samsung_set_invert ( our_chip , i ,
our_chip - > inverter_mask & BIT ( i ) ) ;
2013-04-06 04:40:36 +04:00
2017-04-24 13:01:08 +03:00
if ( chan - > period_ns ) {
__pwm_samsung_config ( chip , pwm , chan - > duty_ns ,
chan - > period_ns , true ) ;
/* needed to make PWM disable work on Odroid-XU3 */
pwm_samsung_manual_update ( our_chip , pwm ) ;
}
2013-04-06 04:40:36 +04:00
2017-04-24 13:01:08 +03:00
if ( our_chip - > disabled_mask & BIT ( i ) )
pwm_samsung_disable ( chip , pwm ) ;
else
pwm_samsung_enable ( chip , pwm ) ;
2013-04-06 04:40:36 +04:00
}
return 0 ;
}
# endif
2017-04-24 13:01:08 +03:00
static SIMPLE_DEV_PM_OPS ( pwm_samsung_pm_ops , NULL , pwm_samsung_resume ) ;
2013-04-06 04:40:36 +04:00
static struct platform_driver pwm_samsung_driver = {
. driver = {
. name = " samsung-pwm " ,
. pm = & pwm_samsung_pm_ops ,
. of_match_table = of_match_ptr ( samsung_pwm_matches ) ,
} ,
. probe = pwm_samsung_probe ,
. remove = pwm_samsung_remove ,
} ;
module_platform_driver ( pwm_samsung_driver ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Tomasz Figa <tomasz.figa@gmail.com> " ) ;
MODULE_ALIAS ( " platform:samsung-pwm " ) ;